diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 227fe17c638..00000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,82 +0,0 @@ -version: 2.1 -jobs: - - lint: - docker: - - image: cimg/node:16.20.2 - - working_directory: ~/repo - - steps: - - checkout - - - restore_cache: - key: node_modules-{{ checksum "package-lock.json" }} - - - run: test -d node_modules || npm i - - - save_cache: - key: node_modules-{{ checksum "package-lock.json" }} - paths: - - node_modules - - # run tests! - - run: - command: npm run tslint && npm run lint - - unit: - docker: - - image: cimg/node:16.20.2 - - working_directory: ~/repo - - steps: - - checkout - - - restore_cache: - key: node_modules-{{ checksum "package-lock.json" }} - - - run: test -d node_modules || npm i - - - save_cache: - key: node_modules-{{ checksum "package-lock.json" }} - paths: - - node_modules - - # run tests! - - run: - command: npm run unit - - - integration: - docker: - - image: cimg/node:16.20.2 - - working_directory: ~/repo - - resource_class: large - - steps: - - checkout - - - restore_cache: - key: node_modules-{{ checksum "package-lock.json" }} - - - run: test -d node_modules || npm i - - - save_cache: - key: node_modules-{{ checksum "package-lock.json" }} - paths: - - node_modules - - # run tests! - - run: - command: npm run jest || npm run jest || npm run jest - -# Orchestrate our job run sequence -workflows: - build_and_test: - jobs: - - lint - - unit - - integration diff --git a/.detoxrc.json b/.detoxrc.json index 5adda885c91..bcb9033d8f7 100644 --- a/.detoxrc.json +++ b/.detoxrc.json @@ -7,10 +7,15 @@ } }, "apps": { - "ios": { + "ios.debug": { "type": "ios.app", - "binaryPath": "SPECIFY_PATH_TO_YOUR_APP_BINARY", - "build": "xcodebuild clean build -workspace ios/BlueWallet.xcworkspace -scheme BlueWallet -configuration Release -derivedDataPath ios/build -sdk iphonesimulator13.2" + "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/BlueWallet.app", + "build": "xcodebuild -workspace ios/BlueWallet.xcworkspace -scheme BlueWallet -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build" + }, + "ios.release": { + "type": "ios.app", + "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/BlueWallet.app", + "build": "npx react-native codegen && xcodebuild -workspace ios/BlueWallet.xcworkspace -scheme BlueWallet -configuration Release -sdk iphonesimulator -derivedDataPath ios/build" }, "android.debug": { "type": "android.apk", @@ -21,14 +26,14 @@ "type": "android.apk", "testBinaryPath": "android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk", "binaryPath": "android/app/build/outputs/apk/release/app-release.apk", - "build": "find android | grep '\\.apk' --color=never | xargs -l rm\n\n# creating fresh keystore\nrm detox.keystore\nkeytool -genkeypair -v -keystore detox.keystore -alias detox -keyalg RSA -keysize 2048 -validity 10000 -storepass 123456 -keypass 123456 -dname 'cn=Unknown, ou=Unknown, o=Unknown, c=Unknown'\n\n# building release APK\ncd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ..\n\n# wip\nfind $ANDROID_HOME | grep apksigner\n\n# signing\nmv ./android/app/build/outputs/apk/release/app-release-unsigned.apk ./android/app/build/outputs/apk/release/app-release.apk\n$ANDROID_HOME/build-tools/30.0.2/apksigner sign --ks detox.keystore --ks-pass=pass:123456 ./android/app/build/outputs/apk/release/app-release.apk\n$ANDROID_HOME/build-tools/30.0.2/apksigner sign --ks detox.keystore --ks-pass=pass:123456 ./android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk" + "build": "./tests/e2e/detox-build-release-apk.sh" } }, "devices": { "simulator": { "type": "ios.simulator", "device": { - "type": "iPhone 11" + "type": "iPhone 17" } }, "emulator": { @@ -39,14 +44,36 @@ } }, "configurations": { - "ios": { + "ios.debug": { "device": "simulator", - "app": "ios" + "app": "ios.debug" + }, + "ios.release": { + "device": "simulator", + "app": "ios.release" }, "android.debug": { "device": "emulator", "app": "android.debug" }, + "android.debug.device": { + "device": { + "device": { + "adbName": ".*" + }, + "type": "android.attached" + }, + "app": "android.debug" + }, + "android.release.device": { + "device": { + "device": { + "adbName": ".*" + }, + "type": "android.attached" + }, + "app": "android.release" + }, "android.release": { "device": "emulator", "app": "android.release" diff --git a/.eslintrc b/.eslintrc index 7bc38aa980f..50c030ebda5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,7 +2,7 @@ "parser": "@typescript-eslint/parser", "plugins": [ "@typescript-eslint", - "react-native" // for no-inline-styles rule + "react-native", // for no-inline-styles rule ], "extends": [ "standard", @@ -11,7 +11,7 @@ "plugin:react-hooks/recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", - "@react-native-community", + "@react-native", "plugin:prettier/recommended" // removes all eslint rules that can mess up with prettier ], "rules": { @@ -19,7 +19,12 @@ "react/display-name": "off", "react-native/no-inline-styles": "error", "react-native/no-unused-styles": "error", + "react/no-is-mounted": "off", "react-native/no-single-element-style-arrays": "error", + "react-hooks/refs": "off", + "react-hooks/immutability": "off", + "react-hooks/purity": "off", + "react-hooks/set-state-in-effect": "off", "prettier/prettier": [ "warn", { @@ -49,7 +54,7 @@ "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-var-requires": "off", "@typescript-eslint/no-this-alias": "off", - "@typescript-eslint/no-use-before-define": "off" + "@typescript-eslint/no-use-before-define": "off", }, "overrides": [ { diff --git a/.github/ISSUE_TEMPLATE/issue-template.md b/.github/ISSUE_TEMPLATE/issue-template.md index d89b00f945a..c531098b920 100644 --- a/.github/ISSUE_TEMPLATE/issue-template.md +++ b/.github/ISSUE_TEMPLATE/issue-template.md @@ -16,7 +16,7 @@ Please provide: * your phone model and OS version * BlueWallet app version (settings->about->scroll down) * self-test passes? Open settings->about->scroll down, tap "Run self-test" -* unique ID for our crash reporting service (settings->about->scroll down, tap "copy") +* unique ID for our crash reporting service (option 1: settings->about->scroll down, tap "copy")(option 2: open the settings app->apps->BlueWallet,double tap the unique id text field and select copy) ## Proposing a feature? diff --git a/.github/workflows/build-ios-release-pullrequest.yml b/.github/workflows/build-ios-release-pullrequest.yml new file mode 100644 index 00000000000..3d407a98a75 --- /dev/null +++ b/.github/workflows/build-ios-release-pullrequest.yml @@ -0,0 +1,566 @@ +name: Build Release and Upload to TestFlight (iOS) + +on: + push: + branches: + - master + pull_request: + types: [opened, reopened, synchronize, labeled] + branches: + - master + workflow_dispatch: + +concurrency: + group: build-ios-release-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: macos-26 + timeout-minutes: 180 + outputs: + new_build_number: ${{ steps.generate_build_number.outputs.build_number }} + project_version: ${{ steps.determine_marketing_version.outputs.project_version }} + ipa_output_path: ${{ steps.build_app.outputs.ipa_output_path }} + latest_commit_message: ${{ steps.get_latest_commit_details.outputs.commit_message }} + branch_name: ${{ steps.get_latest_commit_details.outputs.branch_name }} + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + MATCH_READONLY: "true" + + steps: + - name: Checkout Project + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 # Ensures the full Git history is + + - name: Ensure Correct Branch + if: github.ref != 'refs/heads/master' + run: | + if [ -n "${GITHUB_HEAD_REF}" ]; then + git fetch origin ${GITHUB_HEAD_REF}:${GITHUB_HEAD_REF} + git checkout ${GITHUB_HEAD_REF} + else + git fetch origin ${GITHUB_REF##*/}:${GITHUB_REF##*/} + git checkout ${GITHUB_REF##*/} + fi + echo "Checked out branch: $(git rev-parse --abbrev-ref HEAD)" + + - name: Get Latest Commit Details + id: get_latest_commit_details + run: | + # Check if we are in a detached HEAD state + if [ "$(git rev-parse --abbrev-ref HEAD)" == "HEAD" ]; then + CURRENT_BRANCH=$(git show-ref --head -s HEAD | xargs -I {} git branch --contains {} | grep -v "detached" | head -n 1 | sed 's/^[* ]*//') + else + CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) + fi + + LATEST_COMMIT_MESSAGE=$(git log -1 --pretty=format:"%s") + + echo "CURRENT_BRANCH=${CURRENT_BRANCH}" >> $GITHUB_ENV + echo "LATEST_COMMIT_MESSAGE=${LATEST_COMMIT_MESSAGE}" >> $GITHUB_ENV + echo "branch_name=${CURRENT_BRANCH}" >> $GITHUB_OUTPUT + echo "commit_message=${LATEST_COMMIT_MESSAGE}" >> $GITHUB_OUTPUT + + - name: Print Commit Details + run: | + echo "Commit Message: ${{ env.LATEST_COMMIT_MESSAGE }}" + echo "Branch Name: ${{ env.CURRENT_BRANCH }}" + + - name: Specify Node.js Version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + cache: 'npm' + + - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 + with: + xcode-version: latest + + - name: Setup Xcode Path + run: | + echo -e "\033[1;34m==================== XCODE SETUP DEBUG ====================\033[0m" + echo -e "\033[1;36mSystem Information:\033[0m" + echo " macOS Version: $(sw_vers -productVersion)" + echo " macOS Build: $(sw_vers -buildVersion)" + echo " Architecture: $(uname -m)" + + echo -e "\033[1;36mAvailable Xcode Installations:\033[0m" + find /Applications -name "Xcode*.app" -type d 2>/dev/null | while read -r xcode_path; do + if [ -d "$xcode_path/Contents/Developer" ]; then + echo " Found: $xcode_path" + version=$("$xcode_path/Contents/Developer/usr/bin/xcodebuild" -version 2>/dev/null | head -1 || echo "Version unavailable") + echo " Version: $version" + fi + done + + echo -e "\033[1;36mCurrent Xcode Configuration:\033[0m" + echo " Before xcode-select: $(xcode-select -p 2>/dev/null || echo 'Not set')" + + # Ensure we're using the correct Xcode installation + sudo xcode-select -s /Applications/Xcode.app + + echo " After xcode-select: $(xcode-select -p)" + echo " Xcode Version: $(xcodebuild -version)" + echo " Xcode Build Version: $(xcodebuild -version | tail -1)" + + echo -e "\033[1;36mSDK Information:\033[0m" + echo " Available SDKs:" + xcodebuild -showsdks | grep -E "(iOS|watchOS|macOS)" | head -10 + + echo -e "\033[1;34m========================================================\033[0m" + + - name: Install Simulator Runtimes (iOS & watchOS) + run: | + echo -e "\033[1;34m============ SIMULATOR RUNTIME SETUP DEBUG ============\033[0m" + echo -e "\033[1;33mNote: Installing runtimes for debugging purposes - release builds use physical device targets\033[0m" + echo -e "\033[1;36mInitial Runtime Analysis:\033[0m" + echo " All available runtimes:" + xcrun simctl list runtimes | cat -n + + echo -e "\033[1;36mRuntime Summary:\033[0m" + echo " iOS Runtimes:" + xcrun simctl list runtimes | grep "iOS" | while read -r line; do + echo " $line" + done + + echo " watchOS Runtimes:" + xcrun simctl list runtimes | grep "watchOS" | while read -r line; do + echo " $line" + done + + echo -e "\033[1;33mChecking iOS Runtime Requirements (17+):\033[0m" + if ! xcrun simctl list runtimes | grep -Eq "iOS (1[7-9]|[2-9][0-9])"; then + echo -e "\033[1;31m No iOS 17+ runtime found - installing...\033[0m" + echo " Downloading iOS platform..." + xcodebuild -downloadPlatform iOS + echo -e "\033[1;32m iOS platform download completed\033[0m" + else + echo -e "\033[1;32m iOS 17+ runtime already present\033[0m" + xcrun simctl list runtimes | grep -E "iOS (1[7-9]|[2-9][0-9])" | while read -r line; do + echo " Found: $line" + done + fi + + echo -e "\033[1;33mChecking watchOS Runtime Requirements (11+):\033[0m" + if ! xcrun simctl list runtimes | grep -Eq "watchOS (1[1-9]|[2-9][0-9])"; then + echo -e "\033[1;31m No watchOS 11+ runtime found - installing...\033[0m" + echo " Downloading watchOS platform..." + xcodebuild -downloadPlatform watchOS + echo -e "\033[1;32m watchOS platform download completed\033[0m" + else + echo -e "\033[1;32m watchOS 11+ runtime already present\033[0m" + xcrun simctl list runtimes | grep -E "watchOS (1[1-9]|[2-9][0-9])" | while read -r line; do + echo " Found: $line" + done + fi + + echo -e "\033[1;36mFinal Runtime Analysis:\033[0m" + echo " All runtimes after installation:" + xcrun simctl list runtimes | cat -n + + echo -e "\033[1;36mDevice Analysis & Setup:\033[0m" + echo " Current iOS devices:" + xcrun simctl list devices iOS | cat -n + + echo -e "\033[1;33mChecking for available iPhone simulators:\033[0m" + IPHONE_COUNT=$(xcrun simctl list devices iOS | grep -c "iPhone" || echo "0") + echo " iPhone simulators found: $IPHONE_COUNT" + + if [ "$IPHONE_COUNT" -eq "0" ]; then + echo -e "\033[1;31m No iPhone simulators found - creating one...\033[0m" + + # Get the latest iOS runtime available + echo " Finding latest iOS runtime:" + LATEST_IOS=$(xcrun simctl list runtimes | grep "iOS" | tail -1 | sed 's/.*iOS \([0-9.]*\).*/\1/') + echo " Latest iOS version detected: $LATEST_IOS" + + if [ -n "$LATEST_IOS" ]; then + RUNTIME_ID="com.apple.CoreSimulator.SimRuntime.iOS-${LATEST_IOS//./-}" + DEVICE_TYPE="com.apple.CoreSimulator.SimDeviceType.iPhone-15" + + echo " Creating iPhone 15 simulator:" + echo " Device Type: $DEVICE_TYPE" + echo " Runtime ID: $RUNTIME_ID" + + if xcrun simctl create "iPhone 15" "$DEVICE_TYPE" "$RUNTIME_ID"; then + echo -e "\033[1;32m Successfully created iPhone 15 with iOS $LATEST_IOS\033[0m" + else + echo -e "\033[1;33m Failed to create iPhone 15, trying iPhone 14...\033[0m" + xcrun simctl create "iPhone 14" "com.apple.CoreSimulator.SimDeviceType.iPhone-14" "$RUNTIME_ID" || echo -e "\033[1;31m Failed to create any iPhone simulator\033[0m" + fi + else + echo -e "\033[1;31m No iOS runtime available for device creation\033[0m" + fi + else + echo -e "\033[1;32m iPhone simulators already available\033[0m" + fi + + echo -e "\033[1;36mFinal Device Status:\033[0m" + echo " All iOS devices:" + xcrun simctl list devices iOS | cat -n + + echo -e "\033[1;36mDevice Type Analysis:\033[0m" + echo " Available device types:" + xcrun simctl list devicetypes | grep -i iphone | head -5 + + echo -e "\033[1;34m======================================================\033[0m" + + - name: Set Up Ruby + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 + with: + ruby-version: 3.4.9 + + - name: System Debug Information + run: | + echo -e "\033[1;34m================ SYSTEM DEBUG INFORMATION ================\033[0m" + echo -e "\033[1;36mSystem Overview:\033[0m" + echo " Hostname: $(hostname)" + echo " User: $(whoami)" + echo " Home: $HOME" + echo " Shell: $SHELL" + echo " PATH (first 5 entries):" + echo "$PATH" | tr ':' '\n' | head -5 | sed 's/^/ /' + + echo -e "\033[1;36mDisk Space:\033[0m" + df -h / | tail -1 | awk '{print " Available: " $4 " (" $5 " used)"}' + + echo -e "\033[1;36mMemory:\033[0m" + vm_stat | grep "Pages free" | awk '{print " Free Pages: " $3}' + + echo -e "\033[1;36mPackage Managers:\033[0m" + echo " Bundle version: $(bundle --version 2>/dev/null || echo 'Not available')" + echo " NPM version: $(npm --version 2>/dev/null || echo 'Not available')" + echo " CocoaPods version: $(pod --version 2>/dev/null || echo 'Not available')" + + echo -e "\033[1;36mEnvironment Variables (Build-related):\033[0m" + env | grep -E "(GITHUB_|CI|RUNNER_|PROJECT_|NEW_BUILD)" | sort | head -10 + + echo -e "\033[1;34m========================================================\033[0m" + + - name: Install Dependencies with Bundler + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 --quiet + + - name: Install Node Modules + run: npm ci --omit=dev --yes + + - name: Cache CocoaPods + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ios/Pods + ~/Library/Caches/CocoaPods + ~/.cocoapods/repos + key: ${{ runner.os }}-pods-ios-release-${{ hashFiles('ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods-ios-release- + + - name: Install CocoaPods Dependencies + env: + RCT_USE_RN_DEP: "1" + RCT_USE_PREBUILT_RNCORE: "1" + run: | + bundle exec fastlane ios install_pods + echo "CocoaPods dependencies installed successfully" + + - name: Generate Build Number Based on Timestamp + id: generate_build_number + run: | + NEW_BUILD_NUMBER=$(date +%s) + echo "NEW_BUILD_NUMBER=$NEW_BUILD_NUMBER" >> $GITHUB_ENV + echo "build_number=$NEW_BUILD_NUMBER" >> $GITHUB_OUTPUT + + - name: Set Build Number + run: bundle exec fastlane ios increment_build_number_lane + + - name: Determine Marketing Version + id: determine_marketing_version + run: | + MARKETING_VERSION=$(grep MARKETING_VERSION BlueWallet.xcodeproj/project.pbxproj | awk -F '= ' '{print $2}' | tr -d ' ;' | head -1) + echo "PROJECT_VERSION=$MARKETING_VERSION" >> $GITHUB_ENV + echo "project_version=$MARKETING_VERSION" >> $GITHUB_OUTPUT + working-directory: ios + + - name: Set Up Git Authentication + env: + ACCESS_TOKEN: ${{ secrets.GIT_ACCESS_TOKEN }} + run: | + git config --global credential.helper 'cache --timeout=3600' + git config --global http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n x-access-token:${ACCESS_TOKEN} | base64)" + + - name: Create Temporary Keychain + run: bundle exec fastlane ios create_temp_keychain + env: + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + + - name: Setup Provisioning Profiles + env: + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + GIT_ACCESS_TOKEN: ${{ secrets.GIT_ACCESS_TOKEN }} + GIT_URL: ${{ secrets.GIT_URL }} + ITC_TEAM_ID: ${{ secrets.ITC_TEAM_ID }} + ITC_TEAM_NAME: ${{ secrets.ITC_TEAM_NAME }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + bundle exec fastlane ios setup_provisioning_profiles + + - name: Build App + id: build_app + run: | + echo -e "\033[1;34m==================== BUILD APP DEBUG ====================\033[0m" + echo -e "\033[1;36mPre-Build Environment Check:\033[0m" + echo " Working Directory: $(pwd)" + echo " iOS Directory Contents:" + ls -la ios/ || echo -e "\033[1;31m iOS directory not found\033[0m" + + echo -e "\033[1;36mBuild Configuration:\033[0m" + echo " PROJECT_VERSION: ${PROJECT_VERSION:-'Not set'}" + echo " NEW_BUILD_NUMBER: ${NEW_BUILD_NUMBER:-'Not set'}" + echo " Build Type: Release (App Store)" + + echo -e "\033[1;33mXcode Project Analysis:\033[0m" + if [ -f "ios/BlueWallet.xcworkspace" ]; then + echo -e "\033[1;32m Workspace found: ios/BlueWallet.xcworkspace\033[0m" + else + echo -e "\033[1;31m Workspace missing: ios/BlueWallet.xcworkspace\033[0m" + fi + + if [ -f "ios/export_options.plist" ]; then + echo -e "\033[1;32m Export options found: ios/export_options.plist\033[0m" + echo " Export options content:" + cat ios/export_options.plist | head -20 + else + echo -e "\033[1;31m Export options missing: ios/export_options.plist\033[0m" + fi + + echo -e "\033[1;33mAvailable Build Destinations:\033[0m" + if [ -f "ios/BlueWallet.xcworkspace" ]; then + xcodebuild -workspace ios/BlueWallet.xcworkspace -scheme BlueWallet -showdestinations 2>/dev/null | head -20 || echo -e "\033[1;31m Failed to get destinations\033[0m" + fi + + echo -e "\033[1;35mStarting Fastlane Build (Release Mode):\033[0m" + bundle exec fastlane ios build_app_lane + + echo -e "\033[1;36mPost-Build IPA Analysis:\033[0m" + echo " Searching for IPA files..." + find ./ios -name "*.ipa" -type f 2>/dev/null | while read -r ipa; do + echo " Found IPA: $ipa" + echo " Size: $(ls -lh "$ipa" | awk '{print $5}')" + echo " Modified: $(ls -l "$ipa" | awk '{print $6, $7, $8}')" + done + + # Ensure IPA path is set for subsequent steps + if [ -f "./ios/build/ipa_path.txt" ]; then + IPA_PATH=$(cat ./ios/build/ipa_path.txt) + echo -e "\033[1;32m IPA path from file: $IPA_PATH\033[0m" + echo "IPA_OUTPUT_PATH=$IPA_PATH" >> $GITHUB_ENV + echo "ipa_output_path=$IPA_PATH" >> $GITHUB_OUTPUT + else + echo -e "\033[1;33m ipa_path.txt not found, searching manually...\033[0m" + IPA_PATH=$(find ./ios -name "*.ipa" | head -n 1) + if [ -n "$IPA_PATH" ]; then + echo -e "\033[1;32m IPA found manually: $IPA_PATH\033[0m" + echo "IPA_OUTPUT_PATH=$IPA_PATH" >> $GITHUB_ENV + echo "ipa_output_path=$IPA_PATH" >> $GITHUB_OUTPUT + else + echo -e "\033[1;31m No IPA file found anywhere\033[0m" + echo -e "\033[1;33m Directory structure for debugging:\033[0m" + find ./ios -type f -name "*.xcarchive" -o -name "*.ipa" -o -name "build_logs" 2>/dev/null || echo " No build artifacts found" + exit 1 + fi + fi + + echo -e "\033[1;32mBuild Summary:\033[0m" + echo " Final IPA Path: ${IPA_OUTPUT_PATH:-'Not set'}" + if [ -n "${IPA_OUTPUT_PATH}" ] && [ -f "${IPA_OUTPUT_PATH}" ]; then + echo -e "\033[1;32m IPA file exists and is ready for release\033[0m" + echo " File info: $(ls -lh "${IPA_OUTPUT_PATH}")" + fi + echo -e "\033[1;34m========================================================\033[0m" + + - name: Debug Build Failure + if: failure() + run: | + echo -e "\033[1;31m================== BUILD FAILURE DEBUG ==================\033[0m" + echo -e "\033[1;31mBuild failure analysis initiated...\033[0m" + + echo -e "\033[1;36mProject Structure Analysis:\033[0m" + echo " iOS directory contents:" + ls -la ios/ 2>/dev/null || echo -e "\033[1;31m iOS directory not accessible\033[0m" + + echo " Build directory contents:" + if [ -d "ios/build" ]; then + find ios/build -type f -name "*.log" -o -name "*.ipa" -o -name "*.xcarchive" | head -10 + else + echo -e "\033[1;31m ios/build directory does not exist\033[0m" + fi + + echo -e "\033[1;36mBuild Logs Analysis:\033[0m" + if [ -d "ios/build_logs" ]; then + echo " Build logs directory contents:" + ls -la ios/build_logs/ | head -10 + + echo " Recent log files (last 50 lines each):" + find ios/build_logs -name "*.log" -type f | head -3 | while read -r logfile; do + echo " === $logfile ===" + tail -50 "$logfile" 2>/dev/null | head -20 + echo " === End of $logfile ===" + done + else + echo -e "\033[1;31m No build logs directory found\033[0m" + fi + + echo -e "\033[1;36mXcode Build System Analysis:\033[0m" + echo " Recent archives:" + find ~/Library/Developer/Xcode/Archives -name "*.xcarchive" -type d 2>/dev/null | tail -3 || echo " No archives found" + + echo " Derived data contents:" + find ~/Library/Developer/Xcode/DerivedData -maxdepth 2 -name "*BlueWallet*" 2>/dev/null | head -5 || echo " No derived data found" + + echo -e "\033[1;36mFinal Simulator State:\033[0m" + echo " Available runtimes:" + xcrun simctl list runtimes | grep -E "(iOS|watchOS)" | tail -5 + + echo " Available devices:" + xcrun simctl list devices iOS | head -10 + + echo -e "\033[1;36mSystem State:\033[0m" + echo " Disk space:" + df -h / | tail -1 + + echo " Memory usage:" + vm_stat | grep -E "(Pages free|Pages active)" | head -2 + + echo -e "\033[1;31m========================================================\033[0m" + + - name: Upload Bugsnag Sourcemaps + if: success() + run: bundle exec fastlane ios upload_bugsnag_sourcemaps + env: + BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }} + BUGSNAG_RELEASE_STAGE: production + PROJECT_VERSION: ${{ env.PROJECT_VERSION }} + NEW_BUILD_NUMBER: ${{ env.NEW_BUILD_NUMBER }} + + - name: Upload Build Logs + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: build_logs + path: ./ios/build_logs/ + retention-days: 7 + + - name: Verify IPA File Before Upload + run: | + echo "Checking IPA file at: $IPA_OUTPUT_PATH" + if [ -f "$IPA_OUTPUT_PATH" ]; then + echo "✅ IPA file exists" + ls -la "$IPA_OUTPUT_PATH" + else + echo "❌ IPA file not found at: $IPA_OUTPUT_PATH" + echo "Current directory contents:" + find ./ios -name "*.ipa" + exit 1 + fi + + - name: Upload IPA as Artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: BlueWallet_IPA + path: ${{ env.IPA_OUTPUT_PATH }} + retention-days: 7 + + - name: Delete Temporary Keychain + if: always() + run: bundle exec fastlane ios delete_temp_keychain + + testflight-upload: + needs: build + runs-on: macos-26 + if: github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'testflight') + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + NEW_BUILD_NUMBER: ${{ needs.build.outputs.new_build_number }} + PROJECT_VERSION: ${{ needs.build.outputs.project_version }} + LATEST_COMMIT_MESSAGE: ${{ needs.build.outputs.latest_commit_message }} + BRANCH_NAME: ${{ needs.build.outputs.branch_name }} + steps: + - name: Checkout Project + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set Up Ruby + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 + with: + ruby-version: 3.4.9 + + - name: Install Dependencies with Bundler + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 --quiet + + - name: Download IPA from Artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: BlueWallet_IPA + path: ./ + + - name: Create App Store Connect API Key JSON + run: echo '${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}' > ./appstore_api_key.json + + - name: Set IPA Path Environment Variable + run: echo "IPA_OUTPUT_PATH=$(pwd)/BlueWallet_${{ needs.build.outputs.project_version }}_${{ needs.build.outputs.new_build_number }}.ipa" >> $GITHUB_ENV + + - name: Verify IPA Path Before Upload + run: | + if [ ! -f "$IPA_OUTPUT_PATH" ]; then + echo "❌ IPA file not found at path: $IPA_OUTPUT_PATH" + ls -la $(pwd) + exit 1 + else + echo "✅ Found IPA at: $IPA_OUTPUT_PATH" + fi + + - name: Print Environment Variables for Debugging + run: | + echo "LATEST_COMMIT_MESSAGE: $LATEST_COMMIT_MESSAGE" + echo "BRANCH_NAME: $BRANCH_NAME" + echo "PROJECT_VERSION: $PROJECT_VERSION" + echo "NEW_BUILD_NUMBER: $NEW_BUILD_NUMBER" + echo "IPA_OUTPUT_PATH: $IPA_OUTPUT_PATH" + + - name: Upload to TestFlight + run: bundle exec fastlane ios upload_to_testflight_lane + env: + APP_STORE_CONNECT_API_KEY_PATH: $(pwd)/appstore_api_key.p8 + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + GIT_ACCESS_TOKEN: ${{ secrets.GIT_ACCESS_TOKEN }} + GIT_URL: ${{ secrets.GIT_URL }} + ITC_TEAM_ID: ${{ secrets.ITC_TEAM_ID }} + ITC_TEAM_NAME: ${{ secrets.ITC_TEAM_NAME }} + APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }} + APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + + - name: Post PR Comment + if: success() && github.event_name == 'pull_request' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + BUILD_NUMBER: ${{ needs.build.outputs.new_build_number }} + PROJECT_VERSION: ${{ needs.build.outputs.project_version }} + LATEST_COMMIT_MESSAGE: ${{ needs.build.outputs.latest_commit_message }} + with: + script: | + const buildNumber = process.env.BUILD_NUMBER; + const version = process.env.PROJECT_VERSION; + const message = `✅ Build ${version} (${buildNumber}) has been uploaded to TestFlight and will be available for testing soon.`; + const prNumber = context.payload.pull_request.number; + const repo = context.repo; + github.rest.issues.createComment({ + ...repo, + issue_number: prNumber, + body: message, + }); \ No newline at end of file diff --git a/.github/workflows/build-mac-catalyst.yml b/.github/workflows/build-mac-catalyst.yml new file mode 100644 index 00000000000..39b8bbe01c5 --- /dev/null +++ b/.github/workflows/build-mac-catalyst.yml @@ -0,0 +1,187 @@ +name: Build Mac Catalyst + +on: + workflow_dispatch: + pull_request: + branches: + - master + types: [labeled, synchronize] + +concurrency: + group: catalyst-build-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +jobs: + build: + if: > + github.event_name == 'workflow_dispatch' || + (github.event.action == 'labeled' && (github.event.label.name == 'mac-dmg' || github.event.label.name == 'testflight')) || + github.event.action == 'synchronize' + runs-on: macos-15 + timeout-minutes: 120 + + steps: + - name: Check PR labels + if: github.event_name == 'pull_request' + id: labels + env: + GH_TOKEN: ${{ github.token }} + run: | + LABELS=$(gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels" --jq '.[].name' | tr '\n' ',') + echo "all=${LABELS}" >> $GITHUB_OUTPUT + if [[ "$LABELS" == *"mac-dmg"* ]]; then + echo "has_mac_dmg=true" >> $GITHUB_OUTPUT + else + echo "has_mac_dmg=false" >> $GITHUB_OUTPUT + fi + if [[ "$LABELS" == *"testflight"* ]] && [[ "$LABELS" == *"mac-dmg"* ]]; then + echo "upload_testflight=true" >> $GITHUB_OUTPUT + else + echo "upload_testflight=false" >> $GITHUB_OUTPUT + fi + echo "Labels on PR: ${LABELS}" + + - name: Skip if mac-dmg label not present + if: github.event_name == 'pull_request' && steps.labels.outputs.has_mac_dmg != 'true' + run: | + echo "mac-dmg label not found on PR — skipping build." + exit 0 + + - name: Checkout project + if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Setup Node.js + if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true' + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + cache: 'npm' + + - name: Setup Xcode + if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true' + uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 + with: + xcode-version: latest + + - name: Set up Ruby + if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true' + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 + with: + ruby-version: 3.4.9 + bundler-cache: true + + - name: Install Node modules + if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true' + run: npm ci + + - name: Cache CocoaPods + if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true' + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ios/Pods + ~/Library/Caches/CocoaPods + ~/.cocoapods/repos + key: ${{ runner.os }}-pods-catalyst-${{ hashFiles('ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods-catalyst- + + - name: Install CocoaPods dependencies + if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true' + env: + SKIP_APP_STORE_CONNECT_AUTH: '1' + RCT_USE_RN_DEP: "1" + RCT_USE_PREBUILT_RNCORE: "1" + run: bundle exec fastlane ios install_pods + + - name: Create temporary keychain for signing + if: (github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true') && steps.labels.outputs.upload_testflight == 'true' + run: | + security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain + security set-keychain-settings -t 3600 -u build.keychain + + - name: Build Mac Catalyst app with Fastlane + if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true' + id: build_catalyst + run: bundle exec fastlane ios build_catalyst_app_lane + env: + SKIP_APP_STORE_CONNECT_AUTH: '1' + SKIP_CLEAR_DERIVED_DATA: '1' + CATALYST_SIGNING_IDENTITY: ${{ steps.labels.outputs.upload_testflight == 'true' && secrets.CATALYST_SIGNING_IDENTITY || '' }} + CATALYST_TEAM_ID: ${{ steps.labels.outputs.upload_testflight == 'true' && secrets.CATALYST_TEAM_ID || '' }} + GIT_URL: ${{ steps.labels.outputs.upload_testflight == 'true' && secrets.GIT_URL || '' }} + GIT_ACCESS_TOKEN: ${{ steps.labels.outputs.upload_testflight == 'true' && secrets.GIT_ACCESS_TOKEN || '' }} + MATCH_READONLY: ${{ steps.labels.outputs.upload_testflight == 'true' && 'false' || 'true' }} + KEYCHAIN_NAME: ${{ steps.labels.outputs.upload_testflight == 'true' && 'build' || '' }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + + - name: Upload Mac Catalyst DMG + id: upload_dmg + if: success() && (github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true') + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: BlueWallet-Mac-Catalyst + path: ${{ steps.build_catalyst.outputs.catalyst_dmg_path }} + if-no-files-found: warn + + - name: Create App Store Connect API Key JSON + if: success() && steps.labels.outputs.upload_testflight == 'true' + run: echo '${{ secrets.APPLE_API_KEY_CONTENT }}' > ./appstore_api_key.json + + - name: Upload to TestFlight + if: success() && steps.labels.outputs.upload_testflight == 'true' + run: bundle exec fastlane ios upload_catalyst_to_testflight + env: + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }} + CATALYST_TEAM_ID: ${{ secrets.CATALYST_TEAM_ID }} + TEAM_ID: ${{ secrets.TEAM_ID }} + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + LATEST_COMMIT_MESSAGE: ${{ github.event.pull_request.title || 'Manual build' }} + + - name: Cleanup App Store Connect API Key JSON + if: always() && steps.labels.outputs.upload_testflight == 'true' + run: rm -f ./appstore_api_key.json + + - name: Cleanup temporary keychain + if: always() && steps.labels.outputs.upload_testflight == 'true' + run: security delete-keychain build.keychain || true + + - name: Comment on PR with DMG link + if: success() && github.event_name == 'pull_request' && steps.labels.outputs.has_mac_dmg == 'true' + env: + GH_TOKEN: ${{ github.token }} + UPLOADED_TO_TF: ${{ steps.labels.outputs.upload_testflight }} + run: | + ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.upload_dmg.outputs.artifact-id }}" + COMMENT_TAG="" + + TF_LINE="" + if [[ "$UPLOADED_TO_TF" == "true" ]]; then + TF_LINE=$'\n\n**Also uploaded to TestFlight.** Check [App Store Connect](https://appstoreconnect.apple.com) for the build.' + fi + + COMMENT_FILE="$(mktemp)" + { + printf '%s\n' "${COMMENT_TAG}" + printf '### Mac Catalyst Build\n\n' + printf 'The Mac Catalyst DMG is ready for download:\n\n' + printf '[Download BlueWallet-Mac-Catalyst.dmg](%s)\n' "${ARTIFACT_URL}" + if [[ -n "$TF_LINE" ]]; then + printf '%s\n' "${TF_LINE}" + fi + printf 'Built from `%s`" + } >"${COMMENT_FILE}" + + gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \ + --paginate --jq '.[] | select(.body | contains("")) | .id' | \ + while read -r comment_id; do + gh api -X DELETE "repos/${{ github.repository }}/issues/comments/${comment_id}" || true + done + + gh pr comment "${{ github.event.pull_request.number }}" --body-file "${COMMENT_FILE}" diff --git a/.github/workflows/build-release-apk.yml b/.github/workflows/build-release-apk.yml index e7526962929..7d7fb649f11 100644 --- a/.github/workflows/build-release-apk.yml +++ b/.github/workflows/build-release-apk.yml @@ -1,47 +1,165 @@ name: BuildReleaseApk -on: [pull_request] + +on: + pull_request: + branches: + - master + types: [opened, synchronize, reopened, labeled, unlabeled] + push: + branches: + - master jobs: buildReleaseApk: - runs-on: macos-latest + runs-on: ubuntu-24.04 steps: - name: Checkout project - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: "0" + - name: Free disk space (Android build) + shell: bash + run: | + df -h + sudo rm -rf /usr/share/dotnet || true + sudo rm -rf /opt/ghc || true + sudo rm -rf /usr/local/share/boost || true + sudo rm -rf /usr/local/lib/android/sdk/ndk || true + docker system prune -af || true + sudo rm -rf /usr/local/lib/android/sdk/system-images || true + sudo rm -rf /usr/local/lib/android/sdk/emulator || true + rm -rf ~/.gradle/caches/modules-2/files-2.1 || true + rm -rf ~/.gradle/caches/build-cache || true + rm -rf ~/.npm/_cacache ~/.cache || true + sudo rm -rf /home/runner/work/_temp || true + df -h + - name: Specify node version - uses: actions/setup-node@v2-beta + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: 16 + node-version: 24 + cache: 'npm' - - name: Use npm caches - uses: actions/cache@v2 + - name: Use specific Java version for sdkmanager to work + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: - path: ~/.npm - key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + distribution: 'temurin' + java-version: '17' + + - name: Use gradle caches + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('android/**/*.gradle', 'android/**/*.properties') }} restore-keys: | - ${{ runner.os }}-npm- + ${{ runner.os }}-gradle- - - name: Use specific Java version for sdkmanager to work - uses: actions/setup-java@v3 + - name: Set up Android SDK + uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1 + + - name: Install Android SDK components + run: | + yes | sdkmanager --licenses + sdkmanager "platforms;android-36" "platform-tools" "build-tools;36.0.0" "ndk;27.1.12297006" + + - name: Install node_modules (include dev deps for patch-package) + run: npm ci --yes + + - name: Set up Ruby + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 with: - distribution: 'temurin' - java-version: '11' - cache: 'gradle' + ruby-version: 3.4.9 + bundler-cache: true - - name: Install node_modules - run: npm install --production + - name: Generate Build Number based on timestamp + id: build_number + run: | + NEW_BUILD_NUMBER="$(date +%s)" + echo "NEW_BUILD_NUMBER=$NEW_BUILD_NUMBER" >> $GITHUB_ENV + echo "build_number=$NEW_BUILD_NUMBER" >> $GITHUB_OUTPUT - - name: Build + - name: Build and sign APK + id: build_and_sign_apk + run: bundle exec fastlane android build_release_apk env: + BUILD_NUMBER: ${{ steps.build_number.outputs.build_number }} KEYSTORE_FILE_HEX: ${{ secrets.KEYSTORE_FILE_HEX }} KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} - run: ./scripts/build-release-apk.sh - - uses: actions/upload-artifact@v2 - if: success() + - name: Upload build logs on failure + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: android-build-logs + path: | + fastlane/logs/**/*.log + android/**/*.log + android/**/build/**/*.log + android/**/outputs/logs/**/*.log + android/**/reports/**/*.log + if-no-files-found: warn + + - name: Determine APK Filename and Path + id: determine_apk_path + run: | + BUILD_NUMBER=${{ steps.build_number.outputs.build_number }} + VERSION_NAME=$(grep versionName android/app/build.gradle | awk '{print $2}' | tr -d '"') + BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}} + BRANCH_NAME=$(echo "$BRANCH_NAME" | sed 's/[^a-zA-Z0-9_-]/_/g') + + if [ -n "$BRANCH_NAME" ] && [ "$BRANCH_NAME" != "master" ]; then + EXPECTED_FILENAME="BlueWallet-${VERSION_NAME}-${BUILD_NUMBER}-${BRANCH_NAME}.apk" + else + EXPECTED_FILENAME="BlueWallet-${VERSION_NAME}-${BUILD_NUMBER}.apk" + fi + + APK_PATH="android/app/build/outputs/apk/release/${EXPECTED_FILENAME}" + echo "EXPECTED_FILENAME=${EXPECTED_FILENAME}" >> $GITHUB_ENV + echo "APK_PATH=${APK_PATH}" >> $GITHUB_ENV + + - name: Upload APK as artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: signed-apk + path: ${{ env.APK_PATH }} + if-no-files-found: error + + browserstack: + runs-on: macos-26 + needs: buildReleaseApk + if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'browserstack') }} + + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up Ruby + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 with: - name: apk - path: ./android/app/build/outputs/apk/release/app-release.apk + ruby-version: 3.4.9 + bundler-cache: true + + - name: Install dependencies with Bundler + run: bundle install --jobs 4 --retry 3 + + - name: Download APK artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: signed-apk + + - name: Set APK Path + run: | + APK_PATH=$(find ${{ github.workspace }} -name '*.apk') + echo "APK_PATH=$APK_PATH" >> $GITHUB_ENV + + - name: Upload APK to BrowserStack and Post PR Comment + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: bundle exec fastlane upload_to_browserstack_and_comment diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ecd1930413..e58b535796b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,41 +3,52 @@ name: Tests # https://dev.to/edvinasbartkus/running-react-native-detox-tests-for-ios-and-android-on-github-actions-2ekn # https://medium.com/@reime005/the-best-ci-cd-for-react-native-with-e2e-support-4860b4aaab29 +env: + HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }} + HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }} + on: [pull_request] jobs: - test: - runs-on: macos-latest + lint: + runs-on: ubuntu-latest steps: - name: Checkout project - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 - name: Specify node version - uses: actions/setup-node@v2-beta + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: 16 + node-version: 24 + cache: 'npm' + + - name: Install node_modules + run: npm ci || npm ci - - name: Use npm caches - uses: actions/cache@v2 + - name: Run tests + run: npm run lint + + unit: + runs-on: ubuntu-latest + steps: + - name: Checkout project + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - path: ~/.npm - key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm- + fetch-depth: 0 - - name: Use node_modules caches - id: cache-nm - uses: actions/cache@v2 + - name: Specify node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - path: node_modules - key: ${{ runner.os }}-nm-${{ hashFiles('package-lock.json') }} + node-version: 24 + cache: 'npm' - name: Install node_modules - if: steps.cache-nm.outputs.cache-hit != 'true' - run: npm install + run: npm ci || npm ci - name: Run tests - run: npm test || npm test || npm test + run: npm run unit env: BIP47_HD_MNEMONIC: ${{ secrets.BIP47_HD_MNEMONIC}} HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }} @@ -49,60 +60,33 @@ jobs: MNEMONICS_COBO: ${{ secrets.MNEMONICS_COBO }} MNEMONICS_COLDCARD: ${{ secrets.MNEMONICS_COLDCARD }} - e2e: - runs-on: macos-latest + integration: + runs-on: ubuntu-latest steps: - - name: checkout - uses: actions/checkout@v3 - - - name: Specify node version - uses: actions/setup-node@v2-beta - with: - node-version: 16 - - - name: Use gradle caches - uses: actions/cache@v2 + - name: Checkout project + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} - restore-keys: | - ${{ runner.os }}-gradle- + fetch-depth: 0 - - name: Use npm caches - uses: actions/cache@v2 + - name: Specify node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - path: ~/.npm - key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm- + node-version: 24 + cache: 'npm' - name: Install node_modules - run: npm install + run: npm ci || npm ci - - name: Use specific Java version for sdkmanager to work - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: '11' - - - name: Build - run: npm run e2e:release-build - - - name: run tests - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 31 - avd-name: Pixel_API_29_AOSP - emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none -camera-front none -partition-size 2047 - arch: x86_64 - script: npm run e2e:release-test || npm run e2e:release-test || npm run e2e:release-test || npm run e2e:release-test + - name: Run tests + run: npm run integration || npm run integration || npm run integration || npm run integration env: - TRAVIS: 1 + BIP47_HD_MNEMONIC: ${{ secrets.BIP47_HD_MNEMONIC}} HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }} + HD_MNEMONIC_BIP49: ${{ secrets.HD_MNEMONIC_BIP49 }} + HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }} HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }} - - - uses: actions/upload-artifact@v2 - if: failure() - with: - name: e2e-test-videos - path: ./artifacts/ + HD_MNEMONIC_BREAD: ${{ secrets.HD_MNEMONIC_BREAD }} + FAULTY_ZPUB: ${{ secrets.FAULTY_ZPUB }} + MNEMONICS_COBO: ${{ secrets.MNEMONICS_COBO }} + MNEMONICS_COLDCARD: ${{ secrets.MNEMONICS_COLDCARD }} + RETRY: 1 diff --git a/.github/workflows/e2e-android.yml b/.github/workflows/e2e-android.yml new file mode 100644 index 00000000000..8ce18ad5ae3 --- /dev/null +++ b/.github/workflows/e2e-android.yml @@ -0,0 +1,152 @@ +name: Tests e2e Android + +on: [pull_request] + +permissions: + contents: read + +concurrency: + group: e2e-android-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Free disk space (Ubuntu) + run: | + echo "Disk before cleanup:" && df -h + sudo rm -rf /usr/share/dotnet /opt/ghc + sudo apt-get clean + sudo rm -rf /opt/ghc || true + sudo rm -rf /usr/local/share/boost || true + sudo rm -rf /usr/local/lib/android/sdk/ndk || true + sudo docker system prune -af || true + sudo rm -rf /usr/local/lib/android/sdk/system-images || true + sudo rm -rf /usr/local/lib/android/sdk/emulator || true + rm -rf ~/.gradle/caches/modules-2/files-2.1 || true + rm -rf ~/.gradle/caches/build-cache || true + rm -rf ~/.npm/_cacache ~/.cache || true + sudo rm -rf /home/runner/work/_temp || true + echo "Disk after cleanup:" && df -h + + - name: Specify node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + cache: 'npm' + + - name: Use gradle caches + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('android/**/*.gradle', 'android/**/*.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Install node_modules + run: npm ci || npm ci + + - name: Use specific Java version for sdkmanager to work + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: 'temurin' + java-version: '17' + + - name: Build + run: npm run e2e:release-build || npm run e2e:release-build + + - name: Package APKs + run: | + tar -czf bluewallet-android-apks.tar.gz \ + android/app/build/outputs/apk/release/app-release.apk \ + android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk + + - name: Upload APKs + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: bluewallet-android-apks + path: bluewallet-android-apks.tar.gz + retention-days: 3 + compression-level: 0 + if-no-files-found: error + + test: + runs-on: ubuntu-24.04 + needs: build + env: + HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }} + HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }} + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Free disk space (Ubuntu) + run: | + echo "Disk before cleanup:" && df -h + sudo rm -rf /usr/share/dotnet /opt/ghc + sudo apt-get clean + sudo rm -rf /opt/ghc || true + sudo rm -rf /usr/local/share/boost || true + sudo rm -rf /usr/local/lib/android/sdk/ndk || true + sudo docker system prune -af || true + rm -rf ~/.npm/_cacache ~/.cache || true + sudo rm -rf /home/runner/work/_temp || true + echo "Disk after cleanup:" && df -h + + - name: Ensure artifacts directory + run: mkdir -p ${{ github.workspace }}/artifacts + + - name: Specify node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + cache: 'npm' + + - name: Install node_modules + run: npm ci || npm ci + + - name: Use specific Java version for sdkmanager to work + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: 'temurin' + java-version: '17' + + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Download APKs + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: bluewallet-android-apks + + - name: Restore APKs + run: tar -xzf bluewallet-android-apks.tar.gz + + - name: Run tests + uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0 + with: + api-level: 36 + profile: pixel + avd-name: Pixel_API_29_AOSP + force-avd-creation: true + enable-hw-keyboard: true + emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none -camera-front none -partition-size 2047 + arch: x86_64 + script: npm run e2e:release-test -- --record-videos failing --record-logs failing --take-screenshots failing --headless --retries 4 --reuse --artifacts-location ${{ github.workspace }}/artifacts + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: failure() + with: + name: e2e-android-videos + path: ${{ github.workspace }}/artifacts diff --git a/.github/workflows/e2e-ios.yml b/.github/workflows/e2e-ios.yml new file mode 100644 index 00000000000..6bc398ec87e --- /dev/null +++ b/.github/workflows/e2e-ios.yml @@ -0,0 +1,229 @@ +name: Tests e2e iOS + +on: [pull_request] + +permissions: + contents: read + +concurrency: + group: e2e-ios-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: macos-26 + env: + BUILD_CONFIGURATION: Release + CCACHE_MAXSIZE: "2G" + CCACHE_DIR: /Users/runner/.ccache + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + cache: 'npm' + + - name: Setup Ruby + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 + with: + ruby-version: "3.4.9" + bundler-cache: true + + - name: Install Node dependencies + run: npm ci || npm ci + + - name: Cache CocoaPods + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ios/Pods + ~/Library/Caches/CocoaPods + ~/.cocoapods/repos + key: ${{ runner.os }}-pods-prebuilt-${{ hashFiles('ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods-prebuilt- + + - name: Install ccache + run: brew install ccache + + - name: Cache ccache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ~/.ccache + key: ${{ runner.os }}-ccache-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-ccache- + + - name: Delete extension and watch targets + run: | + bundle exec ruby <<'RUBY' + require 'xcodeproj' + + project_path = 'ios/BlueWallet.xcodeproj' + project = Xcodeproj::Project.open(project_path) + + target_names = %w[BlueWalletWatch WidgetsExtension Stickers] + embed_phase_names = ['Embed Watch Content', 'Embed Foundation Extensions'] + removed_any = false + + target_names.each do |target_name| + target = project.targets.find { |t| t.name == target_name } + next unless target + + puts "Removing target #{target_name}" + target.dependencies.each(&:remove_from_project) + target.build_phases.each(&:remove_from_project) + project.targets.delete(target) + removed_any = true + end + + main_target = project.targets.find { |t| t.name == 'BlueWallet' } + if main_target + main_target.build_phases.select { |phase| embed_phase_names.include?(phase.display_name) }.each do |phase| + puts "Removing build phase #{phase.display_name}" + phase.remove_from_project + main_target.build_phases.delete(phase) + removed_any = true + end + end + + if removed_any + project.save + puts 'Extension and watch target references removed' + else + puts 'No extension or watch targets found' + end + RUBY + + - name: Remove extension and watch schemes + run: | + rm -f ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWalletWatch.xcscheme + rm -f ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme + rm -f ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/Stickers.xcscheme + + - name: Install CocoaPods dependencies + env: + RCT_USE_RN_DEP: "1" + RCT_USE_PREBUILT_RNCORE: "1" + USE_CCACHE: "1" + run: bundle exec fastlane ios install_pods || bundle exec fastlane ios install_pods + + - name: Reset ccache stats + run: ccache -z || true + + - name: Build iOS simulator app + working-directory: ios + env: + RCT_NO_LAUNCH_PACKAGER: "1" + CCACHE_BINARY: /opt/homebrew/bin/ccache + run: | + set -eo pipefail + build() { + xcodebuild \ + -workspace BlueWallet.xcworkspace \ + -scheme BlueWallet \ + -configuration "${BUILD_CONFIGURATION}" \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + -derivedDataPath build \ + CLANG_ENABLE_EXPLICIT_MODULES=NO \ + SWIFT_ENABLE_EXPLICIT_MODULES=NO \ + build + } + build || build + + - name: ccache stats + if: always() + run: ccache -s || true + + - name: Package simulator app + run: | + APP_DIR="ios/build/Build/Products/${BUILD_CONFIGURATION}-iphonesimulator/BlueWallet.app" + if [ ! -d "$APP_DIR" ]; then + echo "Simulator app not found at $APP_DIR" + find ios/build -maxdepth 5 -name '*.app' || true + exit 1 + fi + tar -czf BlueWallet.app.tar.gz -C "$(dirname "$APP_DIR")" "$(basename "$APP_DIR")" + + - name: Upload simulator app + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: bluewallet-ios-app + path: BlueWallet.app.tar.gz + retention-days: 3 + compression-level: 0 + if-no-files-found: error + + test: + runs-on: macos-26 + needs: build + env: + HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }} + HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }} + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + cache: 'npm' + + - name: Install Node dependencies + run: npm ci || npm ci + + - name: Install applesimutils + run: | + brew tap wix/brew + brew install applesimutils + + - name: Download simulator app + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: bluewallet-ios-app + + - name: Restore simulator app + run: | + 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: | + DEVICE_TYPE=$(jq -r '.devices.simulator.device.type' .detoxrc.json) + UDID=$(applesimutils --list --byType "$DEVICE_TYPE" | jq -r '.[0].udid // empty') + if [ -z "$UDID" ]; then + echo "ERROR: no simulator of type '$DEVICE_TYPE' found" + exit 1 + fi + xcrun simctl boot "$UDID" 2>/dev/null || true + xcrun simctl bootstatus "$UDID" -b + xcrun simctl launch "$UDID" com.apple.springboard >/dev/null 2>&1 || true + + - name: Run detox tests + timeout-minutes: 360 + run: | + npm run e2e:test:ios-release -- \ + --record-videos failing \ + --record-logs failing \ + --take-screenshots failing \ + --headless \ + --retries 3 \ + --reuse \ + --artifacts-location ./artifacts + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: failure() + with: + name: e2e-ios-videos + path: ./artifacts/ diff --git a/.gitignore b/.gitignore index d802dd90b55..6fc108c76c9 100644 --- a/.gitignore +++ b/.gitignore @@ -17,15 +17,15 @@ xcuserdata *.xccheckout *.moved-aside DerivedData +.kotlin/ *.hmap *.ipa *.xcuserstate -ios/.xcode.env.local +**/.xcode.env.local *.hprof .cxx/ *.keystore !debug.keystore - # Android/IntelliJ # build/ @@ -34,6 +34,9 @@ build/ local.properties *.iml +# testing +/coverage + # node.js # node_modules/ @@ -57,6 +60,7 @@ buck-out/ */fastlane/Preview.html */fastlane/screenshots **/fastlane/test_output +ios/fastlane # Bundle artifact *.jsbundle @@ -67,7 +71,7 @@ release-notes.txt current-branch.json # Ruby / CocoaPods -/ios/Pods/ +**/Pods/ /vendor/bundle/ ios/BlueWallet.xcodeproj/xcuserdata/ @@ -78,7 +82,19 @@ artifacts/ # Editors .vscode/ /.vs +.claude *.mx *.realm -*.realm.lock \ No newline at end of file +*.realm.lock +android/app/.project +android/.settings/org.eclipse.buildship.core.prefs +android/app/.classpath +android/.settings/org.eclipse.buildship.core.prefs +android/.project +android/app/.settings/org.eclipse.jdt.core.prefs +android/.settings/org.eclipse.buildship.core.prefs +android/app/.classpath +android/app/.project +fastlane/README.md +fastlane/report.xml diff --git a/.ruby-version b/.ruby-version index 49cdd668e1c..7bcbb3808b5 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.6 +3.4.9 diff --git a/App.js b/App.js deleted file mode 100644 index 9051c159d6c..00000000000 --- a/App.js +++ /dev/null @@ -1,396 +0,0 @@ -import 'react-native-gesture-handler'; // should be on top -import React, { useContext, useEffect, useRef } from 'react'; -import { - AppState, - DeviceEventEmitter, - NativeModules, - NativeEventEmitter, - Linking, - Platform, - StyleSheet, - UIManager, - useColorScheme, - View, - StatusBar, - LogBox, -} from 'react-native'; -import { NavigationContainer, CommonActions } from '@react-navigation/native'; -import { SafeAreaProvider } from 'react-native-safe-area-context'; -import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; - -import { navigationRef } from './NavigationService'; -import * as NavigationService from './NavigationService'; -import { Chain } from './models/bitcoinUnits'; -import OnAppLaunch from './class/on-app-launch'; -import DeeplinkSchemaMatch from './class/deeplink-schema-match'; -import loc from './loc'; -import { BlueDefaultTheme, BlueDarkTheme } from './components/themes'; -import InitRoot from './Navigation'; -import BlueClipboard from './blue_modules/clipboard'; -import { isDesktop } from './blue_modules/environment'; -import { BlueStorageContext } from './blue_modules/storage-context'; -import WatchConnectivity from './WatchConnectivity'; -import DeviceQuickActions from './class/quick-actions'; -import Notifications from './blue_modules/notifications'; -import Biometric from './class/biometrics'; -import WidgetCommunication from './blue_modules/WidgetCommunication'; -import changeNavigationBarColor from 'react-native-navigation-bar-color'; -import ActionSheet from './screen/ActionSheet'; -import HandoffComponent from './components/handoff'; -import Privacy from './blue_modules/Privacy'; -const A = require('./blue_modules/analytics'); -const currency = require('./blue_modules/currency'); - -const eventEmitter = Platform.OS === 'ios' ? new NativeEventEmitter(NativeModules.EventEmitter) : undefined; -const { EventEmitter } = NativeModules; - -LogBox.ignoreLogs(['Require cycle:']); - -const ClipboardContentType = Object.freeze({ - BITCOIN: 'BITCOIN', - LIGHTNING: 'LIGHTNING', -}); - -if (Platform.OS === 'android') { - if (UIManager.setLayoutAnimationEnabledExperimental) { - UIManager.setLayoutAnimationEnabledExperimental(true); - } -} - -const App = () => { - const { walletsInitialized, wallets, addWallet, saveToDisk, fetchAndSaveWalletTransactions, refreshAllWalletTransactions } = - useContext(BlueStorageContext); - const appState = useRef(AppState.currentState); - const clipboardContent = useRef(); - const colorScheme = useColorScheme(); - - const onNotificationReceived = async notification => { - const payload = Object.assign({}, notification, notification.data); - if (notification.data && notification.data.data) Object.assign(payload, notification.data.data); - payload.foreground = true; - - await Notifications.addNotification(payload); - // if user is staring at the app when he receives the notification we process it instantly - // so app refetches related wallet - if (payload.foreground) await processPushNotifications(); - }; - - const openSettings = () => { - NavigationService.dispatch( - CommonActions.navigate({ - name: 'Settings', - }), - ); - }; - - const onUserActivityOpen = data => { - switch (data.activityType) { - case HandoffComponent.activityTypes.ReceiveOnchain: - NavigationService.navigate('ReceiveDetailsRoot', { - screen: 'ReceiveDetails', - params: { - address: data.userInfo.address, - }, - }); - break; - case HandoffComponent.activityTypes.Xpub: - NavigationService.navigate('WalletXpubRoot', { - screen: 'WalletXpub', - params: { - xpub: data.userInfo.xpub, - }, - }); - break; - default: - break; - } - }; - - useEffect(() => { - if (walletsInitialized) { - addListeners(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [walletsInitialized]); - - useEffect(() => { - return () => { - Linking.removeEventListener('url', handleOpenURL); - AppState.removeEventListener('change', handleAppStateChange); - eventEmitter?.removeAllListeners('onNotificationReceived'); - eventEmitter?.removeAllListeners('openSettings'); - eventEmitter?.removeAllListeners('onUserActivityOpen'); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (colorScheme) { - if (colorScheme === 'light') { - changeNavigationBarColor(BlueDefaultTheme.colors.background, true, true); - } else { - changeNavigationBarColor(BlueDarkTheme.colors.buttonBackgroundColor, false, true); - } - } - }, [colorScheme]); - - const addListeners = () => { - Linking.addEventListener('url', handleOpenURL); - AppState.addEventListener('change', handleAppStateChange); - DeviceEventEmitter.addListener('quickActionShortcut', walletQuickActions); - DeviceQuickActions.popInitialAction().then(popInitialAction); - EventEmitter?.getMostRecentUserActivity() - .then(onUserActivityOpen) - .catch(() => console.log('No userActivity object sent')); - handleAppStateChange(undefined); - /* - When a notification on iOS is shown while the app is on foreground; - On willPresent on AppDelegate.m - */ - eventEmitter?.addListener('onNotificationReceived', onNotificationReceived); - eventEmitter?.addListener('openSettings', openSettings); - eventEmitter?.addListener('onUserActivityOpen', onUserActivityOpen); - }; - - const popInitialAction = async data => { - if (data) { - const wallet = wallets.find(w => w.getID() === data.userInfo.url.split('wallet/')[1]); - NavigationService.dispatch( - CommonActions.navigate({ - name: 'WalletTransactions', - key: `WalletTransactions-${wallet.getID()}`, - params: { - walletID: wallet.getID(), - walletType: wallet.type, - }, - }), - ); - } else { - const url = await Linking.getInitialURL(); - if (url) { - if (DeeplinkSchemaMatch.hasSchema(url)) { - handleOpenURL({ url }); - } - } else { - const isViewAllWalletsEnabled = await OnAppLaunch.isViewAllWalletsEnabled(); - if (!isViewAllWalletsEnabled) { - const selectedDefaultWallet = await OnAppLaunch.getSelectedDefaultWallet(); - const wallet = wallets.find(w => w.getID() === selectedDefaultWallet.getID()); - if (wallet) { - NavigationService.dispatch( - CommonActions.navigate({ - name: 'WalletTransactions', - key: `WalletTransactions-${wallet.getID()}`, - params: { - walletID: wallet.getID(), - walletType: wallet.type, - }, - }), - ); - } - } - } - } - }; - - const walletQuickActions = data => { - const wallet = wallets.find(w => w.getID() === data.userInfo.url.split('wallet/')[1]); - NavigationService.dispatch( - CommonActions.navigate({ - name: 'WalletTransactions', - key: `WalletTransactions-${wallet.getID()}`, - params: { - walletID: wallet.getID(), - walletType: wallet.type, - }, - }), - ); - }; - - /** - * Processes push notifications stored in AsyncStorage. Might navigate to some screen. - * - * @returns {Promise} returns TRUE if notification was processed _and acted_ upon, i.e. navigation happened - * @private - */ - const processPushNotifications = async () => { - if (!walletsInitialized) { - console.log('not processing push notifications because wallets are not initialized'); - return; - } - await new Promise(resolve => setTimeout(resolve, 200)); - // sleep needed as sometimes unsuspend is faster than notification module actually saves notifications to async storage - const notifications2process = await Notifications.getStoredNotifications(); - - await Notifications.clearStoredNotifications(); - Notifications.setApplicationIconBadgeNumber(0); - const deliveredNotifications = await Notifications.getDeliveredNotifications(); - setTimeout(() => Notifications.removeAllDeliveredNotifications(), 5000); // so notification bubble wont disappear too fast - - for (const payload of notifications2process) { - const wasTapped = payload.foreground === false || (payload.foreground === true && payload.userInteraction); - - console.log('processing push notification:', payload); - let wallet; - switch (+payload.type) { - case 2: - case 3: - wallet = wallets.find(w => w.weOwnAddress(payload.address)); - break; - case 1: - case 4: - wallet = wallets.find(w => w.weOwnTransaction(payload.txid || payload.hash)); - break; - } - - if (wallet) { - const walletID = wallet.getID(); - fetchAndSaveWalletTransactions(walletID); - if (wasTapped) { - if (payload.type !== 3 || wallet.chain === Chain.OFFCHAIN) { - NavigationService.dispatch( - CommonActions.navigate({ - name: 'WalletTransactions', - key: `WalletTransactions-${wallet.getID()}`, - params: { - walletID, - walletType: wallet.type, - }, - }), - ); - } else { - NavigationService.navigate('ReceiveDetailsRoot', { - screen: 'ReceiveDetails', - params: { - walletID, - address: payload.address, - }, - }); - } - - return true; - } - } else { - console.log('could not find wallet while processing push notification, NOP'); - } - } // end foreach notifications loop - - if (deliveredNotifications.length > 0) { - // notification object is missing userInfo. We know we received a notification but don't have sufficient - // data to refresh 1 wallet. let's refresh all. - refreshAllWalletTransactions(); - } - - // if we are here - we did not act upon any push - return false; - }; - - const handleAppStateChange = async nextAppState => { - if (wallets.length === 0) return; - if ((appState.current.match(/background/) && nextAppState === 'active') || nextAppState === undefined) { - setTimeout(() => A(A.ENUM.APP_UNSUSPENDED), 2000); - currency.updateExchangeRate(); - const processed = await processPushNotifications(); - if (processed) return; - const clipboard = await BlueClipboard().getClipboardContent(); - const isAddressFromStoredWallet = wallets.some(wallet => { - if (wallet.chain === Chain.ONCHAIN) { - // checking address validity is faster than unwrapping hierarchy only to compare it to garbage - return wallet.isAddressValid && wallet.isAddressValid(clipboard) && wallet.weOwnAddress(clipboard); - } else { - return wallet.isInvoiceGeneratedByWallet(clipboard) || wallet.weOwnAddress(clipboard); - } - }); - const isBitcoinAddress = DeeplinkSchemaMatch.isBitcoinAddress(clipboard); - const isLightningInvoice = DeeplinkSchemaMatch.isLightningInvoice(clipboard); - const isLNURL = DeeplinkSchemaMatch.isLnUrl(clipboard); - const isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(clipboard); - if ( - !isAddressFromStoredWallet && - clipboardContent.current !== clipboard && - (isBitcoinAddress || isLightningInvoice || isLNURL || isBothBitcoinAndLightning) - ) { - let contentType; - if (isBitcoinAddress) { - contentType = ClipboardContentType.BITCOIN; - } else if (isLightningInvoice || isLNURL) { - contentType = ClipboardContentType.LIGHTNING; - } else if (isBothBitcoinAndLightning) { - contentType = ClipboardContentType.BITCOIN; - } - showClipboardAlert({ contentType }); - } - clipboardContent.current = clipboard; - } - if (nextAppState) { - appState.current = nextAppState; - } - }; - - const handleOpenURL = event => { - DeeplinkSchemaMatch.navigationRouteFor(event, value => NavigationService.navigate(...value), { wallets, addWallet, saveToDisk }); - }; - - const showClipboardAlert = ({ contentType }) => { - ReactNativeHapticFeedback.trigger('impactLight', { ignoreAndroidSystemSettings: false }); - BlueClipboard() - .getClipboardContent() - .then(clipboard => { - if (Platform.OS === 'ios' || Platform.OS === 'macos') { - ActionSheet.showActionSheetWithOptions( - { - options: [loc._.cancel, loc._.continue], - title: loc._.clipboard, - message: contentType === ClipboardContentType.BITCOIN ? loc.wallets.clipboard_bitcoin : loc.wallets.clipboard_lightning, - cancelButtonIndex: 0, - }, - buttonIndex => { - if (buttonIndex === 1) { - handleOpenURL({ url: clipboard }); - } - }, - ); - } else { - ActionSheet.showActionSheetWithOptions({ - buttons: [ - { text: loc._.cancel, style: 'cancel', onPress: () => {} }, - { - text: loc._.continue, - style: 'default', - onPress: () => { - handleOpenURL({ url: clipboard }); - }, - }, - ], - title: loc._.clipboard, - message: contentType === ClipboardContentType.BITCOIN ? loc.wallets.clipboard_bitcoin : loc.wallets.clipboard_lightning, - }); - } - }); - }; - - return ( - - - - - - - - {walletsInitialized && !isDesktop && } - - - - - - - ); -}; - -const styles = StyleSheet.create({ - root: { - flex: 1, - }, -}); - -export default App; diff --git a/App.tsx b/App.tsx new file mode 100644 index 00000000000..1fba4c90cd1 --- /dev/null +++ b/App.tsx @@ -0,0 +1,33 @@ +import { NavigationContainer, NavigationContainerRef, ParamListBase } from '@react-navigation/native'; +import React from 'react'; +import { useColorScheme } from 'react-native'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; +import { SizeClassProvider } from './components/Context/SizeClassProvider'; +import { SettingsProvider } from './components/Context/SettingsProvider'; +import { BlueDarkTheme, BlueDefaultTheme } from './components/themes'; +import MasterView from './navigation/MasterView'; +import { navigationRef } from './NavigationService'; +import { useLogger } from '@react-navigation/devtools'; +import { StorageProvider } from './components/Context/StorageProvider'; + +const App = () => { + const colorScheme = useColorScheme(); + + useLogger(navigationRef as unknown as React.RefObject>); + + return ( + + + + + + + + + + + + ); +}; + +export default App; diff --git a/BlueApp.js b/BlueApp.js deleted file mode 100644 index c0ee48a831d..00000000000 --- a/BlueApp.js +++ /dev/null @@ -1,944 +0,0 @@ -import Biometric from './class/biometrics'; -import { Platform } from 'react-native'; -import loc from './loc'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store'; -import * as Keychain from 'react-native-keychain'; -import { - HDLegacyBreadwalletWallet, - HDSegwitP2SHWallet, - HDLegacyP2PKHWallet, - WatchOnlyWallet, - LegacyWallet, - SegwitP2SHWallet, - SegwitBech32Wallet, - HDSegwitBech32Wallet, - LightningCustodianWallet, - HDLegacyElectrumSeedP2PKHWallet, - HDSegwitElectrumSeedP2WPKHWallet, - HDAezeedWallet, - MultisigHDWallet, - LightningLdkWallet, - SLIP39SegwitP2SHWallet, - SLIP39LegacyP2PKHWallet, - SLIP39SegwitBech32Wallet, -} from './class/'; -import { randomBytes } from './class/rng'; -import alert from './components/Alert'; - -const encryption = require('./blue_modules/encryption'); -const Realm = require('realm'); -const createHash = require('create-hash'); -let usedBucketNum = false; -let savingInProgress = 0; // its both a flag and a counter of attempts to write to disk -const prompt = require('./helpers/prompt'); -const currency = require('./blue_modules/currency'); -const BlueElectrum = require('./blue_modules/BlueElectrum'); -BlueElectrum.connectMain(); - -class AppStorage { - static FLAG_ENCRYPTED = 'data_encrypted'; - static LNDHUB = 'lndhub'; - static ADVANCED_MODE_ENABLED = 'advancedmodeenabled'; - static DO_NOT_TRACK = 'donottrack'; - static HANDOFF_STORAGE_KEY = 'HandOff'; - - static keys2migrate = [AppStorage.HANDOFF_STORAGE_KEY, AppStorage.DO_NOT_TRACK, AppStorage.ADVANCED_MODE_ENABLED]; - - constructor() { - /** {Array.} */ - this.wallets = []; - this.tx_metadata = {}; - this.cachedPassword = false; - } - - async migrateKeys() { - if (!(typeof navigator !== 'undefined' && navigator.product === 'ReactNative')) return; - for (const key of this.constructor.keys2migrate) { - try { - const value = await RNSecureKeyStore.get(key); - if (value) { - await AsyncStorage.setItem(key, value); - await RNSecureKeyStore.remove(key); - } - } catch (_) {} - } - } - - /** - * Wrapper for storage call. Secure store works only in RN environment. AsyncStorage is - * used for cli/tests - * - * @param key - * @param value - * @returns {Promise|Promise | Promise | * | Promise | void} - */ - setItem = (key, value) => { - if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') { - return RNSecureKeyStore.set(key, value, { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY }); - } else { - return AsyncStorage.setItem(key, value); - } - }; - - /** - * Wrapper for storage call. Secure store works only in RN environment. AsyncStorage is - * used for cli/tests - * - * @param key - * @returns {Promise|*} - */ - getItem = key => { - if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') { - return RNSecureKeyStore.get(key); - } else { - return AsyncStorage.getItem(key); - } - }; - - /** - * @throws Error - * @param key {string} - * @returns {Promise<*>|null} - */ - getItemWithFallbackToRealm = async key => { - let value; - try { - return await this.getItem(key); - } catch (error) { - 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 - value = obj?.value; - realmKeyValue.close(); - if (value) { - console.warn('successfully recovered', value.length, 'bytes from realm for key', key); - return value; - } - return null; - } - }; - - storageIsEncrypted = async () => { - let data; - try { - data = await this.getItemWithFallbackToRealm(AppStorage.FLAG_ENCRYPTED); - } catch (error) { - console.warn('error reading `' + AppStorage.FLAG_ENCRYPTED + '` key:', error.message); - return false; - } - - return !!data; - }; - - isPasswordInUse = async password => { - try { - let data = await this.getItem('data'); - data = this.decryptData(data, password); - return !!data; - } catch (_e) { - return false; - } - }; - - /** - * Iterates through all values of `data` trying to - * decrypt each one, and returns first one successfully decrypted - * - * @param data {string} Serialized array - * @param password - * @returns {boolean|string} Either STRING of storage data (which is stringified JSON) or FALSE, which means failure - */ - decryptData(data, password) { - data = JSON.parse(data); - let decrypted; - let num = 0; - for (const value of data) { - decrypted = encryption.decrypt(value, password); - - if (decrypted) { - usedBucketNum = num; - return decrypted; - } - num++; - } - - return false; - } - - decryptStorage = async password => { - if (password === this.cachedPassword) { - this.cachedPassword = undefined; - await this.saveToDisk(); - this.wallets = []; - this.tx_metadata = []; - return this.loadFromDisk(); - } else { - throw new Error('Incorrect password. Please, try again.'); - } - }; - - encryptStorage = async password => { - // assuming the storage is not yet encrypted - await this.saveToDisk(); - let data = await this.getItem('data'); - // TODO: refactor ^^^ (should not save & load to fetch data) - - const encrypted = encryption.encrypt(data, password); - data = []; - data.push(encrypted); // putting in array as we might have many buckets with storages - data = JSON.stringify(data); - this.cachedPassword = password; - await this.setItem('data', data); - await this.setItem(AppStorage.FLAG_ENCRYPTED, '1'); - }; - - /** - * Cleans up all current application data (wallets, tx metadata etc) - * Encrypts the bucket and saves it storage - * - * @returns {Promise.} Success or failure - */ - createFakeStorage = async fakePassword => { - usedBucketNum = false; // resetting currently used bucket so we wont overwrite it - this.wallets = []; - this.tx_metadata = {}; - - const data = { - wallets: [], - tx_metadata: {}, - }; - - let buckets = await this.getItem('data'); - buckets = JSON.parse(buckets); - buckets.push(encryption.encrypt(JSON.stringify(data), fakePassword)); - this.cachedPassword = fakePassword; - const bucketsString = JSON.stringify(buckets); - await this.setItem('data', bucketsString); - return (await this.getItem('data')) === bucketsString; - }; - - hashIt = s => { - return createHash('sha256').update(s).digest().toString('hex'); - }; - - /** - * Returns instace of the Realm database, which is encrypted either by cached user's password OR default password. - * Database file is deterministically derived from encryption key. - * - * @returns {Promise} - */ - async getRealm() { - const password = this.hashIt(this.cachedPassword || 'fyegjitkyf[eqjnc.lf'); - const buf = Buffer.from(this.hashIt(password) + this.hashIt(password), 'hex'); - const encryptionKey = Int8Array.from(buf); - const path = this.hashIt(this.hashIt(password)) + '-wallettransactions.realm'; - - const schema = [ - { - name: 'WalletTransactions', - properties: { - walletid: { type: 'string', indexed: true }, - internal: 'bool?', // true - internal, false - external - index: 'int?', - tx: 'string', // stringified json - }, - }, - ]; - return Realm.open({ - schema, - path, - encryptionKey, - }); - } - - /** - * Returns instace of the Realm database, which is encrypted by device unique id - * Database file is static. - * - * @returns {Promise} - */ - async openRealmKeyValue() { - const service = 'realm_encryption_key'; - let password; - const credentials = await Keychain.getGenericPassword({ service }); - if (credentials) { - password = credentials.password; - } else { - const buf = await randomBytes(64); - password = buf.toString('hex'); - await Keychain.setGenericPassword(service, password, { service }); - } - - const buf = Buffer.from(password, 'hex'); - const encryptionKey = Int8Array.from(buf); - const path = 'keyvalue.realm'; - - const schema = [ - { - name: 'KeyValue', - primaryKey: 'key', - properties: { - key: { type: 'string', indexed: true }, - value: 'string', // stringified json, or whatever - }, - }, - ]; - return Realm.open({ - schema, - path, - encryptionKey, - }); - } - - saveToRealmKeyValue(realmkeyValue, key, value) { - realmkeyValue.write(() => { - realmkeyValue.create( - 'KeyValue', - { - key, - value, - }, - Realm.UpdateMode.Modified, - ); - }); - } - - /** - * Loads from storage all wallets and - * maps them to `this.wallets` - * - * @param password If present means storage must be decrypted before usage - * @returns {Promise.} - */ - async loadFromDisk(password) { - let data = await this.getItemWithFallbackToRealm('data'); - if (password) { - data = this.decryptData(data, password); - if (data) { - // password is good, cache it - this.cachedPassword = password; - } - } - if (data !== null) { - let realm; - try { - realm = await this.getRealm(); - } catch (error) { - alert(error.message); - } - data = JSON.parse(data); - if (!data.wallets) return false; - const wallets = data.wallets; - for (const key of wallets) { - // deciding which type is wallet and instatiating correct object - const tempObj = JSON.parse(key); - let unserializedWallet; - switch (tempObj.type) { - case SegwitBech32Wallet.type: - unserializedWallet = SegwitBech32Wallet.fromJson(key); - break; - case SegwitP2SHWallet.type: - unserializedWallet = SegwitP2SHWallet.fromJson(key); - break; - case WatchOnlyWallet.type: - unserializedWallet = WatchOnlyWallet.fromJson(key); - unserializedWallet.init(); - if (unserializedWallet.isHd() && !unserializedWallet.isXpubValid()) { - continue; - } - break; - case HDLegacyP2PKHWallet.type: - unserializedWallet = HDLegacyP2PKHWallet.fromJson(key); - break; - case HDSegwitP2SHWallet.type: - unserializedWallet = HDSegwitP2SHWallet.fromJson(key); - break; - case HDSegwitBech32Wallet.type: - unserializedWallet = HDSegwitBech32Wallet.fromJson(key); - break; - case HDLegacyBreadwalletWallet.type: - unserializedWallet = HDLegacyBreadwalletWallet.fromJson(key); - break; - case HDLegacyElectrumSeedP2PKHWallet.type: - unserializedWallet = HDLegacyElectrumSeedP2PKHWallet.fromJson(key); - break; - case HDSegwitElectrumSeedP2WPKHWallet.type: - unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key); - break; - case MultisigHDWallet.type: - unserializedWallet = MultisigHDWallet.fromJson(key); - break; - case HDAezeedWallet.type: - unserializedWallet = HDAezeedWallet.fromJson(key); - // migrate password to this.passphrase field - // remove this code somewhere in year 2022 - if (unserializedWallet.secret.includes(':')) { - const [mnemonic, passphrase] = unserializedWallet.secret.split(':'); - unserializedWallet.secret = mnemonic; - unserializedWallet.passphrase = passphrase; - } - - break; - case LightningLdkWallet.type: - unserializedWallet = LightningLdkWallet.fromJson(key); - break; - case SLIP39SegwitP2SHWallet.type: - unserializedWallet = SLIP39SegwitP2SHWallet.fromJson(key); - break; - case SLIP39LegacyP2PKHWallet.type: - unserializedWallet = SLIP39LegacyP2PKHWallet.fromJson(key); - break; - case SLIP39SegwitBech32Wallet.type: - unserializedWallet = SLIP39SegwitBech32Wallet.fromJson(key); - break; - case LightningCustodianWallet.type: { - /** @type {LightningCustodianWallet} */ - unserializedWallet = LightningCustodianWallet.fromJson(key); - let lndhub = false; - try { - lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB); - } catch (error) { - console.warn(error); - } - - if (unserializedWallet.baseURI) { - unserializedWallet.setBaseURI(unserializedWallet.baseURI); // not really necessary, just for the sake of readability - console.log('using saved uri for for ln wallet:', unserializedWallet.baseURI); - } else if (lndhub) { - console.log('using wallet-wide settings ', lndhub, 'for ln wallet'); - unserializedWallet.setBaseURI(lndhub); - } else { - console.log('wallet does not have a baseURI. Continuing init...'); - } - unserializedWallet.init(); - break; - } - case LegacyWallet.type: - default: - unserializedWallet = LegacyWallet.fromJson(key); - break; - } - - try { - if (realm) this.inflateWalletFromRealm(realm, unserializedWallet); - } catch (error) { - alert(error.message); - } - - // done - const ID = unserializedWallet.getID(); - if (!this.wallets.some(wallet => wallet.getID() === ID)) { - this.wallets.push(unserializedWallet); - this.tx_metadata = data.tx_metadata; - } - } - if (realm) realm.close(); - return true; - } else { - return false; // failed loading data or loading/decryptin data - } - } - - /** - * Lookup wallet in list by it's secret and - * remove it from `this.wallets` - * - * @param wallet {AbstractWallet} - */ - deleteWallet = wallet => { - const ID = wallet.getID(); - const tempWallets = []; - - if (wallet.type === LightningLdkWallet.type) { - /** @type {LightningLdkWallet} */ - const ldkwallet = wallet; - ldkwallet.stop().then(ldkwallet.purgeLocalStorage).catch(alert); - } - - for (const value of this.wallets) { - if (value.getID() === ID) { - // the one we should delete - // nop - } else { - // the one we must keep - tempWallets.push(value); - } - } - this.wallets = tempWallets; - }; - - inflateWalletFromRealm(realm, walletToInflate) { - const transactions = realm.objects('WalletTransactions'); - const transactionsForWallet = transactions.filtered(`walletid = "${walletToInflate.getID()}"`); - for (const tx of transactionsForWallet) { - if (tx.internal === false) { - if (walletToInflate._hdWalletInstance) { - walletToInflate._hdWalletInstance._txs_by_external_index[tx.index] = - walletToInflate._hdWalletInstance._txs_by_external_index[tx.index] || []; - walletToInflate._hdWalletInstance._txs_by_external_index[tx.index].push(JSON.parse(tx.tx)); - } else { - walletToInflate._txs_by_external_index[tx.index] = walletToInflate._txs_by_external_index[tx.index] || []; - walletToInflate._txs_by_external_index[tx.index].push(JSON.parse(tx.tx)); - } - } else if (tx.internal === true) { - if (walletToInflate._hdWalletInstance) { - walletToInflate._hdWalletInstance._txs_by_internal_index[tx.index] = - walletToInflate._hdWalletInstance._txs_by_internal_index[tx.index] || []; - walletToInflate._hdWalletInstance._txs_by_internal_index[tx.index].push(JSON.parse(tx.tx)); - } else { - walletToInflate._txs_by_internal_index[tx.index] = walletToInflate._txs_by_internal_index[tx.index] || []; - walletToInflate._txs_by_internal_index[tx.index].push(JSON.parse(tx.tx)); - } - } else { - if (!Array.isArray(walletToInflate._txs_by_external_index)) walletToInflate._txs_by_external_index = []; - walletToInflate._txs_by_external_index = walletToInflate._txs_by_external_index || []; - walletToInflate._txs_by_external_index.push(JSON.parse(tx.tx)); - } - } - } - - offloadWalletToRealm(realm, wallet) { - const id = wallet.getID(); - const walletToSave = 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); - - 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 - const walletTransactionsToDelete = realm.objects('WalletTransactions').filtered(`walletid = '${id}'`); - realm.delete(walletTransactionsToDelete); - - // insert new ones: - for (const index of Object.keys(walletToSave._txs_by_external_index)) { - const txs = walletToSave._txs_by_external_index[index]; - for (const tx of txs) { - realm.create( - 'WalletTransactions', - { - walletid: id, - internal: false, - index: parseInt(index, 10), - tx: JSON.stringify(tx), - }, - Realm.UpdateMode.Modified, - ); - } - } - - for (const index of Object.keys(walletToSave._txs_by_internal_index)) { - const txs = walletToSave._txs_by_internal_index[index]; - for (const tx of txs) { - realm.create( - 'WalletTransactions', - { - walletid: id, - internal: true, - index: parseInt(index, 10), - tx: JSON.stringify(tx), - }, - Realm.UpdateMode.Modified, - ); - } - } - }); - } - } - - /** - * Serializes and saves to storage object data. - * If cached password is saved - finds the correct bucket - * to save to, encrypts and then saves. - * - * @returns {Promise} Result of storage save - */ - async saveToDisk() { - if (savingInProgress) { - console.warn('saveToDisk is in progress'); - if (++savingInProgress > 10) alert('Critical error. Last actions were not saved'); // should never happen - await new Promise(resolve => setTimeout(resolve, 1000 * savingInProgress)); // sleep - return this.saveToDisk(); - } - savingInProgress = 1; - - try { - const walletsToSave = []; - let realm; - try { - realm = await this.getRealm(); - } catch (error) { - alert(error.message); - } - for (const key of this.wallets) { - if (typeof key === 'boolean') continue; - key.prepareForSerialization(); - delete key.current; - const keyCloned = Object.assign({}, key); // stripped-down version of a wallet to save to secure keystore - if (key._hdWalletInstance) keyCloned._hdWalletInstance = Object.assign({}, key._hdWalletInstance); - if (realm) this.offloadWalletToRealm(realm, key); - // stripping down: - if (key._txs_by_external_index) { - keyCloned._txs_by_external_index = {}; - keyCloned._txs_by_internal_index = {}; - } - if (key._hdWalletInstance) { - keyCloned._hdWalletInstance._txs_by_external_index = {}; - keyCloned._hdWalletInstance._txs_by_internal_index = {}; - } - - if (keyCloned._bip47_instance) { - delete keyCloned._bip47_instance; // since it wont be restored into a proper class instance - } - - walletsToSave.push(JSON.stringify({ ...keyCloned, type: keyCloned.type })); - } - if (realm) realm.close(); - let data = { - wallets: walletsToSave, - tx_metadata: this.tx_metadata, - }; - - if (this.cachedPassword) { - // should find the correct bucket, encrypt and then save - let buckets = await this.getItemWithFallbackToRealm('data'); - buckets = JSON.parse(buckets); - const newData = []; - let num = 0; - for (const bucket of buckets) { - let decrypted; - // if we had `usedBucketNum` during loadFromDisk(), no point to try to decode each bucket to find the one we - // need, we just to find bucket with the same index - if (usedBucketNum !== false) { - if (num === usedBucketNum) { - decrypted = true; - } - num++; - } else { - // we dont have `usedBucketNum` for whatever reason, so lets try to decrypt each bucket after bucket - // till we find the right one - decrypted = encryption.decrypt(bucket, this.cachedPassword); - } - - if (!decrypted) { - // no luck decrypting, its not our bucket - newData.push(bucket); - } else { - // decrypted ok, this is our bucket - // we serialize our object's data, encrypt it, and add it to buckets - newData.push(encryption.encrypt(JSON.stringify(data), this.cachedPassword)); - } - } - data = newData; - } - - await this.setItem('data', JSON.stringify(data)); - await this.setItem(AppStorage.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); - - // now, backing up same data in realm: - const realmkeyValue = await this.openRealmKeyValue(); - this.saveToRealmKeyValue(realmkeyValue, 'data', JSON.stringify(data)); - this.saveToRealmKeyValue(realmkeyValue, AppStorage.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); - realmkeyValue.close(); - } catch (error) { - console.error('save to disk exception:', error.message); - alert('save to disk exception: ' + error.message); - if (error.message.includes('Realm file decryption failed')) { - console.warn('purging realm key-value database file'); - this.purgeRealmKeyValueFile(); - } - } finally { - savingInProgress = 0; - } - } - - /** - * For each wallet, fetches balance from remote endpoint. - * Use getter for a specific wallet to get actual balance. - * Returns void. - * If index is present then fetch only from this specific wallet - * - * @return {Promise.} - */ - fetchWalletBalances = async index => { - console.log('fetchWalletBalances for wallet#', typeof index === 'undefined' ? '(all)' : index); - if (index || index === 0) { - let c = 0; - for (const wallet of this.wallets) { - if (c++ === index) { - await wallet.fetchBalance(); - } - } - } else { - for (const wallet of this.wallets) { - console.log('fetching balance for', wallet.getLabel()); - await wallet.fetchBalance(); - } - } - }; - - /** - * Fetches from remote endpoint all transactions for each wallet. - * Returns void. - * To access transactions - get them from each respective wallet. - * If index is present then fetch only from this specific wallet. - * - * @param index {Integer} Index of the wallet in this.wallets array, - * blank to fetch from all wallets - * @return {Promise.} - */ - fetchWalletTransactions = async index => { - console.log('fetchWalletTransactions for wallet#', typeof index === 'undefined' ? '(all)' : index); - if (index || index === 0) { - let c = 0; - for (const wallet of this.wallets) { - if (c++ === index) { - await wallet.fetchTransactions(); - if (wallet.fetchPendingTransactions) { - await wallet.fetchPendingTransactions(); - } - if (wallet.fetchUserInvoices) { - await wallet.fetchUserInvoices(); - } - } - } - } else { - for (const wallet of this.wallets) { - await wallet.fetchTransactions(); - if (wallet.fetchPendingTransactions) { - await wallet.fetchPendingTransactions(); - } - if (wallet.fetchUserInvoices) { - await wallet.fetchUserInvoices(); - } - } - } - }; - - fetchSenderPaymentCodes = async index => { - console.log('fetchSenderPaymentCodes for wallet#', typeof index === 'undefined' ? '(all)' : index); - if (index || index === 0) { - try { - if (!(this.wallets[index].allowBIP47() && this.wallets[index].isBIP47Enabled())) return; - await this.wallets[index].fetchBIP47SenderPaymentCodes(); - } catch (error) { - console.error('Failed to fetch sender payment codes for wallet', index, error); - } - } else { - for (const wallet of this.wallets) { - try { - if (!(wallet.allowBIP47() && wallet.isBIP47Enabled())) continue; - await wallet.fetchBIP47SenderPaymentCodes(); - } catch (error) { - console.error('Failed to fetch sender payment codes for wallet', wallet.label, error); - } - } - } - }; - - /** - * - * @returns {Array.} - */ - getWallets = () => { - return this.wallets; - }; - - /** - * Getter for all transactions in all wallets. - * But if index is provided - only for wallet with corresponding index - * - * @param index {Integer|null} Wallet index in this.wallets. Empty (or null) for all wallets. - * @param limit {Integer} How many txs return, starting from the earliest. Default: all of them. - * @param includeWalletsWithHideTransactionsEnabled {Boolean} Wallets' _hideTransactionsInWalletsList property determines wether the user wants this wallet's txs hidden from the main list view. - * @return {Array} - */ - getTransactions = (index, limit = Infinity, includeWalletsWithHideTransactionsEnabled = false) => { - if (index || index === 0) { - let txs = []; - let c = 0; - for (const wallet of this.wallets) { - if (c++ === index) { - txs = txs.concat(wallet.getTransactions()); - } - } - return txs; - } - - let txs = []; - for (const wallet of this.wallets.filter(w => includeWalletsWithHideTransactionsEnabled || !w.getHideTransactionsInWalletsList())) { - const walletTransactions = wallet.getTransactions(); - const walletID = wallet.getID(); - for (const t of walletTransactions) { - t.walletPreferredBalanceUnit = wallet.getPreferredBalanceUnit(); - t.walletID = walletID; - } - txs = txs.concat(walletTransactions); - } - - for (const t of txs) { - t.sort_ts = +new Date(t.received); - } - - return txs - .sort(function (a, b) { - return b.sort_ts - a.sort_ts; - }) - .slice(0, limit); - }; - - /** - * Getter for a sum of all balances of all wallets - * - * @return {number} - */ - getBalance = () => { - let finalBalance = 0; - for (const wal of this.wallets) { - finalBalance += wal.getBalance(); - } - return finalBalance; - }; - - isAdvancedModeEnabled = async () => { - try { - return !!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED)); - } catch (_) {} - return false; - }; - - setIsAdvancedModeEnabled = async value => { - await AsyncStorage.setItem(AppStorage.ADVANCED_MODE_ENABLED, value ? '1' : ''); - }; - - isHandoffEnabled = async () => { - try { - return !!(await AsyncStorage.getItem(AppStorage.HANDOFF_STORAGE_KEY)); - } catch (_) {} - return false; - }; - - setIsHandoffEnabled = async value => { - await AsyncStorage.setItem(AppStorage.HANDOFF_STORAGE_KEY, value ? '1' : ''); - }; - - isDoNotTrackEnabled = async () => { - try { - return !!(await AsyncStorage.getItem(AppStorage.DO_NOT_TRACK)); - } catch (_) {} - return false; - }; - - setDoNotTrack = async value => { - await AsyncStorage.setItem(AppStorage.DO_NOT_TRACK, value ? '1' : ''); - }; - - /** - * Simple async sleeper function - * - * @param ms {number} Milliseconds to sleep - * @returns {Promise | Promise<*>>} - */ - sleep = ms => { - return new Promise(resolve => setTimeout(resolve, ms)); - }; - - purgeRealmKeyValueFile() { - const path = 'keyvalue.realm'; - return Realm.deleteFile({ - path, - }); - } -} - -const BlueApp = new AppStorage(); -// If attempt reaches 10, a wipe keychain option will be provided to the user. -let unlockAttempt = 0; - -const startAndDecrypt = async retry => { - console.log('startAndDecrypt'); - if (BlueApp.getWallets().length > 0) { - console.log('App already has some wallets, so we are in already started state, exiting startAndDecrypt'); - return true; - } - await BlueApp.migrateKeys(); - let password = false; - if (await BlueApp.storageIsEncrypted()) { - do { - password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, false); - } while (!password); - } - let success = false; - let wasException = false; - try { - success = await BlueApp.loadFromDisk(password); - } catch (error) { - // in case of exception reading from keystore, lets retry instead of assuming there is no storage and - // proceeding with no wallets - console.warn('exception loading from disk:', error); - wasException = true; - } - - if (wasException) { - // retrying, but only once - try { - await new Promise(resolve => setTimeout(resolve, 3000)); // sleep - success = await BlueApp.loadFromDisk(password); - } catch (error) { - console.warn('second exception loading from disk:', error); - } - } - - if (success) { - console.log('loaded from disk'); - // We want to return true to let the UnlockWith screen that its ok to proceed. - return true; - } - - if (password) { - // we had password and yet could not load/decrypt - unlockAttempt++; - if (unlockAttempt < 10 || Platform.OS !== 'ios') { - return startAndDecrypt(true); - } else { - unlockAttempt = 0; - Biometric.showKeychainWipeAlert(); - // We want to return false to let the UnlockWith screen that it is NOT ok to proceed. - return false; - } - } else { - unlockAttempt = 0; - // Return true because there was no wallet data in keychain. Proceed. - return true; - } -}; - -BlueApp.startAndDecrypt = startAndDecrypt; -BlueApp.AppStorage = AppStorage; -currency.init(); - -module.exports = BlueApp; diff --git a/BlueComponents.js b/BlueComponents.js deleted file mode 100644 index bb49e91b87c..00000000000 --- a/BlueComponents.js +++ /dev/null @@ -1,870 +0,0 @@ -/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */ -import React, { Component, forwardRef } from 'react'; -import PropTypes from 'prop-types'; -import { Icon, Text, Header, ListItem, Avatar } from 'react-native-elements'; -import { - ActivityIndicator, - Alert, - Animated, - Dimensions, - Image, - InputAccessoryView, - Keyboard, - KeyboardAvoidingView, - Platform, - SafeAreaView, - StyleSheet, - Switch, - TextInput, - TouchableOpacity, - View, - I18nManager, - ImageBackground, -} from 'react-native'; -import Clipboard from '@react-native-clipboard/clipboard'; -import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from './models/networkTransactionFees'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { useTheme } from '@react-navigation/native'; -import { BlueCurrentTheme } from './components/themes'; -import PlusIcon from './components/icons/PlusIcon'; -import loc, { formatStringAddTwoWhiteSpaces } from './loc'; - -const { height, width } = Dimensions.get('window'); -const aspectRatio = height / width; -let isIpad; -if (aspectRatio > 1.6) { - isIpad = false; -} else { - isIpad = true; -} - -export const BlueButton = props => { - const { colors } = useTheme(); - - let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.mainColor || BlueCurrentTheme.colors.mainColor; - let fontColor = props.buttonTextColor || colors.buttonTextColor; - if (props.disabled === true) { - backgroundColor = colors.buttonDisabledBackgroundColor; - fontColor = colors.buttonDisabledTextColor; - } - - return ( - - - {props.icon && } - {props.title && {props.title}} - - - ); -}; - -export const SecondButton = forwardRef((props, ref) => { - const { colors } = useTheme(); - let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.buttonBlueBackgroundColor; - let fontColor = colors.buttonTextColor; - if (props.disabled === true) { - backgroundColor = colors.buttonDisabledBackgroundColor; - fontColor = colors.buttonDisabledTextColor; - } - - return ( - - - {props.icon && } - {props.title && {props.title}} - - - ); -}); - -export const BitcoinButton = props => { - const { colors } = useTheme(); - return ( - - - - - - - - - {loc.wallets.add_bitcoin} - - - {loc.wallets.add_bitcoin_explain} - - - - - - ); -}; - -export const VaultButton = props => { - const { colors } = useTheme(); - return ( - - - - - - - - - {loc.multisig.multisig_vault} - - - {loc.multisig.multisig_vault_explain} - - - - - - ); -}; - -export const LightningButton = props => { - const { colors } = useTheme(); - return ( - - - - - - - - - {loc.wallets.add_lightning} - - - {loc.wallets.add_lightning_explain} - - - - - - ); -}; - -/** - * TODO: remove this comment once this file gets properly converted to typescript. - * - * @type {React.FC} - */ -export const BlueButtonLink = forwardRef((props, ref) => { - const { colors } = useTheme(); - return ( - - {props.title} - - ); -}); - -export const BlueAlertWalletExportReminder = ({ onSuccess = () => {}, onFailure }) => { - Alert.alert( - loc.wallets.details_title, - loc.pleasebackup.ask, - [ - { text: loc.pleasebackup.ask_yes, onPress: onSuccess, style: 'cancel' }, - { text: loc.pleasebackup.ask_no, onPress: onFailure }, - ], - { cancelable: false }, - ); -}; - -export const BluePrivateBalance = () => { - return ( - - - - - ); -}; - -export const BlueCopyToClipboardButton = ({ stringToCopy, displayText = false }) => { - return ( - Clipboard.setString(stringToCopy)}> - {displayText || loc.transactions.details_copy} - - ); -}; - -export class BlueCopyTextToClipboard extends Component { - static propTypes = { - text: PropTypes.string, - truncated: PropTypes.bool, - }; - - static defaultProps = { - text: '', - truncated: false, - }; - - constructor(props) { - super(props); - this.state = { hasTappedText: false, address: props.text }; - } - - static getDerivedStateFromProps(props, state) { - if (state.hasTappedText) { - return { hasTappedText: state.hasTappedText, address: state.address, truncated: props.truncated }; - } else { - return { hasTappedText: state.hasTappedText, address: props.text, truncated: props.truncated }; - } - } - - copyToClipboard = () => { - this.setState({ hasTappedText: true }, () => { - Clipboard.setString(this.props.text); - this.setState({ address: loc.wallets.xpub_copiedToClipboard }, () => { - setTimeout(() => { - this.setState({ hasTappedText: false, address: this.props.text }); - }, 1000); - }); - }); - }; - - render() { - return ( - - - - {this.state.address} - - - - ); - } -} - -const styleCopyTextToClipboard = StyleSheet.create({ - address: { - marginVertical: 32, - fontSize: 15, - color: '#9aa0aa', - textAlign: 'center', - }, -}); - -export const SafeBlueArea = props => { - const { style, ...nonStyleProps } = props; - const { colors } = useTheme(); - const baseStyle = { flex: 1, backgroundColor: colors.background }; - return ; -}; - -export const BlueCard = props => { - return ; -}; - -export const BlueText = props => { - const { colors } = useTheme(); - const style = StyleSheet.compose({ color: colors.foregroundColor, writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr' }, props.style); - return ; -}; - -export const BlueTextCentered = props => { - const { colors } = useTheme(); - return ; -}; - -export const BlueListItem = React.memo(props => { - const { colors } = useTheme(); - - return ( - - {props.leftAvatar && {props.leftAvatar}} - {props.leftIcon && } - - - {props.title} - - {props.subtitle && ( - - {props.subtitle} - - )} - - {props.rightTitle && ( - - - {props.rightTitle} - - - )} - {props.isLoading ? ( - - ) : ( - <> - {props.chevron && } - {props.rightIcon && } - {props.switch && } - {props.checkmark && } - - )} - - ); -}); - -export const BlueFormLabel = props => { - const { colors } = useTheme(); - - return ( - - ); -}; - -export const BlueFormMultiInput = props => { - const { colors } = useTheme(); - - return ( - - ); -}; - -export const BlueHeaderDefaultSub = props => { - const { colors } = useTheme(); - - return ( - -
- {props.leftText} - - } - {...props} - /> - - ); -}; - -export const BlueHeaderDefaultMain = props => { - const { colors } = useTheme(); - const { isDrawerList } = props; - return ( - - - {props.leftText} - - - - ); -}; - -export const BlueSpacing = props => { - return ; -}; - -export const BlueSpacing40 = props => { - return ; -}; - -export class is { - static ipad() { - return isIpad; - } -} - -export const BlueSpacing20 = props => { - const { horizontal = false } = props; - return ; -}; - -export const BlueSpacing10 = props => { - return ; -}; - -export const BlueDismissKeyboardInputAccessory = () => { - const { colors } = useTheme(); - BlueDismissKeyboardInputAccessory.InputAccessoryViewID = 'BlueDismissKeyboardInputAccessory'; - - return Platform.OS !== 'ios' ? null : ( - - - - - - ); -}; - -export const BlueDoneAndDismissKeyboardInputAccessory = props => { - const { colors } = useTheme(); - BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID = 'BlueDoneAndDismissKeyboardInputAccessory'; - - const onPasteTapped = async () => { - const clipboard = await Clipboard.getString(); - props.onPasteTapped(clipboard); - }; - - const inputView = ( - - - - - - ); - - if (Platform.OS === 'ios') { - return {inputView}; - } else { - return {inputView}; - } -}; - -export const BlueLoading = props => { - return ( - - - - ); -}; - -export class BlueReplaceFeeSuggestions extends Component { - static propTypes = { - onFeeSelected: PropTypes.func.isRequired, - transactionMinimum: PropTypes.number.isRequired, - }; - - static defaultProps = { - transactionMinimum: 1, - }; - - state = { - customFeeValue: '1', - }; - - async componentDidMount() { - try { - const cachedNetworkTransactionFees = JSON.parse(await AsyncStorage.getItem(NetworkTransactionFee.StorageKey)); - - if (cachedNetworkTransactionFees && 'fastestFee' in cachedNetworkTransactionFees) { - this.setState({ networkFees: cachedNetworkTransactionFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST)); - } - } catch (_) {} - const networkFees = await NetworkTransactionFees.recommendedFees(); - this.setState({ networkFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST)); - } - - onFeeSelected = selectedFeeType => { - if (selectedFeeType !== NetworkTransactionFeeType.CUSTOM) { - Keyboard.dismiss(); - } - if (selectedFeeType === NetworkTransactionFeeType.FAST) { - this.props.onFeeSelected(this.state.networkFees.fastestFee); - this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.fastestFee)); - } else if (selectedFeeType === NetworkTransactionFeeType.MEDIUM) { - this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.mediumFee)); - } else if (selectedFeeType === NetworkTransactionFeeType.SLOW) { - this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.slowFee)); - } else if (selectedFeeType === NetworkTransactionFeeType.CUSTOM) { - this.props.onFeeSelected(Number(this.state.customFeeValue)); - } - }; - - onCustomFeeTextChange = customFee => { - const customFeeValue = customFee.replace(/[^0-9]/g, ''); - this.setState({ customFeeValue, selectedFeeType: NetworkTransactionFeeType.CUSTOM }, () => { - this.onFeeSelected(NetworkTransactionFeeType.CUSTOM); - }); - }; - - render() { - const { networkFees, selectedFeeType } = this.state; - - return ( - - {networkFees && - [ - { - label: loc.send.fee_fast, - time: loc.send.fee_10m, - type: NetworkTransactionFeeType.FAST, - rate: networkFees.fastestFee, - active: selectedFeeType === NetworkTransactionFeeType.FAST, - }, - { - label: formatStringAddTwoWhiteSpaces(loc.send.fee_medium), - time: loc.send.fee_3h, - type: NetworkTransactionFeeType.MEDIUM, - rate: networkFees.mediumFee, - active: selectedFeeType === NetworkTransactionFeeType.MEDIUM, - }, - { - label: loc.send.fee_slow, - time: loc.send.fee_1d, - type: NetworkTransactionFeeType.SLOW, - rate: networkFees.slowFee, - active: selectedFeeType === NetworkTransactionFeeType.SLOW, - }, - ].map(({ label, type, time, rate, active }, index) => ( - this.onFeeSelected(type)} - style={[ - { paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 }, - active && { borderRadius: 8, backgroundColor: BlueCurrentTheme.colors.incomingBackgroundColor }, - ]} - > - - {label} - - ~{time} - - - - {rate} sat/byte - - - ))} - this.customTextInput.focus()} - style={[ - { paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 }, - selectedFeeType === NetworkTransactionFeeType.CUSTOM && { - borderRadius: 8, - backgroundColor: BlueCurrentTheme.colors.incomingBackgroundColor, - }, - ]} - > - - - {formatStringAddTwoWhiteSpaces(loc.send.fee_custom)} - - - - (this.customTextInput = ref)} - maxLength={9} - style={{ - backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor, - borderBottomColor: BlueCurrentTheme.colors.formBorder, - borderBottomWidth: 0.5, - borderColor: BlueCurrentTheme.colors.formBorder, - borderRadius: 4, - borderWidth: 1.0, - color: '#81868e', - flex: 1, - marginRight: 10, - minHeight: 33, - paddingRight: 5, - paddingLeft: 5, - }} - onFocus={() => this.onCustomFeeTextChange(this.state.customFeeValue)} - defaultValue={this.props.transactionMinimum} - placeholder={loc.send.fee_satvbyte} - placeholderTextColor="#81868e" - inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID} - /> - sat/byte - - - - {loc.formatString(loc.send.fee_replace_minvb, { min: this.props.transactionMinimum })} - - - ); - } -} - -export function BlueBigCheckmark({ style }) { - const defaultStyles = { - backgroundColor: '#ccddf9', - width: 120, - height: 120, - borderRadius: 60, - alignSelf: 'center', - justifyContent: 'center', - marginTop: 0, - marginBottom: 0, - }; - const mergedStyles = { ...defaultStyles, ...style }; - return ( - - - - ); -} - -const tabsStyles = StyleSheet.create({ - root: { - flexDirection: 'row', - height: 50, - borderColor: '#e3e3e3', - borderBottomWidth: 1, - }, - tabRoot: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - borderColor: 'white', - borderBottomWidth: 2, - }, -}); - -export const BlueTabs = ({ active, onSwitch, tabs }) => ( - - {tabs.map((Tab, i) => ( - onSwitch(i)} - style={[ - tabsStyles.tabRoot, - active === i && { - borderColor: BlueCurrentTheme.colors.buttonAlternativeTextColor, - borderBottomWidth: 2, - }, - ]} - > - - - ))} - -); diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..124c3105103 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,76 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +BlueWallet is a Bitcoin & Lightning Network wallet built with React Native and Electrum. Cross-platform mobile app (iOS/Android/macOS via Catalyst). + +## Common Commands + +```bash +# Development +npm start # Start Metro bundler +npm run ios # Run on iOS +npm run android # Run on Android + +# Testing +npm test # Full suite (lint + unit + integration) +npm run lint # ESLint + TypeScript check + unused loc keys +npm run lint:fix # Auto-fix linting issues +npm run unit # Jest unit tests only + +# E2E Testing (Detox) +npm run e2e:debug # Debug build and test on Android +npm run e2e:release-test # Release build test + +# Clean builds +npm run clean # Full clean (gradle, cache, node_modules) +npm run clean:ios # iOS clean (Pods + node_modules) +npm run android:clean # Android clean +``` + +## Architecture + +**Directory Structure:** +- `components/` - React components and Context providers (SettingsProvider, StorageProvider) +- `class/` - Core business logic including wallet implementations in `class/wallets/` +- `blue_modules/` - Utility modules (BlueElectrum, currency, encryption, etc.) +- `screen/` - Navigation screens organized by feature (wallets, send, receive, settings, lnd) +- `navigation/` - React Navigation setup with typed param lists +- `hooks/` - Custom React hooks (useStorage, useSettings, useBiometrics, etc.) +- `loc/` - Localization files (en.json as source, 55+ languages) +- `models/` - Type definitions for units, fiat, block explorers +- `tests/unit/`, `tests/integration/`, `tests/e2e/` - Test suites + +**Wallet System:** +Multiple wallet implementations in `class/wallets/`: Legacy, SegWit (P2SH, Bech32), Taproot, HD variants, Lightning (Custodian, Ark), Multisig, Watch-only. Types defined in `class/wallets/types.ts`. + +**State Management:** +React Context providers wrap the app. Custom hooks expose state logic. Realm for database, AsyncStorage for persistence, Keychain for secrets. + +**Navigation:** +React Navigation 7.x with native stack. Typed params in `navigation/DetailViewStackParamList.ts` and other param list files. + +## Code Conventions + +**Commit Prefixes:** REL, FIX, ADD, REF, TST, OPS, DOC (e.g., `"ADD: new feature"`) + +**TypeScript:** All new files must be TypeScript. Strict mode enabled. + +**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:** +- No inline styles in React Native (`react-native/no-inline-styles`: error) +- No unused styles (`react-native/no-unused-styles`: error) +- Prettier: single quotes, 140 char width, trailing commas + +**Localization:** Keys in `loc/en.json`. Run `find-unused-loc.js` to detect unused keys. See `loc/vocabulary.md` for the canonical glossary of Bitcoin/Lightning terms and their per-language renderings — use it as ground truth when translating or generating translations with LLMs. + +## Testing + +Unit tests in `tests/unit/` use Jest with `assert`. Test setup mocks React Native modules (Clipboard, Push Notifications, Keychain, etc.). Integration tests require environment variables for test mnemonics (HD_MNEMONIC, HD_MNEMONIC_BIP84, etc.). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 734eb4d9c2f..6607cf867c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,13 @@ -All commits should have one of the following prefixes: REL, FIX, ADD, TST, OPS, DOC. For example `"ADD: new feature"`. -Adding new feature is ADD, fixing a bug is FIX, something related to infrastructure is OPS etc. +## Commits + +All commits should have one of the following prefixes: REL, FIX, ADD, REF, TST, OPS, DOC. For example `"ADD: new feature"`. +Adding new feature is ADD, fixing a bug is FIX, something related to infrastructure is OPS etc. REL is for releases, REF is for +refactoring, DOC is for changing documentation (like this file). Commits should be atomic: one commit - one feature, one commit - one bugfix etc. +## Releases + When you tag a new release, use the following example: `git tag -m "REL v1.4.0: 157c9c2" v1.4.0 -s` You may get the commit hash from git log. Don't forget to push tags `git push origin --tags` @@ -13,5 +18,16 @@ When tagging a new release, make sure to increment version in package.json and o In the commit where you up version you can have the commit message as `"REL vX.X.X: Summary message"`. +## Guidelines Do *not* add new dependencies. Bonus points if you manage to actually remove a dependency. + +All new files must be in typescript. Bonus points if you convert some of the existing files to typescript. + +New components must go in `components/`. Bonus points if you refactor some of old components in `BlueComponents.js` to separate files. + +Don't forget to add tests. Bonus points for e2e tests. + +# PRs + +When submitting PR, it must include screenshot (from the emulator or the device) how the proposed change looks, even better - a video; and a short description of why (it was implemented) and how (it works under the hood). \ No newline at end of file diff --git a/Gemfile b/Gemfile index 67a72298669..a93f4a74749 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,22 @@ -source 'https://rubygems.org' +source "https://rubygems.org" # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version -ruby '>= 2.6.10' +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 'cocoapods', '~> 1.11', '>= 1.11.3' \ No newline at end of file +# Ruby 3.4.0 removed these from the standard library +gem 'bigdecimal' +gem 'logger' +gem 'benchmark' +gem 'mutex_m' + +# Required for App Store Connect API +gem "jwt" + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock index 285424cd4b6..9ab0713462b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,30 +1,60 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.5) - rexml - activesupport (6.1.4.4) - concurrent-ruby (~> 1.0, >= 1.0.2) + CFPropertyList (3.0.8) + abbrev (0.1.2) + activesupport (7.2.3.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + logger (>= 1.4.2) + minitest (>= 5.1, < 6) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) + artifactory (3.0.17) atomos (0.1.3) + aws-eventstream (1.4.0) + aws-partitions (1.1252.0) + aws-sdk-core (3.247.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.127.0) + aws-sdk-core (~> 3, >= 3.247.0) + aws-sigv4 (~> 1.5) + 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.3.0) + benchmark (0.5.0) + bigdecimal (4.1.2) claide (1.1.0) - cocoapods (1.11.2) + cocoapods (1.15.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.2) + cocoapods-core (= 1.15.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) @@ -32,10 +62,10 @@ GEM gh_inspector (~> 1.0) molinillo (~> 0.8.0) nap (~> 1.0) - ruby-macho (>= 1.0, < 3.0) - xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.11.2) - activesupport (>= 5.0, < 7) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.23.0, < 2.0) + cocoapods-core (1.15.2) + activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) concurrent-ruby (~> 1.1) @@ -45,7 +75,7 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.5.1) + cocoapods-downloader (2.1) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -53,48 +83,408 @@ GEM nap (>= 0.8, < 2.0) netrc (~> 0.11) cocoapods-try (1.2.0) + colored (1.2) colored2 (3.1.2) - concurrent-ruby (1.1.9) + commander (4.6.0) + highline (~> 2.0.0) + concurrent-ruby (1.3.3) + connection_pool (3.0.2) + csv (3.3.5) + declarative (0.0.20) + digest-crc (0.7.0) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + drb (2.2.3) + emoji_regex (3.2.3) escape (0.0.4) - ethon (0.15.0) + ethon (0.18.0) ffi (>= 1.15.0) - ffi (1.15.5) + logger + excon (0.112.0) + faraday (1.10.5) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.8) + faraday (>= 0.8.0) + http-cookie (>= 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.1) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.2.0) + multipart-post (~> 2.0) + faraday-net_http (1.0.2) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.4) + faraday_middleware (1.2.1) + faraday (~> 1.0) + fastimage (2.4.1) + fastlane (2.234.0) + CFPropertyList (>= 2.3, < 5.0.0) + abbrev (~> 0.1) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.197) + babosa (>= 1.0.3, < 2.0.0) + base64 (~> 0.2) + benchmark (>= 0.1.0) + bundler (>= 1.17.3, < 5.0.0) + colored (~> 1.2) + commander (~> 4.6) + csv (~> 3.3) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.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) + google-cloud-env (>= 1.6.0, <= 2.1.1) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + 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) + naturally (~> 2.2) + nkf (~> 0.2) + optparse (>= 0.1.1, < 1.0.0) + ostruct (>= 0.1.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.4.1) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-browserstack (0.3.4) + rest-client (~> 2.0, >= 2.0.2) + fastlane-plugin-bugsnag (3.0.0) + abbrev + git + xml-simple + fastlane-plugin-bugsnag_sourcemaps_upload (0.2.0) + fastlane-sirp (1.1.0) + ffi (1.17.3) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - httpclient (2.8.3) - i18n (1.9.1) + git (4.3.1) + activesupport (>= 5.0) + addressable (~> 2.8) + process_executer (~> 4.0) + rchardet (~> 1.9) + 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) + googleauth (~> 1.9) + httpclient (>= 2.8.3, < 3.a) + mini_mime (~> 1.0) + mutex_m + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + 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.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.6.0) + google-cloud-storage (1.60.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-core (>= 0.18, < 2) + google-apis-iamcredentials_v1 (~> 0.18) + google-apis-storage_v1 (>= 0.42) + google-cloud-core (~> 1.6) + googleauth (~> 1.9) + mini_mime (~> 1.0) + googleauth (1.11.2) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.1) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-accept (1.7.0) + http-cookie (1.0.8) + domain_name (~> 0.5) + httpclient (2.9.0) + mutex_m + i18n (1.14.8) concurrent-ruby (~> 1.0) - json (2.6.1) - minitest (5.15.0) + jmespath (1.6.2) + json (2.19.5) + jwt (2.10.2) + base64 + logger (1.7.0) + mime-types (3.7.0) + logger + mime-types-data (~> 3.2025, >= 3.2025.0507) + mime-types-data (3.2026.0317) + mini_magick (4.13.2) + mini_mime (1.1.5) + minitest (5.27.0) molinillo (0.8.0) + multi_json (1.21.1) + multipart-post (2.4.1) + mutex_m (0.3.0) nanaimo (0.3.0) nap (1.1.0) + naturally (2.3.0) netrc (0.11.0) - public_suffix (4.0.6) - rexml (3.2.5) + nkf (0.2.0) + optparse (0.8.1) + os (1.1.4) + ostruct (0.6.3) + plist (3.7.2) + process_executer (4.0.2) + track_open_instances (~> 0.1) + public_suffix (4.0.7) + rake (13.4.2) + rchardet (1.10.0) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + rest-client (2.1.0) + http-accept (>= 1.7.0, < 2.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + retriable (3.4.1) + rexml (3.4.4) + rouge (3.28.0) ruby-macho (2.5.1) - typhoeus (1.4.0) - ethon (>= 0.9.0) - tzinfo (2.0.4) + ruby2_keywords (0.0.5) + rubyzip (2.4.1) + securerandom (0.4.1) + security (0.1.5) + signet (0.21.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 4.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + track_open_instances (0.1.15) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + typhoeus (1.6.0) + ethon (>= 0.18.0) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) - xcodeproj (1.21.0) + uber (0.1.0) + unicode-display_width (2.6.0) + word_wrap (1.0.0) + xcodeproj (1.25.1) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (~> 3.2.4) - zeitwerk (2.5.4) + rexml (>= 3.3.6, < 4.0) + xcpretty (0.4.1) + rouge (~> 3.28.0) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + xml-simple (1.1.9) + rexml PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.11, >= 1.11.2) + activesupport (>= 6.1.7.5, != 7.1.0) + benchmark + bigdecimal + cocoapods (>= 1.13, != 1.15.1, != 1.15.0) + concurrent-ruby (< 1.3.4) + fastlane (~> 2.234.0) + fastlane-plugin-browserstack + fastlane-plugin-bugsnag + fastlane-plugin-bugsnag_sourcemaps_upload + jwt + logger + mutex_m + xcodeproj (< 1.26.0) + +CHECKSUMS + CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261 + abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242 + activesupport (7.2.3.1) sha256=11ebed516a43a0bb47346227a35ebae4d9427465a7c9eb197a03d5c8d283cb34 + addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af + algoliasearch (1.27.5) sha256=26c1cddf3c2ec4bd60c148389e42702c98fdac862881dc6b07a4c0b89ffec853 + artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263 + atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f + aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b + aws-partitions (1.1252.0) sha256=b44c74136ebd634d35f3fb8fd37def5214db21b9375f22c6954dbe7a7f2a449d + aws-sdk-core (3.247.0) sha256=789864594ce8cef05ee3d81fa8ed506099280bda6ea12a7612b8b7c5e5e62851 + aws-sdk-kms (1.127.0) sha256=5d540b6afb9574327202989db2217741211e1cce3fb443ad0e1e37de730202e5 + aws-sdk-s3 (1.223.0) sha256=655e382af34926caa76b77cf0171caed5f61ff52b8b58ae50f6f3e22c39e6cbc + aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 + babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99 + base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b + benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c + 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 + cocoapods-deintegrate (1.0.5) sha256=517c2a448ef563afe99b6e7668704c27f5de9e02715a88ee9de6974dc1b3f6a2 + cocoapods-downloader (2.1) sha256=bb6ebe1b3966dc4055de54f7a28b773485ac724fdf575d9bee2212d235e7b6d1 + cocoapods-plugins (1.0.0) sha256=725d17ce90b52f862e73476623fd91441b4430b742d8a071000831efb440ca9a + cocoapods-search (1.0.1) sha256=1b133b0e6719ed439bd840e84a1828cca46425ab73a11eff5e096c3b2df05589 + cocoapods-trunk (1.6.0) sha256=5f5bda8c172afead48fa2d43a718cf534b1313c367ba1194cebdeb9bfee9ed31 + cocoapods-try (1.2.0) sha256=145b946c6e7747ed0301d975165157951153d27469e6b2763c83e25c84b9defe + colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c + colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a + commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9 + concurrent-ruby (1.3.3) sha256=4f9cd28965c4dcf83ffd3ea7304f9323277be8525819cb18a3b61edcb56a7c6a + connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a + csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f + declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9 + digest-crc (0.7.0) sha256=64adc23a26a241044cbe6732477ca1b3c281d79e2240bcff275a37a5a0d78c07 + domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933 + dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8 + drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373 + emoji_regex (3.2.3) sha256=ecd8be856b7691406c6bf3bb3a5e55d6ed683ffab98b4aa531bb90e1ddcc564b + escape (0.0.4) sha256=e49f44ae2b4f47c6a3abd544ae77fe4157802794e32f19b8e773cbc4dcec4169 + ethon (0.18.0) sha256=b598afc9f30448cb068b850714b7d6948e941476095d04f90a4ac65b8d6efcb2 + excon (0.112.0) sha256=daf9ac3a4c2fc9aa48383a33da77ecb44fa395111e973084d5c52f6f214ae0f0 + faraday (1.10.5) sha256=b144f1d2b045652fa820b5f532723e1643cc28b93dae911d784e5c5f88e8f6ed + faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb + faraday-em_http (1.0.0) sha256=7a3d4c7079789121054f57e08cd4ef7e40ad1549b63101f38c7093a9d6c59689 + faraday-em_synchrony (1.0.1) sha256=bf3ce45dcf543088d319ab051f80985ea6d294930635b7a0b966563179f81750 + faraday-excon (1.1.0) sha256=b055c842376734d7f74350fe8611542ae2000c5387348d9ba9708109d6e40940 + faraday-httpclient (1.0.1) sha256=4c8ff1f0973ff835be8d043ef16aaf54f47f25b7578f6d916deee8399a04d33b + faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757 + faraday-net_http (1.0.2) sha256=63992efea42c925a20818cf3c0830947948541fdcf345842755510d266e4c682 + faraday-net_http_persistent (1.2.0) sha256=0b0cbc8f03dab943c3e1cc58d8b7beb142d9df068b39c718cd83e39260348335 + faraday-patron (1.0.0) sha256=dc2cd7b340bb3cc8e36bcb9e6e7eff43d134b6d526d5f3429c7a7680ddd38fa7 + faraday-rack (1.0.0) sha256=ef60ec969a2bb95b8dbf24400155aee64a00fc8ba6c6a4d3968562bcc92328c0 + faraday-retry (1.0.4) sha256=dc659233777fabf96c69c2ffe56c0a5d2c102af90321a42cc6c90157bcd716aa + faraday_middleware (1.2.1) sha256=d45b78c8ee864c4783fbc276f845243d4a7918a67301c052647bacabec0529e9 + fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20 + fastlane (2.234.0) sha256=b74835681ad9a8e9c0931a5727dad1bab433895ac534c864a1ed5749625d26e9 + fastlane-plugin-browserstack (0.3.4) sha256=a4f3e4a552e2390a4733570857512571535912100ffada177d5374413f2c1333 + fastlane-plugin-bugsnag (3.0.0) sha256=8ddac4b79cb4b5d00432cccd5789a9e1a1119c29f7773a27d01b1d8a2363915d + fastlane-plugin-bugsnag_sourcemaps_upload (0.2.0) sha256=a05afaefa81a7bf56c36386dddeb0931db31ead6886e3eae24f9683bda1a064d + fastlane-sirp (1.1.0) sha256=10bc94f9682efd8e1badfb31452a76dd8981f1f3a33717c765fde6d75b54d847 + ffi (1.17.3) sha256=0e9f39f7bb3934f77ad6feab49662be77e87eedcdeb2a3f5c0234c2938563d4c + fourflusher (2.3.1) sha256=1b3de61c7c791b6a4e64f31e3719eb25203d151746bb519a0292bff1065ccaa9 + fuzzy_match (2.0.4) sha256=b5de4f95816589c5b5c3ad13770c0af539b75131c158135b3f3bbba75d0cfca5 + gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939 + git (4.3.1) sha256=91ca566c39766a033e61a148c8f470908bd4786b818f8f3ff566d3a9a0200c50 + google-apis-androidpublisher_v3 (0.100.0) sha256=7a82935bee985190e8fe23bf5e53df3a27d65dd084114bb71b846b617de16489 + google-apis-core (0.18.0) sha256=96b057816feeeab448139ed5b5c78eab7fc2a9d8958f0fbc8217dedffad054ee + google-apis-iamcredentials_v1 (0.27.0) sha256=9289f29968610754ef11d98b9ec627f0153f3e2616fef839aef096de529f6d1e + google-apis-playcustomapp_v1 (0.17.0) sha256=d5bc90b705f3f862bab4998086449b0abe704ee1685a84821daa90ca7fa95a78 + 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.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 + http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6 + httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8 + i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5 + jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 + 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 + mime-types-data (3.2026.0317) sha256=77f078a4d8631d52b842ba77099734b06eddb7ad339d792e746d2272b67e511b + mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9 + mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef + minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5 + molinillo (0.8.0) sha256=efbff2716324e2a30bccd3eba1ff3a735f4d5d53ffddbc6a2f32c0ca9433045d + multi_json (1.21.1) sha256=e6126a31808e3b4d19f483c775ceac34df190dffa62adfb63a165ee14ba68080 + multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8 + mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751 + nanaimo (0.3.0) sha256=aaaedc60497070b864a7e220f7c4b4cad3a0daddda2c30055ba8dae306342376 + nap (1.1.0) sha256=949691660f9d041d75be611bb2a8d2fd559c467537deac241f4097d9b5eea576 + naturally (2.3.0) sha256=459923cf76c2e6613048301742363200c3c7e4904c324097d54a67401e179e01 + netrc (0.11.0) sha256=de1ce33da8c99ab1d97871726cba75151113f117146becbe45aa85cb3dabee3f + nkf (0.2.0) sha256=fbc151bda025451f627fafdfcb3f4f13d0b22ae11f58c6d3a2939c76c5f5f126 + optparse (0.8.1) sha256=42bea10d53907ccff4f080a69991441d611fbf8733b60ed1ce9ee365ce03bd1a + os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f + ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 + plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42 + process_executer (4.0.2) sha256=c73eb646d450044241c973a8360f6326e33ec5ad933f7acf503f6f3579873a71 + public_suffix (4.0.7) sha256=8be161e2421f8d45b0098c042c06486789731ea93dc3a896d30554ee38b573b8 + rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 + rchardet (1.10.0) sha256=d5ea2ed61a720a220f1914778208e718a0c7ed2a484b6d357ba695aa7001390f + representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace + rest-client (2.1.0) sha256=35a6400bdb14fae28596618e312776c158f7ebbb0ccad752ff4fa142bf2747e3 + retriable (3.4.1) sha256=fb3f114b7d492121c158c01f3d5152b5a615c5b70d5877d0bc08c7ec3725c3bc + rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 + rouge (3.28.0) sha256=0d6de482c7624000d92697772ab14e48dca35629f8ddf3f4b21c99183fd70e20 + ruby-macho (2.5.1) sha256=9075e52e0f9270b552a90b24fcc6219ad149b0d15eae1bc364ecd0ac8984f5c9 + ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef + rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615 + securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 + security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7 + signet (0.21.0) sha256=d617e9fbf24928280d39dcfefba9a0372d1c38187ffffd0a9283957a10a8cd5b + simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b + terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea + terminal-table (3.0.2) sha256=f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91 + track_open_instances (0.1.15) sha256=7f0e48821e6b4c881daaa40fb1583e308937c22a9c84883c150b399c3b5c3029 + trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3 + tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48 + tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50 + tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542 + typhoeus (1.6.0) sha256=bacc41c23e379547e29801dc235cd1699b70b955a1ba3d32b2b877aa844c331d + tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b + uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc + unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a + word_wrap (1.0.0) sha256=f556d4224c812e371000f12a6ee8102e0daa724a314c3f246afaad76d82accc7 + xcodeproj (1.25.1) sha256=9a2310dccf6d717076e86f602b17c640046b6f1dfe64480044596f6f2f13dc84 + xcpretty (0.4.1) sha256=b14c50e721f6589ee3d6f5353e2c2cfcd8541fa1ea16d6c602807dd7327f3892 + xcpretty-travis-formatter (1.0.1) sha256=aacc332f17cb7b2cba222994e2adc74223db88724fe76341483ad3098e232f93 + xml-simple (1.1.9) sha256=d21131e519c86f1a5bc2b6d2d57d46e6998e47f18ed249b25cad86433dbd695d RUBY VERSION - ruby 2.7.4p191 + ruby 3.4.9 BUNDLED WITH - 2.2.27 + 4.0.7 diff --git a/LICENSE b/LICENSE index 9d6b974ffcd..ca4e589c8db 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 BlueWallet developers +Copyright (c) 2026 BlueWallet developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Navigation.js b/Navigation.js deleted file mode 100644 index 9bede94f447..00000000000 --- a/Navigation.js +++ /dev/null @@ -1,528 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { createNativeStackNavigator } from 'react-native-screens/native-stack'; -import { createDrawerNavigator } from '@react-navigation/drawer'; -import { Platform, useWindowDimensions, Dimensions, I18nManager } from 'react-native'; -import { useTheme } from '@react-navigation/native'; - -import Settings from './screen/settings/settings'; -import About from './screen/settings/about'; -import ReleaseNotes from './screen/settings/releasenotes'; -import Licensing from './screen/settings/licensing'; -import Selftest from './screen/selftest'; -import Language from './screen/settings/language'; -import Currency from './screen/settings/currency'; -import EncryptStorage from './screen/settings/encryptStorage'; -import PlausibleDeniability from './screen/plausibledeniability'; -import LightningSettings from './screen/settings/lightningSettings'; -import ElectrumSettings from './screen/settings/electrumSettings'; -import TorSettings from './screen/settings/torSettings'; -import Tools from './screen/settings/tools'; -import GeneralSettings from './screen/settings/GeneralSettings'; -import NetworkSettings from './screen/settings/NetworkSettings'; -import NotificationSettings from './screen/settings/notificationSettings'; -import DefaultView from './screen/settings/defaultView'; - -import WalletsList from './screen/wallets/list'; -import WalletTransactions from './screen/wallets/transactions'; -import AddWallet from './screen/wallets/add'; -import WalletsAddMultisig from './screen/wallets/addMultisig'; -import WalletsAddMultisigStep2 from './screen/wallets/addMultisigStep2'; -import WalletsAddMultisigHelp from './screen/wallets/addMultisigHelp'; -import PleaseBackup from './screen/wallets/pleaseBackup'; -import PleaseBackupLNDHub from './screen/wallets/pleaseBackupLNDHub'; -import PleaseBackupLdk from './screen/wallets/pleaseBackupLdk'; -import ImportWallet from './screen/wallets/import'; -import ImportWalletDiscovery from './screen/wallets/importDiscovery'; -import ImportCustomDerivationPath from './screen/wallets/importCustomDerivationPath'; -import ImportSpeed from './screen/wallets/importSpeed'; -import WalletDetails from './screen/wallets/details'; -import WalletExport from './screen/wallets/export'; -import ExportMultisigCoordinationSetup from './screen/wallets/exportMultisigCoordinationSetup'; -import ViewEditMultisigCosigners from './screen/wallets/viewEditMultisigCosigners'; -import WalletXpub from './screen/wallets/xpub'; -import SignVerify from './screen/wallets/signVerify'; -import WalletAddresses from './screen/wallets/addresses'; -import ReorderWallets from './screen/wallets/reorderWallets'; -import SelectWallet from './screen/wallets/selectWallet'; -import ProvideEntropy from './screen/wallets/provideEntropy'; - -import TransactionDetails from './screen/transactions/details'; -import TransactionStatus from './screen/transactions/transactionStatus'; -import CPFP from './screen/transactions/CPFP'; -import RBFBumpFee from './screen/transactions/RBFBumpFee'; -import RBFCancel from './screen/transactions/RBFCancel'; - -import ReceiveDetails from './screen/receive/details'; -import AztecoRedeem from './screen/receive/aztecoRedeem'; - -import SendDetails from './screen/send/details'; -import ScanQRCode from './screen/send/ScanQRCode'; -import SendCreate from './screen/send/create'; -import Confirm from './screen/send/confirm'; -import PsbtWithHardwareWallet from './screen/send/psbtWithHardwareWallet'; -import PsbtMultisig from './screen/send/psbtMultisig'; -import PsbtMultisigQRCode from './screen/send/psbtMultisigQRCode'; -import Success from './screen/send/success'; -import Broadcast from './screen/send/broadcast'; -import IsItMyAddress from './screen/send/isItMyAddress'; -import CoinControl from './screen/send/coinControl'; - -import ScanLndInvoice from './screen/lnd/scanLndInvoice'; -import LappBrowser from './screen/lnd/browser'; -import LNDCreateInvoice from './screen/lnd/lndCreateInvoice'; -import LNDViewInvoice from './screen/lnd/lndViewInvoice'; -import LdkOpenChannel from './screen/lnd/ldkOpenChannel'; -import LdkInfo from './screen/lnd/ldkInfo'; -import LNDViewAdditionalInvoiceInformation from './screen/lnd/lndViewAdditionalInvoiceInformation'; -import LnurlPay from './screen/lnd/lnurlPay'; -import LnurlPaySuccess from './screen/lnd/lnurlPaySuccess'; -import LnurlAuth from './screen/lnd/lnurlAuth'; -import UnlockWith from './UnlockWith'; -import DrawerList from './screen/wallets/drawerList'; -import { isDesktop, isTablet, isHandset } from './blue_modules/environment'; -import SettingsPrivacy from './screen/settings/SettingsPrivacy'; -import LNDViewAdditionalInvoicePreImage from './screen/lnd/lndViewAdditionalInvoicePreImage'; -import LdkViewLogs from './screen/wallets/ldkViewLogs'; -import PaymentCode from './screen/wallets/paymentCode'; -import PaymentCodesList from './screen/wallets/paymentCodesList'; -import loc from './loc'; - -const WalletsStack = createNativeStackNavigator(); - -const WalletsRoot = () => { - const theme = useTheme(); - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - -const AddWalletStack = createNativeStackNavigator(); -const AddWalletRoot = () => { - const theme = useTheme(); - - return ( - - - - - - - - - - - - - - - ); -}; - -// CreateTransactionStackNavigator === SendDetailsStack -const SendDetailsStack = createNativeStackNavigator(); -const SendDetailsRoot = () => { - const theme = useTheme(); - - return ( - - - - - - - - - - - - ); -}; - -const LNDCreateInvoiceStack = createNativeStackNavigator(); -const LNDCreateInvoiceRoot = () => { - const theme = useTheme(); - - return ( - - - - - - - - ); -}; - -// LightningScanInvoiceStackNavigator === ScanLndInvoiceStack -const ScanLndInvoiceStack = createNativeStackNavigator(); -const ScanLndInvoiceRoot = () => { - const theme = useTheme(); - - return ( - - - - - - - - ); -}; - -const LDKOpenChannelStack = createNativeStackNavigator(); -const LDKOpenChannelRoot = () => { - const theme = useTheme(); - - return ( - - - - - - ); -}; - -const AztecoRedeemStack = createNativeStackNavigator(); -const AztecoRedeemRoot = () => { - const theme = useTheme(); - - return ( - - - - - ); -}; - -const ScanQRCodeStack = createNativeStackNavigator(); -const ScanQRCodeRoot = () => ( - - - -); - -const UnlockWithScreenStack = createNativeStackNavigator(); -const UnlockWithScreenRoot = () => ( - - - -); - -const ReorderWalletsStack = createNativeStackNavigator(); -const ReorderWalletsStackRoot = () => { - const theme = useTheme(); - - return ( - - - - ); -}; - -const Drawer = createDrawerNavigator(); -const DrawerRoot = () => { - const dimensions = useWindowDimensions(); - const isLargeScreen = useMemo(() => { - return Platform.OS === 'android' ? isTablet() : (dimensions.width >= Dimensions.get('screen').width / 2 && isTablet()) || isDesktop; - }, [dimensions.width]); - const drawerStyle = useMemo(() => ({ width: isLargeScreen ? 320 : '0%' }), [isLargeScreen]); - const drawerContent = useCallback(props => (isLargeScreen ? : null), [isLargeScreen]); - - return ( - - - - ); -}; - -const ReceiveDetailsStack = createNativeStackNavigator(); -const ReceiveDetailsStackRoot = () => { - const theme = useTheme(); - - return ( - - - - ); -}; - -const WalletXpubStack = createNativeStackNavigator(); -const WalletXpubStackRoot = () => { - const theme = useTheme(); - - return ( - - - - ); -}; - -const SignVerifyStack = createNativeStackNavigator(); -const SignVerifyStackRoot = () => { - const theme = useTheme(); - - return ( - - - - ); -}; - -const WalletExportStack = createNativeStackNavigator(); -const WalletExportStackRoot = () => { - const theme = useTheme(); - - return ( - - - - ); -}; - -const LappBrowserStack = createNativeStackNavigator(); -const LappBrowserStackRoot = () => { - const theme = useTheme(); - - return ( - - - - ); -}; - -const InitStack = createNativeStackNavigator(); -const InitRoot = () => ( - - - - - -); - -const ViewEditMultisigCosignersStack = createNativeStackNavigator(); -const ViewEditMultisigCosignersRoot = () => { - const theme = useTheme(); - - return ( - - - - ); -}; - -const ExportMultisigCoordinationSetupStack = createNativeStackNavigator(); -const ExportMultisigCoordinationSetupRoot = () => { - const theme = useTheme(); - - return ( - - - - ); -}; - -const PaymentCodeStack = createNativeStackNavigator(); -const PaymentCodeStackRoot = () => { - return ( - - - - - ); -}; - -const RootStack = createNativeStackNavigator(); -const NavigationDefaultOptions = { headerShown: false, stackPresentation: isDesktop ? 'containedModal' : 'modal' }; -const Navigation = () => { - return ( - - {/* stacks */} - - - - - - - {/* screens */} - - - - - - - - - - - - - - - ); -}; - -export default InitRoot; diff --git a/NavigationService.js b/NavigationService.js deleted file mode 100644 index 05316459584..00000000000 --- a/NavigationService.js +++ /dev/null @@ -1,11 +0,0 @@ -import * as React from 'react'; - -export const navigationRef = React.createRef(); - -export function navigate(name, params) { - navigationRef.current?.navigate(name, params); -} - -export function dispatch(params) { - navigationRef.current?.dispatch(params); -} diff --git a/NavigationService.ts b/NavigationService.ts new file mode 100644 index 00000000000..42b61b68e5a --- /dev/null +++ b/NavigationService.ts @@ -0,0 +1,36 @@ +import { createNavigationContainerRef, NavigationAction, ParamListBase, StackActions } from '@react-navigation/native'; + +export const navigationRef = createNavigationContainerRef(); + +export function navigate(name: string, params?: ParamListBase, options?: { merge: boolean }) { + if (navigationRef.isReady()) { + navigationRef.current?.navigate({ name, params, merge: options?.merge }); + } +} + +export function dispatch(action: NavigationAction) { + if (navigationRef.isReady()) { + navigationRef.current?.dispatch(action); + } +} + +export function reset() { + if (navigationRef.isReady()) { + navigationRef.current?.reset({ + index: 0, + routes: [{ name: 'UnlockWithScreen' }], + }); + } +} + +export function popToTop() { + if (navigationRef.isReady()) { + navigationRef.current?.dispatch(StackActions.popToTop()); + } +} + +export function pop() { + if (navigationRef.isReady()) { + navigationRef.current?.dispatch(StackActions.pop()); + } +} diff --git a/README.md b/README.md index 7b33353993f..fb14d088c1f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # BlueWallet - A Bitcoin & Lightning Wallet [![GitHub tag](https://img.shields.io/badge/dynamic/json.svg?url=https://raw.githubusercontent.com/BlueWallet/BlueWallet/master/package.json&query=$.version&label=Version)](https://github.com/BlueWallet/BlueWallet) -[![CircleCI](https://circleci.com/gh/BlueWallet/BlueWallet.svg?style=svg)](https://circleci.com/gh/BlueWallet/BlueWallet) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) ![](https://img.shields.io/github/license/BlueWallet/BlueWallet.svg) @@ -76,14 +75,17 @@ In another terminal window within the BlueWallet folder: ``` npx react-native run-ios ``` +**To debug BlueWallet on the iOS Simulator, you must choose a Rosetta-compatible iOS Simulator. This can be done by navigating to the Product menu in Xcode, selecting Destination Architectures, and then opting for "Show Both." This action will reveal the simulators that support Rosetta. +** * To run on macOS using Mac Catalyst: ``` -npm run maccatalystpatches +npx pod-install +npm start ``` -Once the patches are applied, open Xcode and select "My Mac" as destination. +Open ios/BlueWallet.xcworkspace. Once the project loads, select the scheme/target BlueWallet. Click Run. ## TESTS @@ -98,11 +100,11 @@ MIT ## WANT TO CONTRIBUTE? -Grab an issue from [the backlog](https://github.com/BlueWallet/BlueWallet/projects/1), try to start or submit a PR, any doubts we will try to guide you. Contributors have a private telegram group, request access by email bluewallet@bluewallet.io +Grab an issue from [the backlog](https://github.com/BlueWallet/BlueWallet/issues), try to start or submit a PR, any doubts we will try to guide you. Contributors have a private telegram group, request access by email bluewallet@bluewallet.io ## Translations -We accept translations via [Transifex](https://www.transifex.com/bluewallet/bluewallet/) +We accept translations via [Transifex](https://explore.transifex.com/bluewallet/bluewallet/) To participate you need to: 1. Sign up to Transifex @@ -114,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/.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 diff --git a/RELEASE.md b/RELEASE.md index df06d6c8ee2..c5299fff120 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,27 +2,8 @@ ## Apple -* test the build on a real device. It is imperative that you run selftest and it gives you OK -* if necessary, up version number in all relevant files (you can use `./edit-version-number.sh`) -* run `./scripts/release-notes.sh` - it prints changelog between latest tag and now, put this output under -new version in file `ios/fastlane/metadata/en-US/release_notes.txt` (on top); if file got too big -delete the oldest version from the bottom of the file -* now is a good time to commit a ver bump and release notes changes -* create this release version in App Store Connect (iTunes) and attach appropriate build. note -last 4 digits of the build and announce it - this is now a RC. no need to fill release notes yet -* `cd ios/` and then run `DELIVER_USERNAME="my_itunes_email@example.com" DELIVER_PASSWORD="my_itunes_password" fastlane deliver --force --skip_binary_upload --skip_screenshots --ignore_language_directory_validation -a io.bluewallet.bluewallet --app_version "6.6.6"` -but replace `6.6.6` with your version number - this will upload release notes to all locales in itunes -* go back to App Store Connect and press `Submit for Review`. choose Yes, we use identifiers - for installs tracking -* once its approved and released it is safe to cut a release tag: run `git tag -m "REL v6.6.6: 76ed479" v6.6.6 -s` -where `76ed479` is a latest commit in this version. replace the version as well. then run `git push origin --tags`; alternative way to tag: `git tag -a v6.0.0 2e1a00609d5a0dbc91bcda2421df0f61bdfc6b10 -m "v6.0.0" -s` -* you are awesome! +* TBD ## Android -* do android after ios usually -* test the build on a real device. We have accounts with browserstack where you can do so. -* its imperative that you run selftest and it gives you OK. note which build you are testing -* go to appcenter.ms, find this exact build under `master` builds, and press `Distribute` -> `Store` -> `Production`. -in `Release notes` write the release, this field is to smaller than iOS, so you need to keep it bellow 500 characters. -* now just wait till appcenter displays a message that it is succesfully distributed -* noice! +* TBD diff --git a/UnlockWith.js b/UnlockWith.js deleted file mode 100644 index 6bb0aa979e0..00000000000 --- a/UnlockWith.js +++ /dev/null @@ -1,141 +0,0 @@ -import React, { useContext, useEffect, useState } from 'react'; -import { View, Image, TouchableOpacity, StyleSheet, StatusBar, ActivityIndicator, useColorScheme, LayoutAnimation } from 'react-native'; -import { Icon } from 'react-native-elements'; -import Biometric from './class/biometrics'; -import LottieView from 'lottie-react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import { StackActions, useNavigation, useRoute } from '@react-navigation/native'; -import { BlueStorageContext } from './blue_modules/storage-context'; -import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; -import { isHandset } from './blue_modules/environment'; - -const styles = StyleSheet.create({ - root: { - flex: 1, - }, - container: { - flex: 1, - justifyContent: 'space-between', - alignItems: 'center', - }, - biometric: { - flex: 1, - justifyContent: 'flex-end', - marginBottom: 58, - }, - biometricRow: { - justifyContent: 'center', - flexDirection: 'row', - }, - icon: { - width: 64, - height: 64, - }, -}); - -const UnlockWith = () => { - const { setWalletsInitialized, isStorageEncrypted, startAndDecrypt } = useContext(BlueStorageContext); - const { dispatch } = useNavigation(); - const { unlockOnComponentMount } = useRoute().params; - const [biometricType, setBiometricType] = useState(false); - const [isStorageEncryptedEnabled, setIsStorageEncryptedEnabled] = useState(false); - const [isAuthenticating, setIsAuthenticating] = useState(false); - const [animationDidFinish, setAnimationDidFinish] = useState(false); - const colorScheme = useColorScheme(); - - const initialRender = async () => { - let bt = false; - if (await Biometric.isBiometricUseCapableAndEnabled()) { - bt = await Biometric.biometricType(); - } - - setBiometricType(bt); - }; - - useEffect(() => { - initialRender(); - }, []); - - const successfullyAuthenticated = () => { - setWalletsInitialized(true); - dispatch(StackActions.replace(isHandset ? 'Navigation' : 'DrawerRoot')); - }; - - const unlockWithBiometrics = async () => { - if (await isStorageEncrypted()) { - unlockWithKey(); - } - setIsAuthenticating(true); - - if (await Biometric.unlockWithBiometrics()) { - setIsAuthenticating(false); - await startAndDecrypt(); - return successfullyAuthenticated(); - } - setIsAuthenticating(false); - }; - - const unlockWithKey = async () => { - setIsAuthenticating(true); - if (await startAndDecrypt()) { - ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false }); - successfullyAuthenticated(); - } else { - setIsAuthenticating(false); - } - }; - - const renderUnlockOptions = () => { - if (isAuthenticating) { - return ; - } else { - const color = colorScheme === 'dark' ? '#FFFFFF' : '#000000'; - if ((biometricType === Biometric.TouchID || biometricType === Biometric.Biometrics) && !isStorageEncryptedEnabled) { - return ( - - - - ); - } else if (biometricType === Biometric.FaceID && !isStorageEncryptedEnabled) { - return ( - - - - ); - } else if (isStorageEncryptedEnabled) { - return ( - - - - ); - } - } - }; - - const onAnimationFinish = async () => { - LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); - if (unlockOnComponentMount) { - const storageIsEncrypted = await isStorageEncrypted(); - setIsStorageEncryptedEnabled(storageIsEncrypted); - if (!biometricType || storageIsEncrypted) { - unlockWithKey(); - } else if (typeof biometricType === 'string') unlockWithBiometrics(); - } - setAnimationDidFinish(true); - }; - - return ( - - - - - {animationDidFinish && {renderUnlockOptions()}} - - - ); -}; - -export default UnlockWith; diff --git a/WatchConnectivity.ios.js b/WatchConnectivity.ios.js deleted file mode 100644 index 6c21a16a876..00000000000 --- a/WatchConnectivity.ios.js +++ /dev/null @@ -1,227 +0,0 @@ -import { useContext, useEffect, useRef } from 'react'; -import { - updateApplicationContext, - watchEvents, - useReachability, - useInstalled, - usePaired, - transferCurrentComplicationUserInfo, -} from 'react-native-watch-connectivity'; -import { Chain } from './models/bitcoinUnits'; -import loc, { formatBalance, transactionTimeToReadable } from './loc'; -import { BlueStorageContext } from './blue_modules/storage-context'; -import Notifications from './blue_modules/notifications'; -import { FiatUnit } from './models/fiatUnit'; -import { MultisigHDWallet } from './class'; - -function WatchConnectivity() { - const { walletsInitialized, wallets, fetchWalletTransactions, saveToDisk, txMetadata, preferredFiatCurrency } = - useContext(BlueStorageContext); - const isReachable = useReachability(); - const isPaired = usePaired(); - const isInstalled = useInstalled(); // true | false - const messagesListenerActive = useRef(false); - const lastPreferredCurrency = useRef(FiatUnit.USD.endPointKey); - - useEffect(() => { - let messagesListener = () => {}; - if (isPaired && isInstalled && isReachable && walletsInitialized && messagesListenerActive.current === false) { - messagesListener = watchEvents.addListener('message', handleMessages); - messagesListenerActive.current = true; - } else { - messagesListener(); - messagesListenerActive.current = false; - } - return () => { - messagesListener(); - messagesListenerActive.current = false; - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [walletsInitialized, isPaired, isReachable, isInstalled]); - - useEffect(() => { - if (isPaired && isInstalled && isReachable && walletsInitialized) { - sendWalletsToWatch(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [walletsInitialized, wallets, isPaired, isReachable, isInstalled]); - - useEffect(() => { - updateApplicationContext({ isWalletsInitialized: walletsInitialized, randomID: Math.floor(Math.random() * 11) }); - }, [walletsInitialized]); - - useEffect(() => { - if (isInstalled && isReachable && walletsInitialized && preferredFiatCurrency) { - const preferredFiatCurrencyParsed = JSON.parse(preferredFiatCurrency); - try { - if (lastPreferredCurrency.current !== preferredFiatCurrencyParsed.endPointKey) { - transferCurrentComplicationUserInfo({ - preferredFiatCurrency: preferredFiatCurrencyParsed.endPointKey, - }); - lastPreferredCurrency.current = preferredFiatCurrency.endPointKey; - } else { - console.log('WatchConnectivity lastPreferredCurrency has not changed'); - } - } catch (e) { - console.log('WatchConnectivity useEffect preferredFiatCurrency error'); - console.log(e); - } - } - }, [preferredFiatCurrency, walletsInitialized, isReachable, isInstalled]); - - const handleMessages = (message, reply) => { - if (message.request === 'createInvoice') { - handleLightningInvoiceCreateRequest(message.walletIndex, message.amount, message.description) - .then(createInvoiceRequest => reply({ invoicePaymentRequest: createInvoiceRequest })) - .catch(e => { - console.log(e); - reply({}); - }); - } else if (message.message === 'sendApplicationContext') { - sendWalletsToWatch(); - reply({}); - } else if (message.message === 'fetchTransactions') { - fetchWalletTransactions() - .then(() => saveToDisk()) - .finally(() => reply({})); - } else if (message.message === 'hideBalance') { - const walletIndex = message.walletIndex; - const wallet = wallets[walletIndex]; - wallet.hideBalance = message.hideBalance; - saveToDisk().finally(() => reply({})); - } - }; - - const handleLightningInvoiceCreateRequest = async (walletIndex, amount, description = loc.lnd.placeholder) => { - const wallet = wallets[walletIndex]; - if (wallet.allowReceive() && amount > 0) { - try { - const invoiceRequest = await wallet.addInvoice(amount, description); - - // lets decode payreq and subscribe groundcontrol so we can receive push notification when our invoice is paid - try { - // Let's verify if notifications are already configured. Otherwise the watch app will freeze waiting for user approval in iOS app - if (await Notifications.isNotificationsEnabled()) { - const decoded = await wallet.decodeInvoice(invoiceRequest); - Notifications.majorTomToGroundControl([], [decoded.payment_hash], []); - } - } catch (e) { - console.log('WatchConnectivity - Running in Simulator'); - console.log(e); - } - return invoiceRequest; - } catch (error) { - return error; - } - } - }; - - const sendWalletsToWatch = async () => { - if (!Array.isArray(wallets)) { - console.log('No Wallets set to sync with Watch app. Exiting...'); - return; - } - if (!walletsInitialized) { - console.log('Wallets not initialized. Exiting...'); - return; - } - const walletsToProcess = []; - - for (const wallet of wallets) { - let receiveAddress; - if (wallet.chain === Chain.ONCHAIN) { - try { - receiveAddress = await wallet.getAddressAsync(); - } catch (_) {} - if (!receiveAddress) { - // either sleep expired or getAddressAsync threw an exception - receiveAddress = wallet._getExternalAddressByIndex(wallet.next_free_address_index); - } - } else if (wallet.chain === Chain.OFFCHAIN) { - try { - await wallet.getAddressAsync(); - receiveAddress = wallet.getAddress(); - } catch (_) {} - if (!receiveAddress) { - // either sleep expired or getAddressAsync threw an exception - receiveAddress = wallet.getAddress(); - } - } - const transactions = wallet.getTransactions(10); - const watchTransactions = []; - for (const transaction of transactions) { - let type = 'pendingConfirmation'; - let memo = ''; - let amount = 0; - - if ('confirmations' in transaction && !(transaction.confirmations > 0)) { - type = 'pendingConfirmation'; - } else if (transaction.type === 'user_invoice' || transaction.type === 'payment_request') { - const currentDate = new Date(); - const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise - const invoiceExpiration = transaction.timestamp + transaction.expire_time; - - if (invoiceExpiration > now) { - type = 'pendingConfirmation'; - } else if (invoiceExpiration < now) { - if (transaction.ispaid) { - type = 'received'; - } else { - type = 'sent'; - } - } - } else if (transaction.value / 100000000 < 0) { - type = 'sent'; - } else { - type = 'received'; - } - if (transaction.type === 'user_invoice' || transaction.type === 'payment_request') { - amount = isNaN(transaction.value) ? '0' : amount; - const currentDate = new Date(); - const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise - const invoiceExpiration = transaction.timestamp + transaction.expire_time; - - if (invoiceExpiration > now) { - amount = formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString(); - } else if (invoiceExpiration < now) { - if (transaction.ispaid) { - amount = formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString(); - } else { - amount = loc.lnd.expired; - } - } else { - amount = formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString(); - } - } else { - amount = formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString(); - } - if (txMetadata[transaction.hash] && txMetadata[transaction.hash].memo) { - memo = txMetadata[transaction.hash].memo; - } else if (transaction.memo) { - memo = transaction.memo; - } - const watchTX = { type, amount, memo, time: transactionTimeToReadable(transaction.received) }; - watchTransactions.push(watchTX); - } - - const walletInformation = { - label: wallet.getLabel(), - balance: formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true), - type: wallet.type, - preferredBalanceUnit: wallet.getPreferredBalanceUnit(), - receiveAddress, - transactions: watchTransactions, - hideBalance: wallet.hideBalance, - }; - if (wallet.chain === Chain.ONCHAIN && wallet.type !== MultisigHDWallet.type) { - walletInformation.xpub = wallet.getXpub() ? wallet.getXpub() : wallet.getSecret(); - } - walletsToProcess.push(walletInformation); - } - updateApplicationContext({ wallets: walletsToProcess, randomID: Math.floor(Math.random() * 11) }); - }; - - return null; -} - -export default WatchConnectivity; diff --git a/WatchConnectivity.js b/WatchConnectivity.js deleted file mode 100644 index 93d040a1a59..00000000000 --- a/WatchConnectivity.js +++ /dev/null @@ -1,4 +0,0 @@ -const WatchConnectivity = () => { - return null; -}; -export default WatchConnectivity; diff --git a/__mocks__/@react-native-async-storage/async-storage.js b/__mocks__/@react-native-async-storage/async-storage.ts similarity index 100% rename from __mocks__/@react-native-async-storage/async-storage.js rename to __mocks__/@react-native-async-storage/async-storage.ts diff --git a/__mocks__/react-native-image-picker.js b/__mocks__/react-native-image-picker.js deleted file mode 100644 index 3392a0e6b3e..00000000000 --- a/__mocks__/react-native-image-picker.js +++ /dev/null @@ -1,13 +0,0 @@ -import {NativeModules} from 'react-native'; - -// Mock the ImagePickerManager native module to allow us to unit test the JavaScript code -NativeModules.ImagePickerManager = { - showImagePicker: jest.fn(), - launchCamera: jest.fn(), - launchImageLibrary: jest.fn(), -}; - -// Reset the mocks before each test -global.beforeEach(() => { - jest.resetAllMocks(); -}); diff --git a/__mocks__/react-native-image-picker.ts b/__mocks__/react-native-image-picker.ts new file mode 100644 index 00000000000..c401aabe35d --- /dev/null +++ b/__mocks__/react-native-image-picker.ts @@ -0,0 +1,13 @@ +import { NativeModules } from 'react-native'; + +// Mock the ImagePickerManager native module to allow us to unit test the JavaScript code +NativeModules.ImagePickerManager = { + showImagePicker: jest.fn(), + launchCamera: jest.fn(), + launchImageLibrary: jest.fn(), +}; + +// Reset the mocks before each test +global.beforeEach(() => { + jest.resetAllMocks(); +}); diff --git a/__mocks__/react-native-localize.js b/__mocks__/react-native-localize.ts similarity index 100% rename from __mocks__/react-native-localize.js rename to __mocks__/react-native-localize.ts diff --git a/__mocks__/react-native-tor.js b/__mocks__/react-native-tor.js deleted file mode 100644 index d4bf7deed45..00000000000 --- a/__mocks__/react-native-tor.js +++ /dev/null @@ -1,18 +0,0 @@ -/* global jest */ - -export const startIfNotStarted = jest.fn(async (key, value, callback) => { - return 666; -}); - - -export const get = jest.fn(); -export const post = jest.fn(); -export const deleteMock = jest.fn(); -export const stopIfRunning = jest.fn(); -export const getDaemonStatus = jest.fn(); - -const mock = jest.fn().mockImplementation(() => { - return { startIfNotStarted, get, post, delete: deleteMock, stopIfRunning, getDaemonStatus }; -}); - -export default mock; \ No newline at end of file diff --git a/android/.project b/android/.project index d22beed79e6..0117c3a8138 100644 --- a/android/.project +++ b/android/.project @@ -14,4 +14,15 @@ org.eclipse.buildship.core.gradleprojectnature + + + 1729710829465 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index e8895216fd3..00000000000 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir= -eclipse.preferences.version=1 diff --git a/android/app/.classpath b/android/app/.classpath index eb19361b571..bbe97e501d3 100644 --- a/android/app/.classpath +++ b/android/app/.classpath @@ -1,6 +1,6 @@ - + diff --git a/android/app/.project b/android/app/.project index ac485d7c3e6..1a270d11d7b 100644 --- a/android/app/.project +++ b/android/app/.project @@ -20,4 +20,15 @@ org.eclipse.jdt.core.javanature org.eclipse.buildship.core.gradleprojectnature + + + 1729710829486 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/android/app/.settings/org.eclipse.jdt.core.prefs b/android/app/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..626e0e1d5c6 --- /dev/null +++ b/android/app/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.source=17 diff --git a/android/app/build.gradle b/android/app/build.gradle index a1b77db54d2..4197118b49d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,29 +1,27 @@ apply plugin: "com.android.application" -apply plugin: "kotlin-android" +apply plugin: "org.jetbrains.kotlin.android" apply plugin: "com.facebook.react" -import com.android.build.OutputFile - /** * This is the configuration block to customize your React Native Android app. * By default you don't need to apply any configuration, just uncomment the lines you need. */ react { /* Folders */ - // The root of your project, i.e. where "package.json" lives. Default is '..' - // root = file("../") - // The folder where the react-native NPM package is. Default is ../node_modules/react-native - // reactNativeDir = file("../node_modules/react-native") - // The folder where the react-native Codegen package is. Default is ../node_modules/react-native-codegen - // codegenDir = file("../node_modules/react-native-codegen") - // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js - // cliFile = file("../node_modules/react-native/cli.js") + // The root of your project, i.e. where "package.json" lives. Default is '../..' + // root = file("../../") + // The folder where the react-native NPM package is. Default is ../../node_modules/react-native + // reactNativeDir = file("../../node_modules/react-native") + // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen + // codegenDir = file("../../node_modules/@react-native/codegen") + // The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js + // cliFile = file("../../node_modules/react-native/cli.js") /* Variants */ // The list of variants to that are debuggable. For those we're going to - // skip the bundling of the JS bundle and the assets. By default is just 'debug'. + // skip the bundling of the JS bundle and the assets. Default is "debug", "debugOptimized". // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. - // debuggableVariants = ["liteDebug", "prodDebug"] + // debuggableVariants = ["liteDebug", "liteDebugOptimized", "prodDebug", "prodDebugOptimized"] /* Bundling */ // A list containing the node command and its flags. Default is just 'node'. @@ -51,15 +49,10 @@ react { // // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" // hermesFlags = ["-O", "-output-source-map"] -} -/** - * Set this to true to create four separate APKs instead of one, - * one for each native architecture. This is useful if you don't - * use App Bundles (https://developer.android.com/guide/app-bundle/) - * and want to have separate APKs to upload to the Play Store. - */ -def enableSeparateBuildPerCPUArchitecture = false + /* Autolinking */ + autolinkLibrariesWithApp() +} /** * Set this to true to Run Proguard on Release builds to minify the Java bytecode. @@ -70,90 +63,91 @@ def enableProguardInReleaseBuilds = false * The preferred build flavor of JavaScriptCore (JSC) * * For example, to use the international variant, you can use: - * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` + * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+` * * The international variant includes ICU i18n library and necessary data * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that * give correct results when using with locales other than en-US. Note that * this variant is about 6MiB larger per architecture than default. */ -def jscFlavor = 'org.webkit:android-jsc-intl:+' - -/** - * Private function to get the list of Native Architectures you want to build. - * This reads the value from reactNativeArchitectures in your gradle.properties - * file and works together with the --active-arch-only flag of react-native run-android. - */ -def reactNativeArchitectures() { - def value = project.getProperties().get("reactNativeArchitectures") - return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] -} +def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' android { - ndkVersion rootProject.ext.ndkVersion + androidResources { + noCompress += ["bundle"] + } + ndkVersion rootProject.ext.ndkVersion + buildToolsVersion rootProject.ext.buildToolsVersion compileSdkVersion rootProject.ext.compileSdkVersion + namespace "io.bluewallet.bluewallet" defaultConfig { applicationId "io.bluewallet.bluewallet" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "6.4.9" + versionName "8.0.0" testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + // Keep compatibility across react-native-capture-protection flavor changes. + missingDimensionStrategy "react-native-capture-protection", "callbackTiramisu", "base" } - splits { - abi { - reset() - enable enableSeparateBuildPerCPUArchitecture - universalApk false // If true, also generate a universal APK - include (*reactNativeArchitectures()) + lint { + abortOnError false + checkReleaseBuilds false + } + + sourceSets { + main { + assets.srcDirs = ['src/main/assets', 'src/main/res/assets'] + java.srcDirs = ['src/main/java', '../../blue_modules/Views/SegmentedControl/android'] } } + buildTypes { release { - // Caution! In production, you need to generate your own keystore file. - // see https://reactnative.dev/docs/signed-apk-android. minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" } } - // applicationVariants are e.g. debug, release - applicationVariants.all { variant -> - variant.outputs.each { output -> - // For each separate APK per architecture, set a unique version code as described here: - // https://developer.android.com/studio/build/configure-apk-splits.html - // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc. - def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] - def abi = output.getFilter(OutputFile.ABI) - if (abi != null) { // null for the universal-debug, universal-release variants - output.versionCodeOverride = - defaultConfig.versionCode * 1000 + versionCodes.get(abi) - } +} - } +task copyFiatUnits(type: Copy) { + from '../../models/fiatUnits.json' + into 'src/main/assets' +} + +preBuild.dependsOn(copyFiatUnits) + +// Ensure fiat units are available before codegen scans JS sources +tasks.configureEach { task -> + if (task.name == 'generateCodegenSchemaFromJavaScript') { + task.dependsOn(copyFiatUnits) } } dependencies { + androidTestImplementation('com.wix:detox:+') // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") - implementation files("../../node_modules/rn-ldk/android/libs/LDK-release.aar") - implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0") - implementation files("../../node_modules/react-native-tor/android/libs/sifir_android.aar") + implementation 'androidx.core:core-ktx:1.18.0' + implementation 'androidx.work:work-runtime-ktx:2.11.2' + implementation 'com.google.android.material:material:1.12.0' + implementation 'androidx.compose.ui:ui:1.10.6' + implementation 'androidx.compose.material3:material3:1.3.2' + implementation 'androidx.preference:preference-ktx:1.2.1' if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") } else { implementation jscFlavor } - androidTestImplementation('com.wix:detox:+') - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation 'androidx.appcompat:appcompat:1.7.1' + implementation 'androidx.constraintlayout:constraintlayout:2.2.1' } apply plugin: 'com.google.gms.google-services' // Google Services plugin -apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) \ No newline at end of file +apply plugin: "com.bugsnag.android.gradle" diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index b964573e4e9..cb6ff29732e 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -11,8 +11,5 @@ -keep class com.facebook.hermes.unicode.** { *; } -keep class com.facebook.jni.** { *; } --keep class com.sifir.** { *;} --keep interface com.sifir.** { *;} --keep enum com.sifir.** { *;} -keep class com.swmansion.reanimated.** { *; } -keep class com.facebook.react.turbomodule.** { *; } diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 0c4927bccd6..476d7dc1324 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,10 +1,6 @@ - - - - diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index feb5e9c0bb9..218e62ef4fd 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,115 +1,186 @@ + xmlns:tools="http://schemas.android.com/tools" + android:installLocation="auto"> + + - - - + + + + + - - + + + + + - - + - - - - - - + + + + + + + + + + + + + + + - - - + - + + - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + \ No newline at end of file diff --git a/android/app/src/main/assets/fiatUnits.json b/android/app/src/main/assets/fiatUnits.json new file mode 100644 index 00000000000..cb113540610 --- /dev/null +++ b/android/app/src/main/assets/fiatUnits.json @@ -0,0 +1,443 @@ +{ + "USD": { + "endPointKey": "USD", + "locale": "en-US", + "source": "Kraken", + "symbol": "$", + "country": "United States (US Dollar)" + }, + "AED": { + "endPointKey": "AED", + "locale": "ar-AE", + "source": "CoinGecko", + "symbol": "د.إ.", + "country": "United Arab Emirates (UAE Dirham)" + }, + "AMD": { + "endPointKey": "AMD", + "locale": "hy-AM", + "source": "Coinbase", + "symbol": "֏", + "country": "Armenia (Armenian Dram)" + }, + "ANG": { + "endPointKey": "ANG", + "locale": "en-SX", + "source": "YadioConvert", + "symbol": "ƒ", + "country": "Sint Maarten (Netherlands Antillean Guilder)" + }, + "ARS": { + "endPointKey": "ARS", + "locale": "es-AR", + "source": "Yadio", + "symbol": "$", + "country": "Argentina (Argentine Peso)" + }, + "AUD": { + "endPointKey": "AUD", + "locale": "en-AU", + "source": "CoinGecko", + "symbol": "$", + "country": "Australia (Australian Dollar)" + }, + "AWG": { + "endPointKey": "AWG", + "locale": "nl-AW", + "source": "Coinbase", + "symbol": "ƒ", + "country": "Aruba (Aruban Florin)" + }, + "BHD": { + "endPointKey": "BHD", + "locale": "ar-BH", + "source": "CoinGecko", + "symbol": "د.ب.", + "country": "Bahrain (Bahraini Dinar)" + }, + "BRL": { + "endPointKey": "BRL", + "locale": "pt-BR", + "source": "CoinGecko", + "symbol": "R$", + "country": "Brazil (Brazilian Real)" + }, + "CAD": { + "endPointKey": "CAD", + "locale": "en-CA", + "source": "CoinGecko", + "symbol": "$", + "country": "Canada (Canadian Dollar)" + }, + "CHF": { + "endPointKey": "CHF", + "locale": "de-CH", + "source": "CoinGecko", + "symbol": "CHF", + "country": "Switzerland (Swiss Franc)" + }, + "CLP": { + "endPointKey": "CLP", + "locale": "es-CL", + "source": "Yadio", + "symbol": "$", + "country": "Chile (Chilean Peso)" + }, + "CNY": { + "endPointKey": "CNY", + "locale": "zh-CN", + "source": "Coinbase", + "symbol": "¥", + "country": "China (Chinese Yuan)" + }, + "COP": { + "endPointKey": "COP", + "locale": "es-CO", + "source": "CoinDesk", + "symbol": "$", + "country": "Colombia (Colombian Peso)" + }, + "CZK": { + "endPointKey": "CZK", + "locale": "cs-CZ", + "source": "CoinGecko", + "symbol": "Kč", + "country": "Czech Republic (Czech Koruna)" + }, + "DKK": { + "endPointKey": "DKK", + "locale": "da-DK", + "source": "CoinGecko", + "symbol": "kr", + "country": "Denmark (Danish Krone)" + }, + "EGP": { + "endPointKey": "EGP", + "locale": "ar-EG", + "source": "YadioConvert", + "symbol": "ج.م.", + "country": "Egypt (Egyptian Pound)" + }, + "EUR": { + "endPointKey": "EUR", + "locale": "en-IE", + "source": "Kraken", + "symbol": "€", + "country": "European Union (Euro)" + }, + "GBP": { + "endPointKey": "GBP", + "locale": "en-GB", + "source": "Kraken", + "symbol": "£", + "country": "United Kingdom (British Pound)" + }, + "HKD": { + "endPointKey": "HKD", + "locale": "zh-HK", + "source": "CoinGecko", + "symbol": "HK$", + "country": "Hong Kong (Hong Kong Dollar)" + }, + "HRK": { + "endPointKey": "HRK", + "locale": "hr-HR", + "source": "Coinbase", + "symbol": "HRK", + "country": "Croatia (Croatian Kuna)" + }, + "HUF": { + "endPointKey": "HUF", + "locale": "hu-HU", + "source": "CoinGecko", + "symbol": "Ft", + "country": "Hungary (Hungarian Forint)" + }, + "IDR": { + "endPointKey": "IDR", + "locale": "id-ID", + "source": "CoinGecko", + "symbol": "Rp", + "country": "Indonesia (Indonesian Rupiah)" + }, + "ILS": { + "endPointKey": "ILS", + "locale": "he-IL", + "source": "CoinGecko", + "symbol": "₪", + "country": "Israel (Israeli New Shekel)" + }, + "INR": { + "endPointKey": "INR", + "locale": "hi-IN", + "source": "coinpaprika", + "symbol": "₹", + "country": "India (Indian Rupee)" + }, + "IRR": { + "endPointKey": "IRR", + "locale": "fa-IR", + "source": "Exir", + "symbol": "﷼", + "country": "Iran (Iranian Rial)" + }, + "IRT": { + "endPointKey": "IRT", + "locale": "fa-IR", + "source": "Exir", + "symbol": "تومان", + "country": "Iran (Iranian Toman)" + }, + "ISK": { + "endPointKey": "ISK", + "locale": "is-IS", + "source": "Coinbase", + "symbol": "kr", + "country": "Iceland (Icelandic Króna)" + }, + "JPY": { + "endPointKey": "JPY", + "locale": "ja-JP", + "source": "CoinGecko", + "symbol": "¥", + "country": "Japan (Japanese Yen)" + }, + "KES": { + "endPointKey": "KES", + "locale": "en-KE", + "source": "CoinDesk", + "symbol": "Ksh", + "country": "Kenya (Kenyan Shilling)" + }, + "KRW": { + "endPointKey": "KRW", + "locale": "ko-KR", + "source": "CoinGecko", + "symbol": "₩", + "country": "South Korea (South Korean Won)" + }, + "KWD": { + "endPointKey": "KWD", + "locale": "ar-KW", + "source": "CoinGecko", + "symbol": "د.ك.", + "country": "Kuwait (Kuwaiti Dinar)" + }, + "LBP": { + "endPointKey": "LBP", + "locale": "ar-LB", + "source": "YadioConvert", + "symbol": "ل.ل.", + "country": "Lebanon (Lebanese Pound)" + }, + "LKR": { + "endPointKey": "LKR", + "locale": "si-LK", + "source": "CoinGecko", + "symbol": "රු.", + "country": "Sri Lanka (Sri Lankan Rupee)" + }, + "MXN": { + "endPointKey": "MXN", + "locale": "es-MX", + "source": "CoinGecko", + "symbol": "$", + "country": "Mexico (Mexican Peso)" + }, + "MYR": { + "endPointKey": "MYR", + "locale": "ms-MY", + "source": "CoinGecko", + "symbol": "RM", + "country": "Malaysia (Malaysian Ringgit)" + }, + "MZN": { + "endPointKey": "MZN", + "locale": "seh-MZ", + "source": "Coinbase", + "symbol": "MTn", + "country": "Mozambique (Mozambican Metical)" + }, + "NGN": { + "endPointKey": "NGN", + "locale": "en-NG", + "source": "CoinGecko", + "symbol": "₦", + "country": "Nigeria (Nigerian Naira)" + }, + "NOK": { + "endPointKey": "NOK", + "locale": "nb-NO", + "source": "CoinGecko", + "symbol": "kr", + "country": "Norway (Norwegian Krone)" + }, + "NZD": { + "endPointKey": "NZD", + "locale": "en-NZ", + "source": "CoinGecko", + "symbol": "$", + "country": "New Zealand (New Zealand Dollar)" + }, + "OMR": { + "endPointKey": "OMR", + "locale": "ar-OM", + "source": "Coinbase", + "symbol": "ر.ع.", + "country": "Oman (Omani Rial)" + }, + "PHP": { + "endPointKey": "PHP", + "locale": "en-PH", + "source": "CoinGecko", + "symbol": "₱", + "country": "Philippines (Philippine Peso)" + }, + "PLN": { + "endPointKey": "PLN", + "locale": "pl-PL", + "source": "CoinGecko", + "symbol": "zł", + "country": "Poland (Polish Zloty)" + }, + "PYG": { + "endPointKey": "PYG", + "locale": "es-PY", + "source": "Coinbase", + "symbol": "₲", + "country": "Paraguay (Paraguayan Guarani)" + }, + "QAR": { + "endPointKey": "QAR", + "locale": "ar-QA", + "source": "Coinbase", + "symbol": "ر.ق.", + "country": "Qatar (Qatari Riyal)" + }, + "RON": { + "endPointKey": "RON", + "locale": "ro-RO", + "source": "BNR", + "symbol": "lei", + "country": "Romania (Romanian Leu)" + }, + "RSD": { + "endPointKey": "RSD", + "locale": "sr-RS", + "source": "Coinbase", + "symbol": "DIN", + "country": "Serbia (Serbian Dinar)" + }, + "RUB": { + "endPointKey": "RUB", + "locale": "ru-RU", + "source": "CoinGecko", + "symbol": "₽", + "country": "Russia (Russian Ruble)" + }, + "SAR": { + "endPointKey": "SAR", + "locale": "ar-SA", + "source": "CoinGecko", + "symbol": "ر.س.", + "country": "Saudi Arabia (Saudi Riyal)" + }, + "SEK": { + "endPointKey": "SEK", + "locale": "sv-SE", + "source": "CoinGecko", + "symbol": "kr", + "country": "Sweden (Swedish Krona)" + }, + "SGD": { + "endPointKey": "SGD", + "locale": "zh-SG", + "source": "CoinGecko", + "symbol": "S$", + "country": "Singapore (Singapore Dollar)" + }, + "THB": { + "endPointKey": "THB", + "locale": "th-TH", + "source": "CoinGecko", + "symbol": "฿", + "country": "Thailand (Thai Baht)" + }, + "TRY": { + "endPointKey": "TRY", + "locale": "tr-TR", + "source": "CoinGecko", + "symbol": "₺", + "country": "Turkey (Turkish Lira)" + }, + "TWD": { + "endPointKey": "TWD", + "locale": "zh-Hant-TW", + "source": "CoinGecko", + "symbol": "NT$", + "country": "Taiwan (New Taiwan Dollar)" + }, + "TZS": { + "endPointKey": "TZS", + "locale": "en-TZ", + "source": "Coinbase", + "symbol": "TSh", + "country": "Tanzania (Tanzanian Shilling)" + }, + "UAH": { + "endPointKey": "UAH", + "locale": "uk-UA", + "source": "CoinGecko", + "symbol": "₴", + "country": "Ukraine (Ukrainian Hryvnia)" + }, + "UGX": { + "endPointKey": "UGX", + "locale": "en-UG", + "source": "Coinbase", + "symbol": "USh", + "country": "Uganda (Ugandan Shilling)" + }, + "UYU": { + "endPointKey": "UYU", + "locale": "es-UY", + "source": "Coinbase", + "symbol": "$", + "country": "Uruguay (Uruguayan Peso)" + }, + "VEF": { + "endPointKey": "VEF", + "locale": "es-VE", + "source": "CoinGecko", + "symbol": "Bs.", + "country": "Venezuela (Venezuelan Bolívar Fuerte)" + }, + "VES": { + "endPointKey": "VES", + "locale": "es-VE", + "source": "Yadio", + "symbol": "Bs.", + "country": "Venezuela (Venezuelan Bolívar Soberano)" + }, + "XAF": { + "endPointKey": "XAF", + "locale": "fr-CF", + "source": "Coinbase", + "symbol": "Fr", + "country": "Central African Republic (Central African Franc)" + }, + "ZAR": { + "endPointKey": "ZAR", + "locale": "en-ZA", + "source": "CoinGecko", + "symbol": "R", + "country": "South Africa (South African Rand)" + }, + "GHS": { + "endPointKey": "GHS", + "locale": "en-GH", + "source": "Coinbase", + "symbol": "₵", + "country": "Ghana (Ghanaian Cedi)" + } +} \ No newline at end of file diff --git a/android/app/src/main/assets/fonts/AntDesign.ttf b/android/app/src/main/assets/fonts/AntDesign.ttf deleted file mode 100644 index 2abf03542c1..00000000000 Binary files a/android/app/src/main/assets/fonts/AntDesign.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/Entypo.ttf b/android/app/src/main/assets/fonts/Entypo.ttf deleted file mode 100644 index 1c8f5e910bf..00000000000 Binary files a/android/app/src/main/assets/fonts/Entypo.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/EvilIcons.ttf b/android/app/src/main/assets/fonts/EvilIcons.ttf deleted file mode 100644 index 6868f7bb64b..00000000000 Binary files a/android/app/src/main/assets/fonts/EvilIcons.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/Feather.ttf b/android/app/src/main/assets/fonts/Feather.ttf deleted file mode 100755 index fc963dfe229..00000000000 Binary files a/android/app/src/main/assets/fonts/Feather.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/FontAwesome.ttf b/android/app/src/main/assets/fonts/FontAwesome.ttf deleted file mode 100644 index 35acda2fa11..00000000000 Binary files a/android/app/src/main/assets/fonts/FontAwesome.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf b/android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf deleted file mode 100644 index 5f72e9127ff..00000000000 Binary files a/android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf b/android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf deleted file mode 100644 index a309313d5fb..00000000000 Binary files a/android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf b/android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf deleted file mode 100644 index 7ece3282a4f..00000000000 Binary files a/android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/Foundation.ttf b/android/app/src/main/assets/fonts/Foundation.ttf deleted file mode 100644 index 6cce217ddc2..00000000000 Binary files a/android/app/src/main/assets/fonts/Foundation.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/Ionicons.ttf b/android/app/src/main/assets/fonts/Ionicons.ttf deleted file mode 100644 index 67bd84202ad..00000000000 Binary files a/android/app/src/main/assets/fonts/Ionicons.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf b/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf deleted file mode 100644 index 3219fca04a2..00000000000 Binary files a/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/MaterialIcons.ttf b/android/app/src/main/assets/fonts/MaterialIcons.ttf deleted file mode 100644 index 7015564ad16..00000000000 Binary files a/android/app/src/main/assets/fonts/MaterialIcons.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/Octicons.ttf b/android/app/src/main/assets/fonts/Octicons.ttf deleted file mode 100644 index ceac75d75e6..00000000000 Binary files a/android/app/src/main/assets/fonts/Octicons.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/SimpleLineIcons.ttf b/android/app/src/main/assets/fonts/SimpleLineIcons.ttf deleted file mode 100644 index 6ecb6868347..00000000000 Binary files a/android/app/src/main/assets/fonts/SimpleLineIcons.ttf and /dev/null differ diff --git a/android/app/src/main/assets/fonts/Zocial.ttf b/android/app/src/main/assets/fonts/Zocial.ttf deleted file mode 100644 index e4ae46c6286..00000000000 Binary files a/android/app/src/main/assets/fonts/Zocial.ttf and /dev/null differ diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/AppWidgetUtils.kt b/android/app/src/main/java/io/bluewallet/bluewallet/AppWidgetUtils.kt new file mode 100644 index 00000000000..1473f67669d --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/AppWidgetUtils.kt @@ -0,0 +1,115 @@ +package io.bluewallet.bluewallet + +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.content.Context +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi + +object AppWidgetUtils { + private const val TAG = "AppWidgetUtils" + + /** + * Get all Bitcoin Price Widget IDs + */ + fun getBitcoinPriceWidgetIds(context: Context): IntArray { + val appWidgetManager = AppWidgetManager.getInstance(context) + val component = ComponentName(context, BitcoinPriceWidget::class.java) + return appWidgetManager.getAppWidgetIds(component) + } + + /** + * Trigger update for all widgets when theme changes + */ + fun updateWidgetsForThemeChange(context: Context) { + Log.d(TAG, "Updating widgets for theme change") + + // Update Bitcoin Price widgets - force a complete refresh + val bitcoinWidgetIds = getBitcoinPriceWidgetIds(context) + if (bitcoinWidgetIds.isNotEmpty()) { + Log.d(TAG, "Refreshing ${bitcoinWidgetIds.size} Bitcoin Price widgets") + for (widgetId in bitcoinWidgetIds) { + BitcoinPriceWidget.refreshWidget(context, widgetId) + } + } + + // Update Market widgets + val marketWidgetIds = MarketWidget.getAllWidgetIds(context) + if (marketWidgetIds.isNotEmpty()) { + Log.d(TAG, "Refreshing ${marketWidgetIds.size} Market widgets") + MarketWidget.refreshAllWidgetsImmediately(context) + } + } + + /** + * Check if app widgets are supported and available on this device + */ + fun isWidgetAvailable(context: Context): Boolean { + val appWidgetManager = AppWidgetManager.getInstance(context) + return appWidgetManager != null + } + + /** + * Request to pin a widget to the home screen (Android 8.0+) + */ + @RequiresApi(Build.VERSION_CODES.O) + fun requestPinBitcoinWidget(context: Context): Boolean { + val appWidgetManager = AppWidgetManager.getInstance(context) + if (!appWidgetManager.isRequestPinAppWidgetSupported) { + Log.w(TAG, "Pin widget not supported on this device") + return false + } + + val myProvider = ComponentName(context, BitcoinPriceWidget::class.java) + return try { + appWidgetManager.requestPinAppWidget(myProvider, null, null) + true + } catch (e: Exception) { + Log.e(TAG, "Failed to request pin widget", e) + false + } + } + + /** + * Request to pin a market widget to the home screen (Android 8.0+) + */ + @RequiresApi(Build.VERSION_CODES.O) + fun requestPinMarketWidget(context: Context): Boolean { + val appWidgetManager = AppWidgetManager.getInstance(context) + if (!appWidgetManager.isRequestPinAppWidgetSupported) { + Log.w(TAG, "Pin widget not supported on this device") + return false + } + + val myProvider = ComponentName(context, MarketWidget::class.java) + return try { + appWidgetManager.requestPinAppWidget(myProvider, null, null) + true + } catch (e: Exception) { + Log.e(TAG, "Failed to request pin widget", e) + false + } + } + + /** + * Refresh all widgets by triggering updates + */ + fun refreshAllWidgets(context: Context) { + Log.d(TAG, "Refreshing all widgets") + + // Refresh Bitcoin Price widgets + val bitcoinWidgetIds = getBitcoinPriceWidgetIds(context) + if (bitcoinWidgetIds.isNotEmpty()) { + for (widgetId in bitcoinWidgetIds) { + BitcoinPriceWidget.refreshWidget(context, widgetId) + } + } + + // Refresh Market widgets + val marketWidgetIds = MarketWidget.getAllWidgetIds(context) + if (marketWidgetIds.isNotEmpty()) { + MarketWidget.refreshAllWidgetsImmediately(context) + } + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.kt b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.kt new file mode 100644 index 00000000000..70752e362a7 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.kt @@ -0,0 +1,129 @@ +package io.bluewallet.bluewallet + +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.Context +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.RemoteViews +import androidx.work.WorkManager + +class BitcoinPriceWidget : AppWidgetProvider() { + + companion object { + private const val TAG = "BitcoinPriceWidget" + private const val SHARED_PREF_NAME = "group.io.bluewallet.bluewallet" + + fun updateNetworkStatus(context: Context, appWidgetIds: IntArray) { + val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context) + val appWidgetManager = AppWidgetManager.getInstance(context) + + for (appWidgetId in appWidgetIds) { + val views = RemoteViews(context.packageName, R.layout.widget_layout) + views.setViewVisibility(R.id.network_status, if (isNetworkAvailable) View.GONE else View.VISIBLE) + appWidgetManager.partiallyUpdateAppWidget(appWidgetId, views) + } + } + + fun refreshWidget(context: Context, appWidgetId: Int) { + val appWidgetManager = AppWidgetManager.getInstance(context) + + // Create new RemoteViews to ensure it picks up current theme + val views = RemoteViews(context.packageName, R.layout.widget_layout) + + // Set network status + val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context) + views.setViewVisibility(R.id.network_status, if (isNetworkAvailable) View.GONE else View.VISIBLE) + + // Try to load cached data first + val sharedPref = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + val cachedPrice = sharedPref.getString("previous_price", null) + val preferredCurrency = sharedPref.getString("preferredCurrency", "USD") + val preferredCurrencyLocale = sharedPref.getString("preferredCurrencyLocale", null) + + if (cachedPrice != null) { + // Show cached data immediately + try { + val locale = preferredCurrencyLocale + ?.let { runCatching { java.util.Locale.forLanguageTag(it) }.getOrNull() } + ?.takeIf { it.language.isNotBlank() } + ?: java.util.Locale.getDefault() + + val currencyFormat = java.text.NumberFormat.getCurrencyInstance(locale) + val currency = java.util.Currency.getInstance(preferredCurrency ?: "USD") + currencyFormat.currency = currency + currencyFormat.maximumFractionDigits = 0 + + val parsedCached = cachedPrice.toDoubleOrNull()?.toInt() + + views.setViewVisibility(R.id.loading_indicator, View.GONE) + views.setViewVisibility(R.id.price_arrow_container, View.GONE) + + if (parsedCached != null) { + views.setViewVisibility(R.id.price_value, View.VISIBLE) + views.setViewVisibility(R.id.last_updated_label, View.VISIBLE) + views.setViewVisibility(R.id.last_updated_time, View.VISIBLE) + views.setTextViewText(R.id.price_value, currencyFormat.format(parsedCached)) + views.setTextViewText( + R.id.last_updated_time, + java.text.SimpleDateFormat("hh:mm a", java.util.Locale.getDefault()).format(java.util.Date()) + ) + } else { + // If parsing fails, show loading state + views.setViewVisibility(R.id.price_value, View.GONE) + views.setViewVisibility(R.id.last_updated_label, View.GONE) + views.setViewVisibility(R.id.last_updated_time, View.GONE) + views.setViewVisibility(R.id.loading_indicator, View.VISIBLE) + } + } catch (e: Exception) { + Log.e(TAG, "Error displaying cached price", e) + // Show loading state if cache display fails + views.setViewVisibility(R.id.loading_indicator, View.VISIBLE) + views.setViewVisibility(R.id.price_value, View.GONE) + views.setViewVisibility(R.id.last_updated_label, View.GONE) + views.setViewVisibility(R.id.last_updated_time, View.GONE) + views.setViewVisibility(R.id.price_arrow_container, View.GONE) + } + } else { + // No cached data, show loading state + views.setViewVisibility(R.id.loading_indicator, View.VISIBLE) + views.setViewVisibility(R.id.price_value, View.GONE) + views.setViewVisibility(R.id.last_updated_label, View.GONE) + views.setViewVisibility(R.id.last_updated_time, View.GONE) + views.setViewVisibility(R.id.price_arrow_container, View.GONE) + } + + appWidgetManager.updateAppWidget(appWidgetId, views) + WidgetUpdateWorker.scheduleImmediateUpdate(context) + WidgetUpdateWorker.scheduleWork(context) + } + } + + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + super.onUpdate(context, appWidgetManager, appWidgetIds) + + for (widgetId in appWidgetIds) { + Log.d(TAG, "Updating widget with ID: $widgetId") + refreshWidget(context, widgetId) + } + } + + override fun onEnabled(context: Context) { + super.onEnabled(context) + WidgetUpdateWorker.scheduleImmediateUpdate(context) + WidgetUpdateWorker.scheduleWork(context) + } + + override fun onDisabled(context: Context) { + super.onDisabled(context) + context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE).edit().clear().apply() + WorkManager.getInstance(context).cancelUniqueWork(WidgetUpdateWorker.WORK_NAME) + } + + override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, + appWidgetId: Int, newOptions: Bundle?) { + super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) + refreshWidget(context, appWidgetId) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/ElectrumClient.kt b/android/app/src/main/java/io/bluewallet/bluewallet/ElectrumClient.kt new file mode 100644 index 00000000000..7370fd0d05b --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/ElectrumClient.kt @@ -0,0 +1,348 @@ +package io.bluewallet.bluewallet + +import android.content.Context +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout +import org.json.JSONObject +import java.io.BufferedReader +import java.io.InputStreamReader +import java.io.OutputStream +import java.net.Socket +import java.net.SocketTimeoutException +import java.security.SecureRandom +import java.security.cert.X509Certificate +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSocket +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager + +class ElectrumClient { + companion object { + private const val TAG = "ElectrumClient" + private const val MAX_RETRIES = 3 + private const val RETRY_DELAY_MS = 1000L // 1 second delay between retries + + // Default list of Electrum servers to try + val hardcodedPeers = listOf( + ElectrumServer("electrum1.bluewallet.io", 50001, false), + ElectrumServer("electrum2.bluewallet.io", 50001, false), + ElectrumServer("electrum3.bluewallet.io", 50001, false), + ElectrumServer("electrum1.bluewallet.io", 443, true), + ElectrumServer("electrum2.bluewallet.io", 443, true), + ElectrumServer("electrum3.bluewallet.io", 443, true) + ) + } + + private var socket: Socket? = null + private var outputStream: OutputStream? = null + private var inputReader: BufferedReader? = null + private var context: Context? = null + private var networkStatusListener: NetworkStatusListener? = null + + data class ElectrumServer(val host: String, val port: Int, val isSsl: Boolean) + + /** + * Initialize ElectrumClient with application context for network checks + */ + fun initialize(context: Context) { + Log.i(TAG, "Initializing ElectrumClient with context") + this.context = context + } + + /** + * Set a listener for network status changes + */ + fun setNetworkStatusListener(listener: NetworkStatusListener) { + Log.d(TAG, "Setting network status listener") + this.networkStatusListener = listener + } + + /** + * Interface for listening to network status changes + */ + interface NetworkStatusListener { + fun onNetworkStatusChanged(isConnected: Boolean) + fun onConnectionError(error: String) + fun onConnectionSuccess() + } + + /** + * Check if the device has network connectivity + */ + private fun isNetworkAvailable(): Boolean { + val hasNetwork = context?.let { NetworkUtils.isNetworkAvailable(it) } ?: false + Log.d(TAG, "Network available: $hasNetwork") + return hasNetwork + } + + /** + * Connect to the next available Electrum server with network checks + */ + suspend fun connectToNextAvailable( + servers: List = hardcodedPeers, + validateCertificates: Boolean = true, + connectTimeout: Long = 5000 // 5 seconds + ): Boolean = withContext(Dispatchers.IO) { + val startTime = System.currentTimeMillis() + Log.i(TAG, "Starting connection attempt to Electrum server. Server count: ${servers.size}") + + // Check network availability first + if (!isNetworkAvailable()) { + Log.e(TAG, "No network connection available. Connection attempt aborted.") + networkStatusListener?.onNetworkStatusChanged(false) + return@withContext false + } + + var connected = false + var lastError: Exception? = null + + for (serverIndex in servers.indices) { + val server = servers[serverIndex] + if (connected) break + + Log.d(TAG, "Trying server ${serverIndex+1}/${servers.size}: ${server.host}:${server.port} (SSL: ${server.isSsl})") + + // Try up to MAX_RETRIES times per server + for (attempt in 1..MAX_RETRIES) { + try { + Log.d(TAG, "Connection attempt $attempt/$MAX_RETRIES to ${server.host}:${server.port} (SSL: ${server.isSsl})") + val attemptStartTime = System.currentTimeMillis() + + withTimeout(connectTimeout) { + if (connect(server, validateCertificates)) { + val attemptDuration = System.currentTimeMillis() - attemptStartTime + Log.i(TAG, "Successfully connected to ${server.host}:${server.port} in ${attemptDuration}ms") + networkStatusListener?.onConnectionSuccess() + connected = true + } else { + Log.w(TAG, "Failed to connect to ${server.host}:${server.port} - connect() returned false") + } + } + } catch (e: TimeoutCancellationException) { + lastError = e + Log.e(TAG, "Connection to ${server.host}:${server.port} timed out after ${connectTimeout}ms (attempt $attempt)") + if (attempt < MAX_RETRIES) { + Log.d(TAG, "Retrying after ${RETRY_DELAY_MS}ms delay") + delay(RETRY_DELAY_MS) + } + } catch (e: Exception) { + lastError = e + Log.e(TAG, "Error connecting to ${server.host}:${server.port} (attempt $attempt): ${e.message}") + if (attempt < MAX_RETRIES) { + Log.d(TAG, "Retrying after ${RETRY_DELAY_MS}ms delay") + delay(RETRY_DELAY_MS) + } + } + } + } + + val totalDuration = System.currentTimeMillis() - startTime + + if (!connected) { + Log.e(TAG, "Failed to connect to any Electrum server after ${totalDuration}ms. Last error: ${lastError?.message}") + networkStatusListener?.onConnectionError("Failed to connect to any Electrum server: ${lastError?.message}") + } else { + Log.i(TAG, "Successfully connected to an Electrum server in ${totalDuration}ms") + } + + connected + } + + /** + * Log the server details upon successful connection + */ + private fun logServerDetails(server: ElectrumServer) { + Log.i(TAG, "Connected to Electrum server: ${server.host}:${server.port} (SSL: ${server.isSsl})") + } + + /** + * Connect to a specific Electrum server with network check + */ + suspend fun connect( + server: ElectrumServer, + validateCertificates: Boolean = true + ): Boolean = withContext(Dispatchers.IO) { + val startTime = System.currentTimeMillis() + Log.d(TAG, "Attempting direct connection to ${server.host}:${server.port} (SSL: ${server.isSsl})") + + var result = false + + if (!isNetworkAvailable()) { + Log.e(TAG, "Cannot connect to ${server.host}: No network connection available") + networkStatusListener?.onNetworkStatusChanged(false) + return@withContext false + } + + try { + close() // Close any existing connection + Log.d(TAG, "Creating ${if (server.isSsl) "SSL " else ""}socket to ${server.host}:${server.port}") + + socket = if (server.isSsl) { + createSslSocket(server.host, server.port, validateCertificates) + } else { + Socket(server.host, server.port) + } + + Log.d(TAG, "Socket created successfully. Setting timeout and getting streams.") + socket?.soTimeout = 10000 // 10 seconds read timeout + outputStream = socket?.getOutputStream() + inputReader = BufferedReader(InputStreamReader(socket?.getInputStream())) + + // Testing the connection with simple version request + val versionRequest = "{\"id\": 0, \"method\": \"server.version\", \"params\": [\"BlueWallet\", \"1.4\"]}\n" + Log.d(TAG, "Sending version request to verify connection") + send(versionRequest.toByteArray()) + + val response = receive() + if (response.isNotEmpty()) { + val responseStr = String(response) + Log.d(TAG, "Received server version response: $responseStr") + networkStatusListener?.onNetworkStatusChanged(true) + logServerDetails(server) // Log server details here + result = true + } else { + Log.w(TAG, "Empty response from server when verifying connection") + networkStatusListener?.onConnectionError("Empty response from server") + close() + } + } catch (e: Exception) { + Log.e(TAG, "Error connecting to Electrum server: ${e.javaClass.simpleName} - ${e.message}") + networkStatusListener?.onConnectionError("Error connecting: ${e.message}") + close() + } + + val duration = System.currentTimeMillis() - startTime + Log.d(TAG, "Connection attempt to ${server.host}:${server.port} completed in ${duration}ms, result: $result") + + result + } + + /** + * Send data to the connected Electrum server with network check + */ + suspend fun send(data: ByteArray): Boolean = withContext(Dispatchers.IO) { + val message = String(data).trim() + val messagePreview = if (message.length > 100) message.substring(0, 100) + "..." else message + Log.d(TAG, "Sending to Electrum: $messagePreview") + + if (!isNetworkAvailable()) { + Log.e(TAG, "Cannot send data: No network connection available") + networkStatusListener?.onNetworkStatusChanged(false) + return@withContext false + } + + try { + outputStream?.write(data) + outputStream?.flush() + Log.d(TAG, "Data sent successfully") + return@withContext true + } catch (e: Exception) { + Log.e(TAG, "Error sending data to Electrum server: ${e.javaClass.simpleName} - ${e.message}") + networkStatusListener?.onConnectionError("Error sending data: ${e.message}") + return@withContext false + } + } + + /** + * Receive data from the connected Electrum server with timeout handling + */ + suspend fun receive(): ByteArray = withContext(Dispatchers.IO) { + Log.d(TAG, "Waiting to receive data from Electrum server") + val startTime = System.currentTimeMillis() + + try { + val response = StringBuilder() + var line: String? = null + + try { + while (inputReader?.readLine()?.also { line = it } != null) { + response.append(line) + // Break after receiving a complete JSON object + if (line?.contains("}") == true) { + break + } + } + } catch (e: SocketTimeoutException) { + Log.e(TAG, "Socket read timed out after ${System.currentTimeMillis() - startTime}ms") + networkStatusListener?.onConnectionError("Socket read timed out") + } + + val responseData = response.toString().toByteArray() + val responsePreview = if (response.length > 100) response.substring(0, 100) + "..." else response.toString() + + if (responseData.isNotEmpty()) { + val duration = System.currentTimeMillis() - startTime + Log.d(TAG, "Received data (${responseData.size} bytes) in ${duration}ms: $responsePreview") + } else { + Log.w(TAG, "Received empty response from Electrum server") + } + + return@withContext responseData + } catch (e: Exception) { + Log.e(TAG, "Error receiving data from Electrum server: ${e.javaClass.simpleName} - ${e.message}") + networkStatusListener?.onConnectionError("Error receiving data: ${e.message}") + return@withContext ByteArray(0) + } + } + + /** + * Close the connection to the Electrum server + */ + fun close() { + try { + inputReader?.close() + outputStream?.close() + socket?.close() + } catch (e: Exception) { + Log.e(TAG, "Error closing Electrum connection", e) + } finally { + inputReader = null + outputStream = null + socket = null + } + } + + /** + * Create an SSL socket with optional certificate validation + */ + private fun createSslSocket(host: String, port: Int, validateCertificates: Boolean): SSLSocket { + val sslContext = SSLContext.getInstance("TLS") + + if (!validateCertificates) { + val trustAllCerts = arrayOf(object : X509TrustManager { + override fun getAcceptedIssuers(): Array = arrayOf() + override fun checkClientTrusted(certs: Array, authType: String) {} + override fun checkServerTrusted(certs: Array, authType: String) {} + }) + + sslContext.init(null, trustAllCerts, SecureRandom()) + } else { + sslContext.init(null, null, null) + } + + val factory: SSLSocketFactory = sslContext.socketFactory + return factory.createSocket(host, port) as SSLSocket + } + + private fun getNextPeer(): ElectrumServer { + val savedPeer = getSavedPeer() + return if (savedPeer != null) { + Log.d(TAG, "Using saved peer: ${savedPeer.host}:${savedPeer.port} (SSL: ${savedPeer.isSsl})") + savedPeer + } else { + Log.d(TAG, "No saved peer found. Using default hardcoded peers.") + hardcodedPeers.random() + } + } + + + private fun getSavedPeer(): ElectrumServer? { + // implement later + return null + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MainActivity.java b/android/app/src/main/java/io/bluewallet/bluewallet/MainActivity.java deleted file mode 100644 index 06101f38431..00000000000 --- a/android/app/src/main/java/io/bluewallet/bluewallet/MainActivity.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.bluewallet.bluewallet; - -import android.content.pm.ActivityInfo; -import android.os.Bundle; -import android.os.PersistableBundle; - -import androidx.annotation.Nullable; - -import com.facebook.react.ReactActivity; - -import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; -import com.facebook.react.defaults.DefaultReactActivityDelegate; - -public class MainActivity extends ReactActivity { - - /** - * Returns the name of the main component registered from JavaScript. - * This is used to schedule rendering of the component. - */ - @Override - protected String getMainComponentName() { - return "BlueWallet"; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(null); - if (getResources().getBoolean(R.bool.portrait_only)) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - } - - /** - * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link - * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React - * (aka React 18) with two boolean flags. - */ - @Override - protected ReactActivityDelegate createReactActivityDelegate() { - return new DefaultReactActivityDelegate( - this, - getMainComponentName(), - // If you opted-in for the New Architecture, we enable the Fabric Renderer. - DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled - // If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18). - DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled - ); - } -} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MainActivity.kt b/android/app/src/main/java/io/bluewallet/bluewallet/MainActivity.kt new file mode 100644 index 00000000000..ad2d173a13b --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MainActivity.kt @@ -0,0 +1,72 @@ +package io.bluewallet.bluewallet + +import android.content.Context +import android.content.pm.ActivityInfo +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.util.Log +import androidx.appcompat.app.AlertDialog +import com.facebook.react.ReactActivity +import com.facebook.react.ReactActivityDelegate +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint +import com.facebook.react.defaults.DefaultReactActivityDelegate +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled +import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory + +class MainActivity : ReactActivity() { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + override fun getMainComponentName(): String { + return "BlueWallet" + } + + override fun onCreate(savedInstanceState: Bundle?) { + // react-native-screens override + supportFragmentManager.fragmentFactory = RNScreensFragmentFactory() + super.onCreate(null) + if (resources.getBoolean(R.bool.portrait_only)) { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + } + + override fun onResume() { + super.onResume() + Log.d("MainActivity", "MainActivity resumed. Confirming single instance is active.") + + // Check if we should show cache cleared alert + checkAndShowCacheClearedAlert() + } + + private fun checkAndShowCacheClearedAlert() { + val sharedPref = getSharedPreferences("group.io.bluewallet.bluewallet", Context.MODE_PRIVATE) + val shouldShowAlert = sharedPref.getBoolean("shouldShowCacheClearedAlert", false) + + if (shouldShowAlert) { + // Reset the flag + sharedPref.edit() + .putBoolean("shouldShowCacheClearedAlert", false) + .apply() + + // Show alert after a short delay to ensure UI is ready + Handler(Looper.getMainLooper()).postDelayed({ + AlertDialog.Builder(this) + .setTitle(R.string.cache_cleared_title) + .setMessage(R.string.cache_cleared_message) + .setPositiveButton(android.R.string.ok, null) + .show() + }, 500) + } + } + + /** + * Returns the instance of the [ReactActivityDelegate]. Here we use a util class [DefaultReactActivityDelegate] + * which allows you to easily enable Fabric and Concurrent React (aka React 18) with two boolean flags. + */ + + override fun createReactActivityDelegate(): ReactActivityDelegate = + DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java deleted file mode 100644 index 29dc4e9b6d6..00000000000 --- a/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.bluewallet.bluewallet; - -import android.app.Application; -import android.content.Context; -import com.facebook.react.PackageList; -import com.facebook.react.ReactApplication; -import com.facebook.react.ReactInstanceManager; -import com.facebook.react.ReactNativeHost; -import com.facebook.react.ReactPackage; -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; -import com.facebook.react.defaults.DefaultReactNativeHost; -import com.facebook.soloader.SoLoader; -import java.lang.reflect.InvocationTargetException; -import com.facebook.react.modules.i18nmanager.I18nUtil; -import java.util.List; -import com.bugsnag.android.Bugsnag; - -public class MainApplication extends Application implements ReactApplication { - - private final ReactNativeHost mReactNativeHost = - new DefaultReactNativeHost(this) { - @Override - public boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - @Override - protected List getPackages() { - @SuppressWarnings("UnnecessaryLocalVariable") - List packages = new PackageList(this).getPackages(); - // Packages that cannot be autolinked yet can be added manually here, for example: - // packages.add(new MyReactNativePackage()); - return packages; - } - - @Override - protected String getJSMainModuleName() { - return "index"; - } - - @Override - protected boolean isNewArchEnabled() { - return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; - } - - @Override - protected Boolean isHermesEnabled() { - return BuildConfig.IS_HERMES_ENABLED; - } - }; - - @Override - public ReactNativeHost getReactNativeHost() { - return mReactNativeHost; - } - - @Override - public void onCreate() { - super.onCreate(); - Bugsnag.start(this); - I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance(); - sharedI18nUtilInstance.allowRTL(getApplicationContext(), true); - SoLoader.init(this, /* native exopackage */ false); - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // If you opted-in for the New Architecture, we load the native entry point for this app. - DefaultNewArchitectureEntryPoint.load(); - } - } -} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.kt b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.kt new file mode 100644 index 00000000000..6f27b0ef313 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.kt @@ -0,0 +1,218 @@ +package io.bluewallet.bluewallet + +import android.app.Application +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.SharedPreferences +import android.util.Log +import com.bugsnag.android.Bugsnag +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactHost +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.react.modules.i18nmanager.I18nUtil +import io.bluewallet.bluewallet.components.segmentedcontrol.SegmentedControlPackage + +class MainApplication : Application(), ReactApplication { + + private lateinit var sharedPref: SharedPreferences + private val themeChangeReceiver = ThemeChangeReceiver() + private val preferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { prefs, key -> + if (key == "preferredCurrency") { + prefs.edit().remove("previous_price").apply() + + // Update BitcoinPrice widgets + WidgetUpdateWorker.scheduleWork(this) + + // Immediately refresh Market widgets + MarketWidget.refreshAllWidgetsImmediately(this) + } else if (key == "force_dark_mode") { + // Theme setting changed, update all widgets + ThemeHelper.updateAllWidgets(this) + } else if (key == "donottrack") { + // Handle Do Not Track changes similar to iOS + val isEnabled = prefs.getString("donottrack", "0") == "1" + Log.d("MainApplication", "Do Not Track changed to: $isEnabled") + + if (isEnabled) { + // Set deviceUIDCopy to "Disabled" + prefs.edit() + .putString("deviceUIDCopy", "Disabled") + .apply() + Log.d("MainApplication", "Do Not Track enabled - set deviceUIDCopy to 'Disabled'") + } else { + // Re-initialize device UID + initializeDeviceUID() + } + } else if (key == "deviceUID") { + // When deviceUID changes, update deviceUIDCopy + val isDoNotTrackEnabled = prefs.getString("donottrack", "0") == "1" + if (!isDoNotTrackEnabled) { + val deviceUID = prefs.getString("deviceUID", null) + if (deviceUID != null) { + prefs.edit() + .putString("deviceUIDCopy", deviceUID) + .apply() + Log.d("MainApplication", "deviceUID changed, synced to deviceUIDCopy: $deviceUID") + } + } + } + } + + override val reactNativeHost: ReactNativeHost by lazy { + object : DefaultReactNativeHost(this) { + override fun getPackages() = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + add(SegmentedControlPackage()) + add(SettingsPackage()) + } + + override fun getUseDeveloperSupport() = BuildConfig.DEBUG + + override fun getJSMainModuleName() = "index" + } + } + + override val reactHost: ReactHost by lazy { + getDefaultReactHost(applicationContext, reactNativeHost) + } + + override fun onCreate() { + super.onCreate() + sharedPref = getSharedPreferences("group.io.bluewallet.bluewallet", Context.MODE_PRIVATE) + + // Handle clearFilesOnLaunch before registering listeners + clearFilesIfNeeded() + + sharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener) + + // Register the theme change receiver + registerReceiver(themeChangeReceiver, IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)) + + val sharedI18nUtilInstance = I18nUtil.getInstance() + sharedI18nUtilInstance.allowRTL(applicationContext, true) + loadReactNative(this) + + initializeDeviceUID() + initializeBugsnag() + } + + override fun onTerminate() { + super.onTerminate() + sharedPref.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener) + + // Unregister the theme change receiver + try { + unregisterReceiver(themeChangeReceiver) + } catch (e: Exception) { + Log.e("MainApplication", "Error unregistering theme receiver", e) + } + } + + private fun initializeBugsnag() { + val isDoNotTrackEnabled = sharedPref.getString("donottrack", "0") + if (isDoNotTrackEnabled != "1") { + Bugsnag.start(this) + } + } + + /** + * Initialize device UID similar to iOS implementation + * Uses the same Android ID as react-native-device-info's getUniqueId() + */ + private fun initializeDeviceUID() { + val isDoNotTrackEnabled = sharedPref.getString("donottrack", "0") == "1" + + if (isDoNotTrackEnabled) { + val currentCopy = sharedPref.getString("deviceUIDCopy", "") + if (currentCopy != "Disabled") { + sharedPref.edit() + .putString("deviceUIDCopy", "Disabled") + .apply() + Log.d("MainApplication", "Do Not Track enabled - set deviceUIDCopy to 'Disabled'") + } + return + } + + // Get the Android ID (same as react-native-device-info's getUniqueId()) + val deviceUID = try { + android.provider.Settings.Secure.getString( + contentResolver, + android.provider.Settings.Secure.ANDROID_ID + ) ?: "unknown" + } catch (e: Exception) { + Log.e("MainApplication", "Error getting Android ID", e) + "unknown" + } + + // Store in deviceUID for consistency + sharedPref.edit() + .putString("deviceUID", deviceUID) + .apply() + + // Copy deviceUID to deviceUIDCopy (for Settings compatibility) + val currentCopy = sharedPref.getString("deviceUIDCopy", "") + if (deviceUID != currentCopy) { + sharedPref.edit() + .putString("deviceUIDCopy", deviceUID) + .apply() + Log.d("MainApplication", "Synced deviceUID to deviceUIDCopy: $deviceUID") + } + } + + /** + * Clear files if clearFilesOnLaunch is enabled + * Similar to iOS implementation + */ + private fun clearFilesIfNeeded() { + val shouldClear = sharedPref.getBoolean("clearFilesOnLaunch", false) + + if (shouldClear) { + try { + // Clear cache directory + cacheDir?.let { clearDirectory(it) } + + // Clear files directory + filesDir?.let { clearDirectory(it) } + + // Clear external cache directory + externalCacheDir?.let { clearDirectory(it) } + + // Reset the flag and set a flag to show alert + sharedPref.edit() + .putBoolean("clearFilesOnLaunch", false) + .putBoolean("shouldShowCacheClearedAlert", true) + .apply() + + Log.d("MainApplication", "Cache and files cleared on launch") + } catch (e: Exception) { + Log.e("MainApplication", "Error clearing files", e) + } + } + } + + /** + * Recursively clear all files in a directory + */ + private fun clearDirectory(dir: java.io.File) { + if (!dir.exists()) return + + dir.listFiles()?.forEach { file -> + if (file.isDirectory) { + clearDirectory(file) + } + try { + file.delete() + Log.d("MainApplication", "Deleted: ${file.absolutePath}") + } catch (e: Exception) { + Log.e("MainApplication", "Error deleting file: ${file.absolutePath}", e) + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.kt b/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.kt new file mode 100644 index 00000000000..76aac60620f --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.kt @@ -0,0 +1,450 @@ +package io.bluewallet.bluewallet + +import android.content.Context +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import org.json.JSONArray +import org.json.JSONObject +import java.text.NumberFormat +import java.util.Currency +import kotlin.math.min + +object MarketAPI { + + private const val TAG = "MarketAPI" + private val client = OkHttpClient() + private val numberFormatter = NumberFormat.getNumberInstance() + private val electrumClient = ElectrumClient() + + private var lastFetchedFee: String? = null + + // Single indicator for error/unavailable + private const val ERROR_INDICATOR = "!" + + var baseUrl: String? = null + + data class ApiResponse(val body: String?, val code: Int) + data class PriceResult(val rateDouble: Double, val formattedRate: String?) + + suspend fun fetchPrice(context: Context, currency: String): String? { + Log.i(TAG, "Fetching Bitcoin price for currency: $currency") + val startTime = System.currentTimeMillis() + + return try { + val response = fetchPriceWithResponse(context, currency) + val duration = System.currentTimeMillis() - startTime + + if (response.code == 200) { + Log.i(TAG, "Successfully fetched price in ${duration}ms: ${response.body}") + response.body + } else { + Log.e(TAG, "Failed to fetch price in ${duration}ms, response code: ${response.code}") + null + } + } catch (e: Exception) { + Log.e(TAG, "Error fetching price for $currency", e) + null + } + } + + suspend fun fetchPriceWithResponse(context: Context, currency: String): ApiResponse { + val startTime = System.currentTimeMillis() + Log.d(TAG, "Starting price fetch for currency: $currency") + + try { + // Load the currency info from JSON + val fiatUnitsJson = context.assets.open("fiatUnits.json").bufferedReader().use { it.readText() } + val json = JSONObject(fiatUnitsJson) + + if (!json.has(currency)) { + Log.e(TAG, "Currency $currency not found in fiatUnits.json") + return ApiResponse(null, 404) + } + + val currencyInfo = json.getJSONObject(currency) + val source = currencyInfo.getString("source") + val endPointKey = currencyInfo.getString("endPointKey") + + Log.d(TAG, "Using price source: $source, endpoint key: $endPointKey") + + val urlString = buildURLString(source, endPointKey) + Log.d(TAG, "Fetching price from URL: $urlString") + + val request = Request.Builder().url(urlString).build() + val apiStartTime = System.currentTimeMillis() + + val response = withContext(Dispatchers.IO) { client.newCall(request).execute() } + val apiDuration = System.currentTimeMillis() - apiStartTime + + val responseCode = response.code + Log.d(TAG, "Price API response received in ${apiDuration}ms, response code: $responseCode") + + if (responseCode == 429) { + Log.e(TAG, "Rate limited by API ($source). Response code: $responseCode, Headers: ${response.headers}") + return ApiResponse(null, responseCode) + } + + if (!response.isSuccessful) { + Log.e(TAG, "Failed to fetch price from $source. Response code: $responseCode") + return ApiResponse(null, responseCode) + } + + val jsonResponse = response.body?.string() + Log.d(TAG, "Raw response from $source: $jsonResponse") + + val parsedResult = if (jsonResponse != null) { + parseJSONBasedOnSource(jsonResponse, source, endPointKey) + } else null + + val totalDuration = System.currentTimeMillis() - startTime + if (parsedResult != null) { + Log.i(TAG, "Successfully parsed price for $currency from $source: $parsedResult (total time: ${totalDuration}ms)") + } else { + Log.e(TAG, "Failed to parse price for $currency from $source (total time: ${totalDuration}ms)") + } + + return ApiResponse(parsedResult, responseCode) + } catch (e: Exception) { + val totalDuration = System.currentTimeMillis() - startTime + Log.e(TAG, "Error fetching price for $currency after ${totalDuration}ms: ${e.javaClass.simpleName} - ${e.message}") + return ApiResponse(null, -1) + } + } + + private fun buildURLString(source: String, endPointKey: String): String { + return if (baseUrl != null) { + baseUrl + endPointKey + } else { + when (source) { + "Yadio" -> "https://api.yadio.io/json/$endPointKey" + "YadioConvert" -> "https://api.yadio.io/convert/1/BTC/$endPointKey" + "Exir" -> "https://api.exir.io/v1/ticker?symbol=btc-irt" + "coinpaprika" -> "https://api.coinpaprika.com/v1/tickers/btc-bitcoin?quotes=INR" + "Bitstamp" -> "https://www.bitstamp.net/api/v2/ticker/btc${endPointKey.lowercase()}" + "Coinbase" -> "https://api.coinbase.com/v2/prices/BTC-${endPointKey.uppercase()}/buy" + "CoinGecko" -> "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=${endPointKey.lowercase()}" + "BNR" -> "https://www.bnr.ro/nbrfxrates.xml" + "Kraken" -> "https://api.kraken.com/0/public/Ticker?pair=XXBTZ${endPointKey.uppercase()}" + "CoinDesk" -> "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=${endPointKey.uppercase()}" + else -> "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=${endPointKey.uppercase()}" + } + } + } + + private fun parseJSONBasedOnSource(jsonString: String, source: String, endPointKey: String): String? { + return try { + val json = JSONObject(jsonString) + when (source) { + "Yadio" -> json.getJSONObject(endPointKey).getString("price") + "YadioConvert" -> json.getString("rate") + "CoinGecko" -> json.getJSONObject("bitcoin").getString(endPointKey.lowercase()) + "Exir" -> json.getString("last") + "Bitstamp" -> json.getString("last") + "coinpaprika" -> json.getJSONObject("quotes").getJSONObject("INR").getString("price") + "Coinbase" -> json.getJSONObject("data").getString("amount") + "Kraken" -> json.getJSONObject("result").getJSONObject("XXBTZ${endPointKey.uppercase()}").getJSONArray("c").getString(0) + "CoinDesk" -> { + val rate = json.optDouble(endPointKey.uppercase(), -1.0) + if (rate < 0) null else rate.toString() + } + else -> null + } + } catch (e: Exception) { + Log.e(TAG, "Error parsing price", e) + null + } + } + + /** + * Fetch the next block fee from Electrum servers with network awareness + */ + suspend fun fetchNextBlockFee(context: Context): String { + val startTime = System.currentTimeMillis() + Log.i(TAG, "Fetching next block fee from Electrum") + + // Initialize ElectrumClient with context if not already done + electrumClient.initialize(context) + + // Set up network status listener + electrumClient.setNetworkStatusListener(object : ElectrumClient.NetworkStatusListener { + override fun onNetworkStatusChanged(isConnected: Boolean) { + Log.d(TAG, "Electrum network status changed: ${if (isConnected) "Connected" else "Disconnected"}") + } + + override fun onConnectionError(error: String) { + Log.e(TAG, "Electrum connection error: $error") + } + + override fun onConnectionSuccess() { + Log.d(TAG, "Successfully connected to Electrum server") + } + }) + + try { + // Check network connectivity first + if (!NetworkUtils.isNetworkAvailable(context)) { + Log.e(TAG, "No network connection available for fetching next block fee") + return ERROR_INDICATOR + } + + // For direct testing with hardcoded value + val useTestValue = false + if (useTestValue) { + Log.w(TAG, "Using TEST VALUE for next block fee") + return "25" + } + + // First try connecting directly for fee histogram + Log.d(TAG, "Attempting to connect directly to Electrum server for fee") + var success = electrumClient.connectToNextAvailable(validateCertificates = false) + + if (success) { + Log.i(TAG, "Connected to Electrum server: ${ElectrumClient.hardcodedPeers}") + } else { + Log.e(TAG, "Failed to connect to any Electrum server on first attempt. Retrying once more.") + } + + if (!success) { + Log.e(TAG, "Failed to connect to any Electrum server on first attempt. Retrying once more.") + // Short delay before retry + delay(1000) + success = electrumClient.connectToNextAvailable(validateCertificates = false) + + if (!success) { + Log.e(TAG, "Failed to connect to any Electrum server after retry. Fee unavailable.") + return ERROR_INDICATOR + } + } + + Log.d(TAG, "Successfully connected to Electrum server. Sending fee histogram request") + val message = "{\"id\": 1, \"method\": \"mempool.get_fee_histogram\", \"params\": []}\n" + if (!electrumClient.send(message.toByteArray())) { + Log.e(TAG, "Failed to send fee histogram request. Fee unavailable.") + return ERROR_INDICATOR + } + + Log.d(TAG, "Waiting for fee histogram response") + val receivedData = electrumClient.receive() + if (receivedData.isEmpty()) { + Log.e(TAG, "Empty response from Electrum server when requesting fee histogram. Fee unavailable.") + return ERROR_INDICATOR + } + + val jsonString = String(receivedData) + Log.d(TAG, "Received fee histogram: $jsonString") + + try { + val json = JSONObject(jsonString) + if (!json.has("result")) { + Log.e(TAG, "Invalid fee histogram response - missing 'result' field. Fee unavailable.") + return ERROR_INDICATOR + } + + val feeHistogram = json.getJSONArray("result") + if (feeHistogram.length() == 0) { + Log.e(TAG, "Empty fee histogram array. Fee unavailable.") + return ERROR_INDICATOR + } + + Log.d(TAG, "Calculating fee from ${feeHistogram.length()} data points") + + val feeRate = calculateFeeFromHistogram(feeHistogram, 1) + if (feeRate <= 0) { + Log.e(TAG, "Invalid fee rate calculated: $feeRate. Fee unavailable.") + return ERROR_INDICATOR + } + + val formattedFee = feeRate.toInt().toString() + + Log.i(TAG, "Successfully calculated next block fee: $formattedFee sat/vB") + return formattedFee + } catch (e: Exception) { + Log.e(TAG, "Error parsing fee histogram JSON: ${e.message}", e) + return ERROR_INDICATOR + } + } catch (e: Exception) { + Log.e(TAG, "Error fetching next block fee: ${e.message}", e) + return ERROR_INDICATOR + } finally { + electrumClient.close() + } + } + + /** + * Calculate the estimated fee from the fee histogram + * + * @param feeHistogram the fee histogram from Electrum + * @param targetBlocks the target number of blocks to confirm in + * @return the fee rate in sat/vB that would get confirmed in the target number of blocks + */ + private fun calculateFeeFromHistogram(feeHistogram: JSONArray, targetBlocks: Int): Double { + try { + Log.d(TAG, "Calculating fee from histogram with ${feeHistogram.length()} entries for $targetBlocks blocks") + + // Transform histogram - accumulate vsize until we reach the target block size + val blockSize = 1000000 // 1MB block size + var totalVsize = 0.0 + val histogramToUse = mutableListOf>() // (fee, vsize) + + for (i in 0 until feeHistogram.length()) { + val entry = feeHistogram.getJSONArray(i) + val feeRate = entry.getDouble(0) + var vsize = entry.getDouble(1) + var timeToStop = false + + if (totalVsize + vsize >= blockSize * targetBlocks) { + // Only take what we need to fill the target block size + vsize = blockSize * targetBlocks - totalVsize + timeToStop = true + } + + histogramToUse.add(Pair(feeRate, vsize)) + totalVsize += vsize + + Log.v(TAG, "Fee entry: rate=$feeRate, vsize=$vsize, accumulated=$totalVsize") + + if (timeToStop) break + } + + Log.d(TAG, "Transformed histogram has ${histogramToUse.size} entries with total vsize $totalVsize") + + // Create a weighted flat array (similar to the JS implementation) + val histogramFlat = mutableListOf() + for ((fee, vsize) in histogramToUse) { + // Divide by a factor to keep the array size manageable + val count = (vsize / 25000.0).toInt().coerceAtLeast(1) + repeat(count) { + histogramFlat.add(fee) + } + } + + if (histogramFlat.isEmpty()) { + Log.e(TAG, "Empty flat histogram array") + return 0.0 // Return 0 to indicate failure, will be caught and converted to ERROR_INDICATOR + } + + // Sort the flat array + histogramFlat.sort() + + // Calculate the median (50th percentile) + val median = calculatePercentile(histogramFlat, 0.5) + val result = median.coerceAtLeast(2.0) // Minimum 2 sat/vB + + Log.d(TAG, "Calculated median fee rate: $median, final rate: $result sat/vB") + return result + + } catch (e: Exception) { + Log.e(TAG, "Error calculating fee from histogram: ${e.message}", e) + return 0.0 // Return 0 to indicate failure, will be caught and converted to ERROR_INDICATOR + } + } + + /** + * Calculate the percentile of a sorted list of values + * + * @param sortedValues the sorted list of values + * @param percentile the percentile to calculate (0.0 - 1.0) + * @return the percentile value + */ + private fun calculatePercentile(sortedValues: List, percentile: Double): Double { + if (sortedValues.isEmpty()) return 0.0 + + val index = (percentile * sortedValues.size).toInt().coerceIn(0, sortedValues.size - 1) + return sortedValues[index] + } + + /** + * Format price with currency symbol + */ + fun formatCurrencyAmount(amount: Double, currencyCode: String): String { + val formatter = NumberFormat.getCurrencyInstance() + try { + formatter.currency = Currency.getInstance(currencyCode) + formatter.maximumFractionDigits = 0 // Ensure no fractional parts + } catch (e: Exception) { + Log.e(TAG, "Invalid currency code: $currencyCode", e) + } + return formatter.format(amount.toInt()) // Convert to integer before formatting + } + + /** + * Fetch complete market data including price and next block fee + */ + suspend fun fetchMarketData(context: Context, currency: String): MarketData { + val startTime = System.currentTimeMillis() + Log.i(TAG, "Starting market data fetch for currency: $currency") + + val marketData = MarketData(nextBlock = "...", sats = "...", price = "...", rate = 0.0) + + try { + // Check network connectivity first + if (!NetworkUtils.isNetworkAvailable(context)) { + Log.e(TAG, "No network connection available for fetching market data") + return marketData.apply { + nextBlock = ERROR_INDICATOR + sats = ERROR_INDICATOR + price = ERROR_INDICATOR + } + } + + // 1. Fetch price + Log.d(TAG, "Fetching price for $currency") + val priceStartTime = System.currentTimeMillis() + val response = fetchPriceWithResponse(context, currency) + val priceDuration = System.currentTimeMillis() - priceStartTime + + if (response.code == 429) { + Log.e(TAG, "Rate limited by price API, aborting market data fetch") + throw RateLimitException("Rate limited by price API") + } + + val priceStr = response.body + if (priceStr != null) { + val rate = priceStr.toDoubleOrNull() ?: 0.0 + marketData.rate = rate + Log.d(TAG, "Parsed price rate: $rate") + + if (rate > 0) { + // Format price with currency symbol - convert to integer + marketData.price = formatCurrencyAmount(rate, currency) + Log.d(TAG, "Formatted price: ${marketData.price}") + + // Calculate sats - convert to integer for display + val satsValue = ((10 / rate) * 10000000).toInt() + marketData.sats = numberFormatter.format(satsValue) + Log.d(TAG, "Calculated sats: ${marketData.sats}") + } else { + Log.w(TAG, "Price rate is zero or negative: $rate") + } + } else { + Log.w(TAG, "No price data received") + } + + // 2. Fetch next block fee - Always run this, regardless of price fetch result + Log.d(TAG, "Fetching next block fee") + val feeStartTime = System.currentTimeMillis() + val nextBlockFee = fetchNextBlockFee(context) + val feeDuration = System.currentTimeMillis() - feeStartTime + + Log.d(TAG, "Next block fee fetched in ${feeDuration}ms: $nextBlockFee") + marketData.nextBlock = nextBlockFee + Log.i(TAG, "Set nextBlock fee in marketData: ${marketData.nextBlock}") + + val totalDuration = System.currentTimeMillis() - startTime + Log.i(TAG, "Market data fetch completed in ${totalDuration}ms: $marketData") + + } catch (e: RateLimitException) { + Log.e(TAG, "Rate limit exception during market data fetch: ${e.message}") + throw e + } catch (e: Exception) { + val duration = System.currentTimeMillis() - startTime + Log.e(TAG, "Error fetching market data after ${duration}ms: ${e.javaClass.simpleName} - ${e.message}", e) + } + + return marketData + } +} \ No newline at end of file diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MarketData.kt b/android/app/src/main/java/io/bluewallet/bluewallet/MarketData.kt new file mode 100644 index 00000000000..2755cb7b45c --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MarketData.kt @@ -0,0 +1,61 @@ +package io.bluewallet.bluewallet + +import android.util.Log +import java.text.NumberFormat +import java.util.Locale +import java.util.Date + +data class MarketData( + var nextBlock: String = "...", + var sats: String = "...", + var price: String = "...", + var rate: Double = 0.0, + var dateString: String = "" +) { + val formattedNextBlock: String + get() { + Log.d("MarketData", "Getting formatted next block from value: '$nextBlock'") + return when (nextBlock) { + "..." -> { + Log.d("MarketData", "Next block is a loading placeholder") + "..." + } + "!" -> { + Log.d("MarketData", "Next block is an error placeholder") + "!" + } + else -> { + try { + val nextBlockInt = nextBlock.toInt() + val numberFormatter = NumberFormat.getNumberInstance() + val formattedValue = "${numberFormatter.format(nextBlockInt)} sat/vb" + Log.d("MarketData", "Formatted next block: $formattedValue from $nextBlock") + formattedValue + } catch (e: Exception) { + Log.e("MarketData", "Error formatting next block value: '$nextBlock'", e) + "$nextBlock sat/vb" + } + } + } + } + + val formattedDate: String? + get() { + if (dateString.isEmpty()) return null + + try { + // Simple implementation - proper implementation would parse ISO8601 + return Date().toString() + } catch (e: Exception) { + return null + } + } + + companion object { + const val PREF_KEY = "market_data" + } + + override fun toString(): String { + return "MarketData(nextBlock=$nextBlock, sats=$sats, price=$price, rate=$rate, formattedNextBlock=$formattedNextBlock)" + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MarketWidget.kt b/android/app/src/main/java/io/bluewallet/bluewallet/MarketWidget.kt new file mode 100644 index 00000000000..e0c7d168fb6 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MarketWidget.kt @@ -0,0 +1,229 @@ +package io.bluewallet.bluewallet + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.util.Log +import android.view.View +import android.widget.RemoteViews +import androidx.work.WorkManager +import kotlinx.coroutines.delay +import org.json.JSONObject +import java.util.concurrent.TimeUnit +import io.bluewallet.bluewallet.ElectrumClient.ElectrumServer + +class MarketWidget : AppWidgetProvider() { + + companion object { + private const val TAG = "MarketWidget" + private const val SHARED_PREF_NAME = "group.io.bluewallet.bluewallet" + private const val DEFAULT_CURRENCY = "USD" + private const val KEY_LAST_ONLINE_STATUS = "market_widget_last_online_status" + + private val hardcodedPeers = listOf( + ElectrumServer("mainnet.foundationdevices.com", 50002, true), + ElectrumServer("electrum1.bluewallet.io", 443, true), + ElectrumServer("electrum.acinq.co", 50002, true), + ElectrumServer("electrum.bitaroo.net", 50002, true) + ) + + private suspend fun connectToElectrumServer(): Boolean { + for (peer in hardcodedPeers) { + repeat(3) { attempt -> + Log.d(TAG, "Attempting to connect to Electrum server: ${peer.host}:${peer.port}, Attempt: ${attempt + 1}") + val success = ElectrumClient().connect(peer, validateCertificates = true) + if (success) { + Log.i(TAG, "Successfully connected to Electrum server: ${peer.host}:${peer.port}") + return true + } else { + Log.w(TAG, "Failed to connect to Electrum server: ${peer.host}:${peer.port}, Attempt: ${attempt + 1}") + } + } + } + Log.e(TAG, "Failed to connect to any Electrum server from the hardcoded list after 3 attempts each. Waiting 10 minutes before retrying.") + delay(10 * 60 * 1000) // Wait for 10 minutes + return false + } + + fun updateWidget(context: Context, appWidgetId: Int) { + val appWidgetManager = AppWidgetManager.getInstance(context) + updateAppWidget(context, appWidgetManager, appWidgetId) + } + + fun updateAllWidgets(context: Context) { + val widgetIds = getAllWidgetIds(context) + if (widgetIds.isNotEmpty()) { + MarketWidgetUpdateWorker.scheduleMarketUpdate(context) + } + } + + fun refreshAllWidgetsImmediately(context: Context) { + val widgetIds = getAllWidgetIds(context) + if (widgetIds.isNotEmpty()) { + val appWidgetManager = AppWidgetManager.getInstance(context) + for (widgetId in widgetIds) { + updateAppWidget(context, appWidgetManager, widgetId) + } + + MarketWidgetUpdateWorker.scheduleMarketUpdate(context, forceUpdate = true) + + Log.d(TAG, "Scheduled immediate market widget update") + } + } + + fun getAllWidgetIds(context: Context): IntArray { + val appWidgetManager = AppWidgetManager.getInstance(context) + val thisWidget = ComponentName(context, MarketWidget::class.java) + return appWidgetManager.getAppWidgetIds(thisWidget) + } + + private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) { + Log.d(TAG, "Updating widget: $appWidgetId") + + // Check network connectivity + val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context) + + // Store connectivity status + storeConnectivityStatus(context, isNetworkAvailable) + + // Get market data from shared preferences + val marketData = getStoredMarketData(context) + Log.d(TAG, "Retrieved market data for widget: $marketData") + + // Create RemoteViews to update the widget + val views = RemoteViews(context.packageName, R.layout.widget_market) + + views.setViewVisibility(R.id.network_status, if (isNetworkAvailable) View.GONE else View.VISIBLE) + + // Add click intent to open the app + val intent = Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TOP or + Intent.FLAG_ACTIVITY_SINGLE_TOP + action = Intent.ACTION_MAIN + addCategory(Intent.CATEGORY_LAUNCHER) + } + + val pendingIntent = PendingIntent.getActivity( + context, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + views.setOnClickPendingIntent(R.id.widget_market, pendingIntent) + + // Set the text for each view + val formattedNextBlock = marketData.formattedNextBlock + Log.d(TAG, "Setting next block value to: '$formattedNextBlock'") + + val displayText = when (formattedNextBlock) { + "..." -> context.getString(R.string.loading_placeholder, "...") + "!" -> context.getString(R.string.error_placeholder, "!") + else -> formattedNextBlock + } + views.setTextViewText(R.id.next_block_value, displayText) + + // Get the user preferred currency + val currency = getPreferredCurrency(context) + views.setTextViewText(R.id.sats_label, context.getString(R.string.market_sats_label, currency)) + views.setTextViewText(R.id.sats_value, marketData.sats) + views.setTextViewText(R.id.price_value, marketData.price) + + // Update the widget + appWidgetManager.updateAppWidget(appWidgetId, views) + + // Schedule update if network available, otherwise retry in 30 seconds + if (isNetworkAvailable) { + MarketWidgetUpdateWorker.scheduleMarketUpdate(context) + } else { + MarketWidgetUpdateWorker.scheduleRetryOnNetworkAvailable(context) + } + } + + + + private fun storeConnectivityStatus(context: Context, isOnline: Boolean) { + val sharedPrefs = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + sharedPrefs.edit().putBoolean(KEY_LAST_ONLINE_STATUS, isOnline).apply() + } + + private fun getStoredMarketData(context: Context): MarketData { + val sharedPrefs = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + val marketDataJson = sharedPrefs.getString(MarketData.PREF_KEY, null) + + Log.d(TAG, "Reading market data from preferences: $marketDataJson") + + return if (marketDataJson != null) { + try { + val json = JSONObject(marketDataJson) + val nextBlock = json.optString("nextBlock", "...") + Log.d(TAG, "Retrieved nextBlock from storage: $nextBlock") + + MarketData( + nextBlock = nextBlock, + sats = json.optString("sats", "..."), + price = json.optString("price", "..."), + rate = json.optDouble("rate", 0.0), + dateString = json.optString("dateString", "") + ) + } catch (e: Exception) { + Log.e(TAG, "Error parsing stored market data", e) + MarketData() + } + } else { + Log.d(TAG, "No market data found in preferences") + MarketData() + } + } + + private fun getPreferredCurrency(context: Context): String { + val sharedPrefs = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + val preferredCurrency = sharedPrefs.getString("preferredCurrency", null) + return preferredCurrency ?: DEFAULT_CURRENCY // Default to USD if no currency is saved + } + } + + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + super.onUpdate(context, appWidgetManager, appWidgetIds) + Log.d(TAG, "MarketWidget onUpdate called. Widget IDs: ${appWidgetIds.joinToString()}") + + for (appWidgetId in appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId) + } + + MarketWidgetUpdateWorker.scheduleMarketUpdate(context) + } + + override fun onEnabled(context: Context) { + super.onEnabled(context) + Log.d(TAG, "MarketWidget enabled - First widget added") + val widgetIds = getAllWidgetIds(context) + if (widgetIds.isNotEmpty()) { + MarketWidgetUpdateWorker.scheduleMarketUpdate(context, forceUpdate = true) + } + } + + override fun onDisabled(context: Context) { + super.onDisabled(context) + Log.d(TAG, "MarketWidget disabled - Last widget removed") + // Cancel all scheduled work when last widget is removed + val workManager = WorkManager.getInstance(context) + workManager.cancelUniqueWork(MarketWidgetUpdateWorker.WORK_NAME) + workManager.cancelUniqueWork(MarketWidgetUpdateWorker.NETWORK_RETRY_WORK_NAME) + + // Clear cached data + clearMarketData(context) + } + + private fun clearMarketData(context: Context) { + context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .edit() + .remove(MarketData.PREF_KEY) + .remove(KEY_LAST_ONLINE_STATUS) + .apply() + Log.d(TAG, "Market widget data cleared") + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MarketWidgetConfigureActivity.kt b/android/app/src/main/java/io/bluewallet/bluewallet/MarketWidgetConfigureActivity.kt new file mode 100644 index 00000000000..faa146a7490 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MarketWidgetConfigureActivity.kt @@ -0,0 +1,46 @@ +package io.bluewallet.bluewallet + +import android.appwidget.AppWidgetManager +import android.content.Intent +import android.os.Bundle +import android.widget.Button +import androidx.appcompat.app.AppCompatActivity + +/** + * Configuration activity for the Market Widget. + * This allows the widget to be properly configured when added to the home screen. + */ +class MarketWidgetConfigureActivity : AppCompatActivity() { + + private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Set the result to CANCELED. This will be overridden if the user + // configures the widget properly and clicks the Add button + setResult(RESULT_CANCELED) + + // Find the widget id from the intent + appWidgetId = intent.extras?.getInt( + AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID + ) ?: AppWidgetManager.INVALID_APPWIDGET_ID + + // If the widget ID is invalid, just finish the activity + if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + finish() + return + } + + // Currently no configuration needed, so we set result to OK right away + val resultValue = Intent() + resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + setResult(RESULT_OK, resultValue) + + MarketWidgetUpdateWorker.scheduleMarketUpdate(this, forceUpdate = true) + + // Finish the activity + finish() + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MarketWidgetUpdateWorker.kt b/android/app/src/main/java/io/bluewallet/bluewallet/MarketWidgetUpdateWorker.kt new file mode 100644 index 00000000000..236f727a833 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MarketWidgetUpdateWorker.kt @@ -0,0 +1,226 @@ +package io.bluewallet.bluewallet + +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.content.Context +import android.util.Log +import androidx.work.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import java.util.concurrent.TimeUnit + +class MarketWidgetUpdateWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { + + companion object { + const val TAG = "MarketWidgetUpdateWorker" + const val WORK_NAME = "market_widget_update_work" + const val NETWORK_RETRY_WORK_NAME = "market_network_retry_work" + private const val SHARED_PREF_NAME = "group.io.bluewallet.bluewallet" + private const val DEFAULT_CURRENCY = "USD" + private const val KEY_LAST_UPDATE_TIME = "market_widget_last_update_time" + private const val MIN_UPDATE_INTERVAL_MS = 15L * 60 * 1000 + private const val RATE_LIMIT_COOLDOWN_MS = 30L * 60 * 1000 + private const val NETWORK_RETRY_DELAY_SECONDS = 30L + + fun scheduleMarketUpdate(context: Context, forceUpdate: Boolean = false) { + val sharedPrefs = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + val lastUpdateTime = sharedPrefs.getLong(KEY_LAST_UPDATE_TIME, 0) + val currentTime = System.currentTimeMillis() + + if (!forceUpdate && currentTime - lastUpdateTime < MIN_UPDATE_INTERVAL_MS) { + Log.d(TAG, "Skipping update - too soon since last update") + return + } + + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + val initialDelay = if (forceUpdate) 0 else calculateInitialDelay(context) + + val updateRequest = OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .setInitialDelay(initialDelay, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.MINUTES) + .build() + + WorkManager.getInstance(context).enqueueUniqueWork( + WORK_NAME, + ExistingWorkPolicy.REPLACE, + updateRequest + ) + + Log.d(TAG, "Scheduled market widget update work with delay: ${initialDelay}ms") + } + + /** + * Calculate delay for rate limiting + */ + private fun calculateInitialDelay(context: Context): Long { + val sharedPrefs = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + val rateLimitedTime = sharedPrefs.getLong("market_widget_rate_limited_time", 0) + val currentTime = System.currentTimeMillis() + + return if (rateLimitedTime > 0 && currentTime - rateLimitedTime < RATE_LIMIT_COOLDOWN_MS) { + val remainingCooldown = RATE_LIMIT_COOLDOWN_MS - (currentTime - rateLimitedTime) + Log.d(TAG, "Rate limit cooldown active, delaying for ${remainingCooldown}ms") + remainingCooldown + } else { + 0 + } + } + + fun scheduleRetryOnNetworkAvailable(context: Context) { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + val updateRequest = OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .setInitialDelay(NETWORK_RETRY_DELAY_SECONDS, TimeUnit.SECONDS) + .build() + + WorkManager.getInstance(context).enqueueUniqueWork( + NETWORK_RETRY_WORK_NAME, + ExistingWorkPolicy.REPLACE, + updateRequest + ) + + Log.d(TAG, "Scheduled network retry in $NETWORK_RETRY_DELAY_SECONDS seconds") + } + } + + override suspend fun doWork(): Result { + Log.d(TAG, "MarketWidgetUpdateWorker running. Confirming interaction with MainActivity.") + return updateMarketWidgets() + } + + private suspend fun updateMarketWidgets(): Result { + Log.d(TAG, "Starting market widget update work") + val widgetIds = MarketWidget.getAllWidgetIds(applicationContext) + + val currency = getPreferredCurrency(applicationContext) + + try { + markUpdateTime() + + // Fetch market data + Log.i(TAG, "About to call MarketAPI.fetchMarketData") + val marketData = withContext(Dispatchers.IO) { + MarketAPI.fetchMarketData(applicationContext, currency) + } + Log.i(TAG, "Received market data from API: $marketData with nextBlock=${marketData.nextBlock}") + + storeMarketData(marketData) + Log.i(TAG, "Stored market data including nextBlock=${marketData.nextBlock}") + + for (widgetId in widgetIds) { + MarketWidget.updateWidget(applicationContext, widgetId) + } + + if (marketData.rate > 0) { + clearRateLimitFlag() + scheduleNextMarketUpdate(TimeUnit.MINUTES.toMillis(30)) + return Result.success() + } else { + Log.w(TAG, "Market data fetch returned invalid rate (${marketData.rate}), but fee may be available") + scheduleNextMarketUpdate(TimeUnit.MINUTES.toMillis(15)) + return Result.retry() + } + } catch (e: RateLimitException) { + Log.e(TAG, "Rate limit encountered", e) + setRateLimitFlag() + scheduleNextMarketUpdate(RATE_LIMIT_COOLDOWN_MS) + return Result.failure() + } catch (e: Exception) { + Log.e(TAG, "Error updating market widget", e) + scheduleNextMarketUpdate(TimeUnit.MINUTES.toMillis(15)) + return Result.retry() + } + } + + /** + * Store market data in shared preferences + */ + private fun storeMarketData(marketData: MarketData) { + try { + val json = JSONObject().apply { + put("nextBlock", marketData.nextBlock) + put("sats", marketData.sats) + put("price", marketData.price) + put("rate", marketData.rate) + put("dateString", marketData.dateString) + } + + val jsonString = json.toString() + Log.d(TAG, "Storing market data JSON: $jsonString") + + applicationContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .edit() + .putString(MarketData.PREF_KEY, jsonString) + .apply() + + Log.d(TAG, "Stored market data: $marketData") + } catch (e: Exception) { + Log.e(TAG, "Error storing market data", e) + } + } + + private fun scheduleNextMarketUpdate(delayMs: Long) { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + val updateRequest = OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .setInitialDelay(delayMs, TimeUnit.MILLISECONDS) + .build() + + WorkManager.getInstance(applicationContext).enqueueUniqueWork( + WORK_NAME, + ExistingWorkPolicy.REPLACE, + updateRequest + ) + + Log.d(TAG, "Scheduled next market update with delay: ${delayMs}ms") + } + + /** + * Get user's preferred currency + */ + private fun getPreferredCurrency(context: Context): String { + val sharedPrefs = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + return sharedPrefs.getString("preferredCurrency", DEFAULT_CURRENCY) ?: DEFAULT_CURRENCY + } + + /** + * Mark last update time + */ + private fun markUpdateTime() { + applicationContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .edit() + .putLong(KEY_LAST_UPDATE_TIME, System.currentTimeMillis()) + .apply() + } + + /** + * Set rate limit flag when API rate limit encountered + */ + private fun setRateLimitFlag() { + applicationContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .edit() + .putLong("market_widget_rate_limited_time", System.currentTimeMillis()) + .apply() + } + + /** + * Clear rate limit flag + */ + private fun clearRateLimitFlag() { + applicationContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .edit() + .remove("market_widget_rate_limited_time") + .apply() + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/NetworkUtils.kt b/android/app/src/main/java/io/bluewallet/bluewallet/NetworkUtils.kt new file mode 100644 index 00000000000..45d1252f755 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/NetworkUtils.kt @@ -0,0 +1,32 @@ +package io.bluewallet.bluewallet + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Build + +object NetworkUtils { + /** + * Check if the device has an active network connection + * @param context Application context + * @return true if connected, false otherwise + */ + fun isNetworkAvailable(context: Context): Boolean { + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val network = connectivityManager.activeNetwork ?: return false + val activeNetwork = connectivityManager.getNetworkCapabilities(network) ?: return false + + when { + activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true + activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true + activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true + else -> false + } + } else { + @Suppress("DEPRECATION") + connectivityManager.activeNetworkInfo?.isConnected ?: false + } + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/RateLimitException.kt b/android/app/src/main/java/io/bluewallet/bluewallet/RateLimitException.kt new file mode 100644 index 00000000000..81d59344b45 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/RateLimitException.kt @@ -0,0 +1,6 @@ +package io.bluewallet.bluewallet + +/** + * Exception thrown when an API rate limit is encountered + */ +class RateLimitException(message: String) : Exception(message) diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/SettingsActivity.kt b/android/app/src/main/java/io/bluewallet/bluewallet/SettingsActivity.kt new file mode 100644 index 00000000000..5eb40ce9ff1 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/SettingsActivity.kt @@ -0,0 +1,86 @@ +package io.bluewallet.bluewallet + +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import androidx.preference.PreferenceFragmentCompat + +/** + * Settings Activity accessible from Android System Settings + */ +class SettingsActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.settings_activity) + + Log.d("SettingsActivity", "Settings activity created") + + // Enable back button in action bar + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + if (savedInstanceState == null) { + supportFragmentManager + .beginTransaction() + .replace(R.id.settings_container, SettingsFragment()) + .commit() + } + } + + override fun onSupportNavigateUp(): Boolean { + finish() + return true + } + + class SettingsFragment : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + // Set the SharedPreferences name to match the app's preferences + preferenceManager.sharedPreferencesName = "group.io.bluewallet.bluewallet" + + // Load preferences from XML + setPreferencesFromResource(R.xml.settings_preferences, rootKey) + + Log.d("SettingsFragment", "Preferences loaded from XML") + + // Set up click listener for deviceUIDCopy to copy to clipboard + val deviceUIDPref = findPreference("deviceUIDCopy") + deviceUIDPref?.let { pref -> + // Get the device UID from SharedPreferences + val sharedPref = preferenceManager.sharedPreferences + val deviceUID = sharedPref?.getString("deviceUIDCopy", "") ?: "" + + // Set the summary to show the UUID + pref.summary = deviceUID + + // Check if report issue is disabled + val isDisabled = deviceUID == "Disabled" + + // Make it non-selectable if disabled + pref.isSelectable = !isDisabled + + // Set click listener to copy to clipboard (only if not disabled) + if (!isDisabled) { + pref.setOnPreferenceClickListener { + if (deviceUID.isNotEmpty()) { + val clipboard = requireContext().getSystemService(android.content.Context.CLIPBOARD_SERVICE) + as android.content.ClipboardManager + val clip = android.content.ClipData.newPlainText("Device UID", deviceUID) + clipboard.setPrimaryClip(clip) + + // Show a toast message + android.widget.Toast.makeText( + requireContext(), + R.string.copied_to_clipboard, + android.widget.Toast.LENGTH_SHORT + ).show() + + Log.d("SettingsFragment", "Device UID copied to clipboard: $deviceUID") + } + true + } + } + } + } + } +} + diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/SettingsModule.kt b/android/app/src/main/java/io/bluewallet/bluewallet/SettingsModule.kt new file mode 100644 index 00000000000..4c32ef6e06b --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/SettingsModule.kt @@ -0,0 +1,211 @@ +package io.bluewallet.bluewallet + +import android.content.Context +import android.content.SharedPreferences +import android.util.Log +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.Promise +import com.facebook.react.module.annotations.ReactModule +import io.bluewallet.bluewallet.NativeSettingsModuleSpec + +@ReactModule(name = SettingsModule.NAME) +class SettingsModule(reactContext: ReactApplicationContext) : NativeSettingsModuleSpec(reactContext) { + + private val sharedPref: SharedPreferences = reactContext.getSharedPreferences( + "group.io.bluewallet.bluewallet", + Context.MODE_PRIVATE + ) + + companion object { + private const val TAG = "SettingsModule" + private const val DEVICE_UID_KEY = "deviceUID" + private const val DEVICE_UID_COPY_KEY = "deviceUIDCopy" + private const val CLEAR_FILES_ON_LAUNCH_KEY = "clearFilesOnLaunch" + private const val DO_NOT_TRACK_KEY = "donottrack" + const val NAME = "SettingsModule" + } + + /** + * Initialize device UID if not exists + * Uses the same Android ID as react-native-device-info's getUniqueId() + */ + @ReactMethod + override fun initializeDeviceUID(promise: Promise) { + try { + val isDoNotTrackEnabled = sharedPref.getString(DO_NOT_TRACK_KEY, "0") == "1" + + if (isDoNotTrackEnabled) { + // Set deviceUIDCopy to "Disabled" if Do Not Track is enabled + val currentCopy = sharedPref.getString(DEVICE_UID_COPY_KEY, "") + if (currentCopy != "Disabled") { + sharedPref.edit() + .putString(DEVICE_UID_COPY_KEY, "Disabled") + .apply() + Log.d(TAG, "Do Not Track enabled - set deviceUIDCopy to 'Disabled'") + } + promise.resolve("Disabled") + return + } + + // Get the Android ID (same as react-native-device-info's getUniqueId()) + val deviceUID = try { + android.provider.Settings.Secure.getString( + reactApplicationContext.contentResolver, + android.provider.Settings.Secure.ANDROID_ID + ) ?: "unknown" + } catch (e: Exception) { + Log.e(TAG, "Error getting Android ID", e) + "unknown" + } + + // Store in deviceUID for consistency + sharedPref.edit() + .putString(DEVICE_UID_KEY, deviceUID) + .apply() + + // Copy deviceUID to deviceUIDCopy (for Settings.bundle compatibility) + val currentCopy = sharedPref.getString(DEVICE_UID_COPY_KEY, "") + if (deviceUID != currentCopy) { + sharedPref.edit() + .putString(DEVICE_UID_COPY_KEY, deviceUID) + .apply() + Log.d(TAG, "Synced deviceUID to deviceUIDCopy: $deviceUID") + } + + promise.resolve(deviceUID) + } catch (e: Exception) { + Log.e(TAG, "Error initializing deviceUID", e) + promise.reject("ERROR", e.message) + } + } + + /** + * Get the device UID + */ + @ReactMethod + override fun getDeviceUID(promise: Promise) { + try { + val isDoNotTrackEnabled = sharedPref.getString(DO_NOT_TRACK_KEY, "0") == "1" + + if (isDoNotTrackEnabled) { + promise.resolve("Disabled") + return + } + + val deviceUID = sharedPref.getString(DEVICE_UID_KEY, null) + promise.resolve(deviceUID) + } catch (e: Exception) { + Log.e(TAG, "Error getting deviceUID", e) + promise.reject("ERROR", e.message) + } + } + + /** + * Get the device UID copy (for Settings display) + */ + @ReactMethod + override fun getDeviceUIDCopy(promise: Promise) { + try { + val deviceUIDCopy = sharedPref.getString(DEVICE_UID_COPY_KEY, "") + promise.resolve(deviceUIDCopy) + } catch (e: Exception) { + Log.e(TAG, "Error getting deviceUIDCopy", e) + promise.reject("ERROR", e.message) + } + } + + /** + * Set the clearFilesOnLaunch preference + */ + @ReactMethod + override fun setClearFilesOnLaunch(value: Boolean, promise: Promise) { + try { + sharedPref.edit() + .putBoolean(CLEAR_FILES_ON_LAUNCH_KEY, value) + .apply() + Log.d(TAG, "Set clearFilesOnLaunch to: $value") + promise.resolve(value) + } catch (e: Exception) { + Log.e(TAG, "Error setting clearFilesOnLaunch", e) + promise.reject("ERROR", e.message) + } + } + + /** + * Get the clearFilesOnLaunch preference + */ + @ReactMethod + override fun getClearFilesOnLaunch(promise: Promise) { + try { + val value = sharedPref.getBoolean(CLEAR_FILES_ON_LAUNCH_KEY, false) + promise.resolve(value) + } catch (e: Exception) { + Log.e(TAG, "Error getting clearFilesOnLaunch", e) + promise.reject("ERROR", e.message) + } + } + + /** + * Set Do Not Track setting + */ + @ReactMethod + override fun setDoNotTrack(enabled: Boolean, promise: Promise) { + try { + val value = if (enabled) "1" else "0" + sharedPref.edit() + .putString(DO_NOT_TRACK_KEY, value) + .apply() + + Log.d(TAG, "Set donottrack to: $value") + + // Update deviceUIDCopy based on Do Not Track setting + if (enabled) { + sharedPref.edit() + .putString(DEVICE_UID_COPY_KEY, "Disabled") + .apply() + Log.d(TAG, "Do Not Track enabled - set deviceUIDCopy to 'Disabled'") + } else { + // Re-initialize device UID + initializeDeviceUID(promise) + return + } + + promise.resolve(enabled) + } catch (e: Exception) { + Log.e(TAG, "Error setting donottrack", e) + promise.reject("ERROR", e.message) + } + } + + /** + * Get Do Not Track setting + */ + @ReactMethod + override fun getDoNotTrack(promise: Promise) { + try { + val value = sharedPref.getString(DO_NOT_TRACK_KEY, "0") + val enabled = value == "1" + promise.resolve(enabled) + } catch (e: Exception) { + Log.e(TAG, "Error getting donottrack", e) + promise.reject("ERROR", e.message) + } + } + + /** + * Open the settings activity from JavaScript + */ + @ReactMethod + override fun openSettings(promise: Promise) { + try { + val intent = android.content.Intent(reactApplicationContext, SettingsActivity::class.java) + intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK) + reactApplicationContext.startActivity(intent) + promise.resolve(true) + } catch (e: Exception) { + Log.e(TAG, "Error opening settings", e) + promise.reject("ERROR", e.message) + } + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/SettingsPackage.kt b/android/app/src/main/java/io/bluewallet/bluewallet/SettingsPackage.kt new file mode 100644 index 00000000000..deed9545232 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/SettingsPackage.kt @@ -0,0 +1,29 @@ +package io.bluewallet.bluewallet + +import com.facebook.react.TurboReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfo +import com.facebook.react.module.model.ReactModuleInfoProvider +import com.facebook.react.uimanager.ViewManager + +class SettingsPackage : TurboReactPackage() { + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { + return if (name == SettingsModule.NAME) SettingsModule(reactContext) else null + } + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { + val moduleInfo = ReactModuleInfo( + SettingsModule.NAME, + SettingsModule.NAME, + false, // canOverrideExistingModule + false, // needsEagerInit + false, // hasConstants + false, // isCxxModule + true // isTurboModule + ) + mapOf(SettingsModule.NAME to moduleInfo) + } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> = emptyList() +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/ThemeChangeReceiver.kt b/android/app/src/main/java/io/bluewallet/bluewallet/ThemeChangeReceiver.kt new file mode 100644 index 00000000000..681f178a719 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/ThemeChangeReceiver.kt @@ -0,0 +1,27 @@ +package io.bluewallet.bluewallet + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.res.Configuration +import android.util.Log + +/** + * BroadcastReceiver to handle system theme changes (light/dark mode) + */ +class ThemeChangeReceiver : BroadcastReceiver() { + companion object { + private const val TAG = "ThemeChangeReceiver" + } + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_CONFIGURATION_CHANGED) { + val currentNightMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + + if (!ThemeHelper.isForceDarkModeEnabled(context)) { + Log.d(TAG, "Configuration changed, updating widgets for theme change") + AppWidgetUtils.updateWidgetsForThemeChange(context) + } + } + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/ThemeHelper.kt b/android/app/src/main/java/io/bluewallet/bluewallet/ThemeHelper.kt new file mode 100644 index 00000000000..2872bb0215d --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/ThemeHelper.kt @@ -0,0 +1,76 @@ +package io.bluewallet.bluewallet + +import android.content.Context +import android.content.res.Configuration +import androidx.appcompat.app.AppCompatDelegate + +object ThemeHelper { + private const val SHARED_PREF_NAME = "group.io.bluewallet.bluewallet" + private const val KEY_FORCE_DARK_MODE = "force_dark_mode" + + /** + * Check if dark mode is currently active + * @param context Application context + * @return true if dark mode is active, false otherwise + */ + fun isDarkModeActive(context: Context): Boolean { + val preferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + val forceDarkMode = preferences.getBoolean(KEY_FORCE_DARK_MODE, false) + + return if (forceDarkMode) { + true + } else { + val currentNightMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + currentNightMode == Configuration.UI_MODE_NIGHT_YES + } + } + + /** + * Set the force dark mode option + * @param context Application context + * @param forceDarkMode Whether to force dark mode + */ + fun setForceDarkMode(context: Context, forceDarkMode: Boolean) { + context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .edit() + .putBoolean(KEY_FORCE_DARK_MODE, forceDarkMode) + .apply() + + // Apply theme setting immediately + AppCompatDelegate.setDefaultNightMode( + if (forceDarkMode) AppCompatDelegate.MODE_NIGHT_YES + else AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + ) + + // Update widgets with new theme + updateAllWidgets(context) + } + + /** + * Get whether force dark mode is enabled + * @param context Application context + * @return true if force dark mode is enabled, false otherwise + */ + fun isForceDarkModeEnabled(context: Context): Boolean { + return context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .getBoolean(KEY_FORCE_DARK_MODE, false) + } + + /** + * Update all widgets to reflect current theme + * @param context Application context + */ + fun updateAllWidgets(context: Context) { + // Update Bitcoin Price Widgets + val bitcoinPriceWidgetIds = AppWidgetUtils.getBitcoinPriceWidgetIds(context) + if (bitcoinPriceWidgetIds.isNotEmpty()) { + BitcoinPriceWidget.updateNetworkStatus(context, bitcoinPriceWidgetIds) + } + + // Update Market Widgets + val marketWidgetIds = MarketWidget.getAllWidgetIds(context) + if (marketWidgetIds.isNotEmpty()) { + MarketWidget.refreshAllWidgetsImmediately(context) + } + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.kt b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.kt new file mode 100644 index 00000000000..14e5e2f8f3d --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.kt @@ -0,0 +1,294 @@ +package io.bluewallet.bluewallet + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.util.Log +import android.view.View +import android.widget.RemoteViews +import androidx.work.* +import java.text.DecimalFormatSymbols +import java.text.NumberFormat +import java.text.SimpleDateFormat +import java.util.* +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject + +class WidgetUpdateWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { + + companion object { + const val TAG = "WidgetUpdateWorker" + const val WORK_NAME = "bitcoin_price_widget_update_work" + const val NETWORK_RETRY_WORK_NAME = "bitcoin_price_network_retry_work" + const val REPEAT_INTERVAL_MINUTES = 15L + private const val SHARED_PREF_NAME = "group.io.bluewallet.bluewallet" + private const val DEFAULT_CURRENCY = "USD" + private const val NETWORK_RETRY_DELAY_SECONDS = 30L + + fun scheduleWork(context: Context) { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .setRequiresBatteryNotLow(false) + .build() + + val workRequest = PeriodicWorkRequestBuilder( + REPEAT_INTERVAL_MINUTES, TimeUnit.MINUTES + ) + .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, WorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) + .addTag(TAG) + .build() + + WorkManager.getInstance(context).enqueueUniquePeriodicWork( + WORK_NAME, + ExistingPeriodicWorkPolicy.KEEP, + workRequest + ) + } + + fun scheduleImmediateUpdate(context: Context) { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + val updateRequest = OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, WorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) + .addTag(TAG) + .build() + + WorkManager.getInstance(context).enqueue(updateRequest) + } + + fun scheduleRetryOnNetworkAvailable(context: Context) { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + val updateRequest = OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .setInitialDelay(NETWORK_RETRY_DELAY_SECONDS, TimeUnit.SECONDS) + .build() + + WorkManager.getInstance(context).enqueueUniqueWork( + NETWORK_RETRY_WORK_NAME, + ExistingWorkPolicy.REPLACE, + updateRequest + ) + } + } + + private lateinit var sharedPref: SharedPreferences + + override suspend fun doWork(): Result { + sharedPref = applicationContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + + if (!NetworkUtils.isNetworkAvailable(applicationContext)) { + val component = ComponentName(applicationContext, BitcoinPriceWidget::class.java) + val widgetIds = AppWidgetManager.getInstance(applicationContext).getAppWidgetIds(component) + BitcoinPriceWidget.updateNetworkStatus(applicationContext, widgetIds) + scheduleRetryOnNetworkAvailable(applicationContext) + return Result.retry() + } + + return updatePriceWidgets() + } + + private suspend fun updatePriceWidgets(): Result { + val appWidgetManager = AppWidgetManager.getInstance(applicationContext) + val thisWidget = ComponentName(applicationContext, BitcoinPriceWidget::class.java) + val appWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget) + val views = RemoteViews(applicationContext.packageName, R.layout.widget_layout) + + val intent = Intent(applicationContext, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + action = "android.intent.action.MAIN" + addCategory("android.intent.category.LAUNCHER") + } + val pendingIntent = PendingIntent.getActivity( + applicationContext, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + views.setOnClickPendingIntent(R.id.widget_layout, pendingIntent) + + views.setViewVisibility(R.id.loading_indicator, View.VISIBLE) + views.setViewVisibility(R.id.price_value, View.GONE) + views.setViewVisibility(R.id.last_updated_label, View.GONE) + views.setViewVisibility(R.id.last_updated_time, View.GONE) + views.setViewVisibility(R.id.price_arrow_container, View.GONE) + + appWidgetManager.updateAppWidget(appWidgetIds, views) + + val preferredCurrency = sharedPref.getString("preferredCurrency", null) ?: "USD" + val preferredCurrencyLocale = sharedPref.getString("preferredCurrencyLocale", null) ?: "en-US" + val previousPrice = sharedPref.getString("previous_price", null) + + val currentTime = SimpleDateFormat("hh:mm a", Locale.getDefault()).format(Date()) + + val fetchedPrice = fetchPrice(preferredCurrency) + + // Check network connectivity + val isNetworkAvailable = NetworkUtils.isNetworkAvailable(applicationContext) + views.setViewVisibility(R.id.network_status, if (isNetworkAvailable) View.GONE else View.VISIBLE) + + handlePriceResult( + appWidgetManager, appWidgetIds, views, sharedPref, + fetchedPrice, previousPrice, currentTime, preferredCurrency, preferredCurrencyLocale + ) + + return Result.success() + } + + private suspend fun fetchPrice(currency: String?): String? { + return withContext(Dispatchers.IO) { + MarketAPI.fetchPrice(applicationContext, currency ?: "USD") + } + } + + private fun handlePriceResult( + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray, + views: RemoteViews, + sharedPref: SharedPreferences, + fetchedPrice: String?, + previousPrice: String?, + currentTime: String, + preferredCurrency: String?, + preferredCurrencyLocale: String? + ) { + val isPriceFetched = fetchedPrice != null + val isPriceCached = previousPrice != null + + if (!isPriceFetched) { + Log.e(TAG, "Error fetching price.") + if (!isPriceCached) { + showLoadingError(views) + } else { + displayCachedPrice(views, previousPrice, currentTime, preferredCurrency, preferredCurrencyLocale) + } + } else { + if (fetchedPrice != null) { + displayFetchedPrice( + views, fetchedPrice, previousPrice, currentTime, preferredCurrency, preferredCurrencyLocale + ) + } + if (fetchedPrice != null) { + savePrice(sharedPref, fetchedPrice) + } + } + + appWidgetManager.updateAppWidget(appWidgetIds, views) + } + + private fun showLoadingError(views: RemoteViews) { + views.apply { + setViewVisibility(R.id.loading_indicator, View.GONE) + setViewVisibility(R.id.price_value, View.GONE) + setViewVisibility(R.id.last_updated_label, View.GONE) + setViewVisibility(R.id.last_updated_time, View.GONE) + setViewVisibility(R.id.price_arrow_container, View.GONE) + } + } + + private fun displayCachedPrice( + views: RemoteViews, + previousPrice: String?, + currentTime: String, + preferredCurrency: String?, + preferredCurrencyLocale: String? + ) { + val parsedPrevious = previousPrice?.toDoubleOrNull() + val currencyFormat = getCurrencyFormat(preferredCurrency, preferredCurrencyLocale) + + views.apply { + setViewVisibility(R.id.loading_indicator, View.GONE) + setViewVisibility(R.id.price_arrow_container, View.GONE) + if (parsedPrevious != null) { + setTextViewText(R.id.price_value, currencyFormat.format(parsedPrevious.toInt())) + setViewVisibility(R.id.price_value, View.VISIBLE) + setViewVisibility(R.id.last_updated_label, View.VISIBLE) + setViewVisibility(R.id.last_updated_time, View.VISIBLE) + } else { + setViewVisibility(R.id.price_value, View.GONE) + setViewVisibility(R.id.last_updated_label, View.GONE) + setViewVisibility(R.id.last_updated_time, View.GONE) + } + setTextViewText(R.id.last_updated_time, currentTime) + } + } + + private fun displayFetchedPrice( + views: RemoteViews, + fetchedPrice: String, + previousPrice: String?, + currentTime: String, + preferredCurrency: String?, + preferredCurrencyLocale: String? + ) { + val currentPrice = fetchedPrice.toDoubleOrNull()?.toInt() + val currencyFormat = getCurrencyFormat(preferredCurrency, preferredCurrencyLocale) + + views.apply { + setViewVisibility(R.id.loading_indicator, View.GONE) + if (currentPrice != null) { + setTextViewText(R.id.price_value, currencyFormat.format(currentPrice)) + setTextViewText(R.id.last_updated_time, currentTime) + setViewVisibility(R.id.price_value, View.VISIBLE) + setViewVisibility(R.id.last_updated_label, View.VISIBLE) + setViewVisibility(R.id.last_updated_time, View.VISIBLE) + + val previousParsed = previousPrice?.toDoubleOrNull()?.toInt() + if (previousParsed != null) { + setViewVisibility(R.id.price_arrow_container, View.VISIBLE) + setTextViewText(R.id.previous_price, currencyFormat.format(previousParsed)) + setImageViewResource( + R.id.price_arrow, + if (currentPrice > previousParsed) android.R.drawable.arrow_up_float else android.R.drawable.arrow_down_float + ) + } else { + setViewVisibility(R.id.price_arrow_container, View.GONE) + } + } else { + // Fallback to loading state if parsing failed + setViewVisibility(R.id.price_value, View.GONE) + setViewVisibility(R.id.last_updated_label, View.GONE) + setViewVisibility(R.id.last_updated_time, View.GONE) + setViewVisibility(R.id.price_arrow_container, View.GONE) + setViewVisibility(R.id.loading_indicator, View.VISIBLE) + } + } + } + + private fun getCurrencyFormat(currencyCode: String?, localeString: String?): NumberFormat { + val locale = localeString + ?.let { runCatching { Locale.forLanguageTag(it) }.getOrNull() } + ?.takeIf { it.language.isNotBlank() } + ?: Locale.getDefault() + val currencyFormat = NumberFormat.getCurrencyInstance(locale) + val currency = try { + Currency.getInstance(currencyCode ?: "USD") + } catch (e: IllegalArgumentException) { + Currency.getInstance("USD") + } + currencyFormat.currency = currency + currencyFormat.maximumFractionDigits = 0 + + val decimalFormatSymbols = (currencyFormat as java.text.DecimalFormat).decimalFormatSymbols + decimalFormatSymbols.currencySymbol = currency.symbol + currencyFormat.decimalFormatSymbols = decimalFormatSymbols + + return currencyFormat + } + + private fun savePrice(sharedPref: SharedPreferences, price: String) { + sharedPref.edit().putString("previous_price", price).apply() + } +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable-mdpi/splash_icon.png b/android/app/src/main/res/drawable-mdpi/splash_icon.png new file mode 100644 index 00000000000..ec541528a9a Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/splash_icon.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/splash_icon.png b/android/app/src/main/res/drawable-xhdpi/splash_icon.png new file mode 100644 index 00000000000..a72c80addc6 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/splash_icon.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/splash_icon.png b/android/app/src/main/res/drawable-xxhdpi/splash_icon.png new file mode 100644 index 00000000000..5727bf33172 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/splash_icon.png differ diff --git a/android/app/src/main/res/drawable/green_pill_background.xml b/android/app/src/main/res/drawable/green_pill_background.xml new file mode 100644 index 00000000000..063ce8ad6a9 --- /dev/null +++ b/android/app/src/main/res/drawable/green_pill_background.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/market_widget_preview.png b/android/app/src/main/res/drawable/market_widget_preview.png new file mode 100644 index 00000000000..9ea3b22d19d Binary files /dev/null and b/android/app/src/main/res/drawable/market_widget_preview.png differ diff --git a/android/app/src/main/res/drawable/notification_icon.png b/android/app/src/main/res/drawable/notification_icon.png new file mode 100644 index 00000000000..02787243b63 Binary files /dev/null and b/android/app/src/main/res/drawable/notification_icon.png differ diff --git a/android/app/src/main/res/drawable/pill_shape_green.xml b/android/app/src/main/res/drawable/pill_shape_green.xml new file mode 100644 index 00000000000..842358e8aed --- /dev/null +++ b/android/app/src/main/res/drawable/pill_shape_green.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/android/app/src/main/res/drawable/pill_shape_red.xml b/android/app/src/main/res/drawable/pill_shape_red.xml new file mode 100644 index 00000000000..b6fac1db1bd --- /dev/null +++ b/android/app/src/main/res/drawable/pill_shape_red.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/android/app/src/main/res/drawable/red_pill_background.xml b/android/app/src/main/res/drawable/red_pill_background.xml new file mode 100644 index 00000000000..590814d5e5e --- /dev/null +++ b/android/app/src/main/res/drawable/red_pill_background.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/rn_edit_text_material.xml b/android/app/src/main/res/drawable/rn_edit_text_material.xml index f35d9962026..5c0239d0ad8 100644 --- a/android/app/src/main/res/drawable/rn_edit_text_material.xml +++ b/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -17,7 +17,8 @@ android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material" android:insetRight="@dimen/abc_edit_text_inset_horizontal_material" android:insetTop="@dimen/abc_edit_text_inset_top_material" - android:insetBottom="@dimen/abc_edit_text_inset_bottom_material"> + android:insetBottom="@dimen/abc_edit_text_inset_bottom_material" + > + Bitcoin Market + View the latest Bitcoin market data including price, satoshi rate, and next block fee. + Market + Next Block + Sats/%s + Price + + + Bitcoin Price + View the latest Bitcoin price in your preferred currency + Offline + From: + Price + + %1$s + %1$s + + + Settings + Report Issue + Provide this Unique ID when reporting an issue + Unique ID + Cache + Clear Cache on Next Launch + Enable to clear all cached files when the app starts next time + Cache Cleared + The document, cache, and temp directories have been cleared. + Copied to clipboard diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 7ba83a2ad5a..aa89f8253bd 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,9 +1,20 @@ - - - - + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/xml/bitcoin_price_widget_info.xml b/android/app/src/main/res/xml/bitcoin_price_widget_info.xml new file mode 100644 index 00000000000..8d61bfee55f --- /dev/null +++ b/android/app/src/main/res/xml/bitcoin_price_widget_info.xml @@ -0,0 +1,18 @@ + + diff --git a/android/app/src/main/res/xml/file_paths.xml b/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 00000000000..ecb9eeccd2c --- /dev/null +++ b/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/android/app/src/main/res/xml/market_widget_info.xml b/android/app/src/main/res/xml/market_widget_info.xml new file mode 100644 index 00000000000..6657b69db53 --- /dev/null +++ b/android/app/src/main/res/xml/market_widget_info.xml @@ -0,0 +1,18 @@ + + diff --git a/android/app/src/main/res/xml/settings_preferences.xml b/android/app/src/main/res/xml/settings_preferences.xml new file mode 100644 index 00000000000..821424f4899 --- /dev/null +++ b/android/app/src/main/res/xml/settings_preferences.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android/build.gradle b/android/build.gradle index ecd03e872d7..52cb34c477e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,33 +2,53 @@ buildscript { ext { - minSdkVersion = 28 - supportLibVersion = "28.0.0" - buildToolsVersion = "33.0.0" - compileSdkVersion = 33 - targetSdkVersion = 33 + minSdkVersion = 24 + buildToolsVersion = "36.0.0" + compileSdkVersion = 36 + targetSdkVersion = 36 googlePlayServicesVersion = "16.+" googlePlayServicesIidVersion = "16.0.1" - firebaseVersion = "17.3.4" - firebaseMessagingVersion = "21.1.0" - - // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. - ndkVersion = "23.1.7779620" - kotlin_version = '1.9.0' - kotlinVersion = '1.8.0' - + firebaseVersion = "21.1.0" + ndkVersion = "28.2.13676358" + kotlinVersion = '2.1.20' } repositories { google() mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:7.4.2") - classpath("com.bugsnag:bugsnag-android-gradle-plugin:5.+") - classpath 'com.google.gms:google-services:4.3.14' // Google Services plugin - classpath("com.facebook.react:react-native-gradle-plugin") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") + classpath("com.android.tools.build:gradle") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") + classpath 'com.google.gms:google-services:4.4.4' // Google Services plugin + classpath("com.bugsnag:bugsnag-android-gradle-plugin:8.2.0") + } +} +// Gradle 9 removes jcenter(); add a shim that redirects any jcenter() call to mavenCentral() +def addJcenterShim = { repoContainer, logger -> + def mc = repoContainer.metaClass + if (!mc.respondsTo(repoContainer, 'jcenter')) { + mc.jcenter << { Closure config = null -> + def repo = repoContainer.mavenCentral() + if (config != null) { + config.delegate = repo + config.resolveStrategy = Closure.DELEGATE_FIRST + config.call(repo) + } + logger.lifecycle("Redirected jcenter() to mavenCentral()") + return repo + } + } + if (!mc.respondsTo(repoContainer, 'methodMissing')) { + mc.methodMissing = { String name, args -> + if (name == 'jcenter') { + def repo = repoContainer.mavenCentral() + logger.lifecycle("Redirected jcenter() (methodMissing) to mavenCentral()") + return repo + } + throw new MissingMethodException(name, repoContainer.class, args) + } } } @@ -37,23 +57,7 @@ allprojects { maven { url("$rootDir/../node_modules/detox/Detox-android") } - jcenter() { - content { - includeModule("com.facebook.yoga", "proguard-annotations") - includeModule("com.facebook.fbjni", "fbjni-java-only") - includeModule("com.facebook.fresco", "fresco") - includeModule("com.facebook.fresco", "stetho") - includeModule("com.facebook.fresco", "fbcore") - includeModule("com.facebook.fresco", "drawee") - includeModule("com.facebook.fresco", "imagepipeline") - includeModule("com.facebook.fresco", "imagepipeline-native") - includeModule("com.facebook.fresco", "memory-type-native") - includeModule("com.facebook.fresco", "memory-type-java") - includeModule("com.facebook.fresco", "nativeimagefilters") - includeModule("com.facebook.stetho", "stetho") - includeModule("com.wei.android.lib", "fingerprintidentify") - } - } + mavenCentral { // We don't want to fetch react-native from Maven Central as there are // older versions over there. @@ -61,36 +65,89 @@ allprojects { excludeGroup "com.facebook.react" } } - mavenLocal() - maven { - // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - url("$rootDir/../node_modules/react-native/android") - } - maven { - // Android JSC is installed from npm - url("$rootDir/../node_modules/jsc-android/dist") - } google() maven { url 'https://www.jitpack.io' } } } -subprojects { - afterEvaluate {project -> - if (project.hasProperty("android")) { +// Apply jcenter shim very early for every project before its build.gradle is evaluated +gradle.beforeProject { project -> + addJcenterShim(project.repositories, project.logger) + if (project.buildscript != null) { + addJcenterShim(project.buildscript.repositories, project.logger) + } +} + +// Apply to root as well +addJcenterShim(repositories, logger) +if (buildscript != null) { + addJcenterShim(buildscript.repositories, logger) +} + +subprojects { project -> + // Remove and block any jcenter() repositories at both project and buildscript levels + def scrub = { repoContainer -> + repoContainer.all { repo -> + if (repo instanceof org.gradle.api.artifacts.repositories.MavenArtifactRepository && + repo.url?.toString()?.contains('jcenter')) { + project.logger.lifecycle("Removing jcenter() from ${project.path}") + repoContainer.remove(repo) + } + } + repoContainer.whenObjectAdded { repo -> + if (repo instanceof org.gradle.api.artifacts.repositories.MavenArtifactRepository && + repo.url?.toString()?.contains('jcenter')) { + project.logger.lifecycle("Blocking jcenter() from ${project.path}") + repoContainer.remove(repo) + repoContainer.mavenCentral() + } + } + } + + scrub(project.repositories) + if (project.buildscript != null) { + scrub(project.buildscript.repositories) + } + + afterEvaluate {proj -> + if (proj.hasProperty("android")) { android { - buildToolsVersion '30.0.3' - compileSdkVersion 31 + buildToolsVersion "36.0.0" + compileSdkVersion 36 defaultConfig { - minSdkVersion 28 + minSdkVersion 24 } } } + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { + // FIXME: next line should be removed when https://github.com/wix/Detox/issues/4678 is fixed + kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlin.ExperimentalStdlibApi"] + if (proj.plugins.hasPlugin("com.android.application") || proj.plugins.hasPlugin("com.android.library")) { + kotlinOptions.jvmTarget = android.compileOptions.sourceCompatibility + } else { + kotlinOptions.jvmTarget = sourceCompatibility + } + } + + if (proj.name == "react-native-reanimated" && proj.hasProperty("android")) { + // Wire Reanimated's generated codegen sources so WorkletsModule spec is visible under new architecture + proj.android.sourceSets.main.java.srcDir("${proj.buildDir}/generated/source/codegen/java") + } + } } -subprojects { subproject -> - if(project['name'] == 'react-native-widget-center') { - project.configurations { compile { } } +// Final guard: fail fast if any jcenter repository slips through +gradle.projectsEvaluated { + allprojects { proj -> + def offenders = proj.repositories.findAll { repo -> + repo instanceof org.gradle.api.artifacts.repositories.MavenArtifactRepository && + repo.url?.toString()?.contains('jcenter') + } + if (!offenders.isEmpty()) { + throw new GradleException("jcenter() detected in ${proj.path}; remove or replace with mavenCentral()"); } + } } + +apply plugin: "com.facebook.react.rootproject" \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties index cc86a726d93..7b4ae66fc81 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -10,7 +10,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m -org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m +org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit @@ -21,9 +21,6 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true - # Use this property to specify which architecture you want to build. # You can also override it from the CLI using # ./gradlew -PreactNativeArchitectures=x86_64 @@ -34,8 +31,16 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 # your application. You should enable this flag either if you want # to write custom TurboModules/Fabric components OR use libraries that # are providing them. -newArchEnabled=false +newArchEnabled=true # Use this property to enable or disable the Hermes JS engine. # If set to false, you will be using JSC instead. -hermesEnabled=true \ No newline at end of file +hermesEnabled=true + +# Use this property to enable edge-to-edge display support. +# This allows your app to draw behind system bars for an immersive UI. +# Note: Only works with ReactActivity and should not be used with custom Activity. +edgeToEdgeEnabled=true + +# Use legacy NDK symbol upload for Bugsnag (avoids v5.26.0 requirement) +bugsnag.useLegacyNdkSymbolUpload=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b..61285a659d1 100644 Binary files a/android/gradle/wrapper/gradle-wrapper.jar and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 7cbb1e48ee1..37f78a6af83 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Tue Jul 21 23:04:55 CDT 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/android/gradlew b/android/gradlew index a58591e97bc..ad3611bb2e3 100755 --- a/android/gradlew +++ b/android/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +82,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -114,8 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -165,7 +170,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -193,18 +197,27 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. @@ -231,4 +244,4 @@ eval "set -- $( tr '\n' ' ' )" '"$@"' -exec "$JAVACMD" "$@" \ No newline at end of file +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat index e3ecc7d7777..90a3f3c50fc 100644 --- a/android/gradlew.bat +++ b/android/gradlew.bat @@ -5,7 +5,7 @@ @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem -@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,10 +27,14 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -53,45 +59,32 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/android/settings.gradle b/android/settings.gradle index f003f339a7b..2221f878143 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,6 +1,9 @@ +pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } +plugins { id("com.facebook.react.settings") } +extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } rootProject.name = 'BlueWallet' -apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' -includeBuild('../node_modules/react-native-gradle-plugin') +includeBuild('../node_modules/@react-native/gradle-plugin') include ':detox' -project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox') \ No newline at end of file +project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox') + diff --git a/appcenter-post-build.sh b/appcenter-post-build.sh deleted file mode 100755 index eeff0bf8a39..00000000000 --- a/appcenter-post-build.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -echo Uploading to Appetize and publishing link to Github... -echo "Branch " -BRANCH=`git ls-remote --heads origin | grep $(git rev-parse HEAD) | cut -d / -f 3` -echo $BRANCH -echo "Branch 2 " -git log -n 1 --pretty=%d HEAD | awk '{print $2}' | sed 's/origin\///' | sed 's/)//' - -FILENAME="$APPCENTER_OUTPUT_DIRECTORY/app-release.apk" - -if [ -f $FILENAME ]; then - APTZ=`curl "https://$APPETIZE@api.appetize.io/v1/apps" -F "file=@$FILENAME" -F "platform=android"` - echo Apptezize response: - echo $APTZ - APPURL=`node -e "let e = JSON.parse('$APTZ'); console.log(e.publicURL + '?device=pixel4');"` - echo App url: $APPURL - PR=`node scripts/appcenter-post-build-get-pr-number.js` - echo PR: $PR - - DLOAD_APK="https://lambda-download-android-build.herokuapp.com/download/$BUILD_BUILDID" - - curl -X POST --data "{\"body\":\"♫ This was a triumph. I'm making a note here: HUGE SUCCESS ♫\n\n [android in browser] $APPURL\n\n[download apk]($DLOAD_APK) \"}" -u "$GITHUB" "https://api.github.com/repos/BlueWallet/BlueWallet/issues/$PR/comments" -fi diff --git a/babel.config.js b/babel.config.js index fff45f944a1..8ba8eb658c4 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,4 +1,4 @@ module.exports = { - presets: ['module:metro-react-native-babel-preset'], - plugins: ['react-native-reanimated/plugin'], // required by react-native-reanimated v2 https://docs.swmansion.com/react-native-reanimated/docs/installation/ + presets: ['module:@react-native/babel-preset'], + plugins: ['react-native-worklets/plugin'], }; diff --git a/blue_modules/BlueElectrum.d.ts b/blue_modules/BlueElectrum.d.ts deleted file mode 100644 index a51dfaef162..00000000000 --- a/blue_modules/BlueElectrum.d.ts +++ /dev/null @@ -1,98 +0,0 @@ -type Utxo = { - height: number; - value: number; - address: string; - txId: string; - vout: number; - wif?: string; -}; - -export type ElectrumTransaction = { - txid: string; - hash: string; - version: number; - size: number; - vsize: number; - weight: number; - locktime: number; - vin: { - txid: string; - vout: number; - scriptSig: { asm: string; hex: string }; - txinwitness: string[]; - sequence: number; - addresses?: string[]; - value?: number; - }[]; - vout: { - value: number; - n: number; - scriptPubKey: { - asm: string; - hex: string; - reqSigs: number; - type: string; - addresses: string[]; - }; - }[]; - blockhash: string; - confirmations?: number; - time: number; - blocktime: number; -}; - -type MempoolTransaction = { - height: 0; - tx_hash: string; - fee: number; -}; - -export async function connectMain(): Promise; - -export async function waitTillConnected(): Promise; - -export function forceDisconnect(): void; - -export function getBalanceByAddress(address: string): Promise<{ confirmed: number; unconfirmed: number }>; - -export function multiGetUtxoByAddress(addresses: string[]): Promise>; - -// TODO: this function returns different results based on the value of `verbose`, consider splitting it into two -export function multiGetTransactionByTxid( - txIds: string[], - batchsize: number = 45, - verbose: true = true, -): Promise>; -export function multiGetTransactionByTxid(txIds: string[], batchsize: number, verbose: false): Promise>; - -export type MultiGetBalanceResponse = { - balance: number; - unconfirmed_balance: number; - addresses: Record; -}; - -export function multiGetBalanceByAddress(addresses: string[], batchsize?: number): Promise; - -export function getTransactionsByAddress(address: string): ElectrumTransaction[]; - -export function getMempoolTransactionsByAddress(address: string): Promise; - -export function estimateCurrentBlockheight(): number; - -export type ElectrumHistory = { - tx_hash: string; - height: number; - address: string; -}; - -export function multiGetHistoryByAddress(addresses: string[]): Promise>; - -export function estimateFees(): Promise<{ fast: number; medium: number; slow: number }>; - -export function broadcastV2(txhex: string): Promise; - -export function getTransactionsFullByAddress(address: string): Promise; - -export function txhexToElectrumTransaction(txhes: string): ElectrumTransaction; - -export function isDisabled(): Promise; diff --git a/blue_modules/BlueElectrum.js b/blue_modules/BlueElectrum.js deleted file mode 100644 index 79ede14583b..00000000000 --- a/blue_modules/BlueElectrum.js +++ /dev/null @@ -1,1092 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { Alert } from 'react-native'; -import { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet, TaprootWallet } from '../class'; -import DefaultPreference from 'react-native-default-preference'; -import loc from '../loc'; -import WidgetCommunication from './WidgetCommunication'; -import { isTorDaemonDisabled } from './environment'; -import alert from '../components/Alert'; -const bitcoin = require('bitcoinjs-lib'); -const ElectrumClient = require('electrum-client'); -const reverse = require('buffer-reverse'); -const BigNumber = require('bignumber.js'); -const torrific = require('./torrific'); -const Realm = require('realm'); - -const ELECTRUM_HOST = 'electrum_host'; -const ELECTRUM_TCP_PORT = 'electrum_tcp_port'; -const ELECTRUM_SSL_PORT = 'electrum_ssl_port'; -const ELECTRUM_SERVER_HISTORY = 'electrum_server_history'; -const ELECTRUM_CONNECTION_DISABLED = 'electrum_disabled'; - -let _realm; -async function _getRealm() { - if (_realm) return _realm; - - const password = bitcoin.crypto.sha256(Buffer.from('fyegjitkyf[eqjnc.lf')).toString('hex'); - const buf = Buffer.from(password + password, 'hex'); - const encryptionKey = Int8Array.from(buf); - const path = 'electrumcache.realm'; - - const schema = [ - { - name: 'Cache', - primaryKey: 'cache_key', - properties: { - cache_key: { type: 'string', indexed: true }, - cache_value: 'string', // stringified json - }, - }, - ]; - _realm = await Realm.open({ - schema, - path, - encryptionKey, - }); - return _realm; -} - -const storageKey = 'ELECTRUM_PEERS'; -const defaultPeer = { host: 'electrum1.bluewallet.io', ssl: '443' }; -const hardcodedPeers = [ - { host: 'mainnet.foundationdevices.com', ssl: '50002' }, - { host: 'bitcoin.lukechilds.co', ssl: '50002' }, - { host: 'electrum.jochen-hoenicke.de', ssl: '50006' }, - { host: 'electrum1.bluewallet.io', ssl: '443' }, - { host: 'electrum.acinq.co', ssl: '50002' }, - { host: 'electrum.bitaroo.net', ssl: '50002' }, -]; - -/** @type {ElectrumClient} */ -let mainClient; -let mainConnected = false; -let wasConnectedAtLeastOnce = false; -let serverName = false; -let disableBatching = false; -let connectionAttempt = 0; -let currentPeerIndex = Math.floor(Math.random() * hardcodedPeers.length); - -let latestBlockheight = false; -let latestBlockheightTimestamp = false; - -const txhashHeightCache = {}; - -async function isDisabled() { - let result; - try { - const savedValue = await AsyncStorage.getItem(ELECTRUM_CONNECTION_DISABLED); - if (savedValue === null) { - result = false; - } else { - result = savedValue; - } - } catch { - result = false; - } - return !!result; -} - -async function setDisabled(disabled = true) { - return AsyncStorage.setItem(ELECTRUM_CONNECTION_DISABLED, disabled ? '1' : ''); -} - -async function connectMain() { - if (await isDisabled()) { - console.log('Electrum connection disabled by user. Skipping connectMain call'); - return; - } - let usingPeer = await getNextPeer(); - const savedPeer = await getSavedPeer(); - if (savedPeer && savedPeer.host && (savedPeer.tcp || savedPeer.ssl)) { - usingPeer = savedPeer; - } - - await DefaultPreference.setName('group.io.bluewallet.bluewallet'); - try { - if (usingPeer.host.endsWith('onion')) { - const randomPeer = await getCurrentPeer(); - await DefaultPreference.set(ELECTRUM_HOST, randomPeer.host); - await DefaultPreference.set(ELECTRUM_TCP_PORT, randomPeer.tcp); - await DefaultPreference.set(ELECTRUM_SSL_PORT, randomPeer.ssl); - } else { - await DefaultPreference.set(ELECTRUM_HOST, usingPeer.host); - await DefaultPreference.set(ELECTRUM_TCP_PORT, usingPeer.tcp); - await DefaultPreference.set(ELECTRUM_SSL_PORT, usingPeer.ssl); - } - - WidgetCommunication.reloadAllTimelines(); - } catch (e) { - // Must be running on Android - console.log(e); - } - - try { - console.log('begin connection:', JSON.stringify(usingPeer)); - mainClient = new ElectrumClient( - usingPeer.host.endsWith('.onion') && !(await isTorDaemonDisabled()) ? torrific : global.net, - global.tls, - usingPeer.ssl || usingPeer.tcp, - usingPeer.host, - usingPeer.ssl ? 'tls' : 'tcp', - ); - - mainClient.onError = function (e) { - 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(); - mainConnected = false; - // dropping `mainConnected` flag ensures there wont be reconnection race condition if several - // errors triggered - console.log('reconnecting after socket error'); - setTimeout(connectMain, usingPeer.host.endsWith('.onion') ? 4000 : 500); - } - }; - const ver = await mainClient.initElectrum({ client: 'bluewallet', version: '1.4' }); - if (ver && ver[0]) { - console.log('connected to ', ver); - serverName = ver[0]; - mainConnected = true; - wasConnectedAtLeastOnce = true; - if (ver[0].startsWith('ElectrumPersonalServer') || ver[0].startsWith('electrs') || ver[0].startsWith('Fulcrum')) { - disableBatching = true; - - // exeptions for versions: - const [electrumImplementation, electrumVersion] = ver[0].split(' '); - switch (electrumImplementation) { - case 'electrs': - if (semVerToInt(electrumVersion) >= semVerToInt('0.9.0')) { - disableBatching = false; - } - 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')) { - disableBatching = false; - } - break; - } - } - const header = await mainClient.blockchainHeaders_subscribe(); - if (header && header.height) { - latestBlockheight = header.height; - latestBlockheightTimestamp = Math.floor(+new Date() / 1000); - } - // AsyncStorage.setItem(storageKey, JSON.stringify(peers)); TODO: refactor - } - } catch (e) { - mainConnected = false; - console.log('bad connection:', JSON.stringify(usingPeer), e); - } - - if (!mainConnected) { - console.log('retry'); - connectionAttempt = connectionAttempt + 1; - mainClient.close && mainClient.close(); - if (connectionAttempt >= 5) { - presentNetworkErrorAlert(usingPeer); - } else { - console.log('reconnection attempt #', connectionAttempt); - await new Promise(resolve => setTimeout(resolve, 500)); // sleep - return connectMain(); - } - } -} - -async function presentNetworkErrorAlert(usingPeer) { - 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.', - ); - return; - } - Alert.alert( - loc.errors.network, - loc.formatString( - usingPeer ? loc.settings.electrum_unable_to_connect : loc.settings.electrum_error_connect, - usingPeer ? { server: `${usingPeer.host}:${usingPeer.ssl ?? usingPeer.tcp}` } : {}, - ), - [ - { - text: loc.wallets.list_tryagain, - onPress: () => { - connectionAttempt = 0; - mainClient.close() && mainClient.close(); - setTimeout(connectMain, 500); - }, - style: 'default', - }, - { - text: loc.settings.electrum_reset, - onPress: () => { - Alert.alert( - loc.settings.electrum_reset, - loc.settings.electrum_reset_to_default, - [ - { - text: loc._.cancel, - style: 'cancel', - onPress: () => {}, - }, - { - text: loc._.ok, - style: 'destructive', - onPress: async () => { - await AsyncStorage.setItem(ELECTRUM_HOST, ''); - await AsyncStorage.setItem(ELECTRUM_TCP_PORT, ''); - await AsyncStorage.setItem(ELECTRUM_SSL_PORT, ''); - try { - await DefaultPreference.setName('group.io.bluewallet.bluewallet'); - await DefaultPreference.clear(ELECTRUM_HOST); - await DefaultPreference.clear(ELECTRUM_SSL_PORT); - await DefaultPreference.clear(ELECTRUM_TCP_PORT); - WidgetCommunication.reloadAllTimelines(); - } catch (e) { - // Must be running on Android - console.log(e); - } - alert(loc.settings.electrum_saved); - setTimeout(connectMain, 500); - }, - }, - ], - { cancelable: true }, - ); - connectionAttempt = 0; - mainClient.close() && mainClient.close(); - }, - style: 'destructive', - }, - { - text: loc._.cancel, - onPress: () => { - connectionAttempt = 0; - mainClient.close() && mainClient.close(); - }, - style: 'cancel', - }, - ], - { cancelable: false }, - ); -} - -async function getCurrentPeer() { - return hardcodedPeers[currentPeerIndex]; -} - -/** - * Returns NEXT hardcoded electrum server (increments index after use) - * - * @returns {Promise<{tcp, host, ssl?}|*>} - */ -async function getNextPeer() { - const peer = getCurrentPeer(); - currentPeerIndex++; - if (currentPeerIndex + 1 >= hardcodedPeers.length) currentPeerIndex = 0; - return peer; -} - -async function getSavedPeer() { - const host = await AsyncStorage.getItem(ELECTRUM_HOST); - const port = await AsyncStorage.getItem(ELECTRUM_TCP_PORT); - const sslPort = await AsyncStorage.getItem(ELECTRUM_SSL_PORT); - return { host, tcp: port, ssl: sslPort }; -} - -/** - * Returns random electrum server out of list of servers - * previous electrum server told us. Nearly half of them is - * usually offline. - * Not used for now. - * - * @returns {Promise<{tcp: number, host: string}>} - */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -async function getRandomDynamicPeer() { - try { - let peers = JSON.parse(await AsyncStorage.getItem(storageKey)); - peers = peers.sort(() => Math.random() - 0.5); // shuffle - for (const peer of peers) { - const ret = {}; - ret.host = peer[1]; - for (const item of peer[2]) { - if (item.startsWith('t')) { - ret.tcp = item.replace('t', ''); - } - } - if (ret.host && ret.tcp) return ret; - } - - return defaultPeer; // failed to find random client, using default - } catch (_) { - return defaultPeer; // smth went wrong, using default - } -} - -/** - * - * @param address {String} - * @returns {Promise} - */ -module.exports.getBalanceByAddress = async function (address) { - if (!mainClient) throw new Error('Electrum client is not connected'); - const script = bitcoin.address.toOutputScript(address); - const hash = bitcoin.crypto.sha256(script); - const reversedHash = Buffer.from(reverse(hash)); - const balance = await mainClient.blockchainScripthash_getBalance(reversedHash.toString('hex')); - balance.addr = address; - return balance; -}; - -module.exports.getConfig = async function () { - if (!mainClient) throw new Error('Electrum client is not connected'); - return { - host: mainClient.host, - port: mainClient.port, - serverName, - connected: mainClient.timeLastCall !== 0 && mainClient.status, - }; -}; - -module.exports.getSecondsSinceLastRequest = function () { - return mainClient && mainClient.timeLastCall ? (+new Date() - mainClient.timeLastCall) / 1000 : -1; -}; - -/** - * - * @param address {String} - * @returns {Promise} - */ -module.exports.getTransactionsByAddress = async function (address) { - if (!mainClient) throw new Error('Electrum client is not connected'); - const script = bitcoin.address.toOutputScript(address); - const hash = bitcoin.crypto.sha256(script); - const reversedHash = Buffer.from(reverse(hash)); - const history = await mainClient.blockchainScripthash_getHistory(reversedHash.toString('hex')); - for (const h of history || []) { - if (h.tx_hash) txhashHeightCache[h.tx_hash] = h.height; // cache tx height - } - - return history; -}; - -/** - * - * @param address {String} - * @returns {Promise} - */ -module.exports.getMempoolTransactionsByAddress = async function (address) { - if (!mainClient) throw new Error('Electrum client is not connected'); - const script = bitcoin.address.toOutputScript(address); - const hash = bitcoin.crypto.sha256(script); - const reversedHash = Buffer.from(reverse(hash)); - return mainClient.blockchainScripthash_getMempool(reversedHash.toString('hex')); -}; - -module.exports.ping = async function () { - try { - await mainClient.server_ping(); - } catch (_) { - mainConnected = false; - return false; - } - return true; -}; - -module.exports.getTransactionsFullByAddress = async function (address) { - const txs = await this.getTransactionsByAddress(address); - const ret = []; - for (const tx of txs) { - let full; - try { - full = await mainClient.blockchainTransaction_get(tx.tx_hash, true); - } catch (error) { - if (String(error?.message ?? error).startsWith('verbose transactions are currently unsupported')) { - // apparently, stupid esplora instead of returning txhex when it cant return verbose tx started - // throwing a proper exception. lets fetch txhex manually and decode on our end - const txhex = await mainClient.blockchainTransaction_get(tx.tx_hash, false); - full = txhexToElectrumTransaction(txhex); - } else { - // nope, its something else - throw new Error(String(error?.message ?? error)); - } - } - full.address = address; - for (const input of full.vin) { - // now we need to fetch previous TX where this VIN became an output, so we can see its amount - let prevTxForVin; - try { - prevTxForVin = await mainClient.blockchainTransaction_get(input.txid, true); - } catch (error) { - if (String(error?.message ?? error).startsWith('verbose transactions are currently unsupported')) { - // apparently, stupid esplora instead of returning txhex when it cant return verbose tx started - // throwing a proper exception. lets fetch txhex manually and decode on our end - const txhex = await mainClient.blockchainTransaction_get(input.txid, false); - prevTxForVin = txhexToElectrumTransaction(txhex); - } else { - // nope, its something else - throw new Error(String(error?.message ?? error)); - } - } - if (prevTxForVin && prevTxForVin.vout && prevTxForVin.vout[input.vout]) { - input.value = prevTxForVin.vout[input.vout].value; - // also, we extract destination address from prev output: - if (prevTxForVin.vout[input.vout].scriptPubKey && prevTxForVin.vout[input.vout].scriptPubKey.addresses) { - input.addresses = prevTxForVin.vout[input.vout].scriptPubKey.addresses; - } - // in bitcoin core 22.0.0+ they removed `.addresses` and replaced it with plain `.address`: - if (prevTxForVin.vout[input.vout]?.scriptPubKey?.address) { - input.addresses = [prevTxForVin.vout[input.vout].scriptPubKey.address]; - } - } - } - - for (const output of full.vout) { - if (output.scriptPubKey && output.scriptPubKey.addresses) output.addresses = output.scriptPubKey.addresses; - // in bitcoin core 22.0.0+ they removed `.addresses` and replaced it with plain `.address`: - if (output?.scriptPubKey?.address) output.addresses = [output.scriptPubKey.address]; - } - full.inputs = full.vin; - full.outputs = full.vout; - delete full.vin; - delete full.vout; - delete full.hex; // compact - delete full.hash; // compact - ret.push(full); - } - - return ret; -}; - -/** - * - * @param addresses {Array} - * @param batchsize {Number} - * @returns {Promise<{balance: number, unconfirmed_balance: number, addresses: object}>} - */ -module.exports.multiGetBalanceByAddress = async function (addresses, batchsize) { - batchsize = batchsize || 200; - if (!mainClient) throw new Error('Electrum client is not connected'); - const ret = { balance: 0, unconfirmed_balance: 0, addresses: {} }; - - const chunks = splitIntoChunks(addresses, batchsize); - for (const chunk of chunks) { - const scripthashes = []; - const scripthash2addr = {}; - for (const addr of chunk) { - const script = bitcoin.address.toOutputScript(addr); - const hash = bitcoin.crypto.sha256(script); - let reversedHash = Buffer.from(reverse(hash)); - reversedHash = reversedHash.toString('hex'); - scripthashes.push(reversedHash); - scripthash2addr[reversedHash] = addr; - } - - let balances = []; - - if (disableBatching) { - const promises = []; - const index2scripthash = {}; - for (let promiseIndex = 0; promiseIndex < scripthashes.length; promiseIndex++) { - promises.push(mainClient.blockchainScripthash_getBalance(scripthashes[promiseIndex])); - index2scripthash[promiseIndex] = scripthashes[promiseIndex]; - } - const promiseResults = await Promise.all(promises); - for (let resultIndex = 0; resultIndex < promiseResults.length; resultIndex++) { - balances.push({ result: promiseResults[resultIndex], param: index2scripthash[resultIndex] }); - } - } else { - balances = await mainClient.blockchainScripthash_getBalanceBatch(scripthashes); - } - - for (const bal of balances) { - if (bal.error) console.warn('multiGetBalanceByAddress():', bal.error); - ret.balance += +bal.result.confirmed; - ret.unconfirmed_balance += +bal.result.unconfirmed; - ret.addresses[scripthash2addr[bal.param]] = bal.result; - } - } - - return ret; -}; - -module.exports.multiGetUtxoByAddress = async function (addresses, batchsize) { - batchsize = batchsize || 100; - if (!mainClient) throw new Error('Electrum client is not connected'); - const ret = {}; - - const chunks = splitIntoChunks(addresses, batchsize); - for (const chunk of chunks) { - const scripthashes = []; - const scripthash2addr = {}; - for (const addr of chunk) { - const script = bitcoin.address.toOutputScript(addr); - const hash = bitcoin.crypto.sha256(script); - let reversedHash = Buffer.from(reverse(hash)); - reversedHash = reversedHash.toString('hex'); - scripthashes.push(reversedHash); - scripthash2addr[reversedHash] = addr; - } - - let results = []; - - if (disableBatching) { - // ElectrumPersonalServer doesnt support `blockchain.scripthash.listunspent` - // electrs OTOH supports it, but we dont know it we are currently connected to it or to EPS - // so it is pretty safe to do nothing, as caller can derive UTXO from stored transactions - } else { - results = await mainClient.blockchainScripthash_listunspentBatch(scripthashes); - } - - for (const utxos of results) { - ret[scripthash2addr[utxos.param]] = utxos.result; - for (const utxo of ret[scripthash2addr[utxos.param]]) { - utxo.address = scripthash2addr[utxos.param]; - utxo.txId = utxo.tx_hash; - utxo.vout = utxo.tx_pos; - delete utxo.tx_pos; - delete utxo.tx_hash; - } - } - } - - return ret; -}; - -module.exports.multiGetHistoryByAddress = async function (addresses, batchsize) { - batchsize = batchsize || 100; - if (!mainClient) throw new Error('Electrum client is not connected'); - const ret = {}; - - const chunks = splitIntoChunks(addresses, batchsize); - for (const chunk of chunks) { - const scripthashes = []; - const scripthash2addr = {}; - for (const addr of chunk) { - const script = bitcoin.address.toOutputScript(addr); - const hash = bitcoin.crypto.sha256(script); - let reversedHash = Buffer.from(reverse(hash)); - reversedHash = reversedHash.toString('hex'); - scripthashes.push(reversedHash); - scripthash2addr[reversedHash] = addr; - } - - let results = []; - - if (disableBatching) { - const promises = []; - const index2scripthash = {}; - for (let promiseIndex = 0; promiseIndex < scripthashes.length; promiseIndex++) { - index2scripthash[promiseIndex] = scripthashes[promiseIndex]; - promises.push(mainClient.blockchainScripthash_getHistory(scripthashes[promiseIndex])); - } - const histories = await Promise.all(promises); - for (let historyIndex = 0; historyIndex < histories.length; historyIndex++) { - results.push({ result: histories[historyIndex], param: index2scripthash[historyIndex] }); - } - } else { - results = await mainClient.blockchainScripthash_getHistoryBatch(scripthashes); - } - - for (const history of results) { - if (history.error) console.warn('multiGetHistoryByAddress():', history.error); - ret[scripthash2addr[history.param]] = history.result || []; - for (const result of history.result || []) { - if (result.tx_hash) txhashHeightCache[result.tx_hash] = result.height; // cache tx height - } - - for (const hist of ret[scripthash2addr[history.param]]) { - hist.address = scripthash2addr[history.param]; - } - } - } - - return ret; -}; - -module.exports.multiGetTransactionByTxid = async function (txids, batchsize, verbose = true) { - batchsize = batchsize || 45; - // this value is fine-tuned so althrough wallets in test suite will occasionally - // throw 'response too large (over 1,000,000 bytes', test suite will pass - if (!mainClient) throw new Error('Electrum client is not connected'); - const ret = {}; - txids = [...new Set(txids)]; // deduplicate just for any case - - // lets try cache first: - const realm = await _getRealm(); - const cacheKeySuffix = verbose ? '_verbose' : '_non_verbose'; - const keysCacheMiss = []; - for (const txid of txids) { - const jsonString = realm.objectForPrimaryKey('Cache', txid + cacheKeySuffix); // search for a realm object with a primary key - if (jsonString && jsonString.cache_value) { - try { - ret[txid] = JSON.parse(jsonString.cache_value); - } catch (error) { - console.log(error, 'cache failed to parse', jsonString.cache_value); - } - } - - if (!ret[txid]) keysCacheMiss.push(txid); - } - - if (keysCacheMiss.length === 0) { - return ret; - } - - txids = keysCacheMiss; - // end cache - - const chunks = splitIntoChunks(txids, batchsize); - for (const chunk of chunks) { - let results = []; - - if (disableBatching) { - try { - // in case of ElectrumPersonalServer it might not track some transactions (like source transactions for our transactions) - // so we wrap it in try-catch. note, when `Promise.all` fails we will get _zero_ results, but we have a fallback for that - const promises = []; - const index2txid = {}; - for (let promiseIndex = 0; promiseIndex < chunk.length; promiseIndex++) { - const txid = chunk[promiseIndex]; - index2txid[promiseIndex] = txid; - promises.push(mainClient.blockchainTransaction_get(txid, verbose)); - } - - const transactionResults = await Promise.all(promises); - for (let resultIndex = 0; resultIndex < transactionResults.length; resultIndex++) { - let tx = transactionResults[resultIndex]; - if (typeof tx === 'string' && verbose) { - // apparently electrum server (EPS?) didnt recognize VERBOSE parameter, and sent us plain txhex instead of decoded tx. - // lets decode it manually on our end then: - tx = txhexToElectrumTransaction(tx); - } - const txid = index2txid[resultIndex]; - results.push({ result: tx, param: txid }); - } - } catch (_) { - if (String(_?.message ?? _).startsWith('verbose transactions are currently unsupported')) { - // electrs-esplora. cant use verbose, so fetching txs one by one and decoding locally - for (const txid of chunk) { - try { - let tx = await mainClient.blockchainTransaction_get(txid, false); - tx = txhexToElectrumTransaction(tx); - results.push({ result: tx, param: txid }); - } catch (error) { - console.log(error); - } - } - } else { - // fallback. pretty sure we are connected to EPS. we try getting transactions one-by-one. this way we wont - // fail and only non-tracked by EPS transactions will be omitted - for (const txid of chunk) { - try { - let tx = await mainClient.blockchainTransaction_get(txid, verbose); - if (typeof tx === 'string' && verbose) { - // apparently electrum server (EPS?) didnt recognize VERBOSE parameter, and sent us plain txhex instead of decoded tx. - // lets decode it manually on our end then: - tx = txhexToElectrumTransaction(tx); - } - results.push({ result: tx, param: txid }); - } catch (error) { - console.log(error); - } - } - } - } - } else { - results = await mainClient.blockchainTransaction_getBatch(chunk, verbose); - } - - for (const txdata of results) { - if (txdata.error && txdata.error.code === -32600) { - // response too large - // lets do single call, that should go through okay: - txdata.result = await mainClient.blockchainTransaction_get(txdata.param, false); - // since we used VERBOSE=false, server sent us plain txhex which we must decode on our end: - txdata.result = txhexToElectrumTransaction(txdata.result); - } - ret[txdata.param] = txdata.result; - if (ret[txdata.param]) delete ret[txdata.param].hex; // compact - } - } - - // in bitcoin core 22.0.0+ they removed `.addresses` and replaced it with plain `.address`: - for (const txid of Object.keys(ret) ?? []) { - for (const vout of ret[txid]?.vout ?? []) { - if (vout?.scriptPubKey?.address) vout.scriptPubKey.addresses = [vout.scriptPubKey.address]; - } - } - - // saving cache: - realm.write(() => { - for (const txid of Object.keys(ret)) { - if (verbose && (!ret[txid].confirmations || ret[txid].confirmations < 7)) continue; - // dont cache immature txs, but only for 'verbose', since its fully decoded tx jsons. non-verbose are just plain - // strings txhex - realm.create( - 'Cache', - { - cache_key: txid + cacheKeySuffix, - cache_value: JSON.stringify(ret[txid]), - }, - Realm.UpdateMode.Modified, - ); - } - }); - - return ret; -}; - -/** - * Simple waiter till `mainConnected` becomes true (which means - * it Electrum was connected in other function), or timeout 30 sec. - * - * - * @returns {Promise | Promise<*>>} - */ -module.exports.waitTillConnected = async function () { - let waitTillConnectedInterval = false; - let retriesCounter = 0; - if (await isDisabled()) { - console.warn('Electrum connections disabled by user. waitTillConnected skipping...'); - return; - } - return new Promise(function (resolve, reject) { - waitTillConnectedInterval = setInterval(() => { - if (mainConnected) { - clearInterval(waitTillConnectedInterval); - return resolve(true); - } - - if (wasConnectedAtLeastOnce && mainClient.status === 1) { - clearInterval(waitTillConnectedInterval); - mainConnected = true; - return resolve(true); - } - - if (wasConnectedAtLeastOnce && retriesCounter++ >= 150) { - // `wasConnectedAtLeastOnce` needed otherwise theres gona be a race condition with the code that connects - // electrum during app startup - clearInterval(waitTillConnectedInterval); - presentNetworkErrorAlert(); - reject(new Error('Waiting for Electrum connection timeout')); - } - }, 100); - }); -}; - -// Returns the value at a given percentile in a sorted numeric array. -// "Linear interpolation between closest ranks" method -function percentile(arr, p) { - if (arr.length === 0) return 0; - if (typeof p !== 'number') throw new TypeError('p must be a number'); - if (p <= 0) return arr[0]; - if (p >= 1) return arr[arr.length - 1]; - - const index = (arr.length - 1) * p; - const lower = Math.floor(index); - const upper = lower + 1; - const weight = index % 1; - - if (upper >= arr.length) return arr[lower]; - return arr[lower] * (1 - weight) + arr[upper] * weight; -} - -/** - * The histogram is an array of [fee, vsize] pairs, where vsizen is the cumulative virtual size of mempool transactions - * with a fee rate in the interval [feen-1, feen], and feen-1 > feen. - * - * @param numberOfBlocks {Number} - * @param feeHistorgram {Array} - * @returns {number} - */ -module.exports.calcEstimateFeeFromFeeHistorgam = function (numberOfBlocks, feeHistorgram) { - // first, transforming histogram: - let totalVsize = 0; - const histogramToUse = []; - for (const h of feeHistorgram) { - let [fee, vsize] = h; - let timeToStop = false; - - if (totalVsize + vsize >= 1000000 * numberOfBlocks) { - vsize = 1000000 * numberOfBlocks - totalVsize; // only the difference between current summarized sige to tip of the block - timeToStop = true; - } - - histogramToUse.push({ fee, vsize }); - totalVsize += vsize; - if (timeToStop) break; - } - - // now we have histogram of precisely size for numberOfBlocks. - // lets spread it into flat array so its easier to calculate percentile: - let histogramFlat = []; - for (const hh of histogramToUse) { - histogramFlat = histogramFlat.concat(Array(Math.round(hh.vsize / 25000)).fill(hh.fee)); - // division is needed so resulting flat array is not too huge - } - - histogramFlat = histogramFlat.sort(function (a, b) { - return a - b; - }); - - return Math.round(percentile(histogramFlat, 0.5) || 1); -}; - -module.exports.estimateFees = async function () { - let histogram; - let timeoutId; - try { - histogram = await Promise.race([ - mainClient.mempool_getFeeHistogram(), - new Promise(resolve => (timeoutId = setTimeout(resolve, 15000))), - ]); - } finally { - clearTimeout(timeoutId); - } - - if (!histogram) throw new Error('timeout while getting mempool_getFeeHistogram'); - - // fetching what electrum (which uses bitcoin core) thinks about fees: - const _fast = await module.exports.estimateFee(1); - const _medium = await module.exports.estimateFee(18); - const _slow = await module.exports.estimateFee(144); - - // calculating fast fees from mempool: - const fast = Math.max(2, module.exports.calcEstimateFeeFromFeeHistorgam(1, histogram)); - // recalculating medium and slow fees using bitcoincore estimations only like relative weights: - // (minimum 1 sat, just for any case) - const medium = Math.max(1, Math.round((fast * _medium) / _fast)); - const slow = Math.max(1, Math.round((fast * _slow) / _fast)); - return { fast, medium, slow }; -}; - -/** - * Returns the estimated transaction fee to be confirmed within a certain number of blocks - * - * @param numberOfBlocks {number} The number of blocks to target for confirmation - * @returns {Promise} Satoshis per byte - */ -module.exports.estimateFee = async function (numberOfBlocks) { - if (!mainClient) throw new Error('Electrum client is not connected'); - numberOfBlocks = numberOfBlocks || 1; - const coinUnitsPerKilobyte = await mainClient.blockchainEstimatefee(numberOfBlocks); - if (coinUnitsPerKilobyte === -1) return 1; - return Math.round(new BigNumber(coinUnitsPerKilobyte).dividedBy(1024).multipliedBy(100000000).toNumber()); -}; - -module.exports.serverFeatures = async function () { - if (!mainClient) throw new Error('Electrum client is not connected'); - return mainClient.server_features(); -}; - -module.exports.broadcast = async function (hex) { - if (!mainClient) throw new Error('Electrum client is not connected'); - try { - const broadcast = await mainClient.blockchainTransaction_broadcast(hex); - return broadcast; - } catch (error) { - return error; - } -}; - -module.exports.broadcastV2 = async function (hex) { - if (!mainClient) throw new Error('Electrum client is not connected'); - return mainClient.blockchainTransaction_broadcast(hex); -}; - -module.exports.estimateCurrentBlockheight = function () { - if (latestBlockheight) { - const timeDiff = Math.floor(+new Date() / 1000) - latestBlockheightTimestamp; - const extraBlocks = Math.floor(timeDiff / (9.93 * 60)); - return latestBlockheight + extraBlocks; - } - - const baseTs = 1587570465609; // uS - const baseHeight = 627179; - return Math.floor(baseHeight + (+new Date() - baseTs) / 1000 / 60 / 9.93); -}; - -/** - * - * @param height - * @returns {number} Timestamp in seconds - */ -module.exports.calculateBlockTime = function (height) { - if (latestBlockheight) { - return Math.floor(latestBlockheightTimestamp + (height - latestBlockheight) * 9.93 * 60); - } - - const baseTs = 1585837504; // sec - const baseHeight = 624083; - return Math.floor(baseTs + (height - baseHeight) * 9.93 * 60); -}; - -/** - * - * @param host - * @param tcpPort - * @param sslPort - * @returns {Promise} Whether provided host:port is a valid electrum server - */ -module.exports.testConnection = async function (host, tcpPort, sslPort) { - const isTorDisabled = await isTorDaemonDisabled(); - const client = new ElectrumClient( - host.endsWith('.onion') && !isTorDisabled ? torrific : global.net, - global.tls, - sslPort || tcpPort, - host, - sslPort ? 'tls' : 'tcp', - ); - - client.onError = () => {}; // mute - let timeoutId = false; - try { - const rez = await Promise.race([ - new Promise(resolve => { - timeoutId = setTimeout(() => resolve('timeout'), host.endsWith('.onion') && !isTorDisabled ? 21000 : 5000); - }), - client.connect(), - ]); - if (rez === 'timeout') return false; - - await client.server_version('2.7.11', '1.4'); - await client.server_ping(); - return true; - } catch (_) { - } finally { - if (timeoutId) clearTimeout(timeoutId); - client.close(); - } - - return false; -}; - -module.exports.forceDisconnect = () => { - mainClient.close(); -}; - -module.exports.setBatchingDisabled = () => { - disableBatching = true; -}; - -module.exports.setBatchingEnabled = () => { - disableBatching = false; -}; -module.exports.connectMain = connectMain; -module.exports.isDisabled = isDisabled; -module.exports.setDisabled = setDisabled; -module.exports.hardcodedPeers = hardcodedPeers; -module.exports.ELECTRUM_HOST = ELECTRUM_HOST; -module.exports.ELECTRUM_TCP_PORT = ELECTRUM_TCP_PORT; -module.exports.ELECTRUM_SSL_PORT = ELECTRUM_SSL_PORT; -module.exports.ELECTRUM_SERVER_HISTORY = ELECTRUM_SERVER_HISTORY; - -const splitIntoChunks = function (arr, chunkSize) { - const groups = []; - let i; - for (i = 0; i < arr.length; i += chunkSize) { - groups.push(arr.slice(i, i + chunkSize)); - } - return groups; -}; - -const semVerToInt = function (semver) { - if (!semver) return 0; - if (semver.split('.').length !== 3) return 0; - - const ret = semver.split('.')[0] * 1000000 + semver.split('.')[1] * 1000 + semver.split('.')[2] * 1; - - if (isNaN(ret)) return 0; - - return ret; -}; - -function txhexToElectrumTransaction(txhex) { - const tx = bitcoin.Transaction.fromHex(txhex); - - const ret = { - txid: tx.getId(), - hash: tx.getId(), - version: tx.version, - size: Math.ceil(txhex.length / 2), - vsize: tx.virtualSize(), - weight: tx.weight(), - locktime: tx.locktime, - vin: [], - vout: [], - hex: txhex, - blockhash: '', - confirmations: 0, - time: 0, - blocktime: 0, - }; - - if (txhashHeightCache[ret.txid]) { - // got blockheight where this tx was confirmed - ret.confirmations = module.exports.estimateCurrentBlockheight() - txhashHeightCache[ret.txid]; - if (ret.confirmations < 0) { - // ugly fix for when estimator lags behind - ret.confirmations = 1; - } - ret.time = module.exports.calculateBlockTime(txhashHeightCache[ret.txid]); - ret.blocktime = module.exports.calculateBlockTime(txhashHeightCache[ret.txid]); - } - - for (const inn of tx.ins) { - const txinwitness = []; - if (inn.witness[0]) txinwitness.push(inn.witness[0].toString('hex')); - if (inn.witness[1]) txinwitness.push(inn.witness[1].toString('hex')); - - ret.vin.push({ - txid: reverse(inn.hash).toString('hex'), - vout: inn.index, - scriptSig: { hex: inn.script.toString('hex'), asm: '' }, - txinwitness, - sequence: inn.sequence, - }); - } - - let n = 0; - for (const out of tx.outs) { - const value = new BigNumber(out.value).dividedBy(100000000).toNumber(); - let address = false; - let type = false; - - if (SegwitBech32Wallet.scriptPubKeyToAddress(out.script.toString('hex'))) { - address = SegwitBech32Wallet.scriptPubKeyToAddress(out.script.toString('hex')); - type = 'witness_v0_keyhash'; - } else if (SegwitP2SHWallet.scriptPubKeyToAddress(out.script.toString('hex'))) { - address = SegwitP2SHWallet.scriptPubKeyToAddress(out.script.toString('hex')); - type = '???'; // TODO - } else if (LegacyWallet.scriptPubKeyToAddress(out.script.toString('hex'))) { - address = LegacyWallet.scriptPubKeyToAddress(out.script.toString('hex')); - type = '???'; // TODO - } else { - address = TaprootWallet.scriptPubKeyToAddress(out.script.toString('hex')); - type = 'witness_v1_taproot'; - } - - ret.vout.push({ - value, - n, - scriptPubKey: { - asm: '', - hex: out.script.toString('hex'), - reqSigs: 1, // todo - type, - addresses: [address], - }, - }); - n++; - } - return ret; -} - -// exported only to be used in unit tests -module.exports.txhexToElectrumTransaction = txhexToElectrumTransaction; diff --git a/blue_modules/BlueElectrum.ts b/blue_modules/BlueElectrum.ts new file mode 100644 index 00000000000..4021a1b8492 --- /dev/null +++ b/blue_modules/BlueElectrum.ts @@ -0,0 +1,1277 @@ +import BigNumber from 'bignumber.js'; +import * as bitcoin from 'bitcoinjs-lib'; +import DefaultPreference from 'react-native-default-preference'; +import RNFS from 'react-native-fs'; +import Realm from 'realm'; +import { sha256 as _sha256 } from '@noble/hashes/sha256'; + +import type { LegacyWallet as LegacyWalletT } from '../class/wallets/legacy-wallet'; +import type { SegwitBech32Wallet as SegwitBech32WalletT } from '../class/wallets/segwit-bech32-wallet'; +import type { SegwitP2SHWallet as SegwitP2SHWalletT } from '../class/wallets/segwit-p2sh-wallet'; +import type { TaprootWallet as TaprootWalletT } from '../class/wallets/taproot-wallet'; +import presentAlert from '../components/Alert'; +import loc from '../loc'; +import { GROUP_IO_BLUEWALLET } from './currency'; +import { ElectrumServerItem } from '../screen/settings/ElectrumSettings'; +import { triggerWarningHapticFeedback } from './hapticFeedback'; +import { AlertButton } from 'react-native'; +import { uint8ArrayToHex, stringToUint8Array, hexToUint8Array } from './uint8array-extras/index'; + +const ElectrumClient = require('electrum-client'); +const net = require('net'); +const tls = require('tls'); + +type Utxo = { + height: number; + value: number; + address: string; + txid: string; + vout: number; + wif?: string; +}; + +type ElectrumTransaction = { + txid: string; + hash: string; + version: number; + size: number; + vsize: number; + weight: number; + locktime: number; + vin: { + txid: string; + vout: number; + scriptSig: { asm: string; hex: string }; + txinwitness: string[]; + sequence: number; + addresses?: string[]; + value?: number; + }[]; + vout: { + value: number; + n: number; + scriptPubKey: { + asm: string; + hex: string; + reqSigs: number; + type: string; + addresses: string[]; + }; + }[]; + blockhash: string; + confirmations: number; + time: number; + blocktime: number; +}; + +type ElectrumTransactionWithHex = ElectrumTransaction & { + hex: string; +}; + +type MempoolTransaction = { + height: 0; + tx_hash: string; + fee: number; +}; + +type Peer = { + host: string; + ssl?: number; + tcp?: number; +}; + +export const ELECTRUM_HOST = 'electrum_host'; +export const ELECTRUM_TCP_PORT = 'electrum_tcp_port'; +export const ELECTRUM_SSL_PORT = 'electrum_ssl_port'; +export const ELECTRUM_SERVER_HISTORY = 'electrum_server_history'; +const ELECTRUM_CONNECTION_DISABLED = 'electrum_disabled'; +const storageKey = 'ELECTRUM_PEERS'; +const defaultPeer = { host: 'electrum1.bluewallet.io', ssl: 443 }; +export const hardcodedPeers: Peer[] = [ + { host: 'mainnet.foundationdevices.com', ssl: 50002 }, + { host: 'bitcoin.lu.ke', ssl: 50002 }, + // { host: 'electrum.jochen-hoenicke.de', ssl: '50006' }, + { host: 'electrum1.bluewallet.io', ssl: 443 }, + { host: 'electrum.acinq.co', ssl: 50002 }, +]; + +export const suggestedServers: Peer[] = hardcodedPeers.map(peer => ({ + ...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; + +/** Max wall time for one `waitTillConnected` wait (ms); derived from ticks above for callers (e.g. refresh fetch race). */ +export const WAIT_TILL_CONNECTED_MAX_WALL_MS_AFTER_FIRST = WAIT_TILL_CONNECTED_MAX_TICKS_AFTER_FIRST_CONNECT * WAIT_TILL_CONNECTED_TICK_MS; +export const WAIT_TILL_CONNECTED_MAX_WALL_MS_NEVER = WAIT_TILL_CONNECTED_MAX_TICKS_NEVER_CONNECTED * WAIT_TILL_CONNECTED_TICK_MS; +const txhashHeightCache: Record = {}; +let _realm: Realm | undefined; + +function bitcoinjs_crypto_sha256(buffer: Uint8Array): Uint8Array { + return _sha256(buffer); +} + +async function _getRealm() { + if (_realm) return _realm; + + const cacheFolderPath = RNFS.CachesDirectoryPath; // Path to cache folder + const password = uint8ArrayToHex(bitcoinjs_crypto_sha256(stringToUint8Array('fyegjitkyf[eqjnc.lf'))); + const buf = hexToUint8Array(password + password); + const encryptionKey = Int8Array.from(buf); + const path = `${cacheFolderPath}/electrumcache.realm`; // Use cache folder path + + const schema = [ + { + name: 'Cache', + primaryKey: 'cache_key', + properties: { + cache_key: { type: 'string', indexed: true }, + cache_value: 'string', // stringified json + }, + }, + ]; + + // @ts-ignore schema doesn't match Realm's schema type + _realm = await Realm.open({ + schema, + path, + encryptionKey, + excludeFromIcloudBackup: true, + }); + + return _realm; +} + +export const getPreferredServer = async (): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const host = (await DefaultPreference.get(ELECTRUM_HOST)) as string; + const tcpPort = await DefaultPreference.get(ELECTRUM_TCP_PORT); + const sslPort = await DefaultPreference.get(ELECTRUM_SSL_PORT); + + console.log('Getting preferred server:', { host, tcpPort, sslPort }); + + if (!host) { + console.warn('Preferred server host is undefined'); + return; + } + + return { + host, + tcp: tcpPort ? Number(tcpPort) : undefined, + ssl: sslPort ? Number(sslPort) : undefined, + }; + } catch (error) { + console.error('Error in getPreferredServer:', error); + return undefined; + } +}; + +export const removePreferredServer = async () => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + console.log('Removing preferred server'); + await DefaultPreference.clear(ELECTRUM_HOST); + await DefaultPreference.clear(ELECTRUM_TCP_PORT); + await DefaultPreference.clear(ELECTRUM_SSL_PORT); + } catch (error) { + console.error('Error in removePreferredServer:', error); + } +}; + +export async function isDisabled(): Promise { + let result; + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const savedValue = await DefaultPreference.get(ELECTRUM_CONNECTION_DISABLED); + console.log('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); + result = false; + } + return !!result; +} + +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' : ''); +} + +function getCurrentPeer() { + return hardcodedPeers[currentPeerIndex]; +} + +/** + * Returns NEXT hardcoded electrum server (increments index after use) + */ +function getNextPeer() { + const peer = getCurrentPeer(); + currentPeerIndex++; + if (currentPeerIndex >= hardcodedPeers.length) currentPeerIndex = 0; + return peer; +} + +async function getSavedPeer(): Promise { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const host = (await DefaultPreference.get(ELECTRUM_HOST)) as string; + const tcpPort = await DefaultPreference.get(ELECTRUM_TCP_PORT); + const sslPort = await DefaultPreference.get(ELECTRUM_SSL_PORT); + + console.log('Getting saved peer:', { host, tcpPort, sslPort }); + + if (!host) { + return null; + } + + if (sslPort) { + return { host, ssl: Number(sslPort) }; + } + + if (tcpPort) { + return { host, tcp: Number(tcpPort) }; + } + + return null; + } catch (error) { + console.error('Error in getSavedPeer:', error); + return null; + } +} + +export async function connectMain(): Promise { + if (await isDisabled()) { + console.log('Electrum connection disabled by user. Skipping connectMain call'); + return; + } + let usingPeer = getNextPeer(); + const savedPeer = await getSavedPeer(); + if (savedPeer && savedPeer.host && (savedPeer.tcp || savedPeer.ssl)) { + usingPeer = savedPeer; + } + + console.log('Using peer:', JSON.stringify(usingPeer)); + + try { + console.log('begin connection:', JSON.stringify(usingPeer)); + mainClient = new ElectrumClient(net, tls, usingPeer.ssl || usingPeer.tcp, usingPeer.host, usingPeer.ssl ? 'tls' : 'tcp'); + + mainClient.onError = function (e: { message: string }) { + console.log('electrum mainClient.onError():', e.message); + if (mainConnected) { + // most likely got a timeout from electrum ping. lets reconnect + // but only if we were previously connected (mainConnected), otherwise theres other + // code which does connection retries + mainClient?.close(); + mainClient = undefined; + mainConnected = false; + // dropping `mainConnected` flag ensures there wont be reconnection race condition if several + // errors triggered + console.log('reconnecting after socket error'); + setTimeout(connectMain, usingPeer.host.endsWith('.onion') ? 4000 : 500); + } + }; + const ver = await mainClient.initElectrum({ client: 'bluewallet', version: '1.4' }); + if (ver && ver[0]) { + console.log('connected to ', ver); + serverName = ver[0]; + mainConnected = true; + wasConnectedAtLeastOnce = true; + if (ver[0].startsWith('ElectrumPersonalServer') || ver[0].startsWith('electrs') || ver[0].startsWith('Fulcrum')) { + disableBatching = true; + + // exeptions for versions: + const [electrumImplementation, electrumVersion] = ver[0].split(' '); + switch (electrumImplementation) { + case 'electrs': + if (semVerToInt(electrumVersion) >= semVerToInt('0.9.0')) { + disableBatching = false; + } + 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')) { + disableBatching = false; + } + break; + } + } + const header = await mainClient.blockchainHeaders_subscribe(); + if (header && header.height) { + latestBlock = { + height: header.height, + time: Math.floor(+new Date() / 1000), + }; + } + // AsyncStorage.setItem(storageKey, JSON.stringify(peers)); TODO: refactor + } + } catch (e) { + mainConnected = false; + console.log('bad connection:', JSON.stringify(usingPeer), e); + mainClient?.close(); + mainClient = undefined; + } + + if (!mainConnected) { + console.log('retry'); + connectionAttempt = connectionAttempt + 1; + mainClient?.close(); + mainClient = undefined; + if (connectionAttempt >= 5) { + // eslint-disable-next-line @typescript-eslint/no-use-before-define -- `presentNetworkErrorAlert` is defined below after `connectMain` + presentNetworkErrorAlert(usingPeer); + } else { + console.log('reconnection attempt #', connectionAttempt); + await new Promise(resolve => setTimeout(resolve, 500)); // sleep + return connectMain(); + } + } +} + +export async function presentResetToDefaultsAlert(): Promise { + const hasPreferredServer = await getPreferredServer(); + const serverHistoryStr = await DefaultPreference.get(ELECTRUM_SERVER_HISTORY); + const serverHistory = typeof serverHistoryStr === 'string' ? JSON.parse(serverHistoryStr) : []; + return new Promise(resolve => { + triggerWarningHapticFeedback(); + + const buttons: AlertButton[] = []; + + if (hasPreferredServer?.host && (hasPreferredServer.tcp || hasPreferredServer.ssl)) { + buttons.push({ + text: loc.settings.electrum_reset, + onPress: async () => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + await DefaultPreference.clear(ELECTRUM_HOST); + await DefaultPreference.clear(ELECTRUM_SSL_PORT); + await DefaultPreference.clear(ELECTRUM_TCP_PORT); + } catch (e) { + console.log(e); // Must be running on Android + } + resolve(true); + }, + style: 'default', + }); + } + + if (serverHistory.length > 0) { + buttons.push({ + text: loc.settings.electrum_reset_to_default_and_clear_history, + onPress: async () => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + await DefaultPreference.clear(ELECTRUM_SERVER_HISTORY); + await DefaultPreference.clear(ELECTRUM_HOST); + await DefaultPreference.clear(ELECTRUM_SSL_PORT); + await DefaultPreference.clear(ELECTRUM_TCP_PORT); + } catch (e) { + console.log(e); // Must be running on Android + } + resolve(true); + }, + style: 'destructive', + }); + } + + buttons.push({ + text: loc._.cancel, + onPress: () => resolve(false), + style: 'cancel', + }); + + presentAlert({ + title: loc.settings.electrum_reset, + message: loc.settings.electrum_reset_to_default, + buttons, + options: { cancelable: true }, + }); + }); +} + +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.', + ); + return; + } + + presentAlert({ + allowRepeat, + title: loc.errors.network, + message: loc.formatString( + usingPeer ? loc.settings.electrum_unable_to_connect : loc.settings.electrum_error_connect, + usingPeer ? { server: `${usingPeer.host}:${usingPeer.ssl ?? usingPeer.tcp}` } : {}, + ), + buttons: [ + { + text: loc.wallets.list_tryagain, + onPress: () => { + connectionAttempt = 0; + mainClient?.close(); + mainClient = undefined; + setTimeout(connectMain, 500); + }, + style: 'default', + }, + { + text: loc.settings.electrum_reset, + onPress: () => { + presentResetToDefaultsAlert().then(result => { + if (result) { + connectionAttempt = 0; + mainClient?.close(); + mainClient = undefined; + setTimeout(connectMain, 500); + } + }); + }, + style: 'destructive', + }, + { + text: loc._.cancel, + onPress: () => { + connectionAttempt = 0; + mainClient?.close(); + mainClient = undefined; + }, + style: 'cancel', + }, + ], + options: { cancelable: false }, + }); +} + +/** + * Wallets list header when Electrum looks disconnected: same actions as the internal timeout alert, with allowRepeat so the user can open it again after dismiss. + */ +export async function presentElectrumDisconnectedHelpAlert(): Promise { + await presentNetworkErrorAlert(undefined, true); +} + +/** + * Returns random electrum server out of list of servers + * previous electrum server told us. Nearly half of them is + * usually offline. + * Not used for now. + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +async function getRandomDynamicPeer(): Promise { + try { + let peers = JSON.parse((await DefaultPreference.get(storageKey)) as string); + peers = peers.sort(() => Math.random() - 0.5); // shuffle + for (const peer of peers) { + const ret: Peer = { host: peer[0], ssl: peer[1] }; + ret.host = peer[1]; + + if (peer[1] === 's') { + ret.ssl = peer[2]; + } else { + ret.tcp = peer[2]; + } + + for (const item of peer[2]) { + if (item.startsWith('t')) { + ret.tcp = item.replace('t', ''); + } + } + if (ret.host && ret.tcp) return ret; + } + + return defaultPeer; // failed to find random client, using default + } catch (_) { + return defaultPeer; // smth went wrong, using default + } +} + +export const getBalanceByAddress = async function (address: string): Promise<{ confirmed: number; unconfirmed: number }> { + try { + if (!mainClient) throw new Error('Electrum client is not connected'); + const script = bitcoin.address.toOutputScript(address); + const hash = bitcoinjs_crypto_sha256(script); + const reversedHash = new Uint8Array(hash).reverse(); + const balance = await mainClient.blockchainScripthash_getBalance(uint8ArrayToHex(reversedHash)); + balance.addr = address; + return balance; + } catch (error) { + console.error('Error in getBalanceByAddress:', error); + throw error; + } +}; + +export const getConfig = async function () { + if (!mainClient) throw new Error('Electrum client is not connected'); + return { + host: mainClient.host, + port: mainClient.port, + serverName, + connected: mainClient.timeLastCall !== 0 && mainClient.status, + }; +}; + +export const getSecondsSinceLastRequest = function () { + return mainClient && mainClient.timeLastCall ? (+new Date() - mainClient.timeLastCall) / 1000 : -1; +}; + +export const getTransactionsByAddress = async function (address: string): Promise { + if (!mainClient) throw new Error('Electrum client is not connected'); + const script = bitcoin.address.toOutputScript(address); + const hash = bitcoinjs_crypto_sha256(script); + const reversedHash = new Uint8Array(hash).reverse(); + const history = await mainClient.blockchainScripthash_getHistory(uint8ArrayToHex(reversedHash)); + for (const h of history || []) { + if (h.tx_hash) txhashHeightCache[h.tx_hash] = h.height; // cache tx height + } + + return history; +}; + +export const getMempoolTransactionsByAddress = async function (address: string): Promise { + if (!mainClient) throw new Error('Electrum client is not connected'); + const script = bitcoin.address.toOutputScript(address); + const hash = bitcoinjs_crypto_sha256(script); + const reversedHash = new Uint8Array(hash).reverse(); + return mainClient.blockchainScripthash_getMempool(uint8ArrayToHex(reversedHash)); +}; + +export const ping = async function () { + try { + await mainClient.server_ping(); + return true; + } catch (_) {} + + mainConnected = false; + return false; +}; + +// exported only to be used in unit tests +export function txhexToElectrumTransaction(txhex: string): ElectrumTransactionWithHex { + const tx = bitcoin.Transaction.fromHex(txhex); + + const ret: ElectrumTransactionWithHex = { + txid: tx.getId(), + hash: tx.getId(), + version: tx.version, + size: Math.ceil(txhex.length / 2), + vsize: tx.virtualSize(), + weight: tx.weight(), + locktime: tx.locktime, + vin: [], + vout: [], + hex: txhex, + blockhash: '', + confirmations: 0, + time: 0, + blocktime: 0, + }; + + if (txhashHeightCache[ret.txid]) { + // got blockheight where this tx was confirmed + ret.confirmations = estimateCurrentBlockheight() - txhashHeightCache[ret.txid]; + if (ret.confirmations < 0) { + // ugly fix for when estimator lags behind + ret.confirmations = 1; + } + ret.time = calculateBlockTime(txhashHeightCache[ret.txid]); + ret.blocktime = calculateBlockTime(txhashHeightCache[ret.txid]); + } + + for (const inn of tx.ins) { + const txinwitness = []; + if (inn.witness[0]) txinwitness.push(uint8ArrayToHex(inn.witness[0])); + if (inn.witness[1]) txinwitness.push(uint8ArrayToHex(inn.witness[1])); + + ret.vin.push({ + txid: uint8ArrayToHex(new Uint8Array(inn.hash).reverse()), + vout: inn.index, + scriptSig: { hex: uint8ArrayToHex(inn.script), asm: '' }, + txinwitness, + sequence: inn.sequence, + }); + } + + let n = 0; + for (const out of tx.outs) { + const value = new BigNumber(out.value).dividedBy(100000000).toNumber(); + let address: false | string = false; + let type: false | string = false; + + // Lazy require to avoid the module-scope cycle described above. These + // modules are fully loaded by the time this function is actually invoked. + const { SegwitBech32Wallet } = require('../class/wallets/segwit-bech32-wallet') as { + SegwitBech32Wallet: typeof SegwitBech32WalletT; + }; + const { SegwitP2SHWallet } = require('../class/wallets/segwit-p2sh-wallet') as { + SegwitP2SHWallet: typeof SegwitP2SHWalletT; + }; + const { LegacyWallet } = require('../class/wallets/legacy-wallet') as { + LegacyWallet: typeof LegacyWalletT; + }; + const { TaprootWallet } = require('../class/wallets/taproot-wallet') as { + TaprootWallet: typeof TaprootWalletT; + }; + + if (SegwitBech32Wallet.scriptPubKeyToAddress(uint8ArrayToHex(out.script))) { + address = SegwitBech32Wallet.scriptPubKeyToAddress(uint8ArrayToHex(out.script)); + type = 'witness_v0_keyhash'; + } else if (SegwitP2SHWallet.scriptPubKeyToAddress(uint8ArrayToHex(out.script))) { + address = SegwitP2SHWallet.scriptPubKeyToAddress(uint8ArrayToHex(out.script)); + type = '???'; // TODO + } else if (LegacyWallet.scriptPubKeyToAddress(uint8ArrayToHex(out.script))) { + address = LegacyWallet.scriptPubKeyToAddress(uint8ArrayToHex(out.script)); + type = '???'; // TODO + } else { + address = TaprootWallet.scriptPubKeyToAddress(uint8ArrayToHex(out.script)); + type = 'witness_v1_taproot'; + } + + if (!address) { + throw new Error('Internal error: unable to decode address from output script'); + } + + ret.vout.push({ + value, + n, + scriptPubKey: { + asm: '', + hex: uint8ArrayToHex(out.script), + reqSigs: 1, // todo + type, + addresses: [address], + }, + }); + n++; + } + return ret; +} + +export const getTransactionsFullByAddress = async (address: string): Promise => { + const txs = await getTransactionsByAddress(address); + const ret: ElectrumTransaction[] = []; + for (const tx of txs) { + let full; + try { + full = await mainClient.blockchainTransaction_get(tx.tx_hash, true); + } catch (error: any) { + if (String(error?.message ?? error).startsWith('verbose transactions are currently unsupported')) { + // apparently, stupid esplora instead of returning txhex when it cant return verbose tx started + // throwing a proper exception. lets fetch txhex manually and decode on our end + const txhex = await mainClient.blockchainTransaction_get(tx.tx_hash, false); + full = txhexToElectrumTransaction(txhex); + } else { + // nope, its something else + throw new Error(String(error?.message ?? error)); + } + } + full.address = address; + for (const input of full.vin) { + // now we need to fetch previous TX where this VIN became an output, so we can see its amount + let prevTxForVin; + try { + prevTxForVin = await mainClient.blockchainTransaction_get(input.txid, true); + } catch (error: any) { + if (String(error?.message ?? error).startsWith('verbose transactions are currently unsupported')) { + // apparently, stupid esplora instead of returning txhex when it cant return verbose tx started + // throwing a proper exception. lets fetch txhex manually and decode on our end + const txhex = await mainClient.blockchainTransaction_get(input.txid, false); + prevTxForVin = txhexToElectrumTransaction(txhex); + } else { + // nope, its something else + throw new Error(String(error?.message ?? error)); + } + } + if (prevTxForVin && prevTxForVin.vout && prevTxForVin.vout[input.vout]) { + input.value = prevTxForVin.vout[input.vout].value; + // also, we extract destination address from prev output: + if (prevTxForVin.vout[input.vout].scriptPubKey && prevTxForVin.vout[input.vout].scriptPubKey.addresses) { + input.addresses = prevTxForVin.vout[input.vout].scriptPubKey.addresses; + } + // in bitcoin core 22.0.0+ they removed `.addresses` and replaced it with plain `.address`: + if (prevTxForVin.vout[input.vout]?.scriptPubKey?.address) { + input.addresses = [prevTxForVin.vout[input.vout].scriptPubKey.address]; + } + } + } + + for (const output of full.vout) { + if (output?.scriptPubKey && output.scriptPubKey.addresses) output.addresses = output.scriptPubKey.addresses; + // in bitcoin core 22.0.0+ they removed `.addresses` and replaced it with plain `.address`: + if (output?.scriptPubKey?.address) output.addresses = [output.scriptPubKey.address]; + } + full.inputs = full.vin; + full.outputs = full.vout; + delete full.vin; + delete full.vout; + delete full.hex; // compact + delete full.hash; // compact + ret.push(full); + } + + return ret; +}; + +type MultiGetBalanceResponse = { + balance: number; + unconfirmed_balance: number; + addresses: Record; +}; + +export const multiGetBalanceByAddress = async (addresses: string[], batchsize: number = 200): Promise => { + if (!mainClient) throw new Error('Electrum client is not connected'); + const ret = { + balance: 0, + unconfirmed_balance: 0, + addresses: {} as Record, + }; + + const chunks = splitIntoChunks(addresses, batchsize); + for (const chunk of chunks) { + const scripthashes = []; + const scripthash2addr: Record = {}; + for (const addr of chunk) { + const script = bitcoin.address.toOutputScript(addr); + const hash = bitcoinjs_crypto_sha256(script); + const reversedHash = uint8ArrayToHex(new Uint8Array(hash).reverse()); + scripthashes.push(reversedHash); + scripthash2addr[reversedHash] = addr; + } + + let balances = []; + + if (disableBatching) { + const promises = []; + const index2scripthash: Record = {}; + for (let promiseIndex = 0; promiseIndex < scripthashes.length; promiseIndex++) { + promises.push(mainClient.blockchainScripthash_getBalance(scripthashes[promiseIndex])); + index2scripthash[promiseIndex] = scripthashes[promiseIndex]; + } + const promiseResults = await Promise.all(promises); + for (let resultIndex = 0; resultIndex < promiseResults.length; resultIndex++) { + balances.push({ result: promiseResults[resultIndex], param: index2scripthash[resultIndex] }); + } + } else { + balances = await mainClient.blockchainScripthash_getBalanceBatch(scripthashes); + } + + for (const bal of balances) { + if (bal.error) console.warn('multiGetBalanceByAddress():', bal.error); + ret.balance += +bal.result.confirmed; + ret.unconfirmed_balance += +bal.result.unconfirmed; + ret.addresses[scripthash2addr[bal.param]] = bal.result; + } + } + + return ret; +}; + +export const multiGetUtxoByAddress = async function (addresses: string[], batchsize: number = 100): Promise> { + if (!mainClient) throw new Error('Electrum client is not connected'); + const ret: Record = {}; + + const chunks = splitIntoChunks(addresses, batchsize); + for (const chunk of chunks) { + const scripthashes = []; + const scripthash2addr: Record = {}; + for (const addr of chunk) { + const script = bitcoin.address.toOutputScript(addr); + const hash = bitcoinjs_crypto_sha256(script); + const reversedHash = uint8ArrayToHex(new Uint8Array(hash).reverse()); + scripthashes.push(reversedHash); + scripthash2addr[reversedHash] = addr; + } + + let results = []; + + if (disableBatching) { + // ElectrumPersonalServer doesnt support `blockchain.scripthash.listunspent` + // electrs OTOH supports it, but we dont know it we are currently connected to it or to EPS + // so it is pretty safe to do nothing, as caller can derive UTXO from stored transactions + } else { + results = await mainClient.blockchainScripthash_listunspentBatch(scripthashes); + } + + for (const utxos of results) { + ret[scripthash2addr[utxos.param]] = utxos.result; + for (const utxo of ret[scripthash2addr[utxos.param]]) { + utxo.address = scripthash2addr[utxos.param]; + utxo.txid = utxo.tx_hash; + utxo.vout = utxo.tx_pos; + delete utxo.tx_pos; + delete utxo.tx_hash; + } + } + } + + return ret; +}; + +export type ElectrumHistory = { + tx_hash: string; + height: number; + address: string; +}; + +export const multiGetHistoryByAddress = async function ( + addresses: string[], + batchsize: number = 100, +): Promise> { + if (!mainClient) throw new Error('Electrum client is not connected'); + const ret: Record = {}; + + const chunks = splitIntoChunks(addresses, batchsize); + for (const chunk of chunks) { + const scripthashes = []; + const scripthash2addr: Record = {}; + for (const addr of chunk) { + const script = bitcoin.address.toOutputScript(addr); + const hash = bitcoinjs_crypto_sha256(script); + const reversedHash = uint8ArrayToHex(new Uint8Array(hash).reverse()); + scripthashes.push(reversedHash); + scripthash2addr[reversedHash] = addr; + } + + let results = []; + + if (disableBatching) { + const promises = []; + const index2scripthash: Record = {}; + for (let promiseIndex = 0; promiseIndex < scripthashes.length; promiseIndex++) { + index2scripthash[promiseIndex] = scripthashes[promiseIndex]; + promises.push(mainClient.blockchainScripthash_getHistory(scripthashes[promiseIndex])); + } + const histories = await Promise.all(promises); + for (let historyIndex = 0; historyIndex < histories.length; historyIndex++) { + results.push({ result: histories[historyIndex], param: index2scripthash[historyIndex] }); + } + } else { + results = await mainClient.blockchainScripthash_getHistoryBatch(scripthashes); + } + + for (const history of results) { + if (history.error) console.warn('multiGetHistoryByAddress():', history.error); + ret[scripthash2addr[history.param]] = history.result || []; + for (const result of history.result || []) { + if (result.tx_hash) txhashHeightCache[result.tx_hash] = result.height; // cache tx height + } + + for (const hist of ret[scripthash2addr[history.param]]) { + hist.address = scripthash2addr[history.param]; + } + } + } + + return ret; +}; + +// if verbose === true ? Record : Record +type MultiGetTransactionByTxidResult = T extends true ? Record : Record; + +// TODO: this function returns different results based on the value of `verboseParam`, consider splitting it into two +export async function multiGetTransactionByTxid( + txids: string[], + verbose: T, + batchsize: number = 45, +): Promise> { + txids = txids.filter(txid => !!txid); // failsafe: removing 'undefined' or other falsy stuff from txids array + // this value is fine-tuned so althrough wallets in test suite will occasionally + // throw 'response too large (over 1,000,000 bytes', test suite will pass + if (!mainClient) throw new Error('Electrum client is not connected'); + const ret: MultiGetTransactionByTxidResult = {}; + txids = [...new Set(txids)]; // deduplicate just for any case + + // lets try cache first: + const realm = await _getRealm(); + const cacheKeySuffix = verbose ? '_verbose' : '_non_verbose'; + const keysCacheMiss = []; + for (const txid of txids) { + const jsonString = realm.objectForPrimaryKey('Cache', txid + cacheKeySuffix); // search for a realm object with a primary key + if (jsonString && jsonString.cache_value) { + try { + ret[txid] = JSON.parse(jsonString.cache_value as string); + } catch (error) { + console.log(error, 'cache failed to parse', jsonString.cache_value); + } + } + + if (!ret[txid]) keysCacheMiss.push(txid); + } + + if (keysCacheMiss.length === 0) { + return ret; + } + + txids = keysCacheMiss; + // end cache + + const chunks = splitIntoChunks(txids, batchsize); + for (const chunk of chunks) { + let results = []; + + if (disableBatching) { + try { + // in case of ElectrumPersonalServer it might not track some transactions (like source transactions for our transactions) + // so we wrap it in try-catch. note, when `Promise.all` fails we will get _zero_ results, but we have a fallback for that + const promises = []; + const index2txid: Record = {}; + for (let promiseIndex = 0; promiseIndex < chunk.length; promiseIndex++) { + const txid = chunk[promiseIndex]; + index2txid[promiseIndex] = txid; + promises.push(mainClient.blockchainTransaction_get(txid, verbose)); + } + + const transactionResults = await Promise.all(promises); + for (let resultIndex = 0; resultIndex < transactionResults.length; resultIndex++) { + let tx = transactionResults[resultIndex]; + if (typeof tx === 'string' && verbose) { + // apparently electrum server (EPS?) didnt recognize VERBOSE parameter, and sent us plain txhex instead of decoded tx. + // lets decode it manually on our end then: + tx = txhexToElectrumTransaction(tx); + } + const txid = index2txid[resultIndex]; + results.push({ result: tx, param: txid }); + } + } catch (error: any) { + if (String(error?.message ?? error).startsWith('verbose transactions are currently unsupported')) { + // electrs-esplora. cant use verbose, so fetching txs one by one and decoding locally + for (const txid of chunk) { + try { + let tx = await mainClient.blockchainTransaction_get(txid, false); + tx = txhexToElectrumTransaction(tx); + results.push({ result: tx, param: txid }); + } catch (err) { + console.log(err); + } + } + } else { + // fallback. pretty sure we are connected to EPS. we try getting transactions one-by-one. this way we wont + // fail and only non-tracked by EPS transactions will be omitted + for (const txid of chunk) { + try { + let tx = await mainClient.blockchainTransaction_get(txid, verbose); + if (typeof tx === 'string' && verbose) { + // apparently electrum server (EPS?) didnt recognize VERBOSE parameter, and sent us plain txhex instead of decoded tx. + // lets decode it manually on our end then: + tx = txhexToElectrumTransaction(tx); + } + results.push({ result: tx, param: txid }); + } catch (err) { + console.log(err); + } + } + } + } + } else { + results = await mainClient.blockchainTransaction_getBatch(chunk, verbose); + } + + for (const txdata of results) { + if (txdata.error && txdata.error.code === -32600) { + // response too large + // lets do single call, that should go through okay: + txdata.result = await mainClient.blockchainTransaction_get(txdata.param, false); + // since we used VERBOSE=false, server sent us plain txhex which we must decode on our end: + txdata.result = txhexToElectrumTransaction(txdata.result); + } + ret[txdata.param] = txdata.result; + // @ts-ignore: hex property + if (ret[txdata.param]) delete ret[txdata.param].hex; // compact + } + } + + // in bitcoin core 22.0.0+ they removed `.addresses` and replaced it with plain `.address`: + for (const txid of Object.keys(ret)) { + const tx = ret[txid]; + if (typeof tx === 'string') continue; + for (const vout of tx?.vout ?? []) { + // @ts-ignore: address is not in type definition + if (vout?.scriptPubKey?.address) vout.scriptPubKey.addresses = [vout.scriptPubKey.address]; + } + } + + // saving cache: + try { + realm.write(() => { + for (const txid of Object.keys(ret)) { + const tx = ret[txid]; + // dont cache immature txs, but only for 'verbose', since its fully decoded tx jsons. non-verbose are just plain + // strings txhex + if (verbose && typeof tx !== 'string' && (!tx?.confirmations || tx.confirmations < 7)) { + continue; + } + + realm.create( + 'Cache', + { + cache_key: txid + cacheKeySuffix, + cache_value: JSON.stringify(ret[txid]), + }, + Realm.UpdateMode.Modified, + ); + } + }); + } catch (writeError) { + console.error('Failed to write transaction cache:', writeError); + } + + return ret; +} + +export const waitTillConnected = async function (): Promise { + 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) { + if (arr.length === 0) return 0; + if (typeof p !== 'number') throw new TypeError('p must be a number'); + if (p <= 0) return arr[0]; + if (p >= 1) return arr[arr.length - 1]; + + const index = (arr.length - 1) * p; + const lower = Math.floor(index); + const upper = lower + 1; + const weight = index % 1; + + if (upper >= arr.length) return arr[lower]; + return arr[lower] * (1 - weight) + arr[upper] * weight; +} + +/** + * The histogram is an array of [fee, vsize] pairs, where vsizen is the cumulative virtual size of mempool transactions + * with a fee rate in the interval [feen-1, feen], and feen-1 > feen. + */ +export const calcEstimateFeeFromFeeHistorgam = function (numberOfBlocks: number, feeHistorgram: number[][]) { + // first, transforming histogram: + let totalVsize = 0; + const histogramToUse = []; + for (const h of feeHistorgram) { + let [fee, vsize] = h; + let timeToStop = false; + + if (totalVsize + vsize >= 1000000 * numberOfBlocks) { + vsize = 1000000 * numberOfBlocks - totalVsize; // only the difference between current summarized sige to tip of the block + timeToStop = true; + } + + histogramToUse.push({ fee, vsize }); + totalVsize += vsize; + if (timeToStop) break; + } + + // now we have histogram of precisely size for numberOfBlocks. + // lets spread it into flat array so its easier to calculate percentile: + let histogramFlat: number[] = []; + for (const hh of histogramToUse) { + histogramFlat = histogramFlat.concat(Array(Math.round(hh.vsize / 25000)).fill(hh.fee)); + // division is needed so resulting flat array is not too huge + } + + histogramFlat = histogramFlat.sort(function (a, b) { + return a - b; + }); + + return Math.round(percentile(histogramFlat, 0.5) || 1); +}; + +export const estimateFees = async function (): Promise<{ fast: number; medium: number; slow: number }> { + let histogram; + let timeoutId; + try { + histogram = await Promise.race([ + mainClient.mempool_getFeeHistogram(), + new Promise(resolve => (timeoutId = setTimeout(resolve, 15000))), + ]); + } finally { + clearTimeout(timeoutId); + } + + // fetching what electrum (which uses bitcoin core) thinks about fees: + const _fast = await estimateFee(1); + const _medium = await estimateFee(18); + const _slow = await estimateFee(144); + + /** + * sanity check, see + * @see https://github.com/cculianu/Fulcrum/issues/197 + * (fallback to bitcoin core estimates) + */ + if (!histogram || histogram?.[0]?.[0] > 1000) return { fast: _fast, medium: _medium, slow: _slow }; + + // calculating fast fees from mempool: + const fast = Math.max(2, calcEstimateFeeFromFeeHistorgam(1, histogram)); + // recalculating medium and slow fees using bitcoincore estimations only like relative weights: + // (minimum 1 sat, just for any case) + const medium = Math.max(1, Math.round((fast * _medium) / _fast)); + const slow = Math.max(1, Math.round((fast * _slow) / _fast)); + return { fast, medium, slow }; +}; + +/** + * Returns the estimated transaction fee to be confirmed within a certain number of blocks + * + * @param numberOfBlocks {number} The number of blocks to target for confirmation + * @returns {Promise} Satoshis per byte + */ +export const estimateFee = async function (numberOfBlocks: number): Promise { + if (!mainClient) throw new Error('Electrum client is not connected'); + numberOfBlocks = numberOfBlocks || 1; + const coinUnitsPerKilobyte = await mainClient.blockchainEstimatefee(numberOfBlocks); + if (coinUnitsPerKilobyte === -1) return 1; + return Math.round(new BigNumber(coinUnitsPerKilobyte).dividedBy(1024).multipliedBy(100000000).toNumber()); +}; + +export const serverFeatures = async function () { + if (!mainClient) throw new Error('Electrum client is not connected'); + return mainClient.server_features(); +}; + +export const broadcast = async function (hex: string) { + if (!mainClient) throw new Error('Electrum client is not connected'); + try { + const res = await mainClient.blockchainTransaction_broadcast(hex); + return res; + } catch (error) { + return error; + } +}; + +export const broadcastV2 = async function (hex: string): Promise { + if (!mainClient) throw new Error('Electrum client is not connected'); + return mainClient.blockchainTransaction_broadcast(hex); +}; + +export const estimateCurrentBlockheight = function (): number { + if (latestBlock.height) { + const timeDiff = Math.floor(+new Date() / 1000) - latestBlock.time; + const extraBlocks = Math.floor(timeDiff / (9.93 * 60)); + return latestBlock.height + extraBlocks; + } + + const baseTs = 1587570465609; // uS + const baseHeight = 627179; + return Math.floor(baseHeight + (+new Date() - baseTs) / 1000 / 60 / 9.93); +}; + +export const calculateBlockTime = function (height: number): number { + if (latestBlock.height) { + return Math.floor(latestBlock.time + (height - latestBlock.height) * 9.93 * 60); + } + + const baseTs = 1585837504; // sec + const baseHeight = 624083; + return Math.floor(baseTs + (height - baseHeight) * 9.93 * 60); +}; + +/** + * @returns {Promise} Whether provided host:port is a valid electrum server + */ +export const testConnection = async function (host: string, tcpPort?: number, sslPort?: number): Promise { + const client = new ElectrumClient(net, tls, sslPort || tcpPort, host, sslPort ? 'tls' : 'tcp'); + + client.onError = () => {}; // mute + let timeoutId: NodeJS.Timeout | undefined; + try { + const rez = await Promise.race([ + new Promise(resolve => { + timeoutId = setTimeout(() => resolve('timeout'), 5000); + }), + client.connect(), + ]); + if (rez === 'timeout') return false; + + await client.server_version('2.7.11', '1.4'); + await client.server_ping(); + return true; + } catch (_) { + } finally { + if (timeoutId) clearTimeout(timeoutId); + client.close(); + } + + return false; +}; + +export const forceDisconnect = (): void => { + mainClient?.close(); +}; + +export const setBatchingDisabled = () => { + disableBatching = true; +}; + +export const setBatchingEnabled = () => { + disableBatching = false; +}; + +export function getServerBanner(): Promise { + return mainClient.request('server.banner', []); +} + +const splitIntoChunks = function (arr: any[], chunkSize: number) { + const groups = []; + let i; + for (i = 0; i < arr.length; i += chunkSize) { + groups.push(arr.slice(i, i + chunkSize)); + } + return groups; +}; + +const semVerToInt = function (semver: string): number { + if (!semver) return 0; + if (semver.split('.').length !== 3) return 0; + + const ret = Number(semver.split('.')[0]) * 1000000 + Number(semver.split('.')[1]) * 1000 + Number(semver.split('.')[2]) * 1; + + if (isNaN(ret)) return 0; + + return ret; +}; diff --git a/blue_modules/NativeEventEmitter.ts b/blue_modules/NativeEventEmitter.ts new file mode 100644 index 00000000000..e59419807fc --- /dev/null +++ b/blue_modules/NativeEventEmitter.ts @@ -0,0 +1 @@ +export { default, type Spec } from '../codegen/NativeEventEmitter'; diff --git a/blue_modules/NativeMenuElementsEmitter.ts b/blue_modules/NativeMenuElementsEmitter.ts new file mode 100644 index 00000000000..7ae417749d0 --- /dev/null +++ b/blue_modules/NativeMenuElementsEmitter.ts @@ -0,0 +1 @@ +export { default, type Spec } from '../codegen/NativeMenuElementsEmitter'; diff --git a/blue_modules/NativeWidgetHelper.ts b/blue_modules/NativeWidgetHelper.ts new file mode 100644 index 00000000000..d621b17ab11 --- /dev/null +++ b/blue_modules/NativeWidgetHelper.ts @@ -0,0 +1 @@ +export { default, type Spec } from '../codegen/NativeWidgetHelper'; diff --git a/blue_modules/Privacy.android.tsx b/blue_modules/Privacy.android.tsx deleted file mode 100644 index 07c54680d94..00000000000 --- a/blue_modules/Privacy.android.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useContext, useEffect } from 'react'; -// @ts-ignore: react-native-obscure is not in the type definition -import Obscure from 'react-native-obscure'; -import { BlueStorageContext } from './storage-context'; -interface PrivacyComponent extends React.FC { - enableBlur: (isPrivacyBlurEnabled: boolean) => void; - disableBlur: () => void; -} - -const Privacy: PrivacyComponent = () => { - const { isPrivacyBlurEnabled } = useContext(BlueStorageContext); - - useEffect(() => { - Obscure.deactivateObscure(); - }, [isPrivacyBlurEnabled]); - - return null; -}; - -Privacy.enableBlur = (isPrivacyBlurEnabled: boolean) => { - if (!isPrivacyBlurEnabled) return; - Obscure.activateObscure(); -}; - -Privacy.disableBlur = () => { - Obscure.deactivateObscure(); -}; - -export default Privacy; diff --git a/blue_modules/Privacy.ios.tsx b/blue_modules/Privacy.ios.tsx deleted file mode 100644 index 877a3131b46..00000000000 --- a/blue_modules/Privacy.ios.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useContext, useEffect } from 'react'; -// @ts-ignore: react-native-obscure is not in the type definition -import { enabled } from 'react-native-privacy-snapshot'; -import { BlueStorageContext } from './storage-context'; - -interface PrivacyComponent extends React.FC { - enableBlur: (isPrivacyBlurEnabled: boolean) => void; - disableBlur: () => void; -} - -const Privacy: PrivacyComponent = () => { - const { isPrivacyBlurEnabled } = useContext(BlueStorageContext); - - useEffect(() => { - Privacy.disableBlur(); - }, [isPrivacyBlurEnabled]); - - return null; -}; - -Privacy.enableBlur = (isPrivacyBlurEnabled: boolean) => { - if (!isPrivacyBlurEnabled) return; - enabled(true); -}; - -Privacy.disableBlur = () => { - enabled(false); -}; - -export default Privacy; diff --git a/blue_modules/Privacy.tsx b/blue_modules/Privacy.tsx deleted file mode 100644 index 8feff432446..00000000000 --- a/blue_modules/Privacy.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; - -interface PrivacyComponent extends React.FC { - enableBlur: () => void; - disableBlur: () => void; -} - -const Privacy: PrivacyComponent = () => { - // Define Privacy's behavior - return null; -}; - -Privacy.enableBlur = () => { - // Define the enableBlur behavior -}; - -Privacy.disableBlur = () => { - // Define the disableBlur behavior -}; - -export default Privacy; diff --git a/blue_modules/SettingsModule.ts b/blue_modules/SettingsModule.ts new file mode 100644 index 00000000000..0364e9affed --- /dev/null +++ b/blue_modules/SettingsModule.ts @@ -0,0 +1,53 @@ +import { Platform } from 'react-native'; +import NativeSettingsModule from '../codegen/NativeSettingsModule'; + +interface SettingsModuleInterface { + /** + * Initialize device UID if not exists + * Returns the device UID or "Disabled" if Do Not Track is enabled + */ + initializeDeviceUID(): Promise; + + /** + * Get the device UID + * Returns the device UID or "Disabled" if Do Not Track is enabled + */ + getDeviceUID(): Promise; + + /** + * Get the device UID copy (for Settings display) + */ + getDeviceUIDCopy(): Promise; + + /** + * Set the clearFilesOnLaunch preference + */ + setClearFilesOnLaunch(value: boolean): Promise; + + /** + * Get the clearFilesOnLaunch preference + */ + getClearFilesOnLaunch(): Promise; + + /** + * Set Do Not Track setting + */ + setDoNotTrack(enabled: boolean): Promise; + + /** + * Get Do Not Track setting + */ + getDoNotTrack(): Promise; + + /** + * Open the settings activity (Android only) + * This opens the app's settings screen + */ + openSettings(): Promise; +} + +// Only available on Android +const nativeModule = NativeSettingsModule ?? null; +const SettingsModule: SettingsModuleInterface | null = Platform.OS === 'android' ? nativeModule : null; + +export default SettingsModule; diff --git a/blue_modules/Views/SegmentedControl/android/io/bluewallet/bluewallet/components/segmentedcontrol/SegmentedControl.kt b/blue_modules/Views/SegmentedControl/android/io/bluewallet/bluewallet/components/segmentedcontrol/SegmentedControl.kt new file mode 100644 index 00000000000..eb9ddc2521c --- /dev/null +++ b/blue_modules/Views/SegmentedControl/android/io/bluewallet/bluewallet/components/segmentedcontrol/SegmentedControl.kt @@ -0,0 +1,263 @@ +package io.bluewallet.bluewallet.components.segmentedcontrol + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Color +import android.util.AttributeSet +import android.widget.LinearLayout +import androidx.core.content.ContextCompat +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.UIManagerHelper +import com.facebook.react.uimanager.events.Event +import com.google.android.material.button.MaterialButton +import com.google.android.material.button.MaterialButtonToggleGroup +import io.bluewallet.bluewallet.R + +class SegmentedControl @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr) { + + private val toggleGroup: MaterialButtonToggleGroup + private var currentSelectedIndex: Int = 0 + private var backgroundColorProp: Int? = null + private var tintColorProp: Int? = null + private var textColorProp: Int? = null + private var momentaryProp: Boolean = false + private var isEnabledProp: Boolean = true + + var values: Array = emptyArray() + set(value) { + field = value + updateSegments() + } + + var selectedIndex: Int = 0 + set(value) { + field = value + currentSelectedIndex = value + updateSelectedSegment() + } + + init { + orientation = HORIZONTAL + toggleGroup = MaterialButtonToggleGroup(context).apply { + isSingleSelection = true + isSelectionRequired = true + } + addView( + toggleGroup, + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + ), + ) + + toggleGroup.addOnButtonCheckedListener { _, checkedId, isChecked -> + if (isChecked) { + val newIndex = findIndexById(checkedId) + if (newIndex != -1 && newIndex != currentSelectedIndex) { + currentSelectedIndex = newIndex + emitChangeEvent(newIndex) + if (momentaryProp) { + toggleGroup.clearChecked() + } + } + } + } + } + + private fun updateSegments() { + toggleGroup.removeAllViews() + + values.forEachIndexed { index, title -> + val button = MaterialButton( + context, + null, + com.google.android.material.R.attr.materialButtonOutlinedStyle, + ).apply { + text = title + id = generateViewId() + layoutParams = LinearLayout.LayoutParams( + 0, + LinearLayout.LayoutParams.WRAP_CONTENT, + 1f, + ) + isCheckable = true + + strokeWidth = 2 + applyEnabledState() + + val cornerRadius = resources.getDimensionPixelSize( + com.google.android.material.R.dimen.mtrl_btn_corner_radius, + ) + + when { + values.size == 1 -> { + this.cornerRadius = cornerRadius + } + index == 0 -> { + this.cornerRadius = cornerRadius + } + index == values.size - 1 -> { + this.cornerRadius = cornerRadius + } + else -> { + this.cornerRadius = 0 + } + } + } + + toggleGroup.addView(button) + } + + updateButtonColors() + updateSelectedSegment() + } + + private fun updateButtonColors() { + for (i in 0 until toggleGroup.childCount) { + val button = toggleGroup.getChildAt(i) as? MaterialButton ?: continue + + val selectedBgColor = tintColorProp ?: ContextCompat.getColor(context, R.color.button_background_color) + val unselectedBgColor = backgroundColorProp ?: ContextCompat.getColor(context, R.color.button_disabled_background_color) + val resolvedTextColor = textColorProp ?: ContextCompat.getColor(context, R.color.button_text_color) + val selectedTextColor = resolvedTextColor + val unselectedTextColor = textColorProp ?: ContextCompat.getColor(context, R.color.button_disabled_text_color) + val borderColor = ContextCompat.getColor(context, R.color.form_border_color) + val rippleColor = ContextCompat.getColor(context, R.color.ripple_color) + val rippleColorSelected = ContextCompat.getColor(context, R.color.ripple_color_selected) + + val bgColorStateList = ColorStateList( + arrayOf( + intArrayOf(android.R.attr.state_checked), + intArrayOf(-android.R.attr.state_checked), + ), + intArrayOf(selectedBgColor, unselectedBgColor), + ) + + val textColorStateList = ColorStateList( + arrayOf( + intArrayOf(android.R.attr.state_checked), + intArrayOf(-android.R.attr.state_checked), + ), + intArrayOf(selectedTextColor, unselectedTextColor), + ) + + val strokeColorStateList = ColorStateList( + arrayOf( + intArrayOf(android.R.attr.state_checked), + intArrayOf(-android.R.attr.state_checked), + ), + intArrayOf(borderColor, borderColor), + ) + + val rippleColorStateList = ColorStateList( + arrayOf( + intArrayOf(android.R.attr.state_checked), + intArrayOf(-android.R.attr.state_checked), + ), + intArrayOf(rippleColorSelected, rippleColor), + ) + + button.backgroundTintList = bgColorStateList + button.setTextColor(textColorStateList) + button.strokeColor = strokeColorStateList + button.rippleColor = rippleColorStateList + button.isEnabled = isEnabledProp + } + } + + fun setBackgroundColorProp(color: String?) { + backgroundColorProp = parseColor(color) + updateButtonColors() + } + + fun setTintColorProp(color: String?) { + tintColorProp = parseColor(color) + updateButtonColors() + } + + fun setTextColorProp(color: String?) { + textColorProp = parseColor(color) + updateButtonColors() + } + + fun setMomentaryProp(momentary: Boolean) { + momentaryProp = momentary + toggleGroup.isSelectionRequired = !momentary + } + + fun setEnabledProp(enabled: Boolean) { + isEnabledProp = enabled + toggleGroup.isEnabled = enabled + applyEnabledState() + } + + private fun updateSelectedSegment() { + if (values.isNotEmpty() && currentSelectedIndex in 0 until values.size) { + val buttonId = getButtonIdAtIndex(currentSelectedIndex) + if (buttonId != -1) { + toggleGroup.check(buttonId) + } + } + } + + private fun findIndexById(id: Int): Int { + for (i in 0 until toggleGroup.childCount) { + if (toggleGroup.getChildAt(i).id == id) { + return i + } + } + return -1 + } + + private fun getButtonIdAtIndex(index: Int): Int { + return if (index in 0 until toggleGroup.childCount) { + toggleGroup.getChildAt(index).id + } else { + -1 + } + } + + private fun emitChangeEvent(selectedIndex: Int) { + val reactContext = context as? ReactContext ?: return + val surfaceId = UIManagerHelper.getSurfaceId(reactContext) + val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id) + + val event = Arguments.createMap().apply { + putInt("selectedIndex", selectedIndex) + } + + eventDispatcher?.dispatchEvent(ChangeEvent(surfaceId, id, event)) + } + + private fun applyEnabledState() { + for (i in 0 until toggleGroup.childCount) { + val button = toggleGroup.getChildAt(i) as? MaterialButton ?: continue + button.isEnabled = isEnabledProp + } + } + + private fun parseColor(color: String?): Int? { + return try { + color?.let { Color.parseColor(it) } + } catch (_: IllegalArgumentException) { + null + } + } + + private inner class ChangeEvent( + surfaceId: Int, + viewId: Int, + private val eventData: WritableMap, + ) : Event(surfaceId, viewId) { + + override fun getEventName(): String = "topChange" + + override fun getEventData(): WritableMap = eventData + } +} \ No newline at end of file diff --git a/blue_modules/Views/SegmentedControl/android/io/bluewallet/bluewallet/components/segmentedcontrol/SegmentedControlManager.kt b/blue_modules/Views/SegmentedControl/android/io/bluewallet/bluewallet/components/segmentedcontrol/SegmentedControlManager.kt new file mode 100644 index 00000000000..6ee706bbb6e --- /dev/null +++ b/blue_modules/Views/SegmentedControl/android/io/bluewallet/bluewallet/components/segmentedcontrol/SegmentedControlManager.kt @@ -0,0 +1,67 @@ +package io.bluewallet.bluewallet.components.segmentedcontrol + +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.common.MapBuilder +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.SimpleViewManager +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.annotations.ReactProp + +@ReactModule(name = SegmentedControlManager.REACT_CLASS) +class SegmentedControlManager : SimpleViewManager() { + + companion object { + const val REACT_CLASS = "SegmentedControl" + } + + override fun getName(): String = REACT_CLASS + + override fun createViewInstance(reactContext: ThemedReactContext): SegmentedControl = + SegmentedControl(reactContext) + + @ReactProp(name = "values") + fun setValues(view: SegmentedControl, values: ReadableArray?) { + view.values = values?.let { arr -> Array(arr.size()) { arr.getString(it) ?: "" } } ?: emptyArray() + } + + @ReactProp(name = "selectedIndex", defaultInt = 0) + fun setSelectedIndex(view: SegmentedControl, selectedIndex: Int) { + view.selectedIndex = selectedIndex + } + + @ReactProp(name = "enabled", defaultBoolean = true) + fun setEnabled(view: SegmentedControl, enabled: Boolean) { + view.setEnabledProp(enabled) + } + + @ReactProp(name = "momentary", defaultBoolean = false) + fun setMomentary(view: SegmentedControl, momentary: Boolean) { + view.setMomentaryProp(momentary) + } + + @ReactProp(name = "backgroundColor") + fun setBackgroundColor(view: SegmentedControl, backgroundColor: String?) { + view.setBackgroundColorProp(backgroundColor) + } + + @ReactProp(name = "tintColor") + fun setTintColor(view: SegmentedControl, tintColor: String?) { + view.setTintColorProp(tintColor) + } + + @ReactProp(name = "textColor") + fun setTextColor(view: SegmentedControl, textColor: String?) { + view.setTextColorProp(textColor) + } + + override fun getExportedCustomBubblingEventTypeConstants(): Map? = + MapBuilder.builder() + .put( + "topChange", + MapBuilder.of( + "phasedRegistrationNames", + MapBuilder.of("bubbled", "onChange", "captured", "onChangeCapture"), + ), + ) + .build() +} \ No newline at end of file diff --git a/blue_modules/Views/SegmentedControl/android/io/bluewallet/bluewallet/components/segmentedcontrol/SegmentedControlPackage.kt b/blue_modules/Views/SegmentedControl/android/io/bluewallet/bluewallet/components/segmentedcontrol/SegmentedControlPackage.kt new file mode 100644 index 00000000000..78b2ce12f1a --- /dev/null +++ b/blue_modules/Views/SegmentedControl/android/io/bluewallet/bluewallet/components/segmentedcontrol/SegmentedControlPackage.kt @@ -0,0 +1,17 @@ +package io.bluewallet.bluewallet.components.segmentedcontrol + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class SegmentedControlPackage : ReactPackage { + + override fun createNativeModules(reactContext: ReactApplicationContext): List { + return emptyList() + } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return listOf(SegmentedControlManager()) + } +} \ No newline at end of file diff --git a/blue_modules/Views/SegmentedControl/ios/SegmentedControlBridge.m b/blue_modules/Views/SegmentedControl/ios/SegmentedControlBridge.m new file mode 100644 index 00000000000..5419c31733a --- /dev/null +++ b/blue_modules/Views/SegmentedControl/ios/SegmentedControlBridge.m @@ -0,0 +1,6 @@ +#import + +@interface RCT_EXTERN_MODULE(SegmentedControlManager, RCTViewManager) + +@end + diff --git a/blue_modules/Views/SegmentedControl/ios/SegmentedControlManager.swift b/blue_modules/Views/SegmentedControl/ios/SegmentedControlManager.swift new file mode 100644 index 00000000000..0d60d82bca8 --- /dev/null +++ b/blue_modules/Views/SegmentedControl/ios/SegmentedControlManager.swift @@ -0,0 +1,23 @@ +import Foundation +import UIKit +import React + +@objc(SegmentedControlManager) +final class SegmentedControlManager: RCTViewManager { + + override class func requiresMainQueueSetup() -> Bool { true } + + override func view() -> UIView! { + return SegmentedControlView() + } + + @objc class func propConfig_values() -> [String]! { ["NSArray"] } + @objc class func propConfig_selectedIndex() -> [String]! { ["NSInteger"] } + @objc class func propConfig_enabled() -> [String]! { ["BOOL"] } + @objc class func propConfig_momentary() -> [String]! { ["BOOL"] } + @objc class func propConfig_tintColor() -> [String]! { ["UIColor"] } + @objc class func propConfig_backgroundColor() -> [String]! { ["UIColor"] } + @objc class func propConfig_textColor() -> [String]! { ["UIColor"] } + @objc class func propConfig_onChange() -> [String]! { ["RCTBubblingEventBlock"] } +} + diff --git a/blue_modules/Views/SegmentedControl/ios/SegmentedControlView.swift b/blue_modules/Views/SegmentedControl/ios/SegmentedControlView.swift new file mode 100644 index 00000000000..3e8b6e2e9bb --- /dev/null +++ b/blue_modules/Views/SegmentedControl/ios/SegmentedControlView.swift @@ -0,0 +1,98 @@ +import UIKit +import React + +@objc(SegmentedControlView) +final class SegmentedControlView: UIView { + + private let segmentedControl = UISegmentedControl() + + // MARK: - Lifecycle + + override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setup() + } + + private func setup() { + segmentedControl.addTarget(self, action: #selector(handleValueChanged(_:)), for: .valueChanged) + addSubview(segmentedControl) + segmentedControl.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + segmentedControl.leadingAnchor.constraint(equalTo: leadingAnchor), + segmentedControl.trailingAnchor.constraint(equalTo: trailingAnchor), + segmentedControl.topAnchor.constraint(equalTo: topAnchor), + segmentedControl.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + } + + // MARK: - Prop setters + + @objc var values: NSArray = [] { + didSet { rebuildSegments() } + } + + @objc var selectedIndex: Int = 0 { + didSet { + guard segmentedControl.numberOfSegments > 0 else { return } + let clamped = min(max(selectedIndex, 0), segmentedControl.numberOfSegments - 1) + if segmentedControl.selectedSegmentIndex != clamped { + segmentedControl.selectedSegmentIndex = clamped + } + } + } + + @objc var enabled: Bool = true { + didSet { segmentedControl.isEnabled = enabled } + } + + @objc var momentary: Bool = false { + didSet { segmentedControl.isMomentary = momentary } + } + + @objc var textColor: UIColor? { + didSet { applyTextAttributes() } + } + + @objc var onChange: RCTBubblingEventBlock? + + override var tintColor: UIColor! { + didSet { segmentedControl.selectedSegmentTintColor = tintColor } + } + + override var backgroundColor: UIColor? { + didSet { segmentedControl.backgroundColor = backgroundColor } + } + + // MARK: - Private helpers + + private func rebuildSegments() { + let titles = values as? [String] ?? [] + segmentedControl.removeAllSegments() + for (i, title) in titles.enumerated() { + segmentedControl.insertSegment(withTitle: title, at: i, animated: false) + } + guard !titles.isEmpty else { return } + let clamped = min(max(selectedIndex, 0), titles.count - 1) + segmentedControl.selectedSegmentIndex = clamped + } + + private func applyTextAttributes() { + if let color = textColor { + segmentedControl.setTitleTextAttributes([.foregroundColor: color], for: .normal) + segmentedControl.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected) + } else { + segmentedControl.setTitleTextAttributes(nil, for: .normal) + segmentedControl.setTitleTextAttributes(nil, for: .selected) + } + } + + @objc private func handleValueChanged(_ sender: UISegmentedControl) { + onChange?(["selectedIndex": sender.selectedSegmentIndex]) + } +} + diff --git a/blue_modules/WidgetCommunication.ios.js b/blue_modules/WidgetCommunication.ios.js deleted file mode 100644 index e926ec9648e..00000000000 --- a/blue_modules/WidgetCommunication.ios.js +++ /dev/null @@ -1,79 +0,0 @@ -import { useContext, useEffect } from 'react'; -import { BlueStorageContext } from './storage-context'; -import DefaultPreference from 'react-native-default-preference'; -import RNWidgetCenter from 'react-native-widget-center'; -import AsyncStorage from '@react-native-async-storage/async-storage'; - -function WidgetCommunication() { - WidgetCommunication.WidgetCommunicationAllWalletsSatoshiBalance = 'WidgetCommunicationAllWalletsSatoshiBalance'; - WidgetCommunication.WidgetCommunicationAllWalletsLatestTransactionTime = 'WidgetCommunicationAllWalletsLatestTransactionTime'; - WidgetCommunication.WidgetCommunicationDisplayBalanceAllowed = 'WidgetCommunicationDisplayBalanceAllowed'; - WidgetCommunication.LatestTransactionIsUnconfirmed = 'WidgetCommunicationLatestTransactionIsUnconfirmed'; - const { wallets, walletsInitialized, isStorageEncrypted } = useContext(BlueStorageContext); - - WidgetCommunication.isBalanceDisplayAllowed = async () => { - try { - const displayBalance = JSON.parse(await AsyncStorage.getItem(WidgetCommunication.WidgetCommunicationDisplayBalanceAllowed)); - if (displayBalance !== null) { - return displayBalance; - } else { - return true; - } - } catch (e) { - return true; - } - }; - - WidgetCommunication.setBalanceDisplayAllowed = async value => { - await AsyncStorage.setItem(WidgetCommunication.WidgetCommunicationDisplayBalanceAllowed, JSON.stringify(value)); - setValues(); - }; - - WidgetCommunication.reloadAllTimelines = () => { - RNWidgetCenter.reloadAllTimelines(); - }; - - const allWalletsBalanceAndTransactionTime = async () => { - if ((await isStorageEncrypted()) || !(await WidgetCommunication.isBalanceDisplayAllowed())) { - return { allWalletsBalance: 0, latestTransactionTime: 0 }; - } else { - let balance = 0; - let latestTransactionTime = 0; - for (const wallet of wallets) { - if (wallet.hideBalance) { - continue; - } - balance += wallet.getBalance(); - if (wallet.getLatestTransactionTimeEpoch() > latestTransactionTime) { - if (wallet.getTransactions()[0].confirmations === 0) { - latestTransactionTime = WidgetCommunication.LatestTransactionIsUnconfirmed; - } else { - latestTransactionTime = wallet.getLatestTransactionTimeEpoch(); - } - } - } - return { allWalletsBalance: balance, latestTransactionTime }; - } - }; - const setValues = async () => { - await DefaultPreference.setName('group.io.bluewallet.bluewallet'); - const { allWalletsBalance, latestTransactionTime } = await allWalletsBalanceAndTransactionTime(); - await DefaultPreference.set(WidgetCommunication.WidgetCommunicationAllWalletsSatoshiBalance, JSON.stringify(allWalletsBalance)); - await DefaultPreference.set( - WidgetCommunication.WidgetCommunicationAllWalletsLatestTransactionTime, - JSON.stringify(latestTransactionTime), - ); - RNWidgetCenter.reloadAllTimelines(); - }; - - useEffect(() => { - if (walletsInitialized) { - setValues(); - } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [wallets, walletsInitialized]); - return null; -} - -export default WidgetCommunication; diff --git a/blue_modules/WidgetCommunication.js b/blue_modules/WidgetCommunication.js deleted file mode 100644 index 05cba00d80f..00000000000 --- a/blue_modules/WidgetCommunication.js +++ /dev/null @@ -1,8 +0,0 @@ -function WidgetCommunication(props) { - WidgetCommunication.isBalanceDisplayAllowed = () => {}; - WidgetCommunication.setBalanceDisplayAllowed = () => {}; - WidgetCommunication.reloadAllTimelines = () => {}; - return null; -} - -export default WidgetCommunication; diff --git a/blue_modules/aezeed/README.md b/blue_modules/aezeed/README.md deleted file mode 100644 index e8a32cf3cbb..00000000000 --- a/blue_modules/aezeed/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# aezeed -A package for encoding, decoding, and generating mnemonics of the aezeed specification. (WIP) diff --git a/blue_modules/aezeed/package.json b/blue_modules/aezeed/package.json deleted file mode 100644 index d92abf6c23b..00000000000 --- a/blue_modules/aezeed/package.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "_from": "aezeed", - "_id": "aezeed@0.0.4", - "_inBundle": false, - "_integrity": "sha512-KAv2y2AtbqpdtsabCLE+C0G0h4BZLeMHsLCRga3VicYLxD17RflUBJ++c5qdpN6B6fkvK90r6bWg52Z/gMC7gQ==", - "_location": "/aezeed", - "_phantomChildren": {}, - "_requested": { - "type": "tag", - "registry": true, - "raw": "aezeed", - "name": "aezeed", - "escapedName": "aezeed", - "rawSpec": "", - "saveSpec": null, - "fetchSpec": "latest" - }, - "_requiredBy": [ - "#USER", - "/" - ], - "_resolved": "https://registry.npmjs.org/aezeed/-/aezeed-0.0.4.tgz", - "_shasum": "8fce8778d34f5566328f61df7706351cb15873a9", - "_spec": "aezeed", - "_where": "/home/overtorment/Documents/BlueWallet", - "author": { - "name": "Jonathan Underwood" - }, - "bugs": { - "url": "https://github.com/bitcoinjs/aezeed/issues" - }, - "bundleDependencies": false, - "dependencies": { - "aez": "^1.0.1", - "crc-32": "npm:junderw-crc32c@^1.2.0", - "randombytes": "^2.1.0", - "scryptsy": "^2.1.0" - }, - "deprecated": false, - "description": "A package for encoding, decoding, and generating mnemonics of the aezeed specification.", - "devDependencies": { - "@types/jest": "^26.0.10", - "@types/node": "^16.0.0", - "@types/randombytes": "^2.0.0", - "@types/scryptsy": "^2.0.0", - "jest": "^26.4.2", - "prettier": "^2.1.0", - "ts-jest": "^26.2.0", - "tslint": "^6.1.3", - "typescript": "^4.0.2" - }, - "files": [ - "src" - ], - "homepage": "https://github.com/bitcoinjs/aezeed#readme", - "keywords": [ - "aezeed", - "bitcoin", - "lightning", - "lnd" - ], - "license": "MIT", - "main": "src/cipherseed.js", - "name": "aezeed", - "repository": { - "type": "git", - "url": "git+https://github.com/bitcoinjs/aezeed.git" - }, - "scripts": { - "build": "npm run clean && tsc -p tsconfig.json", - "clean": "rm -rf src", - "coverage": "npm run unit -- --coverage", - "format": "npm run prettier -- --write", - "format:ci": "npm run prettier -- --check", - "gitdiff": "git diff --exit-code", - "gitdiff:ci": "npm run build && npm run gitdiff", - "lint": "tslint -p tsconfig.json -c tslint.json", - "prepublishOnly": "npm run test && npm run gitdiff", - "prettier": "prettier 'ts_src/**/*.ts' --single-quote --trailing-comma=all --ignore-path ./.prettierignore", - "test": "npm run build && npm run format:ci && npm run lint && npm run unit", - "unit": "jest --config=jest.json --runInBand" - }, - "types": "src/cipherseed.d.ts", - "version": "0.0.4" -} diff --git a/blue_modules/aezeed/src/cipherseed.d.ts b/blue_modules/aezeed/src/cipherseed.d.ts deleted file mode 100644 index c664c3beb93..00000000000 --- a/blue_modules/aezeed/src/cipherseed.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/// -export declare class CipherSeed { - entropy: Buffer; - salt: Buffer; - internalVersion: number; - birthday: number; - private static decipher; - static fromMnemonic(mnemonic: string, password?: string): CipherSeed; - static random(): CipherSeed; - static changePassword(mnemonic: string, oldPassword: string | null, newPassword: string): string; - constructor(entropy: Buffer, salt: Buffer, internalVersion?: number, birthday?: number); - get birthDate(): Date; - toMnemonic(password?: string, cipherSeedVersion?: number): string; - private encipher; -} diff --git a/blue_modules/aezeed/src/cipherseed.js b/blue_modules/aezeed/src/cipherseed.js deleted file mode 100644 index 586972dffc0..00000000000 --- a/blue_modules/aezeed/src/cipherseed.js +++ /dev/null @@ -1,105 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.CipherSeed = void 0; -const BlueCrypto = require('react-native-blue-crypto'); -const scrypt = require("scryptsy"); -const rng = require("randombytes"); -const mn = require("./mnemonic"); -const params_1 = require("./params"); -const aez = require('aez'); -const crc = require('junderw-crc32c'); -const BITCOIN_GENESIS = new Date('2009-01-03T18:15:05.000Z').getTime(); -const daysSinceGenesis = (time) => Math.floor((time.getTime() - BITCOIN_GENESIS) / params_1.ONE_DAY); - -async function scryptWrapper(secret, salt, N, r, p, dkLen, progressCallback) { - if (BlueCrypto.isAvailable()) { - secret = Buffer.from(secret).toString('hex'); - salt = Buffer.from(salt).toString('hex'); - const hex = await BlueCrypto.scrypt(secret, salt, N, r, p, dkLen); - return Buffer.from(hex, 'hex'); - } else { - // fallback to js implementation - return scrypt(secret, salt, N, r, p, dkLen, progressCallback); - } -} - -class CipherSeed { - constructor(entropy, salt, internalVersion = 0, birthday = daysSinceGenesis(new Date())) { - this.entropy = entropy; - this.salt = salt; - this.internalVersion = internalVersion; - this.birthday = birthday; - if (entropy && entropy.length !== 16) - throw new Error('incorrect entropy length'); - if (salt && salt.length !== 5) - throw new Error('incorrect salt length'); - } - - static async decipher(cipherBuf, password) { - if (cipherBuf[0] >= params_1.PARAMS.length) { - throw new Error('Invalid cipherSeedVersion'); - } - const cipherSeedVersion = cipherBuf[0]; - const params = params_1.PARAMS[cipherSeedVersion]; - const checksum = Buffer.allocUnsafe(4); - const checksumNum = crc.buf(cipherBuf.slice(0, 29)); - checksum.writeInt32BE(checksumNum); - if (!checksum.equals(cipherBuf.slice(29))) { - throw new Error('CRC checksum mismatch'); - } - const salt = cipherBuf.slice(24, 29); - const key = await scryptWrapper(Buffer.from(password, 'utf8'), salt, params.n, params.r, params.p, 32); - const adBytes = Buffer.allocUnsafe(6); - adBytes.writeUInt8(cipherSeedVersion, 0); - salt.copy(adBytes, 1); - const plainText = aez.decrypt(key, null, [adBytes], 4, cipherBuf.slice(1, 24)); - if (plainText === null) - throw new Error('Invalid Password'); - return new CipherSeed(plainText.slice(3, 19), salt, plainText[0], plainText.readUInt16BE(1)); - } - - static async fromMnemonic(mnemonic, password = params_1.DEFAULT_PASSWORD) { - const bytes = mn.mnemonicToBytes(mnemonic); - return await CipherSeed.decipher(bytes, password); - } - - static random() { - return new CipherSeed(rng(16), rng(5)); - } - - static async changePassword(mnemonic, oldPassword, newPassword) { - const pwd = oldPassword === null ? params_1.DEFAULT_PASSWORD : oldPassword; - const cs = await CipherSeed.fromMnemonic(mnemonic, pwd); - return await cs.toMnemonic(newPassword); - } - - get birthDate() { - return new Date(BITCOIN_GENESIS + this.birthday * params_1.ONE_DAY); - } - - async toMnemonic(password = params_1.DEFAULT_PASSWORD, cipherSeedVersion = params_1.CIPHER_SEED_VERSION) { - return mn.mnemonicFromBytes(await this.encipher(password, cipherSeedVersion)); - } - - async encipher(password, cipherSeedVersion) { - const pwBuf = Buffer.from(password, 'utf8'); - const params = params_1.PARAMS[cipherSeedVersion]; - const key = await scryptWrapper(pwBuf, this.salt, params.n, params.r, params.p, 32); - const seedBytes = Buffer.allocUnsafe(19); - seedBytes.writeUInt8(this.internalVersion, 0); - seedBytes.writeUInt16BE(this.birthday, 1); - this.entropy.copy(seedBytes, 3); - const adBytes = Buffer.allocUnsafe(6); - adBytes.writeUInt8(cipherSeedVersion, 0); - this.salt.copy(adBytes, 1); - const cipherText = aez.encrypt(key, null, [adBytes], 4, seedBytes); - const cipherSeedBytes = Buffer.allocUnsafe(33); - cipherSeedBytes.writeUInt8(cipherSeedVersion, 0); - cipherText.copy(cipherSeedBytes, 1); - this.salt.copy(cipherSeedBytes, 24); - const checksumNum = crc.buf(cipherSeedBytes.slice(0, 29)); - cipherSeedBytes.writeInt32BE(checksumNum, 29); - return cipherSeedBytes; - } -} -exports.CipherSeed = CipherSeed; diff --git a/blue_modules/aezeed/src/mnemonic.d.ts b/blue_modules/aezeed/src/mnemonic.d.ts deleted file mode 100644 index 78a0a7d982b..00000000000 --- a/blue_modules/aezeed/src/mnemonic.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -/// -export declare function mnemonicFromBytes(bytes: Buffer): string; -export declare function mnemonicToBytes(mnemonic: string): Buffer; diff --git a/blue_modules/aezeed/src/mnemonic.js b/blue_modules/aezeed/src/mnemonic.js deleted file mode 100644 index e0415bb3f6a..00000000000 --- a/blue_modules/aezeed/src/mnemonic.js +++ /dev/null @@ -1,2091 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.mnemonicToBytes = exports.mnemonicFromBytes = void 0; -function mnemonicFromBytes(bytes) { - const bits = bytesToBinary(Array.from(bytes)); - const chunks = bits.match(/(.{1,11})/g); - const words = chunks.map((binary) => { - const index = binaryToByte(binary); - return WORDLIST[index]; - }); - return words.join(' '); -} -exports.mnemonicFromBytes = mnemonicFromBytes; -function mnemonicToBytes(mnemonic) { - const INVALID = 'Invalid Mnemonic'; - const words = mnemonic.split(' '); - if (words.length !== 24) - throw new Error(INVALID); - const bits = words - .map((word) => { - const index = WORDLIST.indexOf(word); - if (index === -1) - throw new Error(INVALID); - return lpad(index.toString(2), '0', 11); - }) - .join(''); - const entropyBytes = bits.match(/(.{8})/g).map(binaryToByte); - return Buffer.from(entropyBytes); -} -exports.mnemonicToBytes = mnemonicToBytes; -function bytesToBinary(bytes) { - return bytes.map((x) => lpad(x.toString(2), '0', 8)).join(''); -} -function binaryToByte(bin) { - return parseInt(bin, 2); -} -function lpad(str, padString, length) { - while (str.length < length) - str = padString + str; - return str; -} -const WORDLIST = [ - 'abandon', - 'ability', - 'able', - 'about', - 'above', - 'absent', - 'absorb', - 'abstract', - 'absurd', - 'abuse', - 'access', - 'accident', - 'account', - 'accuse', - 'achieve', - 'acid', - 'acoustic', - 'acquire', - 'across', - 'act', - 'action', - 'actor', - 'actress', - 'actual', - 'adapt', - 'add', - 'addict', - 'address', - 'adjust', - 'admit', - 'adult', - 'advance', - 'advice', - 'aerobic', - 'affair', - 'afford', - 'afraid', - 'again', - 'age', - 'agent', - 'agree', - 'ahead', - 'aim', - 'air', - 'airport', - 'aisle', - 'alarm', - 'album', - 'alcohol', - 'alert', - 'alien', - 'all', - 'alley', - 'allow', - 'almost', - 'alone', - 'alpha', - 'already', - 'also', - 'alter', - 'always', - 'amateur', - 'amazing', - 'among', - 'amount', - 'amused', - 'analyst', - 'anchor', - 'ancient', - 'anger', - 'angle', - 'angry', - 'animal', - 'ankle', - 'announce', - 'annual', - 'another', - 'answer', - 'antenna', - 'antique', - 'anxiety', - 'any', - 'apart', - 'apology', - 'appear', - 'apple', - 'approve', - 'april', - 'arch', - 'arctic', - 'area', - 'arena', - 'argue', - 'arm', - 'armed', - 'armor', - 'army', - 'around', - 'arrange', - 'arrest', - 'arrive', - 'arrow', - 'art', - 'artefact', - 'artist', - 'artwork', - 'ask', - 'aspect', - 'assault', - 'asset', - 'assist', - 'assume', - 'asthma', - 'athlete', - 'atom', - 'attack', - 'attend', - 'attitude', - 'attract', - 'auction', - 'audit', - 'august', - 'aunt', - 'author', - 'auto', - 'autumn', - 'average', - 'avocado', - 'avoid', - 'awake', - 'aware', - 'away', - 'awesome', - 'awful', - 'awkward', - 'axis', - 'baby', - 'bachelor', - 'bacon', - 'badge', - 'bag', - 'balance', - 'balcony', - 'ball', - 'bamboo', - 'banana', - 'banner', - 'bar', - 'barely', - 'bargain', - 'barrel', - 'base', - 'basic', - 'basket', - 'battle', - 'beach', - 'bean', - 'beauty', - 'because', - 'become', - 'beef', - 'before', - 'begin', - 'behave', - 'behind', - 'believe', - 'below', - 'belt', - 'bench', - 'benefit', - 'best', - 'betray', - 'better', - 'between', - 'beyond', - 'bicycle', - 'bid', - 'bike', - 'bind', - 'biology', - 'bird', - 'birth', - 'bitter', - 'black', - 'blade', - 'blame', - 'blanket', - 'blast', - 'bleak', - 'bless', - 'blind', - 'blood', - 'blossom', - 'blouse', - 'blue', - 'blur', - 'blush', - 'board', - 'boat', - 'body', - 'boil', - 'bomb', - 'bone', - 'bonus', - 'book', - 'boost', - 'border', - 'boring', - 'borrow', - 'boss', - 'bottom', - 'bounce', - 'box', - 'boy', - 'bracket', - 'brain', - 'brand', - 'brass', - 'brave', - 'bread', - 'breeze', - 'brick', - 'bridge', - 'brief', - 'bright', - 'bring', - 'brisk', - 'broccoli', - 'broken', - 'bronze', - 'broom', - 'brother', - 'brown', - 'brush', - 'bubble', - 'buddy', - 'budget', - 'buffalo', - 'build', - 'bulb', - 'bulk', - 'bullet', - 'bundle', - 'bunker', - 'burden', - 'burger', - 'burst', - 'bus', - 'business', - 'busy', - 'butter', - 'buyer', - 'buzz', - 'cabbage', - 'cabin', - 'cable', - 'cactus', - 'cage', - 'cake', - 'call', - 'calm', - 'camera', - 'camp', - 'can', - 'canal', - 'cancel', - 'candy', - 'cannon', - 'canoe', - 'canvas', - 'canyon', - 'capable', - 'capital', - 'captain', - 'car', - 'carbon', - 'card', - 'cargo', - 'carpet', - 'carry', - 'cart', - 'case', - 'cash', - 'casino', - 'castle', - 'casual', - 'cat', - 'catalog', - 'catch', - 'category', - 'cattle', - 'caught', - 'cause', - 'caution', - 'cave', - 'ceiling', - 'celery', - 'cement', - 'census', - 'century', - 'cereal', - 'certain', - 'chair', - 'chalk', - 'champion', - 'change', - 'chaos', - 'chapter', - 'charge', - 'chase', - 'chat', - 'cheap', - 'check', - 'cheese', - 'chef', - 'cherry', - 'chest', - 'chicken', - 'chief', - 'child', - 'chimney', - 'choice', - 'choose', - 'chronic', - 'chuckle', - 'chunk', - 'churn', - 'cigar', - 'cinnamon', - 'circle', - 'citizen', - 'city', - 'civil', - 'claim', - 'clap', - 'clarify', - 'claw', - 'clay', - 'clean', - 'clerk', - 'clever', - 'click', - 'client', - 'cliff', - 'climb', - 'clinic', - 'clip', - 'clock', - 'clog', - 'close', - 'cloth', - 'cloud', - 'clown', - 'club', - 'clump', - 'cluster', - 'clutch', - 'coach', - 'coast', - 'coconut', - 'code', - 'coffee', - 'coil', - 'coin', - 'collect', - 'color', - 'column', - 'combine', - 'come', - 'comfort', - 'comic', - 'common', - 'company', - 'concert', - 'conduct', - 'confirm', - 'congress', - 'connect', - 'consider', - 'control', - 'convince', - 'cook', - 'cool', - 'copper', - 'copy', - 'coral', - 'core', - 'corn', - 'correct', - 'cost', - 'cotton', - 'couch', - 'country', - 'couple', - 'course', - 'cousin', - 'cover', - 'coyote', - 'crack', - 'cradle', - 'craft', - 'cram', - 'crane', - 'crash', - 'crater', - 'crawl', - 'crazy', - 'cream', - 'credit', - 'creek', - 'crew', - 'cricket', - 'crime', - 'crisp', - 'critic', - 'crop', - 'cross', - 'crouch', - 'crowd', - 'crucial', - 'cruel', - 'cruise', - 'crumble', - 'crunch', - 'crush', - 'cry', - 'crystal', - 'cube', - 'culture', - 'cup', - 'cupboard', - 'curious', - 'current', - 'curtain', - 'curve', - 'cushion', - 'custom', - 'cute', - 'cycle', - 'dad', - 'damage', - 'damp', - 'dance', - 'danger', - 'daring', - 'dash', - 'daughter', - 'dawn', - 'day', - 'deal', - 'debate', - 'debris', - 'decade', - 'december', - 'decide', - 'decline', - 'decorate', - 'decrease', - 'deer', - 'defense', - 'define', - 'defy', - 'degree', - 'delay', - 'deliver', - 'demand', - 'demise', - 'denial', - 'dentist', - 'deny', - 'depart', - 'depend', - 'deposit', - 'depth', - 'deputy', - 'derive', - 'describe', - 'desert', - 'design', - 'desk', - 'despair', - 'destroy', - 'detail', - 'detect', - 'develop', - 'device', - 'devote', - 'diagram', - 'dial', - 'diamond', - 'diary', - 'dice', - 'diesel', - 'diet', - 'differ', - 'digital', - 'dignity', - 'dilemma', - 'dinner', - 'dinosaur', - 'direct', - 'dirt', - 'disagree', - 'discover', - 'disease', - 'dish', - 'dismiss', - 'disorder', - 'display', - 'distance', - 'divert', - 'divide', - 'divorce', - 'dizzy', - 'doctor', - 'document', - 'dog', - 'doll', - 'dolphin', - 'domain', - 'donate', - 'donkey', - 'donor', - 'door', - 'dose', - 'double', - 'dove', - 'draft', - 'dragon', - 'drama', - 'drastic', - 'draw', - 'dream', - 'dress', - 'drift', - 'drill', - 'drink', - 'drip', - 'drive', - 'drop', - 'drum', - 'dry', - 'duck', - 'dumb', - 'dune', - 'during', - 'dust', - 'dutch', - 'duty', - 'dwarf', - 'dynamic', - 'eager', - 'eagle', - 'early', - 'earn', - 'earth', - 'easily', - 'east', - 'easy', - 'echo', - 'ecology', - 'economy', - 'edge', - 'edit', - 'educate', - 'effort', - 'egg', - 'eight', - 'either', - 'elbow', - 'elder', - 'electric', - 'elegant', - 'element', - 'elephant', - 'elevator', - 'elite', - 'else', - 'embark', - 'embody', - 'embrace', - 'emerge', - 'emotion', - 'employ', - 'empower', - 'empty', - 'enable', - 'enact', - 'end', - 'endless', - 'endorse', - 'enemy', - 'energy', - 'enforce', - 'engage', - 'engine', - 'enhance', - 'enjoy', - 'enlist', - 'enough', - 'enrich', - 'enroll', - 'ensure', - 'enter', - 'entire', - 'entry', - 'envelope', - 'episode', - 'equal', - 'equip', - 'era', - 'erase', - 'erode', - 'erosion', - 'error', - 'erupt', - 'escape', - 'essay', - 'essence', - 'estate', - 'eternal', - 'ethics', - 'evidence', - 'evil', - 'evoke', - 'evolve', - 'exact', - 'example', - 'excess', - 'exchange', - 'excite', - 'exclude', - 'excuse', - 'execute', - 'exercise', - 'exhaust', - 'exhibit', - 'exile', - 'exist', - 'exit', - 'exotic', - 'expand', - 'expect', - 'expire', - 'explain', - 'expose', - 'express', - 'extend', - 'extra', - 'eye', - 'eyebrow', - 'fabric', - 'face', - 'faculty', - 'fade', - 'faint', - 'faith', - 'fall', - 'false', - 'fame', - 'family', - 'famous', - 'fan', - 'fancy', - 'fantasy', - 'farm', - 'fashion', - 'fat', - 'fatal', - 'father', - 'fatigue', - 'fault', - 'favorite', - 'feature', - 'february', - 'federal', - 'fee', - 'feed', - 'feel', - 'female', - 'fence', - 'festival', - 'fetch', - 'fever', - 'few', - 'fiber', - 'fiction', - 'field', - 'figure', - 'file', - 'film', - 'filter', - 'final', - 'find', - 'fine', - 'finger', - 'finish', - 'fire', - 'firm', - 'first', - 'fiscal', - 'fish', - 'fit', - 'fitness', - 'fix', - 'flag', - 'flame', - 'flash', - 'flat', - 'flavor', - 'flee', - 'flight', - 'flip', - 'float', - 'flock', - 'floor', - 'flower', - 'fluid', - 'flush', - 'fly', - 'foam', - 'focus', - 'fog', - 'foil', - 'fold', - 'follow', - 'food', - 'foot', - 'force', - 'forest', - 'forget', - 'fork', - 'fortune', - 'forum', - 'forward', - 'fossil', - 'foster', - 'found', - 'fox', - 'fragile', - 'frame', - 'frequent', - 'fresh', - 'friend', - 'fringe', - 'frog', - 'front', - 'frost', - 'frown', - 'frozen', - 'fruit', - 'fuel', - 'fun', - 'funny', - 'furnace', - 'fury', - 'future', - 'gadget', - 'gain', - 'galaxy', - 'gallery', - 'game', - 'gap', - 'garage', - 'garbage', - 'garden', - 'garlic', - 'garment', - 'gas', - 'gasp', - 'gate', - 'gather', - 'gauge', - 'gaze', - 'general', - 'genius', - 'genre', - 'gentle', - 'genuine', - 'gesture', - 'ghost', - 'giant', - 'gift', - 'giggle', - 'ginger', - 'giraffe', - 'girl', - 'give', - 'glad', - 'glance', - 'glare', - 'glass', - 'glide', - 'glimpse', - 'globe', - 'gloom', - 'glory', - 'glove', - 'glow', - 'glue', - 'goat', - 'goddess', - 'gold', - 'good', - 'goose', - 'gorilla', - 'gospel', - 'gossip', - 'govern', - 'gown', - 'grab', - 'grace', - 'grain', - 'grant', - 'grape', - 'grass', - 'gravity', - 'great', - 'green', - 'grid', - 'grief', - 'grit', - 'grocery', - 'group', - 'grow', - 'grunt', - 'guard', - 'guess', - 'guide', - 'guilt', - 'guitar', - 'gun', - 'gym', - 'habit', - 'hair', - 'half', - 'hammer', - 'hamster', - 'hand', - 'happy', - 'harbor', - 'hard', - 'harsh', - 'harvest', - 'hat', - 'have', - 'hawk', - 'hazard', - 'head', - 'health', - 'heart', - 'heavy', - 'hedgehog', - 'height', - 'hello', - 'helmet', - 'help', - 'hen', - 'hero', - 'hidden', - 'high', - 'hill', - 'hint', - 'hip', - 'hire', - 'history', - 'hobby', - 'hockey', - 'hold', - 'hole', - 'holiday', - 'hollow', - 'home', - 'honey', - 'hood', - 'hope', - 'horn', - 'horror', - 'horse', - 'hospital', - 'host', - 'hotel', - 'hour', - 'hover', - 'hub', - 'huge', - 'human', - 'humble', - 'humor', - 'hundred', - 'hungry', - 'hunt', - 'hurdle', - 'hurry', - 'hurt', - 'husband', - 'hybrid', - 'ice', - 'icon', - 'idea', - 'identify', - 'idle', - 'ignore', - 'ill', - 'illegal', - 'illness', - 'image', - 'imitate', - 'immense', - 'immune', - 'impact', - 'impose', - 'improve', - 'impulse', - 'inch', - 'include', - 'income', - 'increase', - 'index', - 'indicate', - 'indoor', - 'industry', - 'infant', - 'inflict', - 'inform', - 'inhale', - 'inherit', - 'initial', - 'inject', - 'injury', - 'inmate', - 'inner', - 'innocent', - 'input', - 'inquiry', - 'insane', - 'insect', - 'inside', - 'inspire', - 'install', - 'intact', - 'interest', - 'into', - 'invest', - 'invite', - 'involve', - 'iron', - 'island', - 'isolate', - 'issue', - 'item', - 'ivory', - 'jacket', - 'jaguar', - 'jar', - 'jazz', - 'jealous', - 'jeans', - 'jelly', - 'jewel', - 'job', - 'join', - 'joke', - 'journey', - 'joy', - 'judge', - 'juice', - 'jump', - 'jungle', - 'junior', - 'junk', - 'just', - 'kangaroo', - 'keen', - 'keep', - 'ketchup', - 'key', - 'kick', - 'kid', - 'kidney', - 'kind', - 'kingdom', - 'kiss', - 'kit', - 'kitchen', - 'kite', - 'kitten', - 'kiwi', - 'knee', - 'knife', - 'knock', - 'know', - 'lab', - 'label', - 'labor', - 'ladder', - 'lady', - 'lake', - 'lamp', - 'language', - 'laptop', - 'large', - 'later', - 'latin', - 'laugh', - 'laundry', - 'lava', - 'law', - 'lawn', - 'lawsuit', - 'layer', - 'lazy', - 'leader', - 'leaf', - 'learn', - 'leave', - 'lecture', - 'left', - 'leg', - 'legal', - 'legend', - 'leisure', - 'lemon', - 'lend', - 'length', - 'lens', - 'leopard', - 'lesson', - 'letter', - 'level', - 'liar', - 'liberty', - 'library', - 'license', - 'life', - 'lift', - 'light', - 'like', - 'limb', - 'limit', - 'link', - 'lion', - 'liquid', - 'list', - 'little', - 'live', - 'lizard', - 'load', - 'loan', - 'lobster', - 'local', - 'lock', - 'logic', - 'lonely', - 'long', - 'loop', - 'lottery', - 'loud', - 'lounge', - 'love', - 'loyal', - 'lucky', - 'luggage', - 'lumber', - 'lunar', - 'lunch', - 'luxury', - 'lyrics', - 'machine', - 'mad', - 'magic', - 'magnet', - 'maid', - 'mail', - 'main', - 'major', - 'make', - 'mammal', - 'man', - 'manage', - 'mandate', - 'mango', - 'mansion', - 'manual', - 'maple', - 'marble', - 'march', - 'margin', - 'marine', - 'market', - 'marriage', - 'mask', - 'mass', - 'master', - 'match', - 'material', - 'math', - 'matrix', - 'matter', - 'maximum', - 'maze', - 'meadow', - 'mean', - 'measure', - 'meat', - 'mechanic', - 'medal', - 'media', - 'melody', - 'melt', - 'member', - 'memory', - 'mention', - 'menu', - 'mercy', - 'merge', - 'merit', - 'merry', - 'mesh', - 'message', - 'metal', - 'method', - 'middle', - 'midnight', - 'milk', - 'million', - 'mimic', - 'mind', - 'minimum', - 'minor', - 'minute', - 'miracle', - 'mirror', - 'misery', - 'miss', - 'mistake', - 'mix', - 'mixed', - 'mixture', - 'mobile', - 'model', - 'modify', - 'mom', - 'moment', - 'monitor', - 'monkey', - 'monster', - 'month', - 'moon', - 'moral', - 'more', - 'morning', - 'mosquito', - 'mother', - 'motion', - 'motor', - 'mountain', - 'mouse', - 'move', - 'movie', - 'much', - 'muffin', - 'mule', - 'multiply', - 'muscle', - 'museum', - 'mushroom', - 'music', - 'must', - 'mutual', - 'myself', - 'mystery', - 'myth', - 'naive', - 'name', - 'napkin', - 'narrow', - 'nasty', - 'nation', - 'nature', - 'near', - 'neck', - 'need', - 'negative', - 'neglect', - 'neither', - 'nephew', - 'nerve', - 'nest', - 'net', - 'network', - 'neutral', - 'never', - 'news', - 'next', - 'nice', - 'night', - 'noble', - 'noise', - 'nominee', - 'noodle', - 'normal', - 'north', - 'nose', - 'notable', - 'note', - 'nothing', - 'notice', - 'novel', - 'now', - 'nuclear', - 'number', - 'nurse', - 'nut', - 'oak', - 'obey', - 'object', - 'oblige', - 'obscure', - 'observe', - 'obtain', - 'obvious', - 'occur', - 'ocean', - 'october', - 'odor', - 'off', - 'offer', - 'office', - 'often', - 'oil', - 'okay', - 'old', - 'olive', - 'olympic', - 'omit', - 'once', - 'one', - 'onion', - 'online', - 'only', - 'open', - 'opera', - 'opinion', - 'oppose', - 'option', - 'orange', - 'orbit', - 'orchard', - 'order', - 'ordinary', - 'organ', - 'orient', - 'original', - 'orphan', - 'ostrich', - 'other', - 'outdoor', - 'outer', - 'output', - 'outside', - 'oval', - 'oven', - 'over', - 'own', - 'owner', - 'oxygen', - 'oyster', - 'ozone', - 'pact', - 'paddle', - 'page', - 'pair', - 'palace', - 'palm', - 'panda', - 'panel', - 'panic', - 'panther', - 'paper', - 'parade', - 'parent', - 'park', - 'parrot', - 'party', - 'pass', - 'patch', - 'path', - 'patient', - 'patrol', - 'pattern', - 'pause', - 'pave', - 'payment', - 'peace', - 'peanut', - 'pear', - 'peasant', - 'pelican', - 'pen', - 'penalty', - 'pencil', - 'people', - 'pepper', - 'perfect', - 'permit', - 'person', - 'pet', - 'phone', - 'photo', - 'phrase', - 'physical', - 'piano', - 'picnic', - 'picture', - 'piece', - 'pig', - 'pigeon', - 'pill', - 'pilot', - 'pink', - 'pioneer', - 'pipe', - 'pistol', - 'pitch', - 'pizza', - 'place', - 'planet', - 'plastic', - 'plate', - 'play', - 'please', - 'pledge', - 'pluck', - 'plug', - 'plunge', - 'poem', - 'poet', - 'point', - 'polar', - 'pole', - 'police', - 'pond', - 'pony', - 'pool', - 'popular', - 'portion', - 'position', - 'possible', - 'post', - 'potato', - 'pottery', - 'poverty', - 'powder', - 'power', - 'practice', - 'praise', - 'predict', - 'prefer', - 'prepare', - 'present', - 'pretty', - 'prevent', - 'price', - 'pride', - 'primary', - 'print', - 'priority', - 'prison', - 'private', - 'prize', - 'problem', - 'process', - 'produce', - 'profit', - 'program', - 'project', - 'promote', - 'proof', - 'property', - 'prosper', - 'protect', - 'proud', - 'provide', - 'public', - 'pudding', - 'pull', - 'pulp', - 'pulse', - 'pumpkin', - 'punch', - 'pupil', - 'puppy', - 'purchase', - 'purity', - 'purpose', - 'purse', - 'push', - 'put', - 'puzzle', - 'pyramid', - 'quality', - 'quantum', - 'quarter', - 'question', - 'quick', - 'quit', - 'quiz', - 'quote', - 'rabbit', - 'raccoon', - 'race', - 'rack', - 'radar', - 'radio', - 'rail', - 'rain', - 'raise', - 'rally', - 'ramp', - 'ranch', - 'random', - 'range', - 'rapid', - 'rare', - 'rate', - 'rather', - 'raven', - 'raw', - 'razor', - 'ready', - 'real', - 'reason', - 'rebel', - 'rebuild', - 'recall', - 'receive', - 'recipe', - 'record', - 'recycle', - 'reduce', - 'reflect', - 'reform', - 'refuse', - 'region', - 'regret', - 'regular', - 'reject', - 'relax', - 'release', - 'relief', - 'rely', - 'remain', - 'remember', - 'remind', - 'remove', - 'render', - 'renew', - 'rent', - 'reopen', - 'repair', - 'repeat', - 'replace', - 'report', - 'require', - 'rescue', - 'resemble', - 'resist', - 'resource', - 'response', - 'result', - 'retire', - 'retreat', - 'return', - 'reunion', - 'reveal', - 'review', - 'reward', - 'rhythm', - 'rib', - 'ribbon', - 'rice', - 'rich', - 'ride', - 'ridge', - 'rifle', - 'right', - 'rigid', - 'ring', - 'riot', - 'ripple', - 'risk', - 'ritual', - 'rival', - 'river', - 'road', - 'roast', - 'robot', - 'robust', - 'rocket', - 'romance', - 'roof', - 'rookie', - 'room', - 'rose', - 'rotate', - 'rough', - 'round', - 'route', - 'royal', - 'rubber', - 'rude', - 'rug', - 'rule', - 'run', - 'runway', - 'rural', - 'sad', - 'saddle', - 'sadness', - 'safe', - 'sail', - 'salad', - 'salmon', - 'salon', - 'salt', - 'salute', - 'same', - 'sample', - 'sand', - 'satisfy', - 'satoshi', - 'sauce', - 'sausage', - 'save', - 'say', - 'scale', - 'scan', - 'scare', - 'scatter', - 'scene', - 'scheme', - 'school', - 'science', - 'scissors', - 'scorpion', - 'scout', - 'scrap', - 'screen', - 'script', - 'scrub', - 'sea', - 'search', - 'season', - 'seat', - 'second', - 'secret', - 'section', - 'security', - 'seed', - 'seek', - 'segment', - 'select', - 'sell', - 'seminar', - 'senior', - 'sense', - 'sentence', - 'series', - 'service', - 'session', - 'settle', - 'setup', - 'seven', - 'shadow', - 'shaft', - 'shallow', - 'share', - 'shed', - 'shell', - 'sheriff', - 'shield', - 'shift', - 'shine', - 'ship', - 'shiver', - 'shock', - 'shoe', - 'shoot', - 'shop', - 'short', - 'shoulder', - 'shove', - 'shrimp', - 'shrug', - 'shuffle', - 'shy', - 'sibling', - 'sick', - 'side', - 'siege', - 'sight', - 'sign', - 'silent', - 'silk', - 'silly', - 'silver', - 'similar', - 'simple', - 'since', - 'sing', - 'siren', - 'sister', - 'situate', - 'six', - 'size', - 'skate', - 'sketch', - 'ski', - 'skill', - 'skin', - 'skirt', - 'skull', - 'slab', - 'slam', - 'sleep', - 'slender', - 'slice', - 'slide', - 'slight', - 'slim', - 'slogan', - 'slot', - 'slow', - 'slush', - 'small', - 'smart', - 'smile', - 'smoke', - 'smooth', - 'snack', - 'snake', - 'snap', - 'sniff', - 'snow', - 'soap', - 'soccer', - 'social', - 'sock', - 'soda', - 'soft', - 'solar', - 'soldier', - 'solid', - 'solution', - 'solve', - 'someone', - 'song', - 'soon', - 'sorry', - 'sort', - 'soul', - 'sound', - 'soup', - 'source', - 'south', - 'space', - 'spare', - 'spatial', - 'spawn', - 'speak', - 'special', - 'speed', - 'spell', - 'spend', - 'sphere', - 'spice', - 'spider', - 'spike', - 'spin', - 'spirit', - 'split', - 'spoil', - 'sponsor', - 'spoon', - 'sport', - 'spot', - 'spray', - 'spread', - 'spring', - 'spy', - 'square', - 'squeeze', - 'squirrel', - 'stable', - 'stadium', - 'staff', - 'stage', - 'stairs', - 'stamp', - 'stand', - 'start', - 'state', - 'stay', - 'steak', - 'steel', - 'stem', - 'step', - 'stereo', - 'stick', - 'still', - 'sting', - 'stock', - 'stomach', - 'stone', - 'stool', - 'story', - 'stove', - 'strategy', - 'street', - 'strike', - 'strong', - 'struggle', - 'student', - 'stuff', - 'stumble', - 'style', - 'subject', - 'submit', - 'subway', - 'success', - 'such', - 'sudden', - 'suffer', - 'sugar', - 'suggest', - 'suit', - 'summer', - 'sun', - 'sunny', - 'sunset', - 'super', - 'supply', - 'supreme', - 'sure', - 'surface', - 'surge', - 'surprise', - 'surround', - 'survey', - 'suspect', - 'sustain', - 'swallow', - 'swamp', - 'swap', - 'swarm', - 'swear', - 'sweet', - 'swift', - 'swim', - 'swing', - 'switch', - 'sword', - 'symbol', - 'symptom', - 'syrup', - 'system', - 'table', - 'tackle', - 'tag', - 'tail', - 'talent', - 'talk', - 'tank', - 'tape', - 'target', - 'task', - 'taste', - 'tattoo', - 'taxi', - 'teach', - 'team', - 'tell', - 'ten', - 'tenant', - 'tennis', - 'tent', - 'term', - 'test', - 'text', - 'thank', - 'that', - 'theme', - 'then', - 'theory', - 'there', - 'they', - 'thing', - 'this', - 'thought', - 'three', - 'thrive', - 'throw', - 'thumb', - 'thunder', - 'ticket', - 'tide', - 'tiger', - 'tilt', - 'timber', - 'time', - 'tiny', - 'tip', - 'tired', - 'tissue', - 'title', - 'toast', - 'tobacco', - 'today', - 'toddler', - 'toe', - 'together', - 'toilet', - 'token', - 'tomato', - 'tomorrow', - 'tone', - 'tongue', - 'tonight', - 'tool', - 'tooth', - 'top', - 'topic', - 'topple', - 'torch', - 'tornado', - 'tortoise', - 'toss', - 'total', - 'tourist', - 'toward', - 'tower', - 'town', - 'toy', - 'track', - 'trade', - 'traffic', - 'tragic', - 'train', - 'transfer', - 'trap', - 'trash', - 'travel', - 'tray', - 'treat', - 'tree', - 'trend', - 'trial', - 'tribe', - 'trick', - 'trigger', - 'trim', - 'trip', - 'trophy', - 'trouble', - 'truck', - 'true', - 'truly', - 'trumpet', - 'trust', - 'truth', - 'try', - 'tube', - 'tuition', - 'tumble', - 'tuna', - 'tunnel', - 'turkey', - 'turn', - 'turtle', - 'twelve', - 'twenty', - 'twice', - 'twin', - 'twist', - 'two', - 'type', - 'typical', - 'ugly', - 'umbrella', - 'unable', - 'unaware', - 'uncle', - 'uncover', - 'under', - 'undo', - 'unfair', - 'unfold', - 'unhappy', - 'uniform', - 'unique', - 'unit', - 'universe', - 'unknown', - 'unlock', - 'until', - 'unusual', - 'unveil', - 'update', - 'upgrade', - 'uphold', - 'upon', - 'upper', - 'upset', - 'urban', - 'urge', - 'usage', - 'use', - 'used', - 'useful', - 'useless', - 'usual', - 'utility', - 'vacant', - 'vacuum', - 'vague', - 'valid', - 'valley', - 'valve', - 'van', - 'vanish', - 'vapor', - 'various', - 'vast', - 'vault', - 'vehicle', - 'velvet', - 'vendor', - 'venture', - 'venue', - 'verb', - 'verify', - 'version', - 'very', - 'vessel', - 'veteran', - 'viable', - 'vibrant', - 'vicious', - 'victory', - 'video', - 'view', - 'village', - 'vintage', - 'violin', - 'virtual', - 'virus', - 'visa', - 'visit', - 'visual', - 'vital', - 'vivid', - 'vocal', - 'voice', - 'void', - 'volcano', - 'volume', - 'vote', - 'voyage', - 'wage', - 'wagon', - 'wait', - 'walk', - 'wall', - 'walnut', - 'want', - 'warfare', - 'warm', - 'warrior', - 'wash', - 'wasp', - 'waste', - 'water', - 'wave', - 'way', - 'wealth', - 'weapon', - 'wear', - 'weasel', - 'weather', - 'web', - 'wedding', - 'weekend', - 'weird', - 'welcome', - 'west', - 'wet', - 'whale', - 'what', - 'wheat', - 'wheel', - 'when', - 'where', - 'whip', - 'whisper', - 'wide', - 'width', - 'wife', - 'wild', - 'will', - 'win', - 'window', - 'wine', - 'wing', - 'wink', - 'winner', - 'winter', - 'wire', - 'wisdom', - 'wise', - 'wish', - 'witness', - 'wolf', - 'woman', - 'wonder', - 'wood', - 'wool', - 'word', - 'work', - 'world', - 'worry', - 'worth', - 'wrap', - 'wreck', - 'wrestle', - 'wrist', - 'write', - 'wrong', - 'yard', - 'year', - 'yellow', - 'you', - 'young', - 'youth', - 'zebra', - 'zero', - 'zone', - 'zoo', -]; diff --git a/blue_modules/aezeed/src/params.d.ts b/blue_modules/aezeed/src/params.d.ts deleted file mode 100644 index 9f7aefcaddf..00000000000 --- a/blue_modules/aezeed/src/params.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -export declare const PARAMS: { - n: number; - r: number; - p: number; -}[]; -export declare const DEFAULT_PASSWORD = "aezeed"; -export declare const CIPHER_SEED_VERSION = 0; -export declare const ONE_DAY: number; diff --git a/blue_modules/aezeed/src/params.js b/blue_modules/aezeed/src/params.js deleted file mode 100644 index b7eca893f8a..00000000000 --- a/blue_modules/aezeed/src/params.js +++ /dev/null @@ -1,14 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ONE_DAY = exports.CIPHER_SEED_VERSION = exports.DEFAULT_PASSWORD = exports.PARAMS = void 0; -exports.PARAMS = [ - { - // version 0 - n: 32768, - r: 8, - p: 1, - }, -]; -exports.DEFAULT_PASSWORD = 'aezeed'; -exports.CIPHER_SEED_VERSION = 0; -exports.ONE_DAY = 24 * 60 * 60 * 1000; diff --git a/blue_modules/analytics.js b/blue_modules/analytics.js deleted file mode 100644 index 0cd2ae61b95..00000000000 --- a/blue_modules/analytics.js +++ /dev/null @@ -1,44 +0,0 @@ -import { getUniqueId } from 'react-native-device-info'; -import Bugsnag from '@bugsnag/react-native'; -const BlueApp = require('../BlueApp'); - -let userHasOptedOut = false; - -if (process.env.NODE_ENV !== 'development') { - Bugsnag.start({ - collectUserIp: false, - user: { - id: getUniqueId(), - }, - onError: function (event) { - return !userHasOptedOut; - }, - }); -} - -BlueApp.isDoNotTrackEnabled().then(value => { - if (value) userHasOptedOut = true; -}); - -const A = async event => {}; - -A.ENUM = { - INIT: 'INIT', - GOT_NONZERO_BALANCE: 'GOT_NONZERO_BALANCE', - GOT_ZERO_BALANCE: 'GOT_ZERO_BALANCE', - CREATED_WALLET: 'CREATED_WALLET', - CREATED_LIGHTNING_WALLET: 'CREATED_LIGHTNING_WALLET', - APP_UNSUSPENDED: 'APP_UNSUSPENDED', - NAVIGATED_TO_WALLETS_HODLHODL: 'NAVIGATED_TO_WALLETS_HODLHODL', -}; - -A.setOptOut = value => { - if (value) userHasOptedOut = true; -}; - -A.logError = errorString => { - console.error(errorString); - Bugsnag.notify(new Error(String(errorString))); -}; - -module.exports = A; diff --git a/blue_modules/analytics.ts b/blue_modules/analytics.ts new file mode 100644 index 00000000000..91e4ce9e5f6 --- /dev/null +++ b/blue_modules/analytics.ts @@ -0,0 +1,53 @@ +import Bugsnag from '@bugsnag/react-native'; +import { getUniqueId } from 'react-native-device-info'; + +import { BlueApp as BlueAppClass } from '../class/blue-app'; + +const BlueApp = BlueAppClass.getInstance(); + +/** + * in case Bugsnag was started, but user decided to opt out while using the app, we have this + * flag `userHasOptedOut` and we forbid logging in `onError` handler + * @type {boolean} + */ +let userHasOptedOut: boolean = false; + +(async () => { + try { + // Don't try to start Bugsnag again as it's already initialized in native code. + // Configure it only when tracking is allowed. + const doNotTrack = await BlueApp.isDoNotTrackEnabled(); + if (doNotTrack) { + userHasOptedOut = true; + return; + } + + const uniqueID = await getUniqueId(); + Bugsnag.setUser(uniqueID); + Bugsnag.addOnError(function () { + return !userHasOptedOut; + }); + } catch (error) { + // Never let analytics setup crash the app. + console.error('Failed to initialize analytics:', error); + } +})(); + +const A = async (event: string) => {}; + +A.setOptOut = (value: boolean) => { + if (value) userHasOptedOut = true; +}; + +A.logError = (errorString: string) => { + console.error(errorString); + if (!userHasOptedOut) { + try { + Bugsnag.notify(new Error(String(errorString))); + } catch (error) { + console.error('Failed to report error to Bugsnag:', error); + } + } +}; + +export default A; diff --git a/blue_modules/base43.js b/blue_modules/base43.js deleted file mode 100644 index 7bbaa8dd7f9..00000000000 --- a/blue_modules/base43.js +++ /dev/null @@ -1,14 +0,0 @@ -const base = require('base-x'); - -const Base43 = { - encode: function () { - throw new Error('not implemented'); - }, - - decode: function (input) { - const x = base('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:'); - return x.decode(input).toString('hex'); - }, -}; - -module.exports = Base43; diff --git a/blue_modules/base43.ts b/blue_modules/base43.ts new file mode 100644 index 00000000000..b450cb52ccc --- /dev/null +++ b/blue_modules/base43.ts @@ -0,0 +1,16 @@ +import base from 'base-x'; +import { uint8ArrayToHex } from './uint8array-extras/index'; + +const Base43 = { + encode: function () { + throw new Error('not implemented'); + }, + + decode: function (input: string): string { + const x = base('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:'); + const uint8 = x.decode(input); + return uint8ArrayToHex(uint8); + }, +}; + +export default Base43; diff --git a/blue_modules/bbqr/consts.ts b/blue_modules/bbqr/consts.ts new file mode 100644 index 00000000000..0c8f292158f --- /dev/null +++ b/blue_modules/bbqr/consts.ts @@ -0,0 +1,327 @@ +/** + * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain. + * + * Constants and fixed values. + */ + +import { Version } from './types'; + +// Fixed-length header +export const HEADER_LEN = 8; + +export const FILETYPE_NAMES = { + P: 'PSBT', + T: 'Transaction', + J: 'JSON', + U: 'Unicode Text', + X: 'Executable', + B: 'Binary', + R: 'KT Rx', + S: 'KT Tx', + E: 'KT PSBT', +} as const; + +export const ENCODING_NAMES = { + H: 'HEX', + Z: 'Zlib compressed', + '2': 'Base32', +} as const; + +export const ENCODINGS = new Set(Object.keys(ENCODING_NAMES)); + +export const ENCODING_SPLIT_MOD = { + H: 2, + Z: 8, + '2': 8, +} as const; + +// taken from: https://github.com/mnooner256/pyqrcode/blob/674a77b5eaf850d063f518bd90c243ee34ad6b5d/pyqrcode/tables.py#L84 +export const QR_DATA_CAPACITY = { + 1: { + L: { 0: 152, 1: 41, 2: 25, 4: 17, 8: 10 }, + M: { 0: 128, 1: 34, 2: 20, 4: 14, 8: 8 }, + Q: { 0: 104, 1: 27, 2: 16, 4: 11, 8: 7 }, + H: { 0: 72, 1: 17, 2: 10, 4: 7, 8: 4 }, + }, + 2: { + L: { 0: 272, 1: 77, 2: 47, 4: 32, 8: 20 }, + M: { 0: 224, 1: 63, 2: 38, 4: 26, 8: 16 }, + Q: { 0: 176, 1: 48, 2: 29, 4: 20, 8: 12 }, + H: { 0: 128, 1: 34, 2: 20, 4: 14, 8: 8 }, + }, + 3: { + L: { 0: 440, 1: 127, 2: 77, 4: 53, 8: 32 }, + M: { 0: 352, 1: 101, 2: 61, 4: 42, 8: 26 }, + Q: { 0: 272, 1: 77, 2: 47, 4: 32, 8: 20 }, + H: { 0: 208, 1: 58, 2: 35, 4: 24, 8: 15 }, + }, + 4: { + L: { 0: 640, 1: 187, 2: 114, 4: 78, 8: 48 }, + M: { 0: 512, 1: 149, 2: 90, 4: 62, 8: 38 }, + Q: { 0: 384, 1: 111, 2: 67, 4: 46, 8: 28 }, + H: { 0: 288, 1: 82, 2: 50, 4: 34, 8: 21 }, + }, + 5: { + L: { 0: 864, 1: 255, 2: 154, 4: 106, 8: 65 }, + M: { 0: 688, 1: 202, 2: 122, 4: 84, 8: 52 }, + Q: { 0: 496, 1: 144, 2: 87, 4: 60, 8: 37 }, + H: { 0: 368, 1: 106, 2: 64, 4: 44, 8: 27 }, + }, + 6: { + L: { 0: 1088, 1: 322, 2: 195, 4: 134, 8: 82 }, + M: { 0: 864, 1: 255, 2: 154, 4: 106, 8: 65 }, + Q: { 0: 608, 1: 178, 2: 108, 4: 74, 8: 45 }, + H: { 0: 480, 1: 139, 2: 84, 4: 58, 8: 36 }, + }, + 7: { + L: { 0: 1248, 1: 370, 2: 224, 4: 154, 8: 95 }, + M: { 0: 992, 1: 293, 2: 178, 4: 122, 8: 75 }, + Q: { 0: 704, 1: 207, 2: 125, 4: 86, 8: 53 }, + H: { 0: 528, 1: 154, 2: 93, 4: 64, 8: 39 }, + }, + 8: { + L: { 0: 1552, 1: 461, 2: 279, 4: 192, 8: 118 }, + M: { 0: 1232, 1: 365, 2: 221, 4: 152, 8: 93 }, + Q: { 0: 880, 1: 259, 2: 157, 4: 108, 8: 66 }, + H: { 0: 688, 1: 202, 2: 122, 4: 84, 8: 52 }, + }, + 9: { + L: { 0: 1856, 1: 552, 2: 335, 4: 230, 8: 141 }, + M: { 0: 1456, 1: 432, 2: 262, 4: 180, 8: 111 }, + Q: { 0: 1056, 1: 312, 2: 189, 4: 130, 8: 80 }, + H: { 0: 800, 1: 235, 2: 143, 4: 98, 8: 60 }, + }, + 10: { + L: { 0: 2192, 1: 652, 2: 395, 4: 271, 8: 167 }, + M: { 0: 1728, 1: 513, 2: 311, 4: 213, 8: 131 }, + Q: { 0: 1232, 1: 364, 2: 221, 4: 151, 8: 93 }, + H: { 0: 976, 1: 288, 2: 174, 4: 119, 8: 74 }, + }, + 11: { + L: { 0: 2592, 1: 772, 2: 468, 4: 321, 8: 198 }, + M: { 0: 2032, 1: 604, 2: 366, 4: 251, 8: 155 }, + Q: { 0: 1440, 1: 427, 2: 259, 4: 177, 8: 109 }, + H: { 0: 1120, 1: 331, 2: 200, 4: 137, 8: 85 }, + }, + 12: { + L: { 0: 2960, 1: 883, 2: 535, 4: 367, 8: 226 }, + M: { 0: 2320, 1: 691, 2: 419, 4: 287, 8: 177 }, + Q: { 0: 1648, 1: 489, 2: 296, 4: 203, 8: 125 }, + H: { 0: 1264, 1: 374, 2: 227, 4: 155, 8: 96 }, + }, + 13: { + L: { 0: 3424, 1: 1022, 2: 619, 4: 425, 8: 262 }, + M: { 0: 2672, 1: 796, 2: 483, 4: 331, 8: 204 }, + Q: { 0: 1952, 1: 580, 2: 352, 4: 241, 8: 149 }, + H: { 0: 1440, 1: 427, 2: 259, 4: 177, 8: 109 }, + }, + 14: { + L: { 0: 3688, 1: 1101, 2: 667, 4: 458, 8: 282 }, + M: { 0: 2920, 1: 871, 2: 528, 4: 362, 8: 223 }, + Q: { 0: 2088, 1: 621, 2: 376, 4: 258, 8: 159 }, + H: { 0: 1576, 1: 468, 2: 283, 4: 194, 8: 120 }, + }, + 15: { + L: { 0: 4184, 1: 1250, 2: 758, 4: 520, 8: 320 }, + M: { 0: 3320, 1: 991, 2: 600, 4: 412, 8: 254 }, + Q: { 0: 2360, 1: 703, 2: 426, 4: 292, 8: 180 }, + H: { 0: 1784, 1: 530, 2: 321, 4: 220, 8: 136 }, + }, + 16: { + L: { 0: 4712, 1: 1408, 2: 854, 4: 586, 8: 361 }, + M: { 0: 3624, 1: 1082, 2: 656, 4: 450, 8: 277 }, + Q: { 0: 2600, 1: 775, 2: 470, 4: 322, 8: 198 }, + H: { 0: 2024, 1: 602, 2: 365, 4: 250, 8: 154 }, + }, + 17: { + L: { 0: 5176, 1: 1548, 2: 938, 4: 644, 8: 397 }, + M: { 0: 4056, 1: 1212, 2: 734, 4: 504, 8: 310 }, + Q: { 0: 2936, 1: 876, 2: 531, 4: 364, 8: 224 }, + H: { 0: 2264, 1: 674, 2: 408, 4: 280, 8: 173 }, + }, + 18: { + L: { 0: 5768, 1: 1725, 2: 1046, 4: 718, 8: 442 }, + M: { 0: 4504, 1: 1346, 2: 816, 4: 560, 8: 345 }, + Q: { 0: 3176, 1: 948, 2: 574, 4: 394, 8: 243 }, + H: { 0: 2504, 1: 746, 2: 452, 4: 310, 8: 191 }, + }, + 19: { + L: { 0: 6360, 1: 1903, 2: 1153, 4: 792, 8: 488 }, + M: { 0: 5016, 1: 1500, 2: 909, 4: 624, 8: 384 }, + Q: { 0: 3560, 1: 1063, 2: 644, 4: 442, 8: 272 }, + H: { 0: 2728, 1: 813, 2: 493, 4: 338, 8: 208 }, + }, + 20: { + L: { 0: 6888, 1: 2061, 2: 1249, 4: 858, 8: 528 }, + M: { 0: 5352, 1: 1600, 2: 970, 4: 666, 8: 410 }, + Q: { 0: 3880, 1: 1159, 2: 702, 4: 482, 8: 297 }, + H: { 0: 3080, 1: 919, 2: 557, 4: 382, 8: 235 }, + }, + 21: { + L: { 0: 7456, 1: 2232, 2: 1352, 4: 929, 8: 572 }, + M: { 0: 5712, 1: 1708, 2: 1035, 4: 711, 8: 438 }, + Q: { 0: 4096, 1: 1224, 2: 742, 4: 509, 8: 314 }, + H: { 0: 3248, 1: 969, 2: 587, 4: 403, 8: 248 }, + }, + 22: { + L: { 0: 8048, 1: 2409, 2: 1460, 4: 1003, 8: 618 }, + M: { 0: 6256, 1: 1872, 2: 1134, 4: 779, 8: 480 }, + Q: { 0: 4544, 1: 1358, 2: 823, 4: 565, 8: 348 }, + H: { 0: 3536, 1: 1056, 2: 640, 4: 439, 8: 270 }, + }, + 23: { + L: { 0: 8752, 1: 2620, 2: 1588, 4: 1091, 8: 672 }, + M: { 0: 6880, 1: 2059, 2: 1248, 4: 857, 8: 528 }, + Q: { 0: 4912, 1: 1468, 2: 890, 4: 611, 8: 376 }, + H: { 0: 3712, 1: 1108, 2: 672, 4: 461, 8: 284 }, + }, + 24: { + L: { 0: 9392, 1: 2812, 2: 1704, 4: 1171, 8: 721 }, + M: { 0: 7312, 1: 2188, 2: 1326, 4: 911, 8: 561 }, + Q: { 0: 5312, 1: 1588, 2: 963, 4: 661, 8: 407 }, + H: { 0: 4112, 1: 1228, 2: 744, 4: 511, 8: 315 }, + }, + 25: { + L: { 0: 10208, 1: 3057, 2: 1853, 4: 1273, 8: 784 }, + M: { 0: 8000, 1: 2395, 2: 1451, 4: 997, 8: 614 }, + Q: { 0: 5744, 1: 1718, 2: 1041, 4: 715, 8: 440 }, + H: { 0: 4304, 1: 1286, 2: 779, 4: 535, 8: 330 }, + }, + 26: { + L: { 0: 10960, 1: 3283, 2: 1990, 4: 1367, 8: 842 }, + M: { 0: 8496, 1: 2544, 2: 1542, 4: 1059, 8: 652 }, + Q: { 0: 6032, 1: 1804, 2: 1094, 4: 751, 8: 462 }, + H: { 0: 4768, 1: 1425, 2: 864, 4: 593, 8: 365 }, + }, + 27: { + L: { 0: 11744, 1: 3514, 2: 2132, 4: 1465, 8: 902 }, + M: { 0: 9024, 1: 2701, 2: 1637, 4: 1125, 8: 692 }, + Q: { 0: 6464, 1: 1933, 2: 1172, 4: 805, 8: 496 }, + H: { 0: 5024, 1: 1501, 2: 910, 4: 625, 8: 385 }, + }, + 28: { + L: { 0: 12248, 1: 3669, 2: 2223, 4: 1528, 8: 940 }, + M: { 0: 9544, 1: 2857, 2: 1732, 4: 1190, 8: 732 }, + Q: { 0: 6968, 1: 2085, 2: 1263, 4: 868, 8: 534 }, + H: { 0: 5288, 1: 1581, 2: 958, 4: 658, 8: 405 }, + }, + 29: { + L: { 0: 13048, 1: 3909, 2: 2369, 4: 1628, 8: 1002 }, + M: { 0: 10136, 1: 3035, 2: 1839, 4: 1264, 8: 778 }, + Q: { 0: 7288, 1: 2181, 2: 1322, 4: 908, 8: 559 }, + H: { 0: 5608, 1: 1677, 2: 1016, 4: 698, 8: 430 }, + }, + 30: { + L: { 0: 13880, 1: 4158, 2: 2520, 4: 1732, 8: 1066 }, + M: { 0: 10984, 1: 3289, 2: 1994, 4: 1370, 8: 843 }, + Q: { 0: 7880, 1: 2358, 2: 1429, 4: 982, 8: 604 }, + H: { 0: 5960, 1: 1782, 2: 1080, 4: 742, 8: 457 }, + }, + 31: { + L: { 0: 14744, 1: 4417, 2: 2677, 4: 1840, 8: 1132 }, + M: { 0: 11640, 1: 3486, 2: 2113, 4: 1452, 8: 894 }, + Q: { 0: 8264, 1: 2473, 2: 1499, 4: 1030, 8: 634 }, + H: { 0: 6344, 1: 1897, 2: 1150, 4: 790, 8: 486 }, + }, + 32: { + L: { 0: 15640, 1: 4686, 2: 2840, 4: 1952, 8: 1201 }, + M: { 0: 12328, 1: 3693, 2: 2238, 4: 1538, 8: 947 }, + Q: { 0: 8920, 1: 2670, 2: 1618, 4: 1112, 8: 684 }, + H: { 0: 6760, 1: 2022, 2: 1226, 4: 842, 8: 518 }, + }, + 33: { + L: { 0: 16568, 1: 4965, 2: 3009, 4: 2068, 8: 1273 }, + M: { 0: 13048, 1: 3909, 2: 2369, 4: 1628, 8: 1002 }, + Q: { 0: 9368, 1: 2805, 2: 1700, 4: 1168, 8: 719 }, + H: { 0: 7208, 1: 2157, 2: 1307, 4: 898, 8: 553 }, + }, + 34: { + L: { 0: 17528, 1: 5253, 2: 3183, 4: 2188, 8: 1347 }, + M: { 0: 13800, 1: 4134, 2: 2506, 4: 1722, 8: 1060 }, + Q: { 0: 9848, 1: 2949, 2: 1787, 4: 1228, 8: 756 }, + H: { 0: 7688, 1: 2301, 2: 1394, 4: 958, 8: 590 }, + }, + 35: { + L: { 0: 18448, 1: 5529, 2: 3351, 4: 2303, 8: 1417 }, + M: { 0: 14496, 1: 4343, 2: 2632, 4: 1809, 8: 1113 }, + Q: { 0: 10288, 1: 3081, 2: 1867, 4: 1283, 8: 790 }, + H: { 0: 7888, 1: 2361, 2: 1431, 4: 983, 8: 605 }, + }, + 36: { + L: { 0: 19472, 1: 5836, 2: 3537, 4: 2431, 8: 1496 }, + M: { 0: 15312, 1: 4588, 2: 2780, 4: 1911, 8: 1176 }, + Q: { 0: 10832, 1: 3244, 2: 1966, 4: 1351, 8: 832 }, + H: { 0: 8432, 1: 2524, 2: 1530, 4: 1051, 8: 647 }, + }, + 37: { + L: { 0: 20528, 1: 6153, 2: 3729, 4: 2563, 8: 1577 }, + M: { 0: 15936, 1: 4775, 2: 2894, 4: 1989, 8: 1224 }, + Q: { 0: 11408, 1: 3417, 2: 2071, 4: 1423, 8: 876 }, + H: { 0: 8768, 1: 2625, 2: 1591, 4: 1093, 8: 673 }, + }, + 38: { + L: { 0: 21616, 1: 6479, 2: 3927, 4: 2699, 8: 1661 }, + M: { 0: 16816, 1: 5039, 2: 3054, 4: 2099, 8: 1292 }, + Q: { 0: 12016, 1: 3599, 2: 2181, 4: 1499, 8: 923 }, + H: { 0: 9136, 1: 2735, 2: 1658, 4: 1139, 8: 701 }, + }, + 39: { + L: { 0: 22496, 1: 6743, 2: 4087, 4: 2809, 8: 1729 }, + M: { 0: 17728, 1: 5313, 2: 3220, 4: 2213, 8: 1362 }, + Q: { 0: 12656, 1: 3791, 2: 2298, 4: 1579, 8: 972 }, + H: { 0: 9776, 1: 2927, 2: 1774, 4: 1219, 8: 750 }, + }, + 40: { + L: { 0: 23648, 1: 7089, 2: 4296, 4: 2953, 8: 1817 }, + M: { 0: 18672, 1: 5596, 2: 3391, 4: 2331, 8: 1435 }, + Q: { 0: 13328, 1: 3993, 2: 2420, 4: 1663, 8: 1024 }, + H: { 0: 10208, 1: 3057, 2: 1852, 4: 1273, 8: 784 }, + }, +} as const; + +// map version to size in modules +// https://github.com/mnooner256/pyqrcode/blob/674a77b5eaf850d063f518bd90c243ee34ad6b5d/pyqrcode/tables.py#L71 +export const QR_SIZE: Record = { + 1: 21, + 2: 25, + 3: 29, + 4: 33, + 5: 37, + 6: 41, + 7: 45, + 8: 49, + 9: 53, + 10: 57, + 11: 61, + 12: 65, + 13: 69, + 14: 73, + 15: 77, + 16: 81, + 17: 85, + 18: 89, + 19: 93, + 20: 97, + 21: 101, + 22: 105, + 23: 109, + 24: 113, + 25: 117, + 26: 121, + 27: 125, + 28: 129, + 29: 133, + 30: 137, + 31: 141, + 32: 145, + 33: 149, + 34: 153, + 35: 157, + 36: 161, + 37: 165, + 38: 169, + 39: 173, + 40: 177, +} as const; + +// EOF diff --git a/blue_modules/bbqr/join.ts b/blue_modules/bbqr/join.ts new file mode 100644 index 00000000000..e14396051f3 --- /dev/null +++ b/blue_modules/bbqr/join.ts @@ -0,0 +1,81 @@ +/** + * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain. + * + * QR code decoding/joining. + */ + +import { ENCODINGS } from './consts'; +import { Encoding, JoinResult } from './types'; +import { decodeData } from './utils'; + +/** + * Decodes and joins QR code parts back to binary data. + * + * @param parts Array of QR code parts + * @returns Object containing the file type, encoding, and raw binary data. + */ +export function joinQRs(parts: string[]): JoinResult { + const headers = new Set(parts.map(p => p.slice(0, 6))); + + if (headers.size !== 1) { + throw new Error('conflicting/variable filetype/encodings/sizes'); + } + + const header = [...headers][0]; + + if (header.slice(0, 2) !== 'B$') { + throw new Error('fixed header not found, expected B$'); + } + + if (!ENCODINGS.has(header[2])) { + throw new Error(`bad encoding: ${header[2]}`); + } + + const encoding = header[2] as Encoding; + const fileType = header[3]; + + if (!/^[A-Z]$/.test(fileType)) { + throw new Error('fileType must be a single uppercase letter'); + } + + const numParts = parseInt(header.slice(4, 6), 36); + + if (numParts < 1) { + throw new Error('zero parts?'); + } + + const data = new Map(); + + for (const p of parts) { + const idx = parseInt(p.slice(6, 8), 36); + + if (idx >= numParts) { + throw new Error(`got part ${idx} but only expecting ${numParts}`); + } + + if (data.has(idx) && data.get(idx) !== p.slice(8)) { + throw new Error(`Duplicate part 0x${idx.toString(16)} has wrong content`); + } + + data.set(idx, p.slice(8)); + } + + const orderedParts = []; + + for (let i = 0; i < numParts; i++) { + const p = data.get(i); + + if (!p) { + throw new Error(`Part ${i} is missing`); + } + + orderedParts.push(p); + } + + const raw = decodeData(orderedParts, encoding); + + // @ts-ignore + return { fileType, encoding, raw }; +} + +// EOF diff --git a/blue_modules/bbqr/main.ts b/blue_modules/bbqr/main.ts new file mode 100644 index 00000000000..72e24cd541b --- /dev/null +++ b/blue_modules/bbqr/main.ts @@ -0,0 +1,14 @@ +/** + * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain. + * + * Main entry point for the library. + */ + +// import { renderQRImage } from './image.ts'; +import { joinQRs } from './join.ts'; +import { detectFileType, splitQRs } from './split.ts'; + +export * from './types'; +export { detectFileType, joinQRs, splitQRs }; + +// EOF diff --git a/blue_modules/bbqr/split.ts b/blue_modules/bbqr/split.ts new file mode 100644 index 00000000000..d58f441bcf5 --- /dev/null +++ b/blue_modules/bbqr/split.ts @@ -0,0 +1,202 @@ +/** + * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain. + * + * Splitting of data and encoding as BBQr QR codes. + */ + +import { ENCODING_SPLIT_MOD, HEADER_LEN } from './consts'; +import { Encoding, FileType, SplitOptions, SplitResult, Version } from './types'; +import { + base64ToBytes, + encodeData, + fileToBytes, + hexToBytes, + intToBase36, + looksLikePsbt, + validateSplitOptions, + versionToChars, +} from './utils'; + +function numQRNeeded(version: Version, length: number, encoding: Encoding) { + const splitMod = ENCODING_SPLIT_MOD[encoding]; + + const baseCap = versionToChars(version) - HEADER_LEN; + + // adjust capacity to be a multiple of splitMod + const adjustedCap = baseCap - (baseCap % splitMod); + + const estimatedCount = Math.ceil(length / adjustedCap); + + if (estimatedCount === 1) { + // if it fits in one QR, we're done + return { count: 1, perEach: length }; + } + + // the total capacity of our estimated count + // all but the last QR need to use adjusted capacity to ensure proper split + const estimatedCap = (estimatedCount - 1) * adjustedCap + baseCap; + + return { + count: estimatedCap >= length ? estimatedCount : estimatedCount + 1, + perEach: adjustedCap, + }; +} + +function findBestVersion(length: number, opts: Required) { + const options: { version: Version; count: number; perEach: number }[] = []; + + for (let version = opts.minVersion; version <= opts.maxVersion; version++) { + const { count, perEach } = numQRNeeded(version, length, opts.encoding); + + if (opts.minSplit <= count && count <= opts.maxSplit) { + options.push({ version, count, perEach }); + } + } + + if (!options.length) { + throw new Error('Cannot make it fit'); + } + + // pick smallest number of QR, lowest version + options.sort((a, b) => a.count - b.count || a.version - b.version); + + return options[0]; +} + +/** + * Converts the input bytes into a series of QR codes, ensuring that the most efficient QR code + * version is used. + * + * NOTE: When the default 'Z' (Zlib) encoding is selected, it is possible that the actual used encoding + * will be '2' (Base32) in case Zlib compression does not reduce the size of the output. + * + * @param raw The input bytes to split and encode. + * @param fileType The file type to use. Refer to BBQr spec. + * @param opts An optional SplitOptions object. + * + * @returns An object containing the version of the QR codes, their string parts, and the actual encoding used. + */ +export function splitQRs( + raw: Uint8Array, + fileType: string, + opts: SplitOptions = {} +): SplitResult { + if (!/^[A-Z]$/.test(fileType)) { + throw new Error('fileType must be a single uppercase letter A-Z'); + } + + const validatedOpts = validateSplitOptions(opts); + + const { encoding: actualEncoding, encoded } = encodeData(raw, validatedOpts.encoding); + + const { version, count, perEach } = findBestVersion(encoded.length, validatedOpts); + + const parts: string[] = []; + + for (let n = 0, offset = 0; offset < encoded.length; n++, offset += perEach) { + parts.push( + `B$${actualEncoding}${fileType}` + + intToBase36(count) + + intToBase36(n) + + encoded.slice(offset, offset + perEach) + ); + } + + return { version, parts, encoding: actualEncoding }; +} + +/** + * Takes a given given input (Uint8Array, File, or string) and detects its FileType. + * PSBTs and Bitcoin transactions are supported in raw binary, Base64, or hex format. + * + * @param input - The input to detect the FileType of. + * @returns A Promise that resolves to an object containing the FileType and raw data. + */ +export async function detectFileType( + input: File | Uint8Array | string +): Promise<{ fileType: FileType; raw: Uint8Array }> { + // keep references to both raw and decoded versions of the input to run checks on + let raw: Uint8Array | undefined = undefined; + let decoded: string | undefined = undefined; + + if (input instanceof File) { + // convert a File to Uint8Array so we have access to the raw bytes + input = await fileToBytes(input); + } + + if (input instanceof Uint8Array) { + // we got binary, see if we recognize it + raw = input; + + if (looksLikePsbt(input)) { + console.debug('Detected type "P" from binary input'); + return { fileType: 'P', raw }; + } + + if (raw[0] === 0x01 || raw[0] === 0x02) { + console.debug('Detected type "T" from binary input'); + return { fileType: 'T', raw }; + } + + // otherwise, try to decode as text (could be contents of a file) + try { + decoded = new TextDecoder('utf-8', { fatal: true }).decode(raw); + } catch (err) { + // not text, so fall back to generic binary + + console.debug('Detected type "B" from binary input'); + return { fileType: 'B', raw }; + } + } else if (typeof input === 'string') { + decoded = input; + } else { + throw new Error('Invalid input - must be a File, Uint8Array or string'); + } + + const trimmed = decoded.trim(); + + if (/^70736274ff[0-9A-Fa-f]+$/.test(trimmed)) { + // PSBT in hex format + console.debug('Detected type "P" from hex input'); + + return { fileType: 'P', raw: hexToBytes(trimmed) }; + } + + if (/^0[1,2]000000[0-9A-Fa-f]+$/.test(trimmed)) { + // Transaction in hex format + console.debug('Detected type "T" from hex input'); + + return { fileType: 'T', raw: hexToBytes(trimmed) }; + } + + if (/^[A-Za-z0-9+/=]+$/.test(trimmed)) { + // looks like base64 - could be PSBT or transaction + const bytes = base64ToBytes(decoded); + + if (looksLikePsbt(bytes)) { + console.debug('Detected type "P" from base64 input'); + return { fileType: 'P', raw: bytes }; + } + + if (bytes[0] === 0x01 || bytes[0] === 0x02) { + console.debug('Detected type "T" from base64 input'); + return { fileType: 'T', raw: bytes }; + } + } + + // ensure we have raw bytes for the next step + raw = raw ?? new TextEncoder().encode(decoded); + + try { + JSON.parse(decoded); + console.debug('Detected type "J"'); + return { fileType: 'J', raw }; + } catch (err) { + // not JSON - fall back to generic Unicode + + console.debug('Detected type "U"'); + return { fileType: 'U', raw }; + } +} + +// EOF diff --git a/blue_modules/bbqr/types.ts b/blue_modules/bbqr/types.ts new file mode 100644 index 00000000000..36ebb48418e --- /dev/null +++ b/blue_modules/bbqr/types.ts @@ -0,0 +1,90 @@ +/** + * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain. + * + * Types + */ + +import { ENCODING_NAMES, FILETYPE_NAMES, QR_DATA_CAPACITY } from './consts'; + +export type FileType = keyof typeof FILETYPE_NAMES; +export type Encoding = keyof typeof ENCODING_NAMES; +export type Version = keyof typeof QR_DATA_CAPACITY; + +export type SplitOptions = { + /** + * The encoding to use for the split. + * @default 'Z' + */ + encoding?: Encoding; + /** + * The minimum number of QR codes to use. + * @default 1 + */ + minSplit?: number; + /** + * The maximum number of QR codes to use. + * @default 1295 + */ + maxSplit?: number; + /** + * The minimum version of QR code to use. + * @default 5 + */ + minVersion?: Version; + /** + * The maximum version of QR code to use. + * @default 40 + */ + maxVersion?: Version; +}; + +export type SplitResult = { + version: Version; + parts: string[]; + encoding: Encoding; +}; + +export type JoinResult = { + fileType: string; + encoding: Encoding; + raw: Uint8Array; +}; + +export type ImageOptions = { + /** + * The type of PNG image to render: + * + * - `animated`: An animated PNG (APNG) with a delay between frames. + * - `stacked`: A single PNG image with QR codes stacked vertically. + * + * @default `animated` + */ + mode?: 'animated' | 'stacked'; + /** + * The delay between frames in the animated PNG in milliseconds. + * Ignored if `mode` is `stacked`. + * @default 250 + */ + frameDelay?: number; + /** + * Whether to randomize the order of the parts. + * Ignored if `mode` is `stacked`. + * @default false. + */ + randomizeOrder?: boolean; + /** + * The scale factor of of the QR code images. + * A scale of 1 means 1 pixel per QR module (black dot). + * @default 4 + */ + scale?: number; + /** + * The margin or "quiet zone" around the QR code. + * Numeric values are interpreted as number of modules. + * Percentage values like `10%` are interpreted as a percentage of the QR code size. + * @default 4 + */ + margin?: number | `${number}%`; +}; + +// EOF diff --git a/blue_modules/bbqr/utils.ts b/blue_modules/bbqr/utils.ts new file mode 100644 index 00000000000..d442a058889 --- /dev/null +++ b/blue_modules/bbqr/utils.ts @@ -0,0 +1,212 @@ +/** + * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain. + * + * Helper/utility functions. + */ + +import { base32 } from '@scure/base'; +// @ts-ignore not installing types +import pako from 'pako'; +import { QR_DATA_CAPACITY } from './consts'; +import type { Encoding, SplitOptions, Version } from './types'; + +export function hexToBytes(hex: string) { + // convert a hex string to a Uint8Array + + const match = hex.match(/.{1,2}/g) ?? []; + + return Uint8Array.from(match.map(byte => parseInt(byte, 16))); +} + +export function base64ToBytes(base64: string) { + // convert a base64 string to a Uint8Array + + const binaryString = atob(base64); + const len = binaryString.length; + const bytes = new Uint8Array(len); + + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + return bytes; +} + +export function intToBase36(n: number) { + // convert an integer 0-1295 to two digits of base 36 - 00-ZZ + + if (n < 0 || n > 1295 || !Number.isInteger(n)) { + throw new Error('Out of range'); + } + + return n.toString(36).toUpperCase().padStart(2, '0'); +} + +export async function fileToBytes(file: File) { + // read a File's contents and return as a Uint8Array + + const reader = new FileReader(); + + return new Promise((resolve, reject) => { + reader.onload = e => { + const result = e.target?.result; + + if (result instanceof ArrayBuffer) { + resolve(new Uint8Array(result)); + } else { + reject(new Error('FileReader result is not an ArrayBuffer')); + } + }; + + reader.readAsArrayBuffer(file); + }); +} + +function joinByteParts(parts: Uint8Array[]) { + // perf-optimized way to join Uint8Arrays + + const length = parts.reduce((acc, bytes) => acc + bytes.length, 0); + + const rv = new Uint8Array(length); + + let offset = 0; + for (const bytes of parts) { + rv.set(bytes, offset); + offset += bytes.length; + } + + return rv; +} + +export function isValidVersion(v: number): v is Version { + // act as a TS type guard but also a runtime check + + return v in QR_DATA_CAPACITY; +} + +export function isValidSplit(s: number) { + return s >= 1 && s <= 1295; +} + +export function validateSplitOptions(opts: SplitOptions) { + // ensure all split options are valid, filling in defaults as needed + + const allOpts = { + minVersion: opts.minVersion ?? 5, + maxVersion: opts.maxVersion ?? 40, + minSplit: opts.minSplit ?? 1, + maxSplit: opts.maxSplit ?? 1295, + encoding: opts.encoding ?? 'Z', + } as const; + + if (allOpts.minVersion > allOpts.maxVersion || !isValidVersion(allOpts.minVersion) || !isValidVersion(allOpts.maxVersion)) { + throw new Error('min/max version out of range'); + } + + if (!isValidSplit(allOpts.minSplit) || !isValidSplit(allOpts.maxSplit) || allOpts.minSplit > allOpts.maxSplit) { + throw new Error('min/max split out of range'); + } + + return allOpts; +} + +export function looksLikePsbt(data: Uint8Array) { + try { + // 'psbt' + 0xff + return new Uint8Array([0x70, 0x73, 0x62, 0x74, 0xff]).every((b, i) => b === data[i]); + } catch (err) { + return false; + } +} + +export function shuffled(arr: T[]): T[] { + // modern Fisher-Yates shuffle (https://en.wikipedia.org/wiki/Fisher–Yates_shuffle#The_modern_algorithm) + + // create a copy so we don't mutate the original + arr = [...arr]; + + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + const temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } + + return arr; +} + +export function versionToChars(v: Version) { + // return number of **chars** that fit into indicated version QR + // - assumes L for ECC + // - assumes alnum encoding + + if (!isValidVersion(v)) { + throw new Error('Invalid version'); + } + + const ecc = 'L'; + const encoding = 2; // alnum + + return QR_DATA_CAPACITY[v][ecc][encoding]; +} + +export function encodeData(raw: Uint8Array, encoding?: Encoding) { + // return new encoding (if we upgraded) and the + // characters after encoding (a string) + // - default is Zlib or if compression doesn't help, base32 + // - returned data can be split, but must be done modX where X provided + + encoding = encoding ?? 'Z'; + + if (encoding === 'H') { + return { + encoding, + encoded: raw.reduce((acc, byte) => acc + byte.toString(16).padStart(2, '0'), '').toUpperCase(), + }; + } + + if (encoding === 'Z') { + // trial compression, but skip if it embiggens the data + + const compressed = pako.deflate(raw, { windowBits: -10 }); + + // @ts-ignore wont install types + if (compressed.length >= raw.length) { + encoding = '2'; + } else { + encoding = 'Z'; + // @ts-ignore wont install types + raw = compressed; + } + } + + return { + encoding, + // base32 without padding + encoded: base32.encode(raw).replace(/[=]*$/, ''), + }; +} + +export function decodeData(parts: string[], encoding: Encoding) { + // decode the parts back into a Uint8Array + + if (encoding === 'H') { + return joinByteParts(parts.map(p => hexToBytes(p))); + } + + const bytes = joinByteParts( + parts.map(p => { + const padding = (8 - (p.length % 8)) % 8; + + return base32.decode(p + '='.repeat(padding)); + }), + ); + + if (encoding === 'Z') { + return pako.inflate(bytes, { windowBits: -10 }); + } + + return bytes; +} + +// EOF diff --git a/blue_modules/bc-ur/dist/utils.js b/blue_modules/bc-ur/dist/utils.js index 7bfecf77a15..514588dd898 100644 --- a/blue_modules/bc-ur/dist/utils.js +++ b/blue_modules/bc-ur/dist/utils.js @@ -1,10 +1,18 @@ "use strict"; +import { sha256 as _sha256 } from '@noble/hashes/sha256'; Object.defineProperty(exports, "__esModule", { value: true }); exports.compose3 = exports.sha256Hash = void 0; var bitcoinjs_lib_1 = require("bitcoinjs-lib"); +const {uint8ArrayToHex} = require("../../uint8array-extras"); exports.sha256Hash = function (data) { - return bitcoinjs_lib_1.crypto.sha256(data); + return bitcoinjs_crypto_sha256(data); }; + +function bitcoinjs_crypto_sha256(buffer/*: Buffer*/)/*: Buffer*/ { + return Buffer.from(_sha256(Uint8Array.from(buffer))); +} + + exports.compose3 = function (f, g, h) { return function (x) { return f(g(h(x))); }; }; diff --git a/blue_modules/bip39.js b/blue_modules/bip39.js deleted file mode 100644 index afcffd37872..00000000000 --- a/blue_modules/bip39.js +++ /dev/null @@ -1,22 +0,0 @@ -import * as bip39 from 'bip39'; - -const WORDLISTS = [ - bip39.wordlists.english, - bip39.wordlists.french, - bip39.wordlists.spanish, - bip39.wordlists.italian, - bip39.wordlists.japanese, - bip39.wordlists.korean, - bip39.wordlists.chinese_simplified, - bip39.wordlists.chinese_traditional, - bip39.wordlists.czech, - bip39.wordlists.portuguese, -]; - -export function validateMnemonic(mnemonic) { - for (const wordlist of WORDLISTS) { - const valid = bip39.validateMnemonic(mnemonic, wordlist); - if (valid) return true; - } - return false; -} diff --git a/blue_modules/bip39.ts b/blue_modules/bip39.ts new file mode 100644 index 00000000000..347a0d461c9 --- /dev/null +++ b/blue_modules/bip39.ts @@ -0,0 +1,22 @@ +import * as bip39 from 'bip39'; + +const WORDLISTS: string[][] = [ + bip39.wordlists.english, + bip39.wordlists.french, + bip39.wordlists.spanish, + bip39.wordlists.italian, + bip39.wordlists.japanese, + bip39.wordlists.korean, + bip39.wordlists.chinese_simplified, + bip39.wordlists.chinese_traditional, + bip39.wordlists.czech, + bip39.wordlists.portuguese, +]; + +export function validateMnemonic(mnemonic: string) { + for (const wordlist of WORDLISTS) { + const valid = bip39.validateMnemonic(mnemonic, wordlist); + if (valid) return true; + } + return false; +} diff --git a/blue_modules/checksumWords.ts b/blue_modules/checksumWords.ts new file mode 100644 index 00000000000..494c8cd883f --- /dev/null +++ b/blue_modules/checksumWords.ts @@ -0,0 +1,79 @@ +import * as bip39 from 'bip39'; +import { sha256 } from '@noble/hashes/sha256'; + +// partial (11 or 23 word) seed phrase +export function generateChecksumWords(stringSeedPhrase: string) { + const seedPhrase = stringSeedPhrase.toLowerCase().trim().split(' '); + + if ((seedPhrase.length + 1) % 3 > 0) { + return false; // Partial mnemonic size must be multiple of three words, less one. + } + + const wordList = bip39.wordlists[bip39.getDefaultWordlist()]; + + const concatLenBits = seedPhrase.length * 11; + const concatBits = new Array(concatLenBits); + let wordindex = 0; + for (let i = 0; i < seedPhrase.length; i++) { + const word = seedPhrase[i]; + const ndx = wordList.indexOf(word.toLowerCase()); + if (ndx === -1) return false; + // Set the next 11 bits to the value of the index. + for (let ii = 0; ii < 11; ++ii) { + concatBits[wordindex * 11 + ii] = (ndx & (1 << (10 - ii))) !== 0; // eslint-disable-line no-bitwise + } + ++wordindex; + } + + const checksumLengthBits = (concatLenBits + 11) / 33; + const entropyLengthBits = concatLenBits + 11 - checksumLengthBits; + const varyingLengthBits = entropyLengthBits - concatLenBits; + const numPermutations = 2 ** varyingLengthBits; + + const bitPermutations = new Array(numPermutations); + + for (let i = 0; i < numPermutations; i++) { + if (bitPermutations[i] === undefined || bitPermutations[i] === null) bitPermutations[i] = new Array(varyingLengthBits); + for (let j = 0; j < varyingLengthBits; j++) { + bitPermutations[i][j] = ((i >> j) & 1) === 1; // eslint-disable-line no-bitwise + } + } + + const possibleWords = []; + for (let i = 0; i < bitPermutations.length; i++) { + const bitPermutation = bitPermutations[i]; + const entropyBits = new Array(concatLenBits + varyingLengthBits); + entropyBits.splice(0, 0, ...concatBits); + entropyBits.splice(concatBits.length, 0, ...bitPermutation.slice(0, varyingLengthBits)); + + const entropy = new Array(entropyLengthBits / 8); + for (let ii = 0; ii < entropy.length; ++ii) { + for (let jj = 0; jj < 8; ++jj) { + if (entropyBits[ii * 8 + jj]) { + entropy[ii] |= 1 << (7 - jj); // eslint-disable-line no-bitwise + } + } + } + + const hash = sha256(new Uint8Array(entropy)); + + const hashBits = new Array(hash.length * 8); + for (let iq = 0; iq < hash.length; ++iq) for (let jq = 0; jq < 8; ++jq) hashBits[iq * 8 + jq] = (hash[iq] & (1 << (7 - jq))) !== 0; // eslint-disable-line no-bitwise + + const wordBits = new Array(11); + wordBits.splice(0, 0, ...bitPermutation.slice(0, varyingLengthBits)); + wordBits.splice(varyingLengthBits, 0, ...hashBits.slice(0, checksumLengthBits)); + + let index = 0; + for (let j = 0; j < 11; ++j) { + index <<= 1; // eslint-disable-line no-bitwise + if (wordBits[j]) { + index |= 0x1; // eslint-disable-line no-bitwise + } + } + + possibleWords.push(wordList[index]); + } + + return possibleWords; +} diff --git a/blue_modules/clipboard.ts b/blue_modules/clipboard.ts index 8a8994834fd..e26409d7c8d 100644 --- a/blue_modules/clipboard.ts +++ b/blue_modules/clipboard.ts @@ -1,42 +1,40 @@ -import { useAsyncStorage } from '@react-native-async-storage/async-storage'; +import AsyncStorage from '@react-native-async-storage/async-storage'; import Clipboard from '@react-native-clipboard/clipboard'; -const BlueClipboard = () => { - const STORAGE_KEY = 'ClipboardReadAllowed'; - const { getItem, setItem } = useAsyncStorage(STORAGE_KEY); +const STORAGE_KEY: string = 'ClipboardReadAllowed'; - const isReadClipboardAllowed = async () => { - try { - const clipboardAccessAllowed = await getItem(); - if (clipboardAccessAllowed === null) { - await setItem(JSON.stringify(true)); - return true; - } - return !!JSON.parse(clipboardAccessAllowed); - } catch { - await setItem(JSON.stringify(true)); +export const isReadClipboardAllowed = async (): Promise => { + try { + const clipboardAccessAllowed = await AsyncStorage.getItem(STORAGE_KEY); + if (clipboardAccessAllowed === null) { + await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(true)); return true; } - }; + return !!JSON.parse(clipboardAccessAllowed); + } catch { + await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(true)); + return true; + } +}; - const setReadClipboardAllowed = (value: boolean) => { - setItem(JSON.stringify(!!value)); - }; +export const setReadClipboardAllowed = async (value: boolean): Promise => { + try { + await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(Boolean(value))); + } catch (error) { + console.error('Failed to set clipboard permission:', error); + throw error; + } +}; - const getClipboardContent = async () => { +export const getClipboardContent = async (): Promise => { + try { const isAllowed = await isReadClipboardAllowed(); - if (isAllowed) { - return Clipboard.getString(); - } else { - return ''; - } - }; + if (!isAllowed) return undefined; - return { - isReadClipboardAllowed, - setReadClipboardAllowed, - getClipboardContent, - }; + const hasString = await Clipboard.hasString(); + return hasString ? await Clipboard.getString() : undefined; + } catch (error) { + console.error('Error accessing clipboard:', error); + return undefined; + } }; - -export default BlueClipboard; diff --git a/blue_modules/constants.js b/blue_modules/constants.js deleted file mode 100644 index 3066fb6b3de..00000000000 --- a/blue_modules/constants.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Let's keep config vars, constants and definitions here - */ - -export const groundControlUri = 'https://groundcontrol-bluewallet.herokuapp.com/'; diff --git a/blue_modules/constants.ts b/blue_modules/constants.ts new file mode 100644 index 00000000000..ef45d194d38 --- /dev/null +++ b/blue_modules/constants.ts @@ -0,0 +1,5 @@ +/** + * Let's keep config vars, constants and definitions here + */ + +export const groundControlUri: string = 'https://groundcontrol-bluewallet.herokuapp.com'; diff --git a/blue_modules/currency.js b/blue_modules/currency.js deleted file mode 100644 index 7c3424a43ea..00000000000 --- a/blue_modules/currency.js +++ /dev/null @@ -1,260 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import DefaultPreference from 'react-native-default-preference'; -import * as RNLocalize from 'react-native-localize'; -import BigNumber from 'bignumber.js'; -import { FiatUnit, getFiatRate } from '../models/fiatUnit'; -import WidgetCommunication from './WidgetCommunication'; - -const PREFERRED_CURRENCY_STORAGE_KEY = 'preferredCurrency'; -const EXCHANGE_RATES_STORAGE_KEY = 'currency'; - -let preferredFiatCurrency = FiatUnit.USD; -let exchangeRates = { LAST_UPDATED_ERROR: false }; -let lastTimeUpdateExchangeRateWasCalled = 0; -let skipUpdateExchangeRate = false; - -const LAST_UPDATED = 'LAST_UPDATED'; - -/** - * Saves to storage preferred currency, whole object - * from `./models/fiatUnit` - * - * @param item {Object} one of the values in `./models/fiatUnit` - * @returns {Promise} - */ -async function setPrefferedCurrency(item) { - await AsyncStorage.setItem(PREFERRED_CURRENCY_STORAGE_KEY, JSON.stringify(item)); - await DefaultPreference.setName('group.io.bluewallet.bluewallet'); - await DefaultPreference.set('preferredCurrency', item.endPointKey); - await DefaultPreference.set('preferredCurrencyLocale', item.locale.replace('-', '_')); - WidgetCommunication.reloadAllTimelines(); -} - -async function getPreferredCurrency() { - const preferredCurrency = await JSON.parse(await AsyncStorage.getItem(PREFERRED_CURRENCY_STORAGE_KEY)); - await DefaultPreference.setName('group.io.bluewallet.bluewallet'); - await DefaultPreference.set('preferredCurrency', preferredCurrency.endPointKey); - await DefaultPreference.set('preferredCurrencyLocale', preferredCurrency.locale.replace('-', '_')); - return preferredCurrency; -} - -async function _restoreSavedExchangeRatesFromStorage() { - try { - exchangeRates = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)); - if (!exchangeRates) exchangeRates = { LAST_UPDATED_ERROR: false }; - } catch (_) { - exchangeRates = { LAST_UPDATED_ERROR: false }; - } -} - -async function _restoreSavedPreferredFiatCurrencyFromStorage() { - try { - preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(PREFERRED_CURRENCY_STORAGE_KEY)); - if (preferredFiatCurrency === null) { - throw Error('No Preferred Fiat selected'); - } - - preferredFiatCurrency = FiatUnit[preferredFiatCurrency.endPointKey] || preferredFiatCurrency; - // ^^^ in case configuration in json file changed (and is different from what we stored) we reload it - } catch (_) { - const deviceCurrencies = RNLocalize.getCurrencies(); - if (Object.keys(FiatUnit).some(unit => unit === deviceCurrencies[0])) { - preferredFiatCurrency = FiatUnit[deviceCurrencies[0]]; - } else { - preferredFiatCurrency = FiatUnit.USD; - } - } -} - -/** - * actual function to reach api and get fresh currency exchange rate. checks LAST_UPDATED time and skips entirely - * if called too soon (30min); saves exchange rate (with LAST_UPDATED info) to storage. - * should be called when app thinks its a good time to refresh exchange rate - * - * @return {Promise} - */ -async function updateExchangeRate() { - if (skipUpdateExchangeRate) return; - if (+new Date() - lastTimeUpdateExchangeRateWasCalled <= 10 * 1000) { - // simple debounce so theres no race conditions - return; - } - lastTimeUpdateExchangeRateWasCalled = +new Date(); - - if (+new Date() - exchangeRates[LAST_UPDATED] <= 30 * 60 * 1000) { - // not updating too often - return; - } - console.log('updating exchange rate...'); - - let rate; - try { - rate = await getFiatRate(preferredFiatCurrency.endPointKey); - exchangeRates[LAST_UPDATED] = +new Date(); - exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = rate; - exchangeRates.LAST_UPDATED_ERROR = false; - await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(exchangeRates)); - } catch (Err) { - console.log('Error encountered when attempting to update exchange rate...'); - console.warn(Err.message); - rate = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)); - rate.LAST_UPDATED_ERROR = true; - exchangeRates.LAST_UPDATED_ERROR = true; - await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(rate)); - throw Err; - } -} - -async function isRateOutdated() { - try { - const rate = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)); - return rate.LAST_UPDATED_ERROR || +new Date() - rate.LAST_UPDATED >= 31 * 60 * 1000; - } catch { - return true; - } -} - -/** - * this function reads storage and restores current preferred fiat currency & last saved exchange rate, then calls - * updateExchangeRate() to update rates. - * should be called when the app starts and when user changes preferred fiat (with TRUE argument so underlying - * `updateExchangeRate()` would actually update rates via api). - * - * @param clearLastUpdatedTime {boolean} set to TRUE for the underlying - * - * @return {Promise} - */ -async function init(clearLastUpdatedTime = false) { - await _restoreSavedExchangeRatesFromStorage(); - await _restoreSavedPreferredFiatCurrencyFromStorage(); - - if (clearLastUpdatedTime) { - exchangeRates[LAST_UPDATED] = 0; - lastTimeUpdateExchangeRateWasCalled = 0; - } - - return updateExchangeRate(); -} - -function satoshiToLocalCurrency(satoshi, format = true) { - if (!exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]) { - updateExchangeRate(); - return '...'; - } - - let b = new BigNumber(satoshi).dividedBy(100000000).multipliedBy(exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]); - - if (b.isGreaterThanOrEqualTo(0.005) || b.isLessThanOrEqualTo(-0.005)) { - b = b.toFixed(2); - } else { - b = b.toPrecision(2); - } - - if (format === false) return b; - - let formatter; - try { - formatter = new Intl.NumberFormat(preferredFiatCurrency.locale, { - style: 'currency', - currency: preferredFiatCurrency.endPointKey, - minimumFractionDigits: 2, - maximumFractionDigits: 8, - }); - } catch (error) { - console.warn(error); - console.log(error); - formatter = new Intl.NumberFormat(FiatUnit.USD.locale, { - style: 'currency', - currency: preferredFiatCurrency.endPointKey, - minimumFractionDigits: 2, - maximumFractionDigits: 8, - }); - } - - return formatter.format(b); -} - -function BTCToLocalCurrency(bitcoin) { - let sat = new BigNumber(bitcoin); - sat = sat.multipliedBy(100000000).toNumber(); - - return satoshiToLocalCurrency(sat); -} - -async function mostRecentFetchedRate() { - const currencyInformation = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)); - - const formatter = new Intl.NumberFormat(preferredFiatCurrency.locale, { - style: 'currency', - currency: preferredFiatCurrency.endPointKey, - }); - return { - LastUpdated: currencyInformation[LAST_UPDATED], - Rate: formatter.format(currencyInformation[`BTC_${preferredFiatCurrency.endPointKey}`]), - }; -} - -function satoshiToBTC(satoshi) { - let b = new BigNumber(satoshi); - b = b.dividedBy(100000000); - return b.toString(10); -} - -function btcToSatoshi(btc) { - return new BigNumber(btc).multipliedBy(100000000).toNumber(); -} - -function fiatToBTC(fiatFloat) { - let b = new BigNumber(fiatFloat); - b = b.dividedBy(exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]).toFixed(8); - return b; -} - -function getCurrencySymbol() { - return preferredFiatCurrency.symbol; -} - -/** - * Used to mock data in tests - * - * @param {object} currency, one of FiatUnit.* - */ -function _setPreferredFiatCurrency(currency) { - preferredFiatCurrency = currency; -} - -/** - * Used to mock data in tests - * - * @param {string} pair as expected by rest of this module, e.g 'BTC_JPY' or 'BTC_USD' - * @param {number} rate exchange rate - */ -function _setExchangeRate(pair, rate) { - exchangeRates[pair] = rate; -} - -/** - * Used in unit tests, so the `currency` module wont launch actual http request - */ -function _setSkipUpdateExchangeRate() { - skipUpdateExchangeRate = true; -} - -module.exports.updateExchangeRate = updateExchangeRate; -module.exports.init = init; -module.exports.satoshiToLocalCurrency = satoshiToLocalCurrency; -module.exports.fiatToBTC = fiatToBTC; -module.exports.satoshiToBTC = satoshiToBTC; -module.exports.BTCToLocalCurrency = BTCToLocalCurrency; -module.exports.setPrefferedCurrency = setPrefferedCurrency; -module.exports.getPreferredCurrency = getPreferredCurrency; -module.exports.btcToSatoshi = btcToSatoshi; -module.exports.getCurrencySymbol = getCurrencySymbol; -module.exports._setPreferredFiatCurrency = _setPreferredFiatCurrency; // export it to mock data in tests -module.exports._setExchangeRate = _setExchangeRate; // export it to mock data in tests -module.exports._setSkipUpdateExchangeRate = _setSkipUpdateExchangeRate; // export it to mock data in tests -module.exports.PREFERRED_CURRENCY = PREFERRED_CURRENCY_STORAGE_KEY; -module.exports.EXCHANGE_RATES = EXCHANGE_RATES_STORAGE_KEY; -module.exports.LAST_UPDATED = LAST_UPDATED; -module.exports.mostRecentFetchedRate = mostRecentFetchedRate; -module.exports.isRateOutdated = isRateOutdated; diff --git a/blue_modules/currency.ts b/blue_modules/currency.ts new file mode 100644 index 00000000000..acdd3ba158f --- /dev/null +++ b/blue_modules/currency.ts @@ -0,0 +1,402 @@ +import BigNumber from 'bignumber.js'; +import DefaultPreference from 'react-native-default-preference'; +import * as RNLocalize from 'react-native-localize'; + +import { FiatUnit, FiatUnitType, getFiatRate } from '../models/fiatUnit'; + +const PREFERRED_CURRENCY_STORAGE_KEY = 'preferredCurrency'; +const PREFERRED_CURRENCY_LOCALE_STORAGE_KEY = 'preferredCurrencyLocale'; +const EXCHANGE_RATES_STORAGE_KEY = 'exchangeRates'; +const LAST_UPDATED = 'LAST_UPDATED'; +export const GROUP_IO_BLUEWALLET = 'group.io.bluewallet.bluewallet'; +const BTC_PREFIX = 'BTC_'; + +export interface CurrencyRate { + LastUpdated: Date | null; + Rate: number | string | null; +} + +interface ExchangeRates { + [key: string]: number | boolean | undefined; + LAST_UPDATED_ERROR: boolean; +} + +let preferredFiatCurrency: FiatUnitType = FiatUnit.USD; +let exchangeRates: ExchangeRates = { LAST_UPDATED_ERROR: false }; +let lastTimeUpdateExchangeRateWasCalled: number = 0; +let skipUpdateExchangeRate: boolean = false; + +let currencyFormatter: Intl.NumberFormat | null = null; + +function getCurrencyFormatter(): Intl.NumberFormat { + if ( + !currencyFormatter || + currencyFormatter.resolvedOptions().locale !== preferredFiatCurrency.locale || + currencyFormatter.resolvedOptions().currency !== preferredFiatCurrency.endPointKey + ) { + currencyFormatter = new Intl.NumberFormat(preferredFiatCurrency.locale, { + style: 'currency', + currency: preferredFiatCurrency.endPointKey, + minimumFractionDigits: 2, + maximumFractionDigits: 8, + }); + console.debug('Created new currency formatter for: ', preferredFiatCurrency); + } + return currencyFormatter; +} + +async function setPreferredCurrency(item: FiatUnitType): Promise { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + try { + await DefaultPreference.set(PREFERRED_CURRENCY_STORAGE_KEY, item.endPointKey); + await DefaultPreference.set(PREFERRED_CURRENCY_LOCALE_STORAGE_KEY, item.locale.replace('-', '_')); + preferredFiatCurrency = FiatUnit[item.endPointKey]; + currencyFormatter = null; // Remove cached formatter + console.debug('Preferred currency set to:', item); + console.debug('Preferred currency locale set to:', item.locale.replace('-', '_')); + console.debug('Cleared all cached currency formatters'); + } catch (error) { + console.error('Failed to set preferred currency:', error); + throw error; + } + currencyFormatter = null; +} + +async function updateExchangeRate(): Promise { + if (skipUpdateExchangeRate) return; + if (Date.now() - lastTimeUpdateExchangeRateWasCalled <= 10000) { + // simple debounce so there's no race conditions + return; + } + lastTimeUpdateExchangeRateWasCalled = Date.now(); + + const lastUpdated = exchangeRates[LAST_UPDATED] as number | undefined; + if (lastUpdated && Date.now() - lastUpdated <= 30 * 60 * 1000) { + // not updating too often + return; + } + console.log('updating exchange rate...'); + + try { + const rate = await getFiatRate(preferredFiatCurrency.endPointKey); + exchangeRates[LAST_UPDATED] = Date.now(); + exchangeRates[BTC_PREFIX + preferredFiatCurrency.endPointKey] = rate; + exchangeRates.LAST_UPDATED_ERROR = false; + + try { + const exchangeRatesString = JSON.stringify(exchangeRates); + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + await DefaultPreference.set(EXCHANGE_RATES_STORAGE_KEY, exchangeRatesString); + } catch (error) { + await DefaultPreference.clear(EXCHANGE_RATES_STORAGE_KEY); + exchangeRates = { LAST_UPDATED_ERROR: false }; + } + } catch (error) { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const ratesValue = await DefaultPreference.get(EXCHANGE_RATES_STORAGE_KEY); + let ratesString: string | null = null; + + if (typeof ratesValue === 'string') { + ratesString = ratesValue; + } + + let rate; + if (ratesString) { + try { + rate = JSON.parse(ratesString); + } catch (parseError) { + await DefaultPreference.clear(EXCHANGE_RATES_STORAGE_KEY); + rate = {}; + } + } else { + rate = {}; + } + rate.LAST_UPDATED_ERROR = true; + exchangeRates.LAST_UPDATED_ERROR = true; + await DefaultPreference.set(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(rate)); + } catch (storageError) { + exchangeRates = { LAST_UPDATED_ERROR: true }; + throw storageError; + } + } +} + +async function getPreferredCurrency(): Promise { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const preferredCurrencyValue = await DefaultPreference.get(PREFERRED_CURRENCY_STORAGE_KEY); + let preferredCurrency: string | null = null; + + if (typeof preferredCurrencyValue === 'string') { + preferredCurrency = preferredCurrencyValue; + } + + if (preferredCurrency) { + try { + if (!FiatUnit[preferredCurrency]) { + throw new Error('Invalid Fiat Unit'); + } + preferredFiatCurrency = FiatUnit[preferredCurrency]; + } catch (error) { + await DefaultPreference.clear(PREFERRED_CURRENCY_STORAGE_KEY); + } + } + + if (!preferredFiatCurrency) { + const deviceCurrencies = RNLocalize.getCurrencies(); + if (deviceCurrencies[0] && FiatUnit[deviceCurrencies[0]]) { + preferredFiatCurrency = FiatUnit[deviceCurrencies[0]]; + } else { + preferredFiatCurrency = FiatUnit.USD; + } + } + + await DefaultPreference.set(PREFERRED_CURRENCY_LOCALE_STORAGE_KEY, preferredFiatCurrency.locale.replace('-', '_')); + return preferredFiatCurrency; +} + +async function _restoreSavedExchangeRatesFromStorage(): Promise { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const ratesValue = await DefaultPreference.get(EXCHANGE_RATES_STORAGE_KEY); + let ratesString: string | null = null; + + if (typeof ratesValue === 'string') { + ratesString = ratesValue; + } + + if (ratesString) { + try { + const parsedRates = JSON.parse(ratesString); + // Atomic update to prevent race conditions + exchangeRates = parsedRates; + } catch (error) { + await DefaultPreference.clear(EXCHANGE_RATES_STORAGE_KEY); + exchangeRates = { LAST_UPDATED_ERROR: false }; + // Add delay before update to prevent rapid consecutive calls + await new Promise(resolve => setTimeout(resolve, 1000)); + await updateExchangeRate(); + } + } else { + exchangeRates = { LAST_UPDATED_ERROR: false }; + } + } catch (error) { + exchangeRates = { LAST_UPDATED_ERROR: false }; + await updateExchangeRate(); + } +} + +async function _restoreSavedPreferredFiatCurrencyFromStorage(): Promise { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const storedCurrencyValue = await DefaultPreference.get(PREFERRED_CURRENCY_STORAGE_KEY); + let storedCurrency: string | null = null; + + if (typeof storedCurrencyValue === 'string') { + storedCurrency = storedCurrencyValue; + } + + if (!storedCurrency) throw new Error('No Preferred Fiat selected'); + + try { + if (!FiatUnit[storedCurrency]) { + throw new Error('Invalid Fiat Unit'); + } + preferredFiatCurrency = FiatUnit[storedCurrency]; + } catch (error) { + await DefaultPreference.clear(PREFERRED_CURRENCY_STORAGE_KEY); + + const deviceCurrencies = RNLocalize.getCurrencies(); + if (deviceCurrencies[0] && FiatUnit[deviceCurrencies[0]]) { + preferredFiatCurrency = FiatUnit[deviceCurrencies[0]]; + } else { + preferredFiatCurrency = FiatUnit.USD; + } + } + } catch (error) { + const deviceCurrencies = RNLocalize.getCurrencies(); + if (deviceCurrencies[0] && FiatUnit[deviceCurrencies[0]]) { + preferredFiatCurrency = FiatUnit[deviceCurrencies[0]]; + } else { + preferredFiatCurrency = FiatUnit.USD; + } + } +} + +async function isRateOutdated(): Promise { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const rateValue = await DefaultPreference.get(EXCHANGE_RATES_STORAGE_KEY); + let rateString: string | null = null; + + if (typeof rateValue === 'string') { + rateString = rateValue; + } + + let rate; + if (rateString) { + try { + rate = JSON.parse(rateString); + } catch (parseError) { + await DefaultPreference.clear(EXCHANGE_RATES_STORAGE_KEY); + rate = {}; + await updateExchangeRate(); + } + } else { + rate = {}; + } + return rate.LAST_UPDATED_ERROR || Date.now() - (rate[LAST_UPDATED] || 0) >= 31 * 60 * 1000; + } catch { + return true; + } +} + +async function restoreSavedPreferredFiatCurrencyAndExchangeFromStorage(): Promise { + await _restoreSavedExchangeRatesFromStorage(); + await _restoreSavedPreferredFiatCurrencyFromStorage(); +} + +async function initCurrencyDaemon(clearLastUpdatedTime: boolean = false): Promise { + await _restoreSavedExchangeRatesFromStorage(); + await _restoreSavedPreferredFiatCurrencyFromStorage(); + + if (clearLastUpdatedTime) { + exchangeRates[LAST_UPDATED] = 0; + lastTimeUpdateExchangeRateWasCalled = 0; + } + + await updateExchangeRate(); +} + +function satoshiToLocalCurrency(satoshi: number, format: boolean = true): string { + const exchangeRateKey = BTC_PREFIX + preferredFiatCurrency.endPointKey; + const exchangeRate = exchangeRates[exchangeRateKey]; + + if (typeof exchangeRate !== 'number') { + updateExchangeRate(); + return '...'; + } + + const btcAmount = new BigNumber(satoshi).dividedBy(100000000); + const convertedAmount = btcAmount.multipliedBy(exchangeRate); + let formattedAmount: string; + + if (convertedAmount.isGreaterThanOrEqualTo(0.005) || convertedAmount.isLessThanOrEqualTo(-0.005)) { + formattedAmount = convertedAmount.toFixed(2); + } else { + formattedAmount = convertedAmount.toPrecision(2); + } + + if (format === false) return formattedAmount; + + try { + return getCurrencyFormatter().format(Number(formattedAmount)); + } catch (error) { + console.error(error); + return formattedAmount; + } +} + +function BTCToLocalCurrency(bitcoin: BigNumber.Value): string { + const sat = new BigNumber(bitcoin).multipliedBy(100000000).toNumber(); + return satoshiToLocalCurrency(sat); +} + +async function mostRecentFetchedRate(): Promise { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const currencyInfoValue = await DefaultPreference.get(EXCHANGE_RATES_STORAGE_KEY); + let currencyInformationString: string | null = null; + + if (typeof currencyInfoValue === 'string') { + currencyInformationString = currencyInfoValue; + } + + let currencyInformation; + if (currencyInformationString) { + try { + currencyInformation = JSON.parse(currencyInformationString); + } catch (parseError) { + await DefaultPreference.clear(EXCHANGE_RATES_STORAGE_KEY); + currencyInformation = {}; + await updateExchangeRate(); + } + } else { + currencyInformation = {}; + } + + const rate = currencyInformation[BTC_PREFIX + preferredFiatCurrency.endPointKey]; + return { + LastUpdated: currencyInformation[LAST_UPDATED] ? new Date(currencyInformation[LAST_UPDATED]) : null, + Rate: rate ? getCurrencyFormatter().format(rate) : '...', + }; + } catch { + return { + LastUpdated: null, + Rate: null, + }; + } +} + +function satoshiToBTC(satoshi: number): string { + return new BigNumber(satoshi).dividedBy(100000000).toString(10); +} + +function btcToSatoshi(btc: BigNumber.Value): number { + return new BigNumber(btc).multipliedBy(100000000).toNumber(); +} + +function fiatToBTC(fiatFloat: number): string { + const exchangeRateKey = BTC_PREFIX + preferredFiatCurrency.endPointKey; + const exchangeRate = exchangeRates[exchangeRateKey]; + + if (typeof exchangeRate !== 'number') { + throw new Error('Exchange rate not available'); + } + + const btcAmount = new BigNumber(fiatFloat).dividedBy(exchangeRate); + return btcAmount.toFixed(8); +} + +function getCurrencySymbol(): string { + return preferredFiatCurrency.symbol; +} + +function formatBTC(btc: BigNumber.Value): string { + return new BigNumber(btc).toFormat(8); +} + +function _setPreferredFiatCurrency(currency: FiatUnitType): void { + preferredFiatCurrency = currency; +} + +function _setExchangeRate(pair: string, rate: number): void { + exchangeRates[pair] = rate; +} + +function _setSkipUpdateExchangeRate(): void { + skipUpdateExchangeRate = true; +} + +export { + _setExchangeRate, + _setPreferredFiatCurrency, + _setSkipUpdateExchangeRate, + BTCToLocalCurrency, + btcToSatoshi, + EXCHANGE_RATES_STORAGE_KEY, + fiatToBTC, + getCurrencySymbol, + getPreferredCurrency, + initCurrencyDaemon, + isRateOutdated, + LAST_UPDATED, + mostRecentFetchedRate, + PREFERRED_CURRENCY_STORAGE_KEY, + restoreSavedPreferredFiatCurrencyAndExchangeFromStorage, + satoshiToBTC, + satoshiToLocalCurrency, + setPreferredCurrency, + updateExchangeRate, + formatBTC, +}; diff --git a/blue_modules/debounce.js b/blue_modules/debounce.js deleted file mode 100644 index dacfdfefd35..00000000000 --- a/blue_modules/debounce.js +++ /dev/null @@ -1,14 +0,0 @@ -// https://levelup.gitconnected.com/debounce-in-javascript-improve-your-applications-performance-5b01855e086 -const debounce = (func, wait) => { - let timeout; - return function executedFunction(...args) { - const later = () => { - timeout = null; - func(...args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; -}; - -export default debounce; diff --git a/blue_modules/debounce.ts b/blue_modules/debounce.ts new file mode 100644 index 00000000000..9e04dbe7b01 --- /dev/null +++ b/blue_modules/debounce.ts @@ -0,0 +1,31 @@ +// https://levelup.gitconnected.com/debounce-in-javascript-improve-your-applications-performance-5b01855e086 +// blue_modules/debounce.ts +type DebouncedFunction void> = { + (this: ThisParameterType, ...args: Parameters): void; + cancel(): void; +}; + +const debounce = void>(func: T, wait: number): DebouncedFunction => { + let timeout: NodeJS.Timeout | null; + const debouncedFunction = function (this: ThisParameterType, ...args: Parameters) { + const later = () => { + timeout = null; + func.apply(this, args); + }; + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(later, wait); + }; + + debouncedFunction.cancel = () => { + if (timeout) { + clearTimeout(timeout); + } + timeout = null; + }; + + return debouncedFunction as DebouncedFunction; +}; + +export default debounce; diff --git a/blue_modules/encryption.js b/blue_modules/encryption.js deleted file mode 100644 index 04d8cb60f9b..00000000000 --- a/blue_modules/encryption.js +++ /dev/null @@ -1,22 +0,0 @@ -const CryptoJS = require('crypto-js'); - -module.exports.encrypt = function (data, password) { - if (data.length < 10) throw new Error('data length cant be < 10'); - const ciphertext = CryptoJS.AES.encrypt(data, password); - return ciphertext.toString(); -}; - -module.exports.decrypt = function (data, password) { - const bytes = CryptoJS.AES.decrypt(data, password); - let str = false; - try { - str = bytes.toString(CryptoJS.enc.Utf8); - } catch (e) {} - - // for some reason, sometimes decrypt would succeed with incorrect password and return random couple of characters. - // at least in nodejs environment. so with this little hack we are not alowing to encrypt data that is shorter than - // 10 characters, and thus if decrypted data is less than 10 characters we assume that decrypt actually failed. - if (str.length < 10) return false; - - return str; -}; diff --git a/blue_modules/encryption.ts b/blue_modules/encryption.ts new file mode 100644 index 00000000000..746926a7616 --- /dev/null +++ b/blue_modules/encryption.ts @@ -0,0 +1,23 @@ +import AES from 'crypto-js/aes'; +import Utf8 from 'crypto-js/enc-utf8'; + +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(); +} + +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; +} diff --git a/blue_modules/environment.ts b/blue_modules/environment.ts index f0985a6e24a..b9301c70160 100644 --- a/blue_modules/environment.ts +++ b/blue_modules/environment.ts @@ -1,41 +1,7 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { Platform } from 'react-native'; -import { isTablet, getDeviceType } from 'react-native-device-info'; +import { getDeviceType, isTablet as checkIsTablet } from 'react-native-device-info'; +const isTablet: boolean = checkIsTablet(); const isDesktop: boolean = getDeviceType() === 'Desktop'; +const isHandset: boolean = getDeviceType() === 'Handset'; -const getIsTorCapable = (): boolean => { - let capable = true; - if (Platform.OS === 'android' && Platform.Version < 26) { - capable = false; - } else if (isDesktop) { - capable = false; - } - return capable; -}; - -const IS_TOR_DAEMON_DISABLED: string = 'is_tor_daemon_disabled'; - -export async function setIsTorDaemonDisabled(disabled: boolean = true): Promise { - return AsyncStorage.setItem(IS_TOR_DAEMON_DISABLED, disabled ? '1' : ''); -} - -export async function isTorDaemonDisabled(): Promise { - let result: boolean; - try { - const savedValue = await AsyncStorage.getItem(IS_TOR_DAEMON_DISABLED); - if (savedValue === null) { - result = false; - } else { - result = savedValue === '1'; - } - } catch { - result = true; - } - - return result; -} - -export const isHandset: boolean = getDeviceType() === 'Handset'; -export const isTorCapable: boolean = getIsTorCapable(); -export { isDesktop, isTablet }; +export { isDesktop, isHandset, isTablet }; diff --git a/blue_modules/fs.js b/blue_modules/fs.js deleted file mode 100644 index fd9a8d8e2f2..00000000000 --- a/blue_modules/fs.js +++ /dev/null @@ -1,210 +0,0 @@ -import { Alert, Linking, PermissionsAndroid, Platform } from 'react-native'; -import RNFS from 'react-native-fs'; -import Share from 'react-native-share'; -import loc from '../loc'; -import DocumentPicker from 'react-native-document-picker'; -import { launchCamera, launchImageLibrary } from 'react-native-image-picker'; -import { presentCameraNotAuthorizedAlert } from '../class/camera'; -import { isDesktop } from '../blue_modules/environment'; -import alert from '../components/Alert'; -const LocalQRCode = require('@remobile/react-native-qrcode-local-image'); - -const writeFileAndExportToAndroidDestionation = async ({ filename, contents, destinationLocalizedString, destination }) => { - const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, { - title: loc.send.permission_storage_title, - message: loc.send.permission_storage_message, - buttonNeutral: loc.send.permission_storage_later, - buttonNegative: loc._.cancel, - buttonPositive: loc._.ok, - }); - if (granted === PermissionsAndroid.RESULTS.GRANTED || Platform.Version >= 33) { - const filePath = destination + `/${filename}`; - try { - await RNFS.writeFile(filePath, contents); - alert(loc.formatString(loc._.file_saved, { filePath: filename, destination: destinationLocalizedString })); - } catch (e) { - console.log(e); - alert(e.message); - } - } else { - console.log('Storage Permission: Denied'); - Alert.alert(loc.send.permission_storage_title, loc.send.permission_storage_denied_message, [ - { - text: loc.send.open_settings, - onPress: () => { - Linking.openSettings(); - }, - style: 'default', - }, - { text: loc._.cancel, onPress: () => {}, style: 'cancel' }, - ]); - } -}; - -const writeFileAndExport = async function (filename, contents) { - if (Platform.OS === 'ios') { - const filePath = RNFS.TemporaryDirectoryPath + `/${filename}`; - await RNFS.writeFile(filePath, contents); - await Share.open({ - url: 'file://' + filePath, - saveToFiles: isDesktop, - }) - .catch(error => { - console.log(error); - }) - .finally(() => { - RNFS.unlink(filePath); - }); - } else if (Platform.OS === 'android') { - await writeFileAndExportToAndroidDestionation({ - filename, - contents, - destinationLocalizedString: loc._.downloads_folder, - destination: RNFS.DownloadDirectoryPath, - }); - } -}; - -/** - * Opens & reads *.psbt files, and returns base64 psbt. FALSE if something went wrong (wont throw). - * - * @returns {Promise} Base64 PSBT - */ -const openSignedTransaction = async function () { - try { - const res = await DocumentPicker.pickSingle({ - type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] : [DocumentPicker.types.allFiles], - }); - - return await _readPsbtFileIntoBase64(res.uri); - } catch (err) { - if (!DocumentPicker.isCancel(err)) { - alert(loc.send.details_no_signed_tx); - } - } - - return false; -}; - -const _readPsbtFileIntoBase64 = async function (uri) { - const base64 = await RNFS.readFile(uri, 'base64'); - const stringData = Buffer.from(base64, 'base64').toString(); // decode from base64 - if (stringData.startsWith('psbt')) { - // file was binary, but outer code expects base64 psbt, so we return base64 we got from rn-fs; - // most likely produced by Electrum-desktop - return base64; - } else { - // file was a text file, having base64 psbt in there. so we basically have double base64encoded string - // thats why we are returning string that was decoded once; - // most likely produced by Coldcard - return stringData; - } -}; - -const showImagePickerAndReadImage = () => { - return new Promise((resolve, reject) => - launchImageLibrary( - { - title: null, - mediaType: 'photo', - takePhotoButtonTitle: null, - maxHeight: 800, - maxWidth: 600, - selectionLimit: 1, - }, - response => { - if (!response.didCancel) { - const asset = response.assets[0]; - if (asset.uri) { - const uri = asset.uri.toString().replace('file://', ''); - LocalQRCode.decode(uri, (error, result) => { - if (!error) { - resolve(result); - } else { - reject(new Error(loc.send.qr_error_no_qrcode)); - } - }); - } - } - }, - ), - ); -}; - -const takePhotoWithImagePickerAndReadPhoto = () => { - return new Promise((resolve, reject) => - launchCamera( - { - title: null, - mediaType: 'photo', - takePhotoButtonTitle: null, - }, - response => { - if (response.uri) { - const uri = response.uri.toString().replace('file://', ''); - LocalQRCode.decode(uri, (error, result) => { - if (!error) { - resolve(result); - } else { - reject(new Error(loc.send.qr_error_no_qrcode)); - } - }); - } else if (response.error) { - presentCameraNotAuthorizedAlert(response.error); - } - }, - ), - ); -}; - -const showFilePickerAndReadFile = async function () { - try { - const res = await DocumentPicker.pickSingle({ - type: - Platform.OS === 'ios' - ? [ - 'io.bluewallet.psbt', - 'io.bluewallet.psbt.txn', - 'io.bluewallet.backup', - DocumentPicker.types.plainText, - 'public.json', - DocumentPicker.types.images, - ] - : [DocumentPicker.types.allFiles], - }); - - const uri = Platform.OS === 'ios' ? decodeURI(res.uri) : res.uri; - // ^^ some weird difference on how spaces in filenames are treated on ios and android - - let file = false; - if (res.uri.toLowerCase().endsWith('.psbt')) { - // this is either binary file from ElectrumDesktop OR string file with base64 string in there - file = await _readPsbtFileIntoBase64(uri); - return { data: file, uri: decodeURI(res.uri) }; - } - - if (res?.type === DocumentPicker.types.images || res?.type?.startsWith('image/')) { - return new Promise(resolve => { - const uri2 = res.uri.toString().replace('file://', ''); - LocalQRCode.decode(decodeURI(uri2), (error, result) => { - if (!error) { - resolve({ data: result, uri: decodeURI(res.uri) }); - } else { - resolve({ data: false, uri: false }); - } - }); - }); - } - - file = await RNFS.readFile(uri); - return { data: file, uri: decodeURI(res.uri) }; - } catch (err) { - return { data: false, uri: false }; - } -}; - -module.exports.writeFileAndExport = writeFileAndExport; -module.exports.openSignedTransaction = openSignedTransaction; -module.exports.showFilePickerAndReadFile = showFilePickerAndReadFile; -module.exports.showImagePickerAndReadImage = showImagePickerAndReadImage; -module.exports.takePhotoWithImagePickerAndReadPhoto = takePhotoWithImagePickerAndReadPhoto; diff --git a/blue_modules/fs.ts b/blue_modules/fs.ts new file mode 100644 index 00000000000..432d03980c3 --- /dev/null +++ b/blue_modules/fs.ts @@ -0,0 +1,237 @@ +import { Platform } from 'react-native'; +import { pick, types, keepLocalCopy, errorCodes } from '@react-native-documents/picker'; +import RNFS from 'react-native-fs'; +import { launchImageLibrary, ImagePickerResponse } from 'react-native-image-picker'; +import { detectQRCodeInImage } from 'react-native-camera-kit-no-google'; +import Share from 'react-native-share'; + +import presentAlert from '../components/Alert'; +import loc from '../loc'; +import { isDesktop } from './environment'; +import { readFile } from './react-native-bw-file-access'; +import { base64ToUint8Array, uint8ArrayToString } from './uint8array-extras/index'; + +const _sanitizeFileName = (fileName: string) => { + // Remove any path delimiters and non-alphanumeric characters except for -, _, and . + return fileName.replace(/[^a-zA-Z0-9\-_.]/g, ''); +}; + +export const isCancel = (err: any): boolean => { + return err.code && err.code === errorCodes.OPERATION_CANCELED; +}; + +const _shareOpen = async (filePath: string, showShareDialog: boolean = false) => { + try { + await Share.open({ + url: 'file://' + filePath, + saveToFiles: isDesktop || !showShareDialog, + // @ts-ignore: Website claims this propertie exists, but TS cant find it. Send anyways. + useInternalStorage: Platform.OS === 'android', + failOnCancel: false, + }); + } catch (error: any) { + console.log(error); + // If user cancels sharing, we dont want to show an error. for some reason we get 'CANCELLED' string as error + if (error.message !== 'CANCELLED') { + presentAlert({ message: error.message }); + } + } finally { + await RNFS.unlink(filePath); + } +}; + +/** + * Writes a file to fs, and triggers an OS sharing dialog, so user can decide where to put this file (share to cloud + * or perhaps messaging app). Provided filename should be just a file name, NOT a path + */ + +export const writeFileAndExport = async function (fileName: string, contents: string, showShareDialog: boolean = true) { + const sanitizedFileName = _sanitizeFileName(fileName); + try { + if (Platform.OS === 'ios') { + const filePath = `${RNFS.TemporaryDirectoryPath}/${sanitizedFileName}`; + await RNFS.writeFile(filePath, contents); + await _shareOpen(filePath, showShareDialog); + } else if (Platform.OS === 'android') { + const filePath = `${RNFS.DownloadDirectoryPath}/${sanitizedFileName}`; + try { + await RNFS.writeFile(filePath, contents); + if (showShareDialog) { + await _shareOpen(filePath); + } else { + presentAlert({ message: loc.formatString(loc.send.file_saved_at_path, { filePath }) }); + } + } catch (e: any) { + console.error(e); + presentAlert({ message: e.message }); + } + } + } catch (error: any) { + console.error(error); + presentAlert({ message: error.message }); + } +}; + +/** + * Opens & reads *.psbt files, and returns base64 psbt. FALSE if something went wrong (wont throw). + */ +export const openSignedTransaction = async function (): Promise { + try { + const [res] = await pick({ + type: [types.allFiles], + }); + + return await _readPsbtFileIntoBase64(res.uri); + } catch (err) { + if (!isCancel(err)) { + presentAlert({ message: loc.send.details_no_signed_tx }); + } + } + + return false; +}; + +const _readPsbtFileIntoBase64 = async function (uri: string): Promise { + const base64 = await RNFS.readFile(uri, 'base64'); + const stringData = uint8ArrayToString(base64ToUint8Array(base64)); // decode from base64 + if (stringData.startsWith('psbt')) { + // file was binary, but outer code expects base64 psbt, so we return base64 we got from rn-fs; + // most likely produced by Electrum-desktop + return base64; + } else { + // file was a text file, having base64 psbt in there. so we basically have double base64encoded string + // thats why we are returning string that was decoded once; + // most likely produced by ColdCard + return stringData; + } +}; + +export const showImagePickerAndReadImage = async (): Promise => { + try { + const response: ImagePickerResponse = await launchImageLibrary({ + mediaType: 'photo', + maxHeight: 800, + maxWidth: 600, + selectionLimit: 1, + includeBase64: true, + }); + + if (response.didCancel) { + return undefined; + } else if (response.errorCode) { + throw new Error(response.errorMessage); + } else if (response.assets) { + const base64 = response.assets[0].base64; + if (base64) { + const result = await detectQRCodeInImage(base64); + if (result) return result; + } + throw new Error(loc.send.qr_error_no_qrcode); + } + + return undefined; + } catch (error: any) { + console.error(error); + throw error; + } +}; + +export const showFilePickerAndReadFile = async function (): Promise<{ data: string | false; uri: string | false }> { + try { + const [pickedFile] = await pick({ + type: [types.allFiles], + }); + + const [localCopy] = await keepLocalCopy({ + files: [ + { + uri: pickedFile.uri, + fileName: pickedFile.name ?? 'unnamed', + }, + ], + destination: 'cachesDirectory', + }); + + if (localCopy.status !== 'success') { + // to make ts happy, should not need this check here + presentAlert({ message: 'Picking and caching a file failed: ' + localCopy.copyError }); + return { data: false, uri: false }; + } + + const fileCopyUri = decodeURI(localCopy.localUri); + + if (localCopy.localUri.toLowerCase().endsWith('.psbt')) { + // this is either binary file from ElectrumDesktop OR string file with base64 string in there + const file = await _readPsbtFileIntoBase64(fileCopyUri); + return { data: file, uri: fileCopyUri }; + } + + if (localCopy.localUri.endsWith('.png') || localCopy.localUri.endsWith('.jpg') || localCopy.localUri.endsWith('.jpeg')) { + return await handleImageFile(fileCopyUri); + } + + const file = await RNFS.readFile(fileCopyUri); + return { data: file, uri: fileCopyUri }; + } catch (err: any) { + if (!isCancel(err)) { + presentAlert({ message: err.message }); + } + return { data: false, uri: false }; + } +}; + +const readFileAsBase64 = async (uri: string): Promise => { + try { + return await RNFS.readFile(uri, 'base64'); + } catch { + return await RNFS.readFile(uri.replace(/^file:\/\//, ''), 'base64'); + } +}; + +const handleImageFile = async (fileCopyUri: string): Promise<{ data: string | false; uri: string | false }> => { + const base64 = await readFileAsBase64(fileCopyUri); + const result = await detectQRCodeInImage(base64); + if (result) { + return { data: result, uri: fileCopyUri }; + } + throw new Error(loc.send.qr_error_no_qrcode); +}; + +export const readFileOutsideSandbox = (filePath: string) => { + if (Platform.OS === 'ios') { + return readFile(filePath); + } else if (Platform.OS === 'android') { + return RNFS.readFile(filePath); + } else { + presentAlert({ message: 'Not implemented for this platform' }); + throw new Error('Not implemented for this platform'); + } +}; + +export const openSignedTransactionRaw: () => Promise = async () => { + try { + const [res] = await pick({ + type: [types.allFiles], + }); + const file = await RNFS.readFile(res.uri); + if (file) { + return file; + } else { + throw new Error('Could not read file'); + } + } catch (err) { + if (!isCancel(err)) { + presentAlert({ message: loc.send.details_no_signed_tx }); + } + + return ''; + } +}; + +export const pickTransaction = async () => { + const [res] = await pick({ + type: [types.allFiles], + }); + + return res; +}; diff --git a/blue_modules/hapticFeedback.ts b/blue_modules/hapticFeedback.ts new file mode 100644 index 00000000000..4d9bfd28076 --- /dev/null +++ b/blue_modules/hapticFeedback.ts @@ -0,0 +1,43 @@ +import DeviceInfo, { PowerState } from 'react-native-device-info'; +import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; +import { isDesktop } from './environment'; + +// Define a const enum for HapticFeedbackTypes +export const enum HapticFeedbackTypes { + ImpactLight = 'impactLight', + ImpactMedium = 'impactMedium', + ImpactHeavy = 'impactHeavy', + Selection = 'selection', + NotificationSuccess = 'notificationSuccess', + NotificationWarning = 'notificationWarning', + NotificationError = 'notificationError', +} + +const triggerHapticFeedback = (type: HapticFeedbackTypes) => { + if (isDesktop) return; + DeviceInfo.getPowerState().then((state: Partial) => { + if (!state.lowPowerMode) { + ReactNativeHapticFeedback.trigger(type, { ignoreAndroidSystemSettings: false, enableVibrateFallback: true }); + } else { + console.log('Haptic feedback not triggered due to low power mode.'); + } + }); +}; + +export const triggerSuccessHapticFeedback = () => { + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); +}; + +export const triggerWarningHapticFeedback = () => { + triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning); +}; + +export const triggerErrorHapticFeedback = () => { + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); +}; + +export const triggerSelectionHapticFeedback = () => { + triggerHapticFeedback(HapticFeedbackTypes.Selection); +}; + +export default triggerHapticFeedback; diff --git a/blue_modules/net.js b/blue_modules/net.js deleted file mode 100644 index 44d5c0f3a8c..00000000000 --- a/blue_modules/net.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @fileOverview adapter for ReactNative TCP module - * This module mimics the nodejs net api and is intended to work in RN environment. - * @see https://github.com/Rapsssito/react-native-tcp-socket - */ - -import TcpSocket from 'react-native-tcp-socket'; - -/** - * Constructor function. Resulting object has to act as it was a real socket (basically - * conform to nodejs/net api) - * - * @constructor - */ -function Socket() { - this._socket = false; // reference to socket thats gona be created later - // defaults: - this._noDelay = true; - - this._listeners = {}; - - // functions not supported by RN module, yet: - this.setTimeout = () => {}; - this.setEncoding = () => {}; - this.setKeepAlive = () => {}; - - // proxying call to real socket object: - this.setNoDelay = noDelay => { - if (this._socket) this._socket.setNoDelay(noDelay); - this._noDelay = noDelay; - }; - - this.connect = (port, host, callback) => { - this._socket = TcpSocket.createConnection( - { - port, - host, - tls: false, - }, - callback, - ); - - this._socket.on('data', data => { - this._passOnEvent('data', data); - }); - this._socket.on('error', data => { - this._passOnEvent('error', data); - }); - this._socket.on('close', data => { - this._passOnEvent('close', data); - }); - this._socket.on('connect', data => { - this._passOnEvent('connect', data); - this._socket.setNoDelay(this._noDelay); - }); - this._socket.on('connection', data => { - this._passOnEvent('connection', data); - }); - }; - - this._passOnEvent = (event, data) => { - this._listeners[event] = this._listeners[event] || []; - for (const savedListener of this._listeners[event]) { - savedListener(data); - } - }; - - this.on = (event, listener) => { - this._listeners[event] = this._listeners[event] || []; - this._listeners[event].push(listener); - }; - - this.removeListener = (event, listener) => { - this._listeners[event] = this._listeners[event] || []; - const newListeners = []; - - let found = false; - for (const savedListener of this._listeners[event]) { - if (savedListener === listener) { - // found our listener - found = true; - // we just skip it - } else { - // other listeners should go back to original array - newListeners.push(savedListener); - } - } - - if (found) { - this._listeners[event] = newListeners; - } else { - // something went wrong, lets just cleanup all listeners - this._listeners[event] = []; - } - }; - - this.end = () => { - this._socket.end(); - }; - - this.destroy = () => { - this._socket.destroy(); - }; - - this.write = data => { - this._socket.write(data); - }; -} - -module.exports.Socket = Socket; diff --git a/blue_modules/noble_ecc.ts b/blue_modules/noble_ecc.ts index 929fdd8865d..c83b88fb07c 100644 --- a/blue_modules/noble_ecc.ts +++ b/blue_modules/noble_ecc.ts @@ -5,12 +5,12 @@ * @see https://github.com/bitcoinjs/tiny-secp256k1/issues/84#issuecomment-1185682315 * @see https://github.com/bitcoinjs/bitcoinjs-lib/issues/1781 */ -import createHash from 'create-hash'; -import { createHmac } from 'crypto'; import * as necc from '@noble/secp256k1'; -import { TinySecp256k1Interface } from 'ecpair/src/ecpair'; -import { TinySecp256k1Interface as TinySecp256k1InterfaceBIP32 } from 'bip32/types/bip32'; +import { TinySecp256k1Interface as TinySecp256k1InterfaceBIP32 } from 'bip32'; import { XOnlyPointAddTweakResult } from 'bitcoinjs-lib/src/types'; +import { hmac } from '@noble/hashes/hmac'; +import { sha256 } from '@noble/hashes/sha2'; +import { TinySecp256k1Interface } from 'ecpair'; export interface TinySecp256k1InterfaceExtended { pointMultiply(p: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null; @@ -20,18 +20,30 @@ export interface TinySecp256k1InterfaceExtended { isXOnlyPoint(p: Uint8Array): boolean; xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null; + + privateNegate(d: Uint8Array): Uint8Array; + + signDER(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array; } necc.utils.sha256Sync = (...messages: Uint8Array[]): Uint8Array => { - const sha256 = createHash('sha256'); - for (const message of messages) sha256.update(message); - return sha256.digest(); + const combinedMessages = messages.reduce((acc, msg) => { + const newArray = new Uint8Array(acc.length + msg.length); + newArray.set(acc); + newArray.set(msg, acc.length); + return newArray; + }, new Uint8Array(0)); + return sha256(combinedMessages); }; necc.utils.hmacSha256Sync = (key: Uint8Array, ...messages: Uint8Array[]): Uint8Array => { - const hash = createHmac('sha256', Buffer.from(key)); - messages.forEach(m => hash.update(m)); - return Uint8Array.from(hash.digest()); + 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; @@ -111,6 +123,10 @@ const ecc: TinySecp256k1InterfaceExtended & TinySecp256k1Interface & TinySecp256 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); if (ret.join('') === '00000000000000000000000000000000') { @@ -119,13 +135,17 @@ const ecc: TinySecp256k1InterfaceExtended & TinySecp256k1Interface & TinySecp256 return ret; }), - // privateNegate: (d: Uint8Array): Uint8Array => necc.utils.privateNegate(d), + privateNegate: (d: Uint8Array): Uint8Array => necc.utils.privateNegate(d), sign: (h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array => { return necc.signSync(h, d, { der: false, extraEntropy: e }); }, - signSchnorr: (h: Uint8Array, d: Uint8Array, e: Uint8Array = Buffer.alloc(32, 0x00)): Uint8Array => { + signDER: (h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array => { + return necc.signSync(h, d, { der: true, extraEntropy: e }); + }, + + signSchnorr: (h: Uint8Array, d: Uint8Array, e: Uint8Array = new Uint8Array(32).fill(0x00)): Uint8Array => { return necc.schnorr.signSync(h, d, e); }, diff --git a/blue_modules/notifications.js b/blue_modules/notifications.js deleted file mode 100644 index 4b037d26343..00000000000 --- a/blue_modules/notifications.js +++ /dev/null @@ -1,440 +0,0 @@ -import PushNotificationIOS from '@react-native-community/push-notification-ios'; -import { Alert, Platform } from 'react-native'; -import Frisbee from 'frisbee'; -import { getApplicationName, getVersion, getSystemName, getSystemVersion, hasGmsSync, hasHmsSync } from 'react-native-device-info'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import loc from '../loc'; - -const PushNotification = require('react-native-push-notification'); -const constants = require('./constants'); -const PUSH_TOKEN = 'PUSH_TOKEN'; -const GROUNDCONTROL_BASE_URI = 'GROUNDCONTROL_BASE_URI'; -const NOTIFICATIONS_STORAGE = 'NOTIFICATIONS_STORAGE'; -const NOTIFICATIONS_NO_AND_DONT_ASK_FLAG = 'NOTIFICATIONS_NO_AND_DONT_ASK_FLAG'; -let alreadyConfigured = false; -let baseURI = constants.groundControlUri; - -function Notifications(props) { - async function _setPushToken(token) { - token = JSON.stringify(token); - return AsyncStorage.setItem(PUSH_TOKEN, token); - } - - Notifications.getPushToken = async () => { - try { - let token = await AsyncStorage.getItem(PUSH_TOKEN); - token = JSON.parse(token); - return token; - } catch (_) {} - return false; - }; - - Notifications.isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android'; - /** - * Calls `configure`, which tries to obtain push token, save it, and registers all associated with - * notifications callbacks - * - * @returns {Promise} TRUE if acquired token, FALSE if not - */ - const configureNotifications = async function () { - return new Promise(function (resolve) { - PushNotification.configure({ - // (optional) Called when Token is generated (iOS and Android) - onRegister: async function (token) { - console.log('TOKEN:', token); - alreadyConfigured = true; - await _setPushToken(token); - resolve(true); - }, - - // (required) Called when a remote is received or opened, or local notification is opened - onNotification: async function (notification) { - // since we do not know whether we: - // 1) received notification while app is in background (and storage is not decrypted so wallets are not loaded) - // 2) opening this notification right now but storage is still unencrypted - // 3) any of the above but the storage is decrypted, and app wallets are loaded - // - // ...we save notification in internal notifications queue thats gona be processed later (on unsuspend with decrypted storage) - - const payload = Object.assign({}, notification, notification.data); - if (notification.data && notification.data.data) Object.assign(payload, notification.data.data); - delete payload.data; - // ^^^ weird, but sometimes payload data is not in `data` but in root level - console.log('got push notification', payload); - - await Notifications.addNotification(payload); - - // (required) Called when a remote is received or opened, or local notification is opened - notification.finish(PushNotificationIOS.FetchResult.NoData); - - // if user is staring at the app when he receives the notification we process it instantly - // so app refetches related wallet - if (payload.foreground) props.onProcessNotifications(); - }, - - // (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android) - onAction: function (notification) { - console.log('ACTION:', notification.action); - console.log('NOTIFICATION:', notification); - - // process the action - }, - - // (optional) Called when the user fails to register for remote notifications. Typically occurs when APNS is having issues, or the device is a simulator. (iOS) - onRegistrationError: function (err) { - console.error(err.message, err); - resolve(false); - }, - - // IOS ONLY (optional): default: all - Permissions to register. - permissions: { - alert: true, - badge: true, - sound: true, - }, - - // Should the initial notification be popped automatically - // default: true - popInitialNotification: true, - - /** - * (optional) default: true - * - Specified if permissions (ios) and token (android and ios) will requested or not, - * - if not, you must call PushNotificationsHandler.requestPermissions() later - * - if you are not using remote notification or do not have Firebase installed, use this: - * requestPermissions: Platform.OS === 'ios' - */ - requestPermissions: true, - }); - }); - }; - - Notifications.cleanUserOptOutFlag = async function () { - return AsyncStorage.removeItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG); - }; - - /** - * Should be called when user is most interested in receiving push notifications. - * If we dont have a token it will show alert asking whether - * user wants to receive notifications, and if yes - will configure push notifications. - * FYI, on Android permissions are acquired when app is installed, so basically we dont need to ask, - * we can just call `configure`. On iOS its different, and calling `configure` triggers system's dialog box. - * - * @returns {Promise} TRUE if permissions were obtained, FALSE otherwise - */ - Notifications.tryToObtainPermissions = async function () { - if (!Notifications.isNotificationsCapable) return false; - if (await Notifications.getPushToken()) { - // we already have a token, no sense asking again, just configure pushes to register callbacks and we are done - if (!alreadyConfigured) configureNotifications(); // no await so it executes in background while we return TRUE and use token - return true; - } - - if (await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG)) { - // user doesn't want them - return false; - } - - return new Promise(function (resolve) { - Alert.alert( - loc.settings.notifications, - loc.notifications.would_you_like_to_receive_notifications, - [ - { - text: loc.notifications.no_and_dont_ask, - onPress: () => { - AsyncStorage.setItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG, '1'); - resolve(false); - }, - style: 'cancel', - }, - { - text: loc.notifications.ask_me_later, - onPress: () => { - resolve(false); - }, - style: 'cancel', - }, - { - text: loc._.ok, - onPress: async () => { - resolve(await configureNotifications()); - }, - style: 'default', - }, - ], - { cancelable: false }, - ); - }); - }; - - function _getHeaders() { - return { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - }, - }; - } - - async function _sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - - /** - * Submits onchain bitcoin addresses and ln invoice preimage hashes to GroundControl server, so later we could - * be notified if they were paid - * - * @param addresses {string[]} - * @param hashes {string[]} - * @param txids {string[]} - * @returns {Promise} Response object from API rest call - */ - Notifications.majorTomToGroundControl = async function (addresses, hashes, txids) { - if (!Array.isArray(addresses) || !Array.isArray(hashes) || !Array.isArray(txids)) - throw new Error('no addresses or hashes or txids provided'); - const pushToken = await Notifications.getPushToken(); - if (!pushToken || !pushToken.token || !pushToken.os) return; - - const api = new Frisbee({ baseURI }); - - return await api.post( - '/majorTomToGroundControl', - Object.assign({}, _getHeaders(), { - body: { - addresses, - hashes, - txids, - token: pushToken.token, - os: pushToken.os, - }, - }), - ); - }; - - /** - * The opposite of `majorTomToGroundControl` call. - * - * @param addresses {string[]} - * @param hashes {string[]} - * @param txids {string[]} - * @returns {Promise} Response object from API rest call - */ - Notifications.unsubscribe = async function (addresses, hashes, txids) { - if (!Array.isArray(addresses) || !Array.isArray(hashes) || !Array.isArray(txids)) - throw new Error('no addresses or hashes or txids provided'); - const pushToken = await Notifications.getPushToken(); - if (!pushToken || !pushToken.token || !pushToken.os) return; - - const api = new Frisbee({ baseURI }); - - return await api.post( - '/unsubscribe', - Object.assign({}, _getHeaders(), { - body: { - addresses, - hashes, - txids, - token: pushToken.token, - os: pushToken.os, - }, - }), - ); - }; - - Notifications.isNotificationsEnabled = async function () { - const levels = await getLevels(); - - return !!(await Notifications.getPushToken()) && !!levels.level_all; - }; - - Notifications.getDefaultUri = function () { - return constants.groundControlUri; - }; - - Notifications.saveUri = async function (uri) { - baseURI = uri || constants.groundControlUri; // settign the url to use currently. if not set - use default - return AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, uri); - }; - - Notifications.getSavedUri = async function () { - return AsyncStorage.getItem(GROUNDCONTROL_BASE_URI); - }; - - Notifications.isGroundControlUriValid = async uri => { - const apiCall = new Frisbee({ - baseURI: uri, - }); - let response; - try { - response = await Promise.race([apiCall.get('/ping', _getHeaders()), _sleep(2000)]); - } catch (_) {} - - if (!response || !response.body) return false; // either sleep expired or apiCall threw an exception - - const json = response.body; - if (json.description) return true; - - return false; - }; - - /** - * Returns a permissions object: - * alert: boolean - * badge: boolean - * sound: boolean - * - * @returns {Promise} - */ - Notifications.checkPermissions = async function () { - return new Promise(function (resolve) { - PushNotification.checkPermissions(result => { - resolve(result); - }); - }); - }; - - /** - * Posts to groundcontrol info whether we want to opt in or out of specific notifications level - * - * @param levelAll {Boolean} - * @returns {Promise<*>} - */ - Notifications.setLevels = async function (levelAll) { - const pushToken = await Notifications.getPushToken(); - if (!pushToken || !pushToken.token || !pushToken.os) return; - - const api = new Frisbee({ baseURI }); - - try { - await api.post( - '/setTokenConfiguration', - Object.assign({}, _getHeaders(), { - body: { - level_all: !!levelAll, - token: pushToken.token, - os: pushToken.os, - }, - }), - ); - } catch (_) {} - }; - - /** - * Queries groundcontrol for token configuration, which contains subscriptions to notification levels - * - * @returns {Promise<{}|*>} - */ - const getLevels = async function () { - const pushToken = await Notifications.getPushToken(); - if (!pushToken || !pushToken.token || !pushToken.os) return; - - const api = new Frisbee({ baseURI }); - - let response; - try { - response = await Promise.race([ - api.post('/getTokenConfiguration', Object.assign({}, _getHeaders(), { body: { token: pushToken.token, os: pushToken.os } })), - _sleep(3000), - ]); - } catch (_) {} - - if (!response || !response.body) return {}; // either sleep expired or apiCall threw an exception - - return response.body; - }; - - Notifications.getStoredNotifications = async function () { - let notifications = []; - try { - const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE); - notifications = JSON.parse(stringified); - if (!Array.isArray(notifications)) notifications = []; - } catch (_) {} - - return notifications; - }; - - Notifications.addNotification = async function (notification) { - let notifications = []; - try { - const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE); - notifications = JSON.parse(stringified); - if (!Array.isArray(notifications)) notifications = []; - } catch (_) {} - - notifications.push(notification); - await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, JSON.stringify(notifications)); - }; - - const postTokenConfig = async function () { - const pushToken = await Notifications.getPushToken(); - if (!pushToken || !pushToken.token || !pushToken.os) return; - - const api = new Frisbee({ baseURI }); - - try { - const lang = (await AsyncStorage.getItem('lang')) || 'en'; - const appVersion = getSystemName() + ' ' + getSystemVersion() + ';' + getApplicationName() + ' ' + getVersion(); - - await api.post( - '/setTokenConfiguration', - Object.assign({}, _getHeaders(), { - body: { - token: pushToken.token, - os: pushToken.os, - lang, - app_version: appVersion, - }, - }), - ); - } catch (_) {} - }; - - Notifications.clearStoredNotifications = async function () { - try { - await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, JSON.stringify([])); - } catch (_) {} - }; - - Notifications.getDeliveredNotifications = () => { - return new Promise(resolve => { - PushNotification.getDeliveredNotifications(notifications => resolve(notifications)); - }); - }; - - Notifications.removeDeliveredNotifications = (identifiers = []) => { - PushNotification.removeDeliveredNotifications(identifiers); - }; - - Notifications.setApplicationIconBadgeNumber = function (badges) { - PushNotification.setApplicationIconBadgeNumber(badges); - }; - - Notifications.removeAllDeliveredNotifications = () => { - PushNotification.removeAllDeliveredNotifications(); - }; - - // on app launch (load module): - (async () => { - // first, fetching to see if app uses custom GroundControl server, not the default one - try { - const baseUriStored = await AsyncStorage.getItem(GROUNDCONTROL_BASE_URI); - if (baseUriStored) { - baseURI = baseUriStored; - } - } catch (_) {} - - // every launch should clear badges: - Notifications.setApplicationIconBadgeNumber(0); - - if (!(await Notifications.getPushToken())) return; - // if we previously had token that means we already acquired permission from the user and it is safe to call - // `configure` to register callbacks etc - await configureNotifications(); - await postTokenConfig(); - })(); - return null; -} - -export default Notifications; diff --git a/blue_modules/notifications.ts b/blue_modules/notifications.ts new file mode 100644 index 00000000000..79b36d8baf0 --- /dev/null +++ b/blue_modules/notifications.ts @@ -0,0 +1,787 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { AppState, AppStateStatus, EmitterSubscription, Platform } from 'react-native'; +import { getApplicationName, getSystemName, getSystemVersion, getVersion, hasGmsSync, hasHmsSync } from 'react-native-device-info'; +import { + Notification as RNNotification, + NotificationBackgroundFetchResult, + NotificationCompletion, + Notifications, +} from 'react-native-notifications'; +import { checkNotifications, requestNotifications, RESULTS } from 'react-native-permissions'; +import loc from '../loc'; +import { groundControlUri } from './constants'; +import { fetch } from '../util/fetch'; + +const PUSH_TOKEN = 'PUSH_TOKEN'; +const GROUNDCONTROL_BASE_URI = 'GROUNDCONTROL_BASE_URI'; +const NOTIFICATIONS_STORAGE = 'NOTIFICATIONS_STORAGE'; +const ANDROID_NOTIFICATION_CHANNEL_ID = 'channel_01'; +export const NOTIFICATIONS_NO_AND_DONT_ASK_FLAG = 'NOTIFICATIONS_NO_AND_DONT_ASK_FLAG'; +let baseURI = groundControlUri; +let notificationSubscriptions: EmitterSubscription[] = []; +let onProcessNotificationsHandler: undefined | (() => void | Promise); +const handledNotificationKeys = new Set(); +let pendingRegistrationPromise: Promise | null = null; +let pendingRegistrationResolve: ((value: boolean) => void) | null = null; +let pendingRegistrationTimeout: ReturnType | undefined; + +type TPushToken = { + token: string; + os: 'ios' | 'android'; +}; + +type TPayload = { + subText?: string; + title?: string; + identifier?: string; + message?: string | object; + foreground: boolean; + userInteraction: boolean; + address: string; + txid: string; + type: number; + hash: string; + [key: string]: any; +}; + +function deepClone(obj: T): T { + return JSON.parse(JSON.stringify(obj)); +} + +const createPushToken = (deviceToken: string): TPushToken => ({ + token: deviceToken, + os: Platform.OS as TPushToken['os'], +}); + +const settlePendingRegistration = (value: boolean) => { + if (!pendingRegistrationResolve) return; + const resolve = pendingRegistrationResolve; + pendingRegistrationResolve = null; + pendingRegistrationPromise = null; + if (pendingRegistrationTimeout) { + clearTimeout(pendingRegistrationTimeout); + pendingRegistrationTimeout = undefined; + } + resolve(value); +}; + +const waitForRemoteRegistration = (timeoutMs = 10_000): Promise => { + if (pendingRegistrationPromise) return pendingRegistrationPromise; + pendingRegistrationPromise = new Promise(resolve => { + pendingRegistrationResolve = resolve; + pendingRegistrationTimeout = setTimeout(() => { + settlePendingRegistration(false); + }, timeoutMs); + }); + Notifications.registerRemoteNotifications(); + return pendingRegistrationPromise; +}; + +const ensureAndroidNotificationChannel = () => { + if (Platform.OS !== 'android') return; + + Notifications.setNotificationChannel({ + channelId: ANDROID_NOTIFICATION_CHANNEL_ID, + name: 'BlueWallet notifications', + description: 'Notifications about incoming payments', + importance: 4, + enableVibration: true, + showBadge: true, + }); +}; + +const getNotificationKey = (payload: Partial, notification?: RNNotification) => { + return JSON.stringify({ + identifier: notification?.identifier ?? payload.identifier ?? '', + type: payload.type ?? '', + hash: payload.hash ?? '', + txid: payload.txid ?? '', + address: payload.address ?? '', + message: payload.message ?? '', + }); +}; + +const markNotificationHandled = (key: string) => { + handledNotificationKeys.add(key); + if (handledNotificationKeys.size > 100) { + const oldestKey = handledNotificationKeys.values().next().value; + if (oldestKey) handledNotificationKeys.delete(oldestKey); + } +}; + +const normalizeNotificationPayload = (notification: RNNotification, status: Pick): TPayload => { + const rawPayload = + notification.payload && typeof notification.payload === 'object' ? (deepClone(notification.payload) as Record) : {}; + const nestedPayload = rawPayload.data && typeof rawPayload.data === 'object' ? rawPayload.data : {}; + const nestedData = nestedPayload.data && typeof nestedPayload.data === 'object' ? nestedPayload.data : {}; + + const payload: TPayload = { + ...rawPayload, + ...nestedPayload, + ...nestedData, + title: notification.title ?? rawPayload.title, + subText: rawPayload.subText ?? rawPayload.subtitle ?? notification.title, + message: rawPayload.message ?? notification.body, + identifier: notification.identifier, + foreground: status.foreground, + userInteraction: status.userInteraction, + } as TPayload; + + delete payload.data; + return payload; +}; + +const storeIncomingNotification = async ( + notification: RNNotification, + status: Pick, + completion?: ((response: NotificationCompletion) => void) | ((response: NotificationBackgroundFetchResult) => void), +) => { + try { + const payload = normalizeNotificationPayload(notification, status); + const notificationKey = getNotificationKey(payload, notification); + if (handledNotificationKeys.has(notificationKey)) { + return; + } + markNotificationHandled(notificationKey); + + if (!payload.subText && !payload.message) { + console.warn('Notification missing required fields:', payload); + return; + } + + await addNotification(payload); + + if (payload.foreground && onProcessNotificationsHandler) { + await onProcessNotificationsHandler(); + } + } catch (error) { + console.error('Failed to store incoming notification:', error); + } finally { + if (completion) { + if (status.foreground) { + (completion as (response: NotificationCompletion) => void)({ alert: false, sound: false, badge: false }); + } else { + (completion as (response: NotificationBackgroundFetchResult) => void)(NotificationBackgroundFetchResult.NO_DATA); + } + } + } +}; + +const checkAndroidNotificationPermission = async () => { + try { + const { status } = await checkNotifications(); + console.log('Notification permission check:', status); + return status === RESULTS.GRANTED; + } catch (err) { + console.error('Failed to check notification permission:', err); + return false; + } +}; + +export const checkNotificationPermissionStatus = async () => { + try { + const { status } = await checkNotifications(); + return status; + } catch (error) { + console.error('Failed to check notification permissions:', error); + return 'unavailable'; // Return 'unavailable' if the status cannot be retrieved + } +}; + +// Listener to monitor notification permission status changes while app is running +let currentPermissionStatus = 'unavailable'; +const handleAppStateChange = async (nextAppState: AppStateStatus) => { + try { + if (nextAppState === 'active') { + const isDisabledByUser = (await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG)) === 'true'; + if (!isDisabledByUser) { + const newPermissionStatus = await checkNotificationPermissionStatus(); + if (newPermissionStatus !== currentPermissionStatus) { + currentPermissionStatus = newPermissionStatus; + if (newPermissionStatus === 'granted') { + await initializeNotifications(); + } + } + } + } + } catch (error) { + console.error('Failed handling app state notification refresh:', error); + } +}; + +AppState.addEventListener('change', handleAppStateChange); + +export const cleanUserOptOutFlag = async () => { + return AsyncStorage.removeItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG); +}; + +/** + * Should be called when user is most interested in receiving push notifications. + * If we dont have a token it will show alert asking whether + * user wants to receive notifications, and if yes - will configure push notifications. + * + * @returns {Promise} TRUE if permissions were obtained, FALSE otherwise + */ +export const tryToObtainPermissions = async (): Promise => { + console.log('tryToObtainPermissions: Starting user-triggered permission request'); + + if (!isNotificationsCapable) { + console.log('tryToObtainPermissions: Device not capable'); + return false; + } + + try { + const rationale = { + title: loc.settings.notifications, + message: loc.notifications.would_you_like_to_receive_notifications, + buttonPositive: loc._.ok, + buttonNegative: loc.notifications.no_and_dont_ask, + }; + + const { status } = await requestNotifications( + ['alert', 'sound', 'badge'], + Platform.OS === 'android' && Platform.Version < 33 ? rationale : undefined, + ); + if (status !== RESULTS.GRANTED) { + console.log('tryToObtainPermissions: Permission denied'); + return false; + } + return configureNotifications(); + } catch (error) { + console.error('Error requesting notification permissions:', error); + return false; + } +}; +/** + * Submits onchain bitcoin addresses and ln invoice preimage hashes to GroundControl server, so later we could + * be notified if they were paid + * + * @param addresses {string[]} + * @param hashes {string[]} + * @param txids {string[]} + * @returns {Promise} Response object from API rest call + */ +export const majorTomToGroundControl = async (addresses: string[], hashes: string[], txids: string[]) => { + console.log('majorTomToGroundControl: Starting notification registration', { + addressCount: addresses?.length, + hashCount: hashes?.length, + txidCount: txids?.length, + }); + + try { + const noAndDontAskFlag = await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG); + if (noAndDontAskFlag === 'true') { + console.warn('User has opted out of notifications.'); + return; + } + + if (!Array.isArray(addresses) || !Array.isArray(hashes) || !Array.isArray(txids)) { + throw new Error('No addresses, hashes, or txids provided'); + } + + const pushToken = await getPushToken(); + console.log('majorTomToGroundControl: Retrieved push token:', !!pushToken); + if (!pushToken || !pushToken.token || !pushToken.os) { + return; + } + + const requestBody = JSON.stringify({ + addresses, + hashes, + txids, + token: pushToken.token, + os: pushToken.os, + }); + + let response; + try { + console.log('majorTomToGroundControl: Sending request to:', `${baseURI}/majorTomToGroundControl`); + response = await fetch(`${baseURI}/majorTomToGroundControl`, { + method: 'POST', + headers: _getHeaders(), + body: requestBody, + }); + } catch (networkError) { + console.error('Network request failed:', networkError); + throw networkError; + } + + if (!response.ok) { + throw new Error(`Ground Control request failed with status ${response.status}: ${response.statusText}`); + } + + const responseText = await response.text(); + if (responseText) { + try { + return JSON.parse(responseText); + } catch (jsonError) { + console.error('Error parsing response JSON:', jsonError); + throw jsonError; + } + } else { + return {}; // Return an empty object if there is no response body + } + } catch (error) { + console.error('Error in majorTomToGroundControl:', error); + throw error; + } +}; + +/** + * Returns a permissions object: + * alert: boolean + * badge: boolean + * sound: boolean + * + * @returns {Promise} + */ +export const checkPermissions = async () => { + try { + if (Platform.OS === 'ios') { + return Notifications.ios.checkPermissions(); + } + + const { status } = await checkNotifications(); + const granted = status === RESULTS.GRANTED; + return { + alert: granted, + badge: granted, + sound: granted, + status, + }; + } catch (error) { + console.error('Error checking permissions:', error); + throw error; + } +}; + +/** + * Posts to groundcontrol info whether we want to opt in or out of specific notifications level + * + * @param levelAll {Boolean} + * @returns {Promise<*>} + */ +export const setLevels = async (levelAll: boolean) => { + const pushToken = await getPushToken(); + if (!pushToken || !pushToken.token || !pushToken.os) return; + + try { + const response = await fetch(`${baseURI}/setTokenConfiguration`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + level_all: !!levelAll, + token: pushToken.token, + os: pushToken.os, + }), + }); + + if (!response.ok) { + throw new Error('Failed to set token configuration: ' + response.statusText); + } + + if (!levelAll) { + console.log('Disabling notifications as user opted out...'); + Notifications.removeAllDeliveredNotifications(); + if (Platform.OS === 'ios') { + Notifications.ios.setBadgeCount(0); + Notifications.ios.cancelAllLocalNotifications(); + } + await AsyncStorage.setItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG, 'true'); + console.log('Notifications disabled successfully'); + } else { + await AsyncStorage.removeItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG); // Clear flag when enabling + } + } catch (error) { + console.error('Error setting notification levels:', error); + } +}; + +export const addNotification = async (notification: TPayload) => { + let notifications = []; + try { + const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE); + notifications = JSON.parse(String(stringified)); + if (!Array.isArray(notifications)) notifications = []; + } catch (e) { + console.error(e); + // Start fresh with just the new notification + notifications = []; + } + + notifications.push(notification); + await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, JSON.stringify(notifications)); +}; + +const postTokenConfig = async () => { + console.log('postTokenConfig: Starting token configuration'); + const pushToken = await getPushToken(); + console.log('postTokenConfig: Retrieved push token:', !!pushToken); + + if (!pushToken || !pushToken.token || !pushToken.os) { + console.log('postTokenConfig: Invalid token or missing OS info'); + return; + } + + try { + const lang = (await AsyncStorage.getItem('lang')) || 'en'; + const appVersion = getSystemName() + ' ' + getSystemVersion() + ';' + getApplicationName() + ' ' + getVersion(); + console.log('postTokenConfig: Posting configuration', { lang, appVersion }); + + await fetch(`${baseURI}/setTokenConfiguration`, { + method: 'POST', + headers: _getHeaders(), + body: JSON.stringify({ + token: pushToken.token, + os: pushToken.os, + lang, + app_version: appVersion, + }), + }); + } catch (e) { + console.error(e); + await AsyncStorage.setItem('lang', 'en'); + throw e; + } +}; + +const _setPushToken = async (token: TPushToken) => { + try { + return await AsyncStorage.setItem(PUSH_TOKEN, JSON.stringify(token)); + } catch (error) { + console.error('Error setting push token:', error); + throw error; + } +}; + +/** + * Configures notifications. For Android, it will show a native rationale prompt if necessary. + * + * @returns {Promise} whether successfully registered for remote push notifications + */ +const configureNotifications = async (onProcessNotifications?: () => void): Promise => { + console.log('configureNotifications()'); + if (onProcessNotifications) { + onProcessNotificationsHandler = onProcessNotifications; + } + + try { + const { status } = await checkNotifications(); + if (status !== RESULTS.GRANTED) { + console.log('configureNotifications: Permissions not granted'); + return false; + } + + ensureAndroidNotificationChannel(); + + if (notificationSubscriptions.length === 0) { + notificationSubscriptions = [ + Notifications.events().registerRemoteNotificationsRegistered(async event => { + console.log('processing event', event); + const token = createPushToken(event.deviceToken); + if (__DEV__) { + console.log('configureNotifications: Token received:', token); + } + await _setPushToken(token); + await postTokenConfig().catch(error => console.error('Failed to post token configuration:', error)); + settlePendingRegistration(true); + }), + Notifications.events().registerRemoteNotificationsRegistrationFailed(error => { + console.error('Registration error:', error); + settlePendingRegistration(false); + }), + Notifications.events().registerRemoteNotificationsRegistrationDenied(() => { + console.log('Remote notification registration denied'); + settlePendingRegistration(false); + }), + Notifications.events().registerNotificationReceivedForeground(async (notification, completion) => { + await storeIncomingNotification(notification, { foreground: true, userInteraction: false }, completion); + }), + Notifications.events().registerNotificationReceivedBackground(async (notification, completion) => { + await storeIncomingNotification(notification, { foreground: false, userInteraction: false }, completion); + }), + Notifications.events().registerNotificationOpened(async (notification, completion) => { + try { + await storeIncomingNotification(notification, { foreground: false, userInteraction: true }); + } finally { + completion(); + } + }), + ]; + } + + Notifications.getInitialNotification() + .then(async initialNotification => { + if (initialNotification) { + console.log('App was launched by a push notification:', initialNotification); + await storeIncomingNotification(initialNotification, { foreground: false, userInteraction: true }); + } + }) + .catch(error => console.error('Failed to retrieve initial notification:', error)); + + // waiting and returning actual result of remote pushes registration: success or failure + return await waitForRemoteRegistration(); + } catch (error) { + console.error('Error in configureNotifications:', error); + return false; + } +}; + +/** + * Validates whether the provided GroundControl URI is valid by pinging it. + * + * @param uri {string} + * @returns {Promise} 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 => { + try { + const token = await AsyncStorage.getItem(PUSH_TOKEN); + return JSON.parse(String(token)) as TPushToken; + } catch (e) { + console.error(e); + AsyncStorage.removeItem(PUSH_TOKEN); + throw e; + } +}; + +/** + * Queries groundcontrol for token configuration, which contains subscriptions to notification levels + * + * @returns {Promise<{}|*>} + */ +const getLevels = async () => { + const pushToken = await getPushToken(); + if (!pushToken || !pushToken.token || !pushToken.os) return; + + try { + const response = await fetch(`${baseURI}/getTokenConfiguration`, { + method: 'POST', + headers: _getHeaders(), + body: JSON.stringify({ + token: pushToken.token, + os: pushToken.os, + }), + }); + + if (!response) return {}; + return await response.json(); + } catch (_) { + return {}; + } +}; + +/** + * The opposite of `majorTomToGroundControl` call. + * + * @param addresses {string[]} + * @param hashes {string[]} + * @param txids {string[]} + * @returns {Promise} Response object from API rest call + */ +export const unsubscribe = async (addresses: string[], hashes: string[], txids: string[]) => { + if (!Array.isArray(addresses) || !Array.isArray(hashes) || !Array.isArray(txids)) { + throw new Error('No addresses, hashes, or txids provided'); + } + + const token = await getPushToken(); + if (!token?.token || !token?.os) { + console.error('No push token or OS found'); + return; + } + + const body = JSON.stringify({ + addresses, + hashes, + txids, + token: token.token, + os: token.os, + }); + + try { + const response = await fetch(`${baseURI}/unsubscribe`, { + method: 'POST', + headers: _getHeaders(), + body, + }); + + if (!response.ok) { + console.error('Failed to unsubscribe:', response.statusText); + return; + } + + return response; + } catch (error) { + console.error('Error during unsubscribe:', error); + throw error; + } +}; + +const _getHeaders = () => { + return { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + }; +}; + +export const clearStoredNotifications = async () => { + try { + await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, JSON.stringify([])); + } catch (_) {} +}; + +export const getDeliveredNotifications: () => Promise[]> = () => { + try { + if (Platform.OS !== 'ios') { + return Promise.resolve([]); + } + + return Notifications.ios + .getDeliveredNotifications() + .then(notifications => + notifications.map(notification => normalizeNotificationPayload(notification, { foreground: true, userInteraction: false })), + ); + } catch (error) { + console.error('Error getting delivered notifications:', error); + throw error; + } +}; + +export const removeDeliveredNotifications = (identifiers = []) => { + if (Platform.OS === 'ios') { + Notifications.ios.removeDeliveredNotifications(identifiers); + } +}; + +export const setApplicationIconBadgeNumber = (badges: number) => { + if (Platform.OS === 'ios') { + Notifications.ios.setBadgeCount(badges); + } +}; + +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(); + const token = await getPushToken(); + const isDisabledByUser = (await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG)) === 'true'; + + // Return true only if we have all requirements and user hasn't opted out + return !isDisabledByUser && !!token && !!levels.level_all; + } catch (error) { + console.log('Error checking notification levels:', error); + if (error instanceof SyntaxError) { + throw error; + } + return false; + } +}; + +export const getStoredNotifications = async (): Promise => { + let notifications = []; + try { + notifications = JSON.parse(String(await AsyncStorage.getItem(NOTIFICATIONS_STORAGE))); + if (!Array.isArray(notifications)) notifications = []; + } catch (e) { + if (e instanceof SyntaxError) { + console.error('Invalid notifications format:', e); + notifications = []; + await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, '[]'); + } else { + console.error('Error accessing notifications:', e); + throw e; + } + } + + return notifications; +}; + +// on app launch (load module): +export const initializeNotifications = async (onProcessNotifications?: () => void) => { + console.log('initializeNotifications: Starting initialization'); + + try { + const noAndDontAskFlag = await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG); + console.log('initializeNotifications: No ask flag status:', noAndDontAskFlag); + + if (noAndDontAskFlag === 'true') { + console.warn('User has opted out of notifications.'); + 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 + currentPermissionStatus = await checkNotificationPermissionStatus(); + console.log('initializeNotifications: Permission status:', currentPermissionStatus); + + // Handle Android 13+ permissions differently + const canProceed = + Platform.OS === 'android' + ? isNotificationsCapable && (await checkAndroidNotificationPermission()) + : currentPermissionStatus === 'granted'; + + if (canProceed) { + console.log('initializeNotifications: Can proceed with notification setup'); + await configureNotifications(onProcessNotifications); + } else { + console.log('Notifications require user action to enable'); + } + } 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)); + } +}; diff --git a/blue_modules/aezeed/LICENSE b/blue_modules/pako/LICENSE similarity index 84% rename from blue_modules/aezeed/LICENSE rename to blue_modules/pako/LICENSE index b7fe6390c41..a934ef8db47 100644 --- a/blue_modules/aezeed/LICENSE +++ b/blue_modules/pako/LICENSE @@ -1,6 +1,6 @@ -MIT License +(The MIT License) -Copyright (c) 2020 bitcoinjs +Copyright (C) 2014-2017 by Vitaly Puzrin and Andrei Tuputcyn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/blue_modules/pako/README.md b/blue_modules/pako/README.md new file mode 100644 index 00000000000..507b0c91c5e --- /dev/null +++ b/blue_modules/pako/README.md @@ -0,0 +1,177 @@ +pako +========================================== + +[![CI](https://github.com/nodeca/pako/workflows/CI/badge.svg)](https://github.com/nodeca/pako/actions) +[![NPM version](https://img.shields.io/npm/v/pako.svg)](https://www.npmjs.org/package/pako) + +> zlib port to javascript, very fast! + +__Why pako is cool:__ + +- Results are binary equal to well known [zlib](http://www.zlib.net/) (now contains ported zlib v1.2.8). +- Almost as fast in modern JS engines as C implementation (see benchmarks). +- Works in browsers, you can browserify any separate component. + +This project was done to understand how fast JS can be and is it necessary to +develop native C modules for CPU-intensive tasks. Enjoy the result! + + +__Benchmarks:__ + + +node v12.16.3 (zlib 1.2.9), 1mb input sample: + +``` +deflate-imaya x 4.75 ops/sec ±4.93% (15 runs sampled) +deflate-pako x 10.38 ops/sec ±0.37% (29 runs sampled) +deflate-zlib x 17.74 ops/sec ±0.77% (46 runs sampled) +gzip-pako x 8.86 ops/sec ±1.41% (29 runs sampled) +inflate-imaya x 107 ops/sec ±0.69% (77 runs sampled) +inflate-pako x 131 ops/sec ±1.74% (82 runs sampled) +inflate-zlib x 258 ops/sec ±0.66% (88 runs sampled) +ungzip-pako x 115 ops/sec ±1.92% (80 runs sampled) +``` + +node v14.15.0 (google's zlib), 1mb output sample: + +``` +deflate-imaya x 4.93 ops/sec ±3.09% (16 runs sampled) +deflate-pako x 10.22 ops/sec ±0.33% (29 runs sampled) +deflate-zlib x 18.48 ops/sec ±0.24% (48 runs sampled) +gzip-pako x 10.16 ops/sec ±0.25% (28 runs sampled) +inflate-imaya x 110 ops/sec ±0.41% (77 runs sampled) +inflate-pako x 134 ops/sec ±0.66% (83 runs sampled) +inflate-zlib x 402 ops/sec ±0.74% (87 runs sampled) +ungzip-pako x 113 ops/sec ±0.62% (80 runs sampled) +``` + +zlib's test is partially affected by marshalling (that make sense for inflate only). +You can change deflate level to 0 in benchmark source, to investigate details. +For deflate level 6 results can be considered as correct. + +__Install:__ + +``` +npm install pako +``` + + +Examples / API +-------------- + +Full docs - http://nodeca.github.io/pako/ + +```javascript +const pako = require('pako'); + +// Deflate +// +const input = new Uint8Array(); +//... fill input data here +const output = pako.deflate(input); + +// Inflate (simple wrapper can throw exception on broken stream) +// +const compressed = new Uint8Array(); +//... fill data to uncompress here +try { + const result = pako.inflate(compressed); + // ... continue processing +} catch (err) { + console.log(err); +} + +// +// Alternate interface for chunking & without exceptions +// + +const deflator = new pako.Deflate(); + +deflator.push(chunk1, false); +deflator.push(chunk2); // second param is false by default. +... +deflator.push(chunk_last, true); // `true` says this chunk is last + +if (deflator.err) { + console.log(deflator.msg); +} + +const output = deflator.result; + + +const inflator = new pako.Inflate(); + +inflator.push(chunk1); +inflator.push(chunk2); +... +inflator.push(chunk_last); // no second param because end is auto-detected + +if (inflator.err) { + console.log(inflator.msg); +} + +const output = inflator.result; +``` + +Sometime you can wish to work with strings. For example, to send +stringified objects to server. Pako's deflate detects input data type, and +automatically recode strings to utf-8 prior to compress. Inflate has special +option, to say compressed data has utf-8 encoding and should be recoded to +javascript's utf-16. + +```javascript +const pako = require('pako'); + +const test = { my: 'super', puper: [456, 567], awesome: 'pako' }; + +const compressed = pako.deflate(JSON.stringify(test)); + +const restored = JSON.parse(pako.inflate(compressed, { to: 'string' })); +``` + + +Notes +----- + +Pako does not contain some specific zlib functions: + +- __deflate__ - methods `deflateCopy`, `deflateBound`, `deflateParams`, + `deflatePending`, `deflatePrime`, `deflateTune`. +- __inflate__ - methods `inflateCopy`, `inflateMark`, + `inflatePrime`, `inflateGetDictionary`, `inflateSync`, `inflateSyncPoint`, `inflateUndermine`. +- High level inflate/deflate wrappers (classes) may not support some flush + modes. + + +pako for enterprise +------------------- + +Available as part of the Tidelift Subscription + +The maintainers of pako and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-pako?utm_source=npm-pako&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) + + +Authors +------- + +- Andrey Tupitsin [@anrd83](https://github.com/andr83) +- Vitaly Puzrin [@puzrin](https://github.com/puzrin) + +Personal thanks to: + +- Vyacheslav Egorov ([@mraleph](https://github.com/mraleph)) for his awesome + tutorials about optimising JS code for v8, [IRHydra](http://mrale.ph/irhydra/) + tool and his advices. +- David Duponchel ([@dduponchel](https://github.com/dduponchel)) for help with + testing. + +Original implementation (in C): + +- [zlib](http://zlib.net/) by Jean-loup Gailly and Mark Adler. + + +License +------- + +- MIT - all files, except `/lib/zlib` folder +- ZLIB - `/lib/zlib` content diff --git a/blue_modules/pako/dist/pako.esm.mjs b/blue_modules/pako/dist/pako.esm.mjs new file mode 100644 index 00000000000..1f3e0f824f6 --- /dev/null +++ b/blue_modules/pako/dist/pako.esm.mjs @@ -0,0 +1,4 @@ +import * as pako from '../index.js'; + +export * from '../index.js'; +export default pako; diff --git a/blue_modules/pako/index.js b/blue_modules/pako/index.js new file mode 100644 index 00000000000..4fd92312298 --- /dev/null +++ b/blue_modules/pako/index.js @@ -0,0 +1,18 @@ +// Top level file is just a mixin of submodules & constants +'use strict'; + +const { Deflate, deflate, deflateRaw, gzip } = require('./lib/deflate'); + +const { Inflate, inflate, inflateRaw, ungzip } = require('./lib/inflate'); + +const constants = require('./lib/zlib/constants'); + +module.exports.Deflate = Deflate; +module.exports.deflate = deflate; +module.exports.deflateRaw = deflateRaw; +module.exports.gzip = gzip; +module.exports.Inflate = Inflate; +module.exports.inflate = inflate; +module.exports.inflateRaw = inflateRaw; +module.exports.ungzip = ungzip; +module.exports.constants = constants; diff --git a/blue_modules/pako/lib/deflate.js b/blue_modules/pako/lib/deflate.js new file mode 100644 index 00000000000..9c6b88a12b9 --- /dev/null +++ b/blue_modules/pako/lib/deflate.js @@ -0,0 +1,380 @@ +'use strict'; + + +const zlib_deflate = require('./zlib/deflate'); +const utils = require('./utils/common'); +const strings = require('./utils/strings'); +const msg = require('./zlib/messages'); +const ZStream = require('./zlib/zstream'); + +const toString = Object.prototype.toString; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH, + Z_OK, Z_STREAM_END, + Z_DEFAULT_COMPRESSION, + Z_DEFAULT_STRATEGY, + Z_DEFLATED +} = require('./zlib/constants'); + +/* ===========================================================================*/ + + +/** + * class Deflate + * + * Generic JS-style wrapper for zlib calls. If you don't need + * streaming behaviour - use more simple functions: [[deflate]], + * [[deflateRaw]] and [[gzip]]. + **/ + +/* internal + * Deflate.chunks -> Array + * + * Chunks of output data, if [[Deflate#onData]] not overridden. + **/ + +/** + * Deflate.result -> Uint8Array + * + * Compressed result, generated by default [[Deflate#onData]] + * and [[Deflate#onEnd]] handlers. Filled after you push last chunk + * (call [[Deflate#push]] with `Z_FINISH` / `true` param). + **/ + +/** + * Deflate.err -> Number + * + * Error code after deflate finished. 0 (Z_OK) on success. + * You will not need it in real life, because deflate errors + * are possible only on wrong options or bad `onData` / `onEnd` + * custom handlers. + **/ + +/** + * Deflate.msg -> String + * + * Error message, if [[Deflate.err]] != 0 + **/ + + +/** + * new Deflate(options) + * - options (Object): zlib deflate options. + * + * Creates new deflator instance with specified params. Throws exception + * on bad params. Supported options: + * + * - `level` + * - `windowBits` + * - `memLevel` + * - `strategy` + * - `dictionary` + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Additional options, for internal needs: + * + * - `chunkSize` - size of generated data chunks (16K by default) + * - `raw` (Boolean) - do raw deflate + * - `gzip` (Boolean) - create gzip wrapper + * - `header` (Object) - custom header for gzip + * - `text` (Boolean) - true if compressed data believed to be text + * - `time` (Number) - modification time, unix timestamp + * - `os` (Number) - operation system code + * - `extra` (Array) - array of bytes with extra data (max 65536) + * - `name` (String) - file name (binary string) + * - `comment` (String) - comment (binary string) + * - `hcrc` (Boolean) - true if header crc should be added + * + * ##### Example: + * + * ```javascript + * const pako = require('pako') + * , chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) + * , chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); + * + * const deflate = new pako.Deflate({ level: 3}); + * + * deflate.push(chunk1, false); + * deflate.push(chunk2, true); // true -> last chunk + * + * if (deflate.err) { throw new Error(deflate.err); } + * + * console.log(deflate.result); + * ``` + **/ +function Deflate(options) { + this.options = utils.assign({ + level: Z_DEFAULT_COMPRESSION, + method: Z_DEFLATED, + chunkSize: 16384, + windowBits: 15, + memLevel: 8, + strategy: Z_DEFAULT_STRATEGY + }, options || {}); + + let opt = this.options; + + if (opt.raw && (opt.windowBits > 0)) { + opt.windowBits = -opt.windowBits; + } + + else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) { + opt.windowBits += 16; + } + + this.err = 0; // error code, if happens (0 = Z_OK) + this.msg = ''; // error message + this.ended = false; // used to avoid multiple onEnd() calls + this.chunks = []; // chunks of compressed data + + this.strm = new ZStream(); + this.strm.avail_out = 0; + + let status = zlib_deflate.deflateInit2( + this.strm, + opt.level, + opt.method, + opt.windowBits, + opt.memLevel, + opt.strategy + ); + + if (status !== Z_OK) { + throw new Error(msg[status]); + } + + if (opt.header) { + zlib_deflate.deflateSetHeader(this.strm, opt.header); + } + + if (opt.dictionary) { + let dict; + // Convert data if needed + if (typeof opt.dictionary === 'string') { + // If we need to compress text, change encoding to utf8. + dict = strings.string2buf(opt.dictionary); + } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') { + dict = new Uint8Array(opt.dictionary); + } else { + dict = opt.dictionary; + } + + status = zlib_deflate.deflateSetDictionary(this.strm, dict); + + if (status !== Z_OK) { + throw new Error(msg[status]); + } + + this._dict_set = true; + } +} + +/** + * Deflate#push(data[, flush_mode]) -> Boolean + * - data (Uint8Array|ArrayBuffer|String): input data. Strings will be + * converted to utf8 byte sequence. + * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. + * See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH. + * + * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with + * new compressed chunks. Returns `true` on success. The last data block must + * have `flush_mode` Z_FINISH (or `true`). That will flush internal pending + * buffers and call [[Deflate#onEnd]]. + * + * On fail call [[Deflate#onEnd]] with error code and return false. + * + * ##### Example + * + * ```javascript + * push(chunk, false); // push one of data chunks + * ... + * push(chunk, true); // push last chunk + * ``` + **/ +Deflate.prototype.push = function (data, flush_mode) { + const strm = this.strm; + const chunkSize = this.options.chunkSize; + let status, _flush_mode; + + if (this.ended) { return false; } + + if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; + else _flush_mode = flush_mode === true ? Z_FINISH : Z_NO_FLUSH; + + // Convert data if needed + if (typeof data === 'string') { + // If we need to compress text, change encoding to utf8. + strm.input = strings.string2buf(data); + } else if (toString.call(data) === '[object ArrayBuffer]') { + strm.input = new Uint8Array(data); + } else { + strm.input = data; + } + + strm.next_in = 0; + strm.avail_in = strm.input.length; + + for (;;) { + if (strm.avail_out === 0) { + strm.output = new Uint8Array(chunkSize); + strm.next_out = 0; + strm.avail_out = chunkSize; + } + + // Make sure avail_out > 6 to avoid repeating markers + if ((_flush_mode === Z_SYNC_FLUSH || _flush_mode === Z_FULL_FLUSH) && strm.avail_out <= 6) { + this.onData(strm.output.subarray(0, strm.next_out)); + strm.avail_out = 0; + continue; + } + + status = zlib_deflate.deflate(strm, _flush_mode); + + // Ended => flush and finish + if (status === Z_STREAM_END) { + if (strm.next_out > 0) { + this.onData(strm.output.subarray(0, strm.next_out)); + } + status = zlib_deflate.deflateEnd(this.strm); + this.onEnd(status); + this.ended = true; + return status === Z_OK; + } + + // Flush if out buffer full + if (strm.avail_out === 0) { + this.onData(strm.output); + continue; + } + + // Flush if requested and has data + if (_flush_mode > 0 && strm.next_out > 0) { + this.onData(strm.output.subarray(0, strm.next_out)); + strm.avail_out = 0; + continue; + } + + if (strm.avail_in === 0) break; + } + + return true; +}; + + +/** + * Deflate#onData(chunk) -> Void + * - chunk (Uint8Array): output data. + * + * By default, stores data blocks in `chunks[]` property and glue + * those in `onEnd`. Override this handler, if you need another behaviour. + **/ +Deflate.prototype.onData = function (chunk) { + this.chunks.push(chunk); +}; + + +/** + * Deflate#onEnd(status) -> Void + * - status (Number): deflate status. 0 (Z_OK) on success, + * other if not. + * + * Called once after you tell deflate that the input stream is + * complete (Z_FINISH). By default - join collected chunks, + * free memory and fill `results` / `err` properties. + **/ +Deflate.prototype.onEnd = function (status) { + // On success - join + if (status === Z_OK) { + this.result = utils.flattenChunks(this.chunks); + } + this.chunks = []; + this.err = status; + this.msg = this.strm.msg; +}; + + +/** + * deflate(data[, options]) -> Uint8Array + * - data (Uint8Array|ArrayBuffer|String): input data to compress. + * - options (Object): zlib deflate options. + * + * Compress `data` with deflate algorithm and `options`. + * + * Supported options are: + * + * - level + * - windowBits + * - memLevel + * - strategy + * - dictionary + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Sugar (options): + * + * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify + * negative windowBits implicitly. + * + * ##### Example: + * + * ```javascript + * const pako = require('pako') + * const data = new Uint8Array([1,2,3,4,5,6,7,8,9]); + * + * console.log(pako.deflate(data)); + * ``` + **/ +function deflate(input, options) { + const deflator = new Deflate(options); + + deflator.push(input, true); + + // That will never happens, if you don't cheat with options :) + if (deflator.err) { throw deflator.msg || msg[deflator.err]; } + + return deflator.result; +} + + +/** + * deflateRaw(data[, options]) -> Uint8Array + * - data (Uint8Array|ArrayBuffer|String): input data to compress. + * - options (Object): zlib deflate options. + * + * The same as [[deflate]], but creates raw data, without wrapper + * (header and adler32 crc). + **/ +function deflateRaw(input, options) { + options = options || {}; + options.raw = true; + return deflate(input, options); +} + + +/** + * gzip(data[, options]) -> Uint8Array + * - data (Uint8Array|ArrayBuffer|String): input data to compress. + * - options (Object): zlib deflate options. + * + * The same as [[deflate]], but create gzip wrapper instead of + * deflate one. + **/ +function gzip(input, options) { + options = options || {}; + options.gzip = true; + return deflate(input, options); +} + + +module.exports.Deflate = Deflate; +module.exports.deflate = deflate; +module.exports.deflateRaw = deflateRaw; +module.exports.gzip = gzip; +module.exports.constants = require('./zlib/constants'); diff --git a/blue_modules/pako/lib/inflate.js b/blue_modules/pako/lib/inflate.js new file mode 100644 index 00000000000..96c9fb54689 --- /dev/null +++ b/blue_modules/pako/lib/inflate.js @@ -0,0 +1,419 @@ +'use strict'; + + +const zlib_inflate = require('./zlib/inflate'); +const utils = require('./utils/common'); +const strings = require('./utils/strings'); +const msg = require('./zlib/messages'); +const ZStream = require('./zlib/zstream'); +const GZheader = require('./zlib/gzheader'); + +const toString = Object.prototype.toString; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_NO_FLUSH, Z_FINISH, + Z_OK, Z_STREAM_END, Z_NEED_DICT, Z_STREAM_ERROR, Z_DATA_ERROR, Z_MEM_ERROR +} = require('./zlib/constants'); + +/* ===========================================================================*/ + + +/** + * class Inflate + * + * Generic JS-style wrapper for zlib calls. If you don't need + * streaming behaviour - use more simple functions: [[inflate]] + * and [[inflateRaw]]. + **/ + +/* internal + * inflate.chunks -> Array + * + * Chunks of output data, if [[Inflate#onData]] not overridden. + **/ + +/** + * Inflate.result -> Uint8Array|String + * + * Uncompressed result, generated by default [[Inflate#onData]] + * and [[Inflate#onEnd]] handlers. Filled after you push last chunk + * (call [[Inflate#push]] with `Z_FINISH` / `true` param). + **/ + +/** + * Inflate.err -> Number + * + * Error code after inflate finished. 0 (Z_OK) on success. + * Should be checked if broken data possible. + **/ + +/** + * Inflate.msg -> String + * + * Error message, if [[Inflate.err]] != 0 + **/ + + +/** + * new Inflate(options) + * - options (Object): zlib inflate options. + * + * Creates new inflator instance with specified params. Throws exception + * on bad params. Supported options: + * + * - `windowBits` + * - `dictionary` + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Additional options, for internal needs: + * + * - `chunkSize` - size of generated data chunks (16K by default) + * - `raw` (Boolean) - do raw inflate + * - `to` (String) - if equal to 'string', then result will be converted + * from utf8 to utf16 (javascript) string. When string output requested, + * chunk length can differ from `chunkSize`, depending on content. + * + * By default, when no options set, autodetect deflate/gzip data format via + * wrapper header. + * + * ##### Example: + * + * ```javascript + * const pako = require('pako') + * const chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) + * const chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); + * + * const inflate = new pako.Inflate({ level: 3}); + * + * inflate.push(chunk1, false); + * inflate.push(chunk2, true); // true -> last chunk + * + * if (inflate.err) { throw new Error(inflate.err); } + * + * console.log(inflate.result); + * ``` + **/ +function Inflate(options) { + this.options = utils.assign({ + chunkSize: 1024 * 64, + windowBits: 15, + to: '' + }, options || {}); + + const opt = this.options; + + // Force window size for `raw` data, if not set directly, + // because we have no header for autodetect. + if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) { + opt.windowBits = -opt.windowBits; + if (opt.windowBits === 0) { opt.windowBits = -15; } + } + + // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate + if ((opt.windowBits >= 0) && (opt.windowBits < 16) && + !(options && options.windowBits)) { + opt.windowBits += 32; + } + + // Gzip header has no info about windows size, we can do autodetect only + // for deflate. So, if window size not set, force it to max when gzip possible + if ((opt.windowBits > 15) && (opt.windowBits < 48)) { + // bit 3 (16) -> gzipped data + // bit 4 (32) -> autodetect gzip/deflate + if ((opt.windowBits & 15) === 0) { + opt.windowBits |= 15; + } + } + + this.err = 0; // error code, if happens (0 = Z_OK) + this.msg = ''; // error message + this.ended = false; // used to avoid multiple onEnd() calls + this.chunks = []; // chunks of compressed data + + this.strm = new ZStream(); + this.strm.avail_out = 0; + + let status = zlib_inflate.inflateInit2( + this.strm, + opt.windowBits + ); + + if (status !== Z_OK) { + throw new Error(msg[status]); + } + + this.header = new GZheader(); + + zlib_inflate.inflateGetHeader(this.strm, this.header); + + // Setup dictionary + if (opt.dictionary) { + // Convert data if needed + if (typeof opt.dictionary === 'string') { + opt.dictionary = strings.string2buf(opt.dictionary); + } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') { + opt.dictionary = new Uint8Array(opt.dictionary); + } + if (opt.raw) { //In raw mode we need to set the dictionary early + status = zlib_inflate.inflateSetDictionary(this.strm, opt.dictionary); + if (status !== Z_OK) { + throw new Error(msg[status]); + } + } + } +} + +/** + * Inflate#push(data[, flush_mode]) -> Boolean + * - data (Uint8Array|ArrayBuffer): input data + * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE + * flush modes. See constants. Skipped or `false` means Z_NO_FLUSH, + * `true` means Z_FINISH. + * + * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with + * new output chunks. Returns `true` on success. If end of stream detected, + * [[Inflate#onEnd]] will be called. + * + * `flush_mode` is not needed for normal operation, because end of stream + * detected automatically. You may try to use it for advanced things, but + * this functionality was not tested. + * + * On fail call [[Inflate#onEnd]] with error code and return false. + * + * ##### Example + * + * ```javascript + * push(chunk, false); // push one of data chunks + * ... + * push(chunk, true); // push last chunk + * ``` + **/ +Inflate.prototype.push = function (data, flush_mode) { + const strm = this.strm; + const chunkSize = this.options.chunkSize; + const dictionary = this.options.dictionary; + let status, _flush_mode, last_avail_out; + + if (this.ended) return false; + + if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; + else _flush_mode = flush_mode === true ? Z_FINISH : Z_NO_FLUSH; + + // Convert data if needed + if (toString.call(data) === '[object ArrayBuffer]') { + strm.input = new Uint8Array(data); + } else { + strm.input = data; + } + + strm.next_in = 0; + strm.avail_in = strm.input.length; + + for (;;) { + if (strm.avail_out === 0) { + strm.output = new Uint8Array(chunkSize); + strm.next_out = 0; + strm.avail_out = chunkSize; + } + + status = zlib_inflate.inflate(strm, _flush_mode); + + if (status === Z_NEED_DICT && dictionary) { + status = zlib_inflate.inflateSetDictionary(strm, dictionary); + + if (status === Z_OK) { + status = zlib_inflate.inflate(strm, _flush_mode); + } else if (status === Z_DATA_ERROR) { + // Replace code with more verbose + status = Z_NEED_DICT; + } + } + + // Skip snyc markers if more data follows and not raw mode + while (strm.avail_in > 0 && + status === Z_STREAM_END && + strm.state.wrap > 0 && + data[strm.next_in] !== 0) + { + zlib_inflate.inflateReset(strm); + status = zlib_inflate.inflate(strm, _flush_mode); + } + + switch (status) { + case Z_STREAM_ERROR: + case Z_DATA_ERROR: + case Z_NEED_DICT: + case Z_MEM_ERROR: + this.onEnd(status); + this.ended = true; + return false; + } + + // Remember real `avail_out` value, because we may patch out buffer content + // to align utf8 strings boundaries. + last_avail_out = strm.avail_out; + + if (strm.next_out) { + if (strm.avail_out === 0 || status === Z_STREAM_END) { + + if (this.options.to === 'string') { + + let next_out_utf8 = strings.utf8border(strm.output, strm.next_out); + + let tail = strm.next_out - next_out_utf8; + let utf8str = strings.buf2string(strm.output, next_out_utf8); + + // move tail & realign counters + strm.next_out = tail; + strm.avail_out = chunkSize - tail; + if (tail) strm.output.set(strm.output.subarray(next_out_utf8, next_out_utf8 + tail), 0); + + this.onData(utf8str); + + } else { + this.onData(strm.output.length === strm.next_out ? strm.output : strm.output.subarray(0, strm.next_out)); + } + } + } + + // Must repeat iteration if out buffer is full + if (status === Z_OK && last_avail_out === 0) continue; + + // Finalize if end of stream reached. + if (status === Z_STREAM_END) { + status = zlib_inflate.inflateEnd(this.strm); + this.onEnd(status); + this.ended = true; + return true; + } + + if (strm.avail_in === 0) break; + } + + return true; +}; + + +/** + * Inflate#onData(chunk) -> Void + * - chunk (Uint8Array|String): output data. When string output requested, + * each chunk will be string. + * + * By default, stores data blocks in `chunks[]` property and glue + * those in `onEnd`. Override this handler, if you need another behaviour. + **/ +Inflate.prototype.onData = function (chunk) { + this.chunks.push(chunk); +}; + + +/** + * Inflate#onEnd(status) -> Void + * - status (Number): inflate status. 0 (Z_OK) on success, + * other if not. + * + * Called either after you tell inflate that the input stream is + * complete (Z_FINISH). By default - join collected chunks, + * free memory and fill `results` / `err` properties. + **/ +Inflate.prototype.onEnd = function (status) { + // On success - join + if (status === Z_OK) { + if (this.options.to === 'string') { + this.result = this.chunks.join(''); + } else { + this.result = utils.flattenChunks(this.chunks); + } + } + this.chunks = []; + this.err = status; + this.msg = this.strm.msg; +}; + + +/** + * inflate(data[, options]) -> Uint8Array|String + * - data (Uint8Array|ArrayBuffer): input data to decompress. + * - options (Object): zlib inflate options. + * + * Decompress `data` with inflate/ungzip and `options`. Autodetect + * format via wrapper header by default. That's why we don't provide + * separate `ungzip` method. + * + * Supported options are: + * + * - windowBits + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information. + * + * Sugar (options): + * + * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify + * negative windowBits implicitly. + * - `to` (String) - if equal to 'string', then result will be converted + * from utf8 to utf16 (javascript) string. When string output requested, + * chunk length can differ from `chunkSize`, depending on content. + * + * + * ##### Example: + * + * ```javascript + * const pako = require('pako'); + * const input = pako.deflate(new Uint8Array([1,2,3,4,5,6,7,8,9])); + * let output; + * + * try { + * output = pako.inflate(input); + * } catch (err) { + * console.log(err); + * } + * ``` + **/ +function inflate(input, options) { + const inflator = new Inflate(options); + + inflator.push(input); + + // That will never happens, if you don't cheat with options :) + if (inflator.err) throw inflator.msg || msg[inflator.err]; + + return inflator.result; +} + + +/** + * inflateRaw(data[, options]) -> Uint8Array|String + * - data (Uint8Array|ArrayBuffer): input data to decompress. + * - options (Object): zlib inflate options. + * + * The same as [[inflate]], but creates raw data, without wrapper + * (header and adler32 crc). + **/ +function inflateRaw(input, options) { + options = options || {}; + options.raw = true; + return inflate(input, options); +} + + +/** + * ungzip(data[, options]) -> Uint8Array|String + * - data (Uint8Array|ArrayBuffer): input data to decompress. + * - options (Object): zlib inflate options. + * + * Just shortcut to [[inflate]], because it autodetects format + * by header.content. Done for convenience. + **/ + + +module.exports.Inflate = Inflate; +module.exports.inflate = inflate; +module.exports.inflateRaw = inflateRaw; +module.exports.ungzip = inflate; +module.exports.constants = require('./zlib/constants'); diff --git a/blue_modules/pako/lib/utils/common.js b/blue_modules/pako/lib/utils/common.js new file mode 100644 index 00000000000..9a6447a4d6e --- /dev/null +++ b/blue_modules/pako/lib/utils/common.js @@ -0,0 +1,48 @@ +'use strict'; + + +const _has = (obj, key) => { + return Object.prototype.hasOwnProperty.call(obj, key); +}; + +module.exports.assign = function (obj /*from1, from2, from3, ...*/) { + const sources = Array.prototype.slice.call(arguments, 1); + while (sources.length) { + const source = sources.shift(); + if (!source) { continue; } + + if (typeof source !== 'object') { + throw new TypeError(source + 'must be non-object'); + } + + for (const p in source) { + if (_has(source, p)) { + obj[p] = source[p]; + } + } + } + + return obj; +}; + + +// Join array of chunks to single array. +module.exports.flattenChunks = (chunks) => { + // calculate data length + let len = 0; + + for (let i = 0, l = chunks.length; i < l; i++) { + len += chunks[i].length; + } + + // join chunks + const result = new Uint8Array(len); + + for (let i = 0, pos = 0, l = chunks.length; i < l; i++) { + let chunk = chunks[i]; + result.set(chunk, pos); + pos += chunk.length; + } + + return result; +}; diff --git a/blue_modules/pako/lib/utils/strings.js b/blue_modules/pako/lib/utils/strings.js new file mode 100644 index 00000000000..c8f097cdbaa --- /dev/null +++ b/blue_modules/pako/lib/utils/strings.js @@ -0,0 +1,174 @@ +// String encode/decode helpers +'use strict'; + + +// Quick check if we can use fast array to bin string conversion +// +// - apply(Array) can fail on Android 2.2 +// - apply(Uint8Array) can fail on iOS 5.1 Safari +// +let STR_APPLY_UIA_OK = true; + +try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; } + + +// Table with utf8 lengths (calculated by first byte of sequence) +// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, +// because max possible codepoint is 0x10ffff +const _utf8len = new Uint8Array(256); +for (let q = 0; q < 256; q++) { + _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1); +} +_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start + + +// convert string to array (typed, when possible) +module.exports.string2buf = (str) => { + if (typeof TextEncoder === 'function' && TextEncoder.prototype.encode) { + return new TextEncoder().encode(str); + } + + let buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; + + // count binary size + for (m_pos = 0; m_pos < str_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { + c2 = str.charCodeAt(m_pos + 1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; + } + + // allocate buffer + buf = new Uint8Array(buf_len); + + // convert + for (i = 0, m_pos = 0; i < buf_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { + c2 = str.charCodeAt(m_pos + 1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + if (c < 0x80) { + /* one byte */ + buf[i++] = c; + } else if (c < 0x800) { + /* two bytes */ + buf[i++] = 0xC0 | (c >>> 6); + buf[i++] = 0x80 | (c & 0x3f); + } else if (c < 0x10000) { + /* three bytes */ + buf[i++] = 0xE0 | (c >>> 12); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } else { + /* four bytes */ + buf[i++] = 0xf0 | (c >>> 18); + buf[i++] = 0x80 | (c >>> 12 & 0x3f); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } + } + + return buf; +}; + +// Helper +const buf2binstring = (buf, len) => { + // On Chrome, the arguments in a function call that are allowed is `65534`. + // If the length of the buffer is smaller than that, we can use this optimization, + // otherwise we will take a slower path. + if (len < 65534) { + if (buf.subarray && STR_APPLY_UIA_OK) { + return String.fromCharCode.apply(null, buf.length === len ? buf : buf.subarray(0, len)); + } + } + + let result = ''; + for (let i = 0; i < len; i++) { + result += String.fromCharCode(buf[i]); + } + return result; +}; + + +// convert array to string +module.exports.buf2string = (buf, max) => { + const len = max || buf.length; + + if (typeof TextDecoder === 'function' && TextDecoder.prototype.decode) { + return new TextDecoder().decode(buf.subarray(0, max)); + } + + let i, out; + + // Reserve max possible length (2 words per char) + // NB: by unknown reasons, Array is significantly faster for + // String.fromCharCode.apply than Uint16Array. + const utf16buf = new Array(len * 2); + + for (out = 0, i = 0; i < len;) { + let c = buf[i++]; + // quick process ascii + if (c < 0x80) { utf16buf[out++] = c; continue; } + + let c_len = _utf8len[c]; + // skip 5 & 6 byte codes + if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; } + + // apply mask on first byte + c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; + // join the rest + while (c_len > 1 && i < len) { + c = (c << 6) | (buf[i++] & 0x3f); + c_len--; + } + + // terminated by end of string? + if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } + + if (c < 0x10000) { + utf16buf[out++] = c; + } else { + c -= 0x10000; + utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); + utf16buf[out++] = 0xdc00 | (c & 0x3ff); + } + } + + return buf2binstring(utf16buf, out); +}; + + +// Calculate max possible position in utf8 buffer, +// that will not break sequence. If that's not possible +// - (very small limits) return max size as is. +// +// buf[] - utf8 bytes array +// max - length limit (mandatory); +module.exports.utf8border = (buf, max) => { + + max = max || buf.length; + if (max > buf.length) { max = buf.length; } + + // go back from last position, until start of sequence found + let pos = max - 1; + while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } + + // Very small and broken sequence, + // return max, because we should return something anyway. + if (pos < 0) { return max; } + + // If we came to start of buffer - that means buffer is too small, + // return max too. + if (pos === 0) { return max; } + + return (pos + _utf8len[buf[pos]] > max) ? pos : max; +}; diff --git a/blue_modules/pako/lib/zlib/README b/blue_modules/pako/lib/zlib/README new file mode 100644 index 00000000000..88a8752470d --- /dev/null +++ b/blue_modules/pako/lib/zlib/README @@ -0,0 +1,59 @@ +Content of this folder follows zlib C sources as close as possible. +That's intended to simplify maintainability and guarantee equal API +and result. + +Key differences: + +- Everything is in JavaScript. +- No platform-dependent blocks. +- Some things like crc32 rewritten to keep size small and make JIT + work better. +- Some code is different due missed features in JS (macros, pointers, + structures, header files) +- Specific API methods are not implemented (see notes in root readme) + +This port is based on zlib 1.2.8. + +This port is under zlib license (see below) with contribution and addition of javascript +port under expat license (see LICENSE at root of project) + +Copyright: +(C) 1995-2013 Jean-loup Gailly and Mark Adler +(C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin + + +From zlib's README +============================================================================= + +Acknowledgments: + + The deflate format used by zlib was defined by Phil Katz. The deflate and + zlib specifications were written by L. Peter Deutsch. Thanks to all the + people who reported problems and suggested various improvements in zlib; they + are too numerous to cite here. + +Copyright notice: + + (C) 1995-2013 Jean-loup Gailly and Mark Adler + +Copyright (c) <''year''> <''copyright holders''> + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu diff --git a/blue_modules/pako/lib/zlib/adler32.js b/blue_modules/pako/lib/zlib/adler32.js new file mode 100644 index 00000000000..d65072afdbf --- /dev/null +++ b/blue_modules/pako/lib/zlib/adler32.js @@ -0,0 +1,51 @@ +'use strict'; + +// Note: adler32 takes 12% for level 0 and 2% for level 6. +// It isn't worth it to make additional optimizations as in original. +// Small size is preferable. + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const adler32 = (adler, buf, len, pos) => { + let s1 = (adler & 0xffff) |0, + s2 = ((adler >>> 16) & 0xffff) |0, + n = 0; + + while (len !== 0) { + // Set limit ~ twice less than 5552, to keep + // s2 in 31-bits, because we force signed ints. + // in other case %= will fail. + n = len > 2000 ? 2000 : len; + len -= n; + + do { + s1 = (s1 + buf[pos++]) |0; + s2 = (s2 + s1) |0; + } while (--n); + + s1 %= 65521; + s2 %= 65521; + } + + return (s1 | (s2 << 16)) |0; +}; + + +module.exports = adler32; diff --git a/blue_modules/pako/lib/zlib/constants.js b/blue_modules/pako/lib/zlib/constants.js new file mode 100644 index 00000000000..b85cc0191d7 --- /dev/null +++ b/blue_modules/pako/lib/zlib/constants.js @@ -0,0 +1,68 @@ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +module.exports = { + + /* Allowed flush values; see deflate() and inflate() below for details */ + Z_NO_FLUSH: 0, + Z_PARTIAL_FLUSH: 1, + Z_SYNC_FLUSH: 2, + Z_FULL_FLUSH: 3, + Z_FINISH: 4, + Z_BLOCK: 5, + Z_TREES: 6, + + /* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ + Z_OK: 0, + Z_STREAM_END: 1, + Z_NEED_DICT: 2, + Z_ERRNO: -1, + Z_STREAM_ERROR: -2, + Z_DATA_ERROR: -3, + Z_MEM_ERROR: -4, + Z_BUF_ERROR: -5, + //Z_VERSION_ERROR: -6, + + /* compression levels */ + Z_NO_COMPRESSION: 0, + Z_BEST_SPEED: 1, + Z_BEST_COMPRESSION: 9, + Z_DEFAULT_COMPRESSION: -1, + + + Z_FILTERED: 1, + Z_HUFFMAN_ONLY: 2, + Z_RLE: 3, + Z_FIXED: 4, + Z_DEFAULT_STRATEGY: 0, + + /* Possible values of the data_type field (though see inflate()) */ + Z_BINARY: 0, + Z_TEXT: 1, + //Z_ASCII: 1, // = Z_TEXT (deprecated) + Z_UNKNOWN: 2, + + /* The deflate compression method */ + Z_DEFLATED: 8 + //Z_NULL: null // Use -1 or null inline, depending on var type +}; diff --git a/blue_modules/pako/lib/zlib/crc32.js b/blue_modules/pako/lib/zlib/crc32.js new file mode 100644 index 00000000000..60cbd51ec44 --- /dev/null +++ b/blue_modules/pako/lib/zlib/crc32.js @@ -0,0 +1,59 @@ +'use strict'; + +// Note: we can't get significant speed boost here. +// So write code to minimize size - no pregenerated tables +// and array tools dependencies. + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +// Use ordinary array, since untyped makes no boost here +const makeTable = () => { + let c, table = []; + + for (var n = 0; n < 256; n++) { + c = n; + for (var k = 0; k < 8; k++) { + c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); + } + table[n] = c; + } + + return table; +}; + +// Create table on load. Just 255 signed longs. Not a problem. +const crcTable = new Uint32Array(makeTable()); + + +const crc32 = (crc, buf, len, pos) => { + const t = crcTable; + const end = pos + len; + + crc ^= -1; + + for (let i = pos; i < end; i++) { + crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +}; + + +module.exports = crc32; diff --git a/blue_modules/pako/lib/zlib/deflate.js b/blue_modules/pako/lib/zlib/deflate.js new file mode 100644 index 00000000000..00e056eb7d6 --- /dev/null +++ b/blue_modules/pako/lib/zlib/deflate.js @@ -0,0 +1,2048 @@ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const { _tr_init, _tr_stored_block, _tr_flush_block, _tr_tally, _tr_align } = require('./trees'); +const adler32 = require('./adler32'); +const crc32 = require('./crc32'); +const msg = require('./messages'); + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_NO_FLUSH, Z_PARTIAL_FLUSH, Z_FULL_FLUSH, Z_FINISH, Z_BLOCK, + Z_OK, Z_STREAM_END, Z_STREAM_ERROR, Z_DATA_ERROR, Z_BUF_ERROR, + Z_DEFAULT_COMPRESSION, + Z_FILTERED, Z_HUFFMAN_ONLY, Z_RLE, Z_FIXED, Z_DEFAULT_STRATEGY, + Z_UNKNOWN, + Z_DEFLATED +} = require('./constants'); + +/*============================================================================*/ + + +const MAX_MEM_LEVEL = 9; +/* Maximum value for memLevel in deflateInit2 */ +const MAX_WBITS = 15; +/* 32K LZ77 window */ +const DEF_MEM_LEVEL = 8; + + +const LENGTH_CODES = 29; +/* number of length codes, not counting the special END_BLOCK code */ +const LITERALS = 256; +/* number of literal bytes 0..255 */ +const L_CODES = LITERALS + 1 + LENGTH_CODES; +/* number of Literal or Length codes, including the END_BLOCK code */ +const D_CODES = 30; +/* number of distance codes */ +const BL_CODES = 19; +/* number of codes used to transfer the bit lengths */ +const HEAP_SIZE = 2 * L_CODES + 1; +/* maximum heap size */ +const MAX_BITS = 15; +/* All codes must not exceed MAX_BITS bits */ + +const MIN_MATCH = 3; +const MAX_MATCH = 258; +const MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); + +const PRESET_DICT = 0x20; + +const INIT_STATE = 42; /* zlib header -> BUSY_STATE */ +//#ifdef GZIP +const GZIP_STATE = 57; /* gzip header -> BUSY_STATE | EXTRA_STATE */ +//#endif +const EXTRA_STATE = 69; /* gzip extra block -> NAME_STATE */ +const NAME_STATE = 73; /* gzip file name -> COMMENT_STATE */ +const COMMENT_STATE = 91; /* gzip comment -> HCRC_STATE */ +const HCRC_STATE = 103; /* gzip header CRC -> BUSY_STATE */ +const BUSY_STATE = 113; /* deflate -> FINISH_STATE */ +const FINISH_STATE = 666; /* stream complete */ + +const BS_NEED_MORE = 1; /* block not completed, need more input or more output */ +const BS_BLOCK_DONE = 2; /* block flush performed */ +const BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */ +const BS_FINISH_DONE = 4; /* finish done, accept no more input or output */ + +const OS_CODE = 0x03; // Unix :) . Don't detect, use this default. + +const err = (strm, errorCode) => { + strm.msg = msg[errorCode]; + return errorCode; +}; + +const rank = (f) => { + return ((f) * 2) - ((f) > 4 ? 9 : 0); +}; + +const zero = (buf) => { + let len = buf.length; while (--len >= 0) { buf[len] = 0; } +}; + +/* =========================================================================== + * Slide the hash table when sliding the window down (could be avoided with 32 + * bit values at the expense of memory usage). We slide even when level == 0 to + * keep the hash table consistent if we switch back to level > 0 later. + */ +const slide_hash = (s) => { + let n, m; + let p; + let wsize = s.w_size; + + n = s.hash_size; + p = n; + do { + m = s.head[--p]; + s.head[p] = (m >= wsize ? m - wsize : 0); + } while (--n); + n = wsize; +//#ifndef FASTEST + p = n; + do { + m = s.prev[--p]; + s.prev[p] = (m >= wsize ? m - wsize : 0); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); +//#endif +}; + +/* eslint-disable new-cap */ +let HASH_ZLIB = (s, prev, data) => ((prev << s.hash_shift) ^ data) & s.hash_mask; +// This hash causes less collisions, https://github.com/nodeca/pako/issues/135 +// But breaks binary compatibility +//let HASH_FAST = (s, prev, data) => ((prev << 8) + (prev >> 8) + (data << 4)) & s.hash_mask; +let HASH = HASH_ZLIB; + + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output, except for + * some deflate_stored() output, goes through this function so some + * applications may wish to modify it to avoid allocating a large + * strm->next_out buffer and copying into it. (See also read_buf()). + */ +const flush_pending = (strm) => { + const s = strm.state; + + //_tr_flush_bits(s); + let len = s.pending; + if (len > strm.avail_out) { + len = strm.avail_out; + } + if (len === 0) { return; } + + strm.output.set(s.pending_buf.subarray(s.pending_out, s.pending_out + len), strm.next_out); + strm.next_out += len; + s.pending_out += len; + strm.total_out += len; + strm.avail_out -= len; + s.pending -= len; + if (s.pending === 0) { + s.pending_out = 0; + } +}; + + +const flush_block_only = (s, last) => { + _tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last); + s.block_start = s.strstart; + flush_pending(s.strm); +}; + + +const put_byte = (s, b) => { + s.pending_buf[s.pending++] = b; +}; + + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +const putShortMSB = (s, b) => { + + // put_byte(s, (Byte)(b >> 8)); +// put_byte(s, (Byte)(b & 0xff)); + s.pending_buf[s.pending++] = (b >>> 8) & 0xff; + s.pending_buf[s.pending++] = b & 0xff; +}; + + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->input buffer and copying from it. + * (See also flush_pending()). + */ +const read_buf = (strm, buf, start, size) => { + + let len = strm.avail_in; + + if (len > size) { len = size; } + if (len === 0) { return 0; } + + strm.avail_in -= len; + + // zmemcpy(buf, strm->next_in, len); + buf.set(strm.input.subarray(strm.next_in, strm.next_in + len), start); + if (strm.state.wrap === 1) { + strm.adler = adler32(strm.adler, buf, len, start); + } + + else if (strm.state.wrap === 2) { + strm.adler = crc32(strm.adler, buf, len, start); + } + + strm.next_in += len; + strm.total_in += len; + + return len; +}; + + +/* =========================================================================== + * Set match_start to the longest match starting at the given string and + * return its length. Matches shorter or equal to prev_length are discarded, + * in which case the result is equal to prev_length and match_start is + * garbage. + * IN assertions: cur_match is the head of the hash chain for the current + * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + * OUT assertion: the match length is not greater than s->lookahead. + */ +const longest_match = (s, cur_match) => { + + let chain_length = s.max_chain_length; /* max hash chain length */ + let scan = s.strstart; /* current string */ + let match; /* matched string */ + let len; /* length of current match */ + let best_len = s.prev_length; /* best match length so far */ + let nice_match = s.nice_match; /* stop if match long enough */ + const limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ? + s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/; + + const _win = s.window; // shortcut + + const wmask = s.w_mask; + const prev = s.prev; + + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + + const strend = s.strstart + MAX_MATCH; + let scan_end1 = _win[scan + best_len - 1]; + let scan_end = _win[scan + best_len]; + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + /* Do not waste too much time if we already have a good match: */ + if (s.prev_length >= s.good_match) { + chain_length >>= 2; + } + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if (nice_match > s.lookahead) { nice_match = s.lookahead; } + + // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + do { + // Assert(cur_match < s->strstart, "no future"); + match = cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2. Note that the checks below + * for insufficient lookahead only occur occasionally for performance + * reasons. Therefore uninitialized memory will be accessed, and + * conditional jumps will be made that depend on those values. + * However the length of the match is limited to the lookahead, so + * the output of deflate is not affected by the uninitialized values. + */ + + if (_win[match + best_len] !== scan_end || + _win[match + best_len - 1] !== scan_end1 || + _win[match] !== _win[scan] || + _win[++match] !== _win[scan + 1]) { + continue; + } + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2; + match++; + // Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + /*jshint noempty:false*/ + } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + scan < strend); + + // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (strend - scan); + scan = strend - MAX_MATCH; + + if (len > best_len) { + s.match_start = cur_match; + best_len = len; + if (len >= nice_match) { + break; + } + scan_end1 = _win[scan + best_len - 1]; + scan_end = _win[scan + best_len]; + } + } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0); + + if (best_len <= s.lookahead) { + return best_len; + } + return s.lookahead; +}; + + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +const fill_window = (s) => { + + const _w_size = s.w_size; + let n, more, str; + + //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + + do { + more = s.window_size - s.lookahead - s.strstart; + + // JS ints have 32 bit, block below not needed + /* Deal with !@#$% 64K limit: */ + //if (sizeof(int) <= 2) { + // if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + // more = wsize; + // + // } else if (more == (unsigned)(-1)) { + // /* Very unlikely, but possible on 16 bit machine if + // * strstart == 0 && lookahead == 1 (input done a byte at time) + // */ + // more--; + // } + //} + + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) { + + s.window.set(s.window.subarray(_w_size, _w_size + _w_size - more), 0); + s.match_start -= _w_size; + s.strstart -= _w_size; + /* we now have strstart >= MAX_DIST */ + s.block_start -= _w_size; + if (s.insert > s.strstart) { + s.insert = s.strstart; + } + slide_hash(s); + more += _w_size; + } + if (s.strm.avail_in === 0) { + break; + } + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + //Assert(more >= 2, "more < 2"); + n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more); + s.lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s.lookahead + s.insert >= MIN_MATCH) { + str = s.strstart - s.insert; + s.ins_h = s.window[str]; + + /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */ + s.ins_h = HASH(s, s.ins_h, s.window[str + 1]); +//#if MIN_MATCH != 3 +// Call update_hash() MIN_MATCH-3 more times +//#endif + while (s.insert) { + /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ + s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]); + + s.prev[str & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = str; + str++; + s.insert--; + if (s.lookahead + s.insert < MIN_MATCH) { + break; + } + } + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0); + + /* If the WIN_INIT bytes after the end of the current data have never been + * written, then zero those bytes in order to avoid memory check reports of + * the use of uninitialized (or uninitialised as Julian writes) bytes by + * the longest match routines. Update the high water mark for the next + * time through here. WIN_INIT is set to MAX_MATCH since the longest match + * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. + */ +// if (s.high_water < s.window_size) { +// const curr = s.strstart + s.lookahead; +// let init = 0; +// +// if (s.high_water < curr) { +// /* Previous high water mark below current data -- zero WIN_INIT +// * bytes or up to end of window, whichever is less. +// */ +// init = s.window_size - curr; +// if (init > WIN_INIT) +// init = WIN_INIT; +// zmemzero(s->window + curr, (unsigned)init); +// s->high_water = curr + init; +// } +// else if (s->high_water < (ulg)curr + WIN_INIT) { +// /* High water mark at or above current data, but below current data +// * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up +// * to end of window, whichever is less. +// */ +// init = (ulg)curr + WIN_INIT - s->high_water; +// if (init > s->window_size - s->high_water) +// init = s->window_size - s->high_water; +// zmemzero(s->window + s->high_water, (unsigned)init); +// s->high_water += init; +// } +// } +// +// Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, +// "not enough room for search"); +}; + +/* =========================================================================== + * Copy without compression as much as possible from the input stream, return + * the current block state. + * + * In case deflateParams() is used to later switch to a non-zero compression + * level, s->matches (otherwise unused when storing) keeps track of the number + * of hash table slides to perform. If s->matches is 1, then one hash table + * slide will be done when switching. If s->matches is 2, the maximum value + * allowed here, then the hash table will be cleared, since two or more slides + * is the same as a clear. + * + * deflate_stored() is written to minimize the number of times an input byte is + * copied. It is most efficient with large input and output buffers, which + * maximizes the opportunites to have a single copy from next_in to next_out. + */ +const deflate_stored = (s, flush) => { + + /* Smallest worthy block size when not flushing or finishing. By default + * this is 32K. This can be as small as 507 bytes for memLevel == 1. For + * large input and output buffers, the stored block size will be larger. + */ + let min_block = s.pending_buf_size - 5 > s.w_size ? s.w_size : s.pending_buf_size - 5; + + /* Copy as many min_block or larger stored blocks directly to next_out as + * possible. If flushing, copy the remaining available input to next_out as + * stored blocks, if there is enough space. + */ + let len, left, have, last = 0; + let used = s.strm.avail_in; + do { + /* Set len to the maximum size block that we can copy directly with the + * available input data and output space. Set left to how much of that + * would be copied from what's left in the window. + */ + len = 65535/* MAX_STORED */; /* maximum deflate stored block length */ + have = (s.bi_valid + 42) >> 3; /* number of header bytes */ + if (s.strm.avail_out < have) { /* need room for header */ + break; + } + /* maximum stored block length that will fit in avail_out: */ + have = s.strm.avail_out - have; + left = s.strstart - s.block_start; /* bytes left in window */ + if (len > left + s.strm.avail_in) { + len = left + s.strm.avail_in; /* limit len to the input */ + } + if (len > have) { + len = have; /* limit len to the output */ + } + + /* If the stored block would be less than min_block in length, or if + * unable to copy all of the available input when flushing, then try + * copying to the window and the pending buffer instead. Also don't + * write an empty block when flushing -- deflate() does that. + */ + if (len < min_block && ((len === 0 && flush !== Z_FINISH) || + flush === Z_NO_FLUSH || + len !== left + s.strm.avail_in)) { + break; + } + + /* Make a dummy stored block in pending to get the header bytes, + * including any pending bits. This also updates the debugging counts. + */ + last = flush === Z_FINISH && len === left + s.strm.avail_in ? 1 : 0; + _tr_stored_block(s, 0, 0, last); + + /* Replace the lengths in the dummy stored block with len. */ + s.pending_buf[s.pending - 4] = len; + s.pending_buf[s.pending - 3] = len >> 8; + s.pending_buf[s.pending - 2] = ~len; + s.pending_buf[s.pending - 1] = ~len >> 8; + + /* Write the stored block header bytes. */ + flush_pending(s.strm); + +//#ifdef ZLIB_DEBUG +// /* Update debugging counts for the data about to be copied. */ +// s->compressed_len += len << 3; +// s->bits_sent += len << 3; +//#endif + + /* Copy uncompressed bytes from the window to next_out. */ + if (left) { + if (left > len) { + left = len; + } + //zmemcpy(s->strm->next_out, s->window + s->block_start, left); + s.strm.output.set(s.window.subarray(s.block_start, s.block_start + left), s.strm.next_out); + s.strm.next_out += left; + s.strm.avail_out -= left; + s.strm.total_out += left; + s.block_start += left; + len -= left; + } + + /* Copy uncompressed bytes directly from next_in to next_out, updating + * the check value. + */ + if (len) { + read_buf(s.strm, s.strm.output, s.strm.next_out, len); + s.strm.next_out += len; + s.strm.avail_out -= len; + s.strm.total_out += len; + } + } while (last === 0); + + /* Update the sliding window with the last s->w_size bytes of the copied + * data, or append all of the copied data to the existing window if less + * than s->w_size bytes were copied. Also update the number of bytes to + * insert in the hash tables, in the event that deflateParams() switches to + * a non-zero compression level. + */ + used -= s.strm.avail_in; /* number of input bytes directly copied */ + if (used) { + /* If any input was used, then no unused input remains in the window, + * therefore s->block_start == s->strstart. + */ + if (used >= s.w_size) { /* supplant the previous history */ + s.matches = 2; /* clear hash */ + //zmemcpy(s->window, s->strm->next_in - s->w_size, s->w_size); + s.window.set(s.strm.input.subarray(s.strm.next_in - s.w_size, s.strm.next_in), 0); + s.strstart = s.w_size; + s.insert = s.strstart; + } + else { + if (s.window_size - s.strstart <= used) { + /* Slide the window down. */ + s.strstart -= s.w_size; + //zmemcpy(s->window, s->window + s->w_size, s->strstart); + s.window.set(s.window.subarray(s.w_size, s.w_size + s.strstart), 0); + if (s.matches < 2) { + s.matches++; /* add a pending slide_hash() */ + } + if (s.insert > s.strstart) { + s.insert = s.strstart; + } + } + //zmemcpy(s->window + s->strstart, s->strm->next_in - used, used); + s.window.set(s.strm.input.subarray(s.strm.next_in - used, s.strm.next_in), s.strstart); + s.strstart += used; + s.insert += used > s.w_size - s.insert ? s.w_size - s.insert : used; + } + s.block_start = s.strstart; + } + if (s.high_water < s.strstart) { + s.high_water = s.strstart; + } + + /* If the last block was written to next_out, then done. */ + if (last) { + return BS_FINISH_DONE; + } + + /* If flushing and all input has been consumed, then done. */ + if (flush !== Z_NO_FLUSH && flush !== Z_FINISH && + s.strm.avail_in === 0 && s.strstart === s.block_start) { + return BS_BLOCK_DONE; + } + + /* Fill the window with any remaining input. */ + have = s.window_size - s.strstart; + if (s.strm.avail_in > have && s.block_start >= s.w_size) { + /* Slide the window down. */ + s.block_start -= s.w_size; + s.strstart -= s.w_size; + //zmemcpy(s->window, s->window + s->w_size, s->strstart); + s.window.set(s.window.subarray(s.w_size, s.w_size + s.strstart), 0); + if (s.matches < 2) { + s.matches++; /* add a pending slide_hash() */ + } + have += s.w_size; /* more space now */ + if (s.insert > s.strstart) { + s.insert = s.strstart; + } + } + if (have > s.strm.avail_in) { + have = s.strm.avail_in; + } + if (have) { + read_buf(s.strm, s.window, s.strstart, have); + s.strstart += have; + s.insert += have > s.w_size - s.insert ? s.w_size - s.insert : have; + } + if (s.high_water < s.strstart) { + s.high_water = s.strstart; + } + + /* There was not enough avail_out to write a complete worthy or flushed + * stored block to next_out. Write a stored block to pending instead, if we + * have enough input for a worthy block, or if flushing and there is enough + * room for the remaining input as a stored block in the pending buffer. + */ + have = (s.bi_valid + 42) >> 3; /* number of header bytes */ + /* maximum stored block length that will fit in pending: */ + have = s.pending_buf_size - have > 65535/* MAX_STORED */ ? 65535/* MAX_STORED */ : s.pending_buf_size - have; + min_block = have > s.w_size ? s.w_size : have; + left = s.strstart - s.block_start; + if (left >= min_block || + ((left || flush === Z_FINISH) && flush !== Z_NO_FLUSH && + s.strm.avail_in === 0 && left <= have)) { + len = left > have ? have : left; + last = flush === Z_FINISH && s.strm.avail_in === 0 && + len === left ? 1 : 0; + _tr_stored_block(s, s.block_start, len, last); + s.block_start += len; + flush_pending(s.strm); + } + + /* We've done all we can with the available input and output. */ + return last ? BS_FINISH_STARTED : BS_NEED_MORE; +}; + + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +const deflate_fast = (s, flush) => { + + let hash_head; /* head of the hash chain */ + let bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s.lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { + break; /* flush the current block */ + } + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = 0/*NIL*/; + if (s.lookahead >= MIN_MATCH) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s.match_length = longest_match(s, hash_head); + /* longest_match() sets match_start */ + } + if (s.match_length >= MIN_MATCH) { + // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only + + /*** _tr_tally_dist(s, s.strstart - s.match_start, + s.match_length - MIN_MATCH, bflush); ***/ + bflush = _tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH); + + s.lookahead -= s.match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ + if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) { + s.match_length--; /* string at strstart already in table */ + do { + s.strstart++; + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s.match_length !== 0); + s.strstart++; + } else + { + s.strstart += s.match_length; + s.match_length = 0; + s.ins_h = s.window[s.strstart]; + /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + 1]); + +//#if MIN_MATCH != 3 +// Call UPDATE_HASH() MIN_MATCH-3 more times +//#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + //Tracevv((stderr,"%c", s.window[s.strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart]); + + s.lookahead--; + s.strstart++; + } + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1); + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.sym_next) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +}; + +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +const deflate_slow = (s, flush) => { + + let hash_head; /* head of hash chain */ + let bflush; /* set if current block must be flushed */ + + let max_insert; + + /* Process the input block. */ + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s.lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { break; } /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = 0/*NIL*/; + if (s.lookahead >= MIN_MATCH) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + + /* Find the longest match, discarding those <= prev_length. + */ + s.prev_length = s.match_length; + s.prev_match = s.match_start; + s.match_length = MIN_MATCH - 1; + + if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match && + s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s.match_length = longest_match(s, hash_head); + /* longest_match() sets match_start */ + + if (s.match_length <= 5 && + (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) { + + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + s.match_length = MIN_MATCH - 1; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) { + max_insert = s.strstart + s.lookahead - MIN_MATCH; + /* Do not insert strings in hash table beyond this. */ + + //check_match(s, s.strstart-1, s.prev_match, s.prev_length); + + /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match, + s.prev_length - MIN_MATCH, bflush);***/ + bflush = _tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH); + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. If there is not + * enough lookahead, the last two strings are not inserted in + * the hash table. + */ + s.lookahead -= s.prev_length - 1; + s.prev_length -= 2; + do { + if (++s.strstart <= max_insert) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + } while (--s.prev_length !== 0); + s.match_available = 0; + s.match_length = MIN_MATCH - 1; + s.strstart++; + + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + } else if (s.match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + //Tracevv((stderr,"%c", s->window[s->strstart-1])); + /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart - 1]); + + if (bflush) { + /*** FLUSH_BLOCK_ONLY(s, 0) ***/ + flush_block_only(s, false); + /***/ + } + s.strstart++; + s.lookahead--; + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + s.match_available = 1; + s.strstart++; + s.lookahead--; + } + } + //Assert (flush != Z_NO_FLUSH, "no flush?"); + if (s.match_available) { + //Tracevv((stderr,"%c", s->window[s->strstart-1])); + /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart - 1]); + + s.match_available = 0; + } + s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1; + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.sym_next) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + return BS_BLOCK_DONE; +}; + + +/* =========================================================================== + * For Z_RLE, simply look for runs of bytes, generate matches only of distance + * one. Do not maintain a hash table. (It will be regenerated if this run of + * deflate switches away from Z_RLE.) + */ +const deflate_rle = (s, flush) => { + + let bflush; /* set if current block must be flushed */ + let prev; /* byte at distance one to match */ + let scan, strend; /* scan goes up to strend for length of run */ + + const _win = s.window; + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the longest run, plus one for the unrolled loop. + */ + if (s.lookahead <= MAX_MATCH) { + fill_window(s); + if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { break; } /* flush the current block */ + } + + /* See how many times the previous byte repeats */ + s.match_length = 0; + if (s.lookahead >= MIN_MATCH && s.strstart > 0) { + scan = s.strstart - 1; + prev = _win[scan]; + if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) { + strend = s.strstart + MAX_MATCH; + do { + /*jshint noempty:false*/ + } while (prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + scan < strend); + s.match_length = MAX_MATCH - (strend - scan); + if (s.match_length > s.lookahead) { + s.match_length = s.lookahead; + } + } + //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); + } + + /* Emit match if have run of MIN_MATCH or longer, else emit literal */ + if (s.match_length >= MIN_MATCH) { + //check_match(s, s.strstart, s.strstart - 1, s.match_length); + + /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/ + bflush = _tr_tally(s, 1, s.match_length - MIN_MATCH); + + s.lookahead -= s.match_length; + s.strstart += s.match_length; + s.match_length = 0; + } else { + /* No match, output a literal byte */ + //Tracevv((stderr,"%c", s->window[s->strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart]); + + s.lookahead--; + s.strstart++; + } + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = 0; + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.sym_next) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +}; + +/* =========================================================================== + * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. + * (It will be regenerated if this run of deflate switches away from Huffman.) + */ +const deflate_huff = (s, flush) => { + + let bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we have a literal to write. */ + if (s.lookahead === 0) { + fill_window(s); + if (s.lookahead === 0) { + if (flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + break; /* flush the current block */ + } + } + + /* Output a literal byte */ + s.match_length = 0; + //Tracevv((stderr,"%c", s->window[s->strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart]); + s.lookahead--; + s.strstart++; + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = 0; + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.sym_next) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +}; + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ +function Config(good_length, max_lazy, nice_length, max_chain, func) { + + this.good_length = good_length; + this.max_lazy = max_lazy; + this.nice_length = nice_length; + this.max_chain = max_chain; + this.func = func; +} + +const configuration_table = [ + /* good lazy nice chain */ + new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */ + new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */ + new Config(4, 5, 16, 8, deflate_fast), /* 2 */ + new Config(4, 6, 32, 32, deflate_fast), /* 3 */ + + new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */ + new Config(8, 16, 32, 32, deflate_slow), /* 5 */ + new Config(8, 16, 128, 128, deflate_slow), /* 6 */ + new Config(8, 32, 128, 256, deflate_slow), /* 7 */ + new Config(32, 128, 258, 1024, deflate_slow), /* 8 */ + new Config(32, 258, 258, 4096, deflate_slow) /* 9 max compression */ +]; + + +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +const lm_init = (s) => { + + s.window_size = 2 * s.w_size; + + /*** CLEAR_HASH(s); ***/ + zero(s.head); // Fill with NIL (= 0); + + /* Set the default configuration parameters: + */ + s.max_lazy_match = configuration_table[s.level].max_lazy; + s.good_match = configuration_table[s.level].good_length; + s.nice_match = configuration_table[s.level].nice_length; + s.max_chain_length = configuration_table[s.level].max_chain; + + s.strstart = 0; + s.block_start = 0; + s.lookahead = 0; + s.insert = 0; + s.match_length = s.prev_length = MIN_MATCH - 1; + s.match_available = 0; + s.ins_h = 0; +}; + + +function DeflateState() { + this.strm = null; /* pointer back to this zlib stream */ + this.status = 0; /* as the name implies */ + this.pending_buf = null; /* output still pending */ + this.pending_buf_size = 0; /* size of pending_buf */ + this.pending_out = 0; /* next pending byte to output to the stream */ + this.pending = 0; /* nb of bytes in the pending buffer */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ + this.gzhead = null; /* gzip header information to write */ + this.gzindex = 0; /* where in extra, name, or comment */ + this.method = Z_DEFLATED; /* can only be DEFLATED */ + this.last_flush = -1; /* value of flush param for previous deflate call */ + + this.w_size = 0; /* LZ77 window size (32K by default) */ + this.w_bits = 0; /* log2(w_size) (8..16) */ + this.w_mask = 0; /* w_size - 1 */ + + this.window = null; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. + */ + + this.window_size = 0; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + this.prev = null; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + this.head = null; /* Heads of the hash chains or NIL. */ + + this.ins_h = 0; /* hash index of string to be inserted */ + this.hash_size = 0; /* number of elements in hash table */ + this.hash_bits = 0; /* log2(hash_size) */ + this.hash_mask = 0; /* hash_size-1 */ + + this.hash_shift = 0; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + this.block_start = 0; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + this.match_length = 0; /* length of best match */ + this.prev_match = 0; /* previous match */ + this.match_available = 0; /* set if previous match exists */ + this.strstart = 0; /* start of string to insert */ + this.match_start = 0; /* start of matching string */ + this.lookahead = 0; /* number of valid bytes ahead in window */ + + this.prev_length = 0; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + this.max_chain_length = 0; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + this.max_lazy_match = 0; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ + // That's alias to max_lazy_match, don't use directly + //this.max_insert_length = 0; + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + this.level = 0; /* compression level (1..9) */ + this.strategy = 0; /* favor or force Huffman coding*/ + + this.good_match = 0; + /* Use a faster search when the previous match is longer than this */ + + this.nice_match = 0; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + + /* Didn't use ct_data typedef below to suppress compiler warning */ + + // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + // Use flat array of DOUBLE size, with interleaved fata, + // because JS does not support effective + this.dyn_ltree = new Uint16Array(HEAP_SIZE * 2); + this.dyn_dtree = new Uint16Array((2 * D_CODES + 1) * 2); + this.bl_tree = new Uint16Array((2 * BL_CODES + 1) * 2); + zero(this.dyn_ltree); + zero(this.dyn_dtree); + zero(this.bl_tree); + + this.l_desc = null; /* desc. for literal tree */ + this.d_desc = null; /* desc. for distance tree */ + this.bl_desc = null; /* desc. for bit length tree */ + + //ush bl_count[MAX_BITS+1]; + this.bl_count = new Uint16Array(MAX_BITS + 1); + /* number of codes at each bit length for an optimal tree */ + + //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + this.heap = new Uint16Array(2 * L_CODES + 1); /* heap used to build the Huffman trees */ + zero(this.heap); + + this.heap_len = 0; /* number of elements in the heap */ + this.heap_max = 0; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + this.depth = new Uint16Array(2 * L_CODES + 1); //uch depth[2*L_CODES+1]; + zero(this.depth); + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + this.sym_buf = 0; /* buffer for distances and literals/lengths */ + + this.lit_bufsize = 0; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + this.sym_next = 0; /* running index in sym_buf */ + this.sym_end = 0; /* symbol table full when sym_next reaches this */ + + this.opt_len = 0; /* bit length of current block with optimal trees */ + this.static_len = 0; /* bit length of current block with static trees */ + this.matches = 0; /* number of string matches in current block */ + this.insert = 0; /* bytes at end of window left to insert */ + + + this.bi_buf = 0; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + this.bi_valid = 0; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + + // Used for window memory init. We safely ignore it for JS. That makes + // sense only for pointers and memory check tools. + //this.high_water = 0; + /* High water mark offset in window for initialized bytes -- bytes above + * this are set to zero in order to avoid memory check warnings when + * longest match routines access bytes past the input. This is then + * updated to the new high water mark. + */ +} + + +/* ========================================================================= + * Check for a valid deflate stream state. Return 0 if ok, 1 if not. + */ +const deflateStateCheck = (strm) => { + + if (!strm) { + return 1; + } + const s = strm.state; + if (!s || s.strm !== strm || (s.status !== INIT_STATE && +//#ifdef GZIP + s.status !== GZIP_STATE && +//#endif + s.status !== EXTRA_STATE && + s.status !== NAME_STATE && + s.status !== COMMENT_STATE && + s.status !== HCRC_STATE && + s.status !== BUSY_STATE && + s.status !== FINISH_STATE)) { + return 1; + } + return 0; +}; + + +const deflateResetKeep = (strm) => { + + if (deflateStateCheck(strm)) { + return err(strm, Z_STREAM_ERROR); + } + + strm.total_in = strm.total_out = 0; + strm.data_type = Z_UNKNOWN; + + const s = strm.state; + s.pending = 0; + s.pending_out = 0; + + if (s.wrap < 0) { + s.wrap = -s.wrap; + /* was made negative by deflate(..., Z_FINISH); */ + } + s.status = +//#ifdef GZIP + s.wrap === 2 ? GZIP_STATE : +//#endif + s.wrap ? INIT_STATE : BUSY_STATE; + strm.adler = (s.wrap === 2) ? + 0 // crc32(0, Z_NULL, 0) + : + 1; // adler32(0, Z_NULL, 0) + s.last_flush = -2; + _tr_init(s); + return Z_OK; +}; + + +const deflateReset = (strm) => { + + const ret = deflateResetKeep(strm); + if (ret === Z_OK) { + lm_init(strm.state); + } + return ret; +}; + + +const deflateSetHeader = (strm, head) => { + + if (deflateStateCheck(strm) || strm.state.wrap !== 2) { + return Z_STREAM_ERROR; + } + strm.state.gzhead = head; + return Z_OK; +}; + + +const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => { + + if (!strm) { // === Z_NULL + return Z_STREAM_ERROR; + } + let wrap = 1; + + if (level === Z_DEFAULT_COMPRESSION) { + level = 6; + } + + if (windowBits < 0) { /* suppress zlib wrapper */ + wrap = 0; + windowBits = -windowBits; + } + + else if (windowBits > 15) { + wrap = 2; /* write gzip wrapper instead */ + windowBits -= 16; + } + + + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED || + windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_FIXED || (windowBits === 8 && wrap !== 1)) { + return err(strm, Z_STREAM_ERROR); + } + + + if (windowBits === 8) { + windowBits = 9; + } + /* until 256-byte window bug fixed */ + + const s = new DeflateState(); + + strm.state = s; + s.strm = strm; + s.status = INIT_STATE; /* to pass state test in deflateReset() */ + + s.wrap = wrap; + s.gzhead = null; + s.w_bits = windowBits; + s.w_size = 1 << s.w_bits; + s.w_mask = s.w_size - 1; + + s.hash_bits = memLevel + 7; + s.hash_size = 1 << s.hash_bits; + s.hash_mask = s.hash_size - 1; + s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH); + + s.window = new Uint8Array(s.w_size * 2); + s.head = new Uint16Array(s.hash_size); + s.prev = new Uint16Array(s.w_size); + + // Don't need mem init magic for JS. + //s.high_water = 0; /* nothing written to s->window yet */ + + s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ + + /* We overlay pending_buf and sym_buf. This works since the average size + * for length/distance pairs over any compressed block is assured to be 31 + * bits or less. + * + * Analysis: The longest fixed codes are a length code of 8 bits plus 5 + * extra bits, for lengths 131 to 257. The longest fixed distance codes are + * 5 bits plus 13 extra bits, for distances 16385 to 32768. The longest + * possible fixed-codes length/distance pair is then 31 bits total. + * + * sym_buf starts one-fourth of the way into pending_buf. So there are + * three bytes in sym_buf for every four bytes in pending_buf. Each symbol + * in sym_buf is three bytes -- two for the distance and one for the + * literal/length. As each symbol is consumed, the pointer to the next + * sym_buf value to read moves forward three bytes. From that symbol, up to + * 31 bits are written to pending_buf. The closest the written pending_buf + * bits gets to the next sym_buf symbol to read is just before the last + * code is written. At that time, 31*(n-2) bits have been written, just + * after 24*(n-2) bits have been consumed from sym_buf. sym_buf starts at + * 8*n bits into pending_buf. (Note that the symbol buffer fills when n-1 + * symbols are written.) The closest the writing gets to what is unread is + * then n+14 bits. Here n is lit_bufsize, which is 16384 by default, and + * can range from 128 to 32768. + * + * Therefore, at a minimum, there are 142 bits of space between what is + * written and what is read in the overlain buffers, so the symbols cannot + * be overwritten by the compressed data. That space is actually 139 bits, + * due to the three-bit fixed-code block header. + * + * That covers the case where either Z_FIXED is specified, forcing fixed + * codes, or when the use of fixed codes is chosen, because that choice + * results in a smaller compressed block than dynamic codes. That latter + * condition then assures that the above analysis also covers all dynamic + * blocks. A dynamic-code block will only be chosen to be emitted if it has + * fewer bits than a fixed-code block would for the same set of symbols. + * Therefore its average symbol length is assured to be less than 31. So + * the compressed data for a dynamic block also cannot overwrite the + * symbols from which it is being constructed. + */ + + s.pending_buf_size = s.lit_bufsize * 4; + s.pending_buf = new Uint8Array(s.pending_buf_size); + + // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`) + //s->sym_buf = s->pending_buf + s->lit_bufsize; + s.sym_buf = s.lit_bufsize; + + //s->sym_end = (s->lit_bufsize - 1) * 3; + s.sym_end = (s.lit_bufsize - 1) * 3; + /* We avoid equality with lit_bufsize*3 because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ + + s.level = level; + s.strategy = strategy; + s.method = method; + + return deflateReset(strm); +}; + +const deflateInit = (strm, level) => { + + return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); +}; + + +/* ========================================================================= */ +const deflate = (strm, flush) => { + + if (deflateStateCheck(strm) || flush > Z_BLOCK || flush < 0) { + return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR; + } + + const s = strm.state; + + if (!strm.output || + (strm.avail_in !== 0 && !strm.input) || + (s.status === FINISH_STATE && flush !== Z_FINISH)) { + return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR); + } + + const old_flush = s.last_flush; + s.last_flush = flush; + + /* Flush as much pending output as possible */ + if (s.pending !== 0) { + flush_pending(strm); + if (strm.avail_out === 0) { + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: + */ + s.last_flush = -1; + return Z_OK; + } + + /* Make sure there is something to do and avoid duplicate consecutive + * flushes. For repeated and useless calls with Z_FINISH, we keep + * returning Z_STREAM_END instead of Z_BUF_ERROR. + */ + } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) && + flush !== Z_FINISH) { + return err(strm, Z_BUF_ERROR); + } + + /* User must not provide more input after the first FINISH: */ + if (s.status === FINISH_STATE && strm.avail_in !== 0) { + return err(strm, Z_BUF_ERROR); + } + + /* Write the header */ + if (s.status === INIT_STATE && s.wrap === 0) { + s.status = BUSY_STATE; + } + if (s.status === INIT_STATE) { + /* zlib header */ + let header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8; + let level_flags = -1; + + if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) { + level_flags = 0; + } else if (s.level < 6) { + level_flags = 1; + } else if (s.level === 6) { + level_flags = 2; + } else { + level_flags = 3; + } + header |= (level_flags << 6); + if (s.strstart !== 0) { header |= PRESET_DICT; } + header += 31 - (header % 31); + + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s.strstart !== 0) { + putShortMSB(s, strm.adler >>> 16); + putShortMSB(s, strm.adler & 0xffff); + } + strm.adler = 1; // adler32(0L, Z_NULL, 0); + s.status = BUSY_STATE; + + /* Compression must start with an empty pending buffer */ + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK; + } + } +//#ifdef GZIP + if (s.status === GZIP_STATE) { + /* gzip header */ + strm.adler = 0; //crc32(0L, Z_NULL, 0); + put_byte(s, 31); + put_byte(s, 139); + put_byte(s, 8); + if (!s.gzhead) { // s->gzhead == Z_NULL + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, s.level === 9 ? 2 : + (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? + 4 : 0)); + put_byte(s, OS_CODE); + s.status = BUSY_STATE; + + /* Compression must start with an empty pending buffer */ + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK; + } + } + else { + put_byte(s, (s.gzhead.text ? 1 : 0) + + (s.gzhead.hcrc ? 2 : 0) + + (!s.gzhead.extra ? 0 : 4) + + (!s.gzhead.name ? 0 : 8) + + (!s.gzhead.comment ? 0 : 16) + ); + put_byte(s, s.gzhead.time & 0xff); + put_byte(s, (s.gzhead.time >> 8) & 0xff); + put_byte(s, (s.gzhead.time >> 16) & 0xff); + put_byte(s, (s.gzhead.time >> 24) & 0xff); + put_byte(s, s.level === 9 ? 2 : + (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? + 4 : 0)); + put_byte(s, s.gzhead.os & 0xff); + if (s.gzhead.extra && s.gzhead.extra.length) { + put_byte(s, s.gzhead.extra.length & 0xff); + put_byte(s, (s.gzhead.extra.length >> 8) & 0xff); + } + if (s.gzhead.hcrc) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0); + } + s.gzindex = 0; + s.status = EXTRA_STATE; + } + } + if (s.status === EXTRA_STATE) { + if (s.gzhead.extra/* != Z_NULL*/) { + let beg = s.pending; /* start of bytes to update crc */ + let left = (s.gzhead.extra.length & 0xffff) - s.gzindex; + while (s.pending + left > s.pending_buf_size) { + let copy = s.pending_buf_size - s.pending; + // zmemcpy(s.pending_buf + s.pending, + // s.gzhead.extra + s.gzindex, copy); + s.pending_buf.set(s.gzhead.extra.subarray(s.gzindex, s.gzindex + copy), s.pending); + s.pending = s.pending_buf_size; + //--- HCRC_UPDATE(beg) ---// + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + //---// + s.gzindex += copy; + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK; + } + beg = 0; + left -= copy; + } + // JS specific: s.gzhead.extra may be TypedArray or Array for backward compatibility + // TypedArray.slice and TypedArray.from don't exist in IE10-IE11 + let gzhead_extra = new Uint8Array(s.gzhead.extra); + // zmemcpy(s->pending_buf + s->pending, + // s->gzhead->extra + s->gzindex, left); + s.pending_buf.set(gzhead_extra.subarray(s.gzindex, s.gzindex + left), s.pending); + s.pending += left; + //--- HCRC_UPDATE(beg) ---// + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + //---// + s.gzindex = 0; + } + s.status = NAME_STATE; + } + if (s.status === NAME_STATE) { + if (s.gzhead.name/* != Z_NULL*/) { + let beg = s.pending; /* start of bytes to update crc */ + let val; + do { + if (s.pending === s.pending_buf_size) { + //--- HCRC_UPDATE(beg) ---// + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + //---// + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK; + } + beg = 0; + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.name.length) { + val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + //--- HCRC_UPDATE(beg) ---// + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + //---// + s.gzindex = 0; + } + s.status = COMMENT_STATE; + } + if (s.status === COMMENT_STATE) { + if (s.gzhead.comment/* != Z_NULL*/) { + let beg = s.pending; /* start of bytes to update crc */ + let val; + do { + if (s.pending === s.pending_buf_size) { + //--- HCRC_UPDATE(beg) ---// + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + //---// + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK; + } + beg = 0; + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.comment.length) { + val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + //--- HCRC_UPDATE(beg) ---// + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + //---// + } + s.status = HCRC_STATE; + } + if (s.status === HCRC_STATE) { + if (s.gzhead.hcrc) { + if (s.pending + 2 > s.pending_buf_size) { + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK; + } + } + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + strm.adler = 0; //crc32(0L, Z_NULL, 0); + } + s.status = BUSY_STATE; + + /* Compression must start with an empty pending buffer */ + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK; + } + } +//#endif + + /* Start a new block or continue the current one. + */ + if (strm.avail_in !== 0 || s.lookahead !== 0 || + (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) { + let bstate = s.level === 0 ? deflate_stored(s, flush) : + s.strategy === Z_HUFFMAN_ONLY ? deflate_huff(s, flush) : + s.strategy === Z_RLE ? deflate_rle(s, flush) : + configuration_table[s.level].func(s, flush); + + if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) { + s.status = FINISH_STATE; + } + if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) { + if (strm.avail_out === 0) { + s.last_flush = -1; + /* avoid BUF_ERROR next call, see above */ + } + return Z_OK; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } + if (bstate === BS_BLOCK_DONE) { + if (flush === Z_PARTIAL_FLUSH) { + _tr_align(s); + } + else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */ + + _tr_stored_block(s, 0, 0, false); + /* For a full flush, this empty block will be recognized + * as a special marker by inflate_sync(). + */ + if (flush === Z_FULL_FLUSH) { + /*** CLEAR_HASH(s); ***/ /* forget history */ + zero(s.head); // Fill with NIL (= 0); + + if (s.lookahead === 0) { + s.strstart = 0; + s.block_start = 0; + s.insert = 0; + } + } + } + flush_pending(strm); + if (strm.avail_out === 0) { + s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK; + } + } + } + + if (flush !== Z_FINISH) { return Z_OK; } + if (s.wrap <= 0) { return Z_STREAM_END; } + + /* Write the trailer */ + if (s.wrap === 2) { + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + put_byte(s, (strm.adler >> 16) & 0xff); + put_byte(s, (strm.adler >> 24) & 0xff); + put_byte(s, strm.total_in & 0xff); + put_byte(s, (strm.total_in >> 8) & 0xff); + put_byte(s, (strm.total_in >> 16) & 0xff); + put_byte(s, (strm.total_in >> 24) & 0xff); + } + else + { + putShortMSB(s, strm.adler >>> 16); + putShortMSB(s, strm.adler & 0xffff); + } + + flush_pending(strm); + /* If avail_out is zero, the application will call deflate again + * to flush the rest. + */ + if (s.wrap > 0) { s.wrap = -s.wrap; } + /* write the trailer only once! */ + return s.pending !== 0 ? Z_OK : Z_STREAM_END; +}; + + +const deflateEnd = (strm) => { + + if (deflateStateCheck(strm)) { + return Z_STREAM_ERROR; + } + + const status = strm.state.status; + + strm.state = null; + + return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK; +}; + + +/* ========================================================================= + * Initializes the compression dictionary from the given byte + * sequence without producing any compressed output. + */ +const deflateSetDictionary = (strm, dictionary) => { + + let dictLength = dictionary.length; + + if (deflateStateCheck(strm)) { + return Z_STREAM_ERROR; + } + + const s = strm.state; + const wrap = s.wrap; + + if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) { + return Z_STREAM_ERROR; + } + + /* when using zlib wrappers, compute Adler-32 for provided dictionary */ + if (wrap === 1) { + /* adler32(strm->adler, dictionary, dictLength); */ + strm.adler = adler32(strm.adler, dictionary, dictLength, 0); + } + + s.wrap = 0; /* avoid computing Adler-32 in read_buf */ + + /* if dictionary would fill window, just replace the history */ + if (dictLength >= s.w_size) { + if (wrap === 0) { /* already empty otherwise */ + /*** CLEAR_HASH(s); ***/ + zero(s.head); // Fill with NIL (= 0); + s.strstart = 0; + s.block_start = 0; + s.insert = 0; + } + /* use the tail */ + // dictionary = dictionary.slice(dictLength - s.w_size); + let tmpDict = new Uint8Array(s.w_size); + tmpDict.set(dictionary.subarray(dictLength - s.w_size, dictLength), 0); + dictionary = tmpDict; + dictLength = s.w_size; + } + /* insert dictionary into window and hash */ + const avail = strm.avail_in; + const next = strm.next_in; + const input = strm.input; + strm.avail_in = dictLength; + strm.next_in = 0; + strm.input = dictionary; + fill_window(s); + while (s.lookahead >= MIN_MATCH) { + let str = s.strstart; + let n = s.lookahead - (MIN_MATCH - 1); + do { + /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ + s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]); + + s.prev[str & s.w_mask] = s.head[s.ins_h]; + + s.head[s.ins_h] = str; + str++; + } while (--n); + s.strstart = str; + s.lookahead = MIN_MATCH - 1; + fill_window(s); + } + s.strstart += s.lookahead; + s.block_start = s.strstart; + s.insert = s.lookahead; + s.lookahead = 0; + s.match_length = s.prev_length = MIN_MATCH - 1; + s.match_available = 0; + strm.next_in = next; + strm.input = input; + strm.avail_in = avail; + s.wrap = wrap; + return Z_OK; +}; + + +module.exports.deflateInit = deflateInit; +module.exports.deflateInit2 = deflateInit2; +module.exports.deflateReset = deflateReset; +module.exports.deflateResetKeep = deflateResetKeep; +module.exports.deflateSetHeader = deflateSetHeader; +module.exports.deflate = deflate; +module.exports.deflateEnd = deflateEnd; +module.exports.deflateSetDictionary = deflateSetDictionary; +module.exports.deflateInfo = 'pako deflate (from Nodeca project)'; + +/* Not implemented +module.exports.deflateBound = deflateBound; +module.exports.deflateCopy = deflateCopy; +module.exports.deflateGetDictionary = deflateGetDictionary; +module.exports.deflateParams = deflateParams; +module.exports.deflatePending = deflatePending; +module.exports.deflatePrime = deflatePrime; +module.exports.deflateTune = deflateTune; +*/ diff --git a/blue_modules/pako/lib/zlib/gzheader.js b/blue_modules/pako/lib/zlib/gzheader.js new file mode 100644 index 00000000000..9582cba6032 --- /dev/null +++ b/blue_modules/pako/lib/zlib/gzheader.js @@ -0,0 +1,58 @@ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +function GZheader() { + /* true if compressed data believed to be text */ + this.text = 0; + /* modification time */ + this.time = 0; + /* extra flags (not used when writing a gzip file) */ + this.xflags = 0; + /* operating system */ + this.os = 0; + /* pointer to extra field or Z_NULL if none */ + this.extra = null; + /* extra field length (valid if extra != Z_NULL) */ + this.extra_len = 0; // Actually, we don't need it in JS, + // but leave for few code modifications + + // + // Setup limits is not necessary because in js we should not preallocate memory + // for inflate use constant limit in 65536 bytes + // + + /* space at extra (only when reading header) */ + // this.extra_max = 0; + /* pointer to zero-terminated file name or Z_NULL */ + this.name = ''; + /* space at name (only when reading header) */ + // this.name_max = 0; + /* pointer to zero-terminated comment or Z_NULL */ + this.comment = ''; + /* space at comment (only when reading header) */ + // this.comm_max = 0; + /* true if there was or will be a header crc */ + this.hcrc = 0; + /* true when done reading gzip header (not used when writing a gzip file) */ + this.done = false; +} + +module.exports = GZheader; diff --git a/blue_modules/pako/lib/zlib/inffast.js b/blue_modules/pako/lib/zlib/inffast.js new file mode 100644 index 00000000000..f4d6e7e4538 --- /dev/null +++ b/blue_modules/pako/lib/zlib/inffast.js @@ -0,0 +1,344 @@ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +// See state defs from inflate.js +const BAD = 16209; /* got a data error -- remain here until reset */ +const TYPE = 16191; /* i: waiting for type bits, including last-flag bit */ + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state.mode === LEN + strm.avail_in >= 6 + strm.avail_out >= 258 + start >= strm.avail_out + state.bits < 8 + + On return, state.mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm.avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm.avail_out >= 258 for each loop to avoid checking for + output space. + */ +module.exports = function inflate_fast(strm, start) { + let _in; /* local strm.input */ + let last; /* have enough input while in < last */ + let _out; /* local strm.output */ + let beg; /* inflate()'s initial strm.output */ + let end; /* while out < end, enough space available */ +//#ifdef INFLATE_STRICT + let dmax; /* maximum distance from zlib header */ +//#endif + let wsize; /* window size or zero if not using window */ + let whave; /* valid bytes in the window */ + let wnext; /* window write index */ + // Use `s_window` instead `window`, avoid conflict with instrumentation tools + let s_window; /* allocated sliding window, if wsize != 0 */ + let hold; /* local strm.hold */ + let bits; /* local strm.bits */ + let lcode; /* local strm.lencode */ + let dcode; /* local strm.distcode */ + let lmask; /* mask for first level of length codes */ + let dmask; /* mask for first level of distance codes */ + let here; /* retrieved table entry */ + let op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + let len; /* match length, unused bytes */ + let dist; /* match distance */ + let from; /* where to copy match from */ + let from_source; + + + let input, output; // JS specific, because we have no pointers + + /* copy state to local variables */ + const state = strm.state; + //here = state.here; + _in = strm.next_in; + input = strm.input; + last = _in + (strm.avail_in - 5); + _out = strm.next_out; + output = strm.output; + beg = _out - (start - strm.avail_out); + end = _out + (strm.avail_out - 257); +//#ifdef INFLATE_STRICT + dmax = state.dmax; +//#endif + wsize = state.wsize; + whave = state.whave; + wnext = state.wnext; + s_window = state.window; + hold = state.hold; + bits = state.bits; + lcode = state.lencode; + dcode = state.distcode; + lmask = (1 << state.lenbits) - 1; + dmask = (1 << state.distbits) - 1; + + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + top: + do { + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + + here = lcode[hold & lmask]; + + dolen: + for (;;) { // Goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + if (op === 0) { /* literal */ + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + output[_out++] = here & 0xffff/*here.val*/; + } + else if (op & 16) { /* length base */ + len = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + len += hold & ((1 << op) - 1); + hold >>>= op; + bits -= op; + } + //Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + here = dcode[hold & dmask]; + + dodist: + for (;;) { // goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + + if (op & 16) { /* distance base */ + dist = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + } + dist += hold & ((1 << op) - 1); +//#ifdef INFLATE_STRICT + if (dist > dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break top; + } +//#endif + hold >>>= op; + bits -= op; + //Tracevv((stderr, "inflate: distance %u\n", dist)); + op = _out - beg; /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break top; + } + +// (!) This block is disabled in zlib defaults, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// if (len <= op - whave) { +// do { +// output[_out++] = 0; +// } while (--len); +// continue top; +// } +// len -= op - whave; +// do { +// output[_out++] = 0; +// } while (--op > whave); +// if (op === 0) { +// from = _out - dist; +// do { +// output[_out++] = output[from++]; +// } while (--len); +// continue top; +// } +//#endif + } + from = 0; // window index + from_source = s_window; + if (wnext === 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + else if (wnext < op) { /* wrap around window */ + from += wsize + wnext - op; + op -= wnext; + if (op < len) { /* some from end of window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = 0; + if (wnext < len) { /* some from start of window */ + op = wnext; + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + } + else { /* contiguous in window */ + from += wnext - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + while (len > 2) { + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + len -= 3; + } + if (len) { + output[_out++] = from_source[from++]; + if (len > 1) { + output[_out++] = from_source[from++]; + } + } + } + else { + from = _out - dist; /* copy direct from output */ + do { /* minimum length is three */ + output[_out++] = output[from++]; + output[_out++] = output[from++]; + output[_out++] = output[from++]; + len -= 3; + } while (len > 2); + if (len) { + output[_out++] = output[from++]; + if (len > 1) { + output[_out++] = output[from++]; + } + } + } + } + else if ((op & 64) === 0) { /* 2nd level distance code */ + here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dodist; + } + else { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break top; + } + + break; // need to emulate goto via "continue" + } + } + else if ((op & 64) === 0) { /* 2nd level length code */ + here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dolen; + } + else if (op & 32) { /* end-of-block */ + //Tracevv((stderr, "inflate: end of block\n")); + state.mode = TYPE; + break top; + } + else { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break top; + } + + break; // need to emulate goto via "continue" + } + } while (_in < last && _out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + _in -= len; + bits -= len << 3; + hold &= (1 << bits) - 1; + + /* update state and return */ + strm.next_in = _in; + strm.next_out = _out; + strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); + strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); + state.hold = hold; + state.bits = bits; + return; +}; diff --git a/blue_modules/pako/lib/zlib/inflate.js b/blue_modules/pako/lib/zlib/inflate.js new file mode 100644 index 00000000000..f5db4be048a --- /dev/null +++ b/blue_modules/pako/lib/zlib/inflate.js @@ -0,0 +1,1572 @@ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const adler32 = require('./adler32'); +const crc32 = require('./crc32'); +const inflate_fast = require('./inffast'); +const inflate_table = require('./inftrees'); + +const CODES = 0; +const LENS = 1; +const DISTS = 2; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_FINISH, Z_BLOCK, Z_TREES, + Z_OK, Z_STREAM_END, Z_NEED_DICT, Z_STREAM_ERROR, Z_DATA_ERROR, Z_MEM_ERROR, Z_BUF_ERROR, + Z_DEFLATED +} = require('./constants'); + + +/* STATES ====================================================================*/ +/* ===========================================================================*/ + + +const HEAD = 16180; /* i: waiting for magic header */ +const FLAGS = 16181; /* i: waiting for method and flags (gzip) */ +const TIME = 16182; /* i: waiting for modification time (gzip) */ +const OS = 16183; /* i: waiting for extra flags and operating system (gzip) */ +const EXLEN = 16184; /* i: waiting for extra length (gzip) */ +const EXTRA = 16185; /* i: waiting for extra bytes (gzip) */ +const NAME = 16186; /* i: waiting for end of file name (gzip) */ +const COMMENT = 16187; /* i: waiting for end of comment (gzip) */ +const HCRC = 16188; /* i: waiting for header crc (gzip) */ +const DICTID = 16189; /* i: waiting for dictionary check value */ +const DICT = 16190; /* waiting for inflateSetDictionary() call */ +const TYPE = 16191; /* i: waiting for type bits, including last-flag bit */ +const TYPEDO = 16192; /* i: same, but skip check to exit inflate on new block */ +const STORED = 16193; /* i: waiting for stored size (length and complement) */ +const COPY_ = 16194; /* i/o: same as COPY below, but only first time in */ +const COPY = 16195; /* i/o: waiting for input or output to copy stored block */ +const TABLE = 16196; /* i: waiting for dynamic block table lengths */ +const LENLENS = 16197; /* i: waiting for code length code lengths */ +const CODELENS = 16198; /* i: waiting for length/lit and distance code lengths */ +const LEN_ = 16199; /* i: same as LEN below, but only first time in */ +const LEN = 16200; /* i: waiting for length/lit/eob code */ +const LENEXT = 16201; /* i: waiting for length extra bits */ +const DIST = 16202; /* i: waiting for distance code */ +const DISTEXT = 16203; /* i: waiting for distance extra bits */ +const MATCH = 16204; /* o: waiting for output space to copy string */ +const LIT = 16205; /* o: waiting for output space to write literal */ +const CHECK = 16206; /* i: waiting for 32-bit check value */ +const LENGTH = 16207; /* i: waiting for 32-bit length (gzip) */ +const DONE = 16208; /* finished check, done -- remain here until reset */ +const BAD = 16209; /* got a data error -- remain here until reset */ +const MEM = 16210; /* got an inflate() memory error -- remain here until reset */ +const SYNC = 16211; /* looking for synchronization bytes to restart inflate() */ + +/* ===========================================================================*/ + + + +const ENOUGH_LENS = 852; +const ENOUGH_DISTS = 592; +//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +const MAX_WBITS = 15; +/* 32K LZ77 window */ +const DEF_WBITS = MAX_WBITS; + + +const zswap32 = (q) => { + + return (((q >>> 24) & 0xff) + + ((q >>> 8) & 0xff00) + + ((q & 0xff00) << 8) + + ((q & 0xff) << 24)); +}; + + +function InflateState() { + this.strm = null; /* pointer back to this zlib stream */ + this.mode = 0; /* current inflate mode */ + this.last = false; /* true if processing last block */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip, + bit 2 true to validate check value */ + this.havedict = false; /* true if dictionary provided */ + this.flags = 0; /* gzip header method and flags (0 if zlib), or + -1 if raw or no header yet */ + this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ + this.check = 0; /* protected copy of check value */ + this.total = 0; /* protected copy of output count */ + // TODO: may be {} + this.head = null; /* where to save gzip header information */ + + /* sliding window */ + this.wbits = 0; /* log base 2 of requested window size */ + this.wsize = 0; /* window size or zero if not using window */ + this.whave = 0; /* valid bytes in the window */ + this.wnext = 0; /* window write index */ + this.window = null; /* allocated sliding window, if needed */ + + /* bit accumulator */ + this.hold = 0; /* input bit accumulator */ + this.bits = 0; /* number of bits in "in" */ + + /* for string and stored block copying */ + this.length = 0; /* literal or length of data to copy */ + this.offset = 0; /* distance back to copy string from */ + + /* for table and code decoding */ + this.extra = 0; /* extra bits needed */ + + /* fixed and dynamic code tables */ + this.lencode = null; /* starting table for length/literal codes */ + this.distcode = null; /* starting table for distance codes */ + this.lenbits = 0; /* index bits for lencode */ + this.distbits = 0; /* index bits for distcode */ + + /* dynamic table building */ + this.ncode = 0; /* number of code length code lengths */ + this.nlen = 0; /* number of length code lengths */ + this.ndist = 0; /* number of distance code lengths */ + this.have = 0; /* number of code lengths in lens[] */ + this.next = null; /* next available space in codes[] */ + + this.lens = new Uint16Array(320); /* temporary storage for code lengths */ + this.work = new Uint16Array(288); /* work area for code table building */ + + /* + because we don't have pointers in js, we use lencode and distcode directly + as buffers so we don't need codes + */ + //this.codes = new Int32Array(ENOUGH); /* space for code tables */ + this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ + this.distdyn = null; /* dynamic table for distance codes (JS specific) */ + this.sane = 0; /* if false, allow invalid distance too far */ + this.back = 0; /* bits back of last unprocessed length/lit */ + this.was = 0; /* initial length of match */ +} + + +const inflateStateCheck = (strm) => { + + if (!strm) { + return 1; + } + const state = strm.state; + if (!state || state.strm !== strm || + state.mode < HEAD || state.mode > SYNC) { + return 1; + } + return 0; +}; + + +const inflateResetKeep = (strm) => { + + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR; } + const state = strm.state; + strm.total_in = strm.total_out = state.total = 0; + strm.msg = ''; /*Z_NULL*/ + if (state.wrap) { /* to support ill-conceived Java test suite */ + strm.adler = state.wrap & 1; + } + state.mode = HEAD; + state.last = 0; + state.havedict = 0; + state.flags = -1; + state.dmax = 32768; + state.head = null/*Z_NULL*/; + state.hold = 0; + state.bits = 0; + //state.lencode = state.distcode = state.next = state.codes; + state.lencode = state.lendyn = new Int32Array(ENOUGH_LENS); + state.distcode = state.distdyn = new Int32Array(ENOUGH_DISTS); + + state.sane = 1; + state.back = -1; + //Tracev((stderr, "inflate: reset\n")); + return Z_OK; +}; + + +const inflateReset = (strm) => { + + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR; } + const state = strm.state; + state.wsize = 0; + state.whave = 0; + state.wnext = 0; + return inflateResetKeep(strm); + +}; + + +const inflateReset2 = (strm, windowBits) => { + let wrap; + + /* get the state */ + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR; } + const state = strm.state; + + /* extract wrap request from windowBits parameter */ + if (windowBits < 0) { + wrap = 0; + windowBits = -windowBits; + } + else { + wrap = (windowBits >> 4) + 5; + if (windowBits < 48) { + windowBits &= 15; + } + } + + /* set number of window bits, free window if different */ + if (windowBits && (windowBits < 8 || windowBits > 15)) { + return Z_STREAM_ERROR; + } + if (state.window !== null && state.wbits !== windowBits) { + state.window = null; + } + + /* update state and reset the rest of it */ + state.wrap = wrap; + state.wbits = windowBits; + return inflateReset(strm); +}; + + +const inflateInit2 = (strm, windowBits) => { + + if (!strm) { return Z_STREAM_ERROR; } + //strm.msg = Z_NULL; /* in case we return an error */ + + const state = new InflateState(); + + //if (state === Z_NULL) return Z_MEM_ERROR; + //Tracev((stderr, "inflate: allocated\n")); + strm.state = state; + state.strm = strm; + state.window = null/*Z_NULL*/; + state.mode = HEAD; /* to pass state test in inflateReset2() */ + const ret = inflateReset2(strm, windowBits); + if (ret !== Z_OK) { + strm.state = null/*Z_NULL*/; + } + return ret; +}; + + +const inflateInit = (strm) => { + + return inflateInit2(strm, DEF_WBITS); +}; + + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +let virgin = true; + +let lenfix, distfix; // We have no pointers in JS, so keep tables separate + + +const fixedtables = (state) => { + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + lenfix = new Int32Array(512); + distfix = new Int32Array(32); + + /* literal/length table */ + let sym = 0; + while (sym < 144) { state.lens[sym++] = 8; } + while (sym < 256) { state.lens[sym++] = 9; } + while (sym < 280) { state.lens[sym++] = 7; } + while (sym < 288) { state.lens[sym++] = 8; } + + inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); + + /* distance table */ + sym = 0; + while (sym < 32) { state.lens[sym++] = 5; } + + inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); + + /* do this just once */ + virgin = false; + } + + state.lencode = lenfix; + state.lenbits = 9; + state.distcode = distfix; + state.distbits = 5; +}; + + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +const updatewindow = (strm, src, end, copy) => { + + let dist; + const state = strm.state; + + /* if it hasn't been done already, allocate space for the window */ + if (state.window === null) { + state.wsize = 1 << state.wbits; + state.wnext = 0; + state.whave = 0; + + state.window = new Uint8Array(state.wsize); + } + + /* copy state->wsize or less output bytes into the circular window */ + if (copy >= state.wsize) { + state.window.set(src.subarray(end - state.wsize, end), 0); + state.wnext = 0; + state.whave = state.wsize; + } + else { + dist = state.wsize - state.wnext; + if (dist > copy) { + dist = copy; + } + //zmemcpy(state->window + state->wnext, end - copy, dist); + state.window.set(src.subarray(end - copy, end - copy + dist), state.wnext); + copy -= dist; + if (copy) { + //zmemcpy(state->window, end - copy, copy); + state.window.set(src.subarray(end - copy, end), 0); + state.wnext = copy; + state.whave = state.wsize; + } + else { + state.wnext += dist; + if (state.wnext === state.wsize) { state.wnext = 0; } + if (state.whave < state.wsize) { state.whave += dist; } + } + } + return 0; +}; + + +const inflate = (strm, flush) => { + + let state; + let input, output; // input/output buffers + let next; /* next input INDEX */ + let put; /* next output INDEX */ + let have, left; /* available input and output */ + let hold; /* bit buffer */ + let bits; /* bits in bit buffer */ + let _in, _out; /* save starting available input and output */ + let copy; /* number of stored or match bytes to copy */ + let from; /* where to copy match bytes from */ + let from_source; + let here = 0; /* current decoding table entry */ + let here_bits, here_op, here_val; // paked "here" denormalized (JS specific) + //let last; /* parent table entry */ + let last_bits, last_op, last_val; // paked "last" denormalized (JS specific) + let len; /* length to copy for repeats, bits to drop */ + let ret; /* return code */ + const hbuf = new Uint8Array(4); /* buffer for gzip header crc calculation */ + let opts; + + let n; // temporary variable for NEED_BITS + + const order = /* permutation of code lengths */ + new Uint8Array([ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]); + + + if (inflateStateCheck(strm) || !strm.output || + (!strm.input && strm.avail_in !== 0)) { + return Z_STREAM_ERROR; + } + + state = strm.state; + if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ + + + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + _in = have; + _out = left; + ret = Z_OK; + + inf_leave: // goto emulation + for (;;) { + switch (state.mode) { + case HEAD: + if (state.wrap === 0) { + state.mode = TYPEDO; + break; + } + //=== NEEDBITS(16); + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ + if (state.wbits === 0) { + state.wbits = 15; + } + state.check = 0/*crc32(0L, Z_NULL, 0)*/; + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = FLAGS; + break; + } + if (state.head) { + state.head.done = false; + } + if (!(state.wrap & 1) || /* check if zlib header allowed */ + (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { + strm.msg = 'incorrect header check'; + state.mode = BAD; + break; + } + if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// + len = (hold & 0x0f)/*BITS(4)*/ + 8; + if (state.wbits === 0) { + state.wbits = len; + } + if (len > 15 || len > state.wbits) { + strm.msg = 'invalid window size'; + state.mode = BAD; + break; + } + + // !!! pako patch. Force use `options.windowBits` if passed. + // Required to always use max window size by default. + state.dmax = 1 << state.wbits; + //state.dmax = 1 << len; + + state.flags = 0; /* indicate zlib header */ + //Tracev((stderr, "inflate: zlib header ok\n")); + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = hold & 0x200 ? DICTID : TYPE; + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + break; + case FLAGS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.flags = hold; + if ((state.flags & 0xff) !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + if (state.flags & 0xe000) { + strm.msg = 'unknown header flags set'; + state.mode = BAD; + break; + } + if (state.head) { + state.head.text = ((hold >> 8) & 1); + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = TIME; + /* falls through */ + case TIME: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.time = hold; + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + //=== CRC4(state.check, hold) + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + hbuf[2] = (hold >>> 16) & 0xff; + hbuf[3] = (hold >>> 24) & 0xff; + state.check = crc32(state.check, hbuf, 4, 0); + //=== + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = OS; + /* falls through */ + case OS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.xflags = (hold & 0xff); + state.head.os = (hold >> 8); + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = EXLEN; + /* falls through */ + case EXLEN: + if (state.flags & 0x0400) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length = hold; + if (state.head) { + state.head.extra_len = hold; + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + else if (state.head) { + state.head.extra = null/*Z_NULL*/; + } + state.mode = EXTRA; + /* falls through */ + case EXTRA: + if (state.flags & 0x0400) { + copy = state.length; + if (copy > have) { copy = have; } + if (copy) { + if (state.head) { + len = state.head.extra_len - state.length; + if (!state.head.extra) { + // Use untyped array for more convenient processing later + state.head.extra = new Uint8Array(state.head.extra_len); + } + state.head.extra.set( + input.subarray( + next, + // extra field is limited to 65536 bytes + // - no need for additional size check + next + copy + ), + /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ + len + ); + //zmemcpy(state.head.extra + len, next, + // len + copy > state.head.extra_max ? + // state.head.extra_max - len : copy); + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + state.length -= copy; + } + if (state.length) { break inf_leave; } + } + state.length = 0; + state.mode = NAME; + /* falls through */ + case NAME: + if (state.flags & 0x0800) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + // TODO: 2 or 1 bytes? + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.name_max*/)) { + state.head.name += String.fromCharCode(len); + } + } while (len && copy < have); + + if ((state.flags & 0x0200) && (state.wrap & 4)) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.name = null; + } + state.length = 0; + state.mode = COMMENT; + /* falls through */ + case COMMENT: + if (state.flags & 0x1000) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.comm_max*/)) { + state.head.comment += String.fromCharCode(len); + } + } while (len && copy < have); + if ((state.flags & 0x0200) && (state.wrap & 4)) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.comment = null; + } + state.mode = HCRC; + /* falls through */ + case HCRC: + if (state.flags & 0x0200) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 4) && hold !== (state.check & 0xffff)) { + strm.msg = 'header crc mismatch'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + if (state.head) { + state.head.hcrc = ((state.flags >> 9) & 1); + state.head.done = true; + } + strm.adler = state.check = 0; + state.mode = TYPE; + break; + case DICTID: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + strm.adler = state.check = zswap32(hold); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = DICT; + /* falls through */ + case DICT: + if (state.havedict === 0) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + return Z_NEED_DICT; + } + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = TYPE; + /* falls through */ + case TYPE: + if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } + /* falls through */ + case TYPEDO: + if (state.last) { + //--- BYTEBITS() ---// + hold >>>= bits & 7; + bits -= bits & 7; + //---// + state.mode = CHECK; + break; + } + //=== NEEDBITS(3); */ + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.last = (hold & 0x01)/*BITS(1)*/; + //--- DROPBITS(1) ---// + hold >>>= 1; + bits -= 1; + //---// + + switch ((hold & 0x03)/*BITS(2)*/) { + case 0: /* stored block */ + //Tracev((stderr, "inflate: stored block%s\n", + // state.last ? " (last)" : "")); + state.mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + //Tracev((stderr, "inflate: fixed codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = LEN_; /* decode codes */ + if (flush === Z_TREES) { + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break inf_leave; + } + break; + case 2: /* dynamic block */ + //Tracev((stderr, "inflate: dynamic codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = TABLE; + break; + case 3: + strm.msg = 'invalid block type'; + state.mode = BAD; + } + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break; + case STORED: + //--- BYTEBITS() ---// /* go to byte boundary */ + hold >>>= bits & 7; + bits -= bits & 7; + //---// + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { + strm.msg = 'invalid stored block lengths'; + state.mode = BAD; + break; + } + state.length = hold & 0xffff; + //Tracev((stderr, "inflate: stored length %u\n", + // state.length)); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = COPY_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case COPY_: + state.mode = COPY; + /* falls through */ + case COPY: + copy = state.length; + if (copy) { + if (copy > have) { copy = have; } + if (copy > left) { copy = left; } + if (copy === 0) { break inf_leave; } + //--- zmemcpy(put, next, copy); --- + output.set(input.subarray(next, next + copy), put); + //---// + have -= copy; + next += copy; + left -= copy; + put += copy; + state.length -= copy; + break; + } + //Tracev((stderr, "inflate: stored end\n")); + state.mode = TYPE; + break; + case TABLE: + //=== NEEDBITS(14); */ + while (bits < 14) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// +//#ifndef PKZIP_BUG_WORKAROUND + if (state.nlen > 286 || state.ndist > 30) { + strm.msg = 'too many length or distance symbols'; + state.mode = BAD; + break; + } +//#endif + //Tracev((stderr, "inflate: table sizes ok\n")); + state.have = 0; + state.mode = LENLENS; + /* falls through */ + case LENLENS: + while (state.have < state.ncode) { + //=== NEEDBITS(3); + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + while (state.have < 19) { + state.lens[order[state.have++]] = 0; + } + // We have separate tables & no pointers. 2 commented lines below not needed. + //state.next = state.codes; + //state.lencode = state.next; + // Switch to use dynamic table + state.lencode = state.lendyn; + state.lenbits = 7; + + opts = { bits: state.lenbits }; + ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); + state.lenbits = opts.bits; + + if (ret) { + strm.msg = 'invalid code lengths set'; + state.mode = BAD; + break; + } + //Tracev((stderr, "inflate: code lengths ok\n")); + state.have = 0; + state.mode = CODELENS; + /* falls through */ + case CODELENS: + while (state.have < state.nlen + state.ndist) { + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_val < 16) { + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.lens[state.have++] = here_val; + } + else { + if (here_val === 16) { + //=== NEEDBITS(here.bits + 2); + n = here_bits + 2; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + if (state.have === 0) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + len = state.lens[state.have - 1]; + copy = 3 + (hold & 0x03);//BITS(2); + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + } + else if (here_val === 17) { + //=== NEEDBITS(here.bits + 3); + n = here_bits + 3; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 3 + (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + else { + //=== NEEDBITS(here.bits + 7); + n = here_bits + 7; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 11 + (hold & 0x7f);//BITS(7); + //--- DROPBITS(7) ---// + hold >>>= 7; + bits -= 7; + //---// + } + if (state.have + copy > state.nlen + state.ndist) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + while (copy--) { + state.lens[state.have++] = len; + } + } + } + + /* handle error breaks in while */ + if (state.mode === BAD) { break; } + + /* check for end-of-block code (better have one) */ + if (state.lens[256] === 0) { + strm.msg = 'invalid code -- missing end-of-block'; + state.mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state.lenbits = 9; + + opts = { bits: state.lenbits }; + ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.lenbits = opts.bits; + // state.lencode = state.next; + + if (ret) { + strm.msg = 'invalid literal/lengths set'; + state.mode = BAD; + break; + } + + state.distbits = 6; + //state.distcode.copy(state.codes); + // Switch to use dynamic table + state.distcode = state.distdyn; + opts = { bits: state.distbits }; + ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.distbits = opts.bits; + // state.distcode = state.next; + + if (ret) { + strm.msg = 'invalid distances set'; + state.mode = BAD; + break; + } + //Tracev((stderr, 'inflate: codes ok\n')); + state.mode = LEN_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case LEN_: + state.mode = LEN; + /* falls through */ + case LEN: + if (have >= 6 && left >= 258) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + inflate_fast(strm, _out); + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + if (state.mode === TYPE) { + state.back = -1; + } + break; + } + state.back = 0; + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if (here_bits <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_op && (here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.lencode[last_val + + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + state.length = here_val; + if (here_op === 0) { + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + state.mode = LIT; + break; + } + if (here_op & 32) { + //Tracevv((stderr, "inflate: end of block\n")); + state.back = -1; + state.mode = TYPE; + break; + } + if (here_op & 64) { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break; + } + state.extra = here_op & 15; + state.mode = LENEXT; + /* falls through */ + case LENEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } + //Tracevv((stderr, "inflate: length %u\n", state.length)); + state.was = state.length; + state.mode = DIST; + /* falls through */ + case DIST: + for (;;) { + here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if ((here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.distcode[last_val + + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + if (here_op & 64) { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break; + } + state.offset = here_val; + state.extra = (here_op) & 15; + state.mode = DISTEXT; + /* falls through */ + case DISTEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } +//#ifdef INFLATE_STRICT + if (state.offset > state.dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +//#endif + //Tracevv((stderr, "inflate: distance %u\n", state.offset)); + state.mode = MATCH; + /* falls through */ + case MATCH: + if (left === 0) { break inf_leave; } + copy = _out - left; + if (state.offset > copy) { /* copy from window */ + copy = state.offset - copy; + if (copy > state.whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +// (!) This block is disabled in zlib defaults, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// Trace((stderr, "inflate.c too far\n")); +// copy -= state.whave; +// if (copy > state.length) { copy = state.length; } +// if (copy > left) { copy = left; } +// left -= copy; +// state.length -= copy; +// do { +// output[put++] = 0; +// } while (--copy); +// if (state.length === 0) { state.mode = LEN; } +// break; +//#endif + } + if (copy > state.wnext) { + copy -= state.wnext; + from = state.wsize - copy; + } + else { + from = state.wnext - copy; + } + if (copy > state.length) { copy = state.length; } + from_source = state.window; + } + else { /* copy from output */ + from_source = output; + from = put - state.offset; + copy = state.length; + } + if (copy > left) { copy = left; } + left -= copy; + state.length -= copy; + do { + output[put++] = from_source[from++]; + } while (--copy); + if (state.length === 0) { state.mode = LEN; } + break; + case LIT: + if (left === 0) { break inf_leave; } + output[put++] = state.length; + left--; + state.mode = LEN; + break; + case CHECK: + if (state.wrap) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + // Use '|' instead of '+' to make sure that result is signed + hold |= input[next++] << bits; + bits += 8; + } + //===// + _out -= left; + strm.total_out += _out; + state.total += _out; + if ((state.wrap & 4) && _out) { + strm.adler = state.check = + /*UPDATE_CHECK(state.check, put - _out, _out);*/ + (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out)); + + } + _out = left; + // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too + if ((state.wrap & 4) && (state.flags ? hold : zswap32(hold)) !== state.check) { + strm.msg = 'incorrect data check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: check matches trailer\n")); + } + state.mode = LENGTH; + /* falls through */ + case LENGTH: + if (state.wrap && state.flags) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 4) && hold !== (state.total & 0xffffffff)) { + strm.msg = 'incorrect length check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: length matches trailer\n")); + } + state.mode = DONE; + /* falls through */ + case DONE: + ret = Z_STREAM_END; + break inf_leave; + case BAD: + ret = Z_DATA_ERROR; + break inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + /* falls through */ + default: + return Z_STREAM_ERROR; + } + } + + // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + + if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && + (state.mode < CHECK || flush !== Z_FINISH))) { + if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) { + state.mode = MEM; + return Z_MEM_ERROR; + } + } + _in -= strm.avail_in; + _out -= strm.avail_out; + strm.total_in += _in; + strm.total_out += _out; + state.total += _out; + if ((state.wrap & 4) && _out) { + strm.adler = state.check = /*UPDATE_CHECK(state.check, strm.next_out - _out, _out);*/ + (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out)); + } + strm.data_type = state.bits + (state.last ? 64 : 0) + + (state.mode === TYPE ? 128 : 0) + + (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); + if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) { + ret = Z_BUF_ERROR; + } + return ret; +}; + + +const inflateEnd = (strm) => { + + if (inflateStateCheck(strm)) { + return Z_STREAM_ERROR; + } + + let state = strm.state; + if (state.window) { + state.window = null; + } + strm.state = null; + return Z_OK; +}; + + +const inflateGetHeader = (strm, head) => { + + /* check state */ + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR; } + const state = strm.state; + if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; } + + /* save header structure */ + state.head = head; + head.done = false; + return Z_OK; +}; + + +const inflateSetDictionary = (strm, dictionary) => { + const dictLength = dictionary.length; + + let state; + let dictid; + let ret; + + /* check state */ + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR; } + state = strm.state; + + if (state.wrap !== 0 && state.mode !== DICT) { + return Z_STREAM_ERROR; + } + + /* check for correct dictionary identifier */ + if (state.mode === DICT) { + dictid = 1; /* adler32(0, null, 0)*/ + /* dictid = adler32(dictid, dictionary, dictLength); */ + dictid = adler32(dictid, dictionary, dictLength, 0); + if (dictid !== state.check) { + return Z_DATA_ERROR; + } + } + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary, dictLength, dictLength); + if (ret) { + state.mode = MEM; + return Z_MEM_ERROR; + } + state.havedict = 1; + // Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; +}; + + +module.exports.inflateReset = inflateReset; +module.exports.inflateReset2 = inflateReset2; +module.exports.inflateResetKeep = inflateResetKeep; +module.exports.inflateInit = inflateInit; +module.exports.inflateInit2 = inflateInit2; +module.exports.inflate = inflate; +module.exports.inflateEnd = inflateEnd; +module.exports.inflateGetHeader = inflateGetHeader; +module.exports.inflateSetDictionary = inflateSetDictionary; +module.exports.inflateInfo = 'pako inflate (from Nodeca project)'; + +/* Not implemented +module.exports.inflateCodesUsed = inflateCodesUsed; +module.exports.inflateCopy = inflateCopy; +module.exports.inflateGetDictionary = inflateGetDictionary; +module.exports.inflateMark = inflateMark; +module.exports.inflatePrime = inflatePrime; +module.exports.inflateSync = inflateSync; +module.exports.inflateSyncPoint = inflateSyncPoint; +module.exports.inflateUndermine = inflateUndermine; +module.exports.inflateValidate = inflateValidate; +*/ diff --git a/blue_modules/pako/lib/zlib/inftrees.js b/blue_modules/pako/lib/zlib/inftrees.js new file mode 100644 index 00000000000..eee389eacce --- /dev/null +++ b/blue_modules/pako/lib/zlib/inftrees.js @@ -0,0 +1,340 @@ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const MAXBITS = 15; +const ENOUGH_LENS = 852; +const ENOUGH_DISTS = 592; +//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +const CODES = 0; +const LENS = 1; +const DISTS = 2; + +const lbase = new Uint16Array([ /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 +]); + +const lext = new Uint8Array([ /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 +]); + +const dbase = new Uint16Array([ /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0 +]); + +const dext = new Uint8Array([ /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64 +]); + +const inflate_table = (type, lens, lens_index, codes, table, table_index, work, opts) => +{ + const bits = opts.bits; + //here = opts.here; /* table entry for duplication */ + + let len = 0; /* a code's length in bits */ + let sym = 0; /* index of code symbols */ + let min = 0, max = 0; /* minimum and maximum code lengths */ + let root = 0; /* number of index bits for root table */ + let curr = 0; /* number of index bits for current table */ + let drop = 0; /* code bits to drop for sub-table */ + let left = 0; /* number of prefix codes available */ + let used = 0; /* code entries in table used */ + let huff = 0; /* Huffman code */ + let incr; /* for incrementing code, index */ + let fill; /* index for replicating entries */ + let low; /* low bits for current root entry */ + let mask; /* mask for low root bits */ + let next; /* next available space in table */ + let base = null; /* base value table to use */ +// let shoextra; /* extra bits table to use */ + let match; /* use base and extra for symbol >= match */ + const count = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ + const offs = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ + let extra = null; + + let here_bits, here_op, here_val; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) { + count[len] = 0; + } + for (sym = 0; sym < codes; sym++) { + count[lens[lens_index + sym]]++; + } + + /* bound code lengths, force root to be within code lengths */ + root = bits; + for (max = MAXBITS; max >= 1; max--) { + if (count[max] !== 0) { break; } + } + if (root > max) { + root = max; + } + if (max === 0) { /* no symbols to code at all */ + //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ + //table.bits[opts.table_index] = 1; //here.bits = (var char)1; + //table.val[opts.table_index++] = 0; //here.val = (var short)0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + + //table.op[opts.table_index] = 64; + //table.bits[opts.table_index] = 1; + //table.val[opts.table_index++] = 0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + opts.bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min < max; min++) { + if (count[min] !== 0) { break; } + } + if (root < min) { + root = min; + } + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) { + return -1; + } /* over-subscribed */ + } + if (left > 0 && (type === CODES || max !== 1)) { + return -1; /* incomplete set */ + } + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) { + offs[len + 1] = offs[len] + count[len]; + } + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) { + if (lens[lens_index + sym] !== 0) { + work[offs[lens[lens_index + sym]]++] = sym; + } + } + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftrees.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + // poor man optimization - use if-else instead of switch, + // to avoid deopts in old v8 + if (type === CODES) { + base = extra = work; /* dummy value--not used */ + match = 20; + + } else if (type === LENS) { + base = lbase; + extra = lext; + match = 257; + + } else { /* DISTS */ + base = dbase; + extra = dext; + match = 0; + } + + /* initialize opts for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = table_index; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = -1; /* trigger new sub-table when len > root */ + used = 1 << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type === LENS && used > ENOUGH_LENS) || + (type === DISTS && used > ENOUGH_DISTS)) { + return 1; + } + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + here_bits = len - drop; + if (work[sym] + 1 < match) { + here_op = 0; + here_val = work[sym]; + } + else if (work[sym] >= match) { + here_op = extra[work[sym] - match]; + here_val = base[work[sym] - match]; + } + else { + here_op = 32 + 64; /* end of block */ + here_val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1 << (len - drop); + fill = 1 << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; + } while (fill !== 0); + + /* backwards increment the len-bit code huff */ + incr = 1 << (len - 1); + while (huff & incr) { + incr >>= 1; + } + if (incr !== 0) { + huff &= incr - 1; + huff += incr; + } else { + huff = 0; + } + + /* go to next symbol, update count, len */ + sym++; + if (--count[len] === 0) { + if (len === max) { break; } + len = lens[lens_index + work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) !== low) { + /* if first time, transition to sub-tables */ + if (drop === 0) { + drop = root; + } + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = 1 << curr; + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) { break; } + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1 << curr; + if ((type === LENS && used > ENOUGH_LENS) || + (type === DISTS && used > ENOUGH_DISTS)) { + return 1; + } + + /* point entry in root table to sub-table */ + low = huff & mask; + /*table.op[low] = curr; + table.bits[low] = root; + table.val[low] = next - opts.table_index;*/ + table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; + } + } + + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff !== 0) { + //table.op[next + huff] = 64; /* invalid code marker */ + //table.bits[next + huff] = len - drop; + //table.val[next + huff] = 0; + table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; + } + + /* set return parameters */ + //opts.table_index += used; + opts.bits = root; + return 0; +}; + + +module.exports = inflate_table; diff --git a/blue_modules/pako/lib/zlib/messages.js b/blue_modules/pako/lib/zlib/messages.js new file mode 100644 index 00000000000..426daec6b6e --- /dev/null +++ b/blue_modules/pako/lib/zlib/messages.js @@ -0,0 +1,32 @@ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +module.exports = { + 2: 'need dictionary', /* Z_NEED_DICT 2 */ + 1: 'stream end', /* Z_STREAM_END 1 */ + 0: '', /* Z_OK 0 */ + '-1': 'file error', /* Z_ERRNO (-1) */ + '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ + '-3': 'data error', /* Z_DATA_ERROR (-3) */ + '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ + '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ + '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ +}; diff --git a/blue_modules/pako/lib/zlib/trees.js b/blue_modules/pako/lib/zlib/trees.js new file mode 100644 index 00000000000..300f1d832b9 --- /dev/null +++ b/blue_modules/pako/lib/zlib/trees.js @@ -0,0 +1,1179 @@ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +/* eslint-disable space-unary-ops */ + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + + +//const Z_FILTERED = 1; +//const Z_HUFFMAN_ONLY = 2; +//const Z_RLE = 3; +const Z_FIXED = 4; +//const Z_DEFAULT_STRATEGY = 0; + +/* Possible values of the data_type field (though see inflate()) */ +const Z_BINARY = 0; +const Z_TEXT = 1; +//const Z_ASCII = 1; // = Z_TEXT +const Z_UNKNOWN = 2; + +/*============================================================================*/ + + +function zero(buf) { let len = buf.length; while (--len >= 0) { buf[len] = 0; } } + +// From zutil.h + +const STORED_BLOCK = 0; +const STATIC_TREES = 1; +const DYN_TREES = 2; +/* The three kinds of block type */ + +const MIN_MATCH = 3; +const MAX_MATCH = 258; +/* The minimum and maximum match lengths */ + +// From deflate.h +/* =========================================================================== + * Internal compression state. + */ + +const LENGTH_CODES = 29; +/* number of length codes, not counting the special END_BLOCK code */ + +const LITERALS = 256; +/* number of literal bytes 0..255 */ + +const L_CODES = LITERALS + 1 + LENGTH_CODES; +/* number of Literal or Length codes, including the END_BLOCK code */ + +const D_CODES = 30; +/* number of distance codes */ + +const BL_CODES = 19; +/* number of codes used to transfer the bit lengths */ + +const HEAP_SIZE = 2 * L_CODES + 1; +/* maximum heap size */ + +const MAX_BITS = 15; +/* All codes must not exceed MAX_BITS bits */ + +const Buf_size = 16; +/* size of bit buffer in bi_buf */ + + +/* =========================================================================== + * Constants + */ + +const MAX_BL_BITS = 7; +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +const END_BLOCK = 256; +/* end of block literal code */ + +const REP_3_6 = 16; +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ + +const REPZ_3_10 = 17; +/* repeat a zero length 3-10 times (3 bits of repeat count) */ + +const REPZ_11_138 = 18; +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +/* eslint-disable comma-spacing,array-bracket-spacing */ +const extra_lbits = /* extra bits for each length code */ + new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]); + +const extra_dbits = /* extra bits for each distance code */ + new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]); + +const extra_blbits = /* extra bits for each bit length code */ + new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]); + +const bl_order = + new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]); +/* eslint-enable comma-spacing,array-bracket-spacing */ + +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +/* =========================================================================== + * Local data. These are initialized only once. + */ + +// We pre-fill arrays with 0 to avoid uninitialized gaps + +const DIST_CODE_LEN = 512; /* see definition of array dist_code below */ + +// !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1 +const static_ltree = new Array((L_CODES + 2) * 2); +zero(static_ltree); +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ + +const static_dtree = new Array(D_CODES * 2); +zero(static_dtree); +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +const _dist_code = new Array(DIST_CODE_LEN); +zero(_dist_code); +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +const _length_code = new Array(MAX_MATCH - MIN_MATCH + 1); +zero(_length_code); +/* length code for each normalized match length (0 == MIN_MATCH) */ + +const base_length = new Array(LENGTH_CODES); +zero(base_length); +/* First normalized length for each code (0 = MIN_MATCH) */ + +const base_dist = new Array(D_CODES); +zero(base_dist); +/* First normalized distance for each code (0 = distance of 1) */ + + +function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) { + + this.static_tree = static_tree; /* static tree or NULL */ + this.extra_bits = extra_bits; /* extra bits for each code or NULL */ + this.extra_base = extra_base; /* base index for extra_bits */ + this.elems = elems; /* max number of elements in the tree */ + this.max_length = max_length; /* max bit length for the codes */ + + // show if `static_tree` has data or dummy - needed for monomorphic objects + this.has_stree = static_tree && static_tree.length; +} + + +let static_l_desc; +let static_d_desc; +let static_bl_desc; + + +function TreeDesc(dyn_tree, stat_desc) { + this.dyn_tree = dyn_tree; /* the dynamic tree */ + this.max_code = 0; /* largest code with non zero frequency */ + this.stat_desc = stat_desc; /* the corresponding static tree */ +} + + + +const d_code = (dist) => { + + return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)]; +}; + + +/* =========================================================================== + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +const put_short = (s, w) => { +// put_byte(s, (uch)((w) & 0xff)); +// put_byte(s, (uch)((ush)(w) >> 8)); + s.pending_buf[s.pending++] = (w) & 0xff; + s.pending_buf[s.pending++] = (w >>> 8) & 0xff; +}; + + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +const send_bits = (s, value, length) => { + + if (s.bi_valid > (Buf_size - length)) { + s.bi_buf |= (value << s.bi_valid) & 0xffff; + put_short(s, s.bi_buf); + s.bi_buf = value >> (Buf_size - s.bi_valid); + s.bi_valid += length - Buf_size; + } else { + s.bi_buf |= (value << s.bi_valid) & 0xffff; + s.bi_valid += length; + } +}; + + +const send_code = (s, c, tree) => { + + send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/); +}; + + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +const bi_reverse = (code, len) => { + + let res = 0; + do { + res |= code & 1; + code >>>= 1; + res <<= 1; + } while (--len > 0); + return res >>> 1; +}; + + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +const bi_flush = (s) => { + + if (s.bi_valid === 16) { + put_short(s, s.bi_buf); + s.bi_buf = 0; + s.bi_valid = 0; + + } else if (s.bi_valid >= 8) { + s.pending_buf[s.pending++] = s.bi_buf & 0xff; + s.bi_buf >>= 8; + s.bi_valid -= 8; + } +}; + + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +const gen_bitlen = (s, desc) => { +// deflate_state *s; +// tree_desc *desc; /* the tree descriptor */ + + const tree = desc.dyn_tree; + const max_code = desc.max_code; + const stree = desc.stat_desc.static_tree; + const has_stree = desc.stat_desc.has_stree; + const extra = desc.stat_desc.extra_bits; + const base = desc.stat_desc.extra_base; + const max_length = desc.stat_desc.max_length; + let h; /* heap index */ + let n, m; /* iterate over the tree elements */ + let bits; /* bit length */ + let xbits; /* extra bits */ + let f; /* frequency */ + let overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS; bits++) { + s.bl_count[bits] = 0; + } + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */ + + for (h = s.heap_max + 1; h < HEAP_SIZE; h++) { + n = s.heap[h]; + bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1; + if (bits > max_length) { + bits = max_length; + overflow++; + } + tree[n * 2 + 1]/*.Len*/ = bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) { continue; } /* not a leaf node */ + + s.bl_count[bits]++; + xbits = 0; + if (n >= base) { + xbits = extra[n - base]; + } + f = tree[n * 2]/*.Freq*/; + s.opt_len += f * (bits + xbits); + if (has_stree) { + s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits); + } + } + if (overflow === 0) { return; } + + // Tracev((stderr,"\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length - 1; + while (s.bl_count[bits] === 0) { bits--; } + s.bl_count[bits]--; /* move one leaf down the tree */ + s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */ + s.bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits !== 0; bits--) { + n = s.bl_count[bits]; + while (n !== 0) { + m = s.heap[--h]; + if (m > max_code) { continue; } + if (tree[m * 2 + 1]/*.Len*/ !== bits) { + // Tracev((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/; + tree[m * 2 + 1]/*.Len*/ = bits; + } + n--; + } + } +}; + + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +const gen_codes = (tree, max_code, bl_count) => { +// ct_data *tree; /* the tree to decorate */ +// int max_code; /* largest code with non zero frequency */ +// ushf *bl_count; /* number of codes at each bit length */ + + const next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */ + let code = 0; /* running code value */ + let bits; /* bit index */ + let n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + code = (code + bl_count[bits - 1]) << 1; + next_code[bits] = code; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + //Assert (code + bl_count[MAX_BITS]-1 == (1< { + + let n; /* iterates over tree elements */ + let bits; /* bit counter */ + let length; /* length value */ + let code; /* code value */ + let dist; /* distance index */ + const bl_count = new Array(MAX_BITS + 1); + /* number of codes at each bit length for an optimal tree */ + + // do check in _tr_init() + //if (static_init_done) return; + + /* For some embedded targets, global variables are not initialized: */ +/*#ifdef NO_INIT_GLOBAL_POINTERS + static_l_desc.static_tree = static_ltree; + static_l_desc.extra_bits = extra_lbits; + static_d_desc.static_tree = static_dtree; + static_d_desc.extra_bits = extra_dbits; + static_bl_desc.extra_bits = extra_blbits; +#endif*/ + + /* Initialize the mapping length (0..255) -> length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES - 1; code++) { + base_length[code] = length; + for (n = 0; n < (1 << extra_lbits[code]); n++) { + _length_code[length++] = code; + } + } + //Assert (length == 256, "tr_static_init: length != 256"); + /* Note that the length 255 (match length 258) can be represented + * in two different ways: code 284 + 5 bits or code 285, so we + * overwrite length_code[255] to use the best encoding: + */ + _length_code[length - 1] = code; + + /* Initialize the mapping dist (0..32K) -> dist code (0..29) */ + dist = 0; + for (code = 0; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1 << extra_dbits[code]); n++) { + _dist_code[dist++] = code; + } + } + //Assert (dist == 256, "tr_static_init: dist != 256"); + dist >>= 7; /* from now on, all distances are divided by 128 */ + for (; code < D_CODES; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { + _dist_code[256 + dist++] = code; + } + } + //Assert (dist == 256, "tr_static_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS; bits++) { + bl_count[bits] = 0; + } + + n = 0; + while (n <= 143) { + static_ltree[n * 2 + 1]/*.Len*/ = 8; + n++; + bl_count[8]++; + } + while (n <= 255) { + static_ltree[n * 2 + 1]/*.Len*/ = 9; + n++; + bl_count[9]++; + } + while (n <= 279) { + static_ltree[n * 2 + 1]/*.Len*/ = 7; + n++; + bl_count[7]++; + } + while (n <= 287) { + static_ltree[n * 2 + 1]/*.Len*/ = 8; + n++; + bl_count[8]++; + } + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes(static_ltree, L_CODES + 1, bl_count); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES; n++) { + static_dtree[n * 2 + 1]/*.Len*/ = 5; + static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5); + } + + // Now data ready and we can init static trees + static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS); + static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES, MAX_BITS); + static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES, MAX_BL_BITS); + + //static_init_done = true; +}; + + +/* =========================================================================== + * Initialize a new block. + */ +const init_block = (s) => { + + let n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; } + for (n = 0; n < D_CODES; n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; } + for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; } + + s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1; + s.opt_len = s.static_len = 0; + s.sym_next = s.matches = 0; +}; + + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +const bi_windup = (s) => +{ + if (s.bi_valid > 8) { + put_short(s, s.bi_buf); + } else if (s.bi_valid > 0) { + //put_byte(s, (Byte)s->bi_buf); + s.pending_buf[s.pending++] = s.bi_buf; + } + s.bi_buf = 0; + s.bi_valid = 0; +}; + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +const smaller = (tree, n, m, depth) => { + + const _n2 = n * 2; + const _m2 = m * 2; + return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ || + (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m])); +}; + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +const pqdownheap = (s, tree, k) => { +// deflate_state *s; +// ct_data *tree; /* the tree to restore */ +// int k; /* node to move down */ + + const v = s.heap[k]; + let j = k << 1; /* left son of k */ + while (j <= s.heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < s.heap_len && + smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) { + j++; + } + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, s.heap[j], s.depth)) { break; } + + /* Exchange v with the smallest son */ + s.heap[k] = s.heap[j]; + k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + s.heap[k] = v; +}; + + +// inlined manually +// const SMALLEST = 1; + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +const compress_block = (s, ltree, dtree) => { +// deflate_state *s; +// const ct_data *ltree; /* literal tree */ +// const ct_data *dtree; /* distance tree */ + + let dist; /* distance of matched string */ + let lc; /* match length or unmatched char (if dist == 0) */ + let sx = 0; /* running index in sym_buf */ + let code; /* the code to send */ + let extra; /* number of extra bits to send */ + + if (s.sym_next !== 0) { + do { + dist = s.pending_buf[s.sym_buf + sx++] & 0xff; + dist += (s.pending_buf[s.sym_buf + sx++] & 0xff) << 8; + lc = s.pending_buf[s.sym_buf + sx++]; + if (dist === 0) { + send_code(s, lc, ltree); /* send a literal byte */ + //Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code + LITERALS + 1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra !== 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + //Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra !== 0) { + dist -= base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check that the overlay between pending_buf and sym_buf is ok: */ + //Assert(s->pending < s->lit_bufsize + sx, "pendingBuf overflow"); + + } while (sx < s.sym_next); + } + + send_code(s, END_BLOCK, ltree); +}; + + +/* =========================================================================== + * Construct one Huffman tree and assigns the code bit strings and lengths. + * Update the total bit length for the current block. + * IN assertion: the field freq is set for all tree elements. + * OUT assertions: the fields len and code are set to the optimal bit length + * and corresponding code. The length opt_len is updated; static_len is + * also updated if stree is not null. The field max_code is set. + */ +const build_tree = (s, desc) => { +// deflate_state *s; +// tree_desc *desc; /* the tree descriptor */ + + const tree = desc.dyn_tree; + const stree = desc.stat_desc.static_tree; + const has_stree = desc.stat_desc.has_stree; + const elems = desc.stat_desc.elems; + let n, m; /* iterate over heap elements */ + let max_code = -1; /* largest code with non zero frequency */ + let node; /* new node being created */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + s.heap_len = 0; + s.heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) { + if (tree[n * 2]/*.Freq*/ !== 0) { + s.heap[++s.heap_len] = max_code = n; + s.depth[n] = 0; + + } else { + tree[n * 2 + 1]/*.Len*/ = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (s.heap_len < 2) { + node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); + tree[node * 2]/*.Freq*/ = 1; + s.depth[node] = 0; + s.opt_len--; + + if (has_stree) { + s.static_len -= stree[node * 2 + 1]/*.Len*/; + } + /* node is 0 or 1 so it does not have extra bits */ + } + desc.max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); } + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + node = elems; /* next internal node of the tree */ + do { + //pqremove(s, tree, n); /* n = node of least frequency */ + /*** pqremove ***/ + n = s.heap[1/*SMALLEST*/]; + s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--]; + pqdownheap(s, tree, 1/*SMALLEST*/); + /***/ + + m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */ + + s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */ + s.heap[--s.heap_max] = m; + + /* Create a new node father of n and m */ + tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/; + s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1; + tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node; + + /* and insert the new node in the heap */ + s.heap[1/*SMALLEST*/] = node++; + pqdownheap(s, tree, 1/*SMALLEST*/); + + } while (s.heap_len >= 2); + + s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen(s, desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes(tree, max_code, s.bl_count); +}; + + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. + */ +const scan_tree = (s, tree, max_code) => { +// deflate_state *s; +// ct_data *tree; /* the tree to be scanned */ +// int max_code; /* and its largest code of non zero frequency */ + + let n; /* iterates over all tree elements */ + let prevlen = -1; /* last emitted length */ + let curlen; /* length of current code */ + + let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ + + let count = 0; /* repeat count of the current code */ + let max_count = 7; /* max repeat count */ + let min_count = 4; /* min repeat count */ + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; + + if (++count < max_count && curlen === nextlen) { + continue; + + } else if (count < min_count) { + s.bl_tree[curlen * 2]/*.Freq*/ += count; + + } else if (curlen !== 0) { + + if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; } + s.bl_tree[REP_3_6 * 2]/*.Freq*/++; + + } else if (count <= 10) { + s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++; + + } else { + s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++; + } + + count = 0; + prevlen = curlen; + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + + } else if (curlen === nextlen) { + max_count = 6; + min_count = 3; + + } else { + max_count = 7; + min_count = 4; + } + } +}; + + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +const send_tree = (s, tree, max_code) => { +// deflate_state *s; +// ct_data *tree; /* the tree to be scanned */ +// int max_code; /* and its largest code of non zero frequency */ + + let n; /* iterates over all tree elements */ + let prevlen = -1; /* last emitted length */ + let curlen; /* length of current code */ + + let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ + + let count = 0; /* repeat count of the current code */ + let max_count = 7; /* max repeat count */ + let min_count = 4; /* min repeat count */ + + /* tree[max_code+1].Len = -1; */ /* guard already set */ + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; + + if (++count < max_count && curlen === nextlen) { + continue; + + } else if (count < min_count) { + do { send_code(s, curlen, s.bl_tree); } while (--count !== 0); + + } else if (curlen !== 0) { + if (curlen !== prevlen) { + send_code(s, curlen, s.bl_tree); + count--; + } + //Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(s, REP_3_6, s.bl_tree); + send_bits(s, count - 3, 2); + + } else if (count <= 10) { + send_code(s, REPZ_3_10, s.bl_tree); + send_bits(s, count - 3, 3); + + } else { + send_code(s, REPZ_11_138, s.bl_tree); + send_bits(s, count - 11, 7); + } + + count = 0; + prevlen = curlen; + if (nextlen === 0) { + max_count = 138; + min_count = 3; + + } else if (curlen === nextlen) { + max_count = 6; + min_count = 3; + + } else { + max_count = 7; + min_count = 4; + } + } +}; + + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +const build_bl_tree = (s) => { + + let max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree(s, s.dyn_ltree, s.l_desc.max_code); + scan_tree(s, s.dyn_dtree, s.d_desc.max_code); + + /* Build the bit length tree: */ + build_tree(s, s.bl_desc); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) { + if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) { + break; + } + } + /* Update opt_len to include the bit length tree and counts */ + s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; + //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + // s->opt_len, s->static_len)); + + return max_blindex; +}; + + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +const send_all_trees = (s, lcodes, dcodes, blcodes) => { +// deflate_state *s; +// int lcodes, dcodes, blcodes; /* number of codes for each tree */ + + let rank; /* index in bl_order */ + + //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + // "too many codes"); + //Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes - 1, 5); + send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + //Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3); + } + //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + + send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */ + //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + + send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */ + //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); +}; + + +/* =========================================================================== + * Check if the data type is TEXT or BINARY, using the following algorithm: + * - TEXT if the two conditions below are satisfied: + * a) There are no non-portable control characters belonging to the + * "block list" (0..6, 14..25, 28..31). + * b) There is at least one printable character belonging to the + * "allow list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). + * - BINARY otherwise. + * - The following partially-portable control characters form a + * "gray list" that is ignored in this detection algorithm: + * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). + * IN assertion: the fields Freq of dyn_ltree are set. + */ +const detect_data_type = (s) => { + /* block_mask is the bit mask of block-listed bytes + * set bits 0..6, 14..25, and 28..31 + * 0xf3ffc07f = binary 11110011111111111100000001111111 + */ + let block_mask = 0xf3ffc07f; + let n; + + /* Check for non-textual ("block-listed") bytes. */ + for (n = 0; n <= 31; n++, block_mask >>>= 1) { + if ((block_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) { + return Z_BINARY; + } + } + + /* Check for textual ("allow-listed") bytes. */ + if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 || + s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) { + return Z_TEXT; + } + for (n = 32; n < LITERALS; n++) { + if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) { + return Z_TEXT; + } + } + + /* There are no "block-listed" or "allow-listed" bytes: + * this stream either is empty or has tolerated ("gray-listed") bytes only. + */ + return Z_BINARY; +}; + + +let static_init_done = false; + +/* =========================================================================== + * Initialize the tree data structures for a new zlib stream. + */ +const _tr_init = (s) => +{ + + if (!static_init_done) { + tr_static_init(); + static_init_done = true; + } + + s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc); + s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc); + s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc); + + s.bi_buf = 0; + s.bi_valid = 0; + + /* Initialize the first block of the first file: */ + init_block(s); +}; + + +/* =========================================================================== + * Send a stored block + */ +const _tr_stored_block = (s, buf, stored_len, last) => { +//DeflateState *s; +//charf *buf; /* input block */ +//ulg stored_len; /* length of input block */ +//int last; /* one if this is the last block for a file */ + + send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3); /* send block type */ + bi_windup(s); /* align on byte boundary */ + put_short(s, stored_len); + put_short(s, ~stored_len); + if (stored_len) { + s.pending_buf.set(s.window.subarray(buf, buf + stored_len), s.pending); + } + s.pending += stored_len; +}; + + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + */ +const _tr_align = (s) => { + send_bits(s, STATIC_TREES << 1, 3); + send_code(s, END_BLOCK, static_ltree); + bi_flush(s); +}; + + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and write out the encoded block. + */ +const _tr_flush_block = (s, buf, stored_len, last) => { +//DeflateState *s; +//charf *buf; /* input block, or NULL if too old */ +//ulg stored_len; /* length of input block */ +//int last; /* one if this is the last block for a file */ + + let opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + let max_blindex = 0; /* index of last bit length code of non zero freq */ + + /* Build the Huffman trees unless a stored block is forced */ + if (s.level > 0) { + + /* Check if the file is binary or text */ + if (s.strm.data_type === Z_UNKNOWN) { + s.strm.data_type = detect_data_type(s); + } + + /* Construct the literal and distance trees */ + build_tree(s, s.l_desc); + // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + // s->static_len)); + + build_tree(s, s.d_desc); + // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + // s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); + + /* Determine the best encoding. Compute the block lengths in bytes. */ + opt_lenb = (s.opt_len + 3 + 7) >>> 3; + static_lenb = (s.static_len + 3 + 7) >>> 3; + + // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + // s->sym_next / 3)); + + if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; } + + } else { + // Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + } + + if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) { + /* 4: two words for the lengths */ + + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + _tr_stored_block(s, buf, stored_len, last); + + } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) { + + send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3); + compress_block(s, static_ltree, static_dtree); + + } else { + send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3); + send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1); + compress_block(s, s.dyn_ltree, s.dyn_dtree); + } + // Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ + init_block(s); + + if (last) { + bi_windup(s); + } + // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + // s->compressed_len-7*last)); +}; + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +const _tr_tally = (s, dist, lc) => { +// deflate_state *s; +// unsigned dist; /* distance of matched string */ +// unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ + + s.pending_buf[s.sym_buf + s.sym_next++] = dist; + s.pending_buf[s.sym_buf + s.sym_next++] = dist >> 8; + s.pending_buf[s.sym_buf + s.sym_next++] = lc; + if (dist === 0) { + /* lc is the unmatched char */ + s.dyn_ltree[lc * 2]/*.Freq*/++; + } else { + s.matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + //Assert((ush)dist < (ush)MAX_DIST(s) && + // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++; + s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++; + } + + return (s.sym_next === s.sym_end); +}; + +module.exports._tr_init = _tr_init; +module.exports._tr_stored_block = _tr_stored_block; +module.exports._tr_flush_block = _tr_flush_block; +module.exports._tr_tally = _tr_tally; +module.exports._tr_align = _tr_align; diff --git a/blue_modules/pako/lib/zlib/zstream.js b/blue_modules/pako/lib/zlib/zstream.js new file mode 100644 index 00000000000..122acfef0f7 --- /dev/null +++ b/blue_modules/pako/lib/zlib/zstream.js @@ -0,0 +1,47 @@ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +function ZStream() { + /* next input byte */ + this.input = null; // JS specific, because we have no pointers + this.next_in = 0; + /* number of bytes available at input */ + this.avail_in = 0; + /* total number of input bytes read so far */ + this.total_in = 0; + /* next output byte should be put there */ + this.output = null; // JS specific, because we have no pointers + this.next_out = 0; + /* remaining free space at output */ + this.avail_out = 0; + /* total number of bytes output so far */ + this.total_out = 0; + /* last error message, NULL if no error */ + this.msg = ''/*Z_NULL*/; + /* not visible by applications */ + this.state = null; + /* best guess about the data type: binary or text */ + this.data_type = 2/*Z_UNKNOWN*/; + /* adler32 value of the uncompressed data */ + this.adler = 0; +} + +module.exports = ZStream; diff --git a/blue_modules/pako/package.json b/blue_modules/pako/package.json new file mode 100644 index 00000000000..1673520bc3d --- /dev/null +++ b/blue_modules/pako/package.json @@ -0,0 +1,41 @@ +{ + "name": "pako", + "description": "zlib port to javascript - fast, modularized, with browser support", + "version": "2.1.0", + "keywords": [ + "zlib", + "deflate", + "inflate", + "gzip" + ], + "contributors": [ + "Andrei Tuputcyn (https://github.com/andr83)", + "Vitaly Puzrin (https://github.com/puzrin)", + "Friedel Ziegelmayer (https://github.com/dignifiedquire)", + "Kirill Efimov (https://github.com/Kirill89)", + "Jean-loup Gailly", + "Mark Adler" + ], + "files": [ + "index.js", + "dist/", + "lib/" + ], + "license": "(MIT AND Zlib)", + "repository": "nodeca/pako", + "module": "./dist/pako.esm.mjs", + "exports": { + ".": { + "import": "./dist/pako.esm.mjs", + "require": "./index.js" + }, + "./package.json": "./package.json", + "./dist/*": "./dist/*", + "./lib/*": "./lib/*", + "./lib/zlib/*": "./lib/zlib/*", + "./lib/utils/*": "./lib/utils/*" + }, + "scripts": {}, + "devDependencies": {}, + "dependencies": {} +} diff --git a/blue_modules/react-native-bw-file-access/.gitignore b/blue_modules/react-native-bw-file-access/.gitignore new file mode 100644 index 00000000000..a1b76a8a6fa --- /dev/null +++ b/blue_modules/react-native-bw-file-access/.gitignore @@ -0,0 +1,42 @@ +# OSX +# +.DS_Store +**/package-lock.json +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml + +# BUCK +buck-out/ +\.buckd/ +*.keystore diff --git a/blue_modules/react-native-bw-file-access/README.md b/blue_modules/react-native-bw-file-access/README.md new file mode 100644 index 00000000000..3e86cf135c7 --- /dev/null +++ b/blue_modules/react-native-bw-file-access/README.md @@ -0,0 +1,6 @@ +# react-native-bw-file-access + +A custom package written to allow BlueWallet to open files directly from the Files app in iOS. We make use of `startAccessingSecurityScopedResource()` and `stopAccessingSecurityScopedResource()`. + +Read Apple's documentation to understand more about the Open-in-Place mechanics for accessing files which are not in an apps sandbox environment. +[Link here](https://developer.apple.com/documentation/uikit/documents_data_and_pasteboard/synchronizing_documents_in_the_icloud_environment#3743499). diff --git a/blue_modules/react-native-bw-file-access/index.ts b/blue_modules/react-native-bw-file-access/index.ts new file mode 100644 index 00000000000..6cd5303826a --- /dev/null +++ b/blue_modules/react-native-bw-file-access/index.ts @@ -0,0 +1,11 @@ +// main index.js + +import { NativeModules } from 'react-native'; + +const { BwFileAccess } = NativeModules; + +export function readFile(filePath: string): Promise { + return BwFileAccess.readFileContent(filePath); +} + +export default BwFileAccess; diff --git a/blue_modules/react-native-bw-file-access/ios/BwFileAccess.h b/blue_modules/react-native-bw-file-access/ios/BwFileAccess.h new file mode 100644 index 00000000000..0ecd3f53c1e --- /dev/null +++ b/blue_modules/react-native-bw-file-access/ios/BwFileAccess.h @@ -0,0 +1,7 @@ +// BwFileAccess.h + +#import + +@interface BwFileAccess : NSObject + +@end diff --git a/blue_modules/react-native-bw-file-access/ios/BwFileAccess.m b/blue_modules/react-native-bw-file-access/ios/BwFileAccess.m new file mode 100644 index 00000000000..8081536ebdb --- /dev/null +++ b/blue_modules/react-native-bw-file-access/ios/BwFileAccess.m @@ -0,0 +1,33 @@ +// BwFileAccess.m + +#import "BwFileAccess.h" + + +@implementation BwFileAccess + +RCT_EXPORT_MODULE() + +RCT_EXPORT_METHOD(readFileContent:(NSString *)filePath + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + NSURL *fileURL = [NSURL URLWithString:filePath]; + + if ([fileURL startAccessingSecurityScopedResource]) { + NSError *error; + NSData *fileData = [NSData dataWithContentsOfURL:fileURL options:0 error:&error]; + + if (fileData) { + NSString *fileContent = [[NSString alloc] initWithData:fileData encoding:NSUTF8StringEncoding]; + resolve(fileContent); + } else { + reject(@"READ_ERROR", @"Failed to read file", error); + } + + [fileURL stopAccessingSecurityScopedResource]; + } else { + reject(@"ACCESS_ERROR", @"Failed to access security scoped resource", nil); + } +} + +@end diff --git a/blue_modules/react-native-bw-file-access/ios/BwFileAccess.xcodeproj/project.pbxproj b/blue_modules/react-native-bw-file-access/ios/BwFileAccess.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..f1edc9aff36 --- /dev/null +++ b/blue_modules/react-native-bw-file-access/ios/BwFileAccess.xcodeproj/project.pbxproj @@ -0,0 +1,281 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXCopyFilesBuildPhase section */ + 58B511D91A9E6C8500147676 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 134814201AA4EA6300B7C361 /* libBwFileAccess.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBwFileAccess.a; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 58B511D81A9E6C8500147676 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 134814211AA4EA7D00B7C361 /* Products */ = { + isa = PBXGroup; + children = ( + 134814201AA4EA6300B7C361 /* libBwFileAccess.a */, + ); + name = Products; + sourceTree = ""; + }; + 58B511D21A9E6C8500147676 = { + isa = PBXGroup; + children = ( + 134814211AA4EA7D00B7C361 /* Products */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 58B511DA1A9E6C8500147676 /* BwFileAccess */ = { + isa = PBXNativeTarget; + buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "BwFileAccess" */; + buildPhases = ( + 58B511D71A9E6C8500147676 /* Sources */, + 58B511D81A9E6C8500147676 /* Frameworks */, + 58B511D91A9E6C8500147676 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BwFileAccess; + productName = RCTDataManager; + productReference = 134814201AA4EA6300B7C361 /* libBwFileAccess.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 58B511D31A9E6C8500147676 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 58B511DA1A9E6C8500147676 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "BwFileAccess" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 58B511D21A9E6C8500147676; + productRefGroup = 58B511D21A9E6C8500147676; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 58B511DA1A9E6C8500147676 /* BwFileAccess */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 58B511D71A9E6C8500147676 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 58B511ED1A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = ( + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 58B511EE1A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = ( + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 58B511F01A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../../React/**", + "$(SRCROOT)/../../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = BwFileAccess; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 58B511F11A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../../React/**", + "$(SRCROOT)/../../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = BwFileAccess; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "BwFileAccess" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511ED1A9E6C8500147676 /* Debug */, + 58B511EE1A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "BwFileAccess" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511F01A9E6C8500147676 /* Debug */, + 58B511F11A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 58B511D31A9E6C8500147676 /* Project object */; +} diff --git a/blue_modules/react-native-bw-file-access/ios/BwFileAccess.xcworkspace/contents.xcworkspacedata b/blue_modules/react-native-bw-file-access/ios/BwFileAccess.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..a232186952a --- /dev/null +++ b/blue_modules/react-native-bw-file-access/ios/BwFileAccess.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/blue_modules/react-native-bw-file-access/package.json b/blue_modules/react-native-bw-file-access/package.json new file mode 100644 index 00000000000..965bd35bef6 --- /dev/null +++ b/blue_modules/react-native-bw-file-access/package.json @@ -0,0 +1,36 @@ +{ + "name": "react-native-bw-file-access", + "title": "React Native Bw File Access", + "version": "1.0.0", + "description": "TODO", + "main": "index.ts", + "homepage": "https://github.com/setavenger/react-native-bw-file-access", + "files": [ + "README.md", + "android", + "index.ts", + "ios", + "react-native-bw-file-access.podspec" + ], + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/setavenger/react-native-bw-file-access.git", + "baseUrl": "https://github.com/setavenger/react-native-bw-file-access" + }, + "keywords": [ + "react-native" + ], + "author": { + "name": "Setor Blagogee" + }, + "license": "MIT", + "licenseFilename": "LICENSE", + "readmeFilename": "README.md", + "peerDependencies": { + "react": ">=16.8.1", + "react-native": ">=0.60.0-rc.0 <1.0.x" + } +} diff --git a/blue_modules/react-native-bw-file-access/react-native-bw-file-access.podspec b/blue_modules/react-native-bw-file-access/react-native-bw-file-access.podspec new file mode 100644 index 00000000000..84ffdcbc0df --- /dev/null +++ b/blue_modules/react-native-bw-file-access/react-native-bw-file-access.podspec @@ -0,0 +1,19 @@ +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +Pod::Spec.new do |s| + s.name = "react-native-bw-file-access" + s.version = package["version"] + s.summary = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.authors = package["author"] + + s.platforms = { :ios => "10.0" } + s.source = { :git => "https://github.com/setavenger/react-native-bw-file-access.git", :tag => "#{s.version}" } + + s.source_files = "ios/**/*.{h,m,mm}" + + s.dependency "React-Core" +end \ No newline at end of file diff --git a/blue_modules/showPopupMenu.android.ts b/blue_modules/showPopupMenu.android.ts deleted file mode 100644 index 114615f1165..00000000000 --- a/blue_modules/showPopupMenu.android.ts +++ /dev/null @@ -1,32 +0,0 @@ -// @ts-ignore: Ignore -import type { Element } from 'react'; -import { Text, TouchableNativeFeedback, TouchableWithoutFeedback, View, findNodeHandle, UIManager } from 'react-native'; - -type PopupMenuItem = { id?: any; label: string }; -type OnPopupMenuItemSelect = (selectedPopupMenuItem: PopupMenuItem) => void; -type PopupAnchor = Element; -type PopupMenuOptions = { onCancel?: () => void }; - -function showPopupMenu( - items: PopupMenuItem[], - onSelect: OnPopupMenuItemSelect, - anchor: PopupAnchor, - { onCancel }: PopupMenuOptions = {}, -): void { - UIManager.showPopupMenu( - // @ts-ignore: Ignore - findNodeHandle(anchor), - items.map(item => item.label), - function () { - if (onCancel) onCancel(); - }, - function (eventName: 'dismissed' | 'itemSelected', selectedIndex?: number) { - // @ts-ignore: Ignore - if (eventName === 'itemSelected') onSelect(items[selectedIndex]); - else onCancel && onCancel(); - }, - ); -} - -export type { PopupMenuItem, OnPopupMenuItemSelect, PopupMenuOptions }; -export default showPopupMenu; diff --git a/blue_modules/sizeClass.ts b/blue_modules/sizeClass.ts new file mode 100644 index 00000000000..e5ca0a53ac5 --- /dev/null +++ b/blue_modules/sizeClass.ts @@ -0,0 +1,219 @@ +import { AppState, AppStateStatus, Dimensions, NativeEventEmitter, NativeModules, Platform } from 'react-native'; +import { useEffect, useState } from 'react'; +import { isDesktop } from './environment'; + +type NativeSizeClassPayload = { + horizontal?: number; + vertical?: number; + sizeClass?: number; + orientation?: string; + isLargeScreen?: boolean; +}; + +const sizeClassNativeModule = NativeModules.SizeClassEmitter as + | { + getCurrentSizeClass?: () => Promise; + addListener: (eventType: string) => any; + removeListeners: (count: number) => void; + } + | undefined; + +const sizeClassNativeEmitter = sizeClassNativeModule ? new NativeEventEmitter(sizeClassNativeModule) : null; +const NATIVE_EVENT_NAME = 'sizeClassDidChange'; + +// Size class definitions following iOS conventions +export enum SizeClass { + Compact, // Small size (iPhone width or height in landscape) + Regular, // Standard size (iPad, or iPhone height in portrait) + Large, // Additional size for larger screens (not in iOS, but useful for our app) +} + +// Interface for the result of getSizeClass +export interface SizeClassInfo { + // Size classes + horizontalSizeClass: SizeClass; + verticalSizeClass: SizeClass; + + // Overall size class (derived from horizontal and vertical) + sizeClass: SizeClass; + + // Orientation + orientation: 'portrait' | 'landscape'; + + // Helper properties + isCompact: boolean; + isLarge: boolean; + + // Legacy support + isLargeScreen: boolean; +} + +const normalizeOrientation = (orientation?: string): 'portrait' | 'landscape' => (orientation === 'landscape' ? 'landscape' : 'portrait'); + +const coerceSizeClassValue = (value?: number): SizeClass => { + if (value === SizeClass.Compact || value === SizeClass.Regular || value === SizeClass.Large) { + return value; + } + return SizeClass.Regular; +}; + +const calculateFromDimensions = (): SizeClassInfo => { + const { width, height } = Dimensions.get('window'); + const isLandscape = width > height; + const orientation = isLandscape ? 'landscape' : 'portrait'; + + const horizontalSizeClass = + Platform.OS === 'ios' && Platform.isPad + ? SizeClass.Regular + : isDesktop + ? SizeClass.Large + : isLandscape && width >= 667 + ? SizeClass.Regular + : SizeClass.Compact; + + const verticalSizeClass = + Platform.OS === 'ios' && Platform.isPad + ? SizeClass.Regular + : isDesktop + ? SizeClass.Large + : isLandscape + ? SizeClass.Compact + : SizeClass.Regular; + + const sizeClass = coerceSizeClassValue(horizontalSizeClass); + const isLargeScreen = sizeClass === SizeClass.Large; + + return { + horizontalSizeClass, + verticalSizeClass, + sizeClass, + orientation, + isCompact: sizeClass === SizeClass.Compact, + isLarge: sizeClass === SizeClass.Large, + isLargeScreen, + }; +}; + +const normalizeNativePayload = (payload?: NativeSizeClassPayload | null): SizeClassInfo | null => { + if (!payload) { + return null; + } + + const horizontalSizeClass = coerceSizeClassValue(payload.horizontal); + const verticalSizeClass = coerceSizeClassValue(payload.vertical); + const sizeClass = coerceSizeClassValue(payload.sizeClass); + + const isLargeScreen = payload.isLargeScreen ?? sizeClass === SizeClass.Large; + const orientation = normalizeOrientation(payload.orientation); + + return { + horizontalSizeClass, + verticalSizeClass, + sizeClass, + orientation, + isCompact: sizeClass === SizeClass.Compact, + isLarge: sizeClass === SizeClass.Large, + isLargeScreen, + }; +}; + +let cachedSizeClassInfo: SizeClassInfo = calculateFromDimensions(); +let nativeInitRequested = false; + +const fetchNativeSizeClass = async (): Promise => { + if (!sizeClassNativeModule?.getCurrentSizeClass) { + return null; + } + + try { + const result = await sizeClassNativeModule.getCurrentSizeClass(); + return normalizeNativePayload(result); + } catch (error) { + console.debug('[SizeClass] Failed to read native size class', error); + return null; + } +}; + +/** + * Get current size class information. + */ +export function getSizeClass(): SizeClassInfo { + if (!sizeClassNativeModule) { + cachedSizeClassInfo = calculateFromDimensions(); + } else if (!nativeInitRequested) { + nativeInitRequested = true; + fetchNativeSizeClass().then(nativeInfo => { + if (nativeInfo) { + cachedSizeClassInfo = nativeInfo; + } + }); + } + + return cachedSizeClassInfo; +} + +/** + * React hook to use size classes in components + */ +export function useSizeClass(): SizeClassInfo { + const [sizeClassInfo, setSizeClassInfo] = useState(cachedSizeClassInfo); + + useEffect(() => { + let isMounted = true; + + const applySizeClass = (info: SizeClassInfo) => { + if (!isMounted) return; + cachedSizeClassInfo = info; + setSizeClassInfo(info); + console.debug( + `[SizeClass] Updated:`, + `horizontal=${SizeClass[info.horizontalSizeClass]}`, + `vertical=${SizeClass[info.verticalSizeClass]}`, + `orientation=${info.orientation}`, + `isLargeScreen=${info.isLargeScreen}`, + ); + }; + + const updateFromDimensions = () => { + const calculated = calculateFromDimensions(); + applySizeClass(calculated); + }; + + const requestNativeUpdate = async () => { + const nativeInfo = await fetchNativeSizeClass(); + if (nativeInfo) { + applySizeClass(nativeInfo); + } + }; + + const dimensionSubscription = Dimensions.addEventListener('change', () => { + updateFromDimensions(); + requestNativeUpdate(); + }); + + const appStateSubscription = AppState.addEventListener('change', (nextAppState: AppStateStatus) => { + if (nextAppState === 'active') { + requestNativeUpdate(); + } + }); + + const nativeSubscription = sizeClassNativeEmitter?.addListener(NATIVE_EVENT_NAME, (payload: NativeSizeClassPayload) => { + const normalized = normalizeNativePayload(payload); + if (normalized) { + applySizeClass(normalized); + } + }); + + // Kick off an initial native fetch to override the heuristic when available. + requestNativeUpdate(); + + return () => { + isMounted = false; + dimensionSubscription.remove(); + appStateSubscription.remove(); + nativeSubscription?.remove(); + }; + }, []); + + return sizeClassInfo; +} diff --git a/blue_modules/start-and-decrypt.ts b/blue_modules/start-and-decrypt.ts new file mode 100644 index 00000000000..d3771679402 --- /dev/null +++ b/blue_modules/start-and-decrypt.ts @@ -0,0 +1,75 @@ +import { Platform } from 'react-native'; + +import { BlueApp as BlueAppClass } from '../class/blue-app'; +import prompt from '../helpers/prompt'; +import { showKeychainWipeAlert } from '../hooks/useBiometrics'; +import loc from '../loc'; + +const BlueApp = BlueAppClass.getInstance(); +// If attempt reaches 10, a wipe keychain option will be provided to the user. +let unlockAttempt = 0; + +type PasswordPromptCallback = () => Promise; + +export const startAndDecrypt = async (retry?: boolean, passwordPrompt?: PasswordPromptCallback): Promise => { + // If wallets are already loaded, no need to migrate, decrypt, or load from disk. + if (BlueApp.getWallets().length > 0) { + return true; + } + await BlueApp.migrateKeys(); + let password: undefined | string; + if (await BlueApp.storageIsEncrypted()) { + if (passwordPrompt) { + password = await passwordPrompt(); + } else { + do { + password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, { cancelable: false }); + } while (!password); + } + } + let success = false; + let wasException = false; + try { + success = await BlueApp.loadFromDisk(password); + } catch (error) { + // in case of exception reading from keystore, lets retry instead of assuming there is no storage and + // proceeding with no wallets + console.warn('exception loading from disk:', error); + wasException = true; + } + + if (wasException) { + // retrying, but only once + try { + await new Promise(resolve => setTimeout(resolve, 3000)); // sleep + success = await BlueApp.loadFromDisk(password); + } catch (error) { + console.warn('second exception loading from disk:', error); + } + } + + if (success) { + console.log('loaded from disk'); + return true; + } + + if (password) { + // we had password and yet could not load/decrypt + unlockAttempt++; + if (unlockAttempt < 10 || Platform.OS !== 'ios') { + // Return false to indicate wrong password, let UI show error and retry + return false; + } else { + unlockAttempt = 0; + showKeychainWipeAlert(); + // We want to return false to let the UnlockWith screen that it is NOT ok to proceed. + return false; + } + } else { + unlockAttempt = 0; + // Return true because there was no wallet data in keychain. Proceed. + return true; + } +}; + +export default BlueApp; diff --git a/blue_modules/storage-context.js b/blue_modules/storage-context.js deleted file mode 100644 index 01622923af2..00000000000 --- a/blue_modules/storage-context.js +++ /dev/null @@ -1,289 +0,0 @@ -import React, { createContext, useEffect, useState } from 'react'; -import { Alert } from 'react-native'; -import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; -import { useAsyncStorage } from '@react-native-async-storage/async-storage'; -import { FiatUnit } from '../models/fiatUnit'; -import Notifications from '../blue_modules/notifications'; -import loc, { STORAGE_KEY as LOC_STORAGE_KEY } from '../loc'; -import { LegacyWallet, WatchOnlyWallet } from '../class'; -import { isTorDaemonDisabled, setIsTorDaemonDisabled } from './environment'; -import alert from '../components/Alert'; -const BlueApp = require('../BlueApp'); -const BlueElectrum = require('./BlueElectrum'); -const currency = require('../blue_modules/currency'); -const A = require('../blue_modules/analytics'); - -const _lastTimeTriedToRefetchWallet = {}; // hashmap of timestamps we _started_ refetching some wallet - -export const WalletTransactionsStatus = { NONE: false, ALL: true }; -export const BlueStorageContext = createContext(); -export const BlueStorageProvider = ({ children }) => { - const [wallets, setWallets] = useState([]); - const [selectedWallet, setSelectedWallet] = useState(''); - const [walletTransactionUpdateStatus, setWalletTransactionUpdateStatus] = useState(WalletTransactionsStatus.NONE); - const [walletsInitialized, setWalletsInitialized] = useState(false); - const [preferredFiatCurrency, _setPreferredFiatCurrency] = useState(FiatUnit.USD); - const [language, _setLanguage] = useState(); - const getPreferredCurrencyAsyncStorage = useAsyncStorage(currency.PREFERRED_CURRENCY).getItem; - const getLanguageAsyncStorage = useAsyncStorage(LOC_STORAGE_KEY).getItem; - const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false); - const [isElectrumDisabled, setIsElectrumDisabled] = useState(true); - const [isTorDisabled, setIsTorDisabled] = useState(false); - const [isPrivacyBlurEnabled, setIsPrivacyBlurEnabled] = useState(true); - - useEffect(() => { - BlueElectrum.isDisabled().then(setIsElectrumDisabled); - isTorDaemonDisabled().then(setIsTorDisabled); - }, []); - - useEffect(() => { - console.log(`Privacy blur: ${isPrivacyBlurEnabled}`); - if (!isPrivacyBlurEnabled) { - alert('Privacy blur has been disabled.'); - } - }, [isPrivacyBlurEnabled]); - - useEffect(() => { - setIsTorDaemonDisabled(isTorDisabled); - }, [isTorDisabled]); - - const setIsHandOffUseEnabledAsyncStorage = value => { - setIsHandOffUseEnabled(value); - return BlueApp.setIsHandoffEnabled(value); - }; - - const saveToDisk = async (force = false) => { - if (BlueApp.getWallets().length === 0 && !force) { - console.log('not saving empty wallets array'); - return; - } - BlueApp.tx_metadata = txMetadata; - await BlueApp.saveToDisk(); - setWallets([...BlueApp.getWallets()]); - txMetadata = BlueApp.tx_metadata; - }; - - useEffect(() => { - setWallets(BlueApp.getWallets()); - }, []); - - useEffect(() => { - (async () => { - try { - const enabledHandoff = await BlueApp.isHandoffEnabled(); - setIsHandOffUseEnabled(!!enabledHandoff); - } catch (_e) { - setIsHandOffUseEnabledAsyncStorage(false); - setIsHandOffUseEnabled(false); - } - })(); - }, []); - - const getPreferredCurrency = async () => { - const item = await getPreferredCurrencyAsyncStorage(); - _setPreferredFiatCurrency(item); - }; - - const setPreferredFiatCurrency = () => { - getPreferredCurrency(); - }; - - const getLanguage = async () => { - const item = await getLanguageAsyncStorage(); - _setLanguage(item); - }; - - const setLanguage = () => { - getLanguage(); - }; - - useEffect(() => { - getPreferredCurrency(); - getLanguageAsyncStorage(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const resetWallets = () => { - setWallets(BlueApp.getWallets()); - }; - - const setWalletsWithNewOrder = wlts => { - BlueApp.wallets = wlts; - saveToDisk(); - }; - - const refreshAllWalletTransactions = async (lastSnappedTo, showUpdateStatusIndicator = true) => { - let noErr = true; - try { - if (showUpdateStatusIndicator) { - setWalletTransactionUpdateStatus(WalletTransactionsStatus.ALL); - } - await BlueElectrum.waitTillConnected(); - const paymentCodesStart = Date.now(); - await fetchSenderPaymentCodes(lastSnappedTo); - const paymentCodesEnd = Date.now(); - console.log('fetch payment codes took', (paymentCodesEnd - paymentCodesStart) / 1000, 'sec'); - const balanceStart = +new Date(); - await fetchWalletBalances(lastSnappedTo); - const balanceEnd = +new Date(); - console.log('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec'); - const start = +new Date(); - await fetchWalletTransactions(lastSnappedTo); - const end = +new Date(); - console.log('fetch tx took', (end - start) / 1000, 'sec'); - } catch (err) { - noErr = false; - console.warn(err); - } finally { - setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE); - } - if (noErr) await saveToDisk(); // caching - }; - - const fetchAndSaveWalletTransactions = async walletID => { - const index = wallets.findIndex(wallet => wallet.getID() === walletID); - let noErr = true; - try { - // 5sec debounce: - setWalletTransactionUpdateStatus(walletID); - if (+new Date() - _lastTimeTriedToRefetchWallet[walletID] < 5000) { - console.log('re-fetch wallet happens too fast; NOP'); - return; - } - _lastTimeTriedToRefetchWallet[walletID] = +new Date(); - - await BlueElectrum.waitTillConnected(); - const balanceStart = +new Date(); - await fetchWalletBalances(index); - const balanceEnd = +new Date(); - console.log('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec'); - const start = +new Date(); - await fetchWalletTransactions(index); - const end = +new Date(); - console.log('fetch tx took', (end - start) / 1000, 'sec'); - } catch (err) { - noErr = false; - console.warn(err); - } finally { - setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE); - } - if (noErr) await saveToDisk(); // caching - }; - - const addWallet = wallet => { - BlueApp.wallets.push(wallet); - setWallets([...BlueApp.getWallets()]); - }; - - const deleteWallet = wallet => { - BlueApp.deleteWallet(wallet); - setWallets([...BlueApp.getWallets()]); - }; - - const addAndSaveWallet = async w => { - if (wallets.some(i => i.getID() === w.getID())) { - ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); - Alert.alert('', 'This wallet has been previously imported.'); - return; - } - const emptyWalletLabel = new LegacyWallet().getLabel(); - ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false }); - if (w.getLabel() === emptyWalletLabel) w.setLabel(loc.wallets.import_imported + ' ' + w.typeReadable); - w.setUserHasSavedExport(true); - addWallet(w); - await saveToDisk(); - A(A.ENUM.CREATED_WALLET); - Alert.alert('', w.type === WatchOnlyWallet.type ? loc.wallets.import_success_watchonly : loc.wallets.import_success); - Notifications.majorTomToGroundControl(w.getAllExternalAddresses(), [], []); - // start balance fetching at the background - await w.fetchBalance(); - setWallets([...BlueApp.getWallets()]); - }; - - let txMetadata = BlueApp.tx_metadata || {}; - const getTransactions = BlueApp.getTransactions; - const isAdvancedModeEnabled = BlueApp.isAdvancedModeEnabled; - - const fetchSenderPaymentCodes = BlueApp.fetchSenderPaymentCodes; - const fetchWalletBalances = BlueApp.fetchWalletBalances; - const fetchWalletTransactions = BlueApp.fetchWalletTransactions; - const getBalance = BlueApp.getBalance; - const isStorageEncrypted = BlueApp.storageIsEncrypted; - const startAndDecrypt = BlueApp.startAndDecrypt; - const encryptStorage = BlueApp.encryptStorage; - const sleep = BlueApp.sleep; - const setHodlHodlApiKey = BlueApp.setHodlHodlApiKey; - const getHodlHodlApiKey = BlueApp.getHodlHodlApiKey; - const createFakeStorage = BlueApp.createFakeStorage; - const decryptStorage = BlueApp.decryptStorage; - const isPasswordInUse = BlueApp.isPasswordInUse; - const cachedPassword = BlueApp.cachedPassword; - const setIsAdvancedModeEnabled = BlueApp.setIsAdvancedModeEnabled; - const getHodlHodlSignatureKey = BlueApp.getHodlHodlSignatureKey; - const addHodlHodlContract = BlueApp.addHodlHodlContract; - const getHodlHodlContracts = BlueApp.getHodlHodlContracts; - const setDoNotTrack = BlueApp.setDoNotTrack; - const isDoNotTrackEnabled = BlueApp.isDoNotTrackEnabled; - const getItem = BlueApp.getItem; - const setItem = BlueApp.setItem; - - return ( - - {children} - - ); -}; diff --git a/blue_modules/tls.js b/blue_modules/tls.js deleted file mode 100644 index 1684e71c4db..00000000000 --- a/blue_modules/tls.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @fileOverview adapter for ReactNative TCP module - * This module mimics the nodejs tls api and is intended to work in RN environment. - * @see https://github.com/Rapsssito/react-native-tcp-socket - */ - -import TcpSocket from 'react-native-tcp-socket'; - -/** - * Constructor function. Mimicking nodejs/tls api - * - * @constructor - */ -function connect(config, callback) { - const client = TcpSocket.createConnection( - { - port: config.port, - host: config.host, - tls: true, - tlsCheckValidity: config.rejectUnauthorized, - }, - callback, - ); - - // defaults: - this._noDelay = true; - - // functions not supported by RN module, yet: - client.setTimeout = () => {}; - client.setEncoding = () => {}; - client.setKeepAlive = () => {}; - - // we will save `noDelay` and proxy it to socket object when its actually created and connected: - const realSetNoDelay = client.setNoDelay; // reference to real setter - client.setNoDelay = noDelay => { - this._noDelay = noDelay; - }; - - client.on('connect', () => { - realSetNoDelay.apply(client, [this._noDelay]); - }); - - return client; -} - -module.exports.connect = connect; diff --git a/blue_modules/torrific.js b/blue_modules/torrific.js deleted file mode 100644 index bde1b4cf8c6..00000000000 --- a/blue_modules/torrific.js +++ /dev/null @@ -1,290 +0,0 @@ -import Tor from 'react-native-tor'; -const tor = Tor({ - bootstrapTimeoutMs: 35000, - numberConcurrentRequests: 1, -}); - -/** - * TOR wrapper mimicking Frisbee interface - */ -class Torsbee { - baseURI = ''; - - static _testConn; - static _resolveReference; - static _rejectReference; - - constructor(opts) { - opts = opts || {}; - this.baseURI = opts.baseURI || this.baseURI; - } - - async get(path, options) { - console.log('TOR: starting...'); - const socksProxy = await tor.startIfNotStarted(); - console.log('TOR: started', await tor.getDaemonStatus(), 'on local port', socksProxy); - if (path.startsWith('/') && this.baseURI.endsWith('/')) { - // oy vey, duplicate slashes - path = path.substr(1); - } - - const response = {}; - try { - const uri = this.baseURI + path; - console.log('TOR: requesting', uri); - const torResponse = await tor.get(uri, options?.headers || {}, true); - response.originalResponse = torResponse; - - if (options?.headers['Content-Type'] === 'application/json' && torResponse.json) { - response.body = torResponse.json; - } else { - response.body = Buffer.from(torResponse.b64Data, 'base64').toString(); - } - } catch (error) { - response.err = error; - console.warn(error); - } - - return response; - } - - async post(path, options) { - console.log('TOR: starting...'); - const socksProxy = await tor.startIfNotStarted(); - console.log('TOR: started', await tor.getDaemonStatus(), 'on local port', socksProxy); - if (path.startsWith('/') && this.baseURI.endsWith('/')) { - // oy vey, duplicate slashes - path = path.substr(1); - } - - const uri = this.baseURI + path; - console.log('TOR: posting to', uri); - - const response = {}; - try { - const torResponse = await tor.post(uri, JSON.stringify(options?.body || {}), options?.headers || {}, true); - response.originalResponse = torResponse; - - if (options?.headers['Content-Type'] === 'application/json' && torResponse.json) { - response.body = torResponse.json; - } else { - response.body = Buffer.from(torResponse.b64Data, 'base64').toString(); - } - } catch (error) { - response.err = error; - console.warn(error); - } - - return response; - } - - testSocket() { - return new Promise((resolve, reject) => { - this.constructor._resolveReference = resolve; - this.constructor._rejectReference = reject; - (async () => { - console.log('testSocket...'); - try { - if (!this.constructor._testConn) { - // no test conenctino exists, creating it... - await tor.startIfNotStarted(); - const target = 'explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion:110'; - this.constructor._testConn = await tor.createTcpConnection({ target }, (data, err) => { - if (err) { - return this.constructor._rejectReference(new Error(err)); - } - const json = JSON.parse(data); - if (!json || typeof json.result === 'undefined') - return this.constructor._rejectReference(new Error('Unexpected response from TOR socket: ' + JSON.stringify(json))); - - // conn.close(); - // instead of closing connect, we will actualy re-cyce existing test connection as we - // saved it into `this.constructor.testConn` - this.constructor._resolveReference(); - }); - - await this.constructor._testConn.write( - `{ "id": 1, "method": "blockchain.scripthash.get_balance", "params": ["716decbe1660861c3d93906cb1d98ee68b154fd4d23aed9783859c1271b52a9c"] }\n`, - ); - } else { - // test connectino exists, so we are reusing it - await this.constructor._testConn.write( - `{ "id": 1, "method": "blockchain.scripthash.get_balance", "params": ["716decbe1660861c3d93906cb1d98ee68b154fd4d23aed9783859c1271b52a9c"] }\n`, - ); - } - } catch (error) { - this.constructor._rejectReference(error); - } - })(); - }); - } -} - -/** - * Wrapper for react-native-tor mimicking Socket class from NET package - */ -class TorSocket { - constructor() { - this._socket = false; - this._listeners = {}; - } - - setTimeout() {} - - setEncoding() {} - - setKeepAlive() {} - - setNoDelay() {} - - on(event, listener) { - this._listeners[event] = this._listeners[event] || []; - this._listeners[event].push(listener); - } - - removeListener(event, listener) { - this._listeners[event] = this._listeners[event] || []; - const newListeners = []; - - let found = false; - for (const savedListener of this._listeners[event]) { - // eslint-disable-next-line eqeqeq - if (savedListener == listener) { - // found our listener - found = true; - // we just skip it - } else { - // other listeners should go back to original array - newListeners.push(savedListener); - } - } - - if (found) { - this._listeners[event] = newListeners; - } else { - // something went wrong, lets just cleanup all listeners - this._listeners[event] = []; - } - } - - connect(port, host, callback) { - console.log('connecting TOR socket...', host, port); - (async () => { - console.log('starting tor...'); - try { - await tor.startIfNotStarted(); - } catch (e) { - console.warn('Could not bootstrap TOR', e); - await tor.stopIfRunning(); - this._passOnEvent('error', 'Could not bootstrap TOR'); - return false; - } - console.log('started tor'); - const iWillConnectISwear = tor.createTcpConnection({ target: host + ':' + port, connectionTimeout: 15000 }, (data, err) => { - if (err) { - console.log('TOR socket onData error: ', err); - // this._passOnEvent('error', err); - return; - } - this._passOnEvent('data', data); - }); - - try { - this._socket = await Promise.race([iWillConnectISwear, new Promise(resolve => setTimeout(resolve, 21000))]); - } catch (e) {} - - if (!this._socket) { - console.log('connecting TOR socket failed'); // either sleep expired or connect threw an exception - await tor.stopIfRunning(); - this._passOnEvent('error', 'connecting TOR socket failed'); - return false; - } - - console.log('TOR socket connected:', host, port); - setTimeout(() => { - this._passOnEvent('connect', true); - callback(); - }, 1000); - })(); - } - - _passOnEvent(event, data) { - this._listeners[event] = this._listeners[event] || []; - for (const savedListener of this._listeners[event]) { - savedListener(data); - } - } - - emit(event, data) {} - - end() { - console.log('trying to close TOR socket'); - if (this._socket && this._socket.close) { - console.log('trying to close TOR socket SUCCESS'); - return this._socket.close(); - } - } - - destroy() {} - - write(data) { - if (this._socket && this._socket.write) { - try { - return this._socket.write(data); - } catch (error) { - console.log('this._socket.write() failed so we are issuing ERROR event', error); - this._passOnEvent('error', error); - } - } else { - console.log('TOR socket write error, socket not connected'); - this._passOnEvent('error', 'TOR socket not connected'); - } - } -} - -module.exports.getDaemonStatus = async () => { - try { - return await tor.getDaemonStatus(); - } catch (_) { - return false; - } -}; - -module.exports.stopIfRunning = async () => { - try { - Torsbee._testConn = false; - return await tor.stopIfRunning(); - } catch (_) { - return false; - } -}; - -module.exports.startIfNotStarted = async () => { - try { - return await tor.startIfNotStarted(); - } catch (_) { - return false; - } -}; - -module.exports.testSocket = async () => { - const c = new Torsbee(); - return c.testSocket(); -}; - -module.exports.testHttp = async () => { - const api = new Torsbee({ - baseURI: 'http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion:80/', - }); - const torResponse = await api.get('/api/tx/a84dbcf0d2550f673dda9331eea7cab86b645fd6e12049755c4b47bd238adce9', { - headers: { - 'Content-Type': 'application/json', - }, - }); - const json = torResponse.body; - if (json.txid !== 'a84dbcf0d2550f673dda9331eea7cab86b645fd6e12049755c4b47bd238adce9') - throw new Error('TOR failure, got ' + JSON.stringify(torResponse)); -}; - -module.exports.Torsbee = Torsbee; -module.exports.Socket = TorSocket; diff --git a/blue_modules/uint8array-extras/index.d.ts b/blue_modules/uint8array-extras/index.d.ts new file mode 100644 index 00000000000..353ea95ec57 --- /dev/null +++ b/blue_modules/uint8array-extras/index.d.ts @@ -0,0 +1,311 @@ +export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array; + +/** +Check if the given value is an instance of `Uint8Array`. + +Replacement for [`Buffer.isBuffer()`](https://nodejs.org/api/buffer.html#static-method-bufferisbufferobj). + +@example +``` +import {isUint8Array} from 'uint8array-extras'; + +console.log(isUint8Array(new Uint8Array())); +//=> true + +console.log(isUint8Array(Buffer.from('x'))); +//=> true + +console.log(isUint8Array(new ArrayBuffer(10))); +//=> false +``` +*/ +export function isUint8Array(value: unknown): value is Uint8Array; + +/** +Throw a `TypeError` if the given value is not an instance of `Uint8Array`. + +@example +``` +import {assertUint8Array} from 'uint8array-extras'; + +try { + assertUint8Array(new ArrayBuffer(10)); // Throws a TypeError +} catch (error) { + console.error(error.message); +} +``` +*/ +export function assertUint8Array(value: unknown): asserts value is Uint8Array; + +/** +Convert a value to a `Uint8Array` without copying its data. + +This can be useful for converting a `Buffer` to a pure `Uint8Array`. `Buffer` is already an `Uint8Array` subclass, but [`Buffer` alters some behavior](https://sindresorhus.com/blog/goodbye-nodejs-buffer), so it can be useful to cast it to a pure `Uint8Array` before returning it. + +Tip: If you want a copy, just call `.slice()` on the return value. +*/ +export function toUint8Array(value: TypedArray | ArrayBuffer | DataView): Uint8Array; + +/** +Concatenate the given arrays into a new array. + +If `arrays` is empty, it will return a zero-sized `Uint8Array`. + +If `totalLength` is not specified, it is calculated from summing the lengths of the given arrays. + +Replacement for [`Buffer.concat()`](https://nodejs.org/api/buffer.html#static-method-bufferconcatlist-totallength). + +@example +``` +import {concatUint8Arrays} from 'uint8array-extras'; + +const a = new Uint8Array([1, 2, 3]); +const b = new Uint8Array([4, 5, 6]); + +console.log(concatUint8Arrays([a, b])); +//=> Uint8Array [1, 2, 3, 4, 5, 6] +``` +*/ +export function concatUint8Arrays(arrays: Uint8Array[], totalLength?: number): Uint8Array; + +/** +Check if two arrays are identical by verifying that they contain the same bytes in the same sequence. + +Replacement for [`Buffer#equals()`](https://nodejs.org/api/buffer.html#bufequalsotherbuffer). + +@example +``` +import {areUint8ArraysEqual} from 'uint8array-extras'; + +const a = new Uint8Array([1, 2, 3]); +const b = new Uint8Array([1, 2, 3]); +const c = new Uint8Array([4, 5, 6]); + +console.log(areUint8ArraysEqual(a, b)); +//=> true + +console.log(areUint8ArraysEqual(a, c)); +//=> false +``` +*/ +export function areUint8ArraysEqual(a: Uint8Array, b: Uint8Array): boolean; + +/** +Compare two arrays and indicate their relative order or equality. Useful for sorting. + +Replacement for [`Buffer.compare()`](https://nodejs.org/api/buffer.html#static-method-buffercomparebuf1-buf2). + +@example +``` +import {compareUint8Arrays} from 'uint8array-extras'; + +const array1 = new Uint8Array([1, 2, 3]); +const array2 = new Uint8Array([4, 5, 6]); +const array3 = new Uint8Array([7, 8, 9]); + +[array3, array1, array2].sort(compareUint8Arrays); +//=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]] +``` +*/ +export function compareUint8Arrays(a: Uint8Array, b: Uint8Array): 0 | 1 | -1; + +/** +Convert a `Uint8Array` to a string. + +@param encoding - The [encoding](https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings) to convert from. Default: `'utf8'` + +Replacement for [`Buffer#toString()`](https://nodejs.org/api/buffer.html#buftostringencoding-start-end). For the `encoding` parameter, `latin1` should be used instead of `binary` and `utf-16le` instead of `utf16le`. + +@example +``` +import {uint8ArrayToString} from 'uint8array-extras'; + +const byteArray = new Uint8Array([72, 101, 108, 108, 111]); +console.log(uint8ArrayToString(byteArray)); +//=> 'Hello' + +const zh = new Uint8Array([167, 65, 166, 110]); +console.log(uint8ArrayToString(zh, 'big5')); +//=> '你好' + +const ja = new Uint8Array([130, 177, 130, 241, 130, 201, 130, 191, 130, 205]); +console.log(uint8ArrayToString(ja, 'shift-jis')); +//=> 'こんにちは' +``` +*/ +// export function uint8ArrayToString(array: Uint8Array | ArrayBuffer, encoding?: string): string; + +/** +Convert a string to a `Uint8Array` (using UTF-8 encoding). + +Replacement for [`Buffer.from('Hello')`](https://nodejs.org/api/buffer.html#static-method-bufferfromstring-encoding). + +@example +``` +import {stringToUint8Array} from 'uint8array-extras'; + +console.log(stringToUint8Array('Hello')); +//=> Uint8Array [72, 101, 108, 108, 111] +``` +*/ +export function stringToUint8Array(string: string): Uint8Array; + +/** +Convert a `Uint8Array` to a Base64-encoded string. + +Specify `{urlSafe: true}` to get a [Base64URL](https://base64.guru/standards/base64url)-encoded string. + +Replacement for [`Buffer#toString('base64')`](https://nodejs.org/api/buffer.html#buftostringencoding-start-end). + +@example +``` +import {uint8ArrayToBase64} from 'uint8array-extras'; + +const byteArray = new Uint8Array([72, 101, 108, 108, 111]); + +console.log(uint8ArrayToBase64(byteArray)); +//=> 'SGVsbG8=' +``` +*/ +export function uint8ArrayToBase64(array: Uint8Array, options?: { urlSafe: boolean }): string; + +/** +Convert a Base64-encoded or [Base64URL](https://base64.guru/standards/base64url)-encoded string to a `Uint8Array`. + +Replacement for [`Buffer.from('SGVsbG8=', 'base64')`](https://nodejs.org/api/buffer.html#static-method-bufferfromstring-encoding). + +@example +``` +import {base64ToUint8Array} from 'uint8array-extras'; + +console.log(base64ToUint8Array('SGVsbG8=')); +//=> Uint8Array [72, 101, 108, 108, 111] +``` +*/ +export function base64ToUint8Array(string: string): Uint8Array; + +/** +Encode a string to Base64-encoded string. + +Specify `{urlSafe: true}` to get a [Base64URL](https://base64.guru/standards/base64url)-encoded string. + +Replacement for `Buffer.from('Hello').toString('base64')` and [`btoa()`](https://developer.mozilla.org/en-US/docs/Web/API/btoa). + +@example +``` +import {stringToBase64} from 'uint8array-extras'; + +console.log(stringToBase64('Hello')); +//=> 'SGVsbG8=' +``` +*/ +export function stringToBase64(string: string, options?: { urlSafe: boolean }): string; + +/** +Decode a Base64-encoded or [Base64URL](https://base64.guru/standards/base64url)-encoded string to a string. + +Replacement for `Buffer.from('SGVsbG8=', 'base64').toString()` and [`atob()`](https://developer.mozilla.org/en-US/docs/Web/API/atob). + +@example +``` +import {base64ToString} from 'uint8array-extras'; + +console.log(base64ToString('SGVsbG8=')); +//=> 'Hello' +``` +*/ +export function base64ToString(base64String: string): string; + +/** +Convert a `Uint8Array` to a Hex string. + +Replacement for [`Buffer#toString('hex')`](https://nodejs.org/api/buffer.html#buftostringencoding-start-end). + +@example +``` +import {uint8ArrayToHex} from 'uint8array-extras'; + +const byteArray = new Uint8Array([72, 101, 108, 108, 111]); + +console.log(uint8ArrayToHex(byteArray)); +//=> '48656c6c6f' +``` +*/ +export function uint8ArrayToHex(array: Uint8Array): string; + +/** +Convert a Hex string to a `Uint8Array`. + +Replacement for [`Buffer.from('48656c6c6f', 'hex')`](https://nodejs.org/api/buffer.html#static-method-bufferfromstring-encoding). + +@example +``` +import {hexToUint8Array} from 'uint8array-extras'; + +console.log(hexToUint8Array('48656c6c6f')); +//=> Uint8Array [72, 101, 108, 108, 111] +``` +*/ +export function hexToUint8Array(hexString: string): Uint8Array; + +/** +Read `DataView#byteLength` number of bytes from the given view, up to 48-bit. + +Replacement for [`Buffer#readUintBE`](https://nodejs.org/api/buffer.html#bufreadintbeoffset-bytelength) + +@example +``` +import {getUintBE} from 'uint8array-extras'; + +const byteArray = new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + +console.log(getUintBE(new DataView(byteArray.buffer))); +//=> 20015998341291 +``` +*/ +export function getUintBE(view: DataView): number; // eslint-disable-line @typescript-eslint/naming-convention + +/** +Find the index of the first occurrence of the given sequence of bytes (`value`) within the given `Uint8Array` (`array`). + +Replacement for [`Buffer#indexOf`](https://nodejs.org/api/buffer.html#bufindexofvalue-byteoffset-encoding). `Uint8Array#indexOf` only takes a number which is different from Buffer's `indexOf` implementation. + +@example +``` +import {indexOf} from 'uint8array-extras'; + +const byteArray = new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef]); + +console.log(indexOf(byteArray, new Uint8Array([0x78, 0x90]))); +//=> 3 +``` +*/ +export function indexOf(array: Uint8Array, value: Uint8Array): number; + +/** +Checks if the given sequence of bytes (`value`) is within the given `Uint8Array` (`array`). + +Returns true if the value is included, otherwise false. + +Replacement for [`Buffer#includes`](https://nodejs.org/api/buffer.html#bufincludesvalue-byteoffset-encoding). `Uint8Array#includes` only takes a number which is different from Buffer's `includes` implementation. + +``` +import {includes} from 'uint8array-extras'; + +const byteArray = new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef]); + +console.log(includes(byteArray, new Uint8Array([0x78, 0x90]))); +//=> true +``` +*/ +export function includes(array: Uint8Array, value: Uint8Array): boolean; + +/** + * Convert a Uint8Array (or ArrayBuffer) of UTF-8 bytes into a JS string. + * Only "utf8" is supported. For any other encoding you’ll need a polyfill. + * + * @param {Uint8Array|ArrayBuffer} input + * @param {string} [encoding="utf8"] + * @returns {string} + */ +export function uint8ArrayToString(array: Uint8Array, encoding?: string): string; \ No newline at end of file diff --git a/blue_modules/uint8array-extras/index.js b/blue_modules/uint8array-extras/index.js new file mode 100644 index 00000000000..84a03b774f0 --- /dev/null +++ b/blue_modules/uint8array-extras/index.js @@ -0,0 +1,410 @@ +/** + * author: Sindre Sorhus + * license: MIT + * source: https://github.com/sindresorhus/uint8array-extras + */ +const objectToString = Object.prototype.toString; +const uint8ArrayStringified = '[object Uint8Array]'; +const arrayBufferStringified = '[object ArrayBuffer]'; + +function isType(value, typeConstructor, typeStringified) { + if (!value) { + return false; + } + + if (value.constructor === typeConstructor) { + return true; + } + + return objectToString.call(value) === typeStringified; +} + +export function isUint8Array(value) { + return isType(value, Uint8Array, uint8ArrayStringified); +} + +function isArrayBuffer(value) { + return isType(value, ArrayBuffer, arrayBufferStringified); +} + +function isUint8ArrayOrArrayBuffer(value) { + return isUint8Array(value) || isArrayBuffer(value); +} + +export function assertUint8Array(value) { + if (!isUint8Array(value)) { + throw new TypeError(`Expected \`Uint8Array\`, got \`${typeof value}\``); + } +} + +export function assertUint8ArrayOrArrayBuffer(value) { + if (!isUint8ArrayOrArrayBuffer(value)) { + throw new TypeError(`Expected \`Uint8Array\` or \`ArrayBuffer\`, got \`${typeof value}\``); + } +} + +export function toUint8Array(value) { + if (value instanceof ArrayBuffer) { + return new Uint8Array(value); + } + + if (ArrayBuffer.isView(value)) { + return new Uint8Array(value.buffer, value.byteOffset, value.byteLength); + } + + throw new TypeError(`Unsupported value, got \`${typeof value}\`.`); +} + +export function concatUint8Arrays(arrays, totalLength) { + if (arrays.length === 0) { + return new Uint8Array(0); + } + + totalLength ??= arrays.reduce((accumulator, currentValue) => accumulator + currentValue.length, 0); + + const returnValue = new Uint8Array(totalLength); + + let offset = 0; + for (const array of arrays) { + assertUint8Array(array); + returnValue.set(array, offset); + offset += array.length; + } + + return returnValue; +} + +export function areUint8ArraysEqual(a, b) { + assertUint8Array(a); + assertUint8Array(b); + + if (a === b) { + return true; + } + + if (a.length !== b.length) { + return false; + } + + // eslint-disable-next-line unicorn/no-for-loop + for (let index = 0; index < a.length; index++) { + if (a[index] !== b[index]) { + return false; + } + } + + return true; +} + +export function compareUint8Arrays(a, b) { + assertUint8Array(a); + assertUint8Array(b); + + const length = Math.min(a.length, b.length); + + for (let index = 0; index < length; index++) { + const diff = a[index] - b[index]; + if (diff !== 0) { + return Math.sign(diff); + } + } + + // At this point, all the compared elements are equal. + // The shorter array should come first if the arrays are of different lengths. + return Math.sign(a.length - b.length); +} + +// const cachedDecoders = { +// utf8: new globalThis.TextDecoder("utf8"), +// }; + +// +// !!!!!!! commented out because we dont have `TextDecoder` as dep anymore !!!!!!!! +// +// export function uint8ArrayToString(array, encoding = "utf8") { +// assertUint8ArrayOrArrayBuffer(array); +// cachedDecoders[encoding] ??= new globalThis.TextDecoder(encoding); +// return cachedDecoders[encoding].decode(array); +// } + +function assertString(value) { + if (typeof value !== 'string') { + throw new TypeError(`Expected \`string\`, got \`${typeof value}\``); + } +} + +const cachedEncoder = new globalThis.TextEncoder(); + +export function stringToUint8Array(string) { + assertString(string); + return cachedEncoder.encode(string); +} + +function base64ToBase64Url(base64) { + return base64.replaceAll('+', '-').replaceAll('/', '_').replace(/[=]+$/, ''); +} + +function base64UrlToBase64(base64url) { + return base64url.replaceAll('-', '+').replaceAll('_', '/'); +} + +// Reference: https://phuoc.ng/collection/this-vs-that/concat-vs-push/ +const MAX_BLOCK_SIZE = 65_535; + +export function uint8ArrayToBase64(array, { urlSafe = false } = {}) { + assertUint8Array(array); + + let base64; + + if (array.length < MAX_BLOCK_SIZE) { + // Required as `btoa` and `atob` don't properly support Unicode: https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem + base64 = globalThis.btoa(String.fromCodePoint.apply(this, array)); + } else { + base64 = ''; + for (const value of array) { + base64 += String.fromCodePoint(value); + } + + base64 = globalThis.btoa(base64); + } + + return urlSafe ? base64ToBase64Url(base64) : base64; +} + +export function base64ToUint8Array(base64String) { + assertString(base64String); + return Uint8Array.from(globalThis.atob(base64UrlToBase64(base64String)), x => x.codePointAt(0)); +} + +export function stringToBase64(string, { urlSafe = false } = {}) { + assertString(string); + return uint8ArrayToBase64(stringToUint8Array(string), { urlSafe }); +} + +// export function base64ToString(base64String) { +// assertString(base64String); +// return uint8ArrayToString(base64ToUint8Array(base64String)); +// } + +const byteToHexLookupTable = Array.from({ length: 256 }, (_, index) => index.toString(16).padStart(2, '0')); + +export function uint8ArrayToHex(array) { + assertUint8Array(array); + + // Concatenating a string is faster than using an array. + let hexString = ''; + + // eslint-disable-next-line unicorn/no-for-loop -- Max performance is critical. + for (let index = 0; index < array.length; index++) { + hexString += byteToHexLookupTable[array[index]]; + } + + return hexString; +} + +const hexToDecimalLookupTable = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + a: 10, + b: 11, + c: 12, + d: 13, + e: 14, + f: 15, + A: 10, + B: 11, + C: 12, + D: 13, + E: 14, + F: 15, +}; + +export function hexToUint8Array(hexString) { + assertString(hexString); + + if (hexString.length % 2 !== 0) { + throw new Error('Invalid Hex string length.'); + } + + const resultLength = hexString.length / 2; + const bytes = new Uint8Array(resultLength); + + for (let index = 0; index < resultLength; index++) { + const highNibble = hexToDecimalLookupTable[hexString[index * 2]]; + const lowNibble = hexToDecimalLookupTable[hexString[index * 2 + 1]]; + + if (highNibble === undefined || lowNibble === undefined) { + throw new Error(`Invalid Hex character encountered at position ${index * 2}`); + } + + bytes[index] = (highNibble << 4) | lowNibble; // eslint-disable-line no-bitwise + } + + return bytes; +} + +/** +@param {DataView} view +@returns {number} +*/ +export function getUintBE(view) { + const { byteLength } = view; + + if (byteLength === 6) { + return view.getUint16(0) * 2 ** 32 + view.getUint32(2); + } + + if (byteLength === 5) { + return view.getUint8(0) * 2 ** 32 + view.getUint32(1); + } + + if (byteLength === 4) { + return view.getUint32(0); + } + + if (byteLength === 3) { + return view.getUint8(0) * 2 ** 16 + view.getUint16(1); + } + + if (byteLength === 2) { + return view.getUint16(0); + } + + if (byteLength === 1) { + return view.getUint8(0); + } +} + +/** +@param {Uint8Array} array +@param {Uint8Array} value +@returns {number} +*/ +export function indexOf(array, value) { + const arrayLength = array.length; + const valueLength = value.length; + + if (valueLength === 0) { + return -1; + } + + if (valueLength > arrayLength) { + return -1; + } + + const validOffsetLength = arrayLength - valueLength; + + for (let index = 0; index <= validOffsetLength; index++) { + let isMatch = true; + for (let index2 = 0; index2 < valueLength; index2++) { + if (array[index + index2] !== value[index2]) { + isMatch = false; + break; + } + } + + if (isMatch) { + return index; + } + } + + return -1; +} + +/** +@param {Uint8Array} array +@param {Uint8Array} value +@returns {boolean} +*/ +export function includes(array, value) { + return indexOf(array, value) !== -1; +} + +// we can use this implementation when we will have TextDecoder in RN +// const cachedDecoders = { +// utf8: new globalThis.TextDecoder("utf8"), +// }; +// export function uint8ArrayToString(array, encoding = "utf8") { +// assertUint8ArrayOrArrayBuffer(array); +// cachedDecoders[encoding] ??= new globalThis.TextDecoder(encoding); +// return cachedDecoders[encoding].decode(array); +// } +// meanwhile: + +/** + * Convert a Uint8Array (or ArrayBuffer) of UTF-8 bytes into a JS string. + * Only "utf8" is supported. For any other encoding you’ll need a polyfill. + * + * @param {Uint8Array|ArrayBuffer} input + * @param {string} [encoding="utf8"] + * @returns {string} + */ +export function uint8ArrayToString(input, encoding = 'utf8') { + assertUint8ArrayOrArrayBuffer(input); + + // Reject anything other than UTF-8 + if (!/utf-?8/i.test(encoding)) { + throw new Error('Encoding "' + encoding + '" isn’t supported without a TextDecoder polyfill'); + } + + // Normalise to Uint8Array + const bytes = input instanceof Uint8Array ? input : new Uint8Array(input); + return decodeUtf8(bytes); +} + +/** + * Minimal UTF-8 decoder + * @param {Uint8Array} bytes + * @returns {string} + */ +function decodeUtf8(bytes) { + let i = 0; + const l = bytes.length; + const codeUnits = []; + let result = ''; + + while (i < l) { + const byte1 = bytes[i++]; + + // 1-byte (ASCII) + if (byte1 < 0x80) { + codeUnits.push(byte1); + } + // 2-byte + else if (byte1 < 0xe0) { + const byte2 = bytes[i++] & 0x3f; + codeUnits.push(((byte1 & 0x1f) << 6) | byte2); + } + // 3-byte + else if (byte1 < 0xf0) { + const byte2 = bytes[i++] & 0x3f; + const byte3 = bytes[i++] & 0x3f; + codeUnits.push(((byte1 & 0x0f) << 12) | (byte2 << 6) | byte3); + } + // 4-byte (→ surrogate pair) + else { + const byte2 = bytes[i++] & 0x3f; + const byte3 = bytes[i++] & 0x3f; + const byte4 = bytes[i++] & 0x3f; + let cp = ((byte1 & 0x07) << 18) | (byte2 << 12) | (byte3 << 6) | byte4; + cp -= 0x10000; + codeUnits.push(0xd800 + (cp >> 10), 0xdc00 + (cp & 0x3ff)); + } + + // Flush periodically to avoid huge apply() calls + if (codeUnits.length > 0x8000) { + result += String.fromCharCode.apply(null, codeUnits); + codeUnits.length = 0; + } + } + + return result + String.fromCharCode.apply(null, codeUnits); +} diff --git a/blue_modules/ur/index.js b/blue_modules/ur/index.js index 6ae16df28df..764092477b7 100644 --- a/blue_modules/ur/index.js +++ b/blue_modules/ur/index.js @@ -1,23 +1,37 @@ -import { URDecoder } from '@ngraveio/bc-ur'; -import b58 from 'bs58check'; import { + Bytes, + CryptoAccount, CryptoHDKey, CryptoKeypath, CryptoOutput, + CryptoPSBT, PathComponent, ScriptExpressions, - CryptoPSBT, - CryptoAccount, - Bytes, } from '@keystonehq/bc-ur-registry/dist'; -import { decodeUR as origDecodeUr, encodeUR as origEncodeUR, extractSingleWorkload as origExtractSingleWorkload } from '../bc-ur/dist'; -import { MultisigCosigner, MultisigHDWallet } from '../../class'; -import { Psbt } from 'bitcoinjs-lib'; +import { URDecoder } from '@ngraveio/bc-ur'; import AsyncStorage from '@react-native-async-storage/async-storage'; +import { Psbt } from 'bitcoinjs-lib'; +import b58 from 'bs58check'; + +import { MultisigCosigner } from '../../class/multisig-cosigner'; +import { MultisigHDWallet } from '../../class/wallets/multisig-hd-wallet'; +import { joinQRs } from '../bbqr/join'; +import { + concatUint8Arrays, + hexToUint8Array, + stringToUint8Array, + uint8ArrayToHex, + uint8ArrayToBase64, + uint8ArrayToString, +} from '../uint8array-extras'; +import { splitQRs } from '../bbqr/split'; +import { decodeUR as origDecodeUr, encodeUR as origEncodeUR, extractSingleWorkload as origExtractSingleWorkload } from '../bc-ur/dist'; const USE_UR_V1 = 'USE_UR_V1'; +const USE_BBQR_WALLET_IDS = 'USE_BBQR_WALLET_IDS'; let useURv1 = false; +let useBBQRWalletIDs = []; (async () => { try { @@ -25,6 +39,17 @@ let useURv1 = false; } catch (_) {} })(); +(async () => { + try { + // initial load of wallets that must use BBQR for animated QR codes + const json = await AsyncStorage.getItem(USE_BBQR_WALLET_IDS); + const parsed = JSON.parse(json); + if (Array.isArray(parsed)) { + useBBQRWalletIDs = parsed; + } + } catch (_) {} +})(); + async function isURv1Enabled() { try { return !!(await AsyncStorage.getItem(USE_UR_V1)); @@ -38,13 +63,51 @@ async function setUseURv1() { return AsyncStorage.setItem(USE_UR_V1, '1'); } +async function setWalletIdMustUseBBQR(walletID) { + console.log('setting walletID to useBBQR:', walletID); + useBBQRWalletIDs.push(walletID); + await AsyncStorage.setItem(USE_BBQR_WALLET_IDS, JSON.stringify(useBBQRWalletIDs)); +} + async function clearUseURv1() { useURv1 = false; return AsyncStorage.removeItem(USE_UR_V1); } -function encodeUR(arg1, arg2) { - return useURv1 ? encodeURv1(arg1, arg2) : encodeURv2(arg1, arg2); +/** + * + * @param value {string} payload to render in QR + * @param capacity {number?} Bytes per QR fragment + * @param walletID {string?} Optional, if we previously saved preferences for that wallet (which protocol to use) + * @param forceProtocol {'auto' | 'BBQR' | 'URv2' = 'auto'} + * @returns {string[]} + */ +function encodeUR(value, capacity = 175, walletID, forceProtocol = 'auto') { + if (forceProtocol === 'URv2') { + return useURv1 ? encodeURv1(value, capacity) : encodeURv2(value, capacity); + } + + if (forceProtocol === 'BBQR' || (walletID && useBBQRWalletIDs.includes(walletID))) { + // payload should be hex + if (!isHexString(value)) { + value = uint8ArrayToHex(stringToUint8Array(value)); + } + + const minSplit = Math.max(1, Math.ceil(value.length / 2 / capacity)); + + if (uint8ArrayToString(hexToUint8Array(value)).startsWith('psbt')) { + // its a PSBT! + const ret = splitQRs(hexToUint8Array(value), 'P', { minSplit }); + return ret.parts; + } + + // its a random utf8 text! + const ret = splitQRs(hexToUint8Array(value), 'U', { minSplit }); + return ret.parts; + } // end BBQR + + // auto (aka default): + return useURv1 ? encodeURv1(value, capacity) : encodeURv2(value, capacity); } function encodeURv1(arg1, arg2) { @@ -57,10 +120,14 @@ function encodeURv1(arg1, arg2) { return origEncodeUR(arg1, arg2); } +function isHexString(s) { + return /^[0-9a-fA-F]*$/.test(s) && s.length % 2 === 0; +} + /** * * @param str {string} For PSBT, or coordination setup (translates to `bytes`) it expects hex string. For ms cosigner it expects plain json string - * @param len {number} lenght of each fragment + * @param len {number} length of each fragment * @return {string[]} txt fragments ready to be displayed in dynamic QR */ function encodeURv2(str, len) { @@ -125,7 +192,6 @@ function encodeURv2(str, len) { } catch (_) {} // fail. fallback to bytes - const bytes = new Bytes(Buffer.from(str, 'hex')); const encoder = bytes.toUREncoder(len); @@ -186,33 +252,47 @@ function decodeUR(arg) { derivationPath === MultisigHDWallet.PATH_LEGACY || derivationPath === MultisigHDWallet.PATH_WRAPPED_SEGWIT || derivationPath === MultisigHDWallet.PATH_NATIVE_SEGWIT; - const version = Buffer.from(isMultisig ? '02aa7ed3' : '04b24746', 'hex'); + const version = hexToUint8Array(isMultisig ? '02aa7ed3' : '04b24746'); const parentFingerprint = hdKey.getParentFingerprint(); const depth = hdKey.getOrigin().getDepth(); - const depthBuf = Buffer.alloc(1); - depthBuf.writeUInt8(depth); + const depthBuf = new Uint8Array(1); + depthBuf[0] = depth; const components = hdKey.getOrigin().getComponents(); const lastComponents = components[components.length - 1]; const index = lastComponents.isHardened() ? lastComponents.getIndex() + 0x80000000 : lastComponents.getIndex(); - const indexBuf = Buffer.alloc(4); - indexBuf.writeUInt32BE(index); + const indexBuf = new Uint8Array(4); + new DataView(indexBuf.buffer).setUint32(0, index, false); // big-endian const chainCode = hdKey.getChainCode(); const key = hdKey.getKey(); - const data = Buffer.concat([version, depthBuf, parentFingerprint, indexBuf, chainCode, key]); + const data = concatUint8Arrays([version, depthBuf, parentFingerprint, indexBuf, chainCode, key]); const zpub = b58.encode(data); const result = {}; result.ExtPubKey = zpub; - result.MasterFingerprint = cryptoAccount.getMasterFingerprint().toString('hex').toUpperCase(); + result.MasterFingerprint = uint8ArrayToHex(cryptoAccount.getMasterFingerprint()).toUpperCase(); result.AccountKeyPath = derivationPath; const str = JSON.stringify(result); - return Buffer.from(str, 'ascii').toString('hex'); // we are expected to return hex-encoded string + return uint8ArrayToHex(stringToUint8Array(str)); // we are expected to return hex-encoded string } class BlueURDecoder extends URDecoder { + bbqrParts = {}; // key-value, payload->1 + toString() { + if (Object.keys(this.bbqrParts).length > 0) { + // its BBQR, handle differently + const decodedBbqr = joinQRs(Object.keys(this.bbqrParts)); + if (decodedBbqr.fileType === 'P') { + // if its psbt we return base64: + return uint8ArrayToBase64(decodedBbqr.raw); + } + + // for everything else we covnert bytes to string directly + return uint8ArrayToString(decodedBbqr.raw); + } + const decoded = this.resultUR(); if (decoded.type === 'crypto-psbt') { @@ -222,7 +302,8 @@ class BlueURDecoder extends URDecoder { if (decoded.type === 'bytes') { const bytes = Bytes.fromCBOR(decoded.cbor); - return Buffer.from(bytes.getData(), 'hex').toString('ascii'); + const data = bytes.getData(); + return uint8ArrayToString(data); } if (decoded.type === 'crypto-account') { @@ -241,39 +322,39 @@ class BlueURDecoder extends URDecoder { derivationPath === MultisigHDWallet.PATH_LEGACY || derivationPath === MultisigHDWallet.PATH_WRAPPED_SEGWIT || derivationPath === MultisigHDWallet.PATH_NATIVE_SEGWIT; - const version = Buffer.from(isMultisig ? '02aa7ed3' : '04b24746', 'hex'); + const version = hexToUint8Array(isMultisig ? '02aa7ed3' : '04b24746'); const parentFingerprint = hdKey.getParentFingerprint(); const depth = hdKey.getOrigin().getDepth(); - const depthBuf = Buffer.alloc(1); - depthBuf.writeUInt8(depth); + const depthBuf = new Uint8Array(1); + depthBuf[0] = depth; const components = hdKey.getOrigin().getComponents(); const lastComponents = components[components.length - 1]; const index = lastComponents.isHardened() ? lastComponents.getIndex() + 0x80000000 : lastComponents.getIndex(); - const indexBuf = Buffer.alloc(4); - indexBuf.writeUInt32BE(index); + const indexBuf = new Uint8Array(4); + new DataView(indexBuf.buffer).setUint32(0, index, false); // big-endian const chainCode = hdKey.getChainCode(); const key = hdKey.getKey(); - const data = Buffer.concat([version, depthBuf, parentFingerprint, indexBuf, chainCode, key]); + const data = concatUint8Arrays([version, depthBuf, parentFingerprint, indexBuf, chainCode, key]); const zpub = b58.encode(data); const result = {}; result.ExtPubKey = zpub; - result.MasterFingerprint = cryptoAccount.getMasterFingerprint().toString('hex').toUpperCase(); + result.MasterFingerprint = uint8ArrayToHex(cryptoAccount.getMasterFingerprint()).toUpperCase(); result.AccountKeyPath = derivationPath; if (derivationPath.startsWith("m/49'/0'/")) { // converting to ypub let data = b58.decode(result.ExtPubKey); data = data.slice(4); - result.ExtPubKey = b58.encode(Buffer.concat([Buffer.from('049d7cb2', 'hex'), data])); + result.ExtPubKey = b58.encode(concatUint8Arrays([hexToUint8Array('049d7cb2'), data])); } if (derivationPath.startsWith("m/44'/0'/")) { // converting to xpub let data = b58.decode(result.ExtPubKey); data = data.slice(4); - result.ExtPubKey = b58.encode(Buffer.concat([Buffer.from('0488b21e', 'hex'), data])); + result.ExtPubKey = b58.encode(concatUint8Arrays([hexToUint8Array('0488b21e'), data])); } results.push(result); @@ -289,6 +370,59 @@ class BlueURDecoder extends URDecoder { throw new Error('unsupported data format'); } + + isComplete() { + if (Object.keys(this.bbqrParts).length > 0) { + // its BBQR, handle differently + const bbqrPayload = Object.keys(this.bbqrParts)[0]; + if (bbqrPayload.slice(0, 2) !== 'B$') { + throw new Error('fixed header not found, expected B$'); + } + + const numParts = parseInt(bbqrPayload.slice(4, 6), 36); + return Object.keys(this.bbqrParts).length >= numParts; + } + + // fallback to old BC-UR mechanism + return super.isComplete(); + } + + estimatedPercentComplete() { + if (Object.keys(this.bbqrParts).length > 0) { + // its BBQR, handle differently + const bbqrPayload = Object.keys(this.bbqrParts)[0]; + if (bbqrPayload.slice(0, 2) !== 'B$') { + throw new Error('fixed header not found, expected B$'); + } + + const numParts = parseInt(bbqrPayload.slice(4, 6), 36); + return Object.keys(this.bbqrParts).length / numParts; + } + + // fallback to old BC-UR mechanism + return super.estimatedPercentComplete(); + } + + receivePart(s) { + if (s.startsWith('B$')) { + // its BBQR, handle differently + this.bbqrParts[s] = true; + return true; + } + + // fallback to old BC-UR mechanism + return super.receivePart(s); + } } -export { decodeUR, encodeUR, extractSingleWorkload, BlueURDecoder, isURv1Enabled, setUseURv1, clearUseURv1 }; +export { + decodeUR, + encodeUR, + extractSingleWorkload, + BlueURDecoder, + isURv1Enabled, + setUseURv1, + clearUseURv1, + setWalletIdMustUseBBQR, + isHexString, +}; diff --git a/bugsnag.js b/bugsnag.js new file mode 100644 index 00000000000..6b4de6bf078 --- /dev/null +++ b/bugsnag.js @@ -0,0 +1,18 @@ +import Bugsnag from '@bugsnag/react-native'; +import DefaultPreference from 'react-native-default-preference'; + +const GROUP_IO_BLUEWALLET = 'group.io.bluewallet.bluewallet'; +const DO_NOT_TRACK_KEY = 'donottrack'; + +(async () => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const doNotTrack = await DefaultPreference.get(DO_NOT_TRACK_KEY); + if (doNotTrack === '1') return; + + Bugsnag.start(); + } catch (error) { + // Never let analytics setup crash the app. + console.error('Failed to initialize Bugsnag:', error); + } +})(); diff --git a/class/azteco.js b/class/azteco.js deleted file mode 100644 index 78cc4d9e0b6..00000000000 --- a/class/azteco.js +++ /dev/null @@ -1,41 +0,0 @@ -import Frisbee from 'frisbee'; -import URL from 'url'; - -export default class Azteco { - /** - * Redeems an Azteco bitcoin voucher. - * - * @param {string[]} voucher - 16-digit voucher code in groups of 4. - * @param {string} address - Bitcoin address to send the redeemed bitcoin to. - * - * @returns {Promise} Successfully redeemed or not. This method does not throw exceptions - */ - static async redeem(voucher, address) { - const api = new Frisbee({ - baseURI: 'https://azte.co/', - }); - const url = `/blue_despatch.php?CODE_1=${voucher[0]}&CODE_2=${voucher[1]}&CODE_3=${voucher[2]}&CODE_4=${voucher[3]}&ADDRESS=${address}`; - - try { - const response = await api.get(url); - return response && response.originalResponse && +response.originalResponse.status === 200; - } catch (_) { - return false; - } - } - - static isRedeemUrl(u) { - return u.startsWith('https://azte.co'); - } - - static getParamsFromUrl(u) { - const urlObject = URL.parse(u, true); // eslint-disable-line n/no-deprecated-api - return { - uri: u, - c1: urlObject.query.c1, - c2: urlObject.query.c2, - c3: urlObject.query.c3, - c4: urlObject.query.c4, - }; - } -} diff --git a/class/azteco.ts b/class/azteco.ts new file mode 100644 index 00000000000..6aad7d27edd --- /dev/null +++ b/class/azteco.ts @@ -0,0 +1,78 @@ +import URL from 'url'; +import { fetch } from '../util/fetch'; + +export type AztecoVoucher = { + c1: string; + c2: string; + c3: string; + c4: string; +}; + +export default class Azteco { + /** + * Redeems an Azteco bitcoin voucher. + * + * @param {AztecoVoucher} voucher - 16-digit voucher code in groups of 4. + * @param {string} address - Bitcoin address to send the redeemed bitcoin to. + * + * @returns {Promise} Successfully redeemed or not. This method does not throw exceptions + */ + static async redeem(voucher: AztecoVoucher, address: string): Promise { + const baseURI = 'https://azte.co/'; + const url = `${baseURI}blue_despatch.php?CODE_1=${voucher.c1}&CODE_2=${voucher.c2}&CODE_3=${voucher.c3}&CODE_4=${voucher.c4}&ADDRESS=${address}`; + + try { + const response = await fetch(url, { + method: 'GET', + }); + return response && response.status === 200; + } catch (_) { + return false; + } + } + + static isRedeemUrl(u: string): boolean { + return u.startsWith('https://azte.co'); + } + + static getParamsFromUrl(u: string): { aztecoVoucher: AztecoVoucher } { + const urlObject = URL.parse(u, true); // eslint-disable-line n/no-deprecated-api + const q = urlObject.query; + + // new format https://azte.co/redeem?code=1111222233334444 + if (typeof q.code === 'string' && q.code.length === 16) { + return { + aztecoVoucher: { + c1: q.code.substring(0, 4), + c2: q.code.substring(4, 8), + c3: q.code.substring(8, 12), + c4: q.code.substring(12, 16), + }, + }; + } + + // old format https://azte.co?c1=1111&c2=2222&c3=3333&c4=4444 + if ( + typeof q.c1 === 'string' && + typeof q.c2 === 'string' && + typeof q.c3 === 'string' && + typeof q.c4 === 'string' && + q.c1.length === 4 && + q.c2.length === 4 && + q.c3.length === 4 && + q.c4.length === 4 + ) { + return { + aztecoVoucher: { + c1: q.c1, + c2: q.c2, + c3: q.c3, + c4: q.c4, + }, + }; + } + + // if the url does not match any of the formats, throw an error + throw new Error('Invalid Azteco URL'); + } +} diff --git a/class/biometrics.js b/class/biometrics.js deleted file mode 100644 index 14d2b362677..00000000000 --- a/class/biometrics.js +++ /dev/null @@ -1,147 +0,0 @@ -import FingerprintScanner from 'react-native-fingerprint-scanner'; -import { Platform, Alert } from 'react-native'; -import PasscodeAuth from 'react-native-passcode-auth'; -import * as NavigationService from '../NavigationService'; -import { StackActions, CommonActions } from '@react-navigation/native'; -import RNSecureKeyStore from 'react-native-secure-key-store'; -import loc from '../loc'; -import { useContext } from 'react'; -import { BlueStorageContext } from '../blue_modules/storage-context'; -import alert from '../components/Alert'; - -function Biometric() { - const { getItem, setItem } = useContext(BlueStorageContext); - Biometric.STORAGEKEY = 'Biometrics'; - Biometric.FaceID = 'Face ID'; - Biometric.TouchID = 'Touch ID'; - Biometric.Biometrics = 'Biometrics'; - - Biometric.isDeviceBiometricCapable = async () => { - try { - const isDeviceBiometricCapable = await FingerprintScanner.isSensorAvailable(); - if (isDeviceBiometricCapable) { - return true; - } - } catch (e) { - console.log('Biometrics isDeviceBiometricCapable failed'); - console.log(e); - Biometric.setBiometricUseEnabled(false); - return false; - } - }; - - Biometric.biometricType = async () => { - try { - const isSensorAvailable = await FingerprintScanner.isSensorAvailable(); - return isSensorAvailable; - } catch (e) { - console.log('Biometrics biometricType failed'); - console.log(e); - } - return false; - }; - - Biometric.isBiometricUseEnabled = async () => { - try { - const enabledBiometrics = await getItem(Biometric.STORAGEKEY); - return !!enabledBiometrics; - } catch (_) {} - - return false; - }; - - Biometric.isBiometricUseCapableAndEnabled = async () => { - const isBiometricUseEnabled = await Biometric.isBiometricUseEnabled(); - const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable(); - return isBiometricUseEnabled && isDeviceBiometricCapable; - }; - - Biometric.setBiometricUseEnabled = async value => { - await setItem(Biometric.STORAGEKEY, value === true ? '1' : ''); - }; - - Biometric.unlockWithBiometrics = async () => { - const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable(); - if (isDeviceBiometricCapable) { - return new Promise(resolve => { - FingerprintScanner.authenticate({ description: loc.settings.biom_conf_identity, fallbackEnabled: true }) - .then(() => resolve(true)) - .catch(error => { - console.log('Biometrics authentication failed'); - console.log(error); - resolve(false); - }) - .finally(() => FingerprintScanner.release()); - }); - } - return false; - }; - - Biometric.clearKeychain = async () => { - await RNSecureKeyStore.remove('data'); - await RNSecureKeyStore.remove('data_encrypted'); - await RNSecureKeyStore.remove(Biometric.STORAGEKEY); - NavigationService.dispatch(StackActions.replace('WalletsRoot')); - }; - - Biometric.requestDevicePasscode = async () => { - let isDevicePasscodeSupported = false; - try { - isDevicePasscodeSupported = await PasscodeAuth.isSupported(); - if (isDevicePasscodeSupported) { - const isAuthenticated = await PasscodeAuth.authenticate(); - if (isAuthenticated) { - Alert.alert( - loc.settings.encrypt_tstorage, - loc.settings.biom_remove_decrypt, - [ - { text: loc._.cancel, style: 'cancel' }, - { - text: loc._.ok, - onPress: () => Biometric.clearKeychain(), - }, - ], - { cancelable: false }, - ); - } - } - } catch { - isDevicePasscodeSupported = undefined; - } - if (isDevicePasscodeSupported === false) { - alert(loc.settings.biom_no_passcode); - } - }; - - Biometric.showKeychainWipeAlert = () => { - if (Platform.OS === 'ios') { - Alert.alert( - loc.settings.encrypt_tstorage, - loc.settings.biom_10times, - [ - { - text: loc._.cancel, - onPress: () => { - NavigationService.dispatch( - CommonActions.setParams({ - index: 0, - routes: [{ name: 'UnlockWithScreenRoot' }, { params: { unlockOnComponentMount: false } }], - }), - ); - }, - style: 'cancel', - }, - { - text: loc._.ok, - onPress: () => Biometric.requestDevicePasscode(), - style: 'default', - }, - ], - { cancelable: false }, - ); - } - }; - return null; -} - -export default Biometric; diff --git a/class/bip39_wallet_formats.json b/class/bip39_wallet_formats.json index 8fb92c17cfb..dc0d77af2da 100644 --- a/class/bip39_wallet_formats.json +++ b/class/bip39_wallet_formats.json @@ -35,6 +35,30 @@ "script_type": "p2wpkh", "iterate_accounts": true }, + { + "description": "Non-standard legacy on BIP84 path", + "derivation_path": "m/84'/0'/0'", + "script_type": "p2pkh", + "iterate_accounts": true + }, + { + "description": "Non-standard compatibility segwit on BIP84 path", + "derivation_path": "m/84'/0'/0'", + "script_type": "p2wpkh-p2sh", + "iterate_accounts": true + }, + { + "description": "Non-standard legacy on BIP49 path", + "derivation_path": "m/49'/0'/0'", + "script_type": "p2pkh", + "iterate_accounts": true + }, + { + "description": "Non-standard native segwit on BIP49 path", + "derivation_path": "m/49'/0'/0'", + "script_type": "p2wpkh", + "iterate_accounts": true + }, { "description": "Copay native segwit", "derivation_path": "m/44'/0'/0'", diff --git a/class/bip39_wallet_formats_bluewallet.json b/class/bip39_wallet_formats_bluewallet.json index b7394b2ea61..4f95e10aa49 100644 --- a/class/bip39_wallet_formats_bluewallet.json +++ b/class/bip39_wallet_formats_bluewallet.json @@ -16,5 +16,11 @@ "derivation_path": "m/84'/0'/0'", "script_type": "p2wpkh", "iterate_accounts": false + }, + { + "description": "Standard BIP86 native taproot", + "derivation_path": "m/86'/0'/0'", + "script_type": "p2tr", + "iterate_accounts": false } ] diff --git a/class/blue-app.ts b/class/blue-app.ts new file mode 100644 index 00000000000..1dbbf959cca --- /dev/null +++ b/class/blue-app.ts @@ -0,0 +1,983 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { sha256 } from '@noble/hashes/sha256'; +import DefaultPreference from 'react-native-default-preference'; +import RNFS from 'react-native-fs'; +import Keychain from 'react-native-keychain'; +import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store'; +import Realm from 'realm'; + +import * as encryption from '../blue_modules/encryption'; +import presentAlert from '../components/Alert'; +import { randomBytes } from './rng'; +import { HDAezeedWallet } from './wallets/hd-aezeed-wallet'; +import { HDLegacyBreadwalletWallet } from './wallets/hd-legacy-breadwallet-wallet'; +import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-seed-p2pkh-wallet'; +import { HDLegacyP2PKHWallet } from './wallets/hd-legacy-p2pkh-wallet'; +import { HDSegwitBech32Wallet } from './wallets/hd-segwit-bech32-wallet'; +import { HDSegwitElectrumSeedP2WPKHWallet } from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet'; +import { HDSegwitP2SHWallet } from './wallets/hd-segwit-p2sh-wallet'; +import { LegacyWallet } from './wallets/legacy-wallet'; +import { LightningCustodianWallet } from './wallets/lightning-custodian-wallet'; +import { MultisigHDWallet } from './wallets/multisig-hd-wallet'; +import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet'; +import { SegwitP2SHWallet } from './wallets/segwit-p2sh-wallet'; +import { SLIP39LegacyP2PKHWallet, SLIP39SegwitBech32Wallet, SLIP39SegwitP2SHWallet } from './wallets/slip39-wallets'; +import { ExtendedTransaction, Transaction, TWallet } from './wallets/types'; +import { WatchOnlyWallet } from './wallets/watch-only-wallet'; +import { getLNDHub } from '../helpers/lndHub'; +import { LightningArkWallet } from './wallets/lightning-ark-wallet.ts'; +import { hexToUint8Array, uint8ArrayToHex } from '../blue_modules/uint8array-extras'; +import { HDTaprootWallet } from './wallets/hd-taproot-wallet'; + +let usedBucketNum: boolean | number = false; +let savingInProgress = 0; // its both a flag and a counter of attempts to write to disk + +export type TTXMetadata = { + [txid: string]: { + memo?: string; + }; +}; + +export type TCounterpartyMetadata = { + /** + * our contact identifier, such as bip47 payment code + */ + [counterparty: string]: { + /** + * custom human-readable name we assign ourselves + */ + label: string; + /** + * some counterparties cannot be deleted because they sent a notif tx onchain, so we just mark them as hidden when user deletes + */ + hidden?: boolean; + }; +}; + +type TRealmTransaction = { + internal: boolean; + index: number; + tx: string; +}; + +type TBucketStorage = { + wallets: string[]; // array of serialized wallets, not actual wallet objects + tx_metadata: TTXMetadata; + counterparty_metadata: TCounterpartyMetadata; +}; + +const isReactNative = typeof navigator !== 'undefined' && navigator?.product === 'ReactNative'; + +export class BlueApp { + static FLAG_ENCRYPTED = 'data_encrypted'; + static LNDHUB = 'lndhub'; + static DO_NOT_TRACK = 'donottrack'; + static HANDOFF_STORAGE_KEY = 'HandOff'; + + private static _instance: BlueApp | null = null; + + static keys2migrate = [BlueApp.HANDOFF_STORAGE_KEY, BlueApp.DO_NOT_TRACK]; + + public cachedPassword?: false | string; + public tx_metadata: TTXMetadata; + public counterparty_metadata: TCounterpartyMetadata; + public wallets: TWallet[]; + + constructor() { + this.wallets = []; + this.tx_metadata = {}; + this.counterparty_metadata = {}; + this.cachedPassword = false; + } + + static getInstance(): BlueApp { + if (!BlueApp._instance) { + BlueApp._instance = new BlueApp(); + } + + return BlueApp._instance; + } + + async migrateKeys() { + // do not migrate keys if we are not in RN env + if (!isReactNative) { + return; + } + + for (const key of BlueApp.keys2migrate) { + try { + const value = await RNSecureKeyStore.get(key); + if (value) { + await AsyncStorage.setItem(key, value); + await RNSecureKeyStore.remove(key); + } + } catch (_) {} + } + } + + /** + * Wrapper for storage call. Secure store works only in RN environment. AsyncStorage is + * used for cli/tests + */ + setItem = (key: string, value: any): Promise => { + if (isReactNative) { + return RNSecureKeyStore.set(key, value, { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY }); + } else { + return AsyncStorage.setItem(key, value); + } + }; + + /** + * Wrapper for storage call. Secure store works only in RN environment. AsyncStorage is + * used for cli/tests + */ + getItem = (key: string): Promise => { + if (isReactNative) { + return RNSecureKeyStore.get(key); + } else { + return AsyncStorage.getItem(key); + } + }; + + getItemWithFallbackToRealm = async (key: string): Promise => { + let value; + try { + return await this.getItem(key); + } catch (error: any) { + 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 + 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; + } + return null; + } + }; + + storageIsEncrypted = async (): Promise => { + let data; + try { + data = await this.getItemWithFallbackToRealm(BlueApp.FLAG_ENCRYPTED); + } catch (error: any) { + console.warn('error reading `' + BlueApp.FLAG_ENCRYPTED + '` key:', error.message); + return false; + } + + return Boolean(data); + }; + + isPasswordInUse = async (password: string) => { + try { + let data = await this.getItem('data'); + data = this.decryptData(data, password); + return Boolean(data); + } catch (_e) { + return false; + } + }; + + /** + * Iterates through all values of `data` trying to + * decrypt each one, and returns first one successfully decrypted + */ + decryptData(data: string, password: string): boolean | string { + data = JSON.parse(data); + let decrypted; + let num = 0; + for (const value of data) { + decrypted = encryption.decrypt(value, password); + + if (decrypted) { + usedBucketNum = num; + return decrypted; + } + num++; + } + + return false; + } + + decryptStorage = async (password: string): Promise => { + if (password === this.cachedPassword) { + this.cachedPassword = undefined; + await this.saveToDisk(); + this.wallets = []; + this.tx_metadata = {}; + this.counterparty_metadata = {}; + return this.loadFromDisk(); + } else { + throw new Error('Incorrect password. Please, try again.'); + } + }; + + encryptStorage = async (password: string): Promise => { + // assuming the storage is not yet encrypted + await this.saveToDisk(); + let data = await this.getItem('data'); + // TODO: refactor ^^^ (should not save & load to fetch data) + + const encrypted = encryption.encrypt(data, password); + data = []; + data.push(encrypted); // putting in array as we might have many buckets with storages + data = JSON.stringify(data); + this.cachedPassword = password; + await this.setItem('data', data); + await this.setItem(BlueApp.FLAG_ENCRYPTED, '1'); + }; + + /** + * Cleans up all current application data (wallets, tx metadata etc) + * Encrypts the bucket and saves it storage + */ + createFakeStorage = async (fakePassword: string): Promise => { + usedBucketNum = false; // resetting currently used bucket so we wont overwrite it + this.wallets = []; + this.tx_metadata = {}; + this.counterparty_metadata = {}; + + const data: TBucketStorage = { + wallets: [], + tx_metadata: {}, + counterparty_metadata: {}, + }; + + let buckets = await this.getItem('data'); + buckets = JSON.parse(buckets); + buckets.push(encryption.encrypt(JSON.stringify(data), fakePassword)); + this.cachedPassword = fakePassword; + const bucketsString = JSON.stringify(buckets); + await this.setItem('data', bucketsString); + return (await this.getItem('data')) === bucketsString; + }; + + hashIt = (s: string): string => { + return uint8ArrayToHex(sha256(s)); + }; + + /** + * Returns instace of the Realm database, which is encrypted either by cached user's password OR default password. + * Database file is deterministically derived from encryption key. + */ + async getRealmForTransactions() { + const cacheFolderPath = RNFS.CachesDirectoryPath; // Path to cache folder + const password = this.hashIt(this.cachedPassword || 'fyegjitkyf[eqjnc.lf'); + const buf = hexToUint8Array(this.hashIt(password) + this.hashIt(password)); + const encryptionKey = Int8Array.from(buf); + const fileName = this.hashIt(this.hashIt(password)) + '-wallettransactions.realm'; + const path = `${cacheFolderPath}/${fileName}`; // Use cache folder path + + const schema = [ + { + name: 'WalletTransactions', + properties: { + walletid: { type: 'string', indexed: true }, + internal: 'bool?', // true - internal, false - external + index: 'int?', + tx: 'string', // stringified json + }, + }, + ]; + // @ts-ignore schema doesn't match Realm's schema type + return Realm.open({ + // @ts-ignore schema doesn't match Realm's schema type + schema, + path, + encryptionKey, + excludeFromIcloudBackup: true, + }); + } + + /** + * Returns instace of the Realm database, which is encrypted by random bytes stored in keychain. + * Database file is static. + * + * @returns {Promise} + */ + async openRealmKeyValue(): Promise { + const cacheFolderPath = RNFS.CachesDirectoryPath; // Path to cache folder + const service = 'realm_encryption_key'; + let password; + const credentials = await Keychain.getGenericPassword({ service }); + if (credentials) { + password = credentials.password; + } else { + const buf = await randomBytes(64); + password = uint8ArrayToHex(buf); + await Keychain.setGenericPassword(service, password, { service }); + } + + const buf = hexToUint8Array(password); + const encryptionKey = Int8Array.from(buf); + const path = `${cacheFolderPath}/keyvalue.realm`; // Use cache folder path + + const schema = [ + { + name: 'KeyValue', + primaryKey: 'key', + properties: { + key: { type: 'string', indexed: true }, + value: 'string', // stringified json, or whatever + }, + }, + ]; + // @ts-ignore schema doesn't match Realm's schema type + return Realm.open({ + // @ts-ignore schema doesn't match Realm's schema type + schema, + path, + encryptionKey, + excludeFromIcloudBackup: true, + }); + } + + saveToRealmKeyValue(realmkeyValue: Realm, key: string, value: any) { + realmkeyValue.write(() => { + realmkeyValue.create( + 'KeyValue', + { + key, + value, + }, + Realm.UpdateMode.Modified, + ); + }); + } + + /** + * Loads from storage all wallets and + * maps them to `this.wallets` + * + * @param password If present means storage must be decrypted before usage + * @returns {Promise.} + */ + async loadFromDisk(password?: string): Promise { + // Wrap inside a try so if anything goes wrong it wont block loadFromDisk from continuing + try { + await this.moveRealmFilesToCacheDirectory(); + } catch (error: any) { + console.warn('moveRealmFilesToCacheDirectory error:', error.message); + } + let dataRaw = await this.getItemWithFallbackToRealm('data'); + if (password) { + dataRaw = this.decryptData(dataRaw, password); + if (dataRaw) { + // password is good, cache it + this.cachedPassword = password; + } + } + if (dataRaw !== null) { + let realm; + try { + realm = await this.getRealmForTransactions(); + } catch (error: any) { + presentAlert({ message: error.message }); + } + const data: TBucketStorage = JSON.parse(dataRaw); + if (!data.wallets) return false; + const wallets = data.wallets; + for (const key of wallets) { + // deciding which type is wallet and instantiating correct object + const tempObj = JSON.parse(key); + let unserializedWallet: TWallet; + switch (tempObj.type) { + case SegwitBech32Wallet.type: + unserializedWallet = SegwitBech32Wallet.fromJson(key) as unknown as SegwitBech32Wallet; + break; + case SegwitP2SHWallet.type: + unserializedWallet = SegwitP2SHWallet.fromJson(key) as unknown as SegwitP2SHWallet; + break; + case WatchOnlyWallet.type: + unserializedWallet = WatchOnlyWallet.fromJson(key) as unknown as WatchOnlyWallet; + unserializedWallet.init(); + if (unserializedWallet.isHd() && !unserializedWallet.isXpubValid()) { + continue; + } + break; + case HDLegacyP2PKHWallet.type: + unserializedWallet = HDLegacyP2PKHWallet.fromJson(key) as unknown as HDLegacyP2PKHWallet; + break; + case HDSegwitP2SHWallet.type: + unserializedWallet = HDSegwitP2SHWallet.fromJson(key) as unknown as HDSegwitP2SHWallet; + break; + case HDSegwitBech32Wallet.type: + unserializedWallet = HDSegwitBech32Wallet.fromJson(key) as unknown as HDSegwitBech32Wallet; + break; + case HDTaprootWallet.type: + unserializedWallet = HDTaprootWallet.fromJson(key) as unknown as HDTaprootWallet; + break; + case HDLegacyBreadwalletWallet.type: + unserializedWallet = HDLegacyBreadwalletWallet.fromJson(key) as unknown as HDLegacyBreadwalletWallet; + break; + case HDLegacyElectrumSeedP2PKHWallet.type: + unserializedWallet = HDLegacyElectrumSeedP2PKHWallet.fromJson(key) as unknown as HDLegacyElectrumSeedP2PKHWallet; + break; + case HDSegwitElectrumSeedP2WPKHWallet.type: + unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key) as unknown as HDSegwitElectrumSeedP2WPKHWallet; + break; + case MultisigHDWallet.type: + unserializedWallet = MultisigHDWallet.fromJson(key) as unknown as MultisigHDWallet; + break; + case HDAezeedWallet.type: + unserializedWallet = HDAezeedWallet.fromJson(key) as unknown as HDAezeedWallet; + // migrate password to this.passphrase field + // remove this code somewhere in year 2022 + if (unserializedWallet.secret.includes(':')) { + const [mnemonic, passphrase] = unserializedWallet.secret.split(':'); + unserializedWallet.secret = mnemonic; + unserializedWallet.passphrase = passphrase; + } + + break; + case SLIP39SegwitP2SHWallet.type: + unserializedWallet = SLIP39SegwitP2SHWallet.fromJson(key) as unknown as SLIP39SegwitP2SHWallet; + break; + case SLIP39LegacyP2PKHWallet.type: + unserializedWallet = SLIP39LegacyP2PKHWallet.fromJson(key) as unknown as SLIP39LegacyP2PKHWallet; + break; + case SLIP39SegwitBech32Wallet.type: + unserializedWallet = SLIP39SegwitBech32Wallet.fromJson(key) as unknown as SLIP39SegwitBech32Wallet; + break; + case LightningArkWallet.type: + unserializedWallet = LightningArkWallet.fromJson(key) as unknown as LightningArkWallet; + break; + case LightningCustodianWallet.type: { + unserializedWallet = LightningCustodianWallet.fromJson(key) as unknown as LightningCustodianWallet; + let lndhub: false | any = false; + try { + lndhub = await getLNDHub(); + } catch (error) { + console.warn(error); + } + + if (unserializedWallet.baseURI) { + unserializedWallet.setBaseURI(unserializedWallet.baseURI); // not really necessary, just for the sake of readability + console.log('using saved uri for for ln wallet:', unserializedWallet.baseURI); + } else if (lndhub) { + console.log('using wallet-wide settings ', lndhub, 'for ln wallet'); + unserializedWallet.setBaseURI(lndhub); + } else { + console.log('wallet does not have a baseURI. Continuing init...'); + } + unserializedWallet.init(); + break; + } + case 'lightningLdk': + // since ldk wallets are deprecated and removed, we need to handle a case when such wallet still exists in storage + unserializedWallet = new HDSegwitBech32Wallet(); + unserializedWallet.setSecret(tempObj.secret.replace('ldk://', '')); + break; + case LegacyWallet.type: + default: + unserializedWallet = LegacyWallet.fromJson(key) as unknown as LegacyWallet; + break; + } + + try { + if (realm) this.inflateWalletFromRealm(realm, unserializedWallet); + } catch (error: any) { + presentAlert({ message: error.message }); + } + + // done + const ID = unserializedWallet.getID(); + if (!this.wallets.some(wallet => wallet.getID() === ID)) { + this.wallets.push(unserializedWallet); + this.tx_metadata = data.tx_metadata; + this.counterparty_metadata = data.counterparty_metadata; + } + } + if (realm) realm.close(); + return true; + } else { + return false; // failed loading data or loading/decryptin data + } + } + + /** + * Lookup wallet in list by it's secret and + * remove it from `this.wallets` + * + * @param wallet {AbstractWallet} + */ + deleteWallet = (wallet: TWallet): void => { + const ID = wallet.getID(); + const tempWallets = []; + + for (const value of this.wallets) { + if (value.getID() === ID) { + // the one we should delete + // nop + } else { + // the one we must keep + tempWallets.push(value); + } + } + this.wallets = tempWallets; + }; + + inflateWalletFromRealm(realm: Realm, walletToInflate: TWallet) { + const transactions = realm.objects('WalletTransactions'); + const transactionsForWallet = transactions.filtered(`walletid = "${walletToInflate.getID()}"`) as unknown as TRealmTransaction[]; + for (const tx of transactionsForWallet) { + if (tx.internal === false) { + if ('_hdWalletInstance' in walletToInflate && walletToInflate._hdWalletInstance) { + const hd = walletToInflate._hdWalletInstance; + hd._txs_by_external_index[tx.index] = hd._txs_by_external_index[tx.index] || []; + const transaction = JSON.parse(tx.tx); + hd._txs_by_external_index[tx.index].push(transaction); + } else { + walletToInflate._txs_by_external_index[tx.index] = walletToInflate._txs_by_external_index[tx.index] || []; + const transaction = JSON.parse(tx.tx); + (walletToInflate._txs_by_external_index[tx.index] as Transaction[]).push(transaction); + } + } else if (tx.internal === true) { + if ('_hdWalletInstance' in walletToInflate && walletToInflate._hdWalletInstance) { + const hd = walletToInflate._hdWalletInstance; + hd._txs_by_internal_index[tx.index] = hd._txs_by_internal_index[tx.index] || []; + const transaction = JSON.parse(tx.tx); + hd._txs_by_internal_index[tx.index].push(transaction); + } else { + walletToInflate._txs_by_internal_index[tx.index] = walletToInflate._txs_by_internal_index[tx.index] || []; + const transaction = JSON.parse(tx.tx); + (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 || []; + const transaction = JSON.parse(tx.tx); + (walletToInflate._txs_by_external_index as Transaction[]).push(transaction); + } + } + } + + offloadWalletToRealm(realm: Realm, wallet: TWallet): void { + 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 + const walletTransactionsToDelete = realm.objects('WalletTransactions').filtered(`walletid = '${id}'`); + 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 tx of txs) { + realm.create( + 'WalletTransactions', + { + walletid: id, + internal: false, + index: parseInt(index, 10), + tx: JSON.stringify(tx), + }, + Realm.UpdateMode.Modified, + ); + } + } + + for (const index of Object.keys(walletToSave._txs_by_internal_index)) { + // @ts-ignore index is number + const txs = walletToSave._txs_by_internal_index[index]; + for (const tx of txs) { + realm.create( + 'WalletTransactions', + { + walletid: id, + internal: true, + index: parseInt(index, 10), + tx: JSON.stringify(tx), + }, + Realm.UpdateMode.Modified, + ); + } + } + }); + } + } + + /** + * Serializes and saves to storage object data. + * If cached password is saved - finds the correct bucket + * to save to, encrypts and then saves. + * + * @returns {Promise} Result of storage save + */ + async saveToDisk(): Promise { + if (savingInProgress) { + console.warn('saveToDisk is in progress'); + if (++savingInProgress > 10) presentAlert({ message: 'Critical error. Last actions were not saved' }); // should never happen + await new Promise(resolve => setTimeout(resolve, 1000 * savingInProgress)); // sleep + return this.saveToDisk(); + } + savingInProgress = 1; + + try { + const walletsToSave: string[] = []; // serialized wallets + let realm; + try { + realm = await this.getRealmForTransactions(); + } catch (error: any) { + presentAlert({ message: error.message }); + } + for (const key of this.wallets) { + if (typeof key === 'boolean') continue; + key.prepareForSerialization(); + // @ts-ignore wtf is wallet.current? Does it even exist? + delete key.current; + const keyCloned = Object.assign({}, key); // stripped-down version of a wallet to save to secure keystore + if ('_hdWalletInstance' in key) { + const k = keyCloned as any & WatchOnlyWallet; + k._hdWalletInstance = Object.assign({}, key._hdWalletInstance); + k._hdWalletInstance._txs_by_external_index = {}; + k._hdWalletInstance._txs_by_internal_index = {}; + } + if (realm) this.offloadWalletToRealm(realm, key); + // stripping down: + if (key._txs_by_external_index) { + keyCloned._txs_by_external_index = {}; + keyCloned._txs_by_internal_index = {}; + } + + if ('_bip47_instance' in keyCloned) { + delete keyCloned._bip47_instance; // since it wont be restored into a proper class instance + } + + walletsToSave.push(JSON.stringify({ ...keyCloned, type: keyCloned.type })); + } + if (realm) realm.close(); + + let data: TBucketStorage | string[] /* either a bucket, or an array of encrypted buckets */ = { + wallets: walletsToSave, + tx_metadata: this.tx_metadata, + counterparty_metadata: this.counterparty_metadata, + }; + + if (this.cachedPassword) { + // should find the correct bucket, encrypt and then save + let buckets = await this.getItemWithFallbackToRealm('data'); + buckets = JSON.parse(buckets); + const newData: string[] = []; // serialized buckets + let num = 0; + for (const bucket of buckets) { + let decrypted; + // if we had `usedBucketNum` during loadFromDisk(), no point to try to decode each bucket to find the one we + // need, we just to find bucket with the same index + if (usedBucketNum !== false) { + if (num === usedBucketNum) { + decrypted = true; + } + num++; + } else { + // we dont have `usedBucketNum` for whatever reason, so lets try to decrypt each bucket after bucket + // till we find the right one + decrypted = encryption.decrypt(bucket, this.cachedPassword); + } + + if (!decrypted) { + // no luck decrypting, its not our bucket + newData.push(bucket); + } else { + // decrypted ok, this is our bucket + // we serialize our object's data, encrypt it, and add it to buckets + newData.push(encryption.encrypt(JSON.stringify(data), this.cachedPassword)); + } + } + + data = newData; + } + + await this.setItem('data', JSON.stringify(data)); + await this.setItem(BlueApp.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); + + // now, backing up same data in realm: + const realmkeyValue = await this.openRealmKeyValue(); + this.saveToRealmKeyValue(realmkeyValue, 'data', JSON.stringify(data)); + this.saveToRealmKeyValue(realmkeyValue, BlueApp.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); + realmkeyValue.close(); + } catch (error: any) { + console.error('save to disk exception:', error.message); + presentAlert({ message: 'save to disk exception: ' + error.message }); + if (error.message.includes('Realm file decryption failed')) { + console.warn('purging realm key-value database file'); + this.purgeRealmKeyValueFile(); + } + } finally { + savingInProgress = 0; + } + } + + /** + * For each wallet, fetches balance from remote endpoint. + * Use getter for a specific wallet to get actual balance. + * Returns void. + * If index is present then fetch only from this specific wallet + */ + fetchWalletBalances = async (index?: number): Promise => { + console.log('fetchWalletBalances for wallet#', typeof index === 'undefined' ? '(all)' : index); + if (index || index === 0) { + let c = 0; + for (const wallet of this.wallets) { + if (c++ === index) { + await wallet.fetchBalance(); + } + } + } else { + await Promise.all( + this.wallets.map(async wallet => { + console.log('fetching balance for', wallet.getLabel()); + await wallet.fetchBalance(); + }), + ); + } + }; + + /** + * Fetches from remote endpoint all transactions for each wallet. + * Returns void. + * To access transactions - get them from each respective wallet. + * If index is present then fetch only from this specific wallet. + * + * @param index {Integer} Index of the wallet in this.wallets array, + * blank to fetch from all wallets + * @return {Promise.} + */ + fetchWalletTransactions = async (index?: number) => { + console.log('fetchWalletTransactions for wallet#', typeof index === 'undefined' ? '(all)' : index); + if (index || index === 0) { + let c = 0; + for (const wallet of this.wallets) { + if (c++ === index) { + await wallet.fetchTransactions(); + + if ('fetchPendingTransactions' in wallet) { + await wallet.fetchPendingTransactions(); + await wallet.fetchUserInvoices(); + } + } + } + } else { + await Promise.all( + this.wallets.map(async wallet => { + await wallet.fetchTransactions(); + if ('fetchPendingTransactions' in wallet) { + await wallet.fetchPendingTransactions(); + await wallet.fetchUserInvoices(); + } + }), + ); + } + }; + + fetchSenderPaymentCodes = async (index?: number) => { + console.log('fetchSenderPaymentCodes for wallet#', typeof index === 'undefined' ? '(all)' : index); + if (index || index === 0) { + const wallet = this.wallets[index]; + try { + if (!(wallet.allowBIP47() && wallet.isBIP47Enabled() && 'fetchBIP47SenderPaymentCodes' in wallet)) return; + await wallet.fetchBIP47SenderPaymentCodes(); + } catch (error) { + console.error('Failed to fetch sender payment codes for wallet', index, error); + } + } else { + await Promise.all( + this.wallets.map(async wallet => { + try { + if (!(wallet.allowBIP47() && wallet.isBIP47Enabled() && 'fetchBIP47SenderPaymentCodes' in wallet)) return; + await wallet.fetchBIP47SenderPaymentCodes(); + } catch (error) { + console.error('Failed to fetch sender payment codes for wallet', wallet.label, error); + } + }), + ); + } + }; + + getWallets = (): TWallet[] => { + return this.wallets; + }; + + /** + * Getter for all transactions in all wallets. + * But if index is provided - only for wallet with corresponding index + * + * @param index {number|undefined} Wallet index in this.wallets. Empty (or undef) for all wallets. + * @param limit {number} How many txs return, starting from the earliest. Default: all of them. + * @param includeWalletsWithHideTransactionsEnabled {boolean} Wallets' _hideTransactionsInWalletsList property determines wether the user wants this wallet's txs hidden from the main list view. + */ + getTransactions = ( + index?: number, + limit: number = Infinity, + includeWalletsWithHideTransactionsEnabled: boolean = false, + ): ExtendedTransaction[] => { + if (index || index === 0) { + let txs: Transaction[] = []; + let c = 0; + for (const wallet of this.wallets) { + if (c++ === index) { + txs = txs.concat(wallet.getTransactions()); + + const txsRet: ExtendedTransaction[] = []; + const walletID = wallet.getID(); + const walletPreferredBalanceUnit = wallet.getPreferredBalanceUnit(); + txs.map(tx => + txsRet.push({ + ...tx, + walletID, + walletPreferredBalanceUnit, + }), + ); + return txsRet; + } + } + } + + const txs: ExtendedTransaction[] = []; + for (const wallet of this.wallets.filter(w => includeWalletsWithHideTransactionsEnabled || !w.getHideTransactionsInWalletsList())) { + const walletTransactions: Transaction[] = wallet.getTransactions(); + const walletID = wallet.getID(); + const walletPreferredBalanceUnit = wallet.getPreferredBalanceUnit(); + for (const t of walletTransactions) { + txs.push({ + ...t, + walletID, + walletPreferredBalanceUnit, + }); + } + } + + return txs + .sort((a, b) => { + return b.timestamp - a.timestamp; + }) + .slice(0, limit); + }; + + /** + * Getter for a sum of all balances of all wallets + */ + getBalance = (): number => { + let finalBalance = 0; + for (const wal of this.wallets) { + finalBalance += wal.getBalance(); + } + return finalBalance; + }; + + isHandoffEnabled = async (): Promise => { + try { + return !!(await AsyncStorage.getItem(BlueApp.HANDOFF_STORAGE_KEY)); + } catch (_) {} + return false; + }; + + setIsHandoffEnabled = async (value: boolean): Promise => { + await AsyncStorage.setItem(BlueApp.HANDOFF_STORAGE_KEY, value ? '1' : ''); + }; + + isDoNotTrackEnabled = async (): Promise => { + try { + const keyExists = await AsyncStorage.getItem(BlueApp.DO_NOT_TRACK); + if (keyExists !== null) { + const doNotTrackValue = !!keyExists; + if (doNotTrackValue) { + await DefaultPreference.set(BlueApp.DO_NOT_TRACK, '1'); + AsyncStorage.removeItem(BlueApp.DO_NOT_TRACK); + } else { + return Boolean(await DefaultPreference.get(BlueApp.DO_NOT_TRACK)); + } + } + } catch (_) {} + const doNotTrackValue = await DefaultPreference.get(BlueApp.DO_NOT_TRACK); + return doNotTrackValue === '1' || false; + }; + + setDoNotTrack = async (value: boolean) => { + if (value) { + await DefaultPreference.set(BlueApp.DO_NOT_TRACK, '1'); + } else { + await DefaultPreference.clear(BlueApp.DO_NOT_TRACK); + } + }; + + /** + * Simple async sleeper function + */ + sleep = (ms: number): Promise => { + return new Promise(resolve => setTimeout(resolve, ms)); + }; + + purgeRealmKeyValueFile() { + const path = 'keyvalue.realm'; + return Realm.deleteFile({ + path, + }); + } + + async moveRealmFilesToCacheDirectory() { + const documentPath = RNFS.DocumentDirectoryPath; // Path to documentPath folder + const cachePath = RNFS.CachesDirectoryPath; // Path to cachePath folder + try { + if (!(await RNFS.exists(documentPath))) return; // If the documentPath directory does not exist, return (nothing to move) + const files = await RNFS.readDir(documentPath); // Read all files in documentPath directory + if (Array.isArray(files) && files.length === 0) return; // If there are no files, return (nothing to move) + const appRealmFiles = files.filter( + file => file.name.endsWith('.realm') || file.name.endsWith('.realm.lock') || file.name.includes('.realm.management'), + ); + + for (const file of appRealmFiles) { + const filePath = `${documentPath}/${file.name}`; + const newFilePath = `${cachePath}/${file.name}`; + const fileExists = await RNFS.exists(filePath); // Check if the file exists + const cacheFileExists = await RNFS.exists(newFilePath); // Check if the file already exists in the cache directory + + if (fileExists) { + if (cacheFileExists) { + await RNFS.unlink(newFilePath); // Delete the file in the cache directory if it exists + console.log(`Existing file removed from cache: ${newFilePath}`); + } + await RNFS.moveFile(filePath, newFilePath); // Move the file + console.log(`Moved Realm file: ${filePath} to ${newFilePath}`); + } else { + console.log(`File does not exist: ${filePath}`); + } + } + } catch (error) { + console.error('Error moving Realm files:', error); + throw new Error(`Error moving Realm files: ${(error as Error).message}`); + } + } +} diff --git a/class/camera.js b/class/camera.js deleted file mode 100644 index d6baab18041..00000000000 --- a/class/camera.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Linking, Alert } from 'react-native'; -import { getSystemName } from 'react-native-device-info'; -import loc from '../loc'; - -const isDesktop = getSystemName() === 'Mac OS X'; - -export const openPrivacyDesktopSettings = () => { - if (isDesktop) { - Linking.openURL('x-apple.systempreferences:com.apple.preference.security?Privacy_Camera'); - } else { - Linking.openSettings(); - } -}; - -export const presentCameraNotAuthorizedAlert = error => { - Alert.alert( - loc.errors.error, - error, - [ - { - text: loc.send.open_settings, - onPress: openPrivacyDesktopSettings, - style: 'default', - }, - { - text: loc._.ok, - onPress: () => {}, - style: 'cancel', - }, - ], - { cancelable: true }, - ); -}; diff --git a/class/camera.ts b/class/camera.ts new file mode 100644 index 00000000000..e9c5116796f --- /dev/null +++ b/class/camera.ts @@ -0,0 +1,32 @@ +import { Alert, Linking } from 'react-native'; + +import { isDesktop } from '../blue_modules/environment'; +import loc from '../loc'; + +export const openPrivacyDesktopSettings = () => { + if (isDesktop) { + Linking.openURL('x-apple.systempreferences:com.apple.preference.security?Privacy_Camera'); + } else { + Linking.openSettings(); + } +}; + +export const presentCameraNotAuthorizedAlert = (error: string) => { + Alert.alert( + loc.errors.error, + error, + [ + { + text: loc.send.open_settings, + onPress: openPrivacyDesktopSettings, + style: 'default', + }, + { + text: loc._.ok, + onPress: () => {}, + style: 'cancel', + }, + ], + { cancelable: true }, + ); +}; diff --git a/class/contact-list.ts b/class/contact-list.ts new file mode 100644 index 00000000000..28fa673d42c --- /dev/null +++ b/class/contact-list.ts @@ -0,0 +1,43 @@ +import BIP47Factory from '@spsina/bip47'; + +import { SilentPayment } from 'silent-payments'; + +import ecc from '../blue_modules/noble_ecc'; +import { concatUint8Arrays } from '../blue_modules/uint8array-extras'; +import * as bitcoin from 'bitcoinjs-lib'; + +export class ContactList { + isBip47PaymentCodeValid(pc: string) { + try { + BIP47Factory(ecc).fromPaymentCode(pc); + return true; + } catch (_) { + return false; + } + } + + isBip352PaymentCodeValid(pc: string) { + return SilentPayment.isPaymentCodeValid(pc); + } + + isPaymentCodeValid(pc: string): boolean { + return this.isBip47PaymentCodeValid(pc) || this.isBip352PaymentCodeValid(pc); + } + + isAddressValid(address: string): boolean { + try { + bitcoin.address.toOutputScript(address); // throws, no? + + if (!address.toLowerCase().startsWith('bc1')) return true; + const decoded = bitcoin.address.fromBech32(address); + if (decoded.version === 0) return true; + if (decoded.version === 1 && decoded.data.length !== 32) return false; + if (decoded.version === 1 && !ecc.isPoint(concatUint8Arrays([new Uint8Array([2]), decoded.data]))) return false; + if (decoded.version > 1) return false; + // ^^^ some day, when versions above 1 will be actually utilized, we would need to unhardcode this + return true; + } catch (e) { + return false; + } + } +} diff --git a/class/deeplink-schema-match.js b/class/deeplink-schema-match.js deleted file mode 100644 index eb1fd38e76b..00000000000 --- a/class/deeplink-schema-match.js +++ /dev/null @@ -1,465 +0,0 @@ -import { LightningCustodianWallet, WatchOnlyWallet } from './'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import RNFS from 'react-native-fs'; -import URL from 'url'; -import { Chain } from '../models/bitcoinUnits'; -import Lnurl from './lnurl'; -import Azteco from './azteco'; -const bitcoin = require('bitcoinjs-lib'); -const bip21 = require('bip21'); -const BlueApp = require('../BlueApp'); -const AppStorage = BlueApp.AppStorage; - -class DeeplinkSchemaMatch { - static hasSchema(schemaString) { - if (typeof schemaString !== 'string' || schemaString.length <= 0) return false; - const lowercaseString = schemaString.trim().toLowerCase(); - return ( - lowercaseString.startsWith('bitcoin:') || - lowercaseString.startsWith('lightning:') || - lowercaseString.startsWith('blue:') || - lowercaseString.startsWith('bluewallet:') || - lowercaseString.startsWith('lapp:') - ); - } - - /** - * Examines the content of the event parameter. - * If the content is recognizable, create a dictionary with the respective - * navigation dictionary required by react-navigation - * - * @param event {{url: string}} URL deeplink as passed to app, e.g. `bitcoin:bc1qh6tf004ty7z7un2v5ntu4mkf630545gvhs45u7?amount=666&label=Yo` - * @param completionHandler {function} Callback that returns [string, params: object] - */ - static navigationRouteFor(event, completionHandler, context = { wallets: [], saveToDisk: () => {}, addWallet: () => {} }) { - if (event.url === null) { - return; - } - if (typeof event.url !== 'string') { - return; - } - - if (event.url.toLowerCase().startsWith('bluewallet:bitcoin:') || event.url.toLowerCase().startsWith('bluewallet:lightning:')) { - event.url = event.url.substring(11); - } else if (event.url.toLocaleLowerCase().startsWith('bluewallet://widget?action=')) { - event.url = event.url.substring('bluewallet://'.length); - } - - if (DeeplinkSchemaMatch.isWidgetAction(event.url)) { - if (context.wallets.length >= 0) { - const wallet = context.wallets[0]; - const action = event.url.split('widget?action=')[1]; - if (wallet.chain === Chain.ONCHAIN) { - if (action === 'openSend') { - completionHandler([ - 'SendDetailsRoot', - { - screen: 'SendDetails', - params: { - walletID: wallet.getID(), - }, - }, - ]); - } else if (action === 'openReceive') { - completionHandler([ - 'ReceiveDetailsRoot', - { - screen: 'ReceiveDetails', - params: { - walletID: wallet.getID(), - }, - }, - ]); - } - } else if (wallet.chain === Chain.OFFCHAIN) { - if (action === 'openSend') { - completionHandler([ - 'ScanLndInvoiceRoot', - { - screen: 'ScanLndInvoice', - params: { - walletID: wallet.getID(), - }, - }, - ]); - } else if (action === 'openReceive') { - completionHandler(['LNDCreateInvoiceRoot', { screen: 'LNDCreateInvoice', params: { walletID: wallet.getID() } }]); - } - } - } - } else if (DeeplinkSchemaMatch.isPossiblySignedPSBTFile(event.url)) { - RNFS.readFile(decodeURI(event.url)) - .then(file => { - if (file) { - completionHandler([ - 'SendDetailsRoot', - { - screen: 'PsbtWithHardwareWallet', - params: { - deepLinkPSBT: file, - }, - }, - ]); - } - }) - .catch(e => console.warn(e)); - return; - } - let isBothBitcoinAndLightning; - try { - isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(event.url); - } catch (e) { - console.log(e); - } - if (isBothBitcoinAndLightning) { - completionHandler([ - 'SelectWallet', - { - onWalletSelect: (wallet, { navigation }) => { - navigation.pop(); // close select wallet screen - navigation.navigate(...DeeplinkSchemaMatch.isBothBitcoinAndLightningOnWalletSelect(wallet, isBothBitcoinAndLightning)); - }, - }, - ]); - } else if (DeeplinkSchemaMatch.isBitcoinAddress(event.url)) { - completionHandler([ - 'SendDetailsRoot', - { - screen: 'SendDetails', - params: { - uri: event.url.replace('://', ':'), - }, - }, - ]); - } else if (DeeplinkSchemaMatch.isLightningInvoice(event.url)) { - completionHandler([ - 'ScanLndInvoiceRoot', - { - screen: 'ScanLndInvoice', - params: { - uri: event.url.replace('://', ':'), - }, - }, - ]); - } else if (DeeplinkSchemaMatch.isLnUrl(event.url)) { - // at this point we can not tell if it is lnurl-pay or lnurl-withdraw since it needs additional async call - // to the server, which is undesirable here, so LNDCreateInvoice screen will handle it for us and will - // redirect user to LnurlPay screen if necessary - completionHandler([ - 'LNDCreateInvoiceRoot', - { - screen: 'LNDCreateInvoice', - params: { - uri: event.url.replace('lightning:', '').replace('LIGHTNING:', ''), - }, - }, - ]); - } else if (Lnurl.isLightningAddress(event.url)) { - // this might be not just an email but a lightning addres - // @see https://lightningaddress.com - completionHandler([ - 'ScanLndInvoiceRoot', - { - screen: 'ScanLndInvoice', - params: { - uri: event.url, - }, - }, - ]); - } else if (Azteco.isRedeemUrl(event.url)) { - completionHandler([ - 'AztecoRedeemRoot', - { - screen: 'AztecoRedeem', - params: Azteco.getParamsFromUrl(event.url), - }, - ]); - } else if (new WatchOnlyWallet().setSecret(event.url).init().valid()) { - completionHandler([ - 'AddWalletRoot', - { - screen: 'ImportWallet', - params: { - triggerImport: true, - label: event.url, - }, - }, - ]); - } else { - const urlObject = URL.parse(event.url, true); // eslint-disable-line n/no-deprecated-api - (async () => { - if (urlObject.protocol === 'bluewallet:' || urlObject.protocol === 'lapp:' || urlObject.protocol === 'blue:') { - switch (urlObject.host) { - case 'openlappbrowser': { - console.log('opening LAPP', urlObject.query.url); - // searching for LN wallet: - let haveLnWallet = false; - for (const w of context.wallets) { - if (w.type === LightningCustodianWallet.type) { - haveLnWallet = true; - } - } - - if (!haveLnWallet) { - // need to create one - const w = new LightningCustodianWallet(); - w.setLabel(w.typeReadable); - - try { - const lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB); - if (lndhub) { - w.setBaseURI(lndhub); - w.init(); - } - await w.createAccount(); - await w.authorize(); - } catch (Err) { - // giving up, not doing anything - return; - } - context.addWallet(w); - context.saveToDisk(); - } - - // now, opening lapp browser and navigating it to URL. - // looking for a LN wallet: - let lnWallet; - for (const w of context.wallets) { - if (w.type === LightningCustodianWallet.type) { - lnWallet = w; - break; - } - } - - if (!lnWallet) { - // something went wrong - return; - } - - completionHandler([ - 'LappBrowserRoot', - { - screen: 'LappBrowser', - params: { - walletID: lnWallet.getID(), - url: urlObject.query.url, - }, - }, - ]); - break; - } - case 'setelectrumserver': - completionHandler([ - 'ElectrumSettings', - { - server: DeeplinkSchemaMatch.getServerFromSetElectrumServerAction(event.url), - }, - ]); - break; - case 'setlndhuburl': - completionHandler([ - 'LightningSettings', - { - url: DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction(event.url), - }, - ]); - break; - } - } - })(); - } - } - - /** - * Extracts server from a deeplink like `bluewallet:setelectrumserver?server=electrum1.bluewallet.io%3A443%3As` - * returns FALSE if none found - * - * @param url {string} - * @return {string|boolean} - */ - static getServerFromSetElectrumServerAction(url) { - if (!url.startsWith('bluewallet:setelectrumserver') && !url.startsWith('setelectrumserver')) return false; - const splt = url.split('server='); - if (splt[1]) return decodeURIComponent(splt[1]); - return false; - } - - /** - * Extracts url from a deeplink like `bluewallet:setlndhuburl?url=https%3A%2F%2Flndhub.herokuapp.com` - * returns FALSE if none found - * - * @param url {string} - * @return {string|boolean} - */ - static getUrlFromSetLndhubUrlAction(url) { - if (!url.startsWith('bluewallet:setlndhuburl') && !url.startsWith('setlndhuburl')) return false; - const splt = url.split('url='); - if (splt[1]) return decodeURIComponent(splt[1]); - return false; - } - - static isTXNFile(filePath) { - return ( - (filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) && - filePath.toLowerCase().endsWith('.txn') - ); - } - - static isPossiblySignedPSBTFile(filePath) { - return ( - (filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) && - filePath.toLowerCase().endsWith('-signed.psbt') - ); - } - - static isPossiblyPSBTFile(filePath) { - return ( - (filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) && - filePath.toLowerCase().endsWith('.psbt') - ); - } - - static isBothBitcoinAndLightningOnWalletSelect(wallet, uri) { - if (wallet.chain === Chain.ONCHAIN) { - return [ - 'SendDetailsRoot', - { - screen: 'SendDetails', - params: { - uri: uri.bitcoin, - walletID: wallet.getID(), - }, - }, - ]; - } else if (wallet.chain === Chain.OFFCHAIN) { - return [ - 'ScanLndInvoiceRoot', - { - screen: 'ScanLndInvoice', - params: { - uri: uri.lndInvoice, - walletID: wallet.getID(), - }, - }, - ]; - } - } - - static isBitcoinAddress(address) { - address = address.replace('://', ':').replace('bitcoin:', '').replace('BITCOIN:', '').replace('bitcoin=', '').split('?')[0]; - let isValidBitcoinAddress = false; - try { - bitcoin.address.toOutputScript(address); - isValidBitcoinAddress = true; - } catch (err) { - isValidBitcoinAddress = false; - } - return isValidBitcoinAddress; - } - - static isLightningInvoice(invoice) { - let isValidLightningInvoice = false; - if ( - invoice.toLowerCase().startsWith('lightning:lnb') || - invoice.toLowerCase().startsWith('lightning://lnb') || - invoice.toLowerCase().startsWith('lnb') - ) { - isValidLightningInvoice = true; - } - return isValidLightningInvoice; - } - - static isLnUrl(text) { - return Lnurl.isLnurl(text); - } - - static isWidgetAction(text) { - return text.startsWith('widget?action='); - } - - static isBothBitcoinAndLightning(url) { - if (url.includes('lightning') && (url.includes('bitcoin') || url.includes('BITCOIN'))) { - const txInfo = url.split(/(bitcoin:\/\/|BITCOIN:\/\/|bitcoin:|BITCOIN:|lightning:|lightning=|bitcoin=)+/); - let btc; - let lndInvoice; - for (const [index, value] of txInfo.entries()) { - try { - // Inside try-catch. We dont wan't to crash in case of an out-of-bounds error. - if (value.startsWith('bitcoin') || value.startsWith('BITCOIN')) { - btc = `bitcoin:${txInfo[index + 1]}`; - if (!DeeplinkSchemaMatch.isBitcoinAddress(btc)) { - btc = false; - break; - } - } else if (value.startsWith('lightning')) { - const lnpart = txInfo[index + 1].split('&').find(el => el.toLowerCase().startsWith('ln')); - lndInvoice = `lightning:${lnpart}`; - if (!this.isLightningInvoice(lndInvoice)) { - lndInvoice = false; - break; - } - } - } catch (e) { - console.log(e); - } - if (btc && lndInvoice) break; - } - if (btc && lndInvoice) { - return { bitcoin: btc, lndInvoice }; - } else { - return undefined; - } - } - return undefined; - } - - static bip21decode(uri) { - if (!uri) return {}; - let replacedUri = uri; - for (const replaceMe of ['BITCOIN://', 'bitcoin://', 'BITCOIN:']) { - replacedUri = replacedUri.replace(replaceMe, 'bitcoin:'); - } - - return bip21.decode(replacedUri); - } - - static bip21encode() { - const argumentsArray = Array.from(arguments); - for (const argument of argumentsArray) { - if (String(argument.label).replace(' ', '').length === 0) { - delete argument.label; - } - if (!(Number(argument.amount) > 0)) { - delete argument.amount; - } - } - return bip21.encode.apply(bip21, argumentsArray); - } - - static decodeBitcoinUri(uri) { - let amount = ''; - let parsedBitcoinUri = null; - let address = uri || ''; - let memo = ''; - let payjoinUrl = ''; - try { - parsedBitcoinUri = DeeplinkSchemaMatch.bip21decode(uri); - address = 'address' in parsedBitcoinUri ? parsedBitcoinUri.address : address; - if ('options' in parsedBitcoinUri) { - if ('amount' in parsedBitcoinUri.options) { - amount = parsedBitcoinUri.options.amount.toString(); - amount = parsedBitcoinUri.options.amount; - } - if ('label' in parsedBitcoinUri.options) { - memo = parsedBitcoinUri.options.label || memo; - } - if ('pj' in parsedBitcoinUri.options) { - payjoinUrl = parsedBitcoinUri.options.pj; - } - } - } catch (_) {} - return { address, amount, memo, payjoinUrl }; - } -} - -export default DeeplinkSchemaMatch; diff --git a/class/deeplink-schema-match.ts b/class/deeplink-schema-match.ts new file mode 100644 index 00000000000..214ae7907c7 --- /dev/null +++ b/class/deeplink-schema-match.ts @@ -0,0 +1,440 @@ +import bip21, { TOptions } from 'bip21'; +import * as bitcoin from 'bitcoinjs-lib'; +import URL from 'url'; +import { readFileOutsideSandbox } from '../blue_modules/fs'; +import { Chain } from '../models/bitcoinUnits'; +import { WatchOnlyWallet } from './wallets/watch-only-wallet'; +import Azteco from './azteco'; +import Lnurl from './lnurl'; +import type { TWallet } from './wallets/types'; + +type TCompletionHandlerParams = [string, object]; +type TContext = { + wallets: TWallet[]; + saveToDisk: () => void; + addWallet: (wallet: TWallet) => void; + setSharedCosigner: (cosigner: string) => void; +}; + +type TBothBitcoinAndLightning = { bitcoin: string; lndInvoice: string } | undefined; + +class DeeplinkSchemaMatch { + static hasSchema(schemaString: string): boolean { + if (typeof schemaString !== 'string' || schemaString.length <= 0) return false; + const lowercaseString = schemaString.trim().toLowerCase(); + return ( + lowercaseString.startsWith('bitcoin:') || + lowercaseString.startsWith('lightning:') || + lowercaseString.startsWith('blue:') || + lowercaseString.startsWith('bluewallet:') || + lowercaseString.startsWith('lapp:') + ); + } + + /** + * Examines the content of the event parameter. + * If the content is recognizable, create a dictionary with the respective + * navigation dictionary required by react-navigation + * + * @param event {{url: string}} URL deeplink as passed to app, e.g. `bitcoin:bc1qh6tf004ty7z7un2v5ntu4mkf630545gvhs45u7?amount=666&label=Yo` + * @param completionHandler {function} Callback that returns [string, params: object] + */ + static navigationRouteFor( + event: { url: string }, + completionHandler: (args: TCompletionHandlerParams) => void, + context: TContext = { wallets: [], saveToDisk: () => {}, addWallet: () => {}, setSharedCosigner: () => {} }, + ) { + if (event.url === null) { + return; + } + if (typeof event.url !== 'string') { + return; + } + + if (event.url.toLowerCase().startsWith('bluewallet:bitcoin:') || event.url.toLowerCase().startsWith('bluewallet:lightning:')) { + event.url = event.url.substring(11); + } else if (event.url.toLocaleLowerCase().startsWith('bluewallet://widget?action=')) { + event.url = event.url.substring('bluewallet://'.length); + } + + if (DeeplinkSchemaMatch.isWidgetAction(event.url)) { + if (context.wallets.length >= 0) { + const wallet = context.wallets[0]; + const action = event.url.split('widget?action=')[1]; + if (wallet.chain === Chain.ONCHAIN) { + if (action === 'openSend') { + completionHandler([ + 'SendDetailsRoot', + { + screen: 'SendDetails', + params: { + walletID: wallet.getID(), + }, + }, + ]); + } else if (action === 'openReceive') { + completionHandler([ + 'DetailViewStackScreensStack', + { + screen: 'ReceiveDetails', + params: { + walletID: wallet.getID(), + }, + }, + ]); + } + } else if (wallet.chain === Chain.OFFCHAIN) { + if (action === 'openSend') { + completionHandler([ + 'ScanLNDInvoiceRoot', + { + screen: 'ScanLNDInvoice', + params: { + walletID: wallet.getID(), + }, + }, + ]); + } else if (action === 'openReceive') { + completionHandler(['LNDCreateInvoiceRoot', { screen: 'LNDCreateInvoice', params: { walletID: wallet.getID() } }]); + } + } + } + } else if (DeeplinkSchemaMatch.isPossiblyPSBTFile(event.url)) { + readFileOutsideSandbox(decodeURI(event.url)) + .then(file => { + if (file) { + completionHandler([ + 'SendDetailsRoot', + { + screen: 'PsbtWithHardwareWallet', + params: { + deepLinkPSBT: file, + }, + }, + ]); + } + }) + .catch(e => console.warn(e)); + return; + } else if (DeeplinkSchemaMatch.isPossiblyCosignerFile(event.url)) { + readFileOutsideSandbox(decodeURI(event.url)) + .then(file => { + // checks whether the necessary json keys are present in order to set a cosigner, + // doesn't validate the values this happens later + if (!file || !this.hasNeededJsonKeysForMultiSigSharing(file)) { + return; + } + context.setSharedCosigner(file); + }) + .catch(e => console.warn(e)); + } + let isBothBitcoinAndLightning: TBothBitcoinAndLightning; + try { + isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(event.url); + } catch (e) { + console.log(e); + } + if (isBothBitcoinAndLightning) { + completionHandler([ + 'SelectWallet', + { + onWalletSelect: (wallet: TWallet, { navigation }: any) => { + navigation.pop(); // close select wallet screen + navigation.navigate(...DeeplinkSchemaMatch.isBothBitcoinAndLightningOnWalletSelect(wallet, isBothBitcoinAndLightning)); + }, + }, + ]); + } else if (DeeplinkSchemaMatch.isBitcoinAddress(event.url)) { + completionHandler([ + 'SendDetailsRoot', + { + screen: 'SendDetails', + params: { + uri: event.url.replace('://', ':'), + }, + }, + ]); + } else if (DeeplinkSchemaMatch.isLightningInvoice(event.url)) { + completionHandler([ + 'ScanLNDInvoiceRoot', + { + screen: 'ScanLNDInvoice', + params: { + uri: event.url.replace('://', ':'), + }, + }, + ]); + } else if (DeeplinkSchemaMatch.isLnUrl(event.url)) { + // at this point we can not tell if it is lnurl-pay or lnurl-withdraw since it needs additional async call + // to the server, which is undesirable here, so LNDCreateInvoice screen will handle it for us and will + // redirect user to LnurlPay screen if necessary + completionHandler([ + 'LNDCreateInvoiceRoot', + { + screen: 'LNDCreateInvoice', + params: { + uri: event.url.replace('lightning:', '').replace('LIGHTNING:', ''), + }, + }, + ]); + } else if (Lnurl.isLightningAddress(event.url)) { + // this might be not just an email but a lightning address + // @see https://lightningaddress.com + completionHandler([ + 'ScanLNDInvoiceRoot', + { + screen: 'ScanLNDInvoice', + params: { + uri: event.url, + }, + }, + ]); + } else if (Azteco.isRedeemUrl(event.url)) { + completionHandler([ + 'AztecoRedeemRoot', + { + screen: 'AztecoRedeem', + params: Azteco.getParamsFromUrl(event.url), + }, + ]); + } else if (new WatchOnlyWallet().setSecret(event.url).init().valid()) { + completionHandler([ + 'AddWalletRoot', + { + screen: 'ImportWallet', + params: { + triggerImport: true, + label: event.url, + }, + }, + ]); + } else { + const urlObject = URL.parse(event.url, true); // eslint-disable-line n/no-deprecated-api + (async () => { + if (urlObject.protocol === 'bluewallet:' || urlObject.protocol === 'lapp:' || urlObject.protocol === 'blue:') { + switch (urlObject.host) { + case 'setelectrumserver': + completionHandler([ + 'ElectrumSettings', + { + server: DeeplinkSchemaMatch.getServerFromSetElectrumServerAction(event.url), + }, + ]); + break; + case 'setlndhuburl': + completionHandler([ + 'LightningSettings', + { + url: DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction(event.url), + }, + ]); + break; + } + } + })(); + } + } + + /** + * Extracts server from a deeplink like `bluewallet:setelectrumserver?server=electrum1.bluewallet.io%3A443%3As` + * returns FALSE if none found + * + * @param url {string} + * @return {string|boolean} + */ + static getServerFromSetElectrumServerAction(url: string): string | false { + if (!url.startsWith('bluewallet:setelectrumserver') && !url.startsWith('setelectrumserver')) return false; + const splt = url.split('server='); + if (splt[1]) return decodeURIComponent(splt[1]); + return false; + } + + /** + * Extracts url from a deeplink like `bluewallet:setlndhuburl?url=https%3A%2F%2Flndhub.herokuapp.com` + * returns FALSE if none found + * + * @param url {string} + * @return {string|boolean} + */ + static getUrlFromSetLndhubUrlAction(url: string): string | false { + if (!url.startsWith('bluewallet:setlndhuburl') && !url.startsWith('setlndhuburl')) return false; + const splt = url.split('url='); + if (splt[1]) return decodeURIComponent(splt[1]); + return false; + } + + static isTXNFile(filePath: string): boolean { + return filePath.toLowerCase().endsWith('.txn'); + } + + static isPossiblyPSBTFile(filePath: string): boolean { + return filePath.toLowerCase().endsWith('.psbt'); + } + + static isPossiblyCosignerFile(filePath: string): boolean { + return filePath.toLowerCase().endsWith('.bwcosigner'); + } + + static isBothBitcoinAndLightningOnWalletSelect(wallet: TWallet, uri: any): TCompletionHandlerParams { + if (wallet.chain === Chain.ONCHAIN) { + return [ + 'SendDetailsRoot', + { + screen: 'SendDetails', + params: { + uri: uri.bitcoin, + walletID: wallet.getID(), + }, + }, + ]; + } else { + return [ + 'ScanLNDInvoiceRoot', + { + screen: 'ScanLNDInvoice', + params: { + uri: uri.lndInvoice, + walletID: wallet.getID(), + }, + }, + ]; + } + } + + static isBitcoinAddress(address: string): boolean { + address = address.replace('://', ':').replace('bitcoin:', '').replace('BITCOIN:', '').replace('bitcoin=', '').split('?')[0]; + let isValidBitcoinAddress = false; + try { + bitcoin.address.toOutputScript(address); + isValidBitcoinAddress = true; + } catch (err) { + isValidBitcoinAddress = false; + } + return isValidBitcoinAddress; + } + + static isLightningInvoice(invoice: string): boolean { + let isValidLightningInvoice = false; + if ( + invoice.toLowerCase().startsWith('lightning:lnb') || + invoice.toLowerCase().startsWith('lightning://lnb') || + invoice.toLowerCase().startsWith('lnb') + ) { + isValidLightningInvoice = true; + } + return isValidLightningInvoice; + } + + static isLnUrl(text: string): boolean { + return Lnurl.isLnurl(text); + } + + static isWidgetAction(text: string): boolean { + return text.startsWith('widget?action='); + } + + static hasNeededJsonKeysForMultiSigSharing(str: string): boolean { + let obj; + + // Check if it's a valid JSON + try { + obj = JSON.parse(str); + } catch (e) { + return false; + } + + // Check for the existence and type of the keys + return typeof obj.xfp === 'string' && typeof obj.xpub === 'string' && typeof obj.path === 'string'; + } + + static isBothBitcoinAndLightning(url: string): TBothBitcoinAndLightning { + if (url.includes('lightning') && (url.includes('bitcoin') || url.includes('BITCOIN'))) { + const txInfo = url.split(/(bitcoin:\/\/|BITCOIN:\/\/|bitcoin:|BITCOIN:|lightning:|lightning=|bitcoin=)+/); + let btc: string | false = false; + let lndInvoice: string | false = false; + for (const [index, value] of txInfo.entries()) { + try { + // Inside try-catch. We dont wan't to crash in case of an out-of-bounds error. + if (value.startsWith('bitcoin') || value.startsWith('BITCOIN')) { + btc = `bitcoin:${txInfo[index + 1]}`; + if (!DeeplinkSchemaMatch.isBitcoinAddress(btc)) { + btc = false; + break; + } + } else if (value.startsWith('lightning')) { + const lnpart = txInfo[index + 1].split('&').find(el => el.toLowerCase().startsWith('ln')); + lndInvoice = `lightning:${lnpart}`; + if (!this.isLightningInvoice(lndInvoice)) { + lndInvoice = false; + break; + } + } + } catch (e) { + console.log(e); + } + if (btc && lndInvoice) break; + } + if (btc && lndInvoice) { + return { bitcoin: btc, lndInvoice }; + } else { + return undefined; + } + } + return undefined; + } + + static bip21decode(uri?: string) { + if (!uri) { + throw new Error('No URI provided'); + } + let replacedUri = uri; + for (const replaceMe of ['BITCOIN://', 'bitcoin://', 'BITCOIN:']) { + replacedUri = replacedUri.replace(replaceMe, 'bitcoin:'); + } + + return bip21.decode(replacedUri); + } + + static bip21encode(address: string, options?: TOptions): string { + // uppercase address if bech32 to satisfy BIP_0173 + const isBech32 = address.startsWith('bc1'); + if (isBech32) { + address = address.toUpperCase(); + } + + for (const key in options) { + if (key === 'label' && String(options[key]).replace(' ', '').length === 0) { + delete options[key]; + } + if (key === 'amount' && !(Number(options[key]) > 0)) { + delete options[key]; + } + } + return bip21.encode(address, options); + } + + static decodeBitcoinUri(uri: string) { + let amount; + let address = uri || ''; + let memo = ''; + let payjoinUrl = ''; + try { + const parsedBitcoinUri = DeeplinkSchemaMatch.bip21decode(uri); + address = parsedBitcoinUri.address ? parsedBitcoinUri.address.toString() : address; + if ('options' in parsedBitcoinUri) { + if (parsedBitcoinUri.options.amount) { + amount = Number(parsedBitcoinUri.options.amount); + } + if (parsedBitcoinUri.options.label) { + memo = parsedBitcoinUri.options.label; + } + if (parsedBitcoinUri.options.pj) { + payjoinUrl = parsedBitcoinUri.options.pj; + } + } + } catch (_) {} + return { address, amount, memo, payjoinUrl }; + } +} + +export default DeeplinkSchemaMatch; diff --git a/class/hd-segwit-bech32-transaction.js b/class/hd-segwit-bech32-transaction.js deleted file mode 100644 index 758a120a9ab..00000000000 --- a/class/hd-segwit-bech32-transaction.js +++ /dev/null @@ -1,371 +0,0 @@ -import { HDSegwitBech32Wallet } from './wallets/hd-segwit-bech32-wallet'; -import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet'; -const bitcoin = require('bitcoinjs-lib'); -const BlueElectrum = require('../blue_modules/BlueElectrum'); -const reverse = require('buffer-reverse'); -const BigNumber = require('bignumber.js'); - -/** - * Represents transaction of a BIP84 wallet. - * Helpers for RBF, CPFP etc. - */ -export class HDSegwitBech32Transaction { - /** - * @param txhex {string|null} Object is initialized with txhex - * @param txid {string|null} If txhex not present - txid whould be present - * @param wallet {HDSegwitBech32Wallet|null} If set - a wallet object to which transacton belongs - */ - constructor(txhex, txid, wallet) { - if (!txhex && !txid) throw new Error('Bad arguments'); - this._txhex = txhex; - this._txid = txid; - - if (wallet) { - if (wallet.type === HDSegwitBech32Wallet.type) { - /** @type {HDSegwitBech32Wallet} */ - this._wallet = wallet; - } else { - throw new Error('Only HD Bech32 wallets supported'); - } - } - - if (this._txhex) this._txDecoded = bitcoin.Transaction.fromHex(this._txhex); - this._remoteTx = null; - } - - /** - * If only txid present - we fetch hex - * - * @returns {Promise} - * @private - */ - async _fetchTxhexAndDecode() { - const hexes = await BlueElectrum.multiGetTransactionByTxid([this._txid], 10, false); - this._txhex = hexes[this._txid]; - if (!this._txhex) throw new Error("Transaction can't be found in mempool"); - this._txDecoded = bitcoin.Transaction.fromHex(this._txhex); - } - - /** - * Returns max used sequence for this transaction. Next RBF transaction - * should have this sequence + 1 - * - * @returns {Promise} - */ - async getMaxUsedSequence() { - if (!this._txDecoded) await this._fetchTxhexAndDecode(); - - let max = 0; - for (const inp of this._txDecoded.ins) { - max = Math.max(inp.sequence, max); - } - - return max; - } - - /** - * Basic check that Sequence num for this TX is replaceable - * - * @returns {Promise} - */ - async isSequenceReplaceable() { - return (await this.getMaxUsedSequence()) < bitcoin.Transaction.DEFAULT_SEQUENCE; - } - - /** - * If internal extended tx data not set - this is a method - * to fetch and set this data from electrum. Its different data from - * decoded hex - it contains confirmations etc. - * - * @returns {Promise} - * @private - */ - async _fetchRemoteTx() { - const result = await BlueElectrum.multiGetTransactionByTxid([this._txid || this._txDecoded.getId()]); - this._remoteTx = Object.values(result)[0]; - } - - /** - * Fetches from electrum actual confirmations number for this tx - * - * @returns {Promise} - */ - async getRemoteConfirmationsNum() { - if (!this._remoteTx) await this._fetchRemoteTx(); - return this._remoteTx.confirmations || 0; // stupid undefined - } - - /** - * Checks that tx belongs to a wallet and also - * tx value is < 0, which means its a spending transaction - * definately initiated by us, can be RBF'ed. - * - * @returns {Promise} - */ - async isOurTransaction() { - if (!this._wallet) throw new Error('Wallet required for this method'); - let found = false; - for (const tx of this._wallet.getTransactions()) { - if (tx.txid === (this._txid || this._txDecoded.getId())) { - // its our transaction, and its spending transaction, which means we initiated it - if (tx.value < 0) found = true; - } - } - return found; - } - - /** - * Checks that tx belongs to a wallet and also - * tx value is > 0, which means its a receiving transaction and thus - * can be CPFP'ed. - * - * @returns {Promise} - */ - async isToUsTransaction() { - if (!this._wallet) throw new Error('Wallet required for this method'); - let found = false; - for (const tx of this._wallet.getTransactions()) { - if (tx.txid === (this._txid || this._txDecoded.getId())) { - if (tx.value > 0) found = true; - } - } - return found; - } - - /** - * Returns all the info about current transaction which is needed to do a replacement TX - * * fee - current tx fee - * * utxos - UTXOs current tx consumes - * * changeAmount - amount of satoshis that sent to change address (or addresses) we control - * * feeRate - sat/byte for current tx - * * targets - destination(s) of funds (outputs we do not control) - * * unconfirmedUtxos - UTXOs created by this transaction (only the ones we control) - * - * @returns {Promise<{fee: number, utxos: Array, unconfirmedUtxos: Array, changeAmount: number, feeRate: number, targets: Array}>} - */ - async getInfo() { - if (!this._wallet) throw new Error('Wallet required for this method'); - if (!this._remoteTx) await this._fetchRemoteTx(); - if (!this._txDecoded) await this._fetchTxhexAndDecode(); - - const prevInputs = []; - for (const inp of this._txDecoded.ins) { - let reversedHash = Buffer.from(reverse(inp.hash)); - reversedHash = reversedHash.toString('hex'); - prevInputs.push(reversedHash); - } - - const prevTransactions = await BlueElectrum.multiGetTransactionByTxid(prevInputs); - - // fetched, now lets count how much satoshis went in - let wentIn = 0; - const utxos = []; - for (const inp of this._txDecoded.ins) { - let reversedHash = Buffer.from(reverse(inp.hash)); - reversedHash = reversedHash.toString('hex'); - if (prevTransactions[reversedHash] && prevTransactions[reversedHash].vout && prevTransactions[reversedHash].vout[inp.index]) { - let value = prevTransactions[reversedHash].vout[inp.index].value; - value = new BigNumber(value).multipliedBy(100000000).toNumber(); - wentIn += value; - const address = SegwitBech32Wallet.witnessToAddress(inp.witness[inp.witness.length - 1]); - utxos.push({ vout: inp.index, value, txId: reversedHash, address }); - } - } - - // counting how much went into actual outputs - - let wasSpent = 0; - for (const outp of this._txDecoded.outs) { - wasSpent += +outp.value; - } - - const fee = wentIn - wasSpent; - let feeRate = Math.floor(fee / this._txDecoded.virtualSize()); - if (feeRate === 0) feeRate = 1; - - // lets take a look at change - let changeAmount = 0; - const targets = []; - for (const outp of this._remoteTx.vout) { - const address = outp.scriptPubKey.addresses[0]; - const value = new BigNumber(outp.value).multipliedBy(100000000).toNumber(); - if (this._wallet.weOwnAddress(address)) { - changeAmount += value; - } else { - // this is target - targets.push({ value, address }); - } - } - - // lets find outputs we own that current transaction creates. can be used in CPFP - const unconfirmedUtxos = []; - for (const outp of this._remoteTx.vout) { - const address = outp.scriptPubKey.addresses[0]; - const value = new BigNumber(outp.value).multipliedBy(100000000).toNumber(); - if (this._wallet.weOwnAddress(address)) { - unconfirmedUtxos.push({ - vout: outp.n, - value, - txId: this._txid || this._txDecoded.getId(), - address, - }); - } - } - - return { fee, feeRate, targets, changeAmount, utxos, unconfirmedUtxos }; - } - - /** - * We get _all_ our UTXOs (even spent kek), - * and see if each input in this transaction's UTXO is in there. If its not there - its an unknown - * input, we dont own it (possibly a payjoin transaction), and we cant do RBF - * - * @returns {Promise} - */ - async thereAreUnknownInputsInTx() { - if (!this._wallet) throw new Error('Wallet required for this method'); - if (!this._txDecoded) await this._fetchTxhexAndDecode(); - - const spentUtxos = this._wallet.getDerivedUtxoFromOurTransaction(true); - for (const inp of this._txDecoded.ins) { - const txidInUtxo = reverse(inp.hash).toString('hex'); - - let found = false; - for (const spentU of spentUtxos) { - if (spentU.txid === txidInUtxo && spentU.vout === inp.index) found = true; - } - - if (!found) { - return true; - } - } - } - - /** - * Checks if all outputs belong to us, that - * means we already canceled this tx and we can only bump fees - * - * @returns {Promise} - */ - async canCancelTx() { - if (!this._wallet) throw new Error('Wallet required for this method'); - if (!this._txDecoded) await this._fetchTxhexAndDecode(); - - if (await this.thereAreUnknownInputsInTx()) return false; - - // if theres at least one output we dont own - we can cancel this transaction! - for (const outp of this._txDecoded.outs) { - if (!this._wallet.weOwnAddress(SegwitBech32Wallet.scriptPubKeyToAddress(outp.script))) return true; - } - - return false; - } - - async canBumpTx() { - if (!this._wallet) throw new Error('Wallet required for this method'); - if (!this._txDecoded) await this._fetchTxhexAndDecode(); - - if (await this.thereAreUnknownInputsInTx()) return false; - - return true; - } - - /** - * Creates an RBF transaction that can replace previous one and basically cancel it (rewrite - * output to the one our wallet controls). Note, this cannot add more utxo in RBF transaction if - * newFeerate is too high - * - * @param newFeerate {number} Sat/byte. Should be greater than previous tx feerate - * @returns {Promise<{outputs: Array, tx: Transaction, inputs: Array, fee: Number}>} - */ - async createRBFcancelTx(newFeerate) { - if (!this._wallet) throw new Error('Wallet required for this method'); - if (!this._remoteTx) await this._fetchRemoteTx(); - - const { feeRate, utxos } = await this.getInfo(); - - if (newFeerate <= feeRate) throw new Error('New feerate should be bigger than the old one'); - const myAddress = await this._wallet.getChangeAddressAsync(); - - return this._wallet.createTransaction( - utxos, - [{ address: myAddress }], - newFeerate, - /* meaningless in this context */ myAddress, - (await this.getMaxUsedSequence()) + 1, - ); - } - - /** - * Creates an RBF transaction that can bumps fee of previous one. Note, this cannot add more utxo in RBF - * transaction if newFeerate is too high - * - * @param newFeerate {number} Sat/byte - * @returns {Promise<{outputs: Array, tx: Transaction, inputs: Array, fee: Number}>} - */ - async createRBFbumpFee(newFeerate) { - if (!this._wallet) throw new Error('Wallet required for this method'); - if (!this._remoteTx) await this._fetchRemoteTx(); - - const { feeRate, targets, changeAmount, utxos } = await this.getInfo(); - - if (newFeerate <= feeRate) throw new Error('New feerate should be bigger than the old one'); - const myAddress = await this._wallet.getChangeAddressAsync(); - - if (changeAmount === 0) delete targets[0].value; - // looks like this was sendMAX transaction (because there was no change), so we cant reuse amount in this - // target since fee wont change. removing the amount so `createTransaction` will sendMAX correctly with new feeRate - - if (targets.length === 0) { - // looks like this was cancelled tx with single change output, so it wasnt included in `this.getInfo()` targets - // so we add output paying ourselves: - targets.push({ address: this._wallet._getInternalAddressByIndex(this._wallet.next_free_change_address_index) }); - // not checking emptiness on purpose: it could unpredictably generate too far address because of unconfirmed tx. - } - - return this._wallet.createTransaction(utxos, targets, newFeerate, myAddress, (await this.getMaxUsedSequence()) + 1); - } - - /** - * Creates a CPFP transaction that can bumps fee of previous one (spends created but not confirmed outputs - * that belong to us). Note, this cannot add more utxo in CPFP transaction if newFeerate is too high - * - * @param newFeerate {number} sat/byte - * @returns {Promise<{outputs: Array, tx: Transaction, inputs: Array, fee: Number}>} - */ - async createCPFPbumpFee(newFeerate) { - if (!this._wallet) throw new Error('Wallet required for this method'); - if (!this._remoteTx) await this._fetchRemoteTx(); - - const { feeRate, fee: oldFee, unconfirmedUtxos } = await this.getInfo(); - - if (newFeerate <= feeRate) throw new Error('New feerate should be bigger than the old one'); - const myAddress = await this._wallet.getChangeAddressAsync(); - - // calculating feerate for CPFP tx so that average between current and CPFP tx will equal newFeerate. - // this works well if both txs are +/- equal size in bytes - const targetFeeRate = 2 * newFeerate - feeRate; - - let add = 0; - while (add <= 128) { - // eslint-disable-next-line no-var - var { tx, inputs, outputs, fee } = this._wallet.createTransaction( - unconfirmedUtxos, - [{ address: myAddress }], - targetFeeRate + add, - myAddress, - HDSegwitBech32Wallet.defaultRBFSequence, - ); - const combinedFeeRate = (oldFee + fee) / (this._txDecoded.virtualSize() + tx.virtualSize()); // avg - if (Math.round(combinedFeeRate) < newFeerate) { - add *= 2; - if (!add) add = 2; - } else { - // reached target feerate - break; - } - } - - return { tx, inputs, outputs, fee }; - } -} diff --git a/class/hd-segwit-bech32-transaction.ts b/class/hd-segwit-bech32-transaction.ts new file mode 100644 index 00000000000..adb2b99fdaf --- /dev/null +++ b/class/hd-segwit-bech32-transaction.ts @@ -0,0 +1,396 @@ +import BigNumber from 'bignumber.js'; +import * as bitcoin from 'bitcoinjs-lib'; +import assert from 'assert'; + +import * as BlueElectrum from '../blue_modules/BlueElectrum'; +import { HDSegwitBech32Wallet } from './wallets/hd-segwit-bech32-wallet'; +import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet'; +import { CreateTransactionUtxo } from './wallets/types.ts'; +import { CoinSelectOutput, CoinSelectReturnInput } from 'coinselect'; +import { isUint8Array, uint8ArrayToHex } from '../blue_modules/uint8array-extras'; + +/** + * Represents transaction of a BIP84 wallet. + * Helpers for RBF, CPFP etc. + */ +export class HDSegwitBech32Transaction { + private _txhex: string | null; + private _txid: string | null; + private _wallet: HDSegwitBech32Wallet | undefined; + private _txDecoded: bitcoin.Transaction | undefined; + private _remoteTx: any; + + /** + * @param txhex {string|null} Object is initialized with txhex + * @param txid {string|null} If txhex not present - txid whould be present + * @param wallet {HDSegwitBech32Wallet|null} If set - a wallet object to which transacton belongs + */ + constructor(txhex: string | null, txid: string | null, wallet: HDSegwitBech32Wallet | null) { + if (!txhex && !txid) throw new Error('Bad arguments'); + this._txhex = txhex; + this._txid = txid; + + if (wallet) { + if (wallet.type === HDSegwitBech32Wallet.type) { + /** @type {HDSegwitBech32Wallet} */ + this._wallet = wallet; + } else { + throw new Error('Only HD Bech32 wallets supported'); + } + } + + if (this._txhex) this._txDecoded = bitcoin.Transaction.fromHex(this._txhex); + this._remoteTx = null; + } + + /** + * If only txid present - we fetch hex + * + * @returns {Promise} + * @private + */ + async _fetchTxhexAndDecode() { + assert(this._txid, 'this._txid must be a string'); + const hexes = await BlueElectrum.multiGetTransactionByTxid([this._txid], false, 10); + this._txhex = hexes[this._txid]; + if (!this._txhex) throw new Error("Transaction can't be found in mempool"); + this._txDecoded = bitcoin.Transaction.fromHex(this._txhex); + } + + /** + * Returns max used sequence for this transaction. Next RBF transaction + * should have this sequence + 1 + * + * @returns {Promise} + */ + async getMaxUsedSequence() { + if (!this._txDecoded) await this._fetchTxhexAndDecode(); + assert(this._txDecoded, 'Could not fetch tx and decode'); + + let max = 0; + for (const inp of this._txDecoded.ins) { + max = Math.max(inp.sequence, max); + } + + return max; + } + + /** + * Basic check that Sequence num for this TX is replaceable + * + * @returns {Promise} + */ + async isSequenceReplaceable() { + return (await this.getMaxUsedSequence()) < bitcoin.Transaction.DEFAULT_SEQUENCE; + } + + /** + * If internal extended tx data not set - this is a method + * to fetch and set this data from electrum. Its different data from + * decoded hex - it contains confirmations etc. + * + * @returns {Promise} + * @private + */ + async _fetchRemoteTx() { + const result = await BlueElectrum.multiGetTransactionByTxid([this._txid || this._txDecoded!.getId()], true); + this._remoteTx = Object.values(result)[0]; + } + + /** + * Fetches from electrum actual confirmations number for this tx + * + * @returns {Promise} + */ + async getRemoteConfirmationsNum() { + if (!this._remoteTx) await this._fetchRemoteTx(); + return this._remoteTx.confirmations || 0; // stupid undefined + } + + /** + * Checks that tx belongs to a wallet and also + * tx value is < 0, which means its a spending transaction + * definitely initiated by us, can be RBF'ed. + * + * @returns {Promise} + */ + async isOurTransaction() { + if (!this._wallet) throw new Error('Wallet required for this method'); + let found = false; + for (const tx of this._wallet.getTransactions()) { + if (tx.txid === (this._txid || this._txDecoded!.getId())) { + // its our transaction, and its spending transaction, which means we initiated it + if (tx.value && tx.value < 0) found = true; + } + } + return found; + } + + /** + * Checks that tx belongs to a wallet and also + * tx value is > 0, which means its a receiving transaction and thus + * can be CPFP'ed. + * + * @returns {Promise} + */ + async isToUsTransaction() { + if (!this._wallet) throw new Error('Wallet required for this method'); + let found = false; + for (const tx of this._wallet.getTransactions()) { + if (tx.txid === (this._txid || this._txDecoded!.getId())) { + if (tx.value && tx.value > 0) found = true; + } + } + return found; + } + + /** + * Returns all the info about current transaction which is needed to do a replacement TX + * * fee - current tx fee + * * utxos - UTXOs current tx consumes + * * changeAmount - amount of satoshis that sent to change address (or addresses) we control + * * feeRate - sat/byte for current tx + * * targets - destination(s) of funds (outputs we do not control) + * * unconfirmedUtxos - UTXOs created by this transaction (only the ones we control) + * + * @returns {Promise<{fee: number, utxos: Array, unconfirmedUtxos: Array, changeAmount: number, feeRate: number, targets: Array}>} + */ + async getInfo() { + if (!this._wallet) throw new Error('Wallet required for this method'); + if (!this._remoteTx) await this._fetchRemoteTx(); + if (!this._txDecoded) await this._fetchTxhexAndDecode(); + assert(this._txDecoded, 'could not fetch tx and decode'); + + const prevInputs = []; + for (const inp of this._txDecoded.ins) { + prevInputs.push(uint8ArrayToHex(new Uint8Array(inp.hash).reverse())); + } + + const prevTransactions = await BlueElectrum.multiGetTransactionByTxid(prevInputs, true); + + // fetched, now lets count how much satoshis went in + let wentIn = 0; + const utxos: CreateTransactionUtxo[] = []; + for (const inp of this._txDecoded.ins) { + const reversedHash = uint8ArrayToHex(new Uint8Array(inp.hash).reverse()); + if (prevTransactions[reversedHash] && prevTransactions[reversedHash].vout && prevTransactions[reversedHash].vout[inp.index]) { + let value = prevTransactions[reversedHash].vout[inp.index].value; + value = new BigNumber(value).multipliedBy(100000000).toNumber(); + wentIn += value; + const witness = inp.witness[inp.witness.length - 1]; + const address = String(SegwitBech32Wallet.witnessToAddress(isUint8Array(witness) ? uint8ArrayToHex(witness) : witness)); + utxos.push({ vout: inp.index, value, txid: reversedHash, address }); + } + } + + // counting how much went into actual outputs + + let wasSpent = 0; + for (const outp of this._txDecoded.outs) { + wasSpent += Number(outp.value); + } + + const fee = wentIn - wasSpent; + let feeRate = Math.floor(fee / this._txDecoded.virtualSize()); + if (feeRate === 0) feeRate = 1; + + // lets take a look at change + let changeAmount = 0; + const targets: { value?: number; address: string }[] = []; + for (const outp of this._remoteTx.vout) { + const address = outp.scriptPubKey.addresses[0]; + const value = new BigNumber(outp.value).multipliedBy(100000000).toNumber(); + if (this._wallet.weOwnAddress(address)) { + changeAmount += value; + } else { + // this is target + targets.push({ value, address }); + } + } + + // lets find outputs we own that current transaction creates. can be used in CPFP + const unconfirmedUtxos = []; + for (const outp of this._remoteTx.vout) { + const address = outp.scriptPubKey.addresses[0]; + const value = new BigNumber(outp.value).multipliedBy(100000000).toNumber(); + if (this._wallet.weOwnAddress(address)) { + unconfirmedUtxos.push({ + vout: outp.n, + value, + txid: this._txid || this._txDecoded.getId(), + address, + }); + } + } + + return { fee, feeRate, targets, changeAmount, utxos, unconfirmedUtxos }; + } + + /** + * We get _all_ our UTXOs (even spent kek), + * and see if each input in this transaction's UTXO is in there. If its not there - its an unknown + * input, we dont own it (possibly a payjoin transaction), and we cant do RBF + * + * @returns {Promise} + */ + async thereAreUnknownInputsInTx() { + if (!this._wallet) throw new Error('Wallet required for this method'); + if (!this._txDecoded) await this._fetchTxhexAndDecode(); + assert(this._txDecoded, 'could not fetch tx and decode'); + + const spentUtxos = this._wallet.getDerivedUtxoFromOurTransaction(true); + for (const inp of this._txDecoded.ins) { + const txidInUtxo = uint8ArrayToHex(new Uint8Array(inp.hash).reverse()); + + let found = false; + for (const spentU of spentUtxos) { + if (spentU.txid === txidInUtxo && spentU.vout === inp.index) found = true; + } + + if (!found) { + return true; + } + } + } + + /** + * Checks if all outputs belong to us, that + * means we already canceled this tx and we can only bump fees + * + * @returns {Promise} + */ + async canCancelTx() { + if (!this._wallet) throw new Error('Wallet required for this method'); + if (!this._txDecoded) await this._fetchTxhexAndDecode(); + assert(this._txDecoded, 'could not fetch tx and decode'); + + if (await this.thereAreUnknownInputsInTx()) return false; + + // if theres at least one output we dont own - we can cancel this transaction! + for (const outp of this._txDecoded.outs) { + const outpScript = outp.script; + if ( + !this._wallet.weOwnAddress( + String(SegwitBech32Wallet.scriptPubKeyToAddress(isUint8Array(outpScript) ? uint8ArrayToHex(outpScript) : outpScript)), + ) + ) + return true; + } + + return false; + } + + async canBumpTx() { + if (!this._wallet) throw new Error('Wallet required for this method'); + if (!this._txDecoded) await this._fetchTxhexAndDecode(); + + if (await this.thereAreUnknownInputsInTx()) return false; + + return true; + } + + /** + * Creates an RBF transaction that can replace previous one and basically cancel it (rewrite + * output to the one our wallet controls). Note, this cannot add more utxo in RBF transaction if + * newFeerate is too high + * + * @param newFeerate {number} Sat/byte. Should be greater than previous tx feerate + * @returns {Promise<{outputs: Array, tx: Transaction, inputs: Array, fee: Number}>} + */ + async createRBFcancelTx(newFeerate: any) { + if (!this._wallet) throw new Error('Wallet required for this method'); + if (!this._remoteTx) await this._fetchRemoteTx(); + + const { feeRate, utxos } = await this.getInfo(); + + if (newFeerate <= feeRate) throw new Error('New feerate should be bigger than the old one'); + const myAddress = await this._wallet.getChangeAddressAsync(); + + return this._wallet.createTransaction( + utxos, + [{ address: myAddress }], + newFeerate, + /* meaningless in this context */ myAddress, + (await this.getMaxUsedSequence()) + 1, + ); + } + + /** + * Creates an RBF transaction that can bumps fee of previous one. Note, this cannot add more utxo in RBF + * transaction if newFeerate is too high + * + * @param newFeerate {number} Sat/byte + * @returns {Promise<{outputs: Array, tx: Transaction, inputs: Array, fee: Number}>} + */ + async createRBFbumpFee(newFeerate: number) { + if (!this._wallet) throw new Error('Wallet required for this method'); + if (!this._remoteTx) await this._fetchRemoteTx(); + + const { feeRate, targets, changeAmount, utxos } = await this.getInfo(); + + if (newFeerate <= feeRate) throw new Error('New feerate should be bigger than the old one'); + const myAddress = await this._wallet.getChangeAddressAsync(); + + if (changeAmount === 0) delete targets[0].value; + // looks like this was sendMAX transaction (because there was no change), so we cant reuse amount in this + // target since fee wont change. removing the amount so `createTransaction` will sendMAX correctly with new feeRate + + if (targets.length === 0) { + // looks like this was cancelled tx with single change output, so it wasnt included in `this.getInfo()` targets + // so we add output paying ourselves: + targets.push({ address: this._wallet._getInternalAddressByIndex(this._wallet.next_free_change_address_index) }); + // not checking emptiness on purpose: it could unpredictably generate too far address because of unconfirmed tx. + } + + return this._wallet.createTransaction(utxos, targets, newFeerate, myAddress, (await this.getMaxUsedSequence()) + 1); + } + + /** + * Creates a CPFP transaction that can bumps fee of previous one (spends created but not confirmed outputs + * that belong to us). Note, this cannot add more utxo in CPFP transaction if newFeerate is too high + * + * @param newFeerate {number} sat/byte + * @returns {Promise<{outputs: Array, tx: Transaction, inputs: Array, fee: Number}>} + */ + async createCPFPbumpFee(newFeerate: number) { + if (!this._wallet) throw new Error('Wallet required for this method'); + if (!this._remoteTx) await this._fetchRemoteTx(); + + const { feeRate, fee: oldFee, unconfirmedUtxos } = await this.getInfo(); + + if (newFeerate <= feeRate) throw new Error('New feerate should be bigger than the old one'); + const myAddress = await this._wallet.getChangeAddressAsync(); + + // calculating feerate for CPFP tx so that average between current and CPFP tx will equal newFeerate. + // this works well if both txs are +/- equal size in bytes + const targetFeeRate = 2 * newFeerate - feeRate; + + let add = 0; + let tx: bitcoin.Transaction | undefined, inputs: CoinSelectReturnInput[], outputs: CoinSelectOutput[], fee: number; + while (add <= 128) { + const createdTx = this._wallet.createTransaction( + unconfirmedUtxos, + [{ address: myAddress }], + targetFeeRate + add, + myAddress, + HDSegwitBech32Wallet.defaultRBFSequence, + ); + tx = createdTx.tx; + inputs = createdTx.inputs; + outputs = createdTx.outputs; + fee = createdTx.fee; + assert(tx, 'tx is createCPFPbumpFee() is undefined'); + const combinedFeeRate = (oldFee + fee) / (this._txDecoded!.virtualSize() + tx.virtualSize()); // avg + if (Math.round(combinedFeeRate) < newFeerate) { + add *= 2; + if (!add) add = 2; + } else { + // reached target feerate + break; + } + } + + // @ts-ignore stfu + return { tx, inputs, outputs, fee }; + } +} diff --git a/class/index.js b/class/index.js deleted file mode 100644 index 9436a9faec9..00000000000 --- a/class/index.js +++ /dev/null @@ -1,20 +0,0 @@ -export * from './wallets/abstract-wallet'; -export * from './wallets/legacy-wallet'; -export * from './wallets/segwit-bech32-wallet'; -export * from './wallets/taproot-wallet'; -export * from './wallets/segwit-p2sh-wallet'; -export * from './wallets/hd-segwit-p2sh-wallet'; -export * from './wallets/hd-legacy-breadwallet-wallet'; -export * from './wallets/hd-legacy-p2pkh-wallet'; -export * from './wallets/watch-only-wallet'; -export * from './wallets/lightning-custodian-wallet'; -export * from './wallets/lightning-ldk-wallet'; -export * from './wallets/abstract-hd-wallet'; -export * from './wallets/hd-segwit-bech32-wallet'; -export * from './wallets/hd-legacy-electrum-seed-p2pkh-wallet'; -export * from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet'; -export * from './wallets/hd-aezeed-wallet'; -export * from './wallets/multisig-hd-wallet'; -export * from './wallets/slip39-wallets'; -export * from './hd-segwit-bech32-transaction'; -export * from './multisig-cosigner'; diff --git a/class/lnurl.js b/class/lnurl.js deleted file mode 100644 index 6e23959e44f..00000000000 --- a/class/lnurl.js +++ /dev/null @@ -1,366 +0,0 @@ -import { bech32 } from 'bech32'; -import bolt11 from 'bolt11'; -import { isTorDaemonDisabled } from '../blue_modules/environment'; -import { parse } from 'url'; // eslint-disable-line n/no-deprecated-api -import { createHmac } from 'crypto'; -import secp256k1 from 'secp256k1'; -const CryptoJS = require('crypto-js'); -const createHash = require('create-hash'); -const torrific = require('../blue_modules/torrific'); -const ONION_REGEX = /^(http:\/\/[^/:@]+\.onion(?::\d{1,5})?)(\/.*)?$/; // regex for onion URL - -/** - * @see https://github.com/btcontract/lnurl-rfc/blob/master/lnurl-pay.md - */ -export default class Lnurl { - static TAG_PAY_REQUEST = 'payRequest'; // type of LNURL - static TAG_WITHDRAW_REQUEST = 'withdrawRequest'; // type of LNURL - static TAG_LOGIN_REQUEST = 'login'; // type of LNURL - - constructor(url, AsyncStorage) { - this._lnurl = url; - this._lnurlPayServiceBolt11Payload = false; - this._lnurlPayServicePayload = false; - this._AsyncStorage = AsyncStorage; - this._preimage = false; - } - - static findlnurl(bodyOfText) { - const res = /^(?:http.*[&?]lightning=|lightning:)?(lnurl1[02-9ac-hj-np-z]+)/.exec(bodyOfText.toLowerCase()); - if (res) { - return res[1]; - } - return null; - } - - static getUrlFromLnurl(lnurlExample) { - const found = Lnurl.findlnurl(lnurlExample); - if (!found) { - if (Lnurl.isLightningAddress(lnurlExample)) { - const username = lnurlExample.split('@')[0].trim(); - const host = lnurlExample.split('@')[1].trim(); - const proto = host.match(/\.onion$/) ? 'http' : 'https'; - return `${proto}://${host}/.well-known/lnurlp/${username}`; - } else { - return false; - } - } - - const decoded = bech32.decode(found, 10000); - return Buffer.from(bech32.fromWords(decoded.words)).toString(); - } - - static isLnurl(url) { - return Lnurl.findlnurl(url) !== null; - } - - static isOnionUrl(url) { - return Lnurl.parseOnionUrl(url) !== null; - } - - static parseOnionUrl(url) { - const match = url.match(ONION_REGEX); - if (match === null) return null; - const [, baseURI, path] = match; - return [baseURI, path]; - } - - async fetchGet(url) { - const parsedOnionUrl = Lnurl.parseOnionUrl(url); - if (parsedOnionUrl) { - return _fetchGetTor(parsedOnionUrl); - } - - const resp = await fetch(url, { method: 'GET' }); - if (resp.status >= 300) { - throw new Error('Bad response from server'); - } - const reply = await resp.json(); - if (reply.status === 'ERROR') { - throw new Error('Reply from server: ' + reply.reason); - } - return reply; - } - - decodeInvoice(invoice) { - const { payeeNodeKey, tags, satoshis, millisatoshis, timestamp } = bolt11.decode(invoice); - - const decoded = { - destination: payeeNodeKey, - num_satoshis: satoshis ? satoshis.toString() : '0', - num_millisatoshis: millisatoshis ? millisatoshis.toString() : '0', - timestamp: timestamp.toString(), - fallback_addr: '', - route_hints: [], - }; - - for (let i = 0; i < tags.length; i++) { - const { tagName, data } = tags[i]; - switch (tagName) { - case 'payment_hash': - decoded.payment_hash = data; - break; - case 'purpose_commit_hash': - decoded.description_hash = data; - break; - case 'min_final_cltv_expiry': - decoded.cltv_expiry = data.toString(); - break; - case 'expire_time': - decoded.expiry = data.toString(); - break; - case 'description': - decoded.description = data; - break; - } - } - - if (!decoded.expiry) decoded.expiry = '3600'; // default - - if (parseInt(decoded.num_satoshis, 10) === 0 && decoded.num_millisatoshis > 0) { - decoded.num_satoshis = (decoded.num_millisatoshis / 1000).toString(); - } - - return decoded; - } - - async requestBolt11FromLnurlPayService(amountSat, comment = '') { - if (!this._lnurlPayServicePayload) throw new Error('this._lnurlPayServicePayload is not set'); - if (!this._lnurlPayServicePayload.callback) throw new Error('this._lnurlPayServicePayload.callback is not set'); - if (amountSat < this._lnurlPayServicePayload.min || amountSat > this._lnurlPayServicePayload.max) - throw new Error( - 'The specified amount is invalid, ' + - amountSat + - ' it should be between ' + - this._lnurlPayServicePayload.min + - ' and ' + - this._lnurlPayServicePayload.max, - ); - const nonce = Math.floor(Math.random() * 2e16).toString(16); - const separator = this._lnurlPayServicePayload.callback.indexOf('?') === -1 ? '?' : '&'; - if (this.getCommentAllowed() && comment && comment.length > this.getCommentAllowed()) { - comment = comment.substr(0, this.getCommentAllowed()); - } - if (comment) comment = `&comment=${encodeURIComponent(comment)}`; - const urlToFetch = - this._lnurlPayServicePayload.callback + separator + 'amount=' + Math.floor(amountSat * 1000) + '&nonce=' + nonce + comment; - this._lnurlPayServiceBolt11Payload = await this.fetchGet(urlToFetch); - if (this._lnurlPayServiceBolt11Payload.status === 'ERROR') - throw new Error(this._lnurlPayServiceBolt11Payload.reason || 'requestBolt11FromLnurlPayService() error'); - - // check pr description_hash, amount etc: - const decoded = this.decodeInvoice(this._lnurlPayServiceBolt11Payload.pr); - const metadataHash = createHash('sha256').update(this._lnurlPayServicePayload.metadata).digest('hex'); - if (metadataHash !== decoded.description_hash) { - throw new Error(`Invoice description_hash doesn't match metadata.`); - } - if (parseInt(decoded.num_satoshis, 10) !== Math.round(amountSat)) { - throw new Error(`Invoice doesn't match specified amount, got ${decoded.num_satoshis}, expected ${Math.round(amountSat)}`); - } - - return this._lnurlPayServiceBolt11Payload; - } - - async callLnurlPayService() { - if (!this._lnurl) throw new Error('this._lnurl is not set'); - const url = Lnurl.getUrlFromLnurl(this._lnurl); - // calling the url - const reply = await this.fetchGet(url); - - if (reply.tag !== Lnurl.TAG_PAY_REQUEST) { - throw new Error('lnurl-pay expected, found tag ' + reply.tag); - } - - const data = reply; - - // parse metadata and extract things from it - let image; - let description; - const kvs = JSON.parse(data.metadata); - for (let i = 0; i < kvs.length; i++) { - const [k, v] = kvs[i]; - switch (k) { - case 'text/plain': - description = v; - break; - case 'image/png;base64': - case 'image/jpeg;base64': - image = 'data:' + k + ',' + v; - break; - } - } - - // setting the payment screen with the parameters - const min = Math.ceil((data.minSendable || 0) / 1000); - const max = Math.floor(data.maxSendable / 1000); - - this._lnurlPayServicePayload = { - callback: data.callback, - fixed: min === max, - min, - max, - domain: data.callback.match(/^(https|http):\/\/([^/]+)\//)[2], - metadata: data.metadata, - description, - image, - amount: min, - commentAllowed: data.commentAllowed, - // lnurl: uri, - }; - return this._lnurlPayServicePayload; - } - - async loadSuccessfulPayment(paymentHash) { - if (!paymentHash) throw new Error('No paymentHash provided'); - let data; - try { - data = await this._AsyncStorage.getItem('lnurlpay_success_data_' + paymentHash); - data = JSON.parse(data); - } catch (_) { - return false; - } - - if (!data) return false; - - this._lnurlPayServicePayload = data.lnurlPayServicePayload; - this._lnurlPayServiceBolt11Payload = data.lnurlPayServiceBolt11Payload; - this._lnurl = data.lnurl; - this._preimage = data.preimage; - - return true; - } - - async storeSuccess(paymentHash, preimage) { - if (typeof preimage === 'object') { - preimage = Buffer.from(preimage.data).toString('hex'); - } - this._preimage = preimage; - - await this._AsyncStorage.setItem( - 'lnurlpay_success_data_' + paymentHash, - JSON.stringify({ - lnurlPayServicePayload: this._lnurlPayServicePayload, - lnurlPayServiceBolt11Payload: this._lnurlPayServiceBolt11Payload, - lnurl: this._lnurl, - preimage, - }), - ); - } - - getSuccessAction() { - return this._lnurlPayServiceBolt11Payload.successAction; - } - - getDomain() { - return this._lnurlPayServicePayload.domain; - } - - getDescription() { - return this._lnurlPayServicePayload.description; - } - - getImage() { - return this._lnurlPayServicePayload.image; - } - - getLnurl() { - return this._lnurl; - } - - getDisposable() { - return this._lnurlPayServiceBolt11Payload.disposable; - } - - getPreimage() { - return this._preimage; - } - - static decipherAES(ciphertextBase64, preimageHex, ivBase64) { - const iv = CryptoJS.enc.Base64.parse(ivBase64); - const key = CryptoJS.enc.Hex.parse(preimageHex); - return CryptoJS.AES.decrypt(Buffer.from(ciphertextBase64, 'base64').toString('hex'), key, { - iv, - mode: CryptoJS.mode.CBC, - format: CryptoJS.format.Hex, - }).toString(CryptoJS.enc.Utf8); - } - - getCommentAllowed() { - return this?._lnurlPayServicePayload?.commentAllowed ? parseInt(this._lnurlPayServicePayload.commentAllowed, 10) : false; - } - - getMin() { - return this?._lnurlPayServicePayload?.min ? parseInt(this._lnurlPayServicePayload.min, 10) : false; - } - - getMax() { - return this?._lnurlPayServicePayload?.max ? parseInt(this._lnurlPayServicePayload.max, 10) : false; - } - - getAmount() { - return this.getMin(); - } - - authenticate(secret) { - return new Promise((resolve, reject) => { - if (!this._lnurl) throw new Error('this._lnurl is not set'); - - const url = parse(Lnurl.getUrlFromLnurl(this._lnurl), true); - - const hmac = createHmac('sha256', secret); - hmac.on('readable', async () => { - try { - const privateKey = hmac.read(); - if (!privateKey) return; - const privateKeyBuf = Buffer.from(privateKey, 'hex'); - const publicKey = secp256k1.publicKeyCreate(privateKeyBuf); - const signatureObj = secp256k1.sign(Buffer.from(url.query.k1, 'hex'), privateKeyBuf); - const derSignature = secp256k1.signatureExport(signatureObj.signature); - - const reply = await this.fetchGet(`${url.href}&sig=${derSignature.toString('hex')}&key=${publicKey.toString('hex')}`); - if (reply.status === 'OK') { - resolve(); - } else { - reject(reply.reason); - } - } catch (err) { - reject(err); - } - }); - hmac.write(url.hostname); - hmac.end(); - }); - } - - static isLightningAddress(address) { - // ensure only 1 `@` present: - if (address.split('@').length !== 2) return false; - const splitted = address.split('@'); - return !!splitted[0].trim() && !!splitted[1].trim(); - } -} - -async function _fetchGetTor(parsedOnionUrl) { - const torDaemonDisabled = await isTorDaemonDisabled(); - if (torDaemonDisabled) { - throw new Error('Tor onion url support disabled'); - } - const [baseURI, path] = parsedOnionUrl; - const tor = new torrific.Torsbee({ - baseURI, - }); - const response = await tor.get(path || '/', { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - }, - }); - const json = response.body; - if (typeof json === 'undefined' || response.err) { - throw new Error('Bad response from server: ' + response.err + ' ' + JSON.stringify(response.body)); - } - if (json.status === 'ERROR') { - throw new Error('Reply from server: ' + json.reason); - } - return json; -} diff --git a/class/lnurl.ts b/class/lnurl.ts new file mode 100644 index 00000000000..346b75c39e3 --- /dev/null +++ b/class/lnurl.ts @@ -0,0 +1,382 @@ +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 ecc from '../blue_modules/noble_ecc'; +import { parse } from 'url'; // eslint-disable-line n/no-deprecated-api +import { fetch } from '../util/fetch'; +import { base64ToUint8Array, hexToUint8Array, uint8ArrayToHex, uint8ArrayToString } from '../blue_modules/uint8array-extras'; + +const ONION_REGEX = /^(http:\/\/[^/:@]+\.onion(?::\d{1,5})?)(\/.*)?$/; // regex for onion URL + +interface LnurlPayServicePayload { + callback: string; + fixed: boolean; + min: number; + max: number; + domain: string; + metadata: string; + description?: string; + image?: string; + amount: number; + commentAllowed?: number; +} + +interface LnurlPayServiceBolt11Payload { + pr: string; + successAction?: any; + disposable?: boolean; + tag: string; + metadata: any; + minSendable: number; + maxSendable: number; + callback: string; + commentAllowed: number; +} + +interface DecodedInvoice { + destination: string; + num_satoshis: string; + num_millisatoshis: string; + timestamp: string; + fallback_addr: string; + route_hints: any[]; + payment_hash?: string; + description_hash?: string; + cltv_expiry?: string; + expiry?: string; + description?: string; +} + +/** + * @see https://github.com/btcontract/lnurl-rfc/blob/master/lnurl-pay.md + */ +export default class Lnurl { + static TAG_PAY_REQUEST = 'payRequest'; // type of LNURL + static TAG_WITHDRAW_REQUEST = 'withdrawRequest'; // type of LNURL + static TAG_LOGIN_REQUEST = 'login'; // type of LNURL + + private _lnurl: string; + private _lnurlPayServiceBolt11Payload: LnurlPayServiceBolt11Payload | false; + private _lnurlPayServicePayload: LnurlPayServicePayload | false; + private _AsyncStorage: any; + private _preimage: string | false; + + constructor(url: string | false, AsyncStorage?: any) { + this._lnurl = url || ''; + this._lnurlPayServiceBolt11Payload = false; + this._lnurlPayServicePayload = false; + this._AsyncStorage = AsyncStorage; + this._preimage = false; + } + + static findlnurl(bodyOfText: string): string | null { + const res = /^(?:http.*[&?]lightning=|lightning:)?(lnurl1[02-9ac-hj-np-z]+)/.exec(bodyOfText.toLowerCase()); + if (res) { + return res[1]; + } + return null; + } + + static getUrlFromLnurl(lnurlExample: string): string | false { + const found = Lnurl.findlnurl(lnurlExample); + if (!found) { + if (Lnurl.isLightningAddress(lnurlExample)) { + const username = lnurlExample.split('@')[0].trim(); + const host = lnurlExample.split('@')[1].trim(); + const proto = host.match(/\.onion$/) ? 'http' : 'https'; + return `${proto}://${host}/.well-known/lnurlp/${username}`; + } else { + return false; + } + } + + const decoded = bech32.decode(found, 10000); + return uint8ArrayToString(new Uint8Array(bech32.fromWords(decoded.words))); + } + + static isLnurl(url: string): boolean { + return Lnurl.findlnurl(url) !== null; + } + + static isOnionUrl(url: string): boolean { + return Lnurl.parseOnionUrl(url) !== null; + } + + static parseOnionUrl(url: string): [string, string] | null { + const match = url.match(ONION_REGEX); + if (match === null) return null; + const [, baseURI, path] = match; + return [baseURI, path]; + } + + async fetchGet(url: string): Promise { + const resp = await fetch(url, { method: 'GET' }); + if (resp.status >= 300) { + throw new Error('Bad response from server'); + } + const reply = await resp.json(); + if (reply.status === 'ERROR') { + throw new Error('Reply from server: ' + reply.reason); + } + return reply; + } + + decodeInvoice(invoice: string): DecodedInvoice { + const { payeeNodeKey, tags, satoshis, millisatoshis, timestamp } = bolt11.decode(invoice); + + const decoded: DecodedInvoice = { + destination: payeeNodeKey ?? '', + num_satoshis: satoshis ? satoshis.toString() : '0', + num_millisatoshis: millisatoshis ? millisatoshis.toString() : '0', + timestamp: timestamp?.toString() ?? '', + fallback_addr: '', + route_hints: [], + }; + + for (let i = 0; i < tags.length; i++) { + const { tagName, data } = tags[i]; + switch (tagName) { + case 'payment_hash': + decoded.payment_hash = String(data); + break; + case 'purpose_commit_hash': + decoded.description_hash = String(data); + break; + case 'min_final_cltv_expiry': + decoded.cltv_expiry = data.toString(); + break; + case 'expire_time': + decoded.expiry = data.toString(); + break; + case 'description': + decoded.description = String(data); + break; + } + } + + if (!decoded.expiry) decoded.expiry = '3600'; // default + + if (parseInt(decoded.num_satoshis, 10) === 0 && parseInt(decoded.num_millisatoshis, 10) > 0) { + decoded.num_satoshis = (parseInt(decoded.num_millisatoshis, 10) / 1000).toString(); + } + + return decoded; + } + + async requestBolt11FromLnurlPayService(amountSat: number, comment: string = ''): Promise { + if (!this._lnurlPayServicePayload) throw new Error('this._lnurlPayServicePayload is not set'); + if (!this._lnurlPayServicePayload.callback) throw new Error('this._lnurlPayServicePayload.callback is not set'); + if (amountSat < this._lnurlPayServicePayload.min || amountSat > this._lnurlPayServicePayload.max) + throw new Error( + 'The specified amount is invalid, ' + + amountSat + + ' it should be between ' + + this._lnurlPayServicePayload.min + + ' and ' + + this._lnurlPayServicePayload.max, + ); + const nonce = Math.floor(Math.random() * 2e16).toString(16); + const separator = this._lnurlPayServicePayload.callback.indexOf('?') === -1 ? '?' : '&'; + if (this.getCommentAllowed() && comment && comment.length > (this.getCommentAllowed() as number)) { + comment = comment.substr(0, this.getCommentAllowed() as number); + } + if (comment) comment = `&comment=${encodeURIComponent(comment)}`; + const urlToFetch = + this._lnurlPayServicePayload.callback + separator + 'amount=' + Math.floor(amountSat * 1000) + '&nonce=' + nonce + comment; + this._lnurlPayServiceBolt11Payload = (await this.fetchGet(urlToFetch)) as LnurlPayServiceBolt11Payload; + + // check pr description_hash, amount etc: + const decoded = this.decodeInvoice(this._lnurlPayServiceBolt11Payload.pr); + const metadataHash = uint8ArrayToHex(sha256(this._lnurlPayServicePayload.metadata)); + if (metadataHash !== decoded.description_hash) { + console.log(`Invoice description_hash doesn't match metadata.`); + } + if (parseInt(decoded.num_satoshis, 10) !== Math.round(amountSat)) { + throw new Error(`Invoice doesn't match specified amount, got ${decoded.num_satoshis}, expected ${Math.round(amountSat)}`); + } + + return this._lnurlPayServiceBolt11Payload; + } + + async callLnurlPayService(): Promise { + if (!this._lnurl) throw new Error('this._lnurl is not set'); + const url = Lnurl.getUrlFromLnurl(this._lnurl); + if (!url) throw new Error('Invalid LNURL'); + // calling the url + const reply = (await this.fetchGet(url)) as LnurlPayServiceBolt11Payload; + + if (reply.tag !== Lnurl.TAG_PAY_REQUEST) { + throw new Error('lnurl-pay expected, found tag ' + reply.tag); + } + + const data = reply; + + // parse metadata and extract things from it + let image: string | undefined; + let description: string | undefined; + const kvs = JSON.parse(data.metadata); + for (let i = 0; i < kvs.length; i++) { + const [k, v] = kvs[i]; + switch (k) { + case 'text/plain': + description = v; + break; + case 'image/png;base64': + case 'image/jpeg;base64': + image = 'data:' + k + ',' + v; + break; + } + } + + // setting the payment screen with the parameters + const min = Math.ceil((data.minSendable ?? 0) / 1000); + const max = Math.floor((data.maxSendable ?? 0) / 1000); + + this._lnurlPayServicePayload = { + callback: data.callback, + fixed: min === max, + min, + max, + // @ts-ignore idk + domain: data.callback.match(/^(https|http):\/\/([^/]+)\//)[2], + metadata: data.metadata, + description, + image, + amount: min, + commentAllowed: data.commentAllowed, + // lnurl: uri, + }; + return this._lnurlPayServicePayload; + } + + async loadSuccessfulPayment(paymentHash: string): Promise { + if (!paymentHash) throw new Error('No paymentHash provided'); + let data; + try { + data = await this._AsyncStorage.getItem('lnurlpay_success_data_' + paymentHash); + data = JSON.parse(data); + } catch (_) { + return false; + } + + if (!data) return false; + + this._lnurlPayServicePayload = data.lnurlPayServicePayload; + this._lnurlPayServiceBolt11Payload = data.lnurlPayServiceBolt11Payload; + this._lnurl = data.lnurl; + this._preimage = data.preimage; + + return true; + } + + async storeSuccess(paymentHash: string, preimage: string | { data: Buffer }): Promise { + if (typeof preimage === 'object') { + preimage = uint8ArrayToHex(new Uint8Array(preimage.data)); + } + this._preimage = preimage; + + await this._AsyncStorage.setItem( + 'lnurlpay_success_data_' + paymentHash, + JSON.stringify({ + lnurlPayServicePayload: this._lnurlPayServicePayload, + lnurlPayServiceBolt11Payload: this._lnurlPayServiceBolt11Payload, + lnurl: this._lnurl, + preimage, + }), + ); + } + + getSuccessAction(): any | undefined { + return this._lnurlPayServiceBolt11Payload && 'successAction' in this._lnurlPayServiceBolt11Payload + ? this._lnurlPayServiceBolt11Payload.successAction + : undefined; + } + + getDomain(): string | undefined { + return this._lnurlPayServicePayload ? this._lnurlPayServicePayload.domain : undefined; + } + + getDescription(): string | undefined { + return this._lnurlPayServicePayload ? this._lnurlPayServicePayload.description : undefined; + } + + getImage(): string | undefined { + return this._lnurlPayServicePayload ? this._lnurlPayServicePayload.image : undefined; + } + + getLnurl(): string { + return this._lnurl; + } + + getDisposable(): boolean | undefined { + return this._lnurlPayServiceBolt11Payload && 'disposable' in this._lnurlPayServiceBolt11Payload + ? this._lnurlPayServiceBolt11Payload.disposable + : undefined; + } + + getPreimage(): string | false { + return this._preimage; + } + + 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); + } + + getCommentAllowed(): number | false { + if (!this._lnurlPayServicePayload) return false; + return this._lnurlPayServicePayload.commentAllowed ? parseInt(this._lnurlPayServicePayload.commentAllowed.toString(), 10) : false; + } + + getMin(): number | false { + if (!this._lnurlPayServicePayload) return false; + return this._lnurlPayServicePayload.min ? parseInt(this._lnurlPayServicePayload.min.toString(), 10) : false; + } + + getMax(): number | false { + if (!this._lnurlPayServicePayload) return false; + return this._lnurlPayServicePayload.max ? parseInt(this._lnurlPayServicePayload.max.toString(), 10) : false; + } + + getAmount(): number | false { + return this.getMin(); + } + + async authenticate(secret: string): Promise { + if (!this._lnurl) throw new Error('this._lnurl is not set'); + + const url = parse(Lnurl.getUrlFromLnurl(this._lnurl) || '', true); + + if (!url.hostname) { + throw new Error('Invalid URL: hostname is null'); + } + + const privateKey = hmac(sha256, secret, url.hostname); + const publicKey = ecc.pointFromScalar(privateKey); + if (!publicKey) { + throw new Error('Failed to generate public key'); + } + const signature = ecc.signDER(hexToUint8Array(url.query.k1 as string), privateKey); + + const reply = await this.fetchGet(`${url.href}&sig=${uint8ArrayToHex(signature)}&key=${uint8ArrayToHex(publicKey)}`); + if (reply.status === 'OK') { + // Authentication successful + } else { + throw reply.reason; + } + } + + static isLightningAddress(address: string) { + // ensure only 1 `@` present: + if (address.split('@').length !== 2) return false; + const splitted = address.split('@'); + return !!splitted[0].trim() && !!splitted[1].trim(); + } +} diff --git a/class/measure.ts b/class/measure.ts new file mode 100644 index 00000000000..a9f907ed573 --- /dev/null +++ b/class/measure.ts @@ -0,0 +1,18 @@ +/** + * Simple helper to measure execution time of a code block + */ +export class Measure { + private _label: string; + private _start: number; + + constructor(label: string) { + this._label = label; + this._start = Date.now(); + } + + public end() { + const end = Date.now(); + const duration = Number(((end - this._start) / 1000).toFixed(3)); + console.log(`${this._label} took ${duration}s`); + } +} diff --git a/class/multisig-cosigner.js b/class/multisig-cosigner.js deleted file mode 100644 index bf15b24f070..00000000000 --- a/class/multisig-cosigner.js +++ /dev/null @@ -1,220 +0,0 @@ -import b58 from 'bs58check'; -import { MultisigHDWallet } from './wallets/multisig-hd-wallet'; -import BIP32Factory from 'bip32'; -import ecc from '../blue_modules/noble_ecc'; -const bip32 = BIP32Factory(ecc); - -export class MultisigCosigner { - constructor(data) { - this._data = data; - this._fp = false; - this._xpub = false; - this._path = false; - this._valid = false; - this._cosigners = []; - - // is it plain simple Zpub/Ypub/xpub? - if (data.startsWith('Zpub') && MultisigCosigner.isXpubValid(data)) { - this._fp = '00000000'; - this._xpub = data; - this._path = "m/48'/0'/0'/2'"; - this._valid = true; - this._cosigners = [true]; - return; - } else if (data.startsWith('Ypub') && MultisigCosigner.isXpubValid(data)) { - this._fp = '00000000'; - this._xpub = data; - this._path = "m/48'/0'/0'/1'"; - this._valid = true; - this._cosigners = [true]; - return; - } else if (data.startsWith('xpub') && MultisigCosigner.isXpubValid(data)) { - this._fp = '00000000'; - this._xpub = data; - this._path = "m/45'"; - this._valid = true; - this._cosigners = [true]; - return; - } - - // is it wallet descriptor? - if (data.startsWith('[')) { - const end = data.indexOf(']'); - const part = data.substr(1, end - 1).replace(/[h]/g, "'"); - this._fp = part.split('/')[0]; - const xpub = data.substr(end + 1); - - if (MultisigCosigner.isXpubValid(xpub)) { - this._xpub = xpub; - this._path = 'm'; - for (let c = 0; c < part.split('/').length; c++) { - if (c === 0) continue; - this._path += '/' + part.split('/')[c]; - } - this._cosigners = [true]; - this._valid = true; - return; - } - } - - // is it cobo json? - try { - const json = JSON.parse(data); - if (json.xfp && json.xpub && json.path) { - this._fp = json.xfp; - this._xpub = json.xpub; - this._path = json.path; - this._cosigners = [true]; - this._valid = true; - - // a bit more logic here: according to the formal BIP48 spec, this xpub field _can_ start with 'xpub', but - // the actual type of segwit can be inferred from the path - if ( - this._xpub.startsWith('xpub') && - [MultisigHDWallet.PATH_NATIVE_SEGWIT, MultisigHDWallet.PATH_WRAPPED_SEGWIT].includes(this._path) - ) { - const w = new MultisigHDWallet(); - w.addCosigner(this._xpub, '00000000', this._path); - w.setDerivationPath(this._path); - this._xpub = w.convertXpubToMultisignatureXpub(this._xpub); - } - - return; - } - } catch (_) { - this._valid = false; - } - - // is it cobo crypto-account URv2 ? - try { - const json = JSON.parse(data); - if (json && json.ExtPubKey && json.MasterFingerprint && json.AccountKeyPath) { - this._fp = json.MasterFingerprint; - this._xpub = json.ExtPubKey; - this._path = json.AccountKeyPath; - this._cosigners = [true]; - this._valid = true; - return; - } - } catch (_) { - this._valid = false; - } - - // is it coldcard 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)); - 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)); - 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)); - this._valid = true; - this._cosigners.push(cc); - } - } catch (_) { - this._valid = false; - } - } - - static isXpubValid(key) { - let xpub; - - try { - const tempWallet = new MultisigHDWallet(); - xpub = tempWallet._zpubToXpub(key); - bip32.fromBase58(xpub); - return true; - } catch (_) {} - - return false; - } - - static exportToJson(xfp, xpub, path) { - return JSON.stringify({ - xfp, - xpub, - path, - }); - } - - isValid() { - return this._valid; - } - - getFp() { - return this._fp; - } - - getXpub() { - return this._xpub; - } - - getPath() { - return this._path; - } - - howManyCosignersWeHave() { - return this._cosigners.length; - } - - /** - * - * @returns {Array.} - */ - getAllCosigners() { - return this._cosigners; - } - - isNativeSegwit() { - return this.getXpub().startsWith('Zpub'); - } - - isWrappedSegwit() { - return this.getXpub().startsWith('Ypub'); - } - - isLegacy() { - return this.getXpub().startsWith('xpub'); - } - - getChainCodeHex() { - let data = b58.decode(this.getXpub()); - data = data.slice(4); - data = data.slice(1); - data = data.slice(4); - data = data.slice(4, 36); - return data.toString('hex'); - } - - getKeyHex() { - let data = b58.decode(this.getXpub()); - data = data.slice(4); - data = data.slice(1); - data = data.slice(4); - data = data.slice(36); - return data.toString('hex'); - } - - getParentFingerprintHex() { - let data = b58.decode(this.getXpub()); - data = data.slice(4); - data = data.slice(1); - data = data.slice(0, 4); - return data.toString('hex'); - } - - getDepthNumber() { - let data = b58.decode(this.getXpub()); - data = data.slice(4, 5); - return data.readInt8(); - } -} diff --git a/class/multisig-cosigner.ts b/class/multisig-cosigner.ts new file mode 100644 index 00000000000..d8c938520c9 --- /dev/null +++ b/class/multisig-cosigner.ts @@ -0,0 +1,262 @@ +import BIP32Factory from 'bip32'; +import b58 from 'bs58check'; + +import ecc from '../blue_modules/noble_ecc'; +import { MultisigHDWallet } from './wallets/multisig-hd-wallet'; +import assert from 'assert'; +const bip32 = BIP32Factory(ecc); + +export class MultisigCosigner { + private _data: string; + private _fp: string = ''; + private _xpub: string = ''; + private _path: string = ''; + private _valid: boolean = false; + private _cosigners: any[]; + + constructor(data: string) { + this._data = data; + this._cosigners = []; + + // is it plain simple Zpub/Ypub/xpub? + if (data.startsWith('Zpub') && MultisigCosigner.isXpubValid(data)) { + this._fp = '00000000'; + this._xpub = data; + this._path = "m/48'/0'/0'/2'"; + this._valid = true; + this._cosigners = [true]; + return; + } else if (data.startsWith('Ypub') && MultisigCosigner.isXpubValid(data)) { + this._fp = '00000000'; + this._xpub = data; + this._path = "m/48'/0'/0'/1'"; + this._valid = true; + this._cosigners = [true]; + return; + } else if (data.startsWith('xpub') && MultisigCosigner.isXpubValid(data)) { + this._fp = '00000000'; + this._xpub = data; + this._path = "m/45'"; + this._valid = true; + this._cosigners = [true]; + return; + } + + // is it wallet descriptor? + if (data.startsWith('[')) { + const end = data.indexOf(']'); + const part = data.substr(1, end - 1).replace(/[h]/g, "'"); + this._fp = part.split('/')[0]; + const xpub = data.substr(end + 1); + + if (MultisigCosigner.isXpubValid(xpub)) { + this._xpub = xpub; + this._path = 'm'; + for (let c = 0; c < part.split('/').length; c++) { + if (c === 0) continue; + this._path += '/' + part.split('/')[c]; + } + this._cosigners = [true]; + this._valid = true; + return; + } + } + + // is it cobo json? + try { + const json = JSON.parse(data); + if (json.xfp && json.xpub && json.path) { + this._fp = json.xfp; + this._xpub = json.xpub; + this._path = json.path; + this._cosigners = [true]; + this._valid = true; + + // a bit more logic here: according to the formal BIP48 spec, this xpub field _can_ start with 'xpub', but + // the actual type of segwit can be inferred from the path + assert(this._xpub); + if ( + this._xpub.startsWith('xpub') && + [MultisigHDWallet.PATH_NATIVE_SEGWIT, MultisigHDWallet.PATH_WRAPPED_SEGWIT].includes(this._path) + ) { + const w = new MultisigHDWallet(); + w.addCosigner(this._xpub, '00000000', this._path); + w.setDerivationPath(this._path); + this._xpub = w.convertXpubToMultisignatureXpub(this._xpub); + } + + return; + } + } catch (_) { + this._valid = false; + } + + // is it cobo crypto-account URv2 ? + try { + const json = JSON.parse(data); + if (json && json.ExtPubKey && json.MasterFingerprint && json.AccountKeyPath) { + this._fp = json.MasterFingerprint; + this._xpub = json.ExtPubKey; + this._path = json.AccountKeyPath; + this._cosigners = [true]; + this._valid = true; + return; + } + } catch (_) { + this._valid = false; + } + + // is it coldcard 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)); + 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)); + 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)); + this._valid = true; + this._cosigners.push(cc); + } + } catch (_) { + this._valid = false; + } + + // is it coldcardQ json? + try { + const json = JSON.parse(data); + if (json && json.chain === 'BTC' && json.xfp && (json.bip48_1 || json.bip48_2 || json.bip45)) { + if (json.bip48_1) { + const path = json.bip48_1.deriv.replace(/h/g, "'"); + const xpub = json.bip48_1._pub || json.bip48_1.xpub; // ColdcardQ provides SLIP-0132 encoded _pub (Ypub/Zpub). Prefer it when present, fallback to xpub for legacy. + const xfp = json.xfp; + + const cc = new MultisigCosigner(MultisigCosigner.exportToJson(xfp, xpub, path)); + this._valid = true; + this._cosigners.push(cc); + } + if (json.bip48_2) { + const path = json.bip48_2.deriv.replace(/h/g, "'"); + const xpub = json.bip48_2._pub || json.bip48_2.xpub; // ColdcardQ provides SLIP-0132 encoded _pub (Ypub/Zpub). Prefer it when present, fallback to xpub for legacy. + const xfp = json.xfp; + + const cc = new MultisigCosigner(MultisigCosigner.exportToJson(xfp, xpub, path)); + this._valid = true; + this._cosigners.push(cc); + } + if (json.bip45) { + const path = json.bip45.deriv.replace(/h/g, "'"); + const xpub = json.bip45._pub || json.bip45.xpub; // ColdcardQ provides SLIP-0132 encoded _pub (Ypub/Zpub). Prefer it when present, fallback to xpub for legacy. + const xfp = json.xfp; + + const cc = new MultisigCosigner(MultisigCosigner.exportToJson(xfp, xpub, path)); + this._valid = true; + this._cosigners.push(cc); + } + } + } catch (_) { + this._valid = false; + } + } + + static isXpubValid(key: string) { + let xpub; + + try { + const tempWallet = new MultisigHDWallet(); + xpub = tempWallet._zpubToXpub(key); + bip32.fromBase58(xpub); + return true; + } catch (_) {} + + return false; + } + + static exportToJson(xfp: string, xpub: string, path: string) { + return JSON.stringify({ + xfp, + xpub, + path, + }); + } + + isValid() { + return this._valid; + } + + getFp() { + return this._fp; + } + + getXpub() { + return this._xpub; + } + + getPath() { + return this._path; + } + + howManyCosignersWeHave() { + return this._cosigners.length; + } + + /** + * + * @returns {Array.} + */ + getAllCosigners() { + return this._cosigners; + } + + isNativeSegwit() { + return this.getXpub().startsWith('Zpub'); + } + + isWrappedSegwit() { + return this.getXpub().startsWith('Ypub'); + } + + isLegacy() { + return this.getXpub().startsWith('xpub'); + } + + getChainCodeHex() { + let data = b58.decode(this.getXpub()); + data = data.slice(4); + data = data.slice(1); + data = data.slice(4); + data = data.slice(4, 36); + return data.toString('hex'); + } + + getKeyHex() { + let data = b58.decode(this.getXpub()); + data = data.slice(4); + data = data.slice(1); + data = data.slice(4); + data = data.slice(36); + return data.toString('hex'); + } + + getParentFingerprintHex() { + let data = b58.decode(this.getXpub()); + data = data.slice(4); + data = data.slice(1); + data = data.slice(0, 4); + return data.toString('hex'); + } + + getDepthNumber() { + let data = b58.decode(this.getXpub()); + data = data.slice(4, 5); + return data.readInt8(); + } +} diff --git a/class/on-app-launch.js b/class/on-app-launch.js deleted file mode 100644 index 18c5ca0add4..00000000000 --- a/class/on-app-launch.js +++ /dev/null @@ -1,45 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -const BlueApp = require('../BlueApp'); - -export default class OnAppLaunch { - static STORAGE_KEY = 'ONAPP_LAUNCH_SELECTED_DEFAULT_WALLET_KEY'; - - static async isViewAllWalletsEnabled() { - try { - const selectedDefaultWallet = await AsyncStorage.getItem(OnAppLaunch.STORAGE_KEY); - return selectedDefaultWallet === '' || selectedDefaultWallet === null; - } catch (_e) { - return true; - } - } - - static async setViewAllWalletsEnabled(value) { - if (!value) { - const selectedDefaultWallet = await OnAppLaunch.getSelectedDefaultWallet(); - if (!selectedDefaultWallet) { - const firstWallet = BlueApp.getWallets()[0]; - await OnAppLaunch.setSelectedDefaultWallet(firstWallet.getID()); - } - } else { - await AsyncStorage.setItem(OnAppLaunch.STORAGE_KEY, ''); - } - } - - static async getSelectedDefaultWallet() { - let selectedWallet = false; - try { - const selectedWalletID = JSON.parse(await AsyncStorage.getItem(OnAppLaunch.STORAGE_KEY)); - selectedWallet = BlueApp.getWallets().find(wallet => wallet.getID() === selectedWalletID); - if (!selectedWallet) { - await AsyncStorage.setItem(OnAppLaunch.STORAGE_KEY, ''); - } - } catch (_e) { - return false; - } - return selectedWallet; - } - - static async setSelectedDefaultWallet(value) { - await AsyncStorage.setItem(OnAppLaunch.STORAGE_KEY, JSON.stringify(value)); - } -} diff --git a/class/payjoin-transaction.js b/class/payjoin-transaction.js deleted file mode 100644 index 33362be9254..00000000000 --- a/class/payjoin-transaction.js +++ /dev/null @@ -1,88 +0,0 @@ -import * as bitcoin from 'bitcoinjs-lib'; -import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; -import alert from '../components/Alert'; -import { ECPairFactory } from 'ecpair'; -import ecc from '../blue_modules/noble_ecc'; -const ECPair = ECPairFactory(ecc); - -const delay = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds)); - -// Implements IPayjoinClientWallet -// https://github.com/bitcoinjs/payjoin-client/blob/master/ts_src/wallet.ts -export default class PayjoinTransaction { - constructor(psbt, broadcast, wallet) { - this._psbt = psbt; - this._broadcast = broadcast; - this._wallet = wallet; - this._payjoinPsbt = false; - } - - async getPsbt() { - // Nasty hack to get this working for now - const unfinalized = this._psbt.clone(); - for (const [index, input] of unfinalized.data.inputs.entries()) { - delete input.finalScriptWitness; - - const address = bitcoin.address.fromOutputScript(input.witnessUtxo.script); - const wif = this._wallet._getWifForAddress(address); - const keyPair = ECPair.fromWIF(wif); - - unfinalized.signInput(index, keyPair); - } - - return unfinalized; - } - - /** - * Doesnt conform to spec but needed for user-facing wallet software to find out txid of payjoined transaction - * - * @returns {boolean|Psbt} - */ - getPayjoinPsbt() { - return this._payjoinPsbt; - } - - async signPsbt(payjoinPsbt) { - // Do this without relying on private methods - - for (const [index, input] of payjoinPsbt.data.inputs.entries()) { - const address = bitcoin.address.fromOutputScript(input.witnessUtxo.script); - try { - const wif = this._wallet._getWifForAddress(address); - const keyPair = ECPair.fromWIF(wif); - payjoinPsbt.signInput(index, keyPair).finalizeInput(index); - } catch (e) {} - } - this._payjoinPsbt = payjoinPsbt; - return this._payjoinPsbt; - } - - async broadcastTx(txHex) { - try { - const result = await this._broadcast(txHex); - if (!result) { - throw new Error(`Broadcast failed`); - } - return ''; - } catch (e) { - return 'Error: ' + e.message; - } - } - - async scheduleBroadcastTx(txHex, milliseconds) { - delay(milliseconds).then(async () => { - const result = await this.broadcastTx(txHex); - if (result === '') { - // TODO: Improve the wording of this error message - ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); - alert('Something was wrong with the payjoin transaction, the original transaction sucessfully broadcast.'); - } - }); - } - - async isOwnOutputScript(outputScript) { - const address = bitcoin.address.fromOutputScript(outputScript); - - return this._wallet.weOwnAddress(address); - } -} diff --git a/class/payjoin-transaction.ts b/class/payjoin-transaction.ts new file mode 100644 index 00000000000..358ecaa1ac0 --- /dev/null +++ b/class/payjoin-transaction.ts @@ -0,0 +1,113 @@ +import * as bitcoin from 'bitcoinjs-lib'; +import { ECPairFactory } from 'ecpair'; + +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; +import ecc from '../blue_modules/noble_ecc'; +import presentAlert from '../components/Alert'; +import { HDSegwitBech32Wallet } from './wallets/hd-segwit-bech32-wallet'; +import assert from 'assert'; +import { uint8ArrayToHex } from '../blue_modules/uint8array-extras'; +const ECPair = ECPairFactory(ecc); + +const delay = (milliseconds: number) => new Promise(resolve => setTimeout(resolve, milliseconds)); + +// Implements IPayjoinClientWallet +// https://github.com/bitcoinjs/payjoin-client/blob/master/ts_src/wallet.ts +export default class PayjoinTransaction { + private _psbt: bitcoin.Psbt; + private _broadcast: (txhex: string) => Promise; + private _wallet: HDSegwitBech32Wallet; + private _payjoinPsbt: any; + + constructor(psbt: bitcoin.Psbt, broadcast: (txhex: string) => Promise, wallet: HDSegwitBech32Wallet) { + this._psbt = psbt; + this._broadcast = broadcast; + this._wallet = wallet; + this._payjoinPsbt = false; + } + + async getPsbt() { + // Nasty hack to get this working for now + const unfinalized = this._psbt.clone(); + for (const [index, input] of unfinalized.data.inputs.entries()) { + delete input.finalScriptWitness; + + assert(input.witnessUtxo, 'Internal error: input.witnessUtxo is not set'); + const address = bitcoin.address.fromOutputScript(input.witnessUtxo.script); + const wif = this._wallet._getWifForAddress(address); + const keyPair = ECPair.fromWIF(wif); + + unfinalized.signInput(index, keyPair); + } + + // now, since payjoin lib expects an older version of Psbt object (from bitcoinjs-lib v6), + // it expects `script` to be Buffer, and in v7 its actually uint8 array. + // lets monkey patch the cloned PSBT so it returns buffers, as expected: + const origclone = unfinalized.clone; + unfinalized.clone = () => { + const newPsbt = origclone.apply(unfinalized); + const original = newPsbt.txOutputs; + + Object.defineProperty(newPsbt, 'txOutputs', { + get() { + return original.map(o => ({ + ...o, + script: Buffer.from(uint8ArrayToHex(o.script), 'hex'), + })); + }, + }); + + return newPsbt; + }; + + return unfinalized; + } + + /** + * Doesnt conform to spec but needed for user-facing wallet software to find out txid of payjoined transaction + * + * @returns {Psbt} + */ + getPayjoinPsbt() { + return this._payjoinPsbt; + } + + async signPsbt(payjoinPsbt: bitcoin.Psbt) { + // Do this without relying on private methods + + for (const [index, input] of payjoinPsbt.data.inputs.entries()) { + assert(input.witnessUtxo, 'Internal error: input.witnessUtxo is not set'); + const address = bitcoin.address.fromOutputScript(input.witnessUtxo.script); + try { + const wif = this._wallet._getWifForAddress(address); + const keyPair = ECPair.fromWIF(wif); + payjoinPsbt.signInput(index, keyPair).finalizeInput(index); + } catch (e) {} + } + this._payjoinPsbt = payjoinPsbt; + return this._payjoinPsbt; + } + + async broadcastTx(txHex: string) { + try { + const result = await this._broadcast(txHex); + if (!result) { + throw new Error(`Broadcast failed`); + } + return ''; + } catch (e: any) { + return 'Error: ' + e.message; + } + } + + async scheduleBroadcastTx(txHex: string, milliseconds: number) { + delay(milliseconds).then(async () => { + const result = await this.broadcastTx(txHex); + if (result === '') { + // TODO: Improve the wording of this error message + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); + presentAlert({ message: 'Something was wrong with the payjoin transaction, the original transaction successfully broadcast.' }); + } + }); + } +} diff --git a/class/quick-actions.js b/class/quick-actions.js deleted file mode 100644 index 0414b6b6b5c..00000000000 --- a/class/quick-actions.js +++ /dev/null @@ -1,85 +0,0 @@ -import QuickActions from 'react-native-quick-actions'; -import { Platform } from 'react-native'; -import { formatBalance } from '../loc'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { useContext, useEffect } from 'react'; -import { BlueStorageContext } from '../blue_modules/storage-context'; - -function DeviceQuickActions() { - DeviceQuickActions.STORAGE_KEY = 'DeviceQuickActionsEnabled'; - const { wallets, walletsInitialized, isStorageEncrypted, preferredFiatCurrency } = useContext(BlueStorageContext); - - useEffect(() => { - if (walletsInitialized) { - isStorageEncrypted() - .then(value => { - if (value) { - QuickActions.clearShortcutItems(); - } else { - setQuickActions(); - } - }) - .catch(() => QuickActions.clearShortcutItems()); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [wallets, walletsInitialized, preferredFiatCurrency]); - - DeviceQuickActions.setEnabled = (enabled = true) => { - return AsyncStorage.setItem(DeviceQuickActions.STORAGE_KEY, JSON.stringify(enabled)).then(() => { - if (!enabled) { - QuickActions.clearShortcutItems(); - } else { - setQuickActions(); - } - }); - }; - - DeviceQuickActions.popInitialAction = async () => { - const data = await QuickActions.popInitialAction(); - return data; - }; - - DeviceQuickActions.getEnabled = async () => { - try { - const isEnabled = await AsyncStorage.getItem(DeviceQuickActions.STORAGE_KEY); - if (isEnabled === null) { - await DeviceQuickActions.setEnabled(JSON.stringify(true)); - return true; - } - return !!JSON.parse(isEnabled); - } catch { - return true; - } - }; - - const setQuickActions = async () => { - if (await DeviceQuickActions.getEnabled()) { - QuickActions.isSupported((error, _supported) => { - if (error === null) { - const shortcutItems = []; - for (const wallet of wallets.slice(0, 4)) { - shortcutItems.push({ - type: 'Wallets', // Required - title: wallet.getLabel(), // Optional, if empty, `type` will be used instead - subtitle: - wallet.hideBalance || wallet.getBalance() <= 0 - ? '' - : formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true), - userInfo: { - url: `bluewallet://wallet/${wallet.getID()}`, // Provide any custom data like deep linking URL - }, - icon: Platform.select({ android: 'quickactions', ios: 'bookmark' }), - }); - } - QuickActions.setShortcutItems(shortcutItems); - } - }); - } else { - QuickActions.clearShortcutItems(); - } - }; - - return null; -} - -export default DeviceQuickActions; diff --git a/class/quick-actions.windows.js b/class/quick-actions.windows.js deleted file mode 100644 index 842d0f50fa9..00000000000 --- a/class/quick-actions.windows.js +++ /dev/null @@ -1,15 +0,0 @@ -function DeviceQuickActions() { - DeviceQuickActions.STORAGE_KEY = 'DeviceQuickActionsEnabled'; - - DeviceQuickActions.setEnabled = () => {}; - - DeviceQuickActions.getEnabled = async () => { - return false; - }; - - DeviceQuickActions.popInitialAction = () => {}; - - return null; -} - -export default DeviceQuickActions; diff --git a/class/rng.js b/class/rng.js deleted file mode 100644 index 3ba7251ec0d..00000000000 --- a/class/rng.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @fileOverview creates an rng module that will bring all calls to 'crypto' - * into one place to try and prevent mistakes when touching the crypto code. - */ - -import crypto from 'crypto'; -// uses `crypto` module under nodejs/cli and shim under RN -// @see blue_modules/crypto.js - -/** - * Generate cryptographically secure random bytes using native api. - * @param {number} size The number of bytes of randomness - * @return {Promise.} The random bytes - */ -export async function randomBytes(size) { - return new Promise((resolve, reject) => { - crypto.randomBytes(size, (err, data) => { - if (err) reject(err); - else resolve(data); - }); - }); -} diff --git a/class/rng.ts b/class/rng.ts new file mode 100644 index 00000000000..02ca007d23a --- /dev/null +++ b/class/rng.ts @@ -0,0 +1,22 @@ +/** + * @fileOverview creates an rng module that will bring all calls to 'crypto' + * into one place to try and prevent mistakes when touching the crypto code. + */ + +// React Native: entropy via global crypto.getRandomValues (polyfilled by react-native-get-random-values) + +/** + * Generate cryptographically secure random bytes using native api. + * @param {number} size The number of bytes of randomness + * @return {Promise.} The random bytes + */ +export async function randomBytes(size: number): Promise { + const g: any = globalThis as any; + const rnCrypto = g && g.crypto; + if (!rnCrypto || typeof rnCrypto.getRandomValues !== 'function') { + throw new Error('crypto.getRandomValues is not available'); + } + const bytes = new Uint8Array(size); + rnCrypto.getRandomValues(bytes); + return bytes; +} diff --git a/class/synced-async-storage.ts b/class/synced-async-storage.ts deleted file mode 100644 index dee8059812f..00000000000 --- a/class/synced-async-storage.ts +++ /dev/null @@ -1,161 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; - -const SHA256 = require('crypto-js/sha256'); -const ENCHEX = require('crypto-js/enc-hex'); -const ENCUTF8 = require('crypto-js/enc-utf8'); -const AES = require('crypto-js/aes'); - -export default class SyncedAsyncStorage { - defaultBaseUrl = 'https://bytes-store.herokuapp.com'; - encryptionMarker = 'encrypted://'; - - namespace: string = ''; - encryptionKey: string = ''; - - constructor(entropy: string) { - if (!entropy) throw new Error('entropy not provided'); - - this.namespace = this.hashIt(this.hashIt('namespace' + entropy)); - this.encryptionKey = this.hashIt(this.hashIt('encryption' + entropy)); - } - - hashIt(arg: string) { - return ENCHEX.stringify(SHA256(arg)); - } - - encrypt(clearData: string): string { - return this.encryptionMarker + AES.encrypt(clearData, this.encryptionKey).toString(); - } - - decrypt(encryptedData: string | null, encryptionKey: string | null = null): string { - if (encryptedData === null) return ''; - if (!encryptedData.startsWith(this.encryptionMarker)) return encryptedData; - const bytes = AES.decrypt(encryptedData.replace(this.encryptionMarker, ''), encryptionKey || this.encryptionKey); - return bytes.toString(ENCUTF8); - } - - static assertEquals(a: any, b: any) { - if (a !== b) throw new Error('Assertion failed that ' + a + ' equals ' + b); - } - - static assertNotEquals(a: any, b: any) { - if (a === b) throw new Error('Assertion failed that ' + a + ' NOT equals ' + b); - } - - async selftest(): Promise { - const clear = 'text line to be encrypted'; - const encrypted = this.encrypt(clear); - - SyncedAsyncStorage.assertEquals(encrypted.startsWith(this.encryptionMarker), true); - SyncedAsyncStorage.assertNotEquals(clear, encrypted); - const decrypted = this.decrypt(encrypted); - SyncedAsyncStorage.assertEquals(clear, decrypted); - - SyncedAsyncStorage.assertEquals(this.decrypt(clear), clear); - - SyncedAsyncStorage.assertEquals( - this.decrypt( - 'encrypted://U2FsdGVkX19XQWgwS8q5XjQSQ19OmBsNax4k6NZOAsKFhCgw9sJFwb+qVYfqy6X5', - '3a013f391e59daf2f5074fa66652784d17511ea072d7a8329ff9bddf371932ab', - ), - 'text line to be encrypted', - ); - - return true; - } - - /** - * @param key {string} - * @param value {string} - * - * @return {string} New sequence number from remote - */ - async setItemRemote(key: string, value: string): Promise { - const that = this; - return new Promise(function (resolve, reject) { - fetch(that.defaultBaseUrl + '/namespace/' + that.namespace + '/' + key, { - method: 'POST', - headers: { - Accept: 'text/plain', - 'Content-Type': 'text/plain', - }, - body: value, - }) - .then(async response => { - const text = await response.text(); - console.log('saved, seq num:', text); - resolve(text); - }) - .catch(reason => reject(reason)); - }); - } - - async setItem(key: string, value: string) { - value = this.encrypt(value); - await AsyncStorage.setItem(this.namespace + '_' + key, value); - const newSeqNum = await this.setItemRemote(key, value); - const localSeqNum = await this.getLocalSeqNum(); - if (+localSeqNum > +newSeqNum) { - // some race condition during save happened..? - return; - } - await AsyncStorage.setItem(this.namespace + '_' + 'seqnum', newSeqNum); - } - - async getItemRemote(key: string) { - const response = await fetch(this.defaultBaseUrl + '/namespace/' + this.namespace + '/' + key); - return await response.text(); - } - - async getItem(key: string) { - return this.decrypt(await AsyncStorage.getItem(this.namespace + '_' + key)); - } - - async getAllKeysRemote(): Promise { - const response = await fetch(this.defaultBaseUrl + '/namespacekeys/' + this.namespace); - const text = await response.text(); - return text.split(','); - } - - async getAllKeys(): Promise { - return (await AsyncStorage.getAllKeys()) - .filter(key => key.startsWith(this.namespace + '_')) - .map(key => key.replace(this.namespace + '_', '')); - } - - async getLocalSeqNum() { - return (await AsyncStorage.getItem(this.namespace + '_' + 'seqnum')) || '0'; - } - - async purgeLocalStorage() { - if (!this.namespace) throw new Error('No namespace'); - const keys = (await AsyncStorage.getAllKeys()).filter(key => key.startsWith(this.namespace)); - for (const key of keys) { - await AsyncStorage.removeItem(key); - } - } - - /** - * Should be called at init. - * Checks remote sequence number, and if remote is ahead - we sync all keys with local storage. - */ - async synchronize() { - const response = await fetch(this.defaultBaseUrl + '/namespaceseq/' + this.namespace); - const remoteSeqNum = (await response.text()) || '0'; - const localSeqNum = await this.getLocalSeqNum(); - if (+remoteSeqNum > +localSeqNum) { - console.log('remote storage is ahead, need to sync;', +remoteSeqNum, '>', +localSeqNum); - - // sort to ensure channel_manager comes first - for (const key of (await this.getAllKeysRemote()).sort()) { - const value = await this.getItemRemote(key); - await AsyncStorage.setItem(this.namespace + '_' + key, value); - console.log('synced', key, 'to', value); - } - - await AsyncStorage.setItem(this.namespace + '_' + 'seqnum', remoteSeqNum); - } else { - console.log('storage is up-to-date, no need for sync'); - } - } -} diff --git a/class/wallet-descriptor.ts b/class/wallet-descriptor.ts new file mode 100644 index 00000000000..11438f1db73 --- /dev/null +++ b/class/wallet-descriptor.ts @@ -0,0 +1,10 @@ +export class WalletDescriptor { + static getDescriptor(fpHex: string, path: string, xpub: string): string { + switch (true) { + case path.startsWith("m/86'"): + return `tr([${fpHex.toLowerCase()}/${path.replace('m/', '')}]${xpub})`; + default: + throw new Error('Dont know how to make a descriptor'); + } + } +} diff --git a/class/wallet-gradient.js b/class/wallet-gradient.js deleted file mode 100644 index f6bd1db8d6e..00000000000 --- a/class/wallet-gradient.js +++ /dev/null @@ -1,146 +0,0 @@ -import { LegacyWallet } from './wallets/legacy-wallet'; -import { HDSegwitP2SHWallet } from './wallets/hd-segwit-p2sh-wallet'; -import { LightningCustodianWallet } from './wallets/lightning-custodian-wallet'; -import { HDLegacyBreadwalletWallet } from './wallets/hd-legacy-breadwallet-wallet'; -import { HDLegacyP2PKHWallet } from './wallets/hd-legacy-p2pkh-wallet'; -import { WatchOnlyWallet } from './wallets/watch-only-wallet'; -import { HDSegwitBech32Wallet } from './wallets/hd-segwit-bech32-wallet'; -import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet'; -import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-seed-p2pkh-wallet'; -import { HDSegwitElectrumSeedP2WPKHWallet } from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet'; -import { MultisigHDWallet } from './wallets/multisig-hd-wallet'; -import { HDAezeedWallet } from './wallets/hd-aezeed-wallet'; -import { LightningLdkWallet } from './wallets/lightning-ldk-wallet'; -import { SLIP39LegacyP2PKHWallet, SLIP39SegwitP2SHWallet, SLIP39SegwitBech32Wallet } from './wallets/slip39-wallets'; -import { useTheme } from '@react-navigation/native'; - -export default class WalletGradient { - static hdSegwitP2SHWallet = ['#007AFF', '#0040FF']; - static hdSegwitBech32Wallet = ['#6CD9FC', '#44BEE5']; - static segwitBech32Wallet = ['#6CD9FC', '#44BEE5']; - static watchOnlyWallet = ['#474646', '#282828']; - static legacyWallet = ['#37E8C0', '#15BE98']; - static hdLegacyP2PKHWallet = ['#FD7478', '#E73B40']; - static hdLegacyBreadWallet = ['#fe6381', '#f99c42']; - static multisigHdWallet = ['#1ce6eb', '#296fc5', '#3500A2']; - static defaultGradients = ['#B770F6', '#9013FE']; - static lightningCustodianWallet = ['#F1AA07', '#FD7E37']; - static aezeedWallet = ['#8584FF', '#5351FB']; - static ldkWallet = ['#8584FF', '#5351FB']; - - static createWallet = () => { - const { colors } = useTheme(); - return colors.lightButton; - }; - - static gradientsFor(type) { - let gradient; - switch (type) { - case WatchOnlyWallet.type: - gradient = WalletGradient.watchOnlyWallet; - break; - case LegacyWallet.type: - gradient = WalletGradient.legacyWallet; - break; - case HDLegacyP2PKHWallet.type: - case HDLegacyElectrumSeedP2PKHWallet.type: - case SLIP39LegacyP2PKHWallet.type: - gradient = WalletGradient.hdLegacyP2PKHWallet; - break; - case HDLegacyBreadwalletWallet.type: - gradient = WalletGradient.hdLegacyBreadWallet; - break; - case HDSegwitP2SHWallet.type: - case SLIP39SegwitP2SHWallet.type: - gradient = WalletGradient.hdSegwitP2SHWallet; - break; - case HDSegwitBech32Wallet.type: - case HDSegwitElectrumSeedP2WPKHWallet.type: - case SLIP39SegwitBech32Wallet.type: - gradient = WalletGradient.hdSegwitBech32Wallet; - break; - case LightningCustodianWallet.type: - gradient = WalletGradient.lightningCustodianWallet; - break; - case SegwitBech32Wallet.type: - gradient = WalletGradient.segwitBech32Wallet; - break; - case MultisigHDWallet.type: - gradient = WalletGradient.multisigHdWallet; - break; - case HDAezeedWallet.type: - gradient = WalletGradient.aezeedWallet; - break; - case LightningLdkWallet.type: - gradient = WalletGradient.ldkWallet; - break; - default: - gradient = WalletGradient.defaultGradients; - break; - } - return gradient; - } - - static linearGradientProps(type) { - let props; - switch (type) { - case MultisigHDWallet.type: - /* Example - props = { start: { x: 0, y: 0 } }; - https://github.com/react-native-linear-gradient/react-native-linear-gradient - */ - break; - default: - break; - } - return props; - } - - static headerColorFor(type) { - let gradient; - switch (type) { - case WatchOnlyWallet.type: - gradient = WalletGradient.watchOnlyWallet; - break; - case LegacyWallet.type: - gradient = WalletGradient.legacyWallet; - break; - case HDLegacyP2PKHWallet.type: - case HDLegacyElectrumSeedP2PKHWallet.type: - case SLIP39LegacyP2PKHWallet.type: - gradient = WalletGradient.hdLegacyP2PKHWallet; - break; - case HDLegacyBreadwalletWallet.type: - gradient = WalletGradient.hdLegacyBreadWallet; - break; - case HDSegwitP2SHWallet.type: - case SLIP39SegwitP2SHWallet.type: - gradient = WalletGradient.hdSegwitP2SHWallet; - break; - case HDSegwitBech32Wallet.type: - case HDSegwitElectrumSeedP2WPKHWallet.type: - case SLIP39SegwitBech32Wallet.type: - gradient = WalletGradient.hdSegwitBech32Wallet; - break; - case SegwitBech32Wallet.type: - gradient = WalletGradient.segwitBech32Wallet; - break; - case MultisigHDWallet.type: - gradient = WalletGradient.multisigHdWallet; - break; - case LightningCustodianWallet.type: - gradient = WalletGradient.lightningCustodianWallet; - break; - case HDAezeedWallet.type: - gradient = WalletGradient.aezeedWallet; - break; - case LightningLdkWallet.type: - gradient = WalletGradient.ldkWallet; - break; - default: - gradient = WalletGradient.defaultGradients; - break; - } - return gradient[0]; - } -} diff --git a/class/wallet-gradient.ts b/class/wallet-gradient.ts new file mode 100644 index 00000000000..df55b342013 --- /dev/null +++ b/class/wallet-gradient.ts @@ -0,0 +1,87 @@ +import { HDAezeedWallet } from './wallets/hd-aezeed-wallet'; +import { HDLegacyBreadwalletWallet } from './wallets/hd-legacy-breadwallet-wallet'; +import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-seed-p2pkh-wallet'; +import { HDLegacyP2PKHWallet } from './wallets/hd-legacy-p2pkh-wallet'; +import { HDSegwitBech32Wallet } from './wallets/hd-segwit-bech32-wallet'; +import { HDSegwitElectrumSeedP2WPKHWallet } from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet'; +import { HDSegwitP2SHWallet } from './wallets/hd-segwit-p2sh-wallet'; +import { LegacyWallet } from './wallets/legacy-wallet'; +import { LightningCustodianWallet } from './wallets/lightning-custodian-wallet'; // Missing import +import { MultisigHDWallet } from './wallets/multisig-hd-wallet'; +import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet'; +import { SLIP39LegacyP2PKHWallet, SLIP39SegwitBech32Wallet, SLIP39SegwitP2SHWallet } from './wallets/slip39-wallets'; +import { WatchOnlyWallet } from './wallets/watch-only-wallet'; +import { TaprootWallet } from './wallets/taproot-wallet.ts'; +import { LightningArkWallet } from './wallets/lightning-ark-wallet.ts'; + +export default class WalletGradient { + static hdSegwitP2SHWallet: string[] = ['#007AFF', '#0040FF']; + static hdSegwitBech32Wallet: string[] = ['#6CD9FC', '#44BEE5']; + static segwitBech32Wallet: string[] = ['#6CD9FC', '#44BEE5']; + static watchOnlyWallet: string[] = ['#474646', '#282828']; + static legacyWallet: string[] = ['#37E8C0', '#15BE98']; + static taprootWallet: string[] = ['#4DA337', '#326D28']; + static hdLegacyP2PKHWallet: string[] = ['#FD7478', '#E73B40']; + static hdLegacyBreadWallet: string[] = ['#fe6381', '#f99c42']; + static multisigHdWallet: string[] = ['#1ce6eb', '#296fc5', '#3500A2']; + static defaultGradients: string[] = ['#B770F6', '#9013FE']; + static lightningCustodianWallet: string[] = ['#F1AA07', '#FD7E37']; // Corrected property with missing colors + static aezeedWallet: string[] = ['#8584FF', '#5351FB']; + + static createWallet = () => { + return WalletGradient.defaultGradients[0]; + }; + + static gradientsFor(type: string): string[] { + let gradient: string[]; + switch (type) { + case WatchOnlyWallet.type: + gradient = WalletGradient.watchOnlyWallet; + break; + case LegacyWallet.type: + gradient = WalletGradient.legacyWallet; + break; + case TaprootWallet.type: + gradient = WalletGradient.taprootWallet; + break; + case HDLegacyP2PKHWallet.type: + case HDLegacyElectrumSeedP2PKHWallet.type: + case SLIP39LegacyP2PKHWallet.type: + gradient = WalletGradient.hdLegacyP2PKHWallet; + break; + case HDLegacyBreadwalletWallet.type: + gradient = WalletGradient.hdLegacyBreadWallet; + break; + case HDSegwitP2SHWallet.type: + case SLIP39SegwitP2SHWallet.type: + gradient = WalletGradient.hdSegwitP2SHWallet; + break; + case HDSegwitBech32Wallet.type: + case HDSegwitElectrumSeedP2WPKHWallet.type: + case SLIP39SegwitBech32Wallet.type: + gradient = WalletGradient.hdSegwitBech32Wallet; + break; + case SegwitBech32Wallet.type: + gradient = WalletGradient.segwitBech32Wallet; + break; + case MultisigHDWallet.type: + gradient = WalletGradient.multisigHdWallet; + break; + case HDAezeedWallet.type: + gradient = WalletGradient.aezeedWallet; + break; + case LightningArkWallet.type: + case LightningCustodianWallet.type: + gradient = WalletGradient.lightningCustodianWallet; + break; + default: + gradient = WalletGradient.defaultGradients; + break; + } + return gradient; + } + + static headerColorFor(type: string): string { + return WalletGradient.gradientsFor(type)[0]; + } +} diff --git a/class/wallet-import.js b/class/wallet-import.js deleted file mode 100644 index 5367980df41..00000000000 --- a/class/wallet-import.js +++ /dev/null @@ -1,430 +0,0 @@ -import wif from 'wif'; -import bip38 from 'bip38'; - -import { - HDAezeedWallet, - HDLegacyBreadwalletWallet, - HDLegacyElectrumSeedP2PKHWallet, - HDLegacyP2PKHWallet, - HDSegwitBech32Wallet, - HDSegwitElectrumSeedP2WPKHWallet, - HDSegwitP2SHWallet, - LegacyWallet, - LightningCustodianWallet, - LightningLdkWallet, - MultisigHDWallet, - SLIP39LegacyP2PKHWallet, - SLIP39SegwitBech32Wallet, - SLIP39SegwitP2SHWallet, - SegwitBech32Wallet, - SegwitP2SHWallet, - WatchOnlyWallet, -} from '.'; -import loc from '../loc'; -import bip39WalletFormats from './bip39_wallet_formats.json'; // https://github.com/spesmilo/electrum/blob/master/electrum/bip39_wallet_formats.json -import bip39WalletFormatsBlueWallet from './bip39_wallet_formats_bluewallet.json'; - -// https://github.com/bitcoinjs/bip32/blob/master/ts-src/bip32.ts#L43 -export const validateBip32 = path => path.match(/^(m\/)?(\d+'?\/)*\d+'?$/) !== null; - -/** - * Function that starts wallet search and import process. It has async generator inside, so - * that the process can be stoped at any time. It reporst all the progress through callbacks. - * - * @param askPassphrase {bool} If true import process will call onPassword callback for wallet with optional password. - * @param searchAccounts {bool} If true import process will scan for all known derivation path from bip39_wallet_formats.json. If false it will use limited version. - * @param onProgress {function} Callback to report scanning progress - * @param onWallet {function} Callback to report wallet found - * @param onPassword {function} Callback to ask for password if needed - * @returns {{promise: Promise, stop: function}} - */ -const startImport = (importTextOrig, askPassphrase = false, searchAccounts = false, onProgress, onWallet, onPassword) => { - // state - let promiseResolve; - let promiseReject; - let running = true; // if you put it to false, internal generator stops - const wallets = []; - const promise = new Promise((resolve, reject) => { - promiseResolve = resolve; - promiseReject = reject; - }); - - // actions - const reportProgress = name => { - onProgress(name); - }; - const reportFinish = (cancelled, stopped) => { - promiseResolve({ cancelled, stopped, wallets }); - }; - const reportWallet = wallet => { - if (wallets.some(w => w.getID() === wallet.getID())) return; // do not add duplicates - wallets.push(wallet); - onWallet(wallet); - }; - const stop = () => (running = false); - - async function* importGenerator() { - // The plan: - // -3. ask for password, if needed and validate it - // -2. check if BIP38 encrypted - // -1a. check if multisig - // -1. check lightning custodian - // 0. check if its HDSegwitBech32Wallet (BIP84) - // 1. check if its HDSegwitP2SHWallet (BIP49) - // 2. check if its HDLegacyP2PKHWallet (BIP44) - // 3. check if its HDLegacyBreadwalletWallet (no BIP, just "m/0") - // 3.1 check HD Electrum legacy - // 3.2 check if its AEZEED - // 3.3 check if its SLIP39 - // 4. check if its Segwit WIF (P2SH) - // 5. check if its Legacy WIF - // 6. check if its address (watch-only wallet) - // 7. check if its private key (segwit address P2SH) TODO - // 7. check if its private key (legacy address) TODO - // 8. check if its a json array from BC-UR with multiple accounts - let text = importTextOrig.trim(); - let password; - - // BIP38 password required - if (text.startsWith('6P')) { - do { - password = await onPassword(loc.wallets.looks_like_bip38, loc.wallets.enter_bip38_password); - } while (!password); - } - - // HD BIP39 wallet password is optinal - const hd = new HDSegwitBech32Wallet(); - hd.setSecret(text); - if (askPassphrase && hd.validateMnemonic()) { - password = await onPassword(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message); - } - - // AEZEED password needs to be correct - const aezeed = new HDAezeedWallet(); - aezeed.setSecret(text); - if (await aezeed.mnemonicInvalidPassword()) { - do { - password = await onPassword('', loc.wallets.enter_bip38_password); - aezeed.setPassphrase(password); - } while (await aezeed.mnemonicInvalidPassword()); - } - - // SLIP39 wallet password is optinal - if (askPassphrase && text.includes('\n')) { - const s1 = new SLIP39SegwitP2SHWallet(); - s1.setSecret(text); - - if (s1.validateMnemonic()) { - password = await onPassword(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message); - } - } - - // ELECTRUM segwit wallet password is optinal - const electrum1 = new HDSegwitElectrumSeedP2WPKHWallet(); - electrum1.setSecret(text); - if (askPassphrase && electrum1.validateMnemonic()) { - password = await onPassword(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message); - } - - // ELECTRUM legacy wallet password is optinal - const electrum2 = new HDLegacyElectrumSeedP2PKHWallet(); - electrum2.setSecret(text); - if (askPassphrase && electrum2.validateMnemonic()) { - password = await onPassword(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message); - } - - // is it bip38 encrypted - if (text.startsWith('6P')) { - const decryptedKey = await bip38.decryptAsync(text, password); - - if (decryptedKey) { - text = wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed); - } - } - - // is it multisig? - yield { progress: 'multisignature' }; - const ms = new MultisigHDWallet(); - ms.setSecret(text); - if (ms.getN() > 0 && ms.getM() > 0) { - await ms.fetchBalance(); - yield { wallet: ms }; - } - - // is it lightning custodian? - yield { progress: 'lightning custodian' }; - if (text.startsWith('blitzhub://') || text.startsWith('lndhub://')) { - const lnd = new LightningCustodianWallet(); - if (text.includes('@')) { - const split = text.split('@'); - lnd.setBaseURI(split[1]); - lnd.setSecret(split[0]); - } - await lnd.init(); - await lnd.authorize(); - await lnd.fetchTransactions(); - await lnd.fetchUserInvoices(); - await lnd.fetchPendingTransactions(); - await lnd.fetchBalance(); - yield { wallet: lnd }; - } - - // is it LDK? - yield { progress: 'lightning' }; - if (text.startsWith('ldk://')) { - const ldk = new LightningLdkWallet(); - ldk.setSecret(text); - if (ldk.valid()) { - await ldk.init(); - yield { wallet: ldk }; - } - } - - // check bip39 wallets - yield { progress: 'bip39' }; - const hd2 = new HDSegwitBech32Wallet(); - hd2.setSecret(text); - hd2.setPassphrase(password); - if (hd2.validateMnemonic()) { - let walletFound = false; - // by default we don't try all the paths and options - const searchPaths = searchAccounts ? bip39WalletFormats : bip39WalletFormatsBlueWallet; - for (const i of searchPaths) { - // we need to skip m/0' p2pkh from default scan list. It could be a BRD wallet and will be handled later - if (i.derivation_path === "m/0'" && i.script_type === 'p2pkh') continue; - let paths; - if (i.iterate_accounts && searchAccounts) { - const basicPath = i.derivation_path.slice(0, -2); // remove 0' from the end - paths = [...Array(10).keys()].map(j => basicPath + j + "'"); // add account number - } else { - paths = [i.derivation_path]; - } - let WalletClass; - switch (i.script_type) { - case 'p2pkh': - WalletClass = HDLegacyP2PKHWallet; - break; - case 'p2wpkh-p2sh': - WalletClass = HDSegwitP2SHWallet; - break; - default: - // p2wpkh - WalletClass = HDSegwitBech32Wallet; - } - for (const path of paths) { - const wallet = new WalletClass(); - wallet.setSecret(text); - wallet.setPassphrase(password); - wallet.setDerivationPath(path); - yield { progress: `bip39 ${i.script_type} ${path}` }; - if (await wallet.wasEverUsed()) { - yield { wallet }; - walletFound = true; - } else { - break; // don't check second account if first one is empty - } - } - } - - // m/0' p2pkh is a special case. It could be regular a HD wallet or a BRD wallet. - // to decide which one is it let's compare number of transactions - const m0Legacy = new HDLegacyP2PKHWallet(); - m0Legacy.setSecret(text); - m0Legacy.setPassphrase(password); - m0Legacy.setDerivationPath("m/0'"); - yield { progress: "bip39 p2pkh m/0'" }; - // BRD doesn't support passphrase and only works with 12 words seeds - if (!password && text.split(' ').length === 12) { - const brd = new HDLegacyBreadwalletWallet(); - brd.setSecret(text); - - if (await m0Legacy.wasEverUsed()) { - await m0Legacy.fetchBalance(); - await m0Legacy.fetchTransactions(); - yield { progress: 'BRD' }; - await brd.fetchBalance(); - await brd.fetchTransactions(); - if (brd.getTransactions().length > m0Legacy.getTransactions().length) { - yield { wallet: brd }; - } else { - yield { wallet: m0Legacy }; - } - walletFound = true; - } - } else { - if (await m0Legacy.wasEverUsed()) { - yield { wallet: m0Legacy }; - walletFound = true; - } - } - - // if we havent found any wallet for this seed suggest new bech32 wallet - if (!walletFound) { - yield { wallet: hd2 }; - } - // return; - } - - yield { progress: 'wif' }; - const segwitWallet = new SegwitP2SHWallet(); - segwitWallet.setSecret(text); - if (segwitWallet.getAddress()) { - // ok its a valid WIF - let walletFound = false; - - yield { progress: 'wif p2wpkh' }; - const segwitBech32Wallet = new SegwitBech32Wallet(); - segwitBech32Wallet.setSecret(text); - if (await segwitBech32Wallet.wasEverUsed()) { - // yep, its single-address bech32 wallet - await segwitBech32Wallet.fetchBalance(); - walletFound = true; - yield { wallet: segwitBech32Wallet }; - } - - yield { progress: 'wif p2wpkh-p2sh' }; - if (await segwitWallet.wasEverUsed()) { - // yep, its single-address p2wpkh wallet - await segwitWallet.fetchBalance(); - walletFound = true; - yield { wallet: segwitWallet }; - } - - // default wallet is Legacy - yield { progress: 'wif p2pkh' }; - const legacyWallet = new LegacyWallet(); - legacyWallet.setSecret(text); - if (await legacyWallet.wasEverUsed()) { - // yep, its single-address legacy wallet - await legacyWallet.fetchBalance(); - walletFound = true; - yield { wallet: legacyWallet }; - } - - // if no wallets was ever used, import all of them - if (!walletFound) { - yield { wallet: segwitBech32Wallet }; - yield { wallet: segwitWallet }; - yield { wallet: legacyWallet }; - } - } - - // case - WIF is valid, just has uncompressed pubkey - yield { progress: 'wif p2pkh' }; - const legacyWallet = new LegacyWallet(); - legacyWallet.setSecret(text); - if (legacyWallet.getAddress()) { - await legacyWallet.fetchBalance(); - await legacyWallet.fetchTransactions(); - yield { wallet: legacyWallet }; - } - - // maybe its a watch-only address? - yield { progress: 'watch only' }; - const watchOnly = new WatchOnlyWallet(); - watchOnly.setSecret(text); - if (watchOnly.valid()) { - await watchOnly.fetchBalance(); - yield { wallet: watchOnly }; - } - - // electrum p2wpkh-p2sh - yield { progress: 'electrum p2wpkh-p2sh' }; - const el1 = new HDSegwitElectrumSeedP2WPKHWallet(); - el1.setSecret(text); - el1.setPassphrase(password); - if (el1.validateMnemonic()) { - yield { wallet: el1 }; // not fetching txs or balances, fuck it, yolo, life is too short - } - - // electrum p2wpkh-p2sh - yield { progress: 'electrum p2pkh' }; - const el2 = new HDLegacyElectrumSeedP2PKHWallet(); - el2.setSecret(text); - el2.setPassphrase(password); - if (el2.validateMnemonic()) { - yield { wallet: el2 }; // not fetching txs or balances, fuck it, yolo, life is too short - } - - // is it AEZEED? - yield { progress: 'aezeed' }; - const aezeed2 = new HDAezeedWallet(); - aezeed2.setSecret(text); - aezeed2.setPassphrase(password); - if (await aezeed2.validateMnemonicAsync()) { - yield { wallet: aezeed2 }; // not fetching txs or balances, fuck it, yolo, life is too short - } - - // if it is multi-line string, then it is probably SLIP39 wallet - // each line - one share - yield { progress: 'SLIP39' }; - if (text.includes('\n')) { - const s1 = new SLIP39SegwitP2SHWallet(); - s1.setSecret(text); - - if (s1.validateMnemonic()) { - yield { progress: 'SLIP39 p2wpkh-p2sh' }; - s1.setPassphrase(password); - if (await s1.wasEverUsed()) { - yield { wallet: s1 }; - } - - yield { progress: 'SLIP39 p2pkh' }; - const s2 = new SLIP39LegacyP2PKHWallet(); - s2.setPassphrase(password); - s2.setSecret(text); - if (await s2.wasEverUsed()) { - yield { wallet: s2 }; - } - - yield { progress: 'SLIP39 p2wpkh' }; - const s3 = new SLIP39SegwitBech32Wallet(); - s3.setSecret(text); - s3.setPassphrase(password); - yield { wallet: s3 }; - } - } - - // is it BC-UR payload with multiple accounts? - yield { progress: 'BC-UR' }; - try { - const json = JSON.parse(text); - if (Array.isArray(json)) { - for (const account of json) { - if (account.ExtPubKey && account.MasterFingerprint && account.AccountKeyPath) { - const wallet = new WatchOnlyWallet(); - wallet.setSecret(JSON.stringify(account)); - wallet.init(); - yield { wallet }; - } - } - } - } catch (_) {} - } - - // POEHALI - (async () => { - const generator = importGenerator(); - while (true) { - const next = await generator.next(); - if (!running) throw new Error('Discovery stopped'); // break if stop() has been called - if (next.value?.progress) reportProgress(next.value.progress); - if (next.value?.wallet) reportWallet(next.value.wallet); - if (next.done) break; // break if generator has been finished - } - reportFinish(); - })().catch(e => { - if (e.message === 'Cancel Pressed') { - reportFinish(true); - return; - } else if (e.message === 'Discovery stopped') { - reportFinish(undefined, true); - return; - } - promiseReject(e); - }); - - return { promise, stop }; -}; - -export default startImport; diff --git a/class/wallet-import.ts b/class/wallet-import.ts new file mode 100644 index 00000000000..350b7ba6bdd --- /dev/null +++ b/class/wallet-import.ts @@ -0,0 +1,624 @@ +import bip38 from 'bip38'; +import wif from 'wif'; + +import loc from '../loc'; +import { HDAezeedWallet } from './wallets/hd-aezeed-wallet'; +import { HDLegacyBreadwalletWallet } from './wallets/hd-legacy-breadwallet-wallet'; +import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-seed-p2pkh-wallet'; +import { HDLegacyP2PKHWallet } from './wallets/hd-legacy-p2pkh-wallet'; +import { HDSegwitBech32Wallet } from './wallets/hd-segwit-bech32-wallet'; +import { HDSegwitElectrumSeedP2WPKHWallet } from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet'; +import { HDSegwitP2SHWallet } from './wallets/hd-segwit-p2sh-wallet'; +import { HDTaprootWallet } from './wallets/hd-taproot-wallet'; +import { LegacyWallet } from './wallets/legacy-wallet'; +import { LightningCustodianWallet } from './wallets/lightning-custodian-wallet'; +import { LightningArkWallet } from './wallets/lightning-ark-wallet'; +import { MultisigHDWallet } from './wallets/multisig-hd-wallet'; +import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet'; +import { SegwitP2SHWallet } from './wallets/segwit-p2sh-wallet'; +import { SLIP39LegacyP2PKHWallet, SLIP39SegwitBech32Wallet, SLIP39SegwitP2SHWallet } from './wallets/slip39-wallets'; +import { TaprootWallet } from './wallets/taproot-wallet'; +import { WatchOnlyWallet } from './wallets/watch-only-wallet'; +import bip39WalletFormatsElectrum from './bip39_wallet_formats.json'; // https://github.com/spesmilo/electrum/blob/master/electrum/bip39_wallet_formats.json +import bip39WalletFormatsBlueWallet from './bip39_wallet_formats_bluewallet.json'; +import type { TWallet } from './wallets/types'; + +// https://github.com/bitcoinjs/bip32/blob/master/ts-src/bip32.ts#L43 +export const validateBip32 = (path: string) => path.match(/^(m\/)?(\d+'?\/)*\d+'?$/) !== null; + +// because original file bip39WalletFormatsElectrum is from Electrum X and doesn't contain p2tr wallets, we need to add it +bip39WalletFormatsElectrum.push({ + description: 'Standard BIP86 native taproot', + derivation_path: "m/86'/0'/0'", + script_type: 'p2tr', + iterate_accounts: true, +}); + +type TStatus = { + cancelled: boolean; + stopped: boolean; + wallets: TWallet[]; +}; + +export type TImport = { + promise: Promise; + stop: () => void; +}; + +/** + * Function that starts wallet search and import process. It has async generator inside, so + * that the process can be stoped at any time. It reporst all the progress through callbacks. + * + * @param askPassphrase {boolean} If true import process will call onPassword callback for wallet with optional password. + * @param searchAccounts {boolean} If true import process will scan for all known derivation path from bip39_wallet_formats.json. If false it will use limited version. + * @param onProgress {function} Callback to report scanning progress + * @param onWallet {function} Callback to report wallet found + * @param onPassword {function} Callback to ask for password if needed + * @returns {{promise: Promise, stop: function}} + */ +const startImport = ( + importTextOrig: string, + askPassphrase: boolean = false, + searchAccounts: boolean = false, + offline: boolean = false, + onProgress: (name: string) => void, + onWallet: (wallet: TWallet) => void, + onPassword: (title: string, text: string) => Promise, +): TImport => { + // state + let promiseResolve: (arg: TStatus) => void; + let promiseReject: (reason?: any) => void; + let running = true; // if you put it to false, internal generator stops + const wallets: TWallet[] = []; + const promise = new Promise((resolve, reject) => { + promiseResolve = resolve; + promiseReject = reject; + }); + + // helpers + // in offline mode all wallets are considered used + const wasUsed = async (wallet: TWallet): Promise => { + if (offline) return true; + return wallet.wasEverUsed(); + }; + const fetch = async (wallet: TWallet, balance: boolean = false, transactions: boolean = false) => { + if (offline) return; + if (balance) await wallet.fetchBalance(); + if (transactions) await wallet.fetchTransactions(); + }; + + // actions + const reportProgress = (name: string) => { + onProgress(name); + }; + const reportFinish = (cancelled: boolean = false, stopped: boolean = false) => { + promiseResolve({ cancelled, stopped, wallets }); + }; + const reportWallet = (wallet: TWallet) => { + if (wallets.some(w => w.getID() === wallet.getID())) return; // do not add duplicates + wallets.push(wallet); + onWallet(wallet); + }; + const stop = () => (running = false); + + async function* importGenerator() { + // The plan: + // -3. ask for password, if needed and validate it + // -2. check if BIP38 encrypted + // -1a. check if multisig + // -1. check lightning custodian + // 0. check if its HDSegwitBech32Wallet (BIP84) + // 1. check if its HDSegwitP2SHWallet (BIP49) + // 2. check if its HDLegacyP2PKHWallet (BIP44) + // 3. check if its HDLegacyBreadwalletWallet (no BIP, just "m/0") + // 3.1 check HD Electrum legacy + // 3.2 check if its AEZEED + // 3.3 check if its SLIP39 + // 3.4 check if its HDTaprootWallet (BIP86) + // 4. check if its Segwit WIF (P2SH) + // 4.5 check if its Taproot WIF + // 5. check if its Legacy WIF + // 6. check if its address (watch-only wallet) + // 7. check if its private key (segwit address P2SH) TODO + // 7. check if its private key (legacy address) TODO + // 8. check if its a json array from BC-UR with multiple accounts + let text = importTextOrig.trim(); + let password; + + // BIP38 password required + if (text.startsWith('6P')) { + do { + password = await onPassword(loc.wallets.looks_like_bip38, loc.wallets.enter_bip38_password); + } while (!password); + } + + // HD BIP39 wallet password is optinal + const hd = new HDSegwitBech32Wallet(); + hd.setSecret(text); + if (askPassphrase && hd.validateMnemonic()) { + password = await onPassword(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message); + } + + // AEZEED password needs to be correct + const aezeed = new HDAezeedWallet(); + aezeed.setSecret(text); + if (await aezeed.mnemonicInvalidPassword()) { + do { + password = await onPassword('', loc.wallets.enter_bip38_password); + aezeed.setPassphrase(password); + } while (await aezeed.mnemonicInvalidPassword()); + } + + // SLIP39 wallet password is optinal + if (askPassphrase && text.includes('\n')) { + const s1 = new SLIP39SegwitP2SHWallet(); + s1.setSecret(text); + + if (s1.validateMnemonic()) { + password = await onPassword(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message); + } + } + + // ELECTRUM segwit wallet password is optinal + const electrum1 = new HDSegwitElectrumSeedP2WPKHWallet(); + electrum1.setSecret(text); + if (askPassphrase && electrum1.validateMnemonic()) { + password = await onPassword(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message); + } + + // ELECTRUM legacy wallet password is optinal + const electrum2 = new HDLegacyElectrumSeedP2PKHWallet(); + electrum2.setSecret(text); + if (askPassphrase && electrum2.validateMnemonic()) { + password = await onPassword(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message); + } + + // is it bip38 encrypted + if (text.startsWith('6P') && password) { + const decryptedKey = await bip38.decryptAsync(text, password); + + if (decryptedKey) { + text = wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed); + } + } + + // is it multisig? + yield { progress: 'multisignature' }; + const ms = new MultisigHDWallet(); + ms.setSecret(text); + if (ms.getN() > 0 && ms.getM() > 0) { + await fetch(ms, true, false); + yield { wallet: ms }; + } + + // is it lightning custodian? + yield { progress: 'lightning custodian' }; + if (text.startsWith('blitzhub://') || text.startsWith('lndhub://')) { + const lnd = new LightningCustodianWallet(); + if (text.includes('@')) { + const split = text.split('@'); + lnd.setBaseURI(split[1]); + lnd.setSecret(split[0]); + } + await lnd.init(); + if (!offline) { + await lnd.authorize(); + await lnd.fetchTransactions(); + await lnd.fetchUserInvoices(); + await lnd.fetchPendingTransactions(); + await lnd.fetchBalance(); + } + yield { wallet: lnd }; + } + + // is it lightning ark wallet? + yield { progress: 'lightning ark' }; + if (text.startsWith('arkade://')) { + const ark = new LightningArkWallet(); + ark.setSecret(text); + await ark.init(); + if (!offline) { + await ark.fetchBalance(); + await ark.fetchTransactions(); + } + yield { wallet: ark }; + } + + // check bip39 wallets + yield { progress: 'bip39' }; + const hd2 = new HDSegwitBech32Wallet(); + hd2.setSecret(text); + if (password) { + hd2.setPassphrase(password); + } + if (hd2.validateMnemonic()) { + let walletFound = false; + // by default we don't try all the paths and options + const searchPaths = searchAccounts ? bip39WalletFormatsElectrum : bip39WalletFormatsBlueWallet; + for (const i of searchPaths) { + // we need to skip m/0' p2pkh from default scan list. It could be a BRD wallet and will be handled later + if (i.derivation_path === "m/0'" && i.script_type === 'p2pkh') continue; + let paths; + if (i.iterate_accounts && searchAccounts) { + const basicPath = i.derivation_path.slice(0, -2); // remove 0' from the end + paths = [...Array(10).keys()].map(j => basicPath + j + "'"); // add account number + } else { + paths = [i.derivation_path]; + } + let WalletClass; + switch (i.script_type) { + case 'p2pkh': + WalletClass = HDLegacyP2PKHWallet; + break; + case 'p2wpkh-p2sh': + WalletClass = HDSegwitP2SHWallet; + break; + case 'p2tr': + WalletClass = HDTaprootWallet; + break; + default: + // p2wpkh + WalletClass = HDSegwitBech32Wallet; + } + for (const path of paths) { + const wallet = new WalletClass(); + wallet.setSecret(text); + if (password) { + wallet.setPassphrase(password); + } + wallet.setDerivationPath(path); + yield { progress: `bip39 ${i.script_type} ${path}` }; + if (await wasUsed(wallet)) { + yield { wallet }; + walletFound = true; + } else { + break; // don't check second account if first one is empty + } + } + } + + // m/0' p2pkh is a special case. It could be regular a HD wallet or a BRD wallet. + // to decide which one is it let's compare number of transactions + const m0Legacy = new HDLegacyP2PKHWallet(); + m0Legacy.setSecret(text); + if (password) { + m0Legacy.setPassphrase(password); + } + m0Legacy.setDerivationPath("m/0'"); + yield { progress: "bip39 p2pkh m/0'" }; + // BRD doesn't support passphrase and only works with 12 words seeds + // do not try to guess BRD wallet in offline mode + if (!password && text.split(' ').length === 12 && !offline) { + const brd = new HDLegacyBreadwalletWallet(); + brd.setSecret(text); + + if (await wasUsed(m0Legacy)) { + await m0Legacy.fetchBalance(); + await m0Legacy.fetchTransactions(); + yield { progress: 'BRD' }; + await brd.fetchBalance(); + await brd.fetchTransactions(); + if (brd.getTransactions().length > m0Legacy.getTransactions().length) { + yield { wallet: brd }; + } else { + yield { wallet: m0Legacy }; + } + walletFound = true; + } + } else { + if (await wasUsed(m0Legacy)) { + yield { wallet: m0Legacy }; + walletFound = true; + } + } + + // if we havent found any wallet for this seed suggest new bech32 wallet + if (!walletFound) { + yield { wallet: hd2 }; + } + } + + yield { progress: 'wif' }; + + const segwitWallet = new SegwitP2SHWallet(); + segwitWallet.setSecret(text); + if (segwitWallet.getAddress()) { + // ok its a valid WIF + let walletFound = false; + + yield { progress: 'wif p2wpkh' }; + const segwitBech32Wallet = new SegwitBech32Wallet(); + segwitBech32Wallet.setSecret(text); + if (await wasUsed(segwitBech32Wallet)) { + // yep, its single-address bech32 wallet + await fetch(segwitBech32Wallet, true); + walletFound = true; + yield { wallet: segwitBech32Wallet }; + } + + yield { progress: 'wif p2tr' }; + const taprootWallet = new TaprootWallet(); + taprootWallet.setSecret(text); + if (await wasUsed(taprootWallet)) { + // yep, its single-address taproot wallet + await fetch(taprootWallet, true); + walletFound = true; + yield { wallet: taprootWallet }; + } + + yield { progress: 'wif p2wpkh-p2sh' }; + if (await wasUsed(segwitWallet)) { + // yep, its single-address p2wpkh wallet + await fetch(segwitWallet, true); + walletFound = true; + yield { wallet: segwitWallet }; + } + + // default wallet is Legacy + yield { progress: 'wif p2pkh' }; + const legacyWallet = new LegacyWallet(); + legacyWallet.setSecret(text); + if (await wasUsed(legacyWallet)) { + // yep, its single-address legacy wallet + await fetch(legacyWallet, true); + walletFound = true; + yield { wallet: legacyWallet }; + } + + // if no wallets was ever used, import all of them + if (!walletFound) { + yield { wallet: segwitBech32Wallet }; + yield { wallet: segwitWallet }; + yield { wallet: legacyWallet }; + yield { wallet: taprootWallet }; + } + } + + // case - WIF is valid, just has uncompressed pubkey + yield { progress: 'wif p2pkh' }; + const legacyWallet = new LegacyWallet(); + legacyWallet.setSecret(text); + if (legacyWallet.getAddress()) { + await fetch(legacyWallet, true, true); + 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(); + wo1.setSecret(text); + if (wo1.valid()) { + wo1.init(); + if (text.startsWith('xpub')) { + // for xpub we also check ypub and zpub. If any of them was used, we import it. + let found = false; + const pubs = [text, wo1._xpubToYpub(text), wo1._xpubToZpub(text)]; + for (const pub of pubs) { + const wo2 = new WatchOnlyWallet(); + wo2.setSecret(pub); + wo2.init(); + if (await wasUsed(wo2)) { + yield { wallet: wo2 }; + found = true; + } + } + if (!found) { + await fetch(wo1, true); + yield { wallet: wo1 }; + } + } else { + await fetch(wo1, true); + yield { wallet: wo1 }; + } + } + + // electrum p2wpkh-p2sh + yield { progress: 'electrum p2wpkh-p2sh' }; + const el1 = new HDSegwitElectrumSeedP2WPKHWallet(); + el1.setSecret(text); + if (password) { + el1.setPassphrase(password); + } + if (el1.validateMnemonic()) { + yield { wallet: el1 }; // not fetching txs or balances, fuck it, yolo, life is too short + } + + // electrum p2wpkh-p2sh + yield { progress: 'electrum p2pkh' }; + const el2 = new HDLegacyElectrumSeedP2PKHWallet(); + el2.setSecret(text); + if (password) { + el2.setPassphrase(password); + } + if (el2.validateMnemonic()) { + yield { wallet: el2 }; // not fetching txs or balances, fuck it, yolo, life is too short + } + + // is it AEZEED? + yield { progress: 'aezeed' }; + const aezeed2 = new HDAezeedWallet(); + aezeed2.setSecret(text); + if (password) { + aezeed2.setPassphrase(password); + } + if (await aezeed2.validateMnemonicAsync()) { + yield { wallet: aezeed2 }; // not fetching txs or balances, fuck it, yolo, life is too short + } + + // Let's try SLIP39 + yield { progress: 'SLIP39' }; + const s1 = new SLIP39SegwitP2SHWallet(); + s1.setSecret(text); + + if (s1.validateMnemonic()) { + yield { progress: 'SLIP39 p2wpkh-p2sh' }; + if (password) { + s1.setPassphrase(password); + } + if (await wasUsed(s1)) { + yield { wallet: s1 }; + } + + yield { progress: 'SLIP39 p2pkh' }; + const s2 = new SLIP39LegacyP2PKHWallet(); + if (password) { + s2.setPassphrase(password); + } + s2.setSecret(text); + if (await wasUsed(s2)) { + yield { wallet: s2 }; + } + + yield { progress: 'SLIP39 p2wpkh' }; + const s3 = new SLIP39SegwitBech32Wallet(); + s3.setSecret(text); + if (password) { + s3.setPassphrase(password); + } + yield { wallet: s3 }; + } + + // is it BC-UR payload with multiple accounts? + yield { progress: 'BC-UR' }; + try { + const json = JSON.parse(text); + if (Array.isArray(json)) { + for (const account of json) { + if (account.ExtPubKey && account.MasterFingerprint && account.AccountKeyPath) { + const wallet = new WatchOnlyWallet(); + wallet.setSecret(JSON.stringify(account)); + wallet.init(); + yield { wallet }; + } + } + } + } catch (_) {} + + // is it a generic JSON with multiple accounts? + yield { progress: 'multi-account generic JSON' }; + try { + const json = JSON.parse(text); + + if (json.chain === 'BTC' && json.xfp) { + for (const account of ['bip86', 'bip84', 'bip49', 'bip44']) { + if (json[account] && json[account].desc) { + const wallet = new WatchOnlyWallet(); + wallet.setSecret(json[account].desc); + wallet.init(); + yield { wallet }; + } + } + } + } catch (_) {} + } + + // POEHALI + (async () => { + const generator = importGenerator(); + while (true) { + const next = await generator.next(); + if (!running) throw new Error('Discovery stopped'); // break if stop() has been called + if (next.value?.progress) reportProgress(next.value.progress); + if (next.value?.wallet) reportWallet(next.value.wallet); + if (next.done) break; // break if generator has been finished + await new Promise(resolve => setTimeout(resolve, 1)); // try not to block the thread + } + reportFinish(); + })().catch(e => { + if (e.message === 'Cancel Pressed') { + reportFinish(true); + return; + } else if (e.message === 'Discovery stopped') { + reportFinish(undefined, true); + return; + } + promiseReject(e); + }); + + return { promise, stop }; +}; + +export default startImport; diff --git a/class/wallets/abstract-hd-electrum-wallet.ts b/class/wallets/abstract-hd-electrum-wallet.ts index 463401dcc04..f14e77b4784 100644 --- a/class/wallets/abstract-hd-electrum-wallet.ts +++ b/class/wallets/abstract-hd-electrum-wallet.ts @@ -1,27 +1,26 @@ /* eslint react/prop-types: "off", @typescript-eslint/ban-ts-comment: "off", camelcase: "off" */ -import * as bip39 from 'bip39'; +import BIP47Factory, { BIP47Interface } from '@spsina/bip47'; +import assert from 'assert'; import BigNumber from 'bignumber.js'; -import b58 from 'bs58check'; import BIP32Factory, { BIP32Interface } from 'bip32'; - -import { ECPairInterface } from 'ecpair/src/ecpair'; +import * as bip39 from 'bip39'; +import * as bitcoin from 'bitcoinjs-lib'; import { Psbt, Transaction as BTransaction } from 'bitcoinjs-lib'; -import { CoinSelectReturnInput, CoinSelectTarget } from 'coinselect'; -import ecc from '../../blue_modules/noble_ecc'; - -import BIP47Factory, { BIP47Interface } from '@spsina/bip47'; -import { ECPairFactory } from 'ecpair'; +import b58 from 'bs58check'; +import { CoinSelectOutput, CoinSelectReturnInput } from 'coinselect'; +import { ECPairFactory, ECPairInterface } from 'ecpair'; +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; +import { ElectrumHistory } from '../../blue_modules/BlueElectrum'; +import ecc from '../../blue_modules/noble_ecc'; +import { hexToUint8Array, concatUint8Arrays, uint8ArrayToHex } from '../../blue_modules/uint8array-extras'; import { randomBytes } from '../rng'; import { AbstractHDWallet } from './abstract-hd-wallet'; -import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types'; -import { ElectrumHistory } from '../../blue_modules/BlueElectrum'; -import type BlueElectrumNs from '../../blue_modules/BlueElectrum'; +import { CreateTransactionResult, CreateTransactionTarget, CreateTransactionUtxo, Transaction, Utxo } from './types'; +import { SilentPayment, UTXOType as SPUTXOType, UTXO as SPUTXO } from 'silent-payments'; +import { isValidBech32Address } from '../../util/isValidBech32Address.ts'; const ECPair = ECPairFactory(ecc); -const bitcoin = require('bitcoinjs-lib'); -const BlueElectrum: typeof BlueElectrumNs = require('../../blue_modules/BlueElectrum'); -const reverse = require('buffer-reverse'); const bip32 = BIP32Factory(ecc); const bip47 = BIP47Factory(ecc); @@ -34,10 +33,14 @@ type BalanceByIndex = { * Electrum - means that it utilizes Electrum protocol for blockchain data */ export class AbstractHDElectrumWallet extends AbstractHDWallet { - static type = 'abstract'; - static typeReadable = 'abstract'; + static readonly type = 'abstract'; + static readonly typeReadable = 'abstract'; static defaultRBFSequence = 2147483648; // 1 << 31, minimum for replaceable transactions as per BIP68 static finalRBFSequence = 4294967295; // 0xFFFFFFFF + // @ts-ignore: override + public readonly type = AbstractHDElectrumWallet.type; + // @ts-ignore: override + public readonly typeReadable = AbstractHDElectrumWallet.typeReadable; _balances_by_external_index: Record; _balances_by_internal_index: Record; @@ -48,14 +51,49 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { _txs_by_internal_index: Record; _utxo: any[]; + _fp: string; // BIP47 _enable_BIP47: boolean; _payment_code: string; - _sender_payment_codes: string[]; - _addresses_by_payment_code: Record; - _next_free_payment_code_address_index: Record; + + /** + * payment codes of people who can pay us + */ + _receive_payment_codes: string[]; + + /** + * payment codes of people whom we can pay + */ + _send_payment_codes: string[]; + + /** + * joint addresses with remote counterparties, to receive funds + */ + _addresses_by_payment_code_receive: Record; + + /** + * receive index + */ + _next_free_payment_code_address_index_receive: Record; + + /** + * joint addresses with remote counterparties, whom we can send funds + */ + _addresses_by_payment_code_send: Record; + + /** + * send index + */ + _next_free_payment_code_address_index_send: Record; + + /** + * this is where we put transactions related to our PC receive addresses. this is both + * incoming transactions AND outgoing transactions (when we spend those funds) + * + */ _txs_by_payment_code_index: Record; + _balances_by_payment_code_index: Record; _bip47_instance?: BIP47Interface; @@ -72,11 +110,17 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { // BIP47 this._enable_BIP47 = false; this._payment_code = ''; - this._sender_payment_codes = []; - this._next_free_payment_code_address_index = {}; + this._receive_payment_codes = []; + this._send_payment_codes = []; + this._next_free_payment_code_address_index_receive = {}; this._txs_by_payment_code_index = {}; + this._addresses_by_payment_code_send = {}; + this._next_free_payment_code_address_index_send = {}; this._balances_by_payment_code_index = {}; - this._addresses_by_payment_code = {}; + this._addresses_by_payment_code_receive = {}; + + // cache + this._fp = ''; } /** @@ -90,7 +134,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { for (const bal of Object.values(this._balances_by_internal_index)) { ret += bal.c; } - for (const pc of this._sender_payment_codes) { + for (const pc of this._receive_payment_codes) { ret += this._getBalancesByPaymentCodeIndex(pc).c; } return ret + (this.getUnconfirmedBalance() < 0 ? this.getUnconfirmedBalance() : 0); @@ -108,21 +152,31 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { for (const bal of Object.values(this._balances_by_internal_index)) { ret += bal.u; } - for (const pc of this._sender_payment_codes) { + for (const pc of this._receive_payment_codes) { ret += this._getBalancesByPaymentCodeIndex(pc).u; } return ret; } + getBalanceForExternalIndex(index: number): number { + const bal = this._balances_by_external_index[index]; + return (bal?.c || 0) + (bal?.u || 0); + } + + getTransactionCountForExternalIndex(index: number): number { + return this._txs_by_external_index[index]?.length ?? 0; + } + async generate() { const buf = await randomBytes(16); - this.secret = bip39.entropyToMnemonic(buf.toString('hex')); + this.secret = bip39.entropyToMnemonic(uint8ArrayToHex(buf)); } - async generateFromEntropy(user: Buffer) { - const random = await randomBytes(user.length < 32 ? 32 - user.length : 0); - const buf = Buffer.concat([user, random], 32); - this.secret = bip39.entropyToMnemonic(buf.toString('hex')); + async generateFromEntropy(user: Uint8Array) { + if (user.length !== 32 && user.length !== 16) { + throw new Error('Entropy has to be 16 or 32 bytes long'); + } + this.secret = bip39.entropyToMnemonic(uint8ArrayToHex(user)); } _getExternalWIFByIndex(index: number): string | false { @@ -248,8 +302,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { // bitcoinjs does not support zpub yet, so we just convert it from xpub let data = b58.decode(xpub); data = data.slice(4); - data = Buffer.concat([Buffer.from('04b24746', 'hex'), data]); - this._xpub = b58.encode(data); + const concatenated = concatUint8Arrays([hexToUint8Array('04b24746'), data]); + this._xpub = b58.encode(concatenated); return this._xpub; } @@ -270,8 +324,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { // then we combine it all together const addresses2fetch = []; + // Store these values to avoid a race condition if fetchBalance func changes them + const next_free_address_index = this.next_free_address_index; + const next_free_change_address_index = this.next_free_change_address_index; - for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { + for (let c = 0; c < next_free_address_index + this.gap_limit; c++) { // external addresses first let hasUnconfirmed = false; this._txs_by_external_index[c] = this._txs_by_external_index[c] || []; @@ -282,7 +339,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } } - for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { + for (let c = 0; c < next_free_change_address_index + this.gap_limit; c++) { // next, internal addresses let hasUnconfirmed = false; this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || []; @@ -294,8 +351,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } // next, bip47 addresses - for (const pc of this._sender_payment_codes) { - for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) { + for (const pc of this._receive_payment_codes) { + for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + this.gap_limit; c++) { let hasUnconfirmed = false; 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] || []; @@ -303,7 +360,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { hasUnconfirmed = hasUnconfirmed || !tx.confirmations || tx.confirmations < 7; if (hasUnconfirmed || this._txs_by_payment_code_index[pc][c].length === 0 || this._balances_by_payment_code_index[pc].u !== 0) { - addresses2fetch.push(this._getBIP47Address(pc, c)); + addresses2fetch.push(this._getBIP47AddressReceive(pc, c)); } } } @@ -318,17 +375,21 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } // next, batch fetching each txid we got - const txdatas = await BlueElectrum.multiGetTransactionByTxid(Object.keys(txs)); + const txdatas = await BlueElectrum.multiGetTransactionByTxid(Object.keys(txs), true); // now, tricky part. we collect all transactions from inputs (vin), and batch fetch them too. // then we combine all this data (we need inputs to see source addresses and amounts) const vinTxids = []; for (const txdata of Object.values(txdatas)) { + if (txdata.vin.length > 99) continue; + // ^^^ cutoff, some transactions have thousands of inputs, so the resulting array of txs for inputs to fetch + // might be dozens of thousands. too much to handle, so we skip such transactions for (const vin of txdata.vin) { - vinTxids.push(vin.txid); + vin.txid && vinTxids.push(vin.txid); + // ^^^^ not all inputs have txid, some of them are Coinbase (newly-created coins) } } - const vintxdatas = await BlueElectrum.multiGetTransactionByTxid(vinTxids); + const vintxdatas = await BlueElectrum.multiGetTransactionByTxid(vinTxids, true); // fetched all transactions from our inputs. now we need to combine it. // iterating all _our_ transactions: @@ -348,28 +409,34 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { // now purge all unconfirmed txs from internal hashmaps, since some may be evicted from mempool because they became invalid // or replaced. hashmaps are going to be re-populated anyways, since we fetched TXs for addresses with unconfirmed TXs - for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { + for (let c = 0; c < next_free_address_index + this.gap_limit; c++) { this._txs_by_external_index[c] = this._txs_by_external_index[c].filter(tx => !!tx.confirmations); } - for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { + for (let c = 0; c < next_free_change_address_index + this.gap_limit; c++) { this._txs_by_internal_index[c] = this._txs_by_internal_index[c].filter(tx => !!tx.confirmations); } - for (const pc of this._sender_payment_codes) { - for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) { + for (const pc of this._receive_payment_codes) { + for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + this.gap_limit; c++) { this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c].filter(tx => !!tx.confirmations); } } - // now, we need to put transactions in all relevant `cells` of internal hashmaps: this._txs_by_internal_index && this._txs_by_external_index + // 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 - for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { + 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) }; + 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; @@ -387,7 +454,12 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { // 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) }; + 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; @@ -403,14 +475,19 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } } - for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { + 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) }; + 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; @@ -428,7 +505,12 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { // 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) }; + 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; @@ -444,16 +526,22 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } } - for (const pc of this._sender_payment_codes) { - for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) { + 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)) { - for (const vin of tx.vin) { - if (vin.addresses && vin.addresses.indexOf(this._getBIP47Address(pc, c)) !== -1) { + // 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) }; + 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; @@ -466,25 +554,6 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { if (!replaced) this._txs_by_payment_code_index[pc][c].push(clonedTx); } } - for (const vout of tx.vout) { - if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses.indexOf(this._getBIP47Address(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) }; - - // 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); - } - } } } } @@ -501,8 +570,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { for (const addressTxs of Object.values(this._txs_by_internal_index)) { txs = txs.concat(addressTxs); } - if (this._sender_payment_codes) { - for (const pc of this._sender_payment_codes) { + if (this._receive_payment_codes) { + for (const pc of this._receive_payment_codes) { if (this._txs_by_payment_code_index[pc]) for (const addressTxs of Object.values(this._txs_by_payment_code_index[pc])) { txs = txs.concat(addressTxs); @@ -521,10 +590,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { for (let c = 0; c < this.next_free_change_address_index + 1; c++) { ownedAddressesHashmap[this._getInternalAddressByIndex(c)] = true; } - if (this._sender_payment_codes) - for (const pc of this._sender_payment_codes) { - for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + 1; c++) { - ownedAddressesHashmap[this._getBIP47Address(pc, c)] = true; + if (this._receive_payment_codes) + for (const pc of this._receive_payment_codes) { + for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + 1; c++) { + ownedAddressesHashmap[this._getBIP47AddressReceive(pc, c)] = true; } } // hack: in case this code is called from LegacyWallet: @@ -532,8 +601,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { const ret: Transaction[] = []; for (const tx of txs) { - tx.received = tx.blocktime * 1000; - if (!tx.blocktime) tx.received = +new Date() - 30 * 1000; // unconfirmed + tx.timestamp = tx.blocktime; + if (!tx.blocktime) tx.timestamp = Math.floor(+new Date() / 1000) - 30; // unconfirmed tx.confirmations = tx.confirmations || 0; // unconfirmed tx.hash = tx.txid; tx.value = 0; @@ -551,6 +620,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { tx.value += new BigNumber(vout.value).multipliedBy(100000000).toNumber(); } } + + if (this.allowBIP47() && this.isBIP47Enabled()) { + tx.counterparty = this.getBip47CounterpartyByTx(tx); + } ret.push(tx); } @@ -563,7 +636,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } return ret2.sort(function (a, b) { - return Number(b.received) - Number(a.received); + return Number(b.timestamp) - Number(a.timestamp); }); } @@ -657,7 +730,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { const generateChunkAddresses = (chunkNum: number) => { const ret = []; for (let c = this.gap_limit * chunkNum; c < this.gap_limit * (chunkNum + 1); c++) { - ret.push(this._getBIP47Address(paymentCode, c)); + ret.push(this._getBIP47AddressReceive(paymentCode, c)); } return ret; }; @@ -686,7 +759,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { c < Number(lastChunkWithUsedAddressesNum) * this.gap_limit + this.gap_limit; c++ ) { - const address = this._getBIP47Address(paymentCode, c); + const address = this._getBIP47AddressReceive(paymentCode, c); if (lastHistoriesWithUsedAddresses[address] && lastHistoriesWithUsedAddresses[address].length > 0) { lastUsedIndex = Math.max(c, lastUsedIndex) + 1; // point to next, which is supposed to be unused } @@ -702,9 +775,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { // doing binary search for last used address: this.next_free_change_address_index = await this._binarySearchIterationForInternalAddress(1000); this.next_free_address_index = await this._binarySearchIterationForExternalAddress(1000); - if (this._sender_payment_codes) { - for (const pc of this._sender_payment_codes) { - this._next_free_payment_code_address_index[pc] = await this._binarySearchIterationForBIP47Address(pc, 1000); + if (this._receive_payment_codes) { + for (const pc of this._receive_payment_codes) { + this._next_free_payment_code_address_index_receive[pc] = await this._binarySearchIterationForBIP47Address(pc, 1000); } } } // end rescanning fresh wallet @@ -728,13 +801,13 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { for (let c = this.next_free_change_address_index; c < this.next_free_change_address_index + this.gap_limit; c++) { lagAddressesToFetch.push(this._getInternalAddressByIndex(c)); } - for (const pc of this._sender_payment_codes) { + for (const pc of this._receive_payment_codes) { for ( - let c = this._next_free_payment_code_address_index[pc]; - c < this._next_free_payment_code_address_index[pc] + this.gap_limit; + let c = this._next_free_payment_code_address_index_receive[pc]; + c < this._next_free_payment_code_address_index_receive[pc] + this.gap_limit; c++ ) { - lagAddressesToFetch.push(this._getBIP47Address(pc, c)); + lagAddressesToFetch.push(this._getBIP47AddressReceive(pc, c)); } } @@ -756,16 +829,16 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } } - for (const pc of this._sender_payment_codes) { + for (const pc of this._receive_payment_codes) { for ( - let c = this._next_free_payment_code_address_index[pc]; - c < this._next_free_payment_code_address_index[pc] + this.gap_limit; + let c = this._next_free_payment_code_address_index_receive[pc]; + c < this._next_free_payment_code_address_index_receive[pc] + this.gap_limit; c++ ) { - const address = this._getBIP47Address(pc, c); + const address = this._getBIP47AddressReceive(pc, c); if (txs[address] && Array.isArray(txs[address]) && txs[address].length > 0) { // whoa, someone uses our wallet outside! better catch up - this._next_free_payment_code_address_index[pc] = c + 1; + this._next_free_payment_code_address_index_receive[pc] = c + 1; } } } @@ -788,9 +861,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { addresses2fetch.push(this._getInternalAddressByIndex(c)); } - for (const pc of this._sender_payment_codes) { - for (let c = 0; c < this._next_free_payment_code_address_index[pc] + this.gap_limit; c++) { - addresses2fetch.push(this._getBIP47Address(pc, c)); + for (const pc of this._receive_payment_codes) { + for (let c = 0; c < this._next_free_payment_code_address_index_receive[pc] + this.gap_limit; c++) { + addresses2fetch.push(this._getBIP47AddressReceive(pc, c)); } } @@ -838,11 +911,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } } - for (const pc of this._sender_payment_codes) { + for (const pc of this._receive_payment_codes) { let confirmed = 0; let unconfirmed = 0; - for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) { - const addr = this._getBIP47Address(pc, c); + for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + this.gap_limit; c++) { + const addr = this._getBIP47AddressReceive(pc, c); if (balances.addresses[addr].confirmed || balances.addresses[addr].unconfirmed) { confirmed = confirmed + balances.addresses[addr].confirmed; unconfirmed = unconfirmed + balances.addresses[addr].unconfirmed; @@ -857,7 +930,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { this._lastBalanceFetch = +new Date(); } - async fetchUtxo() { + async fetchUtxo(): Promise { // fetching utxo of addresses that only have some balance let addressess = []; @@ -873,10 +946,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } } - for (const pc of this._sender_payment_codes) { - for (let c = 0; c < this._next_free_payment_code_address_index[pc] + this.gap_limit; c++) { + for (const pc of this._receive_payment_codes) { + for (let c = 0; c < this._next_free_payment_code_address_index_receive[pc] + this.gap_limit; c++) { if (this._balances_by_payment_code_index?.[pc]?.c > 0) { - addressess.push(this._getBIP47Address(pc, c)); + addressess.push(this._getBIP47AddressReceive(pc, c)); } } } @@ -893,10 +966,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } } - for (const pc of this._sender_payment_codes) { - for (let c = 0; c < this._next_free_payment_code_address_index[pc] + this.gap_limit; c++) { + for (const pc of this._receive_payment_codes) { + for (let c = 0; c < this._next_free_payment_code_address_index_receive[pc] + this.gap_limit; c++) { if (this._balances_by_payment_code_index?.[pc]?.u > 0) { - addressess.push(this._getBIP47Address(pc, c)); + addressess.push(this._getBIP47AddressReceive(pc, c)); } } } @@ -913,17 +986,13 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { this._utxo = this._utxo.concat(arr); } - // backward compatibility TODO: remove when we make sure `.utxo` is not used - this.utxo = this._utxo; // this belongs in `.getUtxo()` - for (const u of this.utxo) { - u.txid = u.txId; - u.amount = u.value; + for (const u of this._utxo) { u.wif = this._getWifForAddress(u.address); if (!u.confirmations && u.height) u.confirmations = BlueElectrum.estimateCurrentBlockheight() - u.height; } - this.utxo = this.utxo.sort((a, b) => Number(a.amount) - Number(b.amount)); + this._utxo = this._utxo.sort((a, b) => Number(a.value) - Number(b.value)); // more consistent, so txhex in unit tests wont change } @@ -932,10 +1001,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { * [ { height: 0, * value: 666, * address: 'string', - * txId: 'string', * vout: 1, * txid: 'string', - * amount: 666, * wif: 'string', * confirmations: 0 } ] * @@ -968,9 +1035,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { for (let c = 0; c < this.next_free_change_address_index + 1; c++) { ownedAddressesHashmap[this._getInternalAddressByIndex(c)] = true; } - for (const pc of this._sender_payment_codes) { - for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + 1; c++) { - ownedAddressesHashmap[this._getBIP47Address(pc, c)] = true; + for (const pc of this._receive_payment_codes) { + for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + 1; c++) { + ownedAddressesHashmap[this._getBIP47AddressReceive(pc, c)] = true; } } @@ -984,11 +1051,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { const value = new BigNumber(output.value).multipliedBy(100000000).toNumber(); utxos.push({ txid: tx.txid, - txId: tx.txid, vout: output.n, address: String(address), value, - amount: value, confirmations: tx.confirmations, wif: false, height: BlueElectrum.estimateCurrentBlockheight() - (tx.confirmations ?? 0), @@ -1000,10 +1065,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { if (returnSpentUtxoAsWell) return utxos; // got all utxos we ever had. lets filter out the ones that are spent: + const txs = this.getTransactions(); const ret = []; for (const utxo of utxos) { let spent = false; - for (const tx of this.getTransactions()) { + for (const tx of txs) { for (const input of tx.inputs) { if (input.txid === utxo.txid && input.vout === utxo.vout) spent = true; // utxo we got previously was actually spent right here ^^ @@ -1028,10 +1094,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { if (this._getInternalAddressByIndex(c) === address) return path + '/1/' + c; } - for (const pc of this._sender_payment_codes) { - for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) { + for (const pc of this._receive_payment_codes) { + for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + this.gap_limit; c++) { // not technically correct but well, to have at least somethign in PSBT... - if (this._getBIP47Address(pc, c) === address) return "m/47'/0'/0'/" + c; + if (this._getBIP47AddressReceive(pc, c) === address) return "m/47'/0'/0'/" + c; } } @@ -1041,18 +1107,18 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { /** * * @param address {string} Address that belongs to this wallet - * @returns {Buffer|false} Either buffer with pubkey or false + * @returns {Uint8Array|false} Either Uint8Array with pubkey or false */ - _getPubkeyByAddress(address: string): Buffer | false { + _getPubkeyByAddress(address: string): Uint8Array | false { for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { if (this._getExternalAddressByIndex(c) === address) return this._getNodePubkeyByIndex(0, c); } for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { if (this._getInternalAddressByIndex(c) === address) return this._getNodePubkeyByIndex(1, c); } - for (const pc of this._sender_payment_codes) { - for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) { - if (this._getBIP47Address(pc, c) === address) return this._getBIP47PubkeyByIndex(pc, c); + for (const pc of this._receive_payment_codes) { + for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + this.gap_limit; c++) { + if (this._getBIP47AddressReceive(pc, c) === address) return this._getBIP47PubkeyByIndex(pc, c); } } @@ -1072,9 +1138,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { if (this._getInternalAddressByIndex(c) === address) return this._getWIFByIndex(true, c); } - for (const pc of this._sender_payment_codes) { - for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) { - if (this._getBIP47Address(pc, c) === address) return this._getBIP47WIF(pc, c); + for (const pc of this._receive_payment_codes) { + for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + this.gap_limit; c++) { + if (this._getBIP47AddressReceive(pc, c) === address) return this._getBIP47WIF(pc, c); } } return false; @@ -1084,8 +1150,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { if (!address) return false; let cleanAddress = address; - if (this.segwitType === 'p2wpkh') { - cleanAddress = address.toLowerCase(); + const isBech32Address = isValidBech32Address(address); + + if (isBech32Address) { + cleanAddress = address.toLocaleLowerCase(); } for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { @@ -1094,9 +1162,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { if (this._getInternalAddressByIndex(c) === cleanAddress) return true; } - for (const pc of this._sender_payment_codes) { - for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) { - if (this._getBIP47Address(pc, c) === address) return true; + for (const pc of this._receive_payment_codes) { + for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + this.gap_limit; c++) { + if (this._getBIP47AddressReceive(pc, c) === address) return true; } } return false; @@ -1104,7 +1172,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { /** * - * @param utxos {Array.<{vout: Number, value: Number, txId: String, address: String}>} List of spendable utxos + * @param utxos {Array.<{vout: Number, value: Number, txid: String, address: String}>} List of spendable utxos * @param targets {Array.<{value: Number, address: String}>} Where coins are going. If theres only 1 target and that target has no value - this will send MAX to that address (respecting fee rate) * @param feeRate {Number} satoshi per byte * @param changeAddress {String} Excessive coins will go back to that address @@ -1115,33 +1183,44 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { */ createTransaction( utxos: CreateTransactionUtxo[], - targets: CoinSelectTarget[], + targets: CreateTransactionTarget[], feeRate: number, changeAddress: string, - sequence: number, + sequence: number = AbstractHDElectrumWallet.defaultRBFSequence, skipSigning = false, - masterFingerprint: number, + masterFingerprint: number = 0, ): CreateTransactionResult { if (targets.length === 0) throw new Error('No destination provided'); - // compensating for coinselect inability to deal with segwit inputs, and overriding script length for proper vbytes calculation - for (const u of utxos) { - // this is a hacky way to distinguish native/wrapped segwit, but its good enough for our case since we have only - // those 2 wallet types - if (this._getExternalAddressByIndex(0).startsWith('bc1')) { - u.script = { length: 27 }; - } else if (this._getExternalAddressByIndex(0).startsWith('3')) { - u.script = { length: 50 }; + + let { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate); + + const hasSilentPaymentOutput: boolean = !!outputs.find(o => o.address?.startsWith('sp1')); + if (hasSilentPaymentOutput) { + if (!this.allowSilentPaymentSend()) { + throw new Error('This wallet can not send to SilentPayment address'); } - } - for (const t of targets) { - if (t.address.startsWith('bc1')) { - // in case address is non-typical and takes more bytes than coinselect library anticipates by default - t.script = { length: bitcoin.address.toOutputScript(t.address).length + 3 }; + // for a single wallet all utxos gona be the same type, so we define it only once: + let utxoType: SPUTXOType = 'non-eligible'; + switch (this.segwitType) { + case 'p2tr': + utxoType = 'p2tr'; + break; + case 'p2sh(p2wpkh)': + utxoType = 'p2sh-p2wpkh'; + break; + case 'p2wpkh': + utxoType = 'p2wpkh'; + break; + default: + // @ts-ignore override + if (this.type === 'HDlegacyP2PKH') utxoType = 'p2pkh'; } - } - const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress); + const spUtxos: SPUTXO[] = inputs.map(u => ({ ...u, utxoType, wif: u.wif! })); + const sp = new SilentPayment(); + outputs = sp.createTransaction(spUtxos, outputs) as CoinSelectOutput[]; + } sequence = sequence || AbstractHDElectrumWallet.defaultRBFSequence; let psbt = new bitcoin.Psbt(); @@ -1167,10 +1246,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { if (masterFingerprint) { let masterFingerprintHex = Number(masterFingerprint).toString(16); if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte - const hexBuffer = Buffer.from(masterFingerprintHex, 'hex'); - masterFingerprintBuffer = Buffer.from(reverse(hexBuffer)); + const hexBuffer = hexToUint8Array(masterFingerprintHex); + masterFingerprintBuffer = hexBuffer.reverse(); } else { - masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); + masterFingerprintBuffer = new Uint8Array([0x00, 0x00, 0x00, 0x00]); } // this is not correct fingerprint, as we dont know real fingerprint - we got zpub with 84/0, but fingerpting // should be from root. basically, fingerprint should be provided from outside by user when importing zpub @@ -1179,9 +1258,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { }); outputs.forEach(output => { - // if output has no address - this is change output + // if output has no address - this is change output or a custom script output let change = false; - if (!output.address) { + // @ts-ignore + if (!output.address && !output.script?.hex) { change = true; output.address = changeAddress; } @@ -1193,20 +1273,28 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { if (masterFingerprint) { let masterFingerprintHex = Number(masterFingerprint).toString(16); if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte - const hexBuffer = Buffer.from(masterFingerprintHex, 'hex'); - masterFingerprintBuffer = Buffer.from(reverse(hexBuffer)); + const hexBuffer = hexToUint8Array(masterFingerprintHex); + masterFingerprintBuffer = hexBuffer.reverse(); } else { - masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); + masterFingerprintBuffer = new Uint8Array([0x00, 0x00, 0x00, 0x00]); } // this is not correct fingerprint, as we dont know realfingerprint - we got zpub with 84/0, but fingerpting // should be from root. basically, fingerprint should be provided from outside by user when importing zpub + if (output.address?.startsWith('PM')) { + // ok its BIP47 payment code, so we need to unwrap a joint address for the receiver and use it instead: + output.address = this._getNextFreePaymentCodeAddressSend(output.address); + // ^^^ trusting that notification transaction is in place + } + psbt.addOutput({ address: output.address, - value: output.value, + // @ts-ignore types from bitcoinjs are not exported so we cant define outputData separately and add fields conditionally (either address or script should be present) + script: output.script?.hex ? hexToUint8Array(output.script.hex) : undefined, + value: BigInt(output.value), bip32Derivation: - change && path && pubkey + change && path && pubkey && this.segwitType !== 'p2tr' ? [ { masterFingerprint: masterFingerprintBuffer, @@ -1215,13 +1303,33 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { }, ] : [], + tapBip32Derivation: + this.segwitType === 'p2tr' && pubkey && path && change + ? [ + { + pubkey: new Uint8Array(pubkey), + masterFingerprint: new Uint8Array(masterFingerprintBuffer), + path, + leafHashes: [], + }, + ] + : [], + ...(this.segwitType === 'p2tr' && pubkey ? { tapInternalKey: new Uint8Array(pubkey) } : {}), }); }); if (!skipSigning) { // skiping signing related stuff for (let cc = 0; cc < c; cc++) { - psbt.signInput(cc, keypairs[cc]); + if (this.segwitType === 'p2tr') { + assert(psbt.data.inputs[cc].tapInternalKey, 'TapInternalKey is required for taproot inputs'); + psbt.signTaprootInput( + cc, + keypairs[cc].tweak(bitcoin.crypto.taggedHash('TapTweak', psbt.data.inputs[cc].tapInternalKey as Uint8Array)), + ); + } else { + psbt.signInput(cc, keypairs[cc]); + } } } @@ -1232,7 +1340,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { return { tx, inputs, outputs, fee, psbt }; } - _addPsbtInput(psbt: Psbt, input: CoinSelectReturnInput, sequence: number, masterFingerprintBuffer: Buffer) { + _addPsbtInput(psbt: Psbt, input: CoinSelectReturnInput, sequence: number, masterFingerprintBuffer: Uint8Array) { if (!input.address) { throw new Error('Internal error: no address on Utxo during _addPsbtInput()'); } @@ -1242,10 +1350,12 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { throw new Error('Internal error: pubkey or path are invalid'); } const p2wpkh = bitcoin.payments.p2wpkh({ pubkey }); + if (!p2wpkh.output) { + throw new Error('Internal error: could not create p2wpkh output during _addPsbtInput'); + } psbt.addInput({ - // @ts-ignore - hash: input.txid || input.txId, + hash: input.txid, index: input.vout, sequence, bip32Derivation: [ @@ -1257,7 +1367,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { ], witnessUtxo: { script: p2wpkh.output, - value: input.value, + value: BigInt(input.value), }, }); @@ -1292,15 +1402,27 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { * Creates Segwit Bech32 Bitcoin address */ _nodeToBech32SegwitAddress(hdNode: BIP32Interface): string { - return bitcoin.payments.p2wpkh({ + const { address } = bitcoin.payments.p2wpkh({ pubkey: hdNode.publicKey, - }).address; + }); + + if (!address) { + throw new Error('Could not create address in _nodeToBech32SegwitAddress'); + } + + return address; } _nodeToLegacyAddress(hdNode: BIP32Interface): string { - return bitcoin.payments.p2pkh({ + const { address } = bitcoin.payments.p2pkh({ pubkey: hdNode.publicKey, - }).address; + }); + + if (!address) { + throw new Error('Could not create address in _nodeToLegacyAddress'); + } + + return address; } /** @@ -1310,6 +1432,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { const { address } = bitcoin.payments.p2sh({ redeem: bitcoin.payments.p2wpkh({ pubkey: hdNode.publicKey }), }); + + if (!address) { + throw new Error('Could not create address in _nodeToP2shSegwitAddress'); + } + return address; } @@ -1339,6 +1466,12 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { if (!this.allowBIP47()) { return false; } + try { + // watch-only wallet will throw an error here + this.getDerivationPath(); + } catch (_) { + return false; + } // only check BIP47 if derivation path is regular, otherwise too many wallets will be found if (!["m/84'/0'/0'", "m/44'/0'/0'", "m/49'/0'/0'"].includes(this.getDerivationPath() as string)) { return false; @@ -1360,6 +1493,17 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { ret.push(this._getExternalAddressByIndex(c)); } + if (this.allowBIP47() && this.isBIP47Enabled()) { + // returning BIP47 joint addresses with everyone who can pay us because they are kinda our 'external' aka 'receive' addresses + + for (const pc of this._receive_payment_codes) { + for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + this.gap_limit / 4; c++) { + // ^^^ not full gap limit to reduce computation (theoretically, there should not be gaps at all) + ret.push(this._getBIP47AddressReceive(pc, c)); + } + } + } + return ret; } @@ -1426,12 +1570,12 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } /** - * @param seed {Buffer} Buffer object with seed - * @returns {string} Hex string of fingerprint derived from mnemonics. Always has lenght of 8 chars and correct leading zeroes. All caps + * @param seed {Uint8Array} Uint8Array with seed + * @returns {string} Hex string of fingerprint derived from mnemonics. Always has length of 8 chars and correct leading zeroes. All caps */ - static seedToFingerprint(seed: Buffer) { + static seedToFingerprint(seed: Uint8Array) { const root = bip32.fromSeed(seed); - let hex = root.fingerprint.toString('hex'); + let hex = uint8ArrayToHex(root.fingerprint); while (hex.length < 8) hex = '0' + hex; // leading zeroes return hex.toUpperCase(); } @@ -1440,17 +1584,22 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { * @param mnemonic {string} Mnemonic phrase (12 or 24 words) * @returns {string} Hex fingerprint */ - static mnemonicToFingerprint(mnemonic: string, passphrase: string) { + static mnemonicToFingerprint(mnemonic: string, passphrase?: string) { const seed = bip39.mnemonicToSeedSync(mnemonic, passphrase); return AbstractHDElectrumWallet.seedToFingerprint(seed); } /** - * @returns {string} Hex string of fingerprint derived from wallet mnemonics. Always has lenght of 8 chars and correct leading zeroes + * @returns Hex string of fingerprint derived from wallet mnemonics. Always has length of 8 chars and correct leading zeroes */ getMasterFingerprintHex() { + if (this._fp) { + return this._fp; // cache hit + } + const seed = this._getSeed(); - return AbstractHDElectrumWallet.seedToFingerprint(seed); + this._fp = AbstractHDElectrumWallet.seedToFingerprint(seed); + return this._fp; } /** @@ -1473,6 +1622,145 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { return this._bip47_instance; } + /** + * find and return _existing_ notification transaction for the given payment code + * (i.e. if it exists - we notified in the past and dont need to notify again) + */ + getBIP47NotificationTransaction(receiverPaymentCode: string): Transaction | undefined { + const publicBip47 = BIP47Factory(ecc).fromPaymentCode(receiverPaymentCode); + const remoteNotificationAddress = publicBip47.getNotificationAddress(); + + for (const tx of this.getTransactions()) { + for (const output of tx.outputs) { + if (output.scriptPubKey?.addresses?.includes(remoteNotificationAddress)) return tx; + // ^^^ if in the past we sent a tx to his notification address - most likely that was a proper notification + // transaction with OP_RETURN. + // but not gona verify it here, will just trust it + } + } + } + + /** + * return BIP47 payment code of the counterparty of this transaction (someone who paid us, or someone we paid) + * or undefined if it was a non-BIP47 transaction + */ + getBip47CounterpartyByTxid(txid: string): string | undefined { + const foundTx = this.getTransactions().find(tx => tx.txid === txid); + if (foundTx) { + return this.getBip47CounterpartyByTx(foundTx); + } + return undefined; + } + + /** + * return BIP47 payment code of the counterparty of this transaction (someone who paid us, or someone we paid) + * or undefined if it was a non-BIP47 transaction + */ + getBip47CounterpartyByTx(tx: Transaction): string | undefined { + for (const pc of Object.keys(this._txs_by_payment_code_index)) { + // iterating all payment codes + + for (const txs of Object.values(this._txs_by_payment_code_index[pc])) { + for (const tx2 of txs) { + if (tx2.txid === tx.txid) { + return pc; // found it! + } + } + } + } + + // checking txs we sent to counterparties + + for (const pc of this._send_payment_codes) { + for (const out of tx.outputs) { + for (const address of out.scriptPubKey?.addresses ?? []) { + if (this._addresses_by_payment_code_send[pc] && Object.values(this._addresses_by_payment_code_send[pc]).includes(address)) { + // found it! + return pc; + } + } + } + } + + return undefined; // found nothing + } + + createBip47NotificationTransaction(utxos: CreateTransactionUtxo[], receiverPaymentCode: string, feeRate: number, changeAddress: string) { + const aliceBip47 = BIP47Factory(ecc).fromBip39Seed(this.getSecret(), undefined, this.getPassphrase()); + const bobBip47 = BIP47Factory(ecc).fromPaymentCode(receiverPaymentCode); + assert(utxos[0], 'No UTXO'); + assert(utxos[0].wif, 'No UTXO WIF'); + + // constructing targets: notification address, _dummy_ payload (+potential change might be added later) + + const targetsTemp: CreateTransactionTarget[] = []; + targetsTemp.push({ + address: bobBip47.getNotificationAddress(), + value: 546, // minimum permissible utxo size + }); + targetsTemp.push({ + value: 0, + script: { + hex: uint8ArrayToHex(new Uint8Array(83)), // no `address` here, its gonabe op_return. but we pass dummy data here with a correct size just to choose utxo + }, + }); + + // creating temp transaction so that utxo can be selected: + + const { inputs: inputsTemp } = this.createTransaction( + utxos, + targetsTemp, + feeRate, + changeAddress, + AbstractHDElectrumWallet.defaultRBFSequence, + false, + 0, + ); + assert(inputsTemp?.[0]?.wif, 'inputsTemp?.[0]?.wif assert failed'); + + // utxo selected. lets create op_return payload using the correct (first!) utxo and correct targets with that payload + + const keyPair = ECPair.fromWIF(inputsTemp[0].wif); + const outputNumber = new Uint8Array(4); // 00000000 in hex + new DataView(outputNumber.buffer).setUint32(0, inputsTemp[0].vout, true); // little-endian + const blindedPaymentCode = aliceBip47.getBlindedPaymentCode( + bobBip47, + keyPair.privateKey as Buffer, + // txid is reversed, as well as output number + uint8ArrayToHex(hexToUint8Array(inputsTemp[0].txid).reverse()) + uint8ArrayToHex(outputNumber), + ); + + // targets: + + const targets: CreateTransactionTarget[] = []; + targets.push({ + address: bobBip47.getNotificationAddress(), + value: 546, // minimum permissible utxo size + }); + targets.push({ + value: 0, + script: { + hex: '6a4c50' + blindedPaymentCode, // no `address` here, only script (which is OP_RETURN + data payload) + }, + }); + + // finally a transaction: + + const { tx, outputs, inputs, fee, psbt } = this.createTransaction( + utxos, + targets, + feeRate, + changeAddress, + AbstractHDElectrumWallet.defaultRBFSequence, + false, + 0, + ); + assert(inputs && inputs[0] && inputs[0].wif, 'inputs && inputs[0] && inputs[0].wif assert failed'); + assert(inputs[0].txid === inputsTemp[0].txid, 'inputs[0].txid === inputsTemp[0].txid assert failed'); // making sure that no funky business happened under the hood (its supposed to stay the same) + + return { tx, inputs, outputs, fee, psbt }; + } + getBIP47PaymentCode(): string { if (!this._payment_code) { this._payment_code = this.getBIP47FromSeed().getSerializedPaymentCode(); @@ -1486,17 +1774,21 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { return bip47Local.getNotificationAddress(); } + /** + * check our notification address, and decypher all payment codes people notified us + * about (so they can pay us) + */ async fetchBIP47SenderPaymentCodes(): Promise { const bip47_instance = this.getBIP47FromSeed(); const address = bip47_instance.getNotificationAddress(); const histories = await BlueElectrum.multiGetHistoryByAddress([address]); const txHashes = histories[address].map(({ tx_hash }) => tx_hash); - const txHexs = await BlueElectrum.multiGetTransactionByTxid(txHashes, 50, false); + const txHexs = await BlueElectrum.multiGetTransactionByTxid(txHashes, false); for (const txHex of Object.values(txHexs)) { try { const paymentCode = bip47_instance.getPaymentCodeFromRawNotificationTransaction(txHex); - if (this._sender_payment_codes.includes(paymentCode)) continue; // already have it + if (this._receive_payment_codes.includes(paymentCode)) continue; // already have it // final check if PC is even valid (could've been constructed by a buggy code, and our code would crash with that): try { @@ -1505,8 +1797,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { continue; } - this._sender_payment_codes.push(paymentCode); - this._next_free_payment_code_address_index[paymentCode] = 0; // initialize + this._receive_payment_codes.push(paymentCode); + this._next_free_payment_code_address_index_receive[paymentCode] = 0; // initialize this._balances_by_payment_code_index[paymentCode] = { c: 0, u: 0 }; } catch (e) { // do nothing @@ -1514,19 +1806,66 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } } + /** + * for counterparties we can pay, we sync shared addresses to find the one we havent used yet. + * this method could benefit from rewriting in batch requests, but not necessary - its only going to be called + * once in a while (when user decides to pay a given counterparty again) + */ + async syncBip47ReceiversAddresses(receiverPaymentCode: string) { + this._next_free_payment_code_address_index_send[receiverPaymentCode] = + this._next_free_payment_code_address_index_send[receiverPaymentCode] || 0; // init + + for (let c = this._next_free_payment_code_address_index_send[receiverPaymentCode]; c < 999999; c++) { + const address = this._getBIP47AddressSend(receiverPaymentCode, c); + + this._addresses_by_payment_code_send[receiverPaymentCode] = this._addresses_by_payment_code_send[receiverPaymentCode] || {}; // init + this._addresses_by_payment_code_send[receiverPaymentCode][c] = address; + const histories = await BlueElectrum.multiGetHistoryByAddress([address]); + if (histories?.[address]?.length > 0) { + // address is used; + continue; + } + + // empty address, stop here, we found our latest index and filled array with shared addresses + this._next_free_payment_code_address_index_send[receiverPaymentCode] = c; + break; + } + } + + /** + * payment codes of people who can pay us + */ getBIP47SenderPaymentCodes(): string[] { - return this._sender_payment_codes; + return this._receive_payment_codes; + } + + /** + * payment codes of people whom we can pay + */ + getBIP47ReceiverPaymentCodes(): string[] { + return this._send_payment_codes; + } + + /** + * adding counterparty whom we can pay. trusting that notificaton transaction is in place already + */ + addBIP47Receiver(paymentCode: string) { + if (this._send_payment_codes.includes(paymentCode)) return; // duplicates + this._send_payment_codes.push(paymentCode); } _hdNodeToAddress(hdNode: BIP32Interface): string { return this._nodeToBech32SegwitAddress(hdNode); } - _getBIP47Address(paymentCode: string, index: number): string { - if (!this._addresses_by_payment_code[paymentCode]) this._addresses_by_payment_code[paymentCode] = []; + /** + * returns joint addresses to receive coins with a given counterparty + */ + _getBIP47AddressReceive(paymentCode: string, index: number): string { + if (!this._addresses_by_payment_code_receive[paymentCode]) this._addresses_by_payment_code_receive[paymentCode] = []; - if (this._addresses_by_payment_code[paymentCode][index]) { - return this._addresses_by_payment_code[paymentCode][index]; + if (this._addresses_by_payment_code_receive[paymentCode][index]) { + return this._addresses_by_payment_code_receive[paymentCode][index]; } const bip47_instance = this.getBIP47FromSeed(); @@ -1535,12 +1874,38 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { const hdNode = bip47_instance.getPaymentWallet(remotePaymentNode, index); const address = this._hdNodeToAddress(hdNode); this._address_to_wif_cache[address] = hdNode.toWIF(); - this._addresses_by_payment_code[paymentCode][index] = address; + this._addresses_by_payment_code_receive[paymentCode][index] = address; return address; } - _getNextFreePaymentCodeAddress(paymentCode: string) { - return this._next_free_payment_code_address_index[paymentCode] || 0; + /** + * returns joint addresses to send coins to + */ + _getBIP47AddressSend(paymentCode: string, index: number): string { + if (!this._addresses_by_payment_code_send[paymentCode]) this._addresses_by_payment_code_send[paymentCode] = []; + + if (this._addresses_by_payment_code_send[paymentCode][index]) { + // cache hit + return this._addresses_by_payment_code_send[paymentCode][index]; + } + + const hdNode = this.getBIP47FromSeed().getReceiveWallet(BIP47Factory(ecc).fromPaymentCode(paymentCode).getPaymentCodeNode(), index); + const address = this._hdNodeToAddress(hdNode); + this._addresses_by_payment_code_send[paymentCode][index] = address; + return address; + } + + _getNextFreePaymentCodeIndexReceive(paymentCode: string) { + return this._next_free_payment_code_address_index_receive[paymentCode] || 0; + } + + /** + * when sending funds to a payee, this method will return next unused joint address for him. + * this method assumes that we synced our payee via `syncBip47ReceiversAddresses()` + */ + _getNextFreePaymentCodeAddressSend(paymentCode: string) { + this._next_free_payment_code_address_index_send[paymentCode] = this._next_free_payment_code_address_index_send[paymentCode] || 0; + return this._getBIP47AddressSend(paymentCode, this._next_free_payment_code_address_index_send[paymentCode]); } _getBalancesByPaymentCodeIndex(paymentCode: string): BalanceByIndex { diff --git a/class/wallets/abstract-hd-wallet.ts b/class/wallets/abstract-hd-wallet.ts index 50e528a6a0e..a7ccbb04efa 100644 --- a/class/wallets/abstract-hd-wallet.ts +++ b/class/wallets/abstract-hd-wallet.ts @@ -1,8 +1,9 @@ -import { LegacyWallet } from './legacy-wallet'; -import * as bip39 from 'bip39'; import { BIP32Interface } from 'bip32'; +import * as bip39 from 'bip39'; + import * as bip39custom from '../../blue_modules/bip39'; -import BlueElectrum from '../../blue_modules/BlueElectrum'; +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; +import { LegacyWallet } from './legacy-wallet'; import { Transaction } from './types'; type AbstractHDWalletStatics = { @@ -13,8 +14,12 @@ type AbstractHDWalletStatics = { * @deprecated */ export class AbstractHDWallet extends LegacyWallet { - static type = 'abstract'; - static typeReadable = 'abstract'; + static readonly type = 'abstract'; + static readonly typeReadable = 'abstract'; + // @ts-ignore: override + public readonly type = AbstractHDWallet.type; + // @ts-ignore: override + public readonly typeReadable = AbstractHDWallet.typeReadable; next_free_address_index: number; next_free_change_address_index: number; @@ -69,9 +74,9 @@ export class AbstractHDWallet extends LegacyWallet { } /** - * @return {Buffer} wallet seed + * @return {Uint8Array} wallet seed */ - _getSeed(): Buffer { + _getSeed(): Uint8Array { const mnemonic = this.secret; const passphrase = this.passphrase; return bip39.mnemonicToSeedSync(mnemonic, passphrase); @@ -310,7 +315,7 @@ export class AbstractHDWallet extends LegacyWallet { throw new Error('Not implemented'); } - _getNodePubkeyByIndex(node: number, index: number): Buffer | undefined { + _getNodePubkeyByIndex(node: number, index: number): Uint8Array | undefined { throw new Error('Not implemented'); } diff --git a/class/wallets/abstract-wallet.ts b/class/wallets/abstract-wallet.ts index f48a5bd4542..cae1f856b2b 100644 --- a/class/wallets/abstract-wallet.ts +++ b/class/wallets/abstract-wallet.ts @@ -1,14 +1,10 @@ -import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import b58 from 'bs58check'; -import createHash from 'create-hash'; -import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types'; +import { sha256 } from '@noble/hashes/sha256'; +import wif from 'wif'; -type WalletStatics = { - type: string; - typeReadable: string; - segwitType?: 'p2wpkh' | 'p2sh(p2wpkh)'; - derivationPath?: string; -}; +import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; +import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types'; +import { hexToUint8Array, concatUint8Arrays, uint8ArrayToHex } from '../../blue_modules/uint8array-extras'; type WalletWithPassphrase = AbstractWallet & { getPassphrase: () => string }; type UtxoMetadata = { @@ -17,8 +13,12 @@ type UtxoMetadata = { }; export class AbstractWallet { - static type = 'abstract'; - static typeReadable = 'abstract'; + static readonly type = 'abstract'; + static readonly typeReadable = 'abstract'; + // @ts-ignore: override + public readonly type = AbstractWallet.type; + // @ts-ignore: override + public readonly typeReadable = AbstractWallet.typeReadable; static fromJson(obj: string): AbstractWallet { const obj2 = JSON.parse(obj); @@ -31,16 +31,14 @@ export class AbstractWallet { return temp; } - type: string; - typeReadable: string; - segwitType?: 'p2wpkh' | 'p2sh(p2wpkh)'; + segwitType?: 'p2wpkh' | 'p2sh(p2wpkh)' | 'p2tr' | 'p2pkh' /* not segwit but ok */; _derivationPath?: string; label: string; secret: string; balance: number; unconfirmed_balance: number; _address: string | false; - utxo: Utxo[]; + _utxo: Utxo[]; _lastTxFetch: number; _lastBalanceFetch: number; preferredBalanceUnit: BitcoinUnit; @@ -50,20 +48,15 @@ export class AbstractWallet { _hideTransactionsInWalletsList: boolean; _utxoMetadata: Record; use_with_hardware_wallet: boolean; - masterFingerprint: number | false; + masterFingerprint: number; constructor() { - const Constructor = this.constructor as unknown as WalletStatics; - - this.type = Constructor.type; - this.typeReadable = Constructor.typeReadable; - this.segwitType = Constructor.segwitType; this.label = ''; this.secret = ''; // private key or recovery phrase this.balance = 0; this.unconfirmed_balance = 0; this._address = false; // cache - this.utxo = []; + this._utxo = []; this._lastTxFetch = 0; this._lastBalanceFetch = 0; this.preferredBalanceUnit = BitcoinUnit.BTC; @@ -73,7 +66,7 @@ export class AbstractWallet { this._hideTransactionsInWalletsList = false; this._utxoMetadata = {}; this.use_with_hardware_wallet = false; - this.masterFingerprint = false; + this.masterFingerprint = 0; } /** @@ -88,7 +81,7 @@ export class AbstractWallet { const passphrase = thisWithPassphrase.getPassphrase ? thisWithPassphrase.getPassphrase() : ''; const path = this._derivationPath ?? ''; const string2hash = this.type + this.getSecret() + passphrase + path; - return createHash('sha256').update(string2hash).digest().toString('hex'); + return uint8ArrayToHex(sha256(string2hash)); } getTransactions(): Transaction[] { @@ -143,6 +136,14 @@ export class AbstractWallet { return BitcoinUnit.BTC; } + setPreferredBalanceUnit(unit: BitcoinUnit): void { + if (Object.values(BitcoinUnit).includes(unit)) { + this.preferredBalanceUnit = unit; + return; + } + this.preferredBalanceUnit = BitcoinUnit.BTC; + } + async allowOnchainAddress(): Promise { throw new Error('allowOnchainAddress: Not implemented'); } @@ -163,11 +164,11 @@ export class AbstractWallet { return true; } - allowRBF(): boolean { + allowSilentPaymentSend(): boolean { return false; } - allowHodlHodlTrading(): boolean { + allowRBF(): boolean { return false; } @@ -219,10 +220,65 @@ export class AbstractWallet { } setSecret(newSecret: string): this { + const origSecret = newSecret; + + // is it minikey https://en.bitcoin.it/wiki/Mini_private_key_format + // Starts with S, is 22 length or larger, is base58 + if (newSecret.startsWith('S') && newSecret.length >= 22 && /^[1-9A-HJ-NP-Za-km-z]+$/.test(newSecret)) { + // minikey + ? hashed with SHA256 starts with 0x00 byte + if (uint8ArrayToHex(sha256(`${newSecret}?`)).startsWith('00')) { + // it is a valid minikey + newSecret = wif.encode(0x80, Buffer.from(sha256(newSecret)), false); + } + } + this.secret = newSecret.trim().replace('bitcoin:', '').replace('BITCOIN:', ''); if (this.secret.startsWith('BC1')) this.secret = this.secret.toLowerCase(); + // is it output descriptor? + if ( + this.secret.startsWith('wpkh(') || + this.secret.startsWith('pkh(') || + this.secret.startsWith('sh(') || + this.secret.startsWith('tr(') + ) { + const xpubIndex = Math.max(this.secret.indexOf('xpub'), this.secret.indexOf('ypub'), this.secret.indexOf('zpub')); + let fpAndPath; + if (this.secret.includes('[')) { + fpAndPath = this.secret.substring(this.secret.indexOf('['), xpubIndex).replace(/[[\]]/g, ''); + } else { + // old (or broken) format..? no square brackets, only "()" + fpAndPath = this.secret.substring(this.secret.indexOf('('), xpubIndex).replace(/[()]/g, ''); + } + const xpub = this.secret.substring(xpubIndex).replace(/[()]/g, '').split('/')[0]; + + const pathIndex = fpAndPath.indexOf('/'); + const path = 'm' + fpAndPath.substring(pathIndex).replace(/h/g, "'"); + const fp = fpAndPath.substring(0, pathIndex); + + this._derivationPath = path; + const mfp = uint8ArrayToHex(hexToUint8Array(fp).reverse()); + this.masterFingerprint = parseInt(mfp, 16); + + // Store the script type for later use + if (this.secret.startsWith('tr(')) { + this.segwitType = 'p2tr'; + this.secret = xpub; + } else if (this.secret.startsWith('wpkh(')) { + this.segwitType = 'p2wpkh'; + this.secret = this._xpubToZpub(xpub); + } else if (this.secret.startsWith('sh(wpkh(')) { + this.segwitType = 'p2sh(p2wpkh)'; + this.secret = this._xpubToYpub(xpub); + } else if (this.secret.startsWith('pkh(')) { + this.segwitType = 'p2pkh'; + this.secret = xpub; + } + + return this; + } + // [fingerprint/derivation]zpub const re = /\[([^\]]+)\](.*)/; const m = this.secret.match(re); @@ -230,7 +286,7 @@ export class AbstractWallet { let [hexFingerprint, ...derivationPathArray] = m[1].split('/'); const derivationPath = `m/${derivationPathArray.join('/').replace(/h/g, "'")}`; if (hexFingerprint.length === 8) { - hexFingerprint = Buffer.from(hexFingerprint, 'hex').reverse().toString('hex'); + hexFingerprint = uint8ArrayToHex(hexToUint8Array(hexFingerprint).reverse()); this.masterFingerprint = parseInt(hexFingerprint, 16); this._derivationPath = derivationPath; } @@ -238,7 +294,7 @@ export class AbstractWallet { if (derivationPath.startsWith("m/84'/0'/") && this.secret.toLowerCase().startsWith('xpub')) { // need to convert xpub to zpub - this.secret = this._xpubToZpub(this.secret); + this.secret = this._xpubToZpub(this.secret.split('/')[0]); } if (derivationPath.startsWith("m/49'/0'/") && this.secret.toLowerCase().startsWith('xpub')) { @@ -260,7 +316,7 @@ export class AbstractWallet { parsedSecret = JSON.parse(newSecret); } if (parsedSecret && parsedSecret.keystore && parsedSecret.keystore.xpub) { - let masterFingerprint: number | false = false; + let masterFingerprint: number = 0; if (parsedSecret.keystore.ckcc_xfp) { // It is a ColdCard Hardware Wallet masterFingerprint = Number(parsedSecret.keystore.ckcc_xfp); @@ -273,6 +329,7 @@ export class AbstractWallet { } if (parsedSecret.keystore.derivation) { this._derivationPath = parsedSecret.keystore.derivation; + this._derivationPath = this._derivationPath?.replace(/h/g, "'"); } this.secret = parsedSecret.keystore.xpub; this.masterFingerprint = masterFingerprint; @@ -282,12 +339,13 @@ export class AbstractWallet { // It is a Cobo Vault Hardware Wallet if (parsedSecret && parsedSecret.ExtPubKey && parsedSecret.MasterFingerprint && parsedSecret.AccountKeyPath) { this.secret = parsedSecret.ExtPubKey; - const mfp = Buffer.from(parsedSecret.MasterFingerprint, 'hex').reverse().toString('hex'); + const mfp = uint8ArrayToHex(hexToUint8Array(parsedSecret.MasterFingerprint).reverse()); this.masterFingerprint = parseInt(mfp, 16); this._derivationPath = parsedSecret.AccountKeyPath.startsWith('m/') ? parsedSecret.AccountKeyPath : `m/${parsedSecret.AccountKeyPath}`; if (parsedSecret.CoboVaultFirmwareVersion) this.use_with_hardware_wallet = true; + return this; } } catch (_) {} @@ -301,26 +359,17 @@ export class AbstractWallet { } } - // is it output descriptor? - if (this.secret.startsWith('wpkh(') || this.secret.startsWith('pkh(') || this.secret.startsWith('sh(')) { - const xpubIndex = Math.max(this.secret.indexOf('xpub'), this.secret.indexOf('ypub'), this.secret.indexOf('zpub')); - const fpAndPath = this.secret.substring(this.secret.indexOf('(') + 1, xpubIndex); - const xpub = this.secret.substring(xpubIndex).replace(/\(|\)/, ''); - const pathIndex = fpAndPath.indexOf('/'); - const path = 'm' + fpAndPath.substring(pathIndex); - const fp = fpAndPath.substring(0, pathIndex); - - this._derivationPath = path; - const mfp = Buffer.from(fp, 'hex').reverse().toString('hex'); - this.masterFingerprint = parseInt(mfp, 16); - - if (this.secret.startsWith('wpkh(')) { - this.secret = this._xpubToZpub(xpub); - } else { - // nop - this.secret = xpub; + // is it new-wasabi.json exported from coldcard? + try { + const json = JSON.parse(origSecret); + if (json.MasterFingerprint && json.ExtPubKey) { + // technically we should allow choosing which format user wants, BIP44 / BIP49 / BIP84, but meh... + this.secret = this._xpubToZpub(json.ExtPubKey); + const mfp = uint8ArrayToHex(hexToUint8Array(json.MasterFingerprint).reverse()); + this.masterFingerprint = parseInt(mfp, 16); + return this; } - } + } catch (_) {} return this; } @@ -329,17 +378,6 @@ export class AbstractWallet { return 0; } - getLatestTransactionTimeEpoch(): number { - if (this.getTransactions().length === 0) { - return 0; - } - let max = 0; - for (const tx of this.getTransactions()) { - max = Math.max(new Date(tx.received ?? 0).getTime(), max); - } - return max; - } - /** * @deprecated * TODO: be more precise on the type @@ -351,7 +389,7 @@ export class AbstractWallet { /** * - * @param utxos {Array.<{vout: Number, value: Number, txId: String, address: String}>} List of spendable utxos + * @param utxos {Array.<{vout: Number, value: Number, txid: String, address: String}>} List of spendable utxos * @param targets {Array.<{value: Number, address: String}>} Where coins are going. If theres only 1 target and that target has no value - this will send MAX to that address (respecting fee rate) * @param feeRate {Number} satoshi per byte * @param changeAddress {String} Excessive coins will go back to that address @@ -418,9 +456,9 @@ export class AbstractWallet { _zpubToXpub(zpub: string): string { let data = b58.decode(zpub); data = data.slice(4); - data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]); + const concatenated = concatUint8Arrays([hexToUint8Array('0488b21e'), data]); - return b58.encode(data); + return b58.encode(concatenated); } /** @@ -432,25 +470,25 @@ export class AbstractWallet { let data = b58.decode(ypub); if (data.readUInt32BE() !== 0x049d7cb2) throw new Error('Not a valid ypub extended key!'); data = data.slice(4); - data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]); + const concatenated = concatUint8Arrays([hexToUint8Array('0488b21e'), data]); - return b58.encode(data); + return b58.encode(concatenated); } _xpubToZpub(xpub: string): string { let data = b58.decode(xpub); data = data.slice(4); - data = Buffer.concat([Buffer.from('04b24746', 'hex'), data]); + const concatenated = concatUint8Arrays([hexToUint8Array('04b24746'), data]); - return b58.encode(data); + return b58.encode(concatenated); } _xpubToYpub(xpub: string): string { let data = b58.decode(xpub); data = data.slice(4); - data = Buffer.concat([Buffer.from('049d7cb2', 'hex'), data]); + const concatenated = concatUint8Arrays([hexToUint8Array('049d7cb2'), data]); - return b58.encode(data); + return b58.encode(concatenated); } prepareForSerialization(): void {} @@ -485,7 +523,7 @@ export class AbstractWallet { getMasterFingerprintFromHex(hexValue: string): number { if (hexValue.length < 8) hexValue = '0' + hexValue; - const b = Buffer.from(hexValue, 'hex'); + const b = hexToUint8Array(hexValue); if (b.length !== 4) throw new Error('invalid fingerprint hex'); hexValue = hexValue[6] + hexValue[7] + hexValue[4] + hexValue[5] + hexValue[2] + hexValue[3] + hexValue[0] + hexValue[1]; diff --git a/class/wallets/hd-aezeed-wallet.js b/class/wallets/hd-aezeed-wallet.js deleted file mode 100644 index 593e86c0ab9..00000000000 --- a/class/wallets/hd-aezeed-wallet.js +++ /dev/null @@ -1,191 +0,0 @@ -import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; -import b58 from 'bs58check'; -import BIP32Factory from 'bip32'; -import ecc from '../../blue_modules/noble_ecc'; - -const bitcoin = require('bitcoinjs-lib'); -const { CipherSeed } = require('aezeed'); -const bip32 = BIP32Factory(ecc); - -/** - * AEZEED mnemonics support, which is used in LND - * Support only BIP84 (native segwit) derivations - * - * @see https://github.com/lightningnetwork/lnd/tree/master/aezeed - * @see https://github.com/bitcoinjs/aezeed - * @see https://github.com/lightningnetwork/lnd/issues/4960 - * @see https://github.com/guggero/chantools/blob/master/doc/chantools_genimportscript.md - * @see https://github.com/lightningnetwork/lnd/blob/master/keychain/derivation.go - */ -export class HDAezeedWallet extends AbstractHDElectrumWallet { - static type = 'HDAezeedWallet'; - static typeReadable = 'HD Aezeed'; - static segwitType = 'p2wpkh'; - static derivationPath = "m/84'/0'/0'"; - - setSecret(newSecret) { - this.secret = newSecret.trim(); - this.secret = this.secret.replace(/[^a-zA-Z0-9]/g, ' ').replace(/\s+/g, ' '); - return this; - } - - _getEntropyCached() { - if (this._entropyHex) { - // cache hit - return Buffer.from(this._entropyHex, 'hex'); - } else { - throw new Error('Entropy cache is not filled'); - } - } - - getXpub() { - // first, getting xpub - const root = bip32.fromSeed(this._getEntropyCached()); - - const path = "m/84'/0'/0'"; - const child = root.derivePath(path).neutered(); - const xpub = child.toBase58(); - - // bitcoinjs does not support zpub yet, so we just convert it from xpub - let data = b58.decode(xpub); - data = data.slice(4); - data = Buffer.concat([Buffer.from('04b24746', 'hex'), data]); - this._xpub = b58.encode(data); - - return this._xpub; - } - - validateMnemonic() { - throw new Error('Use validateMnemonicAsync()'); - } - - async validateMnemonicAsync() { - const passphrase = this.getPassphrase() || 'aezeed'; - try { - const cipherSeed1 = await CipherSeed.fromMnemonic(this.secret, passphrase); - this._entropyHex = cipherSeed1.entropy.toString('hex'); // save cache - return !!cipherSeed1.entropy; - } catch (_) { - return false; - } - } - - async mnemonicInvalidPassword() { - const passphrase = this.getPassphrase() || 'aezeed'; - try { - const cipherSeed1 = await CipherSeed.fromMnemonic(this.secret, passphrase); - this._entropyHex = cipherSeed1.entropy.toString('hex'); // save cache - } catch (error) { - return error.message === 'Invalid Password'; - } - return false; - } - - async generate() { - throw new Error('Not implemented'); - } - - _getNode0() { - const root = bip32.fromSeed(this._getEntropyCached()); - const node = root.derivePath("m/84'/0'/0'"); - return node.derive(0); - } - - _getNode1() { - const root = bip32.fromSeed(this._getEntropyCached()); - const node = root.derivePath("m/84'/0'/0'"); - return node.derive(1); - } - - _getInternalAddressByIndex(index) { - index = index * 1; // cast to int - if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit - - this._node1 = this._node1 || this._getNode1(); // cache - - const address = bitcoin.payments.p2wpkh({ - pubkey: this._node1.derive(index).publicKey, - }).address; - - return (this.internal_addresses_cache[index] = address); - } - - _getExternalAddressByIndex(index) { - index = index * 1; // cast to int - if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit - - this._node0 = this._node0 || this._getNode0(); // cache - - const address = bitcoin.payments.p2wpkh({ - pubkey: this._node0.derive(index).publicKey, - }).address; - - return (this.external_addresses_cache[index] = address); - } - - _getWIFByIndex(internal, index) { - if (!this.secret) return false; - const root = bip32.fromSeed(this._getEntropyCached()); - const path = `m/84'/0'/0'/${internal ? 1 : 0}/${index}`; - const child = root.derivePath(path); - - return child.toWIF(); - } - - _getNodePubkeyByIndex(node, index) { - index = index * 1; // cast to int - - if (node === 0 && !this._node0) { - this._node0 = this._getNode0(); - } - - if (node === 1 && !this._node1) { - this._node1 = this._getNode1(); - } - - if (node === 0) { - return this._node0.derive(index).publicKey; - } - - if (node === 1) { - return this._node1.derive(index).publicKey; - } - } - - getIdentityPubkey() { - const root = bip32.fromSeed(this._getEntropyCached()); - const node = root.derivePath("m/1017'/0'/6'/0/0"); - - return node.publicKey.toString('hex'); - } - - // since its basically a bip84 wallet, we allow all other standard BIP84 features: - - allowSend() { - return true; - } - - allowHodlHodlTrading() { - return true; - } - - allowRBF() { - return true; - } - - allowPayJoin() { - return true; - } - - isSegwit() { - return true; - } - - allowSignVerifyMessage() { - return true; - } - - allowXpub() { - return true; - } -} diff --git a/class/wallets/hd-aezeed-wallet.ts b/class/wallets/hd-aezeed-wallet.ts new file mode 100644 index 00000000000..aea2e92aee4 --- /dev/null +++ b/class/wallets/hd-aezeed-wallet.ts @@ -0,0 +1,203 @@ +import { CipherSeed } from 'aezeed'; +import BIP32Factory from 'bip32'; +import * as bitcoin from 'bitcoinjs-lib'; +import b58 from 'bs58check'; + +import ecc from '../../blue_modules/noble_ecc'; +import { concatUint8Arrays, hexToUint8Array, uint8ArrayToHex } from '../../blue_modules/uint8array-extras'; +import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; + +const bip32 = BIP32Factory(ecc); + +/** + * AEZEED mnemonics support, which is used in LND + * Support only BIP84 (native segwit) derivations + * + * @see https://github.com/lightningnetwork/lnd/tree/master/aezeed + * @see https://github.com/bitcoinjs/aezeed + * @see https://github.com/lightningnetwork/lnd/issues/4960 + * @see https://github.com/guggero/chantools/blob/master/doc/chantools_genimportscript.md + * @see https://github.com/lightningnetwork/lnd/blob/master/keychain/derivation.go + */ +export class HDAezeedWallet extends AbstractHDElectrumWallet { + static readonly type = 'HDAezeedWallet'; + static readonly typeReadable = 'HD Aezeed'; + public readonly segwitType = 'p2wpkh'; + static readonly derivationPath = "m/84'/0'/0'"; + // @ts-ignore: override + public readonly type = HDAezeedWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDAezeedWallet.typeReadable; + + private _entropyHex?: string; + + setSecret(newSecret: string): this { + this.secret = newSecret.trim(); + this.secret = this.secret.replace(/[^a-zA-Z0-9]/g, ' ').replace(/\s+/g, ' '); + return this; + } + + _getEntropyCached(): Uint8Array { + if (this._entropyHex) { + // cache hit + return hexToUint8Array(this._entropyHex); + } else { + throw new Error('Entropy cache is not filled'); + } + } + + getXpub() { + // first, getting xpub + const root = bip32.fromSeed(this._getEntropyCached()); + + const path = "m/84'/0'/0'"; + const child = root.derivePath(path).neutered(); + const xpub = child.toBase58(); + + // bitcoinjs does not support zpub yet, so we just convert it from xpub + let data = b58.decode(xpub); + data = data.slice(4); + const concatenated = concatUint8Arrays([hexToUint8Array('04b24746'), data]); + this._xpub = b58.encode(concatenated); + + return this._xpub; + } + + validateMnemonic(): boolean { + throw new Error('Use validateMnemonicAsync()'); + } + + async validateMnemonicAsync() { + const passphrase = this.getPassphrase() || 'aezeed'; + try { + const cipherSeed1 = await CipherSeed.fromMnemonic(this.secret, passphrase); + this._entropyHex = cipherSeed1.entropy.toString('hex'); // save cache + return !!cipherSeed1.entropy; + } catch (_) { + return false; + } + } + + async mnemonicInvalidPassword() { + const passphrase = this.getPassphrase() || 'aezeed'; + try { + const cipherSeed1 = await CipherSeed.fromMnemonic(this.secret, passphrase); + this._entropyHex = cipherSeed1.entropy.toString('hex'); // save cache + } catch (error: any) { + return error.message === 'Invalid Password'; + } + return false; + } + + async generate() { + throw new Error('Not implemented'); + } + + _getNode0() { + const root = bip32.fromSeed(this._getEntropyCached()); + const node = root.derivePath("m/84'/0'/0'"); + return node.derive(0); + } + + _getNode1() { + const root = bip32.fromSeed(this._getEntropyCached()); + const node = root.derivePath("m/84'/0'/0'"); + return node.derive(1); + } + + _getInternalAddressByIndex(index: number): string { + index = index * 1; // cast to int + if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit + + this._node1 = this._node1 || this._getNode1(); // cache + + const address = bitcoin.payments.p2wpkh({ + pubkey: this._node1.derive(index).publicKey, + }).address; + if (!address) { + throw new Error('Internal error: no address in _getInternalAddressByIndex'); + } + + return (this.internal_addresses_cache[index] = address); + } + + _getExternalAddressByIndex(index: number): string { + index = index * 1; // cast to int + if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit + + this._node0 = this._node0 || this._getNode0(); // cache + + const address = bitcoin.payments.p2wpkh({ + pubkey: this._node0.derive(index).publicKey, + }).address; + if (!address) { + throw new Error('Internal error: no address in _getExternalAddressByIndex'); + } + + return (this.external_addresses_cache[index] = address); + } + + _getWIFByIndex(internal: boolean, index: number): string | false { + if (!this.secret) return false; + const root = bip32.fromSeed(this._getEntropyCached()); + const path = `m/84'/0'/0'/${internal ? 1 : 0}/${index}`; + const child = root.derivePath(path); + + return child.toWIF(); + } + + _getNodePubkeyByIndex(node: number, index: number) { + index = index * 1; // cast to int + + if (node === 0 && !this._node0) { + this._node0 = this._getNode0(); + } + + if (node === 1 && !this._node1) { + this._node1 = this._getNode1(); + } + + if (node === 0 && this._node0) { + return this._node0.derive(index).publicKey; + } + + if (node === 1 && this._node1) { + return this._node1.derive(index).publicKey; + } + + throw new Error('Internal error: this._node0 or this._node1 is undefined'); + } + + getIdentityPubkey() { + const root = bip32.fromSeed(this._getEntropyCached()); + const node = root.derivePath("m/1017'/0'/6'/0/0"); + + return uint8ArrayToHex(node.publicKey); + } + + // since its basically a bip84 wallet, we allow all other standard BIP84 features: + + allowSend() { + return true; + } + + allowRBF() { + return true; + } + + allowPayJoin() { + return true; + } + + isSegwit() { + return true; + } + + allowSignVerifyMessage() { + return true; + } + + allowXpub() { + return true; + } +} diff --git a/class/wallets/hd-legacy-breadwallet-wallet.js b/class/wallets/hd-legacy-breadwallet-wallet.js deleted file mode 100644 index 5a6d5e92914..00000000000 --- a/class/wallets/hd-legacy-breadwallet-wallet.js +++ /dev/null @@ -1,149 +0,0 @@ -import * as bitcoinjs from 'bitcoinjs-lib'; -import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; -import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; -import BIP32Factory from 'bip32'; -import ecc from '../../blue_modules/noble_ecc'; - -const BlueElectrum = require('../../blue_modules/BlueElectrum'); -const bip32 = BIP32Factory(ecc); - -/** - * HD Wallet (BIP39). - * In particular, Breadwallet-compatible (Legacy addresses) - */ -export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { - static type = 'HDLegacyBreadwallet'; - static typeReadable = 'HD Legacy Breadwallet (P2PKH)'; - static derivationPath = "m/0'"; - - // track address index at which wallet switched to segwit - _external_segwit_index = null; - _internal_segwit_index = null; - - // we need a separate function without external_addresses_cache to use in binarySearch - _calcNodeAddressByIndex(node, index, p2wpkh = false) { - let _node; - if (node === 0) { - _node = this._node0 || (this._node0 = bip32.fromBase58(this.getXpub()).derive(node)); - } - if (node === 1) { - _node = this._node1 || (this._node1 = bip32.fromBase58(this.getXpub()).derive(node)); - } - const pubkey = _node.derive(index).publicKey; - const address = p2wpkh ? bitcoinjs.payments.p2wpkh({ pubkey }).address : bitcoinjs.payments.p2pkh({ pubkey }).address; - return address; - } - - // this function is different from HDLegacyP2PKHWallet._getNodeAddressByIndex. - // It takes _external_segwit_index _internal_segwit_index for account - // and starts to generate segwit addresses if index more than them - _getNodeAddressByIndex(node, index) { - index = index * 1; // cast to int - 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 - } - - let p2wpkh = false; - if ( - (node === 0 && this._external_segwit_index !== null && index >= this._external_segwit_index) || - (node === 1 && this._internal_segwit_index !== null && index >= this._internal_segwit_index) - ) { - p2wpkh = true; - } - - const address = this._calcNodeAddressByIndex(node, index, p2wpkh); - - if (node === 0) { - return (this.external_addresses_cache[index] = address); - } - - if (node === 1) { - return (this.internal_addresses_cache[index] = address); - } - } - - async fetchBalance() { - try { - if (this.next_free_change_address_index === 0 && this.next_free_address_index === 0) { - // doing binary search for last used addresses external/internal and legacy/bech32: - const [nextFreeExternalLegacy, nextFreeInternalLegacy] = await Promise.all([ - this._binarySearchIteration(0, 1000, 0, false), - this._binarySearchIteration(0, 1000, 1, false), - ]); - const [nextFreeExternalBech32, nextFreeInternalBech32] = await Promise.all([ - this._binarySearchIteration(nextFreeExternalLegacy, nextFreeExternalLegacy + 1000, 0, true), - this._binarySearchIteration(nextFreeInternalLegacy, nextFreeInternalLegacy + 1000, 1, true), - ]); - - // trying to detect if segwit activated. This condition can be deleted when BRD will enable segwit by default - if (nextFreeExternalLegacy < nextFreeExternalBech32) { - this._external_segwit_index = nextFreeExternalLegacy; - } - this.next_free_address_index = nextFreeExternalBech32; - - this._internal_segwit_index = nextFreeInternalLegacy; // force segwit for change - this.next_free_change_address_index = nextFreeInternalBech32; - } // end rescanning fresh wallet - - // finally fetching balance - await this._fetchBalance(); - } catch (err) { - console.warn(err); - } - } - - async _binarySearchIteration(startIndex, endIndex, node = 0, p2wpkh = false) { - const gerenateChunkAddresses = chunkNum => { - const ret = []; - for (let c = this.gap_limit * chunkNum; c < this.gap_limit * (chunkNum + 1); c++) { - ret.push(this._calcNodeAddressByIndex(node, c, p2wpkh)); - } - return ret; - }; - - let lastChunkWithUsedAddressesNum = null; - let lastHistoriesWithUsedAddresses = null; - for (let c = 0; c < Math.round(endIndex / this.gap_limit); c++) { - const histories = await BlueElectrum.multiGetHistoryByAddress(gerenateChunkAddresses(c)); - if (this.constructor._getTransactionsFromHistories(histories).length > 0) { - // in this particular chunk we have used addresses - lastChunkWithUsedAddressesNum = c; - lastHistoriesWithUsedAddresses = histories; - } else { - // empty chunk. no sense searching more chunks - break; - } - } - - let lastUsedIndex = startIndex; - - if (lastHistoriesWithUsedAddresses) { - // now searching for last used address in batch lastChunkWithUsedAddressesNum - for ( - let c = lastChunkWithUsedAddressesNum * this.gap_limit; - c < lastChunkWithUsedAddressesNum * this.gap_limit + this.gap_limit; - c++ - ) { - const address = this._calcNodeAddressByIndex(node, c, p2wpkh); - if (lastHistoriesWithUsedAddresses[address] && lastHistoriesWithUsedAddresses[address].length > 0) { - lastUsedIndex = Math.max(c, lastUsedIndex) + 1; // point to next, which is supposed to be unsued - } - } - } - - return lastUsedIndex; - } - - _addPsbtInput(psbt, input, sequence, masterFingerprintBuffer) { - // hack to use - // AbstractHDElectrumWallet._addPsbtInput for bech32 address - // HDLegacyP2PKHWallet._addPsbtInput for legacy address - const ProxyClass = input.address.startsWith('bc1') ? AbstractHDElectrumWallet : HDLegacyP2PKHWallet; - const proxy = new ProxyClass(); - return proxy._addPsbtInput.apply(this, [psbt, input, sequence, masterFingerprintBuffer]); - } -} diff --git a/class/wallets/hd-legacy-breadwallet-wallet.ts b/class/wallets/hd-legacy-breadwallet-wallet.ts new file mode 100644 index 00000000000..adc1e027b79 --- /dev/null +++ b/class/wallets/hd-legacy-breadwallet-wallet.ts @@ -0,0 +1,169 @@ +import BIP32Factory, { BIP32Interface } from 'bip32'; +import * as bitcoinjs from 'bitcoinjs-lib'; +import { Psbt } from 'bitcoinjs-lib'; +import { CoinSelectReturnInput } from 'coinselect'; + +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; +import { ElectrumHistory } from '../../blue_modules/BlueElectrum'; +import ecc from '../../blue_modules/noble_ecc'; +import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; +import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; + +const bip32 = BIP32Factory(ecc); + +/** + * HD Wallet (BIP39). + * In particular, Breadwallet-compatible (Legacy addresses) + */ +export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { + static readonly type = 'HDLegacyBreadwallet'; + static readonly typeReadable = 'HD Legacy Breadwallet (P2PKH)'; + // @ts-ignore: override + public readonly type = HDLegacyBreadwalletWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDLegacyBreadwalletWallet.typeReadable; + static readonly derivationPath = "m/0'"; + + // track address index at which wallet switched to segwit + _external_segwit_index: number | null = null; + _internal_segwit_index: number | null = null; + + // we need a separate function without external_addresses_cache to use in binarySearch + _calcNodeAddressByIndex(node: number, index: number, p2wpkh: boolean = false) { + let _node: BIP32Interface | undefined; + if (node === 0) { + _node = this._node0 || (this._node0 = bip32.fromBase58(this.getXpub()).derive(node)); + } + if (node === 1) { + _node = this._node1 || (this._node1 = bip32.fromBase58(this.getXpub()).derive(node)); + } + + if (!_node) { + throw new Error('Internal error: this._node0 or this._node1 is undefined'); + } + + const pubkey = _node.derive(index).publicKey; + const address = p2wpkh ? bitcoinjs.payments.p2wpkh({ pubkey }).address : bitcoinjs.payments.p2pkh({ pubkey }).address; + + if (!address) { + throw new Error('Internal error: no address in _calcNodeAddressByIndex'); + } + + return address; + } + + // this function is different from HDLegacyP2PKHWallet._getNodeAddressByIndex. + // It takes _external_segwit_index _internal_segwit_index for account + // and starts to generate segwit addresses if index more than them + _getNodeAddressByIndex(node: number, index: number): string { + index = index * 1; // cast to int + if (node === 0) { + if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit + } + + if (node === 1) { + if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit + } + + let p2wpkh = false; + if ( + (node === 0 && this._external_segwit_index !== null && index >= this._external_segwit_index) || + (node === 1 && this._internal_segwit_index !== null && index >= this._internal_segwit_index) + ) { + p2wpkh = true; + } + + const address = this._calcNodeAddressByIndex(node, index, p2wpkh); + + if (node === 0) { + return (this.external_addresses_cache[index] = address); + } + + if (node === 1) { + return (this.internal_addresses_cache[index] = address); + } + + throw new Error('Internal error: unknown node'); + } + + async fetchBalance() { + try { + if (this.next_free_change_address_index === 0 && this.next_free_address_index === 0) { + // doing binary search for last used addresses external/internal and legacy/bech32: + const [nextFreeExternalLegacy, nextFreeInternalLegacy] = await Promise.all([ + this._binarySearchIteration(0, 1000, 0, false), + this._binarySearchIteration(0, 1000, 1, false), + ]); + const [nextFreeExternalBech32, nextFreeInternalBech32] = await Promise.all([ + this._binarySearchIteration(nextFreeExternalLegacy, nextFreeExternalLegacy + 1000, 0, true), + this._binarySearchIteration(nextFreeInternalLegacy, nextFreeInternalLegacy + 1000, 1, true), + ]); + + // trying to detect if segwit activated. This condition can be deleted when BRD will enable segwit by default + if (nextFreeExternalLegacy < nextFreeExternalBech32) { + this._external_segwit_index = nextFreeExternalLegacy; + } + this.next_free_address_index = nextFreeExternalBech32; + + this._internal_segwit_index = nextFreeInternalLegacy; // force segwit for change + this.next_free_change_address_index = nextFreeInternalBech32; + } // end rescanning fresh wallet + + // finally fetching balance + await this._fetchBalance(); + } catch (err) { + console.warn(err); + } + } + + async _binarySearchIteration(startIndex: number, endIndex: number, node: number = 0, p2wpkh: boolean = false) { + const gerenateChunkAddresses = (chunkNum: number) => { + const ret = []; + for (let c = this.gap_limit * chunkNum; c < this.gap_limit * (chunkNum + 1); c++) { + ret.push(this._calcNodeAddressByIndex(node, c, p2wpkh)); + } + return ret; + }; + + let lastChunkWithUsedAddressesNum: number; + let lastHistoriesWithUsedAddresses: Record; + for (let c = 0; c < Math.round(endIndex / this.gap_limit); c++) { + const histories = await BlueElectrum.multiGetHistoryByAddress(gerenateChunkAddresses(c)); + if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) { + // in this particular chunk we have used addresses + lastChunkWithUsedAddressesNum = c; + lastHistoriesWithUsedAddresses = histories; + } else { + // empty chunk. no sense searching more chunks + break; + } + } + + let lastUsedIndex = startIndex; + + if (lastHistoriesWithUsedAddresses!) { + // now searching for last used address in batch lastChunkWithUsedAddressesNum + for ( + let c = lastChunkWithUsedAddressesNum! * this.gap_limit; + c < lastChunkWithUsedAddressesNum! * this.gap_limit + this.gap_limit; + c++ + ) { + const address = this._calcNodeAddressByIndex(node, c, p2wpkh); + if (lastHistoriesWithUsedAddresses[address] && lastHistoriesWithUsedAddresses[address].length > 0) { + lastUsedIndex = Math.max(c, lastUsedIndex) + 1; // point to next, which is supposed to be unsued + } + } + } + + return lastUsedIndex; + } + + _addPsbtInput(psbt: Psbt, input: CoinSelectReturnInput, sequence: number, masterFingerprintBuffer: Uint8Array) { + // hack to use + // AbstractHDElectrumWallet._addPsbtInput for bech32 address + // HDLegacyP2PKHWallet._addPsbtInput for legacy address + const ProxyClass = input?.address?.startsWith('bc1') ? AbstractHDElectrumWallet : HDLegacyP2PKHWallet; + const proxy = new ProxyClass(); + return proxy._addPsbtInput.apply(this, [psbt, input, sequence, masterFingerprintBuffer]); + } +} diff --git a/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.js b/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.js deleted file mode 100644 index ea46112434c..00000000000 --- a/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.js +++ /dev/null @@ -1,103 +0,0 @@ -import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; -import BIP32Factory from 'bip32'; -import ecc from '../../blue_modules/noble_ecc'; - -const bitcoin = require('bitcoinjs-lib'); -const mn = require('electrum-mnemonic'); -const bip32 = BIP32Factory(ecc); - -const PREFIX = mn.PREFIXES.standard; - -/** - * ElectrumSeed means that instead of BIP39 seed format it works with the format invented by Electrum wallet. Otherwise - * its a regular HD wallet that has all the properties of parent class. - * - * @see https://electrum.readthedocs.io/en/latest/seedphrase.html - */ -export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet { - static type = 'HDlegacyElectrumSeedP2PKH'; - static typeReadable = 'HD Legacy Electrum (BIP32 P2PKH)'; - static derivationPath = 'm'; - - validateMnemonic() { - return mn.validateMnemonic(this.secret, PREFIX); - } - - allowBIP47() { - return false; - } - - async generate() { - throw new Error('Not implemented'); - } - - getXpub() { - if (this._xpub) { - return this._xpub; // cache hit - } - const args = { prefix: PREFIX }; - if (this.passphrase) args.passphrase = this.passphrase; - const root = bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args)); - this._xpub = root.neutered().toBase58(); - return this._xpub; - } - - _getInternalAddressByIndex(index) { - index = index * 1; // cast to int - if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit - - const node = bip32.fromBase58(this.getXpub()); - const address = bitcoin.payments.p2pkh({ - pubkey: node.derive(1).derive(index).publicKey, - }).address; - - return (this.internal_addresses_cache[index] = address); - } - - _getExternalAddressByIndex(index) { - index = index * 1; // cast to int - if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit - - const node = bip32.fromBase58(this.getXpub()); - const address = bitcoin.payments.p2pkh({ - pubkey: node.derive(0).derive(index).publicKey, - }).address; - - return (this.external_addresses_cache[index] = address); - } - - _getWIFByIndex(internal, index) { - if (!this.secret) return false; - const args = { prefix: PREFIX }; - if (this.passphrase) args.passphrase = this.passphrase; - const root = bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args)); - const path = `m/${internal ? 1 : 0}/${index}`; - const child = root.derivePath(path); - - return child.toWIF(); - } - - _getNodePubkeyByIndex(node, index) { - index = index * 1; // cast to int - - if (node === 0 && !this._node0) { - const xpub = this.getXpub(); - const hdNode = bip32.fromBase58(xpub); - this._node0 = hdNode.derive(node); - } - - if (node === 1 && !this._node1) { - const xpub = this.getXpub(); - const hdNode = bip32.fromBase58(xpub); - this._node1 = hdNode.derive(node); - } - - if (node === 0) { - return this._node0.derive(index).publicKey; - } - - if (node === 1) { - return this._node1.derive(index).publicKey; - } - } -} diff --git a/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.ts b/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.ts new file mode 100644 index 00000000000..6ce6aac804d --- /dev/null +++ b/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.ts @@ -0,0 +1,120 @@ +import BIP32Factory from 'bip32'; +import * as bitcoin from 'bitcoinjs-lib'; +import * as mn from 'electrum-mnemonic'; + +import ecc from '../../blue_modules/noble_ecc'; +import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; + +const bip32 = BIP32Factory(ecc); +const PREFIX = mn.PREFIXES.standard; + +type SeedOpts = { + prefix?: string; + passphrase?: string; +}; + +/** + * ElectrumSeed means that instead of BIP39 seed format it works with the format invented by Electrum wallet. Otherwise + * its a regular HD wallet that has all the properties of parent class. + * + * @see https://electrum.readthedocs.io/en/latest/seedphrase.html + */ +export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet { + static readonly type = 'HDlegacyElectrumSeedP2PKH'; + static readonly typeReadable = 'HD Legacy Electrum (BIP32 P2PKH)'; + // @ts-ignore: override + public readonly type = HDLegacyElectrumSeedP2PKHWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDLegacyElectrumSeedP2PKHWallet.typeReadable; + static readonly derivationPath = 'm'; + + validateMnemonic() { + return mn.validateMnemonic(this.secret, PREFIX); + } + + allowBIP47() { + return false; + } + + async generate() { + throw new Error('Not implemented'); + } + + getXpub() { + if (this._xpub) { + return this._xpub; // cache hit + } + const args: SeedOpts = { prefix: PREFIX }; + if (this.passphrase) args.passphrase = this.passphrase; + const root = bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args)); + this._xpub = root.neutered().toBase58(); + return this._xpub; + } + + _getInternalAddressByIndex(index: number) { + index = index * 1; // cast to int + if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit + + const node = bip32.fromBase58(this.getXpub()); + const address = bitcoin.payments.p2pkh({ + pubkey: node.derive(1).derive(index).publicKey, + }).address; + if (!address) { + throw new Error('Internal error: no address in _getInternalAddressByIndex'); + } + + return (this.internal_addresses_cache[index] = address); + } + + _getExternalAddressByIndex(index: number) { + index = index * 1; // cast to int + if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit + + const node = bip32.fromBase58(this.getXpub()); + const address = bitcoin.payments.p2pkh({ + pubkey: node.derive(0).derive(index).publicKey, + }).address; + if (!address) { + throw new Error('Internal error: no address in _getExternalAddressByIndex'); + } + + return (this.external_addresses_cache[index] = address); + } + + _getWIFByIndex(internal: boolean, index: number): string | false { + if (!this.secret) return false; + const args: SeedOpts = { prefix: PREFIX }; + if (this.passphrase) args.passphrase = this.passphrase; + const root = bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args)); + const path = `m/${internal ? 1 : 0}/${index}`; + const child = root.derivePath(path); + + return child.toWIF(); + } + + _getNodePubkeyByIndex(node: number, index: number) { + index = index * 1; // cast to int + + if (node === 0 && !this._node0) { + const xpub = this.getXpub(); + const hdNode = bip32.fromBase58(xpub); + this._node0 = hdNode.derive(node); + } + + if (node === 1 && !this._node1) { + const xpub = this.getXpub(); + const hdNode = bip32.fromBase58(xpub); + this._node1 = hdNode.derive(node); + } + + if (node === 0 && this._node0) { + return this._node0.derive(index).publicKey; + } + + if (node === 1 && this._node1) { + return this._node1.derive(index).publicKey; + } + + throw new Error('Internal error: this._node0 or this._node1 is undefined'); + } +} diff --git a/class/wallets/hd-legacy-p2pkh-wallet.js b/class/wallets/hd-legacy-p2pkh-wallet.js deleted file mode 100644 index 088db99b5b1..00000000000 --- a/class/wallets/hd-legacy-p2pkh-wallet.js +++ /dev/null @@ -1,100 +0,0 @@ -import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; -import BIP32Factory from 'bip32'; -import ecc from '../../blue_modules/noble_ecc'; -const bip32 = BIP32Factory(ecc); -const BlueElectrum = require('../../blue_modules/BlueElectrum'); - -/** - * HD Wallet (BIP39). - * In particular, BIP44 (P2PKH legacy addressess) - * @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki - */ -export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet { - static type = 'HDlegacyP2PKH'; - static typeReadable = 'HD Legacy (BIP44 P2PKH)'; - static derivationPath = "m/44'/0'/0'"; - - allowSend() { - return true; - } - - allowCosignPsbt() { - return true; - } - - allowSignVerifyMessage() { - return true; - } - - allowMasterFingerprint() { - return true; - } - - allowXpub() { - return true; - } - - allowBIP47() { - return true; - } - - getXpub() { - if (this._xpub) { - return this._xpub; // cache hit - } - const seed = this._getSeed(); - const root = bip32.fromSeed(seed); - - const path = this.getDerivationPath(); - const child = root.derivePath(path).neutered(); - this._xpub = child.toBase58(); - - return this._xpub; - } - - _hdNodeToAddress(hdNode) { - return this._nodeToLegacyAddress(hdNode); - } - - async fetchUtxo() { - await super.fetchUtxo(); - // now we need to fetch txhash for each input as required by PSBT - const txhexes = await BlueElectrum.multiGetTransactionByTxid( - this.getUtxo().map(x => x.txid), - 50, - false, - ); - - const newUtxos = []; - for (const u of this.getUtxo()) { - if (txhexes[u.txid]) u.txhex = txhexes[u.txid]; - newUtxos.push(u); - } - - return newUtxos; - } - - _addPsbtInput(psbt, input, sequence, masterFingerprintBuffer) { - const pubkey = this._getPubkeyByAddress(input.address); - const path = this._getDerivationPathByAddress(input.address, 44); - - if (!input.txhex) throw new Error('UTXO is missing txhex of the input, which is required by PSBT for non-segwit input'); - - psbt.addInput({ - hash: input.txid, - index: input.vout, - sequence, - bip32Derivation: [ - { - masterFingerprint: masterFingerprintBuffer, - path, - pubkey, - }, - ], - // non-segwit inputs now require passing the whole previous tx as Buffer - nonWitnessUtxo: Buffer.from(input.txhex, 'hex'), - }); - - return psbt; - } -} diff --git a/class/wallets/hd-legacy-p2pkh-wallet.ts b/class/wallets/hd-legacy-p2pkh-wallet.ts new file mode 100644 index 00000000000..8b81f9e8689 --- /dev/null +++ b/class/wallets/hd-legacy-p2pkh-wallet.ts @@ -0,0 +1,117 @@ +import BIP32Factory, { BIP32Interface } from 'bip32'; +import { Psbt } from 'bitcoinjs-lib'; +import { CoinSelectReturnInput } from 'coinselect'; + +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; +import ecc from '../../blue_modules/noble_ecc'; +import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; +import { hexToUint8Array } from '../../blue_modules/uint8array-extras'; + +const bip32 = BIP32Factory(ecc); + +/** + * HD Wallet (BIP39). + * In particular, BIP44 (P2PKH legacy addressess) + * @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki + */ +export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet { + static readonly type = 'HDlegacyP2PKH'; + static readonly typeReadable = 'HD Legacy (BIP44 P2PKH)'; + // @ts-ignore: override + public readonly type = HDLegacyP2PKHWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDLegacyP2PKHWallet.typeReadable; + static readonly derivationPath = "m/44'/0'/0'"; + + allowSend() { + return true; + } + + allowCosignPsbt() { + return true; + } + + allowSignVerifyMessage() { + return true; + } + + allowMasterFingerprint() { + return true; + } + + allowXpub() { + return true; + } + + allowBIP47() { + return true; + } + + getXpub() { + if (this._xpub) { + return this._xpub; // cache hit + } + const seed = this._getSeed(); + const root = bip32.fromSeed(seed); + + const path = this.getDerivationPath(); + if (!path) { + throw new Error('Internal error: no path'); + } + const child = root.derivePath(path).neutered(); + this._xpub = child.toBase58(); + + return this._xpub; + } + + _hdNodeToAddress(hdNode: BIP32Interface): string { + return this._nodeToLegacyAddress(hdNode); + } + + async fetchUtxo(): Promise { + await super.fetchUtxo(); + // now we need to fetch txhash for each input as required by PSBT + const txhexes = await BlueElectrum.multiGetTransactionByTxid( + this.getUtxo().map(x => x.txid), + false, + ); + + for (const u of this.getUtxo()) { + if (txhexes[u.txid]) u.txhex = txhexes[u.txid]; + } + } + + _addPsbtInput(psbt: Psbt, input: CoinSelectReturnInput, sequence: number, masterFingerprintBuffer: Uint8Array) { + if (!input.address) { + throw new Error('Internal error: no address on Utxo during _addPsbtInput()'); + } + const pubkey = this._getPubkeyByAddress(input.address); + const path = this._getDerivationPathByAddress(input.address); + if (!pubkey || !path) { + throw new Error('Internal error: pubkey or path are invalid'); + } + + if (!input.txhex) throw new Error('UTXO is missing txhex of the input, which is required by PSBT for non-segwit input'); + + psbt.addInput({ + hash: input.txid, + index: input.vout, + sequence, + bip32Derivation: [ + { + masterFingerprint: masterFingerprintBuffer, + path, + pubkey, + }, + ], + // non-segwit inputs now require passing the whole previous tx as Buffer + nonWitnessUtxo: hexToUint8Array(input.txhex), + }); + + return psbt; + } + + allowSilentPaymentSend(): boolean { + return true; + } +} diff --git a/class/wallets/hd-segwit-bech32-wallet.js b/class/wallets/hd-segwit-bech32-wallet.js deleted file mode 100644 index e86d158ed74..00000000000 --- a/class/wallets/hd-segwit-bech32-wallet.js +++ /dev/null @@ -1,53 +0,0 @@ -import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; - -/** - * HD Wallet (BIP39). - * In particular, BIP84 (Bech32 Native Segwit) - * @see https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki - */ -export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet { - static type = 'HDsegwitBech32'; - static typeReadable = 'HD SegWit (BIP84 Bech32 Native)'; - static segwitType = 'p2wpkh'; - static derivationPath = "m/84'/0'/0'"; - - allowSend() { - return true; - } - - allowHodlHodlTrading() { - return true; - } - - allowRBF() { - return true; - } - - allowPayJoin() { - return true; - } - - allowCosignPsbt() { - return true; - } - - isSegwit() { - return true; - } - - allowSignVerifyMessage() { - return true; - } - - allowMasterFingerprint() { - return true; - } - - allowXpub() { - return true; - } - - allowBIP47() { - return true; - } -} diff --git a/class/wallets/hd-segwit-bech32-wallet.ts b/class/wallets/hd-segwit-bech32-wallet.ts new file mode 100644 index 00000000000..bb51a22c203 --- /dev/null +++ b/class/wallets/hd-segwit-bech32-wallet.ts @@ -0,0 +1,57 @@ +import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; + +/** + * HD Wallet (BIP39). + * In particular, BIP84 (Bech32 Native Segwit) + * @see https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki + */ +export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet { + static readonly type = 'HDsegwitBech32'; + static readonly typeReadable = 'HD SegWit (BIP84 Bech32 Native)'; + // @ts-ignore: override + public readonly type = HDSegwitBech32Wallet.type; + // @ts-ignore: override + public readonly typeReadable = HDSegwitBech32Wallet.typeReadable; + public readonly segwitType = 'p2wpkh'; + static readonly derivationPath = "m/84'/0'/0'"; + + allowSend() { + return true; + } + + allowRBF() { + return true; + } + + allowPayJoin() { + return true; + } + + allowCosignPsbt() { + return true; + } + + isSegwit() { + return true; + } + + allowSignVerifyMessage() { + return true; + } + + allowMasterFingerprint() { + return true; + } + + allowXpub() { + return true; + } + + allowBIP47() { + return true; + } + + allowSilentPaymentSend(): boolean { + return true; + } +} diff --git a/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.js b/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.js deleted file mode 100644 index 469830a10ea..00000000000 --- a/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.js +++ /dev/null @@ -1,117 +0,0 @@ -import b58 from 'bs58check'; -import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; -import BIP32Factory from 'bip32'; -import ecc from '../../blue_modules/noble_ecc'; - -const bitcoin = require('bitcoinjs-lib'); -const mn = require('electrum-mnemonic'); -const bip32 = BIP32Factory(ecc); - -const PREFIX = mn.PREFIXES.segwit; - -/** - * ElectrumSeed means that instead of BIP39 seed format it works with the format invented by Electrum wallet. Otherwise - * its a regular HD wallet that has all the properties of parent class. - * - * @see https://electrum.readthedocs.io/en/latest/seedphrase.html - */ -export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { - static type = 'HDSegwitElectrumSeedP2WPKHWallet'; - static typeReadable = 'HD Electrum (BIP32 P2WPKH)'; - static derivationPath = "m/0'"; - - validateMnemonic() { - return mn.validateMnemonic(this.secret, PREFIX); - } - - allowBIP47() { - return false; - } - - async generate() { - throw new Error('Not implemented'); - } - - getXpub() { - if (this._xpub) { - return this._xpub; // cache hit - } - const args = { prefix: PREFIX }; - if (this.passphrase) args.passphrase = this.passphrase; - const root = bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args)); - const xpub = root.derivePath("m/0'").neutered().toBase58(); - - // bitcoinjs does not support zpub yet, so we just convert it from xpub - let data = b58.decode(xpub); - data = data.slice(4); - data = Buffer.concat([Buffer.from('04b24746', 'hex'), data]); - this._xpub = b58.encode(data); - - return this._xpub; - } - - _getInternalAddressByIndex(index) { - index = index * 1; // cast to int - if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit - - const xpub = this._zpubToXpub(this.getXpub()); - const node = bip32.fromBase58(xpub); - const address = bitcoin.payments.p2wpkh({ - pubkey: node.derive(1).derive(index).publicKey, - }).address; - - return (this.internal_addresses_cache[index] = address); - } - - _getExternalAddressByIndex(index) { - index = index * 1; // cast to int - if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit - - const xpub = this._zpubToXpub(this.getXpub()); - const node = bip32.fromBase58(xpub); - const address = bitcoin.payments.p2wpkh({ - pubkey: node.derive(0).derive(index).publicKey, - }).address; - - return (this.external_addresses_cache[index] = address); - } - - _getWIFByIndex(internal, index) { - if (!this.secret) return false; - const args = { prefix: PREFIX }; - if (this.passphrase) args.passphrase = this.passphrase; - const root = bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args)); - const path = `m/0'/${internal ? 1 : 0}/${index}`; - const child = root.derivePath(path); - - return child.toWIF(); - } - - _getNodePubkeyByIndex(node, index) { - index = index * 1; // cast to int - - if (node === 0 && !this._node0) { - const xpub = this._zpubToXpub(this.getXpub()); - const hdNode = bip32.fromBase58(xpub); - this._node0 = hdNode.derive(node); - } - - if (node === 1 && !this._node1) { - const xpub = this._zpubToXpub(this.getXpub()); - const hdNode = bip32.fromBase58(xpub); - this._node1 = hdNode.derive(node); - } - - if (node === 0) { - return this._node0.derive(index).publicKey; - } - - if (node === 1) { - return this._node1.derive(index).publicKey; - } - } - - isSegwit() { - return true; - } -} diff --git a/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.ts b/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.ts new file mode 100644 index 00000000000..0ec4071844d --- /dev/null +++ b/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.ts @@ -0,0 +1,135 @@ +import BIP32Factory from 'bip32'; +import * as bitcoin from 'bitcoinjs-lib'; +import b58 from 'bs58check'; +import * as mn from 'electrum-mnemonic'; + +import ecc from '../../blue_modules/noble_ecc'; +import { concatUint8Arrays, hexToUint8Array } from '../../blue_modules/uint8array-extras'; +import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; + +const bip32 = BIP32Factory(ecc); +const PREFIX = mn.PREFIXES.segwit; + +type SeedOpts = { + prefix?: string; + passphrase?: string; +}; + +/** + * ElectrumSeed means that instead of BIP39 seed format it works with the format invented by Electrum wallet. Otherwise + * its a regular HD wallet that has all the properties of parent class. + * + * @see https://electrum.readthedocs.io/en/latest/seedphrase.html + */ +export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { + static readonly type = 'HDSegwitElectrumSeedP2WPKHWallet'; + static readonly typeReadable = 'HD Electrum (BIP32 P2WPKH)'; + // @ts-ignore: override + public readonly type = HDSegwitElectrumSeedP2WPKHWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDSegwitElectrumSeedP2WPKHWallet.typeReadable; + static readonly derivationPath = "m/0'"; + + validateMnemonic() { + return mn.validateMnemonic(this.secret, PREFIX); + } + + allowBIP47() { + return false; + } + + async generate() { + throw new Error('Not implemented'); + } + + getXpub() { + if (this._xpub) { + return this._xpub; // cache hit + } + const args: SeedOpts = { prefix: PREFIX }; + if (this.passphrase) args.passphrase = this.passphrase; + const root = bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args)); + const xpub = root.derivePath("m/0'").neutered().toBase58(); + + // bitcoinjs does not support zpub yet, so we just convert it from xpub + let data = b58.decode(xpub); + data = data.slice(4); + const concatenated = concatUint8Arrays([hexToUint8Array('04b24746'), data]); + this._xpub = b58.encode(concatenated); + + return this._xpub; + } + + _getInternalAddressByIndex(index: number) { + index = index * 1; // cast to int + if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit + + const xpub = this._zpubToXpub(this.getXpub()); + const node = bip32.fromBase58(xpub); + const address = bitcoin.payments.p2wpkh({ + pubkey: node.derive(1).derive(index).publicKey, + }).address; + if (!address) { + throw new Error('Internal error: no address in _getInternalAddressByIndex'); + } + + return (this.internal_addresses_cache[index] = address); + } + + _getExternalAddressByIndex(index: number) { + index = index * 1; // cast to int + if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit + + const xpub = this._zpubToXpub(this.getXpub()); + const node = bip32.fromBase58(xpub); + const address = bitcoin.payments.p2wpkh({ + pubkey: node.derive(0).derive(index).publicKey, + }).address; + if (!address) { + throw new Error('Internal error: no address in _getExternalAddressByIndex'); + } + + return (this.external_addresses_cache[index] = address); + } + + _getWIFByIndex(internal: boolean, index: number): string | false { + if (!this.secret) return false; + const args: SeedOpts = { prefix: PREFIX }; + if (this.passphrase) args.passphrase = this.passphrase; + const root = bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args)); + const path = `m/0'/${internal ? 1 : 0}/${index}`; + const child = root.derivePath(path); + + return child.toWIF(); + } + + _getNodePubkeyByIndex(node: number, index: number) { + index = index * 1; // cast to int + + if (node === 0 && !this._node0) { + const xpub = this._zpubToXpub(this.getXpub()); + const hdNode = bip32.fromBase58(xpub); + this._node0 = hdNode.derive(node); + } + + if (node === 1 && !this._node1) { + const xpub = this._zpubToXpub(this.getXpub()); + const hdNode = bip32.fromBase58(xpub); + this._node1 = hdNode.derive(node); + } + + if (node === 0 && this._node0) { + return this._node0.derive(index).publicKey; + } + + if (node === 1 && this._node1) { + return this._node1.derive(index).publicKey; + } + + throw new Error('Internal error: this._node0 or this._node1 is undefined'); + } + + isSegwit() { + return true; + } +} diff --git a/class/wallets/hd-segwit-p2sh-wallet.js b/class/wallets/hd-segwit-p2sh-wallet.js deleted file mode 100644 index 5ed8171502a..00000000000 --- a/class/wallets/hd-segwit-p2sh-wallet.js +++ /dev/null @@ -1,104 +0,0 @@ -import b58 from 'bs58check'; -import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; -import BIP32Factory from 'bip32'; -import ecc from '../../blue_modules/noble_ecc'; -const bip32 = BIP32Factory(ecc); -const bitcoin = require('bitcoinjs-lib'); - -/** - * HD Wallet (BIP39). - * In particular, BIP49 (P2SH Segwit) - * @see https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki - */ -export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet { - static type = 'HDsegwitP2SH'; - static typeReadable = 'HD SegWit (BIP49 P2SH)'; - static segwitType = 'p2sh(p2wpkh)'; - static derivationPath = "m/49'/0'/0'"; - - allowSend() { - return true; - } - - allowCosignPsbt() { - return true; - } - - allowSignVerifyMessage() { - return true; - } - - allowHodlHodlTrading() { - return true; - } - - allowMasterFingerprint() { - return true; - } - - allowXpub() { - return true; - } - - _hdNodeToAddress(hdNode) { - return this._nodeToP2shSegwitAddress(hdNode); - } - - /** - * Returning ypub actually, not xpub. Keeping same method name - * for compatibility. - * - * @return {String} ypub - */ - getXpub() { - if (this._xpub) { - return this._xpub; // cache hit - } - // first, getting xpub - const seed = this._getSeed(); - const root = bip32.fromSeed(seed); - - const path = this.getDerivationPath(); - const child = root.derivePath(path).neutered(); - const xpub = child.toBase58(); - - // bitcoinjs does not support ypub yet, so we just convert it from xpub - let data = b58.decode(xpub); - data = data.slice(4); - data = Buffer.concat([Buffer.from('049d7cb2', 'hex'), data]); - this._xpub = b58.encode(data); - - return this._xpub; - } - - _addPsbtInput(psbt, input, sequence, masterFingerprintBuffer) { - const pubkey = this._getPubkeyByAddress(input.address); - const path = this._getDerivationPathByAddress(input.address); - const p2wpkh = bitcoin.payments.p2wpkh({ pubkey }); - const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh }); - - psbt.addInput({ - hash: input.txid, - index: input.vout, - sequence, - bip32Derivation: [ - { - masterFingerprint: masterFingerprintBuffer, - path, - pubkey, - }, - ], - witnessUtxo: { - script: p2sh.output, - value: input.amount || input.value, - }, - redeemScript: p2wpkh.output, - }); - - return psbt; - } - - isSegwit() { - return true; - } -} diff --git a/class/wallets/hd-segwit-p2sh-wallet.ts b/class/wallets/hd-segwit-p2sh-wallet.ts new file mode 100644 index 00000000000..0e3d88f6ed0 --- /dev/null +++ b/class/wallets/hd-segwit-p2sh-wallet.ts @@ -0,0 +1,125 @@ +import BIP32Factory, { BIP32Interface } from 'bip32'; +import * as bitcoin from 'bitcoinjs-lib'; +import { Psbt } from 'bitcoinjs-lib'; +import b58 from 'bs58check'; +import { CoinSelectReturnInput } from 'coinselect'; + +import ecc from '../../blue_modules/noble_ecc'; +import { concatUint8Arrays, hexToUint8Array } from '../../blue_modules/uint8array-extras'; +import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; + +const bip32 = BIP32Factory(ecc); + +/** + * HD Wallet (BIP39). + * In particular, BIP49 (P2SH Segwit) + * @see https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki + */ +export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet { + static readonly type = 'HDsegwitP2SH'; + static readonly typeReadable = 'HD SegWit (BIP49 P2SH)'; + // @ts-ignore: override + public readonly type = HDSegwitP2SHWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDSegwitP2SHWallet.typeReadable; + public readonly segwitType = 'p2sh(p2wpkh)'; + static readonly derivationPath = "m/49'/0'/0'"; + + allowSend() { + return true; + } + + allowCosignPsbt() { + return true; + } + + allowSignVerifyMessage() { + return true; + } + + allowMasterFingerprint() { + return true; + } + + allowXpub() { + return true; + } + + _hdNodeToAddress(hdNode: BIP32Interface): string { + return this._nodeToP2shSegwitAddress(hdNode); + } + + /** + * Returning ypub actually, not xpub. Keeping same method name + * for compatibility. + * + * @return {String} ypub + */ + getXpub() { + if (this._xpub) { + return this._xpub; // cache hit + } + // first, getting xpub + const seed = this._getSeed(); + const root = bip32.fromSeed(seed); + + const path = this.getDerivationPath(); + if (!path) { + throw new Error('Internal error: no path'); + } + const child = root.derivePath(path).neutered(); + const xpub = child.toBase58(); + + // bitcoinjs does not support ypub yet, so we just convert it from xpub + let data = b58.decode(xpub); + data = data.slice(4); + const concatenated = concatUint8Arrays([hexToUint8Array('049d7cb2'), data]); + this._xpub = b58.encode(concatenated); + + return this._xpub; + } + + _addPsbtInput(psbt: Psbt, input: CoinSelectReturnInput, sequence: number, masterFingerprintBuffer: Uint8Array) { + if (!input.address) { + throw new Error('Internal error: no address on Utxo during _addPsbtInput()'); + } + const pubkey = this._getPubkeyByAddress(input.address); + const path = this._getDerivationPathByAddress(input.address); + if (!pubkey || !path) { + throw new Error('Internal error: pubkey or path are invalid'); + } + const p2wpkh = bitcoin.payments.p2wpkh({ pubkey }); + const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh }); + if (!p2sh.output) { + throw new Error('Internal error: no p2sh.output during _addPsbtInput()'); + } + + psbt.addInput({ + hash: input.txid, + index: input.vout, + sequence, + bip32Derivation: [ + { + masterFingerprint: masterFingerprintBuffer, + path, + pubkey, + }, + ], + witnessUtxo: { + script: p2sh.output, + value: BigInt(input.value), + }, + redeemScript: p2wpkh.output, + }); + + return psbt; + } + + isSegwit() { + return true; + } + + allowSilentPaymentSend(): boolean { + return true; + } +} diff --git a/class/wallets/hd-taproot-wallet.ts b/class/wallets/hd-taproot-wallet.ts new file mode 100644 index 00000000000..8d0851b1a2a --- /dev/null +++ b/class/wallets/hd-taproot-wallet.ts @@ -0,0 +1,166 @@ +import BIP32Factory, { BIP32Interface } from 'bip32'; +import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; +import ecc from '../../blue_modules/noble_ecc'; +import * as bitcoin from 'bitcoinjs-lib'; +import { Psbt } from 'bitcoinjs-lib'; +import { CoinSelectReturnInput } from 'coinselect'; + +const bip32 = BIP32Factory(ecc); + +/** + * @see https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki + */ +export class HDTaprootWallet extends AbstractHDElectrumWallet { + static readonly type = 'HDtaproot'; + static readonly typeReadable = 'HD Taproot (BIP86)'; + // @ts-ignore: override + public readonly type = HDTaprootWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDTaprootWallet.typeReadable; + public readonly segwitType = 'p2tr'; + static readonly derivationPath = "m/86'/0'/0'"; + + getXpub() { + if (this._xpub) { + return this._xpub; // cache hit + } + const seed = this._getSeed(); + const root = bip32.fromSeed(seed); + + const path = this.getDerivationPath(); + if (!path) { + throw new Error('Internal error: no path'); + } + const child = root.derivePath(path).neutered(); + const xpub = child.toBase58(); + this._xpub = xpub; + + // returning regular xpub since industry standard is to use regular xpubs for Taproot wallets without any + // kind of prefix change (like ypub or zpub) + return xpub; + } + + _hdNodeToAddress(hdNode: BIP32Interface): string { + return this._nodeToTaprootAddress(hdNode); + } + + _nodeToTaprootAddress(hdNode: BIP32Interface): string { + const xOnlyPubkey = hdNode.publicKey.subarray(1, 33); + + const { address } = bitcoin.payments.p2tr({ + internalPubkey: xOnlyPubkey, + }); + + if (!address) { + throw new Error('Could not create address in _nodeToTaprootAddress'); + } + + return address; + } + + _getNodePubkeyByIndex(node: number, index: number) { + index = index * 1; // cast to int + + if (node === 0 && !this._node0) { + let xpub = this.getXpub(); + if (xpub.startsWith('zpub')) { + // bip32.fromBase58() wont work with zpub prefix, need to swap it for the traditional one + xpub = this._zpubToXpub(xpub); + } + const hdNode = bip32.fromBase58(xpub); + this._node0 = hdNode.derive(node); + } + + if (node === 1 && !this._node1) { + let xpub = this.getXpub(); + if (xpub.startsWith('zpub')) { + // bip32.fromBase58() wont work with zpub prefix, need to swap it for the traditional one + xpub = this._zpubToXpub(xpub); + } + const hdNode = bip32.fromBase58(xpub); + this._node1 = hdNode.derive(node); + } + + if (node === 0 && this._node0) { + return this._node0.derive(index).publicKey.subarray(1, 33); + } + + if (node === 1 && this._node1) { + return this._node1.derive(index).publicKey.subarray(1, 33); + } + + throw new Error('Internal error: this._node0 or this._node1 is undefined'); + } + + _addPsbtInput(psbt: Psbt, input: CoinSelectReturnInput, sequence: number, masterFingerprintBuffer: Buffer) { + if (!input.address) { + throw new Error('Internal error: no address on Utxo during _addPsbtInput()'); + } + const pubkey = this._getPubkeyByAddress(input.address); + const path = this._getDerivationPathByAddress(input.address); + if (!pubkey || !path) { + throw new Error('Internal error: pubkey or path are invalid'); + } + + const p2tr = bitcoin.payments.p2tr({ + internalPubkey: pubkey, + }); + if (!p2tr.output) throw new Error('Could not build p2tr.output'); + + psbt.addInput({ + hash: input.txid, + index: input.vout, + sequence, + witnessUtxo: { + script: p2tr.output!, + value: BigInt(input.value), + }, + tapBip32Derivation: [ + { + pubkey: new Uint8Array(pubkey), + masterFingerprint: new Uint8Array(masterFingerprintBuffer), + path, + leafHashes: [], + }, + ], + + // tell PSBT it’s a key-path Taproot spend + tapInternalKey: pubkey, + }); + + return psbt; + } + + allowSend() { + return true; + } + + allowCosignPsbt() { + return true; + } + + // is it even used anywhere..? + isSegwit() { + return true; + } + + allowSignVerifyMessage() { + return false; + } + + allowMasterFingerprint() { + return true; + } + + allowXpub() { + return true; + } + + allowBIP47() { + return true; + } + + allowSilentPaymentSend(): boolean { + return true; + } +} diff --git a/class/wallets/legacy-wallet.ts b/class/wallets/legacy-wallet.ts index e39ae85809e..81a981e1b12 100644 --- a/class/wallets/legacy-wallet.ts +++ b/class/wallets/legacy-wallet.ts @@ -1,16 +1,17 @@ import BigNumber from 'bignumber.js'; -import bitcoinMessage from 'bitcoinjs-message'; -import { randomBytes } from '../rng'; -import { AbstractWallet } from './abstract-wallet'; -import { HDSegwitBech32Wallet } from '..'; import * as bitcoin from 'bitcoinjs-lib'; -import * as BlueElectrum from '../../blue_modules/BlueElectrum'; -import coinSelect, { CoinSelectOutput, CoinSelectReturnInput, CoinSelectTarget, CoinSelectUtxo } from 'coinselect'; +import bitcoinMessage from 'bitcoinjs-message'; +import coinSelect, { CoinSelectOutput, CoinSelectReturnInput, CoinSelectTarget } from 'coinselect'; import coinSelectSplit from 'coinselect/split'; -import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types'; import { ECPairAPI, ECPairFactory, Signer } from 'ecpair'; +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; import ecc from '../../blue_modules/noble_ecc'; +import { hexToUint8Array, concatUint8Arrays } from '../../blue_modules/uint8array-extras'; +import type { HDSegwitBech32Wallet as HDSegwitBech32WalletT } from './hd-segwit-bech32-wallet'; +import { randomBytes } from '../rng'; +import { AbstractWallet } from './abstract-wallet'; +import { CreateTransactionResult, CreateTransactionTarget, CreateTransactionUtxo, Transaction, Utxo } from './types'; const ECPair: ECPairAPI = ECPairFactory(ecc); bitcoin.initEccLib(ecc); @@ -19,12 +20,22 @@ bitcoin.initEccLib(ecc); * (legacy P2PKH compressed) */ export class LegacyWallet extends AbstractWallet { - static type = 'legacy'; - static typeReadable = 'Legacy (P2PKH)'; + static readonly type = 'legacy'; + static readonly defaultTypeReadable = 'Legacy (P2PKH)'; + // @ts-ignore: override + public readonly type = LegacyWallet.type; + // @ts-ignore: override + public readonly typeReadable: string; _txs_by_external_index: Transaction[] = []; _txs_by_internal_index: Transaction[] = []; + constructor(typeReadable?: string) { + super(); + + this.typeReadable = typeReadable ?? LegacyWallet.defaultTypeReadable; + } + /** * Simple function which says that we havent tried to fetch balance * for a long time @@ -58,25 +69,13 @@ export class LegacyWallet extends AbstractWallet { this.secret = ECPair.makeRandom({ rng: () => buf }).toWIF(); } - async generateFromEntropy(user: Buffer): Promise { - let i = 0; - do { - i += 1; - const random = await randomBytes(user.length < 32 ? 32 - user.length : 0); - const buf = Buffer.concat([user, random], 32); - try { - this.secret = ECPair.fromPrivateKey(buf).toWIF(); - return; - } catch (e) { - if (i === 5) throw e; - } - } while (true); + async generateFromEntropy(user: Uint8Array): Promise { + if (user.length !== 32) { + throw new Error('Entropy should be 32 bytes'); + } + this.secret = ECPair.fromPrivateKey(user).toWIF(); } - /** - * - * @returns {string} - */ getAddress(): string | false { if (this._address) return this._address; let address; @@ -131,26 +130,25 @@ export class LegacyWallet extends AbstractWallet { const address = this.getAddress(); if (!address) throw new Error('LegacyWallet: Invalid address'); const utxos = await BlueElectrum.multiGetUtxoByAddress([address]); - this.utxo = []; + this._utxo = []; for (const arr of Object.values(utxos)) { - this.utxo = this.utxo.concat(arr); + this._utxo = this._utxo.concat(arr); } // now we need to fetch txhash for each input as required by PSBT if (LegacyWallet.type !== this.type) return; // but only for LEGACY single-address wallets const txhexes = await BlueElectrum.multiGetTransactionByTxid( - this.utxo.map(u => u.txId), - 50, + this._utxo.map(u => u.txid), false, ); const newUtxos = []; - for (const u of this.utxo) { - if (txhexes[u.txId]) u.txhex = txhexes[u.txId]; + for (const u of this._utxo) { + if (txhexes[u.txid]) u.txhex = txhexes[u.txid]; newUtxos.push(u); } - this.utxo = newUtxos; + this._utxo = newUtxos; } catch (error) { console.warn(error); } @@ -161,10 +159,8 @@ export class LegacyWallet extends AbstractWallet { * [ { height: 0, * value: 666, * address: 'string', - * txId: 'string', * vout: 1, * txid: 'string', - * amount: 666, * wif: 'string', * confirmations: 0 } ] * @@ -173,8 +169,7 @@ export class LegacyWallet extends AbstractWallet { */ getUtxo(respectFrozen = false): Utxo[] { let ret: Utxo[] = []; - for (const u of this.utxo) { - if (u.txId) u.txid = u.txId; + for (const u of this._utxo) { if (!u.confirmations && u.height) u.confirmations = BlueElectrum.estimateCurrentBlockheight() - u.height; ret.push(u); } @@ -211,11 +206,9 @@ export class LegacyWallet extends AbstractWallet { const value = new BigNumber(output.value).multipliedBy(100000000).toNumber(); utxos.push({ txid: tx.txid, - txId: tx.txid, vout: output.n, address, value, - amount: value, confirmations: tx.confirmations, wif: false, height: BlueElectrum.estimateCurrentBlockheight() - (tx.confirmations ?? 0), @@ -228,9 +221,10 @@ export class LegacyWallet extends AbstractWallet { // got all utxos we ever had. lets filter out the ones that are spent: const ret = []; + const txs = this.getTransactions(); for (const utxo of utxos) { let spent = false; - for (const tx of this.getTransactions()) { + for (const tx of txs) { for (const input of tx.inputs) { if (input.txid === utxo.txid && input.vout === utxo.vout) spent = true; // utxo we got previously was actually spent right here ^^ @@ -280,7 +274,7 @@ export class LegacyWallet extends AbstractWallet { // is safe because in that case our cache is filled // next, batch fetching each txid we got - const txdatas = await BlueElectrum.multiGetTransactionByTxid(Object.keys(txs)); + const txdatas = await BlueElectrum.multiGetTransactionByTxid(Object.keys(txs), true); const transactions = Object.values(txdatas); // now, tricky part. we collect all transactions from inputs (vin), and batch fetch them too. @@ -288,10 +282,11 @@ export class LegacyWallet extends AbstractWallet { const vinTxids = []; for (const txdata of transactions) { for (const vin of txdata.vin) { - vinTxids.push(vin.txid); + vin.txid && vinTxids.push(vin.txid); + // ^^^^ not all inputs have txid, some of them are Coinbase (newly-created coins) } } - const vintxdatas = await BlueElectrum.multiGetTransactionByTxid(vinTxids); + const vintxdatas = await BlueElectrum.multiGetTransactionByTxid(vinTxids, true); // fetched all transactions from our inputs. now we need to combine it. // iterating all _our_ transactions: @@ -327,6 +322,7 @@ export class LegacyWallet extends AbstractWallet { ...txRest, inputs: [...vin2], outputs: [...vout], + timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */, }; _txsByExternalIndex.push(clonedTx); @@ -340,6 +336,7 @@ export class LegacyWallet extends AbstractWallet { ...txRest, inputs: [...vin], outputs: [...vout2], + timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */, }; _txsByExternalIndex.push(clonedTx); @@ -356,6 +353,9 @@ export class LegacyWallet extends AbstractWallet { 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; + }; const hd = new HDSegwitBech32Wallet(); return hd.getTransactions.apply(this); } @@ -374,24 +374,57 @@ export class LegacyWallet extends AbstractWallet { } coinselect( - utxos: CoinSelectUtxo[], - targets: CoinSelectTarget[], + utxos: CreateTransactionUtxo[], + targets: CreateTransactionTarget[], feeRate: number, - changeAddress: string, ): { inputs: CoinSelectReturnInput[]; outputs: CoinSelectOutput[]; fee: number; } { - if (!changeAddress) throw new Error('No change address provided'); - let algo = coinSelect; // if targets has output without a value, we want send MAX to it if (targets.some(i => !('value' in i))) { algo = coinSelectSplit; } - const { inputs, outputs, fee } = algo(utxos, targets, feeRate); + const _utxos = JSON.parse(JSON.stringify(utxos)) as CreateTransactionUtxo[]; + const _targets = JSON.parse(JSON.stringify(targets)) as CreateTransactionTarget[]; + + // compensating for coinselect inability to deal with segwit inputs, and overriding script length for proper vbytes calculation + for (const u of _utxos) { + if (u.script?.length) { + continue; + } + + // counting the number of vbytes for each script type: + if (this.segwitType === 'p2wpkh') { + // 72 (high R low S signature) + 1 + 33 (comp pubkey) + 1 = 107 / 4 = 26.75 rounded up. + u.script = { length: 27 }; + } else if (this.segwitType === 'p2sh(p2wpkh)') { + // ((72 (high R low S signature) + 1 + 33 (comp pubkey) + 1) / 4) + 22 (P2WPKH output on scriptSig stack) + 1 = 49.75 rounded up + u.script = { length: 50 }; + } else if (this.segwitType === 'p2tr') { + // taproot key path spend is just a 64 or 65 byte signature on the witness stack. + // So it would be 65 bytes (assuming max size) + the pushbyte for 65 bytes on the stack, which makes 66. + // 66 / 4 = 16.5 round up to 17 + u.script = { length: 17 }; + } + } + + for (const t of _targets) { + if (t.address?.startsWith('bc1')) { + // in case address is non-typical and takes more bytes than coinselect library anticipates by default + t.script = { length: bitcoin.address.toOutputScript(t.address).length + 3 }; + } + + if (t.script?.hex) { + // setting length for coinselect lib manually as it is not aware of our field `hex` + t.script.length = t.script.hex.length / 2 - 4; + } + } + + const { inputs, outputs, fee } = algo(_utxos, _targets as CoinSelectTarget[], feeRate); // .inputs and .outputs will be undefined if no solution was found if (!inputs || !outputs) { @@ -403,7 +436,7 @@ export class LegacyWallet extends AbstractWallet { /** * - * @param utxos {Array.<{vout: Number, value: Number, txId: String, address: String, txhex: String, }>} List of spendable utxos + * @param utxos {Array.<{vout: Number, value: Number, txid: String, address: String, txhex: String, }>} List of spendable utxos * @param targets {Array.<{value: Number, address: String}>} Where coins are going. If theres only 1 target and that target has no value - this will send MAX to that address (respecting fee rate) * @param feeRate {Number} satoshi per byte * @param changeAddress {String} Excessive coins will go back to that address @@ -422,18 +455,19 @@ export class LegacyWallet extends AbstractWallet { masterFingerprint: number, ): CreateTransactionResult { if (targets.length === 0) throw new Error('No destination provided'); - const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress); + const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate); sequence = sequence || 0xffffffff; // disable RBF by default const psbt = new bitcoin.Psbt(); let c = 0; const values: Record = {}; let keyPair: Signer | null = null; + if (!skipSigning) { + // skiping signing related stuff + keyPair = ECPair.fromWIF(this.secret); // secret is WIF + } + inputs.forEach(input => { - if (!skipSigning) { - // skiping signing related stuff - keyPair = ECPair.fromWIF(this.secret); // secret is WIF - } values[c] = input.value; c++; @@ -444,7 +478,7 @@ export class LegacyWallet extends AbstractWallet { index: input.vout, sequence, // non-segwit inputs now require passing the whole previous tx as Buffer - nonWitnessUtxo: Buffer.from(input.txhex, 'hex'), + nonWitnessUtxo: hexToUint8Array(input.txhex), }); }); @@ -457,7 +491,7 @@ export class LegacyWallet extends AbstractWallet { sanitizedOutputs.forEach(output => { const outputData = { address: output.address, - value: output.value, + value: BigInt(output.value), }; psbt.addOutput(outputData); @@ -483,7 +517,7 @@ export class LegacyWallet extends AbstractWallet { } let max = 0; for (const tx of this.getTransactions()) { - max = Math.max(new Date(tx.received ?? 0).getTime(), max); + max = Math.max(tx.timestamp ? tx.timestamp * 1000 : 0, max); } return new Date(max).toString(); } @@ -507,7 +541,7 @@ export class LegacyWallet extends AbstractWallet { const decoded = bitcoin.address.fromBech32(address); if (decoded.version === 0) return true; if (decoded.version === 1 && decoded.data.length !== 32) return false; - if (decoded.version === 1 && !ecc.isPoint(Buffer.concat([Buffer.from([2]), decoded.data]))) return false; + if (decoded.version === 1 && !ecc.isPoint(concatUint8Arrays([new Uint8Array([2]), decoded.data]))) return false; if (decoded.version > 1) return false; // ^^^ some day, when versions above 1 will be actually utilized, we would need to unhardcode this return true; @@ -524,7 +558,7 @@ export class LegacyWallet extends AbstractWallet { */ static scriptPubKeyToAddress(scriptPubKey: string): string | false { try { - const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex'); + const scriptPubKey2 = hexToUint8Array(scriptPubKey); return ( bitcoin.payments.p2pkh({ output: scriptPubKey2, @@ -593,8 +627,17 @@ export class LegacyWallet extends AbstractWallet { const keyPair = ECPair.fromWIF(wif); const privateKey = keyPair.privateKey; if (!privateKey) throw new Error('Invalid private key'); - const options = this.segwitType && useSegwit ? { segwitType: this.segwitType } : undefined; - const signature = bitcoinMessage.sign(message, privateKey, keyPair.compressed, options); + let segwitType: 'p2wpkh' | 'p2sh(p2wpkh)'; + switch (this.segwitType) { + case 'p2sh(p2wpkh)': + segwitType = 'p2sh(p2wpkh)'; + break; + default: + segwitType = 'p2wpkh'; + break; + } + const options = this.segwitType && useSegwit ? { segwitType } : undefined; + const signature = bitcoinMessage.sign(message, Buffer.from(privateKey), keyPair.compressed, options); return signature.toString('base64'); } diff --git a/class/wallets/lightning-ark-wallet.ts b/class/wallets/lightning-ark-wallet.ts new file mode 100644 index 00000000000..20639e0af49 --- /dev/null +++ b/class/wallets/lightning-ark-wallet.ts @@ -0,0 +1,518 @@ +import BigNumber from 'bignumber.js'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { sha256 } from '@noble/hashes/sha256'; +import { ArkadeLightning, BoltzSwapProvider, decodeInvoice, PendingReverseSwap, PendingSubmarineSwap } from '@arkade-os/boltz-swap'; +import { SingleKey, VtxoManager, Ramps, Wallet, ExtendedCoin, ArkTransaction } from '@arkade-os/sdk'; +import { ExpoArkProvider, ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo'; +import { fetch } from '../../util/fetch'; + +import BIP32Factory from 'bip32'; + +import { LightningCustodianWallet } from './lightning-custodian-wallet.ts'; +import { randomBytes } from '../rng.ts'; +import * as bip39 from 'bip39'; +import { LightningTransaction, Transaction } from './types.ts'; +import { hexToUint8Array, uint8ArrayToHex } from '../../blue_modules/uint8array-extras/index'; +import assert from 'assert'; +import ecc from '../../blue_modules/noble_ecc.ts'; +import { Measure } from '../measure.ts'; +const { bech32m } = require('bech32'); + +const bip32 = BIP32Factory(ecc); + +const staticWalletCache: Record = {}; +const initLock: Record = {}; +const boardingLock: Record = {}; + +export class LightningArkWallet extends LightningCustodianWallet { + static readonly type = 'lightningArkWallet'; + static readonly typeReadable = 'Lightning Ark'; + static readonly subtitleReadable = 'Ark'; + // @ts-ignore: override + public readonly type = LightningArkWallet.type; + // @ts-ignore: override + public readonly typeReadable = LightningArkWallet.typeReadable; + + private _wallet: Wallet | undefined; + private _arkadeLightning: ArkadeLightning | undefined = undefined; + private _arkServerUrl: string = 'https://arkade.computer'; + private _arkServerPublicKey: string = '022b74c2011af089c849383ee527c72325de52df6a788428b68d49e9174053aaba'; + private _boltzApiUrl: string = 'https://api.ark.boltz.exchange'; + + private _swapHistory: (PendingReverseSwap | PendingSubmarineSwap)[] = []; + private _transactionsHistory: ArkTransaction[] = []; + private _claimedSwaps: Record = {}; + private _privateKeyCache = ''; + private _boardingUtxos: ExtendedCoin[] = []; + + // fees from Boltz: + private _limitMin: number = 0; + private _limitMax: number = 0; + private _feePercentage: number = 0; + + hashIt = (s: string): string => { + return uint8ArrayToHex(sha256(s)); + }; + + prepareForSerialization() { + this._wallet = undefined; + this._arkadeLightning = undefined; + } + + _getIdentity() { + assert(this.secret, 'No secret provided'); + + if (!this._privateKeyCache) { + const mnemonic = this.secret.replace('arkade://', '').trim(); + const seed = bip39.mnemonicToSeedSync(mnemonic); + + const index = 0; + const internal = 0; + const accountNumber = 0; + const root = bip32.fromSeed(seed); + const path = `m/86'/0'/${accountNumber}'/${internal}/${index}`; + const child = root.derivePath(path); + assert(child.privateKey, 'Internal error: no private key for child'); + + this._privateKeyCache = uint8ArrayToHex(child.privateKey); + } + + return SingleKey.fromPrivateKey(hexToUint8Array(this._privateKeyCache)); + } + + getNamespace(): string { + assert(this.secret, 'No secret provided'); + return this.hashIt(this.secret); + } + + async init() { + const namespace = this.getNamespace(); + + if (initLock[namespace]) { + let c = 0; + while (!this._wallet || !this._arkadeLightning) { + await new Promise(resolve => setTimeout(resolve, 500)); // sleep + if (c++ > 30) { + throw new Error('Ark wallet initialization timed out'); + } + } + initLock[namespace] = false; + return; // wallet is initialized, so we can return + } + + initLock[namespace] = true; + + try { + const identity = this._getIdentity(); + + class ArkCustomStorage { + async getItem(key: string): Promise { + return await AsyncStorage.getItem(`${namespace}_${key}`); + } + + async setItem(key: string, value: string): Promise { + return await AsyncStorage.setItem(`${namespace}_${key}`, value); + } + + async removeItem(key: string): Promise { + await AsyncStorage.removeItem(`${namespace}_${key}`); + } + + async clear(): Promise { + // nop + } + } + + const storage = new ArkCustomStorage(); + + const mm = new Measure('Wallet.create()'); + if (!staticWalletCache[namespace]) { + const wallet = await Wallet.create({ + storage, + identity, + arkProvider: new ExpoArkProvider(this._arkServerUrl), + indexerProvider: new ExpoIndexerProvider(this._arkServerUrl), + arkServerPublicKey: this._arkServerPublicKey, + }); + staticWalletCache[namespace] = wallet; + } + + mm.end(); + this._wallet = staticWalletCache[namespace]; + + await this._initLightningSwaps(); + + // initialize VTXO manager in set timeout so it doesnt block the wallet initialization + setTimeout(async () => { + const manager = new VtxoManager(staticWalletCache[namespace], { + enabled: true, // Enable expiration monitoring + }); + try { + const expiringVtxos = await manager.getExpiringVtxos(); + if (expiringVtxos.length > 0) { + console.log(`ARK renewing ${expiringVtxos.length} expiring VTXOs...`); + const renewTxid = await manager.renewVtxos(); + console.log('ARK VTXO renewed:', renewTxid); + } + } catch (error: any) { + console.log('ARK Error renewing VTXOs:', error.message); + } + }, 1_000); + } finally { + initLock[namespace] = false; + } + } + + async _initLightningSwaps() { + assert(this._wallet, 'Ark wallet must be initialized first'); + assert(this._boltzApiUrl, 'Boltz Api Url is not set'); + + // fetching fees boltz takes: + const feesResponse = await fetch(this._boltzApiUrl + '/v2/swap/submarine'); + const feesResponseJson = await feesResponse.json(); + this._limitMin = feesResponseJson?.ARK?.BTC?.limits?.minimal ?? 333; + this._limitMax = feesResponseJson?.ARK?.BTC?.limits?.maximal ?? 1000000; + this._feePercentage = feesResponseJson?.ARK?.BTC?.fees?.percentage ?? 0; + if (!feesResponseJson?.ARK?.BTC?.fees?.percentage) { + console.log('warning: unexpected fees response from boltz:', JSON.stringify(feesResponseJson, null, 2)); + } + + // Initialize the Lightning swap provider + const swapProvider = new BoltzSwapProvider({ + apiUrl: this._boltzApiUrl, + network: 'bitcoin', + }); + + // Create the ArkadeLightning instance + this._arkadeLightning = new ArkadeLightning({ + wallet: this._wallet, + swapProvider, + }); + } + + async generate(): Promise { + const buf = await randomBytes(16); + this.secret = 'arkade://' + bip39.entropyToMnemonic(uint8ArrayToHex(buf)); + + await this.init(); + } + + getSecret() { + return this.secret; + } + + getTransactions(): (Transaction & LightningTransaction)[] { + const walletID = this.getID(); + const ret: LightningTransaction[] = []; + for (const swap of this._swapHistory) { + let memo = ''; + let value = 0; + let timestamp = 0; + let payment_hash = ''; + let bolt11invoice = ''; + let direction = 1; + let ispaid = false; + let expiry = 3600; + + try { + // @ts-ignore properties do exist + bolt11invoice = swap.request.invoice || swap.response.invoice; + const invoiceDetails = this.decodeInvoice(bolt11invoice); + value = invoiceDetails.num_satoshis; + memo = invoiceDetails.description; + payment_hash = invoiceDetails.payment_hash; + expiry = invoiceDetails.expiry; + } catch {} + + timestamp = swap.createdAt; + + switch (swap.status) { + case 'transaction.claimed': + direction = -1; + ispaid = true; + break; + case 'invoice.settled': + direction = 1; + ispaid = true; + break; + case 'swap.created': + // nop, this is invoice that we created + break; + case 'invoice.set': + // dont return it, its an invoice we trief to pay but could not + continue; + } + + if (this._claimedSwaps[swap.id]) { + ispaid = true; + } + // @ts-ignore properties do exist + value = swap.response.onchainAmount || swap.response.expectedAmount || value || swap.request.invoiceAmount || 0; + value = value * direction; + + ret.push({ + type: direction < 0 ? 'paid_invoice' : 'user_invoice', + walletID, + description: memo, + memo, + value, + timestamp, + ispaid, + payment_hash, + payment_request: bolt11invoice, + amt: value, + payment_preimage: swap.preimage, + expire_time: expiry, + }); + } + + for (const boardingTx of this._boardingUtxos) { + ret.push({ + type: 'bitcoind_tx', + walletID, + description: 'Pending refill', + memo: 'Pending refill', + value: boardingTx.value, + timestamp: boardingTx.status.block_time ?? Math.floor(Date.now() / 1000), + }); + } + + for (const histTx of this._transactionsHistory) { + if (histTx.key.boardingTxid && histTx.type === 'RECEIVED' && histTx.settled) { + // for now putting on the list only onchain top-up transactions: + ret.push({ + type: 'bitcoind_tx', + walletID, + description: 'Refill', + memo: 'Refill', + value: histTx.amount, + timestamp: Math.floor(histTx.createdAt / 1000), + }); + } + } + + // @ts-ignore meh + return ret; + } + + async fetchUserInvoices() { + // nop + } + + async fetchTransactions() { + if (!this._wallet) await this.init(); + if (!this._wallet) throw new Error('Ark wallet not initialized'); + if (!this._arkadeLightning) throw new Error('Ark Lightning not initialized'); + + this._swapHistory = await this._arkadeLightning.getSwapHistory(); + this._transactionsHistory = await this._wallet.getTransactionHistory(); + this._lastTxFetch = +new Date(); + } + + async _attemptToClaimPendingVHTLCs() { + assert(this._wallet, 'Ark wallet not initialized'); + assert(this._arkadeLightning, 'Ark Lightning not initialized'); + const arkadeLightning = this._arkadeLightning; + + const pendingReverseSwaps = await this._arkadeLightning.getPendingReverseSwaps(); + if ((pendingReverseSwaps ?? []).length > 0) console.log('got', pendingReverseSwaps?.length ?? [], 'pending swaps'); + + await Promise.all( + (pendingReverseSwaps ?? []).map(async swap => { + if (this._claimedSwaps[swap.id]) return; + + console.log(`claiming ${swap.id}...`); + if (swap?.response?.timeoutBlockHeights?.refund && swap?.response?.timeoutBlockHeights?.refund <= Date.now() / 1000) { + console.log(`skipping ${swap.id} (too old)`); + return; + } + try { + await arkadeLightning.claimVHTLC(swap); + console.log('claimed!'); + this._claimedSwaps[swap.id] = true; + } catch (error: any) { + console.log(`could not claim ${swap.id}:`, error.message); + } + }), + ); + } + + async fetchBalance(noRetry?: boolean): Promise { + if (!this._wallet) await this.init(); + if (!this._wallet) throw new Error('Ark wallet not initialized'); + + if (this._arkadeLightning) { + await this._attemptToClaimPendingVHTLCs(); + } + + await this._attemptBoardUtxos(); + + const balance = await this._wallet.getBalance(); + this._lastBalanceFetch = +new Date(); + this.balance = balance.available; + } + + getBalance() { + return this.balance; + } + + async payInvoice(invoice: string, freeAmount: number = 0) { + if (!this._wallet) await this.init(); + if (!this._wallet) throw new Error('Ark wallet not initialized'); + + if (this.isAddressValid(invoice)) { + // its an ark address, so we need to do native ark-to-ark transfer + await this._wallet.sendBitcoin({ + address: invoice, + amount: freeAmount, + }); + return; + } + + assert(this._arkadeLightning, 'Ark Lightning not initialized'); + + const invoiceDetails = decodeInvoice(invoice); + + console.log('Invoice amount:', invoiceDetails.amountSats, 'sats'); + console.log('Description:', invoiceDetails.description); + console.log('Payment Hash:', invoiceDetails.paymentHash); + + assert(invoiceDetails.amountSats > this._limitMin, `Minimum you can send is ${this._limitMin} sat`); + assert(invoiceDetails.amountSats < this._limitMax, `Maximum you can is ${this._limitMax} sat`); + + const paymentResult = await this._arkadeLightning.sendLightningPayment({ invoice }); + + console.log('Payment successful!'); + console.log('Amount:', paymentResult.amount); + console.log('Preimage:', paymentResult.preimage); + console.log('Transaction ID:', paymentResult.txid); + } + + async getUserInvoices(limit: number | false = false): Promise { + if (this._arkadeLightning) { + await this._attemptToClaimPendingVHTLCs(); + } + await this.fetchTransactions(); + const txs = this.getTransactions(); + return txs.filter(tx => tx.value! > 0); + } + + async addInvoice(amt: number, memo: string) { + if (!this._wallet) await this.init(); + assert(this._arkadeLightning, 'Ark Lightning not initialized'); + assert(amt > this._limitMin, `Minimum to receive is ${this._limitMin} sat`); + assert(amt < this._limitMax, `Maximum to receive is ${this._limitMin} sat`); + + // fee percentage is smth like `0.01`, but its not 1%, its one-hundredth of a percent, rounded up + const serviceFee = Math.ceil(new BigNumber(amt).multipliedBy(this._feePercentage).dividedBy(100).toNumber()); + + const result = await this._arkadeLightning.createLightningInvoice({ + amount: amt + serviceFee, + description: memo, + }); + + console.log('Expiry (seconds):', result.expiry); + console.log('Lightning Invoice:', result.invoice); + console.log('Payment Hash:', result.paymentHash); + console.log('Pending swap', result.pendingSwap); + console.log('Preimage', result.preimage); + + return result.invoice; + } + + async getArkAddress(): Promise { + if (!this._wallet) await this.init(); + if (!this._wallet) throw new Error('Ark not initialized'); + return await this._wallet.getAddress(); + } + + async fetchPendingTransactions() { + // nop + } + + async decodeInvoiceRemote(invoice: string) { + throw new Error('decodeInvoiceRemote not implemented'); + } + + async allowOnchainAddress() { + return true; + } + + async fetchBtcAddress() { + if (!this._wallet) await this.init(); + assert(this._wallet, 'Ark wallet not initialized'); + + this.refill_addressess = this.refill_addressess || []; + const address = await this._wallet.getBoardingAddress(); + if (!this.refill_addressess.includes(address)) { + this.refill_addressess.push(address); + } + } + + async refreshAcessToken() { + // nop + } + + async checkLogin() { + // nop + } + + async authorize() { + // nop + } + + isInvoiceGeneratedByWallet(paymentRequest: string) { + return this.getTransactions().some(tx => tx.payment_request === paymentRequest && typeof tx.value !== 'undefined' && tx?.value >= 0); + } + + async createAccount(isTest: boolean = false) { + // nop + } + + accessTokenExpired() { + return false; + } + + refreshTokenExpired() { + return false; + } + + private async _attemptBoardUtxos() { + // executing in background since it can take a lot of time, but setting the lock so there wont be any races + // (for example, during another pull-to-refresh) + const namespace = this.getNamespace(); + if (boardingLock[namespace]) return; + + if (!this._wallet) return; + + boardingLock[namespace] = true; + this._boardingUtxos = await this._wallet.getBoardingUtxos(); // calling it here so fetchBalance will pick it up and then `getTransactions` will show it in tx list + (async () => { + if (this._boardingUtxos.length > 0) { + if (!this._wallet) return; + // not instantiating, this is supposed to be called inside `fetchBalance` + console.log('attempting to board ', this._boardingUtxos.length, 'UTXOs...'); + const info = await this._wallet.arkProvider.getInfo(); + const feeInfo = info.fees; + await new Ramps(this._wallet).onboard(feeInfo, this._boardingUtxos); + this._boardingUtxos = await this._wallet.getBoardingUtxos(); // refetch UTXOs, if we succeeded boarding previosuly the set should be reduced + } + })() + .catch(e => console.log('ark boarding failed:', e.message)) + .finally(() => { + boardingLock[namespace] = false; + }); + } + + isAddressValid(address: string): boolean { + try { + const decoded = bech32m.decode(address, 1000); + if (decoded.prefix !== 'ark') return false; + if (decoded.words[0] !== 0) return false; + if (decoded.words.length !== 104) return false; + return true; + } catch (_) { + return false; + } + } +} diff --git a/class/wallets/lightning-custodian-wallet.js b/class/wallets/lightning-custodian-wallet.js deleted file mode 100644 index 9def152da52..00000000000 --- a/class/wallets/lightning-custodian-wallet.js +++ /dev/null @@ -1,715 +0,0 @@ -import { LegacyWallet } from './legacy-wallet'; -import Frisbee from 'frisbee'; -import bolt11 from 'bolt11'; -import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; -import { isTorDaemonDisabled } from '../../blue_modules/environment'; -const torrific = require('../../blue_modules/torrific'); -export class LightningCustodianWallet extends LegacyWallet { - static type = 'lightningCustodianWallet'; - static typeReadable = 'Lightning'; - - constructor(props) { - super(props); - this.setBaseURI(); // no args to init with default value - this.init(); - this.refresh_token = ''; - this.access_token = ''; - this._refresh_token_created_ts = 0; - this._access_token_created_ts = 0; - this.refill_addressess = []; - this.pending_transactions_raw = []; - this.user_invoices_raw = []; - this.info_raw = false; - this.preferredBalanceUnit = BitcoinUnit.SATS; - this.chain = Chain.OFFCHAIN; - } - - /** - * requires calling init() after setting - * - * @param URI - */ - setBaseURI(URI) { - this.baseURI = URI; - } - - getBaseURI() { - return this.baseURI; - } - - allowSend() { - return true; - } - - getAddress() { - if (this.refill_addressess.length > 0) { - return this.refill_addressess[0]; - } else { - return undefined; - } - } - - getSecret() { - return this.secret + '@' + this.baseURI; - } - - timeToRefreshBalance() { - return (+new Date() - this._lastBalanceFetch) / 1000 > 300; // 5 min - } - - timeToRefreshTransaction() { - return (+new Date() - this._lastTxFetch) / 1000 > 300; // 5 min - } - - static fromJson(param) { - const obj = super.fromJson(param); - obj.init(); - return obj; - } - - async init() { - // un-cache refill onchain addresses on cold start. should help for cases when certain lndhub - // is turned off permanently, so users cant pull refill address from cache and send money to a black hole - this.refill_addressess = []; - - this._api = new Frisbee({ - baseURI: this.baseURI, - }); - const isTorDisabled = await isTorDaemonDisabled(); - - if (!isTorDisabled && this.baseURI && this.baseURI?.indexOf('.onion') !== -1) { - this._api = new torrific.Torsbee({ - baseURI: this.baseURI, - }); - } - } - - accessTokenExpired() { - return (+new Date() - this._access_token_created_ts) / 1000 >= 3600 * 2; // 2h - } - - refreshTokenExpired() { - return (+new Date() - this._refresh_token_created_ts) / 1000 >= 3600 * 24 * 7; // 7d - } - - generate() { - // nop - } - - async createAccount(isTest) { - const response = await this._api.post('/create', { - body: { partnerid: 'bluewallet', accounttype: (isTest && 'test') || 'common' }, - headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' }, - }); - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); - } - - if (json && json.error) { - throw new Error('API error: ' + (json.message ? json.message : json.error) + ' (code ' + json.code + ')'); - } - - if (!json.login || !json.password) { - throw new Error('API unexpected response: ' + JSON.stringify(response.body)); - } - - this.secret = 'lndhub://' + json.login + ':' + json.password; - } - - async payInvoice(invoice, freeAmount = 0) { - const response = await this._api.post('/payinvoice', { - body: { invoice, amount: freeAmount }, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - Authorization: 'Bearer' + ' ' + this.access_token, - }, - }); - - if (response.originalResponse && typeof response.originalResponse === 'string') { - try { - response.originalResponse = JSON.parse(response.originalResponse); - } catch (_) {} - } - - if (response.originalResponse && response.originalResponse.status && response.originalResponse.status === 503) { - throw new Error('Payment is in transit'); - } - - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.originalResponse)); - } - - if (json && json.error) { - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - - this.last_paid_invoice_result = json; - } - - /** - * Returns list of LND invoices created by user - * - * @return {Promise.} - */ - async getUserInvoices(limit = false) { - let limitString = ''; - if (limit) limitString = '?limit=' + parseInt(limit, 10); - const response = await this._api.get('/getuserinvoices' + limitString, { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - Authorization: 'Bearer' + ' ' + this.access_token, - }, - }); - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.originalResponse)); - } - - if (json && json.error) { - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - - if (limit) { - // need to merge existing invoices with the ones that arrived - // but the ones received later should overwrite older ones - - for (const oldInvoice of this.user_invoices_raw) { - // iterate all OLD invoices - let found = false; - for (const newInvoice of json) { - // iterate all NEW invoices - if (newInvoice.payment_request === oldInvoice.payment_request) found = true; - } - - if (!found) { - // if old invoice is not found in NEW array, we simply add it: - json.push(oldInvoice); - } - } - } - - this.user_invoices_raw = json.sort(function (a, b) { - return a.timestamp - b.timestamp; - }); - - return this.user_invoices_raw; - } - - /** - * Basically the same as this.getUserInvoices() but saves invoices list - * to internal variable - * - * @returns {Promise} - */ - async fetchUserInvoices() { - await this.getUserInvoices(); - } - - isInvoiceGeneratedByWallet(paymentRequest) { - return this.user_invoices_raw.some(invoice => invoice.payment_request === paymentRequest); - } - - weOwnAddress(address) { - return this.refill_addressess.some(refillAddress => address === refillAddress); - } - - async addInvoice(amt, memo) { - const response = await this._api.post('/addinvoice', { - body: { amt: amt + '', memo }, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - Authorization: 'Bearer' + ' ' + this.access_token, - }, - }); - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.originalResponse)); - } - - if (json && json.error) { - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - - if (!json.r_hash || !json.pay_req) { - throw new Error('API unexpected response: ' + JSON.stringify(response.body)); - } - - return json.pay_req; - } - - /** - * Uses login & pass stored in `this.secret` to authorize - * and set internal `access_token` & `refresh_token` - * - * @return {Promise.} - */ - async authorize() { - let login, password; - if (this.secret.indexOf('blitzhub://') !== -1) { - login = this.secret.replace('blitzhub://', '').split(':')[0]; - password = this.secret.replace('blitzhub://', '').split(':')[1]; - } else { - login = this.secret.replace('lndhub://', '').split(':')[0]; - password = this.secret.replace('lndhub://', '').split(':')[1]; - } - const response = await this._api.post('/auth?type=auth', { - body: { login, password }, - headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' }, - }); - - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); - } - - if (json && json.error) { - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - - if (!json.access_token || !json.refresh_token) { - throw new Error('API unexpected response: ' + JSON.stringify(response.body)); - } - - this.refresh_token = json.refresh_token; - this.access_token = json.access_token; - this._refresh_token_created_ts = +new Date(); - this._access_token_created_ts = +new Date(); - } - - async checkLogin() { - if (this.accessTokenExpired() && this.refreshTokenExpired()) { - // all tokens expired, only option is to login with login and password - return this.authorize(); - } - - if (this.accessTokenExpired()) { - // only access token expired, so only refreshing it - let refreshedOk = true; - try { - await this.refreshAcessToken(); - } catch (Err) { - refreshedOk = false; - } - - if (!refreshedOk) { - // something went wrong, lets try to login regularly - return this.authorize(); - } - } - } - - async refreshAcessToken() { - const response = await this._api.post('/auth?type=refresh_token', { - body: { refresh_token: this.refresh_token }, - headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' }, - }); - - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); - } - - if (json && json.error) { - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - - if (!json.access_token || !json.refresh_token) { - throw new Error('API unexpected response: ' + JSON.stringify(response.body)); - } - - this.refresh_token = json.refresh_token; - this.access_token = json.access_token; - this._refresh_token_created_ts = +new Date(); - this._access_token_created_ts = +new Date(); - } - - async fetchBtcAddress() { - const response = await this._api.get('/getbtc', { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - Authorization: 'Bearer' + ' ' + this.access_token, - }, - }); - - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); - } - - if (json && json.error) { - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - - this.refill_addressess = []; - - for (const arr of json) { - this.refill_addressess.push(arr.address); - } - } - - async getAddressAsync() { - await this.fetchBtcAddress(); - return this.getAddress(); - } - - async allowOnchainAddress() { - if (this.getAddress() !== undefined && this.getAddress() !== null) { - return true; - } else { - await this.fetchBtcAddress(); - return this.getAddress() !== undefined && this.getAddress() !== null; - } - } - - getTransactions() { - let txs = []; - this.pending_transactions_raw = this.pending_transactions_raw || []; - this.user_invoices_raw = this.user_invoices_raw || []; - this.transactions_raw = this.transactions_raw || []; - txs = txs.concat(this.pending_transactions_raw.slice(), this.transactions_raw.slice().reverse(), this.user_invoices_raw.slice()); // slice so array is cloned - // transforming to how wallets/list screen expects it - for (const tx of txs) { - tx.walletID = this.getID(); - if (tx.amount) { - // pending tx - tx.amt = tx.amount * -100000000; - tx.fee = 0; - tx.timestamp = tx.time; - tx.memo = 'On-chain transaction'; - } - - if (typeof tx.amt !== 'undefined' && typeof tx.fee !== 'undefined') { - // lnd tx outgoing - tx.value = parseInt((tx.amt * 1 + tx.fee * 1) * -1, 10); - } - - if (tx.type === 'paid_invoice') { - tx.memo = tx.memo || 'Lightning payment'; - if (tx.value > 0) tx.value = tx.value * -1; // value already includes fee in it (see lndhub) - // outer code expects spending transactions to of negative value - } - - if (tx.type === 'bitcoind_tx') { - tx.memo = 'On-chain transaction'; - } - - if (tx.type === 'user_invoice') { - // incoming ln tx - tx.value = parseInt(tx.amt, 10); - tx.memo = tx.description || 'Lightning invoice'; - } - - tx.received = new Date(tx.timestamp * 1000).toString(); - } - return txs.sort(function (a, b) { - return b.timestamp - a.timestamp; - }); - } - - async fetchPendingTransactions() { - const response = await this._api.get('/getpending', { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - Authorization: 'Bearer' + ' ' + this.access_token, - }, - }); - - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response)); - } - - if (json && json.error) { - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - - this.pending_transactions_raw = json; - } - - async fetchTransactions() { - // TODO: iterate over all available pages - const limit = 10; - let queryRes = ''; - const offset = 0; - queryRes += '?limit=' + limit; - queryRes += '&offset=' + offset; - - const response = await this._api.get('/gettxs' + queryRes, { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - Authorization: 'Bearer' + ' ' + this.access_token, - }, - }); - - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); - } - - if (json && json.error) { - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - - if (!Array.isArray(json)) { - throw new Error('API unexpected response: ' + JSON.stringify(response.body)); - } - - this._lastTxFetch = +new Date(); - this.transactions_raw = json; - } - - getBalance() { - return this.balance; - } - - async fetchBalance(noRetry) { - await this.checkLogin(); - - const response = await this._api.get('/balance', { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - Authorization: 'Bearer' + ' ' + this.access_token, - }, - }); - - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); - } - - if (json && json.error) { - if (json.code * 1 === 1 && !noRetry) { - await this.authorize(); - return this.fetchBalance(true); - } - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - - if (!json.BTC || typeof json.BTC.AvailableBalance === 'undefined') { - throw new Error('API unexpected response: ' + JSON.stringify(response.body)); - } - - this.balance_raw = json; - this.balance = json.BTC.AvailableBalance; - this._lastBalanceFetch = +new Date(); - } - - /** - * Example return: - * { destination: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f', - * payment_hash: 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4', - * num_satoshis: '100', - * timestamp: '1535116657', - * expiry: '3600', - * description: 'hundredSatoshis blitzhub', - * description_hash: '', - * fallback_addr: '', - * cltv_expiry: '10', - * route_hints: [] } - * - * @param invoice BOLT invoice string - * @return {payment_hash: string} - */ - decodeInvoice(invoice) { - const { payeeNodeKey, tags, satoshis, millisatoshis, timestamp } = bolt11.decode(invoice); - - const decoded = { - destination: payeeNodeKey, - num_satoshis: satoshis ? satoshis.toString() : '0', - num_millisatoshis: millisatoshis ? millisatoshis.toString() : '0', - timestamp: timestamp.toString(), - fallback_addr: '', - route_hints: [], - }; - - for (let i = 0; i < tags.length; i++) { - const { tagName, data } = tags[i]; - switch (tagName) { - case 'payment_hash': - decoded.payment_hash = data; - break; - case 'purpose_commit_hash': - decoded.description_hash = data; - break; - case 'min_final_cltv_expiry': - decoded.cltv_expiry = data.toString(); - break; - case 'expire_time': - decoded.expiry = data.toString(); - break; - case 'description': - decoded.description = data; - break; - } - } - - if (!decoded.expiry) decoded.expiry = '3600'; // default - - if (parseInt(decoded.num_satoshis, 10) === 0 && decoded.num_millisatoshis > 0) { - decoded.num_satoshis = (decoded.num_millisatoshis / 1000).toString(); - } - - return (this.decoded_invoice_raw = decoded); - } - - async fetchInfo() { - const response = await this._api.get('/getinfo', { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - Authorization: 'Bearer' + ' ' + this.access_token, - }, - }); - - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); - } - - if (json && json.error) { - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - - if (!json.identity_pubkey) { - throw new Error('API unexpected response: ' + JSON.stringify(response.body)); - } - this.info_raw = json; - } - - static async isValidNodeAddress(address) { - const isTorDisabled = await isTorDaemonDisabled(); - const isTor = address.indexOf('.onion') !== -1; - const apiCall = - isTor && !isTorDisabled - ? new torrific.Torsbee({ - baseURI: address, - }) - : new Frisbee({ - baseURI: address, - }); - const response = await apiCall.get('/getinfo', { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - }, - }); - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); - } - - if (json && json.code && json.code !== 1) { - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - return true; - } - - allowReceive() { - return true; - } - - allowSignVerifyMessage() { - return false; - } - - /** - * Example return: - * { destination: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f', - * payment_hash: 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4', - * num_satoshis: '100', - * timestamp: '1535116657', - * expiry: '3600', - * description: 'hundredSatoshis blitzhub', - * description_hash: '', - * fallback_addr: '', - * cltv_expiry: '10', - * route_hints: [] } - * - * @param invoice BOLT invoice string - * @return {Promise.} - */ - async decodeInvoiceRemote(invoice) { - await this.checkLogin(); - - const response = await this._api.get('/decodeinvoice?invoice=' + invoice, { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - Authorization: 'Bearer' + ' ' + this.access_token, - }, - }); - - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); - } - - if (json && json.error) { - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - - if (!json.payment_hash) { - throw new Error('API unexpected response: ' + JSON.stringify(response.body)); - } - - return (this.decoded_invoice_raw = json); - } - - weOwnTransaction(txid) { - for (const tx of this.getTransactions()) { - if (tx && tx.payment_hash && tx.payment_hash === txid) return true; - } - - return false; - } - - authenticate(lnurl) { - return lnurl.authenticate(this.secret); - } -} - -/* - - - -pending tx: - - [ { amount: 0.00078061, - account: '521172', - address: '3F9seBGCJZQ4WJJHwGhrxeGXCGbrm5SNpF', - category: 'receive', - confirmations: 0, - blockhash: '', - blockindex: 0, - blocktime: 0, - txid: '28a74277e47c2d772ee8a40464209c90dce084f3b5de38a2f41b14c79e3bfc62', - walletconflicts: [], - time: 1535024434, - timereceived: 1535024434 } ] - - -tx: - - [ { amount: 0.00078061, - account: '521172', - address: '3F9seBGCJZQ4WJJHwGhrxeGXCGbrm5SNpF', - category: 'receive', - confirmations: 5, - blockhash: '0000000000000000000edf18e9ece18e449c6d8eed1f729946b3531c32ee9f57', - blockindex: 693, - blocktime: 1535024914, - txid: '28a74277e47c2d772ee8a40464209c90dce084f3b5de38a2f41b14c79e3bfc62', - walletconflicts: [], - time: 1535024434, - timereceived: 1535024434 } ] - - */ diff --git a/class/wallets/lightning-custodian-wallet.ts b/class/wallets/lightning-custodian-wallet.ts new file mode 100644 index 00000000000..00731ac17c2 --- /dev/null +++ b/class/wallets/lightning-custodian-wallet.ts @@ -0,0 +1,694 @@ +import bolt11 from 'bolt11'; +import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; +import { fetch } from '../../util/fetch'; +import { LegacyWallet } from './legacy-wallet'; +import { DecodedInvoice, LightningTransaction, Transaction } from './types'; + +const _staticDecodedInvoiceCache: Record = {}; + +export class LightningCustodianWallet extends LegacyWallet { + static readonly type = 'lightningCustodianWallet'; + static readonly typeReadable = 'Lightning'; + static readonly subtitleReadable = 'LNDhub'; + // @ts-ignore: override + public readonly type = LightningCustodianWallet.type; + // @ts-ignore: override + public readonly typeReadable = LightningCustodianWallet.typeReadable; + + baseURI?: string; + refresh_token: string = ''; + access_token: string = ''; + _refresh_token_created_ts: number = 0; + _access_token_created_ts: number = 0; + refill_addressess: string[] = []; + pending_transactions_raw: any[] = []; + transactions_raw: any[] = []; + user_invoices_raw: any[] = []; + preferredBalanceUnit = BitcoinUnit.SATS; + chain = Chain.OFFCHAIN; + last_paid_invoice_result?: any; + + /** + * requires calling init() after setting + * + * @param URI + */ + setBaseURI(URI: string | undefined) { + this.baseURI = URI?.endsWith('/') ? URI.slice(0, -1) : URI; + } + + getBaseURI() { + return this.baseURI; + } + + allowSend() { + return true; + } + + getAddress(): string | false { + if (this.refill_addressess.length > 0) { + return this.refill_addressess[0]; + } else { + return false; + } + } + + getSecret() { + return this.secret + '@' + this.baseURI; + } + + timeToRefreshBalance() { + return (+new Date() - this._lastBalanceFetch) / 1000 > 300; // 5 min + } + + timeToRefreshTransaction() { + return (+new Date() - this._lastTxFetch) / 1000 > 300; // 5 min + } + + async init() { + // un-cache refill onchain addresses on cold start. should help for cases when certain lndhub + // is turned off permanently, so users cant pull refill address from cache and send money to a black hole + this.refill_addressess = []; + } + + accessTokenExpired() { + return (+new Date() - this._access_token_created_ts) / 1000 >= 3600 * 2; // 2h + } + + refreshTokenExpired() { + return (+new Date() - this._refresh_token_created_ts) / 1000 >= 3600 * 24 * 7; // 7d + } + + generate(): Promise { + // nop + return Promise.resolve(); + } + + async createAccount(isTest: boolean = false) { + const response = await fetch(this.baseURI + '/create', { + method: 'POST', + body: JSON.stringify({ partnerid: 'bluewallet', accounttype: (isTest && 'test') || 'common' }), + headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' }, + }); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); + } + + if (json.error) { + throw new Error('API error: ' + (json.message ? json.message : json.error) + ' (code ' + json.code + ')'); + } + + if (!json.login || !json.password) { + throw new Error('API unexpected response: ' + JSON.stringify(json)); + } + + this.secret = 'lndhub://' + json.login + ':' + json.password; + } + + async payInvoice(invoice: string, freeAmount: number = 0) { + const response = await fetch(this.baseURI + '/payinvoice', { + method: 'POST', + body: JSON.stringify({ invoice, amount: freeAmount }), + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + Authorization: 'Bearer' + ' ' + this.access_token, + }, + }); + + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); + } + + if (json.error) { + throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); + } + + this.last_paid_invoice_result = json; + } + + /** + * Returns list of LND invoices created by user + * + * @return {Promise.} + */ + async getUserInvoices(limit: number | false = false) { + let limitString = ''; + if (limit) limitString = '?limit=' + parseInt(limit as unknown as string, 10); + const response = await fetch(this.baseURI + '/getuserinvoices' + limitString, { + method: 'GET', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + Authorization: 'Bearer' + ' ' + this.access_token, + }, + }); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); + } + + if (json.error) { + throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); + } + + if (limit) { + // need to merge existing invoices with the ones that arrived + // but the ones received later should overwrite older ones + + for (const oldInvoice of this.user_invoices_raw) { + // iterate all OLD invoices + let found = false; + for (const newInvoice of json) { + // iterate all NEW invoices + if (newInvoice.payment_request === oldInvoice.payment_request) found = true; + } + + if (!found) { + // if old invoice is not found in NEW array, we simply add it: + json.push(oldInvoice); + } + } + } + + this.user_invoices_raw = json.sort(function (a: { timestamp: number }, b: { timestamp: number }) { + return a.timestamp - b.timestamp; + }); + + return this.user_invoices_raw; + } + + /** + * Basically the same as this.getUserInvoices() but saves invoices list + * to internal variable + * + * @returns {Promise} + */ + async fetchUserInvoices() { + await this.getUserInvoices(); + } + + isInvoiceGeneratedByWallet(paymentRequest: string) { + return this.user_invoices_raw.some(invoice => invoice.payment_request === paymentRequest); + } + + weOwnAddress(address: string) { + return this.refill_addressess.some(refillAddress => address === refillAddress); + } + + async addInvoice(amt: number, memo: string) { + const response = await fetch(this.baseURI + '/addinvoice', { + method: 'POST', + body: JSON.stringify({ amt: amt + '', memo }), + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + Authorization: 'Bearer' + ' ' + this.access_token, + }, + }); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); + } + + if (json.error) { + throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); + } + + if (!json.r_hash || !json.pay_req) { + throw new Error('API unexpected response: ' + JSON.stringify(json)); + } + + return json.pay_req; + } + + /** + * Uses login & pass stored in `this.secret` to authorize + * and set internal `access_token` & `refresh_token` + * + * @return {Promise.} + */ + async authorize() { + let login, password; + if (this.secret.indexOf('blitzhub://') !== -1) { + login = this.secret.replace('blitzhub://', '').split(':')[0]; + password = this.secret.replace('blitzhub://', '').split(':')[1]; + } else { + login = this.secret.replace('lndhub://', '').split(':')[0]; + password = this.secret.replace('lndhub://', '').split(':')[1]; + } + const response = await fetch(this.baseURI + '/auth?type=auth', { + method: 'POST', + body: JSON.stringify({ login, password }), + headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' }, + }); + + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); + } + + if (json.error) { + throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); + } + + if (!json.access_token || !json.refresh_token) { + throw new Error('API unexpected response: ' + JSON.stringify(json)); + } + + this.refresh_token = json.refresh_token; + this.access_token = json.access_token; + this._refresh_token_created_ts = +new Date(); + this._access_token_created_ts = +new Date(); + } + + async checkLogin() { + if (this.accessTokenExpired() && this.refreshTokenExpired()) { + // all tokens expired, only option is to login with login and password + return this.authorize(); + } + + if (this.accessTokenExpired()) { + // only access token expired, so only refreshing it + let refreshedOk = true; + try { + await this.refreshAcessToken(); + } catch (Err) { + refreshedOk = false; + } + + if (!refreshedOk) { + // something went wrong, lets try to login regularly + return this.authorize(); + } + } + } + + async refreshAcessToken() { + const response = await fetch(this.baseURI + '/auth?type=refresh_token', { + method: 'POST', + body: JSON.stringify({ refresh_token: this.refresh_token }), + headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' }, + }); + + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); + } + + if (json.error) { + throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); + } + + if (!json.access_token || !json.refresh_token) { + throw new Error('API unexpected response: ' + JSON.stringify(json)); + } + + this.refresh_token = json.refresh_token; + this.access_token = json.access_token; + this._refresh_token_created_ts = +new Date(); + this._access_token_created_ts = +new Date(); + } + + async fetchBtcAddress() { + const response = await fetch(this.baseURI + '/getbtc', { + method: 'GET', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + Authorization: 'Bearer' + ' ' + this.access_token, + }, + }); + + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); + } + + if (json.error) { + throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); + } + + this.refill_addressess = []; + + for (const arr of json) { + this.refill_addressess.push(arr.address); + } + } + + async getAddressAsync() { + await this.fetchBtcAddress(); + return this.getAddress(); + } + + async allowOnchainAddress() { + if (this.getAddress() !== undefined && this.getAddress() !== null) { + return true; + } else { + await this.fetchBtcAddress(); + return this.getAddress() !== undefined && this.getAddress() !== null; + } + } + + getTransactions(): (Transaction & LightningTransaction)[] { + let txs: any = []; + txs = txs.concat(this.pending_transactions_raw.slice(), this.transactions_raw.slice().reverse(), this.user_invoices_raw.slice()); // slice so array is cloned + + for (const tx of txs) { + tx.walletID = this.getID(); + if (tx.amount) { + // pending tx + tx.amt = tx.amount * -100000000; + tx.fee = 0; + tx.memo = 'On-chain transaction'; + } + + if (typeof tx.amt !== 'undefined' && typeof tx.fee !== 'undefined') { + // lnd tx outgoing + tx.value = (tx.amt * 1 + tx.fee * 1) * -1; + } + + if (tx.type === 'paid_invoice') { + tx.memo = tx.memo || 'Lightning payment'; + if (tx.value > 0) tx.value = tx.value * -1; // value already includes fee in it (see lndhub) + // outer code expects spending transactions to of negative value + } + + if (tx.type === 'bitcoind_tx') { + tx.memo = 'On-chain transaction'; + } + + if (tx.type === 'user_invoice') { + // incoming ln tx + tx.value = parseInt(tx.amt, 10); + tx.fee = 0; + tx.memo = tx.description || 'Lightning invoice'; + } + + tx.timestamp = tx.timestamp || tx.time; + } + return txs.sort(function (a: { timestamp: number }, b: { timestamp: number }) { + return b.timestamp - a.timestamp; + }); + } + + async fetchPendingTransactions() { + const response = await fetch(this.baseURI + '/getpending', { + method: 'GET', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + Authorization: 'Bearer' + ' ' + this.access_token, + }, + }); + + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); + } + + if (json.error) { + throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); + } + + this.pending_transactions_raw = json; + } + + async fetchTransactions() { + // TODO: iterate over all available pages + const limit = 10; + let queryRes = ''; + const offset = 0; + queryRes += '?limit=' + limit; + queryRes += '&offset=' + offset; + + const response = await fetch(this.baseURI + '/gettxs' + queryRes, { + method: 'GET', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + Authorization: 'Bearer' + ' ' + this.access_token, + }, + }); + + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); + } + + if (json.error) { + throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); + } + + if (!Array.isArray(json)) { + throw new Error('API unexpected response: ' + JSON.stringify(json)); + } + + this._lastTxFetch = +new Date(); + this.transactions_raw = json; + } + + getBalance() { + return this.balance; + } + + async fetchBalance(noRetry?: boolean): Promise { + await this.checkLogin(); + + const response = await fetch(this.baseURI + '/balance', { + method: 'GET', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + Authorization: 'Bearer' + ' ' + this.access_token, + }, + }); + + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); + } + + if (json.error) { + if (json.code * 1 === 1 && !noRetry) { + await this.authorize(); + return this.fetchBalance(true); + } + throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); + } + + if (!json.BTC || typeof json.BTC.AvailableBalance === 'undefined') { + throw new Error('API unexpected response: ' + JSON.stringify(json)); + } + + this.balance = json.BTC.AvailableBalance; + this._lastBalanceFetch = +new Date(); + } + + /** + * Example return: + * { destination: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f', + * payment_hash: 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4', + * num_satoshis: '100', + * timestamp: '1535116657', + * expiry: '3600', + * description: 'hundredSatoshis blitzhub', + * description_hash: '', + * fallback_addr: '', + * cltv_expiry: '10', + * route_hints: [] } + * + * @param invoice BOLT invoice string + * @return {DecodedInvoice} + */ + decodeInvoice(invoice: string): DecodedInvoice { + if (_staticDecodedInvoiceCache[invoice]) return _staticDecodedInvoiceCache[invoice]; // cache hit + + const { payeeNodeKey, tags, satoshis, millisatoshis, timestamp } = bolt11.decode(invoice); + + const decoded: DecodedInvoice = { + destination: payeeNodeKey ?? '', + num_satoshis: satoshis ? +satoshis : 0, + num_millisatoshis: millisatoshis ? +millisatoshis : 0, + timestamp: timestamp ?? 0, + fallback_addr: '', + route_hints: [], + payment_hash: '', + expiry: 3600, // default + description: '', + description_hash: '', + cltv_expiry: '', + }; + + for (let i = 0; i < tags.length; i++) { + const { tagName, data } = tags[i]; + switch (tagName) { + case 'payment_hash': + decoded.payment_hash = String(data); + break; + case 'purpose_commit_hash': + decoded.description_hash = String(data); + break; + case 'min_final_cltv_expiry': + decoded.cltv_expiry = data.toString(); + break; + case 'expire_time': + decoded.expiry = +data; + break; + case 'description': + decoded.description = String(data); + break; + } + } + + if (!decoded.expiry) decoded.expiry = 3600; // default + + if (decoded.num_satoshis === 0 && decoded.num_millisatoshis > 0) { + decoded.num_satoshis = Math.floor(decoded.num_millisatoshis / 1000); + } + + _staticDecodedInvoiceCache[invoice] = decoded; + + return decoded; + } + + static async isValidNodeAddress(address: string): Promise { + const normalizedAddress = new URL('/getinfo', address.replace(/([^:]\/)\/+/g, '$1')); + + const response = await fetch(normalizedAddress.toString(), { + method: 'GET', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + }, + }); + + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); + } + + if (json.code && json.code !== 1) { + throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); + } + + return true; + } + + allowReceive() { + return true; + } + + allowSignVerifyMessage() { + return false; + } + + /** + * Example return: + * { destination: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f', + * payment_hash: 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4', + * num_satoshis: '100', + * timestamp: '1535116657', + * expiry: '3600', + * description: 'hundredSatoshis blitzhub', + * description_hash: '', + * fallback_addr: '', + * cltv_expiry: '10', + * route_hints: [] } + * + * @param invoice BOLT invoice string + * @return {Promise.} + */ + async decodeInvoiceRemote(invoice: string) { + await this.checkLogin(); + + const response = await fetch(this.baseURI + '/decodeinvoice?invoice=' + invoice, { + method: 'GET', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + Authorization: 'Bearer' + ' ' + this.access_token, + }, + }); + + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); + } + + if (json.error) { + throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); + } + + if (!json.payment_hash) { + throw new Error('API unexpected response: ' + JSON.stringify(json)); + } + + return json; + } + + weOwnTransaction(txid: string) { + for (const tx of this.getTransactions()) { + if (tx && tx.payment_hash && tx.payment_hash === txid) return true; + } + + return false; + } + + authenticate(lnurl: any) { + return lnurl.authenticate(this.secret); + } + + getLatestTransactionTime(): string | 0 { + const transactions = this.getTransactions(); + if (transactions.length === 0) { + return 0; + } + return new Date(transactions.reduce((max: number, tx: any) => Math.max(max, tx.timestamp), 0) * 1000).toString(); + } + + isInvoiceExpired(invoice: string, currentTimestamp?: number): boolean { + currentTimestamp = currentTimestamp || Date.now() / 1000; // current ts in seconds + const decoded = this.decodeInvoice(invoice); + return decoded.timestamp + decoded.expiry < currentTimestamp; + } +} + +/* + + + +pending tx: + + [ { amount: 0.00078061, + account: '521172', + address: '3F9seBGCJZQ4WJJHwGhrxeGXCGbrm5SNpF', + category: 'receive', + confirmations: 0, + blockhash: '', + blockindex: 0, + blocktime: 0, + txid: '28a74277e47c2d772ee8a40464209c90dce084f3b5de38a2f41b14c79e3bfc62', + walletconflicts: [], + time: 1535024434, + timereceived: 1535024434 } ] + + +tx: + + [ { amount: 0.00078061, + account: '521172', + address: '3F9seBGCJZQ4WJJHwGhrxeGXCGbrm5SNpF', + category: 'receive', + confirmations: 5, + blockhash: '0000000000000000000edf18e9ece18e449c6d8eed1f729946b3531c32ee9f57', + blockindex: 693, + blocktime: 1535024914, + txid: '28a74277e47c2d772ee8a40464209c90dce084f3b5de38a2f41b14c79e3bfc62', + walletconflicts: [], + time: 1535024434, + timereceived: 1535024434 } ] + + */ diff --git a/class/wallets/lightning-ldk-wallet.ts b/class/wallets/lightning-ldk-wallet.ts deleted file mode 100644 index 8a84ab68f23..00000000000 --- a/class/wallets/lightning-ldk-wallet.ts +++ /dev/null @@ -1,691 +0,0 @@ -import RNFS from 'react-native-fs'; -import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; -import RnLdk from 'rn-ldk/src/index'; -import { LightningCustodianWallet } from './lightning-custodian-wallet'; -import SyncedAsyncStorage from '../synced-async-storage'; -import { randomBytes } from '../rng'; -import * as bip39 from 'bip39'; -import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; -import bolt11 from 'bolt11'; -import { SegwitBech32Wallet } from './segwit-bech32-wallet'; -import alert from '../../components/Alert'; -const bitcoin = require('bitcoinjs-lib'); - -export class LightningLdkWallet extends LightningCustodianWallet { - static type = 'lightningLdk'; - static typeReadable = 'Lightning LDK'; - private _listChannels: any[] = []; - private _listPayments: any[] = []; - private _listInvoices: any[] = []; - private _nodeConnectionDetailsCache: any = {}; // pubkey -> {pubkey, host, port, ts} - private _refundAddressScriptHex: string = ''; - private _lastTimeBlockchainCheckedTs: number = 0; - private _unwrapFirstExternalAddressFromMnemonicsCache: string = ''; - private static _predefinedNodes: Record = { - Bitrefill: '03d607f3e69fd032524a867b288216bfab263b6eaee4e07783799a6fe69bb84fac@3.237.23.179:9735', - 'OpenNode.com': '03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e@3.132.230.42:9735', - Fold: '02816caed43171d3c9854e3b0ab2cf0c42be086ff1bd4005acc2a5f7db70d83774@35.238.153.25:9735', - 'Moon (paywithmoon.com)': '025f1456582e70c4c06b61d5c8ed3ce229e6d0db538be337a2dc6d163b0ebc05a5@52.86.210.65:9735', - 'coingate.com': '0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3@3.124.63.44:9735', - 'Blockstream Store': '02df5ffe895c778e10f7742a6c5b8a0cefbe9465df58b92fadeb883752c8107c8f@35.232.170.67:9735', - ACINQ: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f@3.33.236.230:9735', - }; - - static getPredefinedNodes() { - return LightningLdkWallet._predefinedNodes; - } - - static pubkeyToAlias(pubkeyHex: string) { - for (const key of Object.keys(LightningLdkWallet._predefinedNodes)) { - const val = LightningLdkWallet._predefinedNodes[key]; - if (val.startsWith(pubkeyHex)) return key; - } - - return pubkeyHex; - } - - constructor(props: any) { - super(props); - this.preferredBalanceUnit = BitcoinUnit.SATS; - this.chain = Chain.OFFCHAIN; - this.user_invoices_raw = []; // compatibility with other lightning wallet class - } - - valid() { - try { - const entropy = bip39.mnemonicToEntropy(this.secret.replace('ldk://', '')); - return entropy.length === 64 || entropy.length === 32; - } catch (_) {} - - return false; - } - - async stop() { - return RnLdk.stop(); - } - - async wipeLndDir() {} - - async listPeers() { - return RnLdk.listPeers(); - } - - async listChannels() { - try { - // exception might be in case of incompletely-started LDK. then just ignore and return cached version - this._listChannels = await RnLdk.listChannels(); - } catch (_) {} - - return this._listChannels; - } - - async getLndTransactions() { - return []; - } - - async getInfo() { - const identityPubkey = await RnLdk.getNodeId(); - return { - identityPubkey, - }; - } - - allowSend() { - return true; - } - - timeToCheckBlockchain() { - return +new Date() - this._lastTimeBlockchainCheckedTs > 5 * 60 * 1000; // 5 min, half of block time - } - - async fundingStateStepFinalize(txhex: string) { - return RnLdk.openChannelStep2(txhex); - } - - async getMaturingBalance(): Promise { - return RnLdk.getMaturingBalance(); - } - - async getMaturingHeight(): Promise { - return RnLdk.getMaturingHeight(); - } - - /** - * Probes getNodeId() call. if its available - LDK has started - * - * @return {Promise} - */ - async isStarted() { - let rez; - try { - rez = await Promise.race([new Promise(resolve => setTimeout(() => resolve('timeout'), 1000)), RnLdk.getNodeId()]); - } catch (_) {} - - if (rez === 'timeout' || !rez) { - return false; - } - - return true; - } - - /** - * Waiter till getNodeId() starts to respond. Returns true if it eventually does, - * false in case of timeout. - * - * @return {Promise} - */ - async waitTillStarted() { - for (let c = 0; c < 30; c++) { - if (await this.isStarted()) return true; - await new Promise(resolve => setTimeout(resolve, 500)); // sleep - } - - return false; - } - - async openChannel(pubkeyHex: string, host: string, amountSats: number, privateChannel: boolean) { - let triedToConnect = false; - let port = 9735; - - if (host.includes(':')) { - const splitted = host.split(':'); - host = splitted[0]; - port = +splitted[1]; - } - - for (let c = 0; c < 20; c++) { - const peers = await this.listPeers(); - if (peers.includes(pubkeyHex)) { - // all good, connected, lets open channel - return await RnLdk.openChannelStep1(pubkeyHex, +amountSats); - } - - if (!triedToConnect) { - triedToConnect = true; - await RnLdk.connectPeer(pubkeyHex, host, +port); - } - - await new Promise(resolve => setTimeout(resolve, 500)); // sleep - } - - throw new Error('timeout waiting for peer connection'); - } - - async connectPeer(pubkeyHex: string, host: string, port: number) { - return RnLdk.connectPeer(pubkeyHex, host, +port); - } - - async lookupNodeConnectionDetailsByPubkey(pubkey: string) { - // first, trying cache: - if (this._nodeConnectionDetailsCache[pubkey] && +new Date() - this._nodeConnectionDetailsCache[pubkey].ts < 4 * 7 * 24 * 3600 * 1000) { - // cache hit - return this._nodeConnectionDetailsCache[pubkey]; - } - - // doing actual fetch and filling cache: - const response = await fetch(`https://1ml.com/node/${pubkey}/json`); - const json = await response.json(); - if (json && json.addresses && Array.isArray(json.addresses)) { - for (const address of json.addresses) { - if (address.network === 'tcp') { - const ret = { - pubkey, - host: address.addr.split(':')[0], - port: parseInt(address.addr.split(':')[1], 10), - }; - - this._nodeConnectionDetailsCache[pubkey] = Object.assign({}, ret, { ts: +new Date() }); - - return ret; - } - } - } - } - - getAddress() { - return undefined; - } - - getSecret() { - return this.secret; - } - - timeToRefreshBalance() { - return (+new Date() - this._lastBalanceFetch) / 1000 > 300; // 5 min - } - - timeToRefreshTransaction() { - return (+new Date() - this._lastTxFetch) / 1000 > 300; // 5 min - } - - async generate() { - const buf = await randomBytes(16); - this.secret = 'ldk://' + bip39.entropyToMnemonic(buf.toString('hex')); - } - - getEntropyHex() { - let ret = bip39.mnemonicToEntropy(this.secret.replace('ldk://', '')); - while (ret.length < 64) ret = '0' + ret; - return ret; - } - - getStorageNamespace() { - return RnLdk.getStorage().namespace; - } - - static async _decodeInvoice(invoice: string) { - return bolt11.decode(invoice); - } - - static async _script2address(scriptHex: string) { - return bitcoin.address.fromOutputScript(Buffer.from(scriptHex, 'hex')); - } - - async selftest() { - await RnLdk.getStorage().selftest(); - await RnLdk.selftest(); - } - - async init() { - if (!this.getSecret()) return; - console.warn('starting ldk'); - - try { - // providing simple functions that RnLdk would otherwise rely on 3rd party APIs - RnLdk.provideDecodeInvoiceFunc(LightningLdkWallet._decodeInvoice); - RnLdk.provideScript2addressFunc(LightningLdkWallet._script2address); - const syncedStorage = new SyncedAsyncStorage(this.getEntropyHex()); - // await syncedStorage.selftest(); - // await RnLdk.selftest(); - // console.warn('selftest passed'); - await syncedStorage.synchronize(); - - RnLdk.setStorage(syncedStorage); - if (this._refundAddressScriptHex) { - await RnLdk.setRefundAddressScript(this._refundAddressScriptHex); - } else { - // fallback, unwrapping address from bip39 mnemonic we have - const address = this.unwrapFirstExternalAddressFromMnemonics(); - await this.setRefundAddress(address); - } - await RnLdk.start(this.getEntropyHex(), RNFS.DocumentDirectoryPath); - - this._execInBackground(this.reestablishChannels); - if (this.timeToCheckBlockchain()) this._execInBackground(this.checkBlockchain); - } catch (error: any) { - alert('LDK init error: ' + error.message); - } - } - - unwrapFirstExternalAddressFromMnemonics() { - if (this._unwrapFirstExternalAddressFromMnemonicsCache) return this._unwrapFirstExternalAddressFromMnemonicsCache; // cache hit - const hd = new HDSegwitBech32Wallet(); - hd.setSecret(this.getSecret().replace('ldk://', '')); - const address = hd._getExternalAddressByIndex(0); - this._unwrapFirstExternalAddressFromMnemonicsCache = address; - return address; - } - - unwrapFirstExternalWIFFromMnemonics() { - const hd = new HDSegwitBech32Wallet(); - hd.setSecret(this.getSecret().replace('ldk://', '')); - return hd._getExternalWIFByIndex(0); - } - - async checkBlockchain() { - this._lastTimeBlockchainCheckedTs = +new Date(); - return RnLdk.checkBlockchain(); - } - - async payInvoice(invoice: string, freeAmount = 0) { - const decoded = this.decodeInvoice(invoice); - - // if its NOT zero amount invoice, we forcefully reset passed amount argument so underlying LDK code - // would extract amount from bolt11 - if (decoded.num_satoshis && parseInt(decoded.num_satoshis, 10) > 0) freeAmount = 0; - - if (await this.channelsNeedReestablish()) { - await this.reestablishChannels(); - await this.waitForAtLeastOneChannelBecomeActive(); - } - - const result = await RnLdk.payInvoice(invoice, freeAmount); - if (!result) throw new Error('Failed'); - - // ok, it was sent. now, waiting for an event that it was _actually_ paid: - for (let c = 0; c < 60; c++) { - await new Promise(resolve => setTimeout(resolve, 500)); // sleep - - for (const sentPayment of RnLdk.sentPayments || []) { - const paidHash = LightningLdkWallet.preimage2hash(sentPayment.payment_preimage); - if (paidHash === decoded.payment_hash) { - this._listPayments = this._listPayments || []; - this._listPayments.push( - Object.assign({}, sentPayment, { - memo: decoded.description || 'Lightning payment', - value: (freeAmount || decoded.num_satoshis) * -1, - received: +new Date(), - payment_preimage: sentPayment.payment_preimage, - payment_hash: decoded.payment_hash, - }), - ); - return; - } - } - - for (const failedPayment of RnLdk.failedPayments || []) { - if (failedPayment.payment_hash === decoded.payment_hash) throw new Error(JSON.stringify(failedPayment)); - } - } - - // no? lets just throw timeout error - throw new Error('Payment timeout'); - } - - /** - * In case user initiated channel opening, and then lost peer connection (i.e. app went in background for an - * extended period of time), when user gets back to the app the channel might already have enough confirmations, - * but will never be acknowledged as 'established' by LDK until peer reconnects so that ldk & peer can negotiate and - * agree that channel is now established - */ - async reconnectPeersWithPendingChannels() { - const peers = await RnLdk.listPeers(); - const peers2reconnect: Record = {}; - if (this._listChannels) { - for (const channel of this._listChannels) { - if (!channel.is_funding_locked) { - // pending channel - if (!peers.includes(channel.remote_node_id)) peers2reconnect[channel.remote_node_id] = true; - } - } - } - - for (const pubkey of Object.keys(peers2reconnect)) { - const { host, port } = await this.lookupNodeConnectionDetailsByPubkey(pubkey); - await this.connectPeer(pubkey, host, port); - } - } - - async getUserInvoices(limit = false) { - const newInvoices: any[] = []; - let found = false; - - // okay, so the idea is that `this._listInvoices` is a persistant storage of invoices, while - // `RnLdk.receivedPayments` is only a temp storage of emited events - - // we iterate through all stored invoices - for (const invoice of this._listInvoices) { - const newInvoice = Object.assign({}, invoice); - - // iterate through events of received payments - for (const receivedPayment of RnLdk.receivedPayments || []) { - if (receivedPayment.payment_hash === invoice.payment_hash) { - // match! this particular payment was paid - newInvoice.ispaid = true; - newInvoice.value = Math.floor(parseInt(receivedPayment.amt, 10) / 1000); - found = true; - } - } - - newInvoices.push(newInvoice); - } - - // overwrite stored array if flag was set - if (found) this._listInvoices = newInvoices; - - return this._listInvoices; - } - - isInvoiceGeneratedByWallet(paymentRequest: string) { - return Boolean(this?._listInvoices?.some(invoice => invoice.payment_request === paymentRequest)); - } - - weOwnAddress(address: string) { - return false; - } - - async addInvoice(amtSat: number, memo: string) { - if (await this.channelsNeedReestablish()) { - await this.reestablishChannels(); - await this.waitForAtLeastOneChannelBecomeActive(); - } - - if (this.getReceivableBalance() < amtSat) throw new Error('You dont have enough inbound capacity'); - - const paymentRequest = await RnLdk.addInvoice(amtSat * 1000, memo); - if (!paymentRequest) return false; - - const decoded = this.decodeInvoice(paymentRequest); - - this._listInvoices = this._listInvoices || []; - const tx = { - payment_request: paymentRequest, - ispaid: false, - timestamp: +new Date(), - expire_time: 3600 * 1000, - amt: amtSat, - type: 'user_invoice', - payment_hash: decoded.payment_hash, - description: memo || '', - }; - this._listInvoices.push(tx); - - return paymentRequest; - } - - async getAddressAsync() { - throw new Error('getAddressAsync: Not implemented'); - } - - async allowOnchainAddress(): Promise { - throw new Error('allowOnchainAddress: Not implemented'); - } - - getTransactions() { - const ret = []; - - for (const payment of this?._listPayments || []) { - const newTx = Object.assign({}, payment, { - type: 'paid_invoice', - walletID: this.getID(), - }); - ret.push(newTx); - } - - // ############################################ - - for (const invoice of this?._listInvoices || []) { - const tx = { - payment_request: invoice.payment_request, - ispaid: invoice.ispaid, - received: invoice.timestamp, - type: invoice.type, - value: invoice.value || invoice.amt, - memo: invoice.description, - timestamp: invoice.timestamp, // important - expire_time: invoice.expire_time, // important - walletID: this.getID(), - }; - - if (tx.ispaid || invoice.timestamp + invoice.expire_time > +new Date()) { - // expired non-paid invoices are not shown - ret.push(tx); - } - } - - ret.sort(function (a, b) { - return b.received - a.received; - }); - - return ret; - } - - async fetchTransactions() { - if (this.timeToCheckBlockchain()) { - try { - // exception might be in case of incompletely-started LDK - this._listChannels = await RnLdk.listChannels(); - await this.checkBlockchain(); - // ^^^ will be executed if above didnt throw exceptions, which means ldk fully started. - // we need this for a case when app returns from background if it was in bg for a really long time. - // ldk needs to update it's blockchain data, and this is practically the only place where it can - // do that (except on cold start) - } catch (_) {} - } - - try { - await this.reconnectPeersWithPendingChannels(); - } catch (error: any) { - console.log('fetchTransactions failed'); - console.log(error.message); - } - - await this.getUserInvoices(); // it internally updates paid user invoices - } - - getBalance() { - let sum = 0; - if (this._listChannels) { - for (const channel of this._listChannels) { - if (!channel.is_funding_locked) continue; // pending channel - sum += Math.floor(parseInt(channel.outbound_capacity_msat, 10) / 1000); - } - } - - return sum; - } - - getReceivableBalance() { - let sum = 0; - if (this._listChannels) { - for (const channel of this._listChannels) { - if (!channel.is_funding_locked) continue; // pending channel - sum += Math.floor(parseInt(channel.inbound_capacity_msat, 10) / 1000); - } - } - return sum; - } - - /** - * This method checks if there is balance on first unwapped address we have. - * This address is a fallback in case user has _no_ other wallets to withdraw onchain coins to, so closed-channel - * funds land on this address. Ofcourse, if user provided us a withdraw address, it should be stored in - * `this._refundAddressScriptHex` and its balance frankly is not our concern. - * - * @return {Promise<{confirmedBalance: number}>} - */ - async walletBalance() { - let confirmedSat = 0; - if (this._unwrapFirstExternalAddressFromMnemonicsCache) { - const response = await fetch('https://blockstream.info/api/address/' + this._unwrapFirstExternalAddressFromMnemonicsCache + '/utxo'); - const json = await response.json(); - if (json && Array.isArray(json)) { - for (const utxo of json) { - if (utxo?.status?.confirmed) { - confirmedSat += parseInt(utxo.value, 10); - } - } - } - } - - return { confirmedBalance: confirmedSat }; - } - - async fetchBalance() { - await this.listChannels(); // updates channels - } - - async claimCoins(address: string) { - console.log('unwrapping wif...'); - const wif = this.unwrapFirstExternalWIFFromMnemonics(); - const wallet = new SegwitBech32Wallet(); - wallet.setSecret(String(wif)); - console.log('fetching balance...'); - await wallet.fetchUtxo(); - console.log(wallet.getBalance(), wallet.getUtxo()); - console.log('creating transation...'); - const { tx } = wallet.createTransaction(wallet.getUtxo(), [{ address }], 2, address, 0, false, 0); - if (!tx) throw new Error('claimCoins: could not create transaction'); - console.log('broadcasting...'); - return await wallet.broadcastTx(tx.toHex()); - } - - async fetchInfo() { - throw new Error('fetchInfo: Not implemented'); - } - - allowReceive() { - return true; - } - - async closeChannel(fundingTxidHex: string, force = false) { - return force ? await RnLdk.closeChannelForce(fundingTxidHex) : await RnLdk.closeChannelCooperatively(fundingTxidHex); - } - - getLatestTransactionTime(): string | 0 { - if (this.getTransactions().length === 0) { - return 0; - } - let max = -1; - for (const tx of this.getTransactions()) { - if (tx.received) max = Math.max(tx.received, max); - } - return new Date(max).toString(); - } - - async getLogs() { - return RnLdk.getLogs() - .map(log => log.line) - .join('\n'); - } - - async getLogsWithTs() { - return RnLdk.getLogs() - .map(log => log.ts + ' ' + log.line) - .join('\n'); - } - - async fetchPendingTransactions() {} - - async fetchUserInvoices() { - await this.getUserInvoices(); - } - - static preimage2hash(preimageHex: string): string { - const hash = bitcoin.crypto.sha256(Buffer.from(preimageHex, 'hex')); - return hash.toString('hex'); - } - - async reestablishChannels() { - const connectedInThisRun: any = {}; - for (const channel of await this.listChannels()) { - if (channel.is_usable) continue; // already connected..? - if (connectedInThisRun[channel.remote_node_id]) continue; // already tried to reconnect (in case there are several channels with the same node) - const { pubkey, host, port } = await this.lookupNodeConnectionDetailsByPubkey(channel.remote_node_id); - await this.connectPeer(pubkey, host, port); - connectedInThisRun[pubkey] = true; - } - } - - async channelsNeedReestablish() { - const freshListChannels = await this.listChannels(); - const active = freshListChannels.filter(chan => !!chan.is_usable && chan.is_funding_locked).length; - return freshListChannels.length !== +active; - } - - async waitForAtLeastOneChannelBecomeActive() { - const active = (await this.listChannels()).filter(chan => !!chan.is_usable).length; - - for (let c = 0; c < 10; c++) { - await new Promise(resolve => setTimeout(resolve, 500)); // sleep - const freshListChannels = await this.listChannels(); - const active2 = freshListChannels.filter(chan => !!chan.is_usable).length; - if (freshListChannels.length === +active2) return true; // all active kek - - if (freshListChannels.length === 0) return true; // no channels at all - if (+active2 > +active) return true; // something became active, lets ret - } - - return false; - } - - async setRefundAddress(address: string) { - const script = bitcoin.address.toOutputScript(address); - this._refundAddressScriptHex = script.toString('hex'); - await RnLdk.setRefundAddressScript(this._refundAddressScriptHex); - } - - static async getVersion() { - return RnLdk.getVersion(); - } - - static getPackageVersion() { - return RnLdk.getPackageVersion(); - } - - getChannelsClosedEvents() { - return RnLdk.channelsClosed; - } - - async purgeLocalStorage() { - return RnLdk.getStorage().purgeLocalStorage(); - } - - /** - * executes async function in background, so calling code can return immediately, while catching all thrown exceptions - * and showing them in alert() instead of propagating them up - * - * @param func {function} Async functino to execute - * @private - */ - _execInBackground(func: () => void) { - const that = this; - (async () => { - try { - await func.call(that); - } catch (error: any) { - alert('_execInBackground error:' + error.message); - } - })(); - } -} diff --git a/class/wallets/multisig-hd-wallet.js b/class/wallets/multisig-hd-wallet.js deleted file mode 100644 index 88506d73d3d..00000000000 --- a/class/wallets/multisig-hd-wallet.js +++ /dev/null @@ -1,1197 +0,0 @@ -import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; -import * as bip39 from 'bip39'; -import b58 from 'bs58check'; -import { decodeUR } from '../../blue_modules/ur'; -import { ECPairFactory } from 'ecpair'; -import BIP32Factory from 'bip32'; -import ecc from '../../blue_modules/noble_ecc'; -const ECPair = ECPairFactory(ecc); -const BlueElectrum = require('../../blue_modules/BlueElectrum'); -const bip32 = BIP32Factory(ecc); -const bitcoin = require('bitcoinjs-lib'); -const createHash = require('create-hash'); -const reverse = require('buffer-reverse'); -const mn = require('electrum-mnemonic'); - -const electrumSegwit = passphrase => ({ - prefix: mn.PREFIXES.segwit, - ...(passphrase ? { passphrase } : {}), -}); - -const electrumStandart = passphrase => ({ - prefix: mn.PREFIXES.standard, - ...(passphrase ? { passphrase } : {}), -}); - -const ELECTRUM_SEED_PREFIX = 'electrumseed:'; - -export class MultisigHDWallet extends AbstractHDElectrumWallet { - static type = 'HDmultisig'; - static typeReadable = 'Multisig Vault'; - - static FORMAT_P2WSH = 'p2wsh'; - static FORMAT_P2SH_P2WSH = 'p2sh-p2wsh'; - static FORMAT_P2SH_P2WSH_ALT = 'p2wsh-p2sh'; - static FORMAT_P2SH = 'p2sh'; - - static PATH_NATIVE_SEGWIT = "m/48'/0'/0'/2'"; - static PATH_WRAPPED_SEGWIT = "m/48'/0'/0'/1'"; - static PATH_LEGACY = "m/45'"; - - constructor() { - super(); - this._m = 0; // minimum required signatures so spend (m out of n) - this._cosigners = []; // array of xpubs or mnemonic seeds - this._cosignersFingerprints = []; // array of according fingerprints (if any provided) - this._cosignersCustomPaths = []; // array of according paths (if any provided) - this._cosignersPassphrases = []; // array of according passphrases (if any provided) - this._derivationPath = ''; - this._isNativeSegwit = false; - this._isWrappedSegwit = false; - this._isLegacy = false; - this.gap_limit = 10; - } - - isLegacy() { - return this._isLegacy; - } - - isNativeSegwit() { - return this._isNativeSegwit; - } - - isWrappedSegwit() { - return this._isWrappedSegwit; - } - - setWrappedSegwit() { - this._isWrappedSegwit = true; - } - - setNativeSegwit() { - this._isNativeSegwit = true; - } - - setLegacy() { - this._isLegacy = true; - } - - setM(m) { - this._m = m; - } - - /** - * @returns {number} How many minumim signatures required to authorize a spend - */ - getM() { - return this._m; - } - - /** - * @returns {number} Total count of cosigners - */ - getN() { - return this._cosigners.length; - } - - setDerivationPath(path) { - this._derivationPath = path; - switch (this._derivationPath) { - case "m/48'/0'/0'/2'": - this._isNativeSegwit = true; - break; - case "m/48'/0'/0'/1'": - this._isWrappedSegwit = true; - break; - case "m/45'": - this._isLegacy = true; - break; - case "m/44'": - this._isLegacy = true; - break; - } - } - - getCustomDerivationPathForCosigner(index) { - if (index === 0) throw new Error('cosigners indexation starts from 1'); - if (index > this.getN()) return false; - return this._cosignersCustomPaths[index - 1] || this.getDerivationPath(); - } - - getCosigner(index) { - if (index === 0) throw new Error('cosigners indexation starts from 1'); - return this._cosigners[index - 1]; - } - - getFingerprint(index) { - if (index === 0) throw new Error('cosigners fingerprints indexation starts from 1'); - return this._cosignersFingerprints[index - 1]; - } - - getCosignerForFingerprint(fp) { - const index = this._cosignersFingerprints.indexOf(fp); - return this._cosigners[index]; - } - - getPassphrase(index) { - if (index === 0) throw new Error('cosigners indexation starts from 1'); - return this._cosignersPassphrases[index - 1]; - } - - static isXpubValid(key) { - let xpub; - - try { - const tempWallet = new MultisigHDWallet(); - xpub = tempWallet._zpubToXpub(key); - bip32.fromBase58(xpub); - return true; - } catch (_) {} - - return false; - } - - static isXprvValid(xprv) { - try { - xprv = MultisigHDWallet.convertMultisigXprvToRegularXprv(xprv); - bip32.fromBase58(xprv); - return true; - } catch (_) { - return false; - } - } - - /** - * - * @param key {string} Either xpub or mnemonic phrase - * @param fingerprint {string} Fingerprint for cosigner that is added as xpub - * @param path {string} Custom path (if any) for cosigner that is added as mnemonics - * @param passphrase {string} BIP38 Passphrase (if any) - */ - addCosigner(key, fingerprint, path, passphrase) { - if (MultisigHDWallet.isXpubString(key) && !fingerprint) { - throw new Error('fingerprint is required when adding cosigner as xpub (watch-only)'); - } - - if (path && !this.constructor.isPathValid(path)) { - throw new Error('path is not valid'); - } - - if (MultisigHDWallet.isXprvString(key)) { - // nop, but probably should validate xprv - } else if (MultisigHDWallet.isXpubString(key)) { - // nop, just validate - if (!MultisigHDWallet.isXpubValid(key)) throw new Error('Not a valid xpub: ' + key); - } else if (key.startsWith(ELECTRUM_SEED_PREFIX) && fingerprint && path) { - // its an electrum seed - const mnemonic = key.replace(ELECTRUM_SEED_PREFIX, ''); - try { - mn.mnemonicToSeedSync(mnemonic, electrumStandart(passphrase)); - this.setLegacy(); - } catch (_) { - try { - mn.mnemonicToSeedSync(mnemonic, electrumSegwit(passphrase)); - this.setNativeSegwit(); - } catch (__) { - throw new Error('Not a valid electrum seed'); - } - } - } else { - // mnemonics. lets derive fingerprint (if it wasnt provided) - if (!bip39.validateMnemonic(key)) throw new Error('Not a valid mnemonic phrase'); - fingerprint = fingerprint || MultisigHDWallet.mnemonicToFingerprint(key, passphrase); - } - - if (fingerprint && this._cosignersFingerprints.indexOf(fingerprint.toUpperCase()) !== -1 && fingerprint !== '00000000') { - // 00000000 is a special case, means we have no idea what the FP is but its okay - throw new Error('Duplicate fingerprint'); - } - - const index = this._cosigners.length; - this._cosigners[index] = key; - if (fingerprint) this._cosignersFingerprints[index] = fingerprint.toUpperCase(); - if (path) this._cosignersCustomPaths[index] = path; - if (passphrase) this._cosignersPassphrases[index] = passphrase; - } - - static convertMultisigXprvToRegularXprv(Zprv) { - let data = b58.decode(Zprv); - data = data.slice(4); - return b58.encode(Buffer.concat([Buffer.from('0488ade4', 'hex'), data])); - } - - static convertXprvToXpub(xprv) { - const restored = bip32.fromBase58(MultisigHDWallet.convertMultisigXprvToRegularXprv(xprv)); - return restored.neutered().toBase58(); - } - - /** - * Stored cosigner can be EITHER xpub (or Zpub or smth), OR mnemonic phrase. This method converts it to xpub - * - * @param cosigner {string} Zpub (or similar) or mnemonic seed - * @returns {string} xpub - * @private - */ - _getXpubFromCosigner(cosigner) { - if (MultisigHDWallet.isXprvString(cosigner)) cosigner = MultisigHDWallet.convertXprvToXpub(cosigner); - let xpub = cosigner; - if (!MultisigHDWallet.isXpubString(cosigner)) { - const index = this._cosigners.indexOf(cosigner); - xpub = MultisigHDWallet.seedToXpub( - cosigner, - this._cosignersCustomPaths[index] || this._derivationPath, - this._cosignersPassphrases[index], - ); - } - return this._zpubToXpub(xpub); - } - - _getExternalAddressByIndex(index) { - if (!this._m) throw new Error('m is not set'); - index = +index; - if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit - - const address = this._getAddressFromNode(0, index); - this.external_addresses_cache[index] = address; - return address; - } - - _getAddressFromNode(nodeIndex, index) { - const pubkeys = []; - for (const [cosignerIndex, cosigner] of this._cosigners.entries()) { - this._nodes = this._nodes || []; - this._nodes[nodeIndex] = this._nodes[nodeIndex] || []; - let _node; - - if (!this._nodes[nodeIndex][cosignerIndex]) { - const xpub = this._getXpubFromCosigner(cosigner); - const hdNode = bip32.fromBase58(xpub); - _node = hdNode.derive(nodeIndex); - this._nodes[nodeIndex][cosignerIndex] = _node; - } else { - _node = this._nodes[nodeIndex][cosignerIndex]; - } - - pubkeys.push(_node.derive(index).publicKey); - } - - if (this.isWrappedSegwit()) { - const { address } = bitcoin.payments.p2sh({ - redeem: bitcoin.payments.p2wsh({ - redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), - }), - }); - - return address; - } else if (this.isNativeSegwit()) { - const { address } = bitcoin.payments.p2wsh({ - redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), - }); - - return address; - } else if (this.isLegacy()) { - const { address } = bitcoin.payments.p2sh({ - redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), - }); - - return address; - } else { - throw new Error('Dont know how to make address'); - } - } - - _getInternalAddressByIndex(index) { - if (!this._m) throw new Error('m is not set'); - index = +index; - if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit - - const address = this._getAddressFromNode(1, index); - this.internal_addresses_cache[index] = address; - return address; - } - - static seedToXpub(mnemonic, path, passphrase) { - let seed; - if (mnemonic.startsWith(ELECTRUM_SEED_PREFIX)) { - seed = MultisigHDWallet.convertElectrumMnemonicToSeed(mnemonic, passphrase); - } else { - seed = bip39.mnemonicToSeedSync(mnemonic, passphrase); - } - - const root = bip32.fromSeed(seed); - const child = root.derivePath(path).neutered(); - return child.toBase58(); - } - - /** - * Returns xpub with correct prefix accodting to this objects set derivation path, for example 'Zpub' (with - * capital Z) for bech32 multisig - * @see https://github.com/satoshilabs/slips/blob/master/slip-0132.md - * - * @param xpub {string} Any kind of xpub, including zpub etc since we are only swapping the prefix bytes - * @returns {string} - */ - convertXpubToMultisignatureXpub(xpub) { - let data = b58.decode(xpub); - data = data.slice(4); - if (this.isNativeSegwit()) { - return b58.encode(Buffer.concat([Buffer.from('02aa7ed3', 'hex'), data])); - } else if (this.isWrappedSegwit()) { - return b58.encode(Buffer.concat([Buffer.from('0295b43f', 'hex'), data])); - } - - return xpub; - } - - convertXprvToMultisignatureXprv(xpub) { - let data = b58.decode(xpub); - data = data.slice(4); - if (this.isNativeSegwit()) { - return b58.encode(Buffer.concat([Buffer.from('02aa7a99', 'hex'), data])); - } else if (this.isWrappedSegwit()) { - return b58.encode(Buffer.concat([Buffer.from('0295b005', 'hex'), data])); - } - - return xpub; - } - - static isXpubString(xpub) { - return ['xpub', 'ypub', 'zpub', 'Ypub', 'Zpub'].includes(xpub.substring(0, 4)); - } - - static isXprvString(xpub) { - return ['xprv', 'yprv', 'zprv', 'Yprv', 'Zprv'].includes(xpub.substring(0, 4)); - } - - /** - * Converts fingerprint that is stored as a deciman number to hex string (all caps) - * - * @param xfp {number} For example 64392470 - * @returns {string} For example 168DD603 - */ - static ckccXfp2fingerprint(xfp) { - let masterFingerprintHex = Number(xfp).toString(16); - while (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte - - // poor man's little-endian conversion: - // ¯\_(ツ)_/¯ - return ( - masterFingerprintHex[6] + - masterFingerprintHex[7] + - masterFingerprintHex[4] + - masterFingerprintHex[5] + - masterFingerprintHex[2] + - masterFingerprintHex[3] + - masterFingerprintHex[0] + - masterFingerprintHex[1] - ).toUpperCase(); - } - - getXpub() { - return this.getSecret(true); - } - - getSecret(coordinationSetup = false) { - let ret = '# BlueWallet Multisig setup file\n'; - if (coordinationSetup) ret += '# this file contains only public keys and is safe to\n# distribute among cosigners\n'; - if (!coordinationSetup) ret += '# this file may contain private information\n'; - ret += '#\n'; - ret += 'Name: ' + this.getLabel() + '\n'; - ret += 'Policy: ' + this.getM() + ' of ' + this.getN() + '\n'; - - let hasCustomPaths = 0; - const customPaths = {}; - for (let index = 0; index < this.getN(); index++) { - if (this._cosignersCustomPaths[index]) hasCustomPaths++; - if (this._cosignersCustomPaths[index]) customPaths[this._cosignersCustomPaths[index]] = 1; - } - - let printedGlobalDerivation = false; - - if (this.getDerivationPath()) customPaths[this.getDerivationPath()] = 1; - if (Object.keys(customPaths).length === 1) { - // we have exactly one path, for everyone. lets just print it - for (const path of Object.keys(customPaths)) { - ret += 'Derivation: ' + path + '\n'; - printedGlobalDerivation = true; - } - } - - if (hasCustomPaths !== this.getN() && !printedGlobalDerivation) { - printedGlobalDerivation = true; - ret += 'Derivation: ' + this.getDerivationPath() + '\n'; - } - - if (this.isNativeSegwit()) { - ret += 'Format: P2WSH\n'; - } else if (this.isWrappedSegwit()) { - ret += 'Format: P2SH-P2WSH\n'; - } else if (this.isLegacy()) { - ret += 'Format: P2SH\n'; - } else { - ret += 'Format: unknown\n'; - } - ret += '\n'; - - for (let index = 0; index < this.getN(); index++) { - if ( - this._cosignersCustomPaths[index] && - ((printedGlobalDerivation && this._cosignersCustomPaths[index] !== this.getDerivationPath()) || !printedGlobalDerivation) - ) { - ret += '# derivation: ' + this._cosignersCustomPaths[index] + '\n'; - // if we printed global derivation and this cosigned _has_ derivation and its different from global - we print it ; - // or we print it if cosigner _has_ some derivation set and we did not print global - } - if (this.constructor.isXpubString(this._cosigners[index])) { - ret += this._cosignersFingerprints[index] + ': ' + this._cosigners[index] + '\n'; - } else { - if (coordinationSetup) { - const xpub = this.convertXpubToMultisignatureXpub( - MultisigHDWallet.seedToXpub( - this._cosigners[index], - this._cosignersCustomPaths[index] || this._derivationPath, - this._cosignersPassphrases[index], - ), - ); - const fingerprint = MultisigHDWallet.mnemonicToFingerprint(this._cosigners[index], this._cosignersPassphrases[index]); - ret += fingerprint + ': ' + xpub + '\n'; - } else { - ret += 'seed: ' + this._cosigners[index]; - if (this._cosignersPassphrases[index]) ret += ' - ' + this._cosignersPassphrases[index]; - ret += '\n# warning! sensitive information, do not disclose ^^^ \n'; - } - } - - ret += '\n'; - } - - return ret; - } - - setSecret(secret) { - if (secret.toUpperCase().startsWith('UR:BYTES')) { - const decoded = decodeUR([secret]); - const b = Buffer.from(decoded, 'hex'); - secret = b.toString(); - } - - // is it Coldcard json file? - let json; - try { - json = JSON.parse(secret); - } catch (_) {} - if (json && json.xfp && json.p2wsh_deriv && json.p2wsh) { - this.addCosigner(json.p2wsh, json.xfp); // technically we dont need deriv (json.p2wsh_deriv), since cosigner is already an xpub - return; - } - - // is it electrum json? - if (json && json.wallet_type && json.wallet_type !== 'standard') { - const mofn = json.wallet_type.split('of'); - this.setM(parseInt(mofn[0].trim(), 10)); - const n = parseInt(mofn[1].trim(), 10); - for (let c = 1; c <= n; c++) { - const cosignerData = json['x' + c + '/']; - if (cosignerData) { - const fingerprint = - (cosignerData.ckcc_xfp - ? MultisigHDWallet.ckccXfp2fingerprint(cosignerData.ckcc_xfp) - : cosignerData.root_fingerprint?.toUpperCase()) || '00000000'; - if (cosignerData.seed) { - this.addCosigner(ELECTRUM_SEED_PREFIX + cosignerData.seed, fingerprint, cosignerData.derivation, cosignerData.passphrase); - } else if (cosignerData.xprv && MultisigHDWallet.isXprvValid(cosignerData.xprv)) { - this.addCosigner(cosignerData.xprv, fingerprint, cosignerData.derivation); - } else { - this.addCosigner(cosignerData.xpub, fingerprint, cosignerData.derivation); - } - } - - if (cosignerData?.xpub?.startsWith('Zpub')) this.setNativeSegwit(); - if (cosignerData?.xpub?.startsWith('Ypub')) this.setWrappedSegwit(); - if (cosignerData?.xpub?.startsWith('xpub')) this.setLegacy(); - } - } - - // coldcard & cobo txt format: - let customPathForCurrentCosigner = false; - for (const line of secret.split('\n')) { - const [key, value] = line.split(':'); - - switch (key) { - case 'Name': - this.setLabel(value.trim()); - break; - - case 'Policy': - this.setM(parseInt(value.trim().split('of')[0].trim(), 10)); - break; - - case 'Derivation': - this.setDerivationPath(value.trim()); - break; - - case 'Format': - switch (value.trim()) { - case MultisigHDWallet.FORMAT_P2WSH.toUpperCase(): - this.setNativeSegwit(); - break; - case MultisigHDWallet.FORMAT_P2SH_P2WSH.toUpperCase(): - case MultisigHDWallet.FORMAT_P2SH_P2WSH_ALT.toUpperCase(): - this.setWrappedSegwit(); - break; - case MultisigHDWallet.FORMAT_P2SH.toUpperCase(): - this.setLegacy(); - break; - } - break; - - default: - if (key && value && MultisigHDWallet.isXpubString(value.trim())) { - this.addCosigner(value.trim(), key, customPathForCurrentCosigner); - } else if (key.replace('#', '').trim() === 'derivation') { - customPathForCurrentCosigner = value.trim(); - } else if (key === 'seed') { - const [seed, passphrase] = value.split(' - '); - this.addCosigner(seed.trim(), false, customPathForCurrentCosigner, passphrase); - } - break; - } - } - - // is it wallet descriptor? - // @see https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md - // @see https://github.com/Fonta1n3/FullyNoded/blob/master/Docs/Wallets/Wallet-Export-Spec.md - if (!json && secret.indexOf('sortedmulti(')) { - // provided secret was NOT json but plain wallet descriptor text. lets mock json - json = { descriptor: secret, label: 'Multisig vault' }; - } - if (secret.indexOf('sortedmulti(') !== -1 && json.descriptor) { - if (json.label) this.setLabel(json.label); - if (json.descriptor.includes('sh(wsh(')) { - this.setWrappedSegwit(); - } else if (json.descriptor.includes('wsh(')) { - this.setNativeSegwit(); - } else if (json.descriptor.includes('sh(')) { - this.setLegacy(); - } - - const s2 = json.descriptor.substr(json.descriptor.indexOf('sortedmulti(') + 12); - const s3 = s2.split(','); - const M = parseInt(s3[0], 10); - if (M) this.setM(M); - - for (let c = 1; c < s3.length; c++) { - const re = /\[([^\]]+)\](.*)/; - const m = s3[c].match(re); - if (m && m.length === 3) { - let hexFingerprint = m[1].split('/')[0]; - if (hexFingerprint.length === 8) { - hexFingerprint = Buffer.from(hexFingerprint, 'hex').toString('hex'); - } - - const path = 'm/' + m[1].split('/').slice(1).join('/').replace(/[h]/g, "'"); - let xpub = m[2]; - if (xpub.indexOf('/') !== -1) { - xpub = xpub.substr(0, xpub.indexOf('/')); - } - if (xpub.indexOf(')') !== -1) { - xpub = xpub.substr(0, xpub.indexOf(')')); - } - - this.addCosigner(xpub, hexFingerprint.toUpperCase(), path); - } - } - - if (this.getN() === 0) { - // handling a case when smth went wrong and we didnt parse any cosigners, probably because - // string is a bit non-standard, deesnt have chars like '[' - for (let c = 1; c < s3.length; c++) { - const hexFingerprint = s3[c].split('/')[0]; - let indexOfXpub = s3[c].indexOf('xpub'); - if (indexOfXpub === -1) { - // just for any case - indexOfXpub = s3[c].indexOf('ypub'); - } - if (indexOfXpub === -1) { - // just for any case - indexOfXpub = s3[c].indexOf('zpub'); - } - if (indexOfXpub === -1) { - throw new Error('Could not parse cosigner in a descriptor'); - } - - const xpub = s3[c].substring(indexOfXpub).replaceAll(')', ''); - const path = 'm' + s3[c].substring(hexFingerprint.length, indexOfXpub); - - this.addCosigner(xpub, hexFingerprint.toUpperCase(), path); - } - } - } - - // is it caravan? - if (json && json.network === 'mainnet' && json.quorum) { - this.setM(+json.quorum.requiredSigners); - if (json.name) this.setLabel(json.name); - - switch (json.addressType.toLowerCase()) { - case MultisigHDWallet.FORMAT_P2SH: - this.setLegacy(); - break; - case MultisigHDWallet.FORMAT_P2SH_P2WSH: - case MultisigHDWallet.FORMAT_P2SH_P2WSH_ALT: - this.setWrappedSegwit(); - break; - case MultisigHDWallet.FORMAT_P2WSH: - default: - this.setNativeSegwit(); - break; - } - - for (const pk of json.extendedPublicKeys) { - const path = this.constructor.isPathValid(json.bip32Path) ? json.bip32Path : "m/1'"; - this.addCosigner(pk.xpub, pk.xfp ?? '00000000', path); - } - } - - if (!this.getLabel()) this.setLabel('Multisig vault'); - } - - _getDerivationPathByAddressWithCustomPath(address, customPathPrefix) { - const path = customPathPrefix || this._derivationPath; - for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { - if (this._getExternalAddressByIndex(c) === address) return path + '/0/' + c; - } - for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { - if (this._getInternalAddressByIndex(c) === address) return path + '/1/' + c; - } - - return false; - } - - _getWifForAddress(address) { - return false; - } - - _getPubkeyByAddress(address) { - throw new Error('Not applicable in multisig'); - } - - _getDerivationPathByAddress(address) { - throw new Error('Not applicable in multisig'); - } - - _addPsbtInput(psbt, input, sequence, masterFingerprintBuffer) { - const bip32Derivation = []; // array per each pubkey thats gona be used - const pubkeys = []; - for (const [cosignerIndex, cosigner] of this._cosigners.entries()) { - const path = this._getDerivationPathByAddressWithCustomPath( - input.address, - this._cosignersCustomPaths[cosignerIndex] || this._derivationPath, - ); - // ^^ path resembles _custom path_, if provided by user during setup, otherwise default path for wallet type gona be used - const masterFingerprint = Buffer.from(this._cosignersFingerprints[cosignerIndex], 'hex'); - - const xpub = this._getXpubFromCosigner(cosigner); - const hdNode0 = bip32.fromBase58(xpub); - const splt = path.split('/'); - const internal = +splt[splt.length - 2]; - const index = +splt[splt.length - 1]; - const _node0 = hdNode0.derive(internal); - const pubkey = _node0.derive(index).publicKey; - pubkeys.push(pubkey); - - bip32Derivation.push({ - masterFingerprint, - path, - pubkey, - }); - } - - if (this.isNativeSegwit()) { - const p2wsh = bitcoin.payments.p2wsh({ - redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), - }); - const witnessScript = p2wsh.redeem.output; - - if (!input.txhex) throw new Error('Electrum server didnt provide txhex to properly create PSBT transaction'); - - psbt.addInput({ - hash: input.txId, - index: input.vout, - sequence, - bip32Derivation, - witnessUtxo: { - script: p2wsh.output, - value: input.value, - }, - witnessScript, - // hw wallets now require passing the whole previous tx as Buffer, as if it was non-segwit input, to mitigate - // some hw wallets attack vector - nonWitnessUtxo: Buffer.from(input.txhex, 'hex'), - }); - } else if (this.isWrappedSegwit()) { - const p2shP2wsh = bitcoin.payments.p2sh({ - redeem: bitcoin.payments.p2wsh({ - redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), - }), - }); - const witnessScript = p2shP2wsh.redeem.redeem.output; - const redeemScript = p2shP2wsh.redeem.output; - - psbt.addInput({ - hash: input.txId, - index: input.vout, - sequence, - bip32Derivation, - witnessUtxo: { - script: p2shP2wsh.output, - value: input.value, - }, - witnessScript, - redeemScript, - // hw wallets now require passing the whole previous tx as Buffer, as if it was non-segwit input, to mitigate - // some hw wallets attack vector - nonWitnessUtxo: Buffer.from(input.txhex, 'hex'), - }); - } else if (this.isLegacy()) { - const p2sh = bitcoin.payments.p2sh({ - redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), - }); - const redeemScript = p2sh.redeem.output; - psbt.addInput({ - hash: input.txId, - index: input.vout, - sequence, - bip32Derivation, - redeemScript, - nonWitnessUtxo: Buffer.from(input.txhex, 'hex'), - }); - } else { - throw new Error('Dont know how to add input'); - } - - return psbt; - } - - _getOutputDataForChange(outputData) { - const bip32Derivation = []; // array per each pubkey thats gona be used - const pubkeys = []; - for (const [cosignerIndex, cosigner] of this._cosigners.entries()) { - const path = this._getDerivationPathByAddressWithCustomPath( - outputData.address, - this._cosignersCustomPaths[cosignerIndex] || this._derivationPath, - ); - // ^^ path resembles _custom path_, if provided by user during setup, otherwise default path for wallet type gona be used - const masterFingerprint = Buffer.from(this._cosignersFingerprints[cosignerIndex], 'hex'); - - const xpub = this._getXpubFromCosigner(cosigner); - const hdNode0 = bip32.fromBase58(xpub); - const splt = path.split('/'); - const internal = +splt[splt.length - 2]; - const index = +splt[splt.length - 1]; - const _node0 = hdNode0.derive(internal); - const pubkey = _node0.derive(index).publicKey; - pubkeys.push(pubkey); - - bip32Derivation.push({ - masterFingerprint, - path, - pubkey, - }); - } - - outputData.bip32Derivation = bip32Derivation; - - if (this.isLegacy()) { - const p2sh = bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }); - outputData.redeemScript = p2sh.output; - } else if (this.isWrappedSegwit()) { - const p2shP2wsh = bitcoin.payments.p2sh({ - redeem: bitcoin.payments.p2wsh({ - redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), - }), - }); - outputData.witnessScript = p2shP2wsh.redeem.redeem.output; - outputData.redeemScript = p2shP2wsh.redeem.output; - } else if (this.isNativeSegwit()) { - // not needed by coldcard, apparently..? - const p2wsh = bitcoin.payments.p2wsh({ - redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), - }); - outputData.witnessScript = p2wsh.redeem.output; - } else { - throw new Error('dont know how to add change output'); - } - - return outputData; - } - - howManySignaturesCanWeMake() { - let howManyPrivKeysWeGot = 0; - for (const cosigner of this._cosigners) { - if (!MultisigHDWallet.isXpubString(cosigner) && !MultisigHDWallet.isXprvString(cosigner)) howManyPrivKeysWeGot++; - } - - return howManyPrivKeysWeGot; - } - - /** - * @inheritDoc - */ - createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false, masterFingerprint) { - if (targets.length === 0) throw new Error('No destination provided'); - if (this.howManySignaturesCanWeMake() === 0) skipSigning = true; - - // overriding script length for proper vbytes calculation - for (const u of utxos) { - u.script = u.script || {}; - if (this.isNativeSegwit()) { - u.script.length = u.script.length || Math.ceil((8 + this.getM() * 74 + this.getN() * 34) / 4); - } else if (this.isWrappedSegwit()) { - u.script.length = u.script.length || 35 + Math.ceil((8 + this.getM() * 74 + this.getN() * 34) / 4); - } else { - u.script.length = u.script.length || 9 + this.getM() * 74 + this.getN() * 34; - } - } - - const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress); - sequence = sequence || AbstractHDElectrumWallet.defaultRBFSequence; - - let psbt = new bitcoin.Psbt(); - - let c = 0; - inputs.forEach(input => { - c++; - psbt = this._addPsbtInput(psbt, input, sequence); - }); - - outputs.forEach(output => { - // if output has no address - this is change output - let change = false; - if (!output.address) { - change = true; - output.address = changeAddress; - } - - let outputData = { - address: output.address, - value: output.value, - }; - - if (change) { - outputData = this._getOutputDataForChange(outputData); - } - - psbt.addOutput(outputData); - }); - - if (!skipSigning) { - for (let cc = 0; cc < c; cc++) { - let signaturesMade = 0; - for (const [cosignerIndex, cosigner] of this._cosigners.entries()) { - if (MultisigHDWallet.isXpubString(cosigner)) continue; - // ok this is a mnemonic, lets try to sign - if (signaturesMade >= this.getM()) { - // dont sign more than we need, otherwise there will be "Too many signatures" error - continue; - } - const passphrase = this._cosignersPassphrases[cosignerIndex]; - let seed = bip39.mnemonicToSeedSync(cosigner, passphrase); - if (cosigner.startsWith(ELECTRUM_SEED_PREFIX)) { - seed = MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner, passphrase); - } - - const hdRoot = bip32.fromSeed(seed); - psbt.signInputHD(cc, hdRoot); - signaturesMade++; - } - } - } - - let tx; - if (!skipSigning && this.howManySignaturesCanWeMake() >= this.getM()) { - tx = psbt.finalizeAllInputs().extractTransaction(); - } - return { tx, inputs, outputs, fee, psbt }; - } - - static convertElectrumMnemonicToSeed(cosigner, passphrase) { - let seed; - try { - seed = mn.mnemonicToSeedSync(cosigner.replace(ELECTRUM_SEED_PREFIX, ''), electrumSegwit(passphrase)); - } catch (_) { - try { - seed = mn.mnemonicToSeedSync(cosigner.replace(ELECTRUM_SEED_PREFIX, ''), electrumStandart(passphrase)); - } catch (__) { - throw new Error('Not a valid electrum mnemonic'); - } - } - return seed; - } - - /** - * @see https://github.com/bitcoin/bips/blob/master/bip-0067.mediawiki - * - * @param bufArr {Array.} - * @returns {Array.} - */ - static sortBuffers(bufArr) { - return bufArr.sort(Buffer.compare); - } - - prepareForSerialization() { - // deleting structures that cant be serialized - delete this._nodes; - } - - static isPathValid(path) { - const root = bip32.fromSeed(Buffer.alloc(32)); - try { - root.derivePath(path); - return true; - } catch (_) {} - return false; - } - - allowSend() { - return true; - } - - allowSignVerifyMessage() { - return false; - } - - async fetchUtxo() { - await super.fetchUtxo(); - // now we need to fetch txhash for each input as required by PSBT - const txhexes = await BlueElectrum.multiGetTransactionByTxid( - this.getUtxo(true).map(x => x.txid), - 50, - false, - ); - - const newUtxos = []; - for (const u of this.getUtxo(true)) { - if (txhexes[u.txid]) u.txhex = txhexes[u.txid]; - newUtxos.push(u); - } - - this._utxo = newUtxos; - } - - getID() { - const string2hash = [...this._cosigners].sort().join(',') + ';' + [...this._cosignersFingerprints].sort().join(','); - return createHash('sha256').update(string2hash).digest().toString('hex'); - } - - calculateFeeFromPsbt(psbt) { - let goesIn = 0; - const cacheUtxoAmounts = {}; - for (const inp of psbt.data.inputs) { - if (inp.witnessUtxo && inp.witnessUtxo.value) { - // segwit input - goesIn += inp.witnessUtxo.value; - } else if (inp.nonWitnessUtxo) { - // non-segwit input - // lets parse this transaction and cache how much each input was worth - const inputTx = bitcoin.Transaction.fromHex(inp.nonWitnessUtxo); - let index = 0; - for (const out of inputTx.outs) { - cacheUtxoAmounts[inputTx.getId() + ':' + index] = out.value; - index++; - } - } - } - - if (goesIn === 0) { - // means we failed to get amounts that go in previously, so lets use utxo amounts cache we've build - // from non-segwit inputs - for (const inp of psbt.txInputs) { - const cacheKey = reverse(inp.hash).toString('hex') + ':' + inp.index; - if (cacheUtxoAmounts[cacheKey]) goesIn += cacheUtxoAmounts[cacheKey]; - } - } - - let goesOut = 0; - for (const output of psbt.txOutputs) { - goesOut += output.value; - } - - return goesIn - goesOut; - } - - calculateHowManySignaturesWeHaveFromPsbt(psbt) { - let sigsHave = 0; - for (const inp of psbt.data.inputs) { - sigsHave = Math.max(sigsHave, inp.partialSig?.length || 0); - if (inp.finalScriptSig || inp.finalScriptWitness) sigsHave = this.getM(); // hacky, but it means we have enough - // He who knows that enough is enough will always have enough. Lao Tzu - } - - return sigsHave; - } - - /** - * Tries to signs passed psbt object (by reference). If there are enough signatures - tries to finalize psbt - * and returns Transaction (ready to extract hex) - * - * @param psbt {Psbt} - * @returns {{ tx: Transaction }} - */ - cosignPsbt(psbt) { - for (let cc = 0; cc < psbt.inputCount; cc++) { - for (const [cosignerIndex, cosigner] of this._cosigners.entries()) { - if (MultisigHDWallet.isXpubString(cosigner)) continue; - - let hdRoot; - if (MultisigHDWallet.isXprvString(cosigner)) { - const xprv = MultisigHDWallet.convertMultisigXprvToRegularXprv(cosigner); - hdRoot = bip32.fromBase58(xprv); - } else { - const passphrase = this._cosignersPassphrases[cosignerIndex]; - const seed = cosigner.startsWith(ELECTRUM_SEED_PREFIX) - ? MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner, passphrase) - : bip39.mnemonicToSeedSync(cosigner, passphrase); - hdRoot = bip32.fromSeed(seed); - } - - try { - psbt.signInputHD(cc, hdRoot); - } catch (_) {} // protects agains duplicate cosignings - - if (!psbt.inputHasHDKey(cc, hdRoot)) { - // failed signing as HD. probably bitcoinjs-lib could not match provided hdRoot's - // fingerprint (or path?) to the ones in psbt, which is the case of stupid Electrum desktop which can - // put bullshit paths and fingerprints in created psbt. - // lets try to find correct priv key and sign manually. - for (const derivation of psbt.data.inputs[cc].bip32Derivation || []) { - // okay, here we assume that fingerprint is irrelevant, but ending of the path is somewhat correct and - // correctly points to `/internal/index`, so we extract pubkey from our stored mnemonics+path and - // match it to the one provided in PSBT's input, and if we have a match - we are in luck! we can sign - // with this private key. - const splt = derivation.path.split('/'); - const internal = +splt[splt.length - 2]; - const index = +splt[splt.length - 1]; - - const path = - hdRoot.depth === 0 - ? this.getCustomDerivationPathForCosigner(cosignerIndex + 1) + `/${internal ? 1 : 0}/${index}` - : `${internal ? 1 : 0}/${index}`; - // ^^^ we assume that counterparty has Zpub for specified derivation path - // if hdRoot.depth !== 0 than this hdnode was recovered from xprv and it already has been set to root path - const child = hdRoot.derivePath(path); - if (psbt.inputHasPubkey(cc, child.publicKey)) { - const keyPair = ECPair.fromPrivateKey(child.privateKey); - try { - psbt.signInput(cc, keyPair); - } catch (_) {} - } - } - } - } - } - - let tx = false; - if (this.calculateHowManySignaturesWeHaveFromPsbt(psbt) >= this.getM()) { - tx = psbt.finalizeAllInputs().extractTransaction(); - } - - return { tx }; - } - - /** - * Looks up xpub cosigner by index, and repalces it with seed + passphrase - * - * @param externalIndex {number} - * @param mnemonic {string} - * @param passphrase {string} - */ - replaceCosignerXpubWithSeed(externalIndex, mnemonic, passphrase) { - const index = externalIndex - 1; - const fingerprint = this._cosignersFingerprints[index]; - if (!MultisigHDWallet.isXpubValid(this._cosigners[index])) throw new Error('This cosigner doesnt contain valid xpub'); - if (!bip39.validateMnemonic(mnemonic)) throw new Error('Not a valid mnemonic phrase'); - if (fingerprint !== MultisigHDWallet.mnemonicToFingerprint(mnemonic, passphrase)) { - throw new Error('Fingerprint of new seed doesnt match'); - } - this._cosigners[index] = mnemonic.trim(); - this._cosignersPassphrases[index] = passphrase || undefined; - } - - /** - * Looks up cosigner with seed by index, and repalces it with xpub - * - * @param externalIndex {number} - */ - replaceCosignerSeedWithXpub(externalIndex) { - const index = externalIndex - 1; - const mnemonics = this._cosigners[index]; - if (!bip39.validateMnemonic(mnemonics)) throw new Error('This cosigner doesnt contain valid xpub mnemonic phrase'); - const passphrase = this._cosignersPassphrases[index]; - const path = this._cosignersCustomPaths[index] || this._derivationPath; - const xpub = this.convertXpubToMultisignatureXpub(MultisigHDWallet.seedToXpub(mnemonics, path, passphrase)); - this._cosigners[index] = xpub; - this._cosignersPassphrases[index] = undefined; - } - - deleteCosigner(fp) { - const foundIndex = this._cosignersFingerprints.indexOf(fp); - if (foundIndex === -1) throw new Error('Cant find cosigner by fingerprint'); - - this._cosignersFingerprints = this._cosignersFingerprints.filter((el, index) => { - return index !== foundIndex; - }); - - this._cosigners = this._cosigners.filter((el, index) => { - return index !== foundIndex; - }); - - this._cosignersCustomPaths = this._cosignersCustomPaths.filter((el, index) => { - return index !== foundIndex; - }); - - this._cosignersPassphrases = this._cosignersPassphrases.filter((el, index) => { - return index !== foundIndex; - }); - - /* const newCosigners = []; - for (let c = 0; c < this._cosignersFingerprints.length; c++) { - if (c !== index) newCosigners.push(this._cosignersFingerprints[c]); - } */ - - // this._cosignersFingerprints = newCosigners; - } - - getFormat() { - if (this.isNativeSegwit()) return this.constructor.FORMAT_P2WSH; - if (this.isWrappedSegwit()) return this.constructor.FORMAT_P2SH_P2WSH; - if (this.isLegacy()) return this.constructor.FORMAT_P2SH; - - throw new Error('This should never happen'); - } - - /** - * @param fp {string} Exactly 8 chars of hex - * @return {boolean} - */ - static isFpValid(fp) { - if (fp.length !== 8) return false; - return /^[0-9A-F]{8}$/i.test(fp); - } - - /** - * Returns TRUE only for _multisignature_ xpubs as per SLIP-0132 - * (capital Z, capital Y, or just xpub) - * @see https://github.com/satoshilabs/slips/blob/master/slip-0132.md - * - * @param xpub - * @return {boolean} - */ - static isXpubForMultisig(xpub) { - return ['xpub', 'Ypub', 'Zpub'].includes(xpub.substring(0, 4)); - } - - isSegwit() { - return this.isNativeSegwit() || this.isWrappedSegwit(); - } -} diff --git a/class/wallets/multisig-hd-wallet.ts b/class/wallets/multisig-hd-wallet.ts new file mode 100644 index 00000000000..1ffe2cd0c8d --- /dev/null +++ b/class/wallets/multisig-hd-wallet.ts @@ -0,0 +1,1316 @@ +import BIP32Factory, { BIP32Interface } from 'bip32'; +import * as bip39 from 'bip39'; +import * as bitcoin from 'bitcoinjs-lib'; +import { Psbt, Transaction } from 'bitcoinjs-lib'; +import b58 from 'bs58check'; +import { CoinSelectOutput, CoinSelectReturnInput, CoinSelectTarget } from 'coinselect'; +import { sha256 } from '@noble/hashes/sha256'; +import { ECPairFactory } from 'ecpair'; +import * as mn from 'electrum-mnemonic'; + +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; +import ecc from '../../blue_modules/noble_ecc'; +import { decodeUR } from '../../blue_modules/ur'; +import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; +import { CreateTransactionResult, CreateTransactionTarget, CreateTransactionUtxo } from './types'; +import { + uint8ArrayToHex, + hexToUint8Array, + concatUint8Arrays, + uint8ArrayToString, + compareUint8Arrays, +} from '../../blue_modules/uint8array-extras'; + +const ECPair = ECPairFactory(ecc); +const bip32 = BIP32Factory(ecc); + +type SeedOpts = { + prefix: string; + passphrase?: string; +}; + +type TBip32Derivation = { + masterFingerprint: Uint8Array; + path: string; + pubkey: Uint8Array; +}[]; + +type TOutputData = + | { + bip32Derivation: TBip32Derivation; + redeemScript: Uint8Array; + } + | { + bip32Derivation: TBip32Derivation; + witnessScript: Uint8Array; + } + | { + bip32Derivation: TBip32Derivation; + redeemScript: Uint8Array; + witnessScript: Uint8Array; + }; + +const electrumSegwit = (passphrase?: string): SeedOpts => ({ + prefix: mn.PREFIXES.segwit, + ...(passphrase ? { passphrase } : {}), +}); + +const electrumStandart = (passphrase?: string): SeedOpts => ({ + prefix: mn.PREFIXES.standard, + ...(passphrase ? { passphrase } : {}), +}); + +const ELECTRUM_SEED_PREFIX = 'electrumseed:'; + +export class MultisigHDWallet extends AbstractHDElectrumWallet { + static readonly type = 'HDmultisig'; + static readonly typeReadable = 'Multisig Vault'; + // @ts-ignore: override + public readonly type = MultisigHDWallet.type; + // @ts-ignore: override + public readonly typeReadable = MultisigHDWallet.typeReadable; + + static FORMAT_P2WSH = 'p2wsh'; + static FORMAT_P2SH_P2WSH = 'p2sh-p2wsh'; + static FORMAT_P2SH_P2WSH_ALT = 'p2wsh-p2sh'; + static FORMAT_P2SH = 'p2sh'; + + static PATH_NATIVE_SEGWIT = "m/48'/0'/0'/2'"; + static PATH_WRAPPED_SEGWIT = "m/48'/0'/0'/1'"; + static PATH_LEGACY = "m/45'"; + + private _m: number = 0; // minimum required signatures so spend (m out of n) + private _cosigners: string[] = []; // array of xpubs or mnemonic seeds + private _cosignersFingerprints: string[] = []; // array of according fingerprints (if any provided) + private _cosignersCustomPaths: string[] = []; // array of according paths (if any provided) + private _cosignersPassphrases: (string | undefined)[] = []; // array of according passphrases (if any provided) + private _isNativeSegwit: boolean = false; + private _isWrappedSegwit: boolean = false; + private _isLegacy: boolean = false; + private _nodes: Record> = {}; // nodeIndex -> cosignerIndex -> BIP32Interface + public _derivationPath: string = ''; + public gap_limit: number = 20; + + isLegacy() { + return this._isLegacy; + } + + isNativeSegwit() { + return this._isNativeSegwit; + } + + isWrappedSegwit() { + return this._isWrappedSegwit; + } + + setWrappedSegwit() { + this._isWrappedSegwit = true; + } + + setNativeSegwit() { + this._isNativeSegwit = true; + } + + setLegacy() { + this._isLegacy = true; + } + + setM(m: number) { + this._m = m; + } + + /** + * @returns {number} How many minumim signatures required to authorize a spend + */ + getM(): number { + return this._m; + } + + /** + * @returns {number} Total count of cosigners + */ + getN(): number { + return this._cosigners.length; + } + + setDerivationPath(path: string) { + this._derivationPath = path; + switch (this._derivationPath) { + case "m/48'/0'/0'/2'": + this._isNativeSegwit = true; + break; + case "m/48'/0'/0'/1'": + this._isWrappedSegwit = true; + break; + case "m/45'": + this._isLegacy = true; + break; + case "m/44'": + this._isLegacy = true; + break; + } + } + + getCustomDerivationPathForCosigner(index: number): string | false { + if (index === 0) throw new Error('cosigners indexation starts from 1'); + if (index > this.getN()) return false; + return this._cosignersCustomPaths[index - 1] || this.getDerivationPath()!; + } + + getCosigner(index: number) { + if (index === 0) throw new Error('cosigners indexation starts from 1'); + return this._cosigners[index - 1]; + } + + getFingerprint(index: number) { + if (index === 0) throw new Error('cosigners fingerprints indexation starts from 1'); + return this._cosignersFingerprints[index - 1]; + } + + getCosignerForFingerprint(fp: string) { + const index = this._cosignersFingerprints.indexOf(fp); + return this._cosigners[index]; + } + + getCosignerPassphrase(index: number) { + if (index === 0) throw new Error('cosigners indexation starts from 1'); + return this._cosignersPassphrases[index - 1]; + } + + static isXpubValid(key: string): boolean { + let xpub; + + try { + const tempWallet = new MultisigHDWallet(); + xpub = tempWallet._zpubToXpub(key); + bip32.fromBase58(xpub); + return true; + } catch (_) {} + + return false; + } + + static isXprvValid(xprv: string): boolean { + try { + xprv = MultisigHDWallet.convertMultisigXprvToRegularXprv(xprv); + bip32.fromBase58(xprv); + return true; + } catch (_) { + return false; + } + } + + /** + * + * @param key {string} Either xpub or mnemonic phrase + * @param fingerprint {string} Fingerprint for cosigner that is added as xpub + * @param path {string} Custom path (if any) for cosigner that is added as mnemonics + * @param passphrase {string} BIP38 Passphrase (if any) + */ + addCosigner(key: string, fingerprint?: string, path?: string, passphrase?: string) { + if (MultisigHDWallet.isXpubString(key) && !fingerprint) { + throw new Error('fingerprint is required when adding cosigner as xpub (watch-only)'); + } + + if (path && !MultisigHDWallet.isPathValid(path)) { + throw new Error('path is not valid'); + } + + if (MultisigHDWallet.isXprvString(key)) { + // nop, but probably should validate xprv + } else if (MultisigHDWallet.isXpubString(key)) { + // nop, just validate + if (!MultisigHDWallet.isXpubValid(key)) throw new Error('Not a valid xpub: ' + key); + } else if (key.startsWith(ELECTRUM_SEED_PREFIX) && fingerprint && path) { + // its an electrum seed + const mnemonic = key.replace(ELECTRUM_SEED_PREFIX, ''); + try { + mn.mnemonicToSeedSync(mnemonic, electrumStandart(passphrase)); + this.setLegacy(); + } catch (_) { + try { + mn.mnemonicToSeedSync(mnemonic, electrumSegwit(passphrase)); + this.setNativeSegwit(); + } catch (__) { + throw new Error('Not a valid electrum seed'); + } + } + } else { + // mnemonics. lets derive fingerprint (if it wasnt provided) + if (!bip39.validateMnemonic(key)) throw new Error('Not a valid mnemonic phrase'); + fingerprint = fingerprint || MultisigHDWallet.mnemonicToFingerprint(key, passphrase); + } + + if (fingerprint && this._cosignersFingerprints.indexOf(fingerprint.toUpperCase()) !== -1 && fingerprint !== '00000000') { + // 00000000 is a special case, means we have no idea what the FP is but its okay + throw new Error('Duplicate fingerprint'); + } + + const index = this._cosigners.length; + this._cosigners[index] = key; + if (fingerprint) this._cosignersFingerprints[index] = fingerprint.toUpperCase(); + if (path) this._cosignersCustomPaths[index] = path; + if (passphrase) this._cosignersPassphrases[index] = passphrase; + } + + static convertMultisigXprvToRegularXprv(Zprv: string) { + let data = b58.decode(Zprv); + data = data.slice(4); + return b58.encode(concatUint8Arrays([hexToUint8Array('0488ade4'), data])); + } + + static convertXprvToXpub(xprv: string) { + const restored = bip32.fromBase58(MultisigHDWallet.convertMultisigXprvToRegularXprv(xprv)); + return restored.neutered().toBase58(); + } + + /** + * Stored cosigner can be EITHER xpub (or Zpub or smth), OR mnemonic phrase. This method converts it to xpub + * + * @param index {number} + * @returns {string} xpub + * @private + */ + protected _getXpubFromCosignerIndex(index: number) { + if (!this._cosigners || !this._cosigners[index]) { + throw new Error('Invalid cosigner index or cosigners not initialized'); + } + let cosigner: string = this._cosigners[index]; + if (MultisigHDWallet.isXprvString(cosigner)) cosigner = MultisigHDWallet.convertXprvToXpub(cosigner); + let xpub = cosigner; + if (!MultisigHDWallet.isXpubString(cosigner)) { + xpub = MultisigHDWallet.seedToXpub( + cosigner, + this._cosignersCustomPaths[index] || this._derivationPath, + this._cosignersPassphrases[index], + ); + } + return this._zpubToXpub(xpub); + } + + _getExternalAddressByIndex(index: number) { + if (!this._m) throw new Error('m is not set'); + index = +index; + if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit + + const address = this._getAddressFromNode(0, index); + this.external_addresses_cache[index] = address; + return address; + } + + _getAddressFromNode(nodeIndex: number, index: number) { + this._nodes = this._nodes || {}; + const pubkeys = []; + for (const [cosignerIndex] of this._cosigners.entries()) { + this._nodes[nodeIndex] = this._nodes[nodeIndex] || {}; + let _node; + + if (!this._nodes[nodeIndex][cosignerIndex]) { + const xpub = this._getXpubFromCosignerIndex(cosignerIndex); + const hdNode = bip32.fromBase58(xpub); + _node = hdNode.derive(nodeIndex); + this._nodes[nodeIndex][cosignerIndex] = _node; + } else { + _node = this._nodes[nodeIndex][cosignerIndex]; + } + + pubkeys.push(_node.derive(index).publicKey); + } + + if (this.isWrappedSegwit()) { + const { address } = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wsh({ + redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), + }), + }); + if (!address) { + throw new Error('Internal error: could not make p2sh address'); + } + + return address; + } else if (this.isNativeSegwit()) { + const { address } = bitcoin.payments.p2wsh({ + redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), + }); + if (!address) { + throw new Error('Internal error: could not make p2wsh address'); + } + + return address; + } else if (this.isLegacy()) { + const { address } = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), + }); + if (!address) { + throw new Error('Internal error: could not make p2sh address'); + } + + return address; + } else { + throw new Error('Dont know how to make address'); + } + } + + _getInternalAddressByIndex(index: number) { + if (!this._m) throw new Error('m is not set'); + index = +index; + if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit + + const address = this._getAddressFromNode(1, index); + this.internal_addresses_cache[index] = address; + return address; + } + + static seedToXpub(mnemonic: string, path: string, passphrase?: string): string { + let seed; + if (mnemonic.startsWith(ELECTRUM_SEED_PREFIX)) { + seed = MultisigHDWallet.convertElectrumMnemonicToSeed(mnemonic, passphrase); + } else { + seed = bip39.mnemonicToSeedSync(mnemonic, passphrase); + } + + const root = bip32.fromSeed(seed); + const child = root.derivePath(path).neutered(); + return child.toBase58(); + } + + /** + * Returns xpub with correct prefix accodting to this objects set derivation path, for example 'Zpub' (with + * capital Z) for bech32 multisig + * @see https://github.com/satoshilabs/slips/blob/master/slip-0132.md + * + * @param xpub {string} Any kind of xpub, including zpub etc since we are only swapping the prefix bytes + * @returns {string} + */ + convertXpubToMultisignatureXpub(xpub: string): string { + let data = b58.decode(xpub); + data = data.slice(4); + if (this.isNativeSegwit()) { + return b58.encode(concatUint8Arrays([hexToUint8Array('02aa7ed3'), data])); + } else if (this.isWrappedSegwit()) { + return b58.encode(concatUint8Arrays([hexToUint8Array('0295b43f'), data])); + } + + return xpub; + } + + convertXprvToMultisignatureXprv(xpub: string): string { + let data = b58.decode(xpub); + data = data.slice(4); + if (this.isNativeSegwit()) { + return b58.encode(concatUint8Arrays([hexToUint8Array('02aa7a99'), data])); + } else if (this.isWrappedSegwit()) { + return b58.encode(concatUint8Arrays([hexToUint8Array('0295b005'), data])); + } + + return xpub; + } + + static isXpubString(xpub: string): boolean { + return ['xpub', 'ypub', 'zpub', 'Ypub', 'Zpub'].includes(xpub.substring(0, 4)); + } + + static isXprvString(xpub: string): boolean { + return ['xprv', 'yprv', 'zprv', 'Yprv', 'Zprv'].includes(xpub.substring(0, 4)); + } + + /** + * Converts fingerprint that is stored as a deciman number to hex string (all caps) + * + * @param xfp {number} For example 64392470 + * @returns {string} For example 168DD603 + */ + static ckccXfp2fingerprint(xfp: string | number): string { + let masterFingerprintHex = Number(xfp).toString(16); + while (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte + + // poor man's little-endian conversion: + // ¯\_(ツ)_/¯ + return ( + masterFingerprintHex[6] + + masterFingerprintHex[7] + + masterFingerprintHex[4] + + masterFingerprintHex[5] + + masterFingerprintHex[2] + + masterFingerprintHex[3] + + masterFingerprintHex[0] + + masterFingerprintHex[1] + ).toUpperCase(); + } + + getXpub() { + return this.getSecret(true); + } + + getSecret(coordinationSetup = false) { + let ret = '# BlueWallet Multisig setup file\n'; + if (coordinationSetup) ret += '# this file contains only public keys and is safe to\n# distribute among cosigners\n'; + if (!coordinationSetup) ret += '# this file may contain private information\n'; + ret += '#\n'; + ret += 'Name: ' + this.getLabel() + '\n'; + ret += 'Policy: ' + this.getM() + ' of ' + this.getN() + '\n'; + + let hasCustomPaths = 0; + const customPaths: Record = {}; + for (let index = 0; index < this.getN(); index++) { + if (this._cosignersCustomPaths[index]) hasCustomPaths++; + if (this._cosignersCustomPaths[index]) customPaths[this._cosignersCustomPaths[index]] = 1; + } + + let printedGlobalDerivation = false; + const derivationPath = this.getDerivationPath(); + if (derivationPath) customPaths[derivationPath] = 1; + if (Object.keys(customPaths).length === 1) { + // we have exactly one path, for everyone. lets just print it + for (const path of Object.keys(customPaths)) { + ret += 'Derivation: ' + path + '\n'; + printedGlobalDerivation = true; + } + } + + if (hasCustomPaths !== this.getN() && !printedGlobalDerivation) { + printedGlobalDerivation = true; + ret += 'Derivation: ' + this.getDerivationPath() + '\n'; + } + + if (this.isNativeSegwit()) { + ret += 'Format: P2WSH\n'; + } else if (this.isWrappedSegwit()) { + ret += 'Format: P2SH-P2WSH\n'; + } else if (this.isLegacy()) { + ret += 'Format: P2SH\n'; + } else { + ret += 'Format: unknown\n'; + } + ret += '\n'; + + for (let index = 0; index < this.getN(); index++) { + if ( + this._cosignersCustomPaths[index] && + ((printedGlobalDerivation && this._cosignersCustomPaths[index] !== this.getDerivationPath()) || !printedGlobalDerivation) + ) { + ret += '# derivation: ' + this._cosignersCustomPaths[index] + '\n'; + // if we printed global derivation and this cosigned _has_ derivation and its different from global - we print it ; + // or we print it if cosigner _has_ some derivation set and we did not print global + } + if (MultisigHDWallet.isXpubString(this._cosigners[index])) { + ret += this._cosignersFingerprints[index] + ': ' + this._cosigners[index] + '\n'; + } else { + if (coordinationSetup) { + const xpub = this.convertXpubToMultisignatureXpub( + MultisigHDWallet.seedToXpub( + this._cosigners[index], + this._cosignersCustomPaths[index] || this._derivationPath, + this._cosignersPassphrases[index], + ), + ); + const fingerprint = MultisigHDWallet.mnemonicToFingerprint(this._cosigners[index], this._cosignersPassphrases[index]); + ret += fingerprint + ': ' + xpub + '\n'; + } else { + ret += 'seed: ' + this._cosigners[index]; + if (this._cosignersPassphrases[index]) ret += ' - ' + this._cosignersPassphrases[index]; + ret += '\n# warning! sensitive information, do not disclose ^^^ \n'; + } + } + + ret += '\n'; + } + + return ret; + } + + setSecret(secret: string) { + if (secret.toUpperCase().startsWith('UR:BYTES')) { + const decoded = decodeUR([secret]) as string; + const b = hexToUint8Array(decoded); + secret = uint8ArrayToString(b); + } + + // is it Coldcard json file? + let json; + try { + json = JSON.parse(secret); + } catch (_) {} + if (json && json.xfp && json.p2wsh_deriv && json.p2wsh) { + this.addCosigner(json.p2wsh, json.xfp); // technically we dont need deriv (json.p2wsh_deriv), since cosigner is already an xpub + return this; + } + + // is it electrum json? + if (json && json.wallet_type && json.wallet_type !== 'standard') { + const mofn = json.wallet_type.split('of'); + this.setM(parseInt(mofn[0].trim(), 10)); + const n = parseInt(mofn[1].trim(), 10); + for (let c = 1; c <= n; c++) { + const cosignerData = json['x' + c + '/']; + if (cosignerData) { + const fingerprint = + (cosignerData.ckcc_xfp + ? MultisigHDWallet.ckccXfp2fingerprint(cosignerData.ckcc_xfp) + : cosignerData.root_fingerprint?.toUpperCase()) || '00000000'; + if (cosignerData.seed) { + this.addCosigner(ELECTRUM_SEED_PREFIX + cosignerData.seed, fingerprint, cosignerData.derivation, cosignerData.passphrase); + } else if (cosignerData.xprv && MultisigHDWallet.isXprvValid(cosignerData.xprv)) { + this.addCosigner(cosignerData.xprv, fingerprint, cosignerData.derivation); + } else { + this.addCosigner(cosignerData.xpub, fingerprint, cosignerData.derivation); + } + } + + if (cosignerData?.xpub?.startsWith('Zpub')) this.setNativeSegwit(); + if (cosignerData?.xpub?.startsWith('Ypub')) this.setWrappedSegwit(); + if (cosignerData?.xpub?.startsWith('xpub')) this.setLegacy(); + } + } + + // coldcard & cobo txt format: + let customPathForCurrentCosigner: string | undefined; + for (const line of secret.split('\n')) { + const [key, value] = line.split(':'); + + switch (key) { + case 'Name': + this.setLabel(value.trim()); + break; + + case 'Policy': + this.setM(parseInt(value.trim().split('of')[0].trim(), 10)); + break; + + case 'Derivation': + this.setDerivationPath(value.trim()); + break; + + case 'Format': + switch (value.trim()) { + case MultisigHDWallet.FORMAT_P2WSH.toUpperCase(): + this.setNativeSegwit(); + break; + case MultisigHDWallet.FORMAT_P2SH_P2WSH.toUpperCase(): + case MultisigHDWallet.FORMAT_P2SH_P2WSH_ALT.toUpperCase(): + this.setWrappedSegwit(); + break; + case MultisigHDWallet.FORMAT_P2SH.toUpperCase(): + this.setLegacy(); + break; + } + break; + + default: + if (key && value && MultisigHDWallet.isXpubString(value.trim())) { + this.addCosigner(value.trim(), key, customPathForCurrentCosigner); + } else if (key.replace('#', '').trim() === 'derivation') { + customPathForCurrentCosigner = value.trim(); + } else if (key === 'seed') { + const [seed, passphrase] = value.split(' - '); + this.addCosigner(seed.trim(), undefined, customPathForCurrentCosigner, passphrase); + } + break; + } + } + + // is it wallet descriptor? + // @see https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md + // @see https://github.com/Fonta1n3/FullyNoded/blob/master/Docs/Wallets/Wallet-Export-Spec.md + if (!json && secret.indexOf('sortedmulti(')) { + // provided secret was NOT json but plain wallet descriptor text. lets mock json + json = { descriptor: secret, label: 'Multisig vault' }; + } + if (secret.indexOf('sortedmulti(') !== -1 && json.descriptor) { + if (json.label) this.setLabel(json.label); + if (json.descriptor.includes('sh(wsh(')) { + this.setWrappedSegwit(); + } else if (json.descriptor.includes('wsh(')) { + this.setNativeSegwit(); + } else if (json.descriptor.includes('sh(')) { + this.setLegacy(); + } + + const s2 = json.descriptor.substr(json.descriptor.indexOf('sortedmulti(') + 12); + const s3 = s2.split(','); + const M = parseInt(s3[0], 10); + if (M) this.setM(M); + + for (let c = 1; c < s3.length; c++) { + const re = /\[([^\]]+)\](.*)/; + const m = s3[c].match(re); + if (m && m.length === 3) { + const hexFingerprint = m[1].split('/')[0]; + + let path = 'm/' + m[1].split('/').slice(1).join('/').replace(/[h]/g, "'"); + if (path === 'm/') { + // not considered valid by Bip32 lib + path = 'm/0'; + } + let xpub = m[2]; + if (xpub.indexOf('/') !== -1) { + xpub = xpub.substr(0, xpub.indexOf('/')); + } + if (xpub.indexOf(')') !== -1) { + xpub = xpub.substr(0, xpub.indexOf(')')); + } + + this.addCosigner(xpub, hexFingerprint.toUpperCase(), path); + } + } + + if (this.getN() === 0) { + // handling a case when smth went wrong and we didnt parse any cosigners, probably because + // string is a bit non-standard, deesnt have chars like '[' + for (let c = 1; c < s3.length; c++) { + const hexFingerprint = s3[c].split('/')[0]; + let indexOfXpub = s3[c].indexOf('xpub'); + if (indexOfXpub === -1) { + // just for any case + indexOfXpub = s3[c].indexOf('ypub'); + } + if (indexOfXpub === -1) { + // just for any case + indexOfXpub = s3[c].indexOf('zpub'); + } + if (indexOfXpub === -1) { + throw new Error('Could not parse cosigner in a descriptor'); + } + + const xpub = s3[c].substring(indexOfXpub).replaceAll(')', ''); + const path = 'm' + s3[c].substring(hexFingerprint.length, indexOfXpub); + + this.addCosigner(xpub, hexFingerprint.toUpperCase(), path); + } + } + } + + // is it caravan? + if (json && json.network === 'mainnet' && json.quorum) { + this.setM(+json.quorum.requiredSigners); + if (json.name) this.setLabel(json.name); + + switch (json.addressType.toLowerCase()) { + case MultisigHDWallet.FORMAT_P2SH: + this.setLegacy(); + break; + case MultisigHDWallet.FORMAT_P2SH_P2WSH: + case MultisigHDWallet.FORMAT_P2SH_P2WSH_ALT: + this.setWrappedSegwit(); + break; + case MultisigHDWallet.FORMAT_P2WSH: + default: + this.setNativeSegwit(); + break; + } + + for (const pk of json.extendedPublicKeys) { + const path = MultisigHDWallet.isPathValid(json.bip32Path) ? json.bip32Path : "m/1'"; + this.addCosigner(pk.xpub, pk.xfp ?? '00000000', path); + } + } + + if (!this.getLabel()) this.setLabel('Multisig vault'); + + return this; + } + + _getDerivationPathByAddressWithCustomPath(address: string, customPathPrefix: string | undefined) { + const path = customPathPrefix || this._derivationPath; + for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { + if (this._getExternalAddressByIndex(c) === address) return path + '/0/' + c; + } + for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { + if (this._getInternalAddressByIndex(c) === address) return path + '/1/' + c; + } + + return false; + } + + _getWifForAddress(address: string): string { + // @ts-ignore not applicable in multisig + return false; + } + + _getPubkeyByAddress(address: string): false | Buffer { + throw new Error('Not applicable in multisig'); + } + + _getDerivationPathByAddress(address: string): string { + throw new Error('Not applicable in multisig'); + } + + _addPsbtInput(psbt: Psbt, input: CoinSelectReturnInput, sequence: number, masterFingerprintBuffer?: Uint8Array) { + const bip32Derivation = []; // array per each pubkey thats gona be used + const pubkeys = []; + for (const [cosignerIndex] of this._cosigners.entries()) { + if (!input.address) { + throw new Error('Could not find address in input'); + } + const path = this._getDerivationPathByAddressWithCustomPath( + input.address, + this._cosignersCustomPaths[cosignerIndex] || this._derivationPath, + ); + // ^^ path resembles _custom path_, if provided by user during setup, otherwise default path for wallet type gona be used + const masterFingerprint = hexToUint8Array(this._cosignersFingerprints[cosignerIndex]); + + if (!path) { + throw new Error('Could not find derivation path for address ' + input.address); + } + + const xpub = this._getXpubFromCosignerIndex(cosignerIndex); + const hdNode0 = bip32.fromBase58(xpub); + const splt = path.split('/'); + const internal = +splt[splt.length - 2]; + const index = +splt[splt.length - 1]; + const _node0 = hdNode0.derive(internal); + const pubkey = _node0.derive(index).publicKey; + pubkeys.push(pubkey); + + bip32Derivation.push({ + masterFingerprint, + path, + pubkey, + }); + } + + if (!input.txhex) { + throw new Error('Electrum server didnt provide txhex to properly create PSBT transaction'); + } + + if (this.isNativeSegwit()) { + const p2wsh = bitcoin.payments.p2wsh({ + redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), + }); + if (!p2wsh.redeem || !p2wsh.output) { + throw new Error('Could not create p2wsh output'); + } + const witnessScript = p2wsh.redeem.output; + + psbt.addInput({ + hash: input.txid, + index: input.vout, + sequence, + bip32Derivation, + witnessUtxo: { + script: p2wsh.output, + value: BigInt(input.value), + }, + witnessScript, + // hw wallets now require passing the whole previous tx as Buffer, as if it was non-segwit input, to mitigate + // some hw wallets attack vector + nonWitnessUtxo: hexToUint8Array(input.txhex), + }); + } else if (this.isWrappedSegwit()) { + const p2shP2wsh = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wsh({ + redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), + }), + }); + if (!p2shP2wsh?.redeem?.redeem?.output || !p2shP2wsh?.redeem?.output || !p2shP2wsh.output) { + throw new Error('Could not create p2sh-p2wsh output'); + } + + const witnessScript = p2shP2wsh.redeem.redeem.output; + const redeemScript = p2shP2wsh.redeem.output; + + psbt.addInput({ + hash: input.txid, + index: input.vout, + sequence, + bip32Derivation, + witnessUtxo: { + script: p2shP2wsh.output, + value: BigInt(input.value), + }, + witnessScript, + redeemScript, + // hw wallets now require passing the whole previous tx as Buffer, as if it was non-segwit input, to mitigate + // some hw wallets attack vector + nonWitnessUtxo: hexToUint8Array(input.txhex), + }); + } else if (this.isLegacy()) { + const p2sh = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), + }); + if (!p2sh?.redeem?.output) { + throw new Error('Could not create p2sh output'); + } + const redeemScript = p2sh.redeem.output; + psbt.addInput({ + hash: input.txid, + index: input.vout, + sequence, + bip32Derivation, + redeemScript, + nonWitnessUtxo: hexToUint8Array(input.txhex), + }); + } else { + throw new Error('Dont know how to add input'); + } + + return psbt; + } + + _getOutputDataForChange(address: string): TOutputData { + const bip32Derivation: TBip32Derivation = []; // array per each pubkey thats gona be used + const pubkeys = []; + for (const [cosignerIndex] of this._cosigners.entries()) { + const path = this._getDerivationPathByAddressWithCustomPath( + address, + this._cosignersCustomPaths[cosignerIndex] || this._derivationPath, + ); + // ^^ path resembles _custom path_, if provided by user during setup, otherwise default path for wallet type gona be used + const masterFingerprint = hexToUint8Array(this._cosignersFingerprints[cosignerIndex]); + + if (!path) { + throw new Error('Could not find derivation path for address ' + address); + } + + const xpub = this._getXpubFromCosignerIndex(cosignerIndex); + const hdNode0 = bip32.fromBase58(xpub); + const splt = path.split('/'); + const internal = +splt[splt.length - 2]; + const index = +splt[splt.length - 1]; + const _node0 = hdNode0.derive(internal); + const pubkey = _node0.derive(index).publicKey; + pubkeys.push(pubkey); + + bip32Derivation.push({ + masterFingerprint, + path, + pubkey, + }); + } + + if (this.isLegacy()) { + const p2sh = bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }); + if (!p2sh.output) { + throw new Error('Could not create redeemScript'); + } + return { + bip32Derivation, + redeemScript: p2sh.output, + }; + } + + if (this.isWrappedSegwit()) { + const p2shP2wsh = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wsh({ + redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), + }), + }); + const witnessScript = p2shP2wsh?.redeem?.redeem?.output; + const redeemScript = p2shP2wsh?.redeem?.output; + if (!witnessScript || !redeemScript) { + throw new Error('Could not create redeemScript or witnessScript'); + } + return { + bip32Derivation, + witnessScript, + redeemScript, + }; + } + + if (this.isNativeSegwit()) { + // not needed by coldcard, apparently..? + const p2wsh = bitcoin.payments.p2wsh({ + redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }), + }); + const witnessScript = p2wsh?.redeem?.output; + if (!witnessScript) { + throw new Error('Could not create witnessScript'); + } + return { + bip32Derivation, + witnessScript, + }; + } + + throw new Error('dont know how to add change output'); + } + + howManySignaturesCanWeMake() { + let howManyPrivKeysWeGot = 0; + for (const cosigner of this._cosigners) { + if (!MultisigHDWallet.isXpubString(cosigner) && !MultisigHDWallet.isXprvString(cosigner)) howManyPrivKeysWeGot++; + } + + return howManyPrivKeysWeGot; + } + + coinselect( + utxos: CreateTransactionUtxo[], + targets: CreateTransactionTarget[], + feeRate: number, + ): { inputs: CoinSelectReturnInput[]; outputs: CoinSelectOutput[]; fee: number } { + const _utxos = JSON.parse(JSON.stringify(utxos)) as CreateTransactionUtxo[]; + + // overriding script length for proper vbytes calculation + for (const u of _utxos) { + if (u.script?.length) { + continue; + } + + if (this.isNativeSegwit()) { + u.script = { + length: Math.ceil((8 + this.getM() * 74 + this.getN() * 34) / 4), + }; + } else if (this.isWrappedSegwit()) { + u.script = { + length: 35 + Math.ceil((8 + this.getM() * 74 + this.getN() * 34) / 4), + }; + } else { + u.script = { + length: 2 + this.getM() * 74 + this.getN() * 34, + }; + } + } + + return super.coinselect(_utxos, targets, feeRate); + } + + /** + * @inheritDoc + */ + createTransaction( + utxos: CreateTransactionUtxo[], + targets: CoinSelectTarget[], + feeRate: number, + changeAddress: string, + sequence: number, + skipSigning = false, + masterFingerprint: number, + ): CreateTransactionResult { + if (targets.length === 0) throw new Error('No destination provided'); + if (this.howManySignaturesCanWeMake() === 0) skipSigning = true; + + const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate); + sequence = sequence || AbstractHDElectrumWallet.defaultRBFSequence; + + let psbt = new bitcoin.Psbt(); + + let c = 0; + inputs.forEach(input => { + c++; + psbt = this._addPsbtInput(psbt, input, sequence); + }); + + outputs.forEach(output => { + // if output has no address - this is change output + let change = false; + let address: string | undefined = output.address; + if (!address) { + change = true; + output.address = changeAddress; + address = changeAddress; + } + + let outputData: Parameters[0] = { + address, + value: BigInt(output.value), + }; + + if (change) { + outputData = { + ...outputData, + ...this._getOutputDataForChange(address), + }; + } + + psbt.addOutput(outputData); + }); + + if (!skipSigning) { + for (let cc = 0; cc < c; cc++) { + let signaturesMade = 0; + for (const [cosignerIndex, cosigner] of this._cosigners.entries()) { + if (MultisigHDWallet.isXpubString(cosigner)) continue; + // ok this is a mnemonic, lets try to sign + if (signaturesMade >= this.getM()) { + // dont sign more than we need, otherwise there will be "Too many signatures" error + continue; + } + const passphrase = this._cosignersPassphrases[cosignerIndex]; + let seed = bip39.mnemonicToSeedSync(cosigner, passphrase); + if (cosigner.startsWith(ELECTRUM_SEED_PREFIX)) { + seed = MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner, passphrase); + } + + const hdRoot = bip32.fromSeed(seed); + psbt.signInputHD(cc, hdRoot); + signaturesMade++; + } + } + } + + let tx; + if (!skipSigning && this.howManySignaturesCanWeMake() >= this.getM()) { + tx = psbt.finalizeAllInputs().extractTransaction(); + } + return { tx, inputs, outputs, fee, psbt }; + } + + static convertElectrumMnemonicToSeed(cosigner: string, passphrase?: string) { + let seed; + try { + seed = mn.mnemonicToSeedSync(cosigner.replace(ELECTRUM_SEED_PREFIX, ''), electrumSegwit(passphrase)); + } catch (_) { + try { + seed = mn.mnemonicToSeedSync(cosigner.replace(ELECTRUM_SEED_PREFIX, ''), electrumStandart(passphrase)); + } catch (__) { + throw new Error('Not a valid electrum mnemonic'); + } + } + return seed; + } + + /** + * @see https://github.com/bitcoin/bips/blob/master/bip-0067.mediawiki + */ + static sortBuffers(bufArr: Uint8Array[]): Uint8Array[] { + return bufArr.sort(compareUint8Arrays); + } + + prepareForSerialization() { + // deleting structures that cant be serialized + // @ts-ignore I dont want to make it optional + delete this._nodes; + } + + static isPathValid(path: string): boolean { + const root = bip32.fromSeed(new Uint8Array(32)); + try { + root.derivePath(path); + return true; + } catch (_) {} + return false; + } + + allowSend() { + return true; + } + + allowSignVerifyMessage() { + return false; + } + + async fetchUtxo() { + await super.fetchUtxo(); + // now we need to fetch txhash for each input as required by PSBT + const txhexes = await BlueElectrum.multiGetTransactionByTxid( + this.getUtxo(true).map(x => x.txid), + false, + ); + + const newUtxos = []; + for (const u of this.getUtxo(true)) { + if (txhexes[u.txid]) u.txhex = txhexes[u.txid]; + newUtxos.push(u); + } + + this._utxo = newUtxos; + } + + getID() { + const string2hash = [...this._cosigners].sort().join(',') + ';' + [...this._cosignersFingerprints].sort().join(','); + return uint8ArrayToHex(sha256(string2hash)); + } + + calculateFeeFromPsbt(psbt: Psbt) { + let goesIn = 0; + const cacheUtxoAmounts: { [key: string]: number } = {}; + for (const inp of psbt.data.inputs) { + if (inp.witnessUtxo && inp.witnessUtxo.value) { + // segwit input + goesIn += Number(inp.witnessUtxo.value); + } else if (inp.nonWitnessUtxo) { + // non-segwit input + // lets parse this transaction and cache how much each input was worth + const inputTx = bitcoin.Transaction.fromBuffer(inp.nonWitnessUtxo); + let index = 0; + for (const out of inputTx.outs) { + cacheUtxoAmounts[inputTx.getId() + ':' + index] = Number(out.value); + index++; + } + } + } + + if (goesIn === 0) { + // means we failed to get amounts that go in previously, so lets use utxo amounts cache we've build + // from non-segwit inputs + for (const inp of psbt.txInputs) { + const cacheKey = uint8ArrayToHex(new Uint8Array(inp.hash).reverse()) + ':' + inp.index; + if (cacheUtxoAmounts[cacheKey]) goesIn += cacheUtxoAmounts[cacheKey]; + } + } + + let goesOut = 0; + for (const output of psbt.txOutputs) { + goesOut += Number(output.value); + } + + return goesIn - goesOut; + } + + calculateHowManySignaturesWeHaveFromPsbt(psbt: Psbt) { + let sigsHave = 0; + for (const inp of psbt.data.inputs) { + sigsHave = Math.max(sigsHave, inp.partialSig?.length || 0); + if (inp.finalScriptSig || inp.finalScriptWitness) sigsHave = this.getM(); // hacky, but it means we have enough + // He who knows that enough is enough will always have enough. Lao Tzu + } + + return sigsHave; + } + + /** + * Tries to signs passed psbt object (by reference). If there are enough signatures - tries to finalize psbt + * and returns Transaction (ready to extract hex) + */ + cosignPsbt(psbt: Psbt): { tx: Transaction | false } { + for (let cc = 0; cc < psbt.inputCount; cc++) { + for (const [cosignerIndex, cosigner] of this._cosigners.entries()) { + if (MultisigHDWallet.isXpubString(cosigner)) continue; + + let hdRoot; + if (MultisigHDWallet.isXprvString(cosigner)) { + const xprv = MultisigHDWallet.convertMultisigXprvToRegularXprv(cosigner); + hdRoot = bip32.fromBase58(xprv); + } else { + const passphrase = this._cosignersPassphrases[cosignerIndex]; + const seed = cosigner.startsWith(ELECTRUM_SEED_PREFIX) + ? MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner, passphrase) + : bip39.mnemonicToSeedSync(cosigner, passphrase); + hdRoot = bip32.fromSeed(seed); + } + + try { + psbt.signInputHD(cc, hdRoot); + } catch (_) {} // protects agains duplicate cosignings + + if (!psbt.inputHasHDKey(cc, hdRoot)) { + // failed signing as HD. probably bitcoinjs-lib could not match provided hdRoot's + // fingerprint (or path?) to the ones in psbt, which is the case of stupid Electrum desktop which can + // put bullshit paths and fingerprints in created psbt. + // lets try to find correct priv key and sign manually. + for (const derivation of psbt.data.inputs[cc].bip32Derivation || []) { + // okay, here we assume that fingerprint is irrelevant, but ending of the path is somewhat correct and + // correctly points to `/internal/index`, so we extract pubkey from our stored mnemonics+path and + // match it to the one provided in PSBT's input, and if we have a match - we are in luck! we can sign + // with this private key. + const splt = derivation.path.split('/'); + const internal = +splt[splt.length - 2]; + const index = +splt[splt.length - 1]; + + const path = + hdRoot.depth === 0 + ? this.getCustomDerivationPathForCosigner(cosignerIndex + 1) + `/${internal ? 1 : 0}/${index}` + : `${internal ? 1 : 0}/${index}`; + // ^^^ we assume that counterparty has Zpub for specified derivation path + // if hdRoot.depth !== 0 than this hdnode was recovered from xprv and it already has been set to root path + const child = hdRoot.derivePath(path); + if (child.privateKey && psbt.inputHasPubkey(cc, child.publicKey)) { + const keyPair = ECPair.fromPrivateKey(child.privateKey); + try { + psbt.signInput(cc, keyPair); + } catch (_) {} + } + } + } + } + } + + if (this.calculateHowManySignaturesWeHaveFromPsbt(psbt) >= this.getM()) { + const tx = psbt.finalizeAllInputs().extractTransaction(); + return { tx }; + } + + return { tx: false }; + } + + /** + * Looks up xpub cosigner by index, and repalces it with seed + passphrase + */ + replaceCosignerXpubWithSeed(externalIndex: number, mnemonic: string, passphrase?: string) { + const index = externalIndex - 1; + const fingerprint = this._cosignersFingerprints[index]; + if (!MultisigHDWallet.isXpubValid(this._cosigners[index])) throw new Error('This cosigner doesnt contain valid xpub'); + if (!bip39.validateMnemonic(mnemonic)) throw new Error('Not a valid mnemonic phrase'); + if (fingerprint !== MultisigHDWallet.mnemonicToFingerprint(mnemonic, passphrase)) { + throw new Error('Fingerprint of new seed doesnt match'); + } + this._cosigners[index] = mnemonic.trim(); + this._cosignersPassphrases[index] = passphrase || undefined; + } + + /** + * Looks up cosigner with seed by index, and repalces it with xpub + */ + replaceCosignerSeedWithXpub(externalIndex: number) { + const index = externalIndex - 1; + const mnemonics = this._cosigners[index]; + if (!bip39.validateMnemonic(mnemonics)) throw new Error('This cosigner doesnt contain valid xpub mnemonic phrase'); + const passphrase = this._cosignersPassphrases[index]; + const path = this._cosignersCustomPaths[index] || this._derivationPath; + const xpub = this.convertXpubToMultisignatureXpub(MultisigHDWallet.seedToXpub(mnemonics, path, passphrase)); + this._cosigners[index] = xpub; + this._cosignersPassphrases[index] = undefined; + } + + deleteCosigner(fp: string) { + const foundIndex = this._cosignersFingerprints.indexOf(fp); + if (foundIndex === -1) throw new Error('Cant find cosigner by fingerprint'); + + this._cosignersFingerprints = this._cosignersFingerprints.filter((el, index) => { + return index !== foundIndex; + }); + + this._cosigners = this._cosigners.filter((el, index) => { + return index !== foundIndex; + }); + + this._cosignersCustomPaths = this._cosignersCustomPaths.filter((el, index) => { + return index !== foundIndex; + }); + + this._cosignersPassphrases = this._cosignersPassphrases.filter((el, index) => { + return index !== foundIndex; + }); + + /* const newCosigners = []; + for (let c = 0; c < this._cosignersFingerprints.length; c++) { + if (c !== index) newCosigners.push(this._cosignersFingerprints[c]); + } */ + + // this._cosignersFingerprints = newCosigners; + } + + getFormat() { + if (this.isNativeSegwit()) return MultisigHDWallet.FORMAT_P2WSH; + if (this.isWrappedSegwit()) return MultisigHDWallet.FORMAT_P2SH_P2WSH; + if (this.isLegacy()) return MultisigHDWallet.FORMAT_P2SH; + + throw new Error('This should never happen'); + } + + /** + * @param fp {string} Exactly 8 chars of hex + * @return {boolean} + */ + static isFpValid(fp: string) { + if (fp.length !== 8) return false; + return /^[0-9A-F]{8}$/i.test(fp); + } + + /** + * Returns TRUE only for _multisignature_ xpubs as per SLIP-0132 + * (capital Z, capital Y, or just xpub) + * @see https://github.com/satoshilabs/slips/blob/master/slip-0132.md + * + * @param xpub + * @return {boolean} + */ + static isXpubForMultisig(xpub: string): boolean { + return ['xpub', 'Ypub', 'Zpub'].includes(xpub.substring(0, 4)); + } + + isSegwit() { + return this.isNativeSegwit() || this.isWrappedSegwit(); + } +} diff --git a/class/wallets/segwit-bech32-wallet.js b/class/wallets/segwit-bech32-wallet.js deleted file mode 100644 index e184dcdc60b..00000000000 --- a/class/wallets/segwit-bech32-wallet.js +++ /dev/null @@ -1,140 +0,0 @@ -import { LegacyWallet } from './legacy-wallet'; -import { ECPairFactory } from 'ecpair'; -import ecc from '../../blue_modules/noble_ecc'; -const ECPair = ECPairFactory(ecc); -const bitcoin = require('bitcoinjs-lib'); - -export class SegwitBech32Wallet extends LegacyWallet { - static type = 'segwitBech32'; - static typeReadable = 'P2 WPKH'; - static segwitType = 'p2wpkh'; - - getAddress() { - if (this._address) return this._address; - let address; - try { - const keyPair = ECPair.fromWIF(this.secret); - if (!keyPair.compressed) { - console.warn('only compressed public keys are good for segwit'); - return false; - } - address = bitcoin.payments.p2wpkh({ - pubkey: keyPair.publicKey, - }).address; - } catch (err) { - return false; - } - this._address = address; - - return this._address; - } - - static witnessToAddress(witness) { - try { - const pubKey = Buffer.from(witness, 'hex'); - return bitcoin.payments.p2wpkh({ - pubkey: pubKey, - network: bitcoin.networks.bitcoin, - }).address; - } catch (_) { - return false; - } - } - - /** - * Converts script pub key to bech32 address if it can. Returns FALSE if it cant. - * - * @param scriptPubKey - * @returns {boolean|string} Either bech32 address or false - */ - static scriptPubKeyToAddress(scriptPubKey) { - try { - const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex'); - return bitcoin.payments.p2wpkh({ - output: scriptPubKey2, - network: bitcoin.networks.bitcoin, - }).address; - } catch (_) { - return false; - } - } - - createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false, masterFingerprint) { - if (targets.length === 0) throw new Error('No destination provided'); - // compensating for coinselect inability to deal with segwit inputs, and overriding script length for proper vbytes calculation - for (const u of utxos) { - u.script = { length: 27 }; - } - const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress); - sequence = sequence || 0xffffffff; // disable RBF by default - const psbt = new bitcoin.Psbt(); - let c = 0; - const values = {}; - let keyPair; - - inputs.forEach(input => { - if (!skipSigning) { - // skiping signing related stuff - keyPair = ECPair.fromWIF(this.secret); // secret is WIF - } - values[c] = input.value; - c++; - - const pubkey = keyPair.publicKey; - const p2wpkh = bitcoin.payments.p2wpkh({ pubkey }); - - psbt.addInput({ - hash: input.txid, - index: input.vout, - sequence, - witnessUtxo: { - script: p2wpkh.output, - value: input.value, - }, - }); - }); - - outputs.forEach(output => { - // if output has no address - this is change output - if (!output.address) { - output.address = changeAddress; - } - - const outputData = { - address: output.address, - value: output.value, - }; - - psbt.addOutput(outputData); - }); - - if (!skipSigning) { - // skiping signing related stuff - for (let cc = 0; cc < c; cc++) { - psbt.signInput(cc, keyPair); - } - } - - let tx; - if (!skipSigning) { - tx = psbt.finalizeAllInputs().extractTransaction(); - } - return { tx, inputs, outputs, fee, psbt }; - } - - allowSend() { - return true; - } - - allowSendMax() { - return true; - } - - isSegwit() { - return true; - } - - allowSignVerifyMessage() { - return true; - } -} diff --git a/class/wallets/segwit-bech32-wallet.ts b/class/wallets/segwit-bech32-wallet.ts new file mode 100644 index 00000000000..3e3f49db40b --- /dev/null +++ b/class/wallets/segwit-bech32-wallet.ts @@ -0,0 +1,156 @@ +import * as bitcoin from 'bitcoinjs-lib'; +import { CoinSelectTarget } from 'coinselect'; +import { ECPairFactory } from 'ecpair'; + +import ecc from '../../blue_modules/noble_ecc'; +import { LegacyWallet } from './legacy-wallet'; +import { CreateTransactionResult, CreateTransactionUtxo } from './types'; +import { hexToUint8Array } from '../../blue_modules/uint8array-extras'; + +const ECPair = ECPairFactory(ecc); + +export class SegwitBech32Wallet extends LegacyWallet { + static readonly type = 'segwitBech32'; + static readonly typeReadable = 'SegWit (P2WPKH)'; + // @ts-ignore: override + public readonly type = SegwitBech32Wallet.type; + // @ts-ignore: override + public readonly typeReadable = SegwitBech32Wallet.typeReadable; + public readonly segwitType = 'p2wpkh'; + + getAddress(): string | false { + if (this._address) return this._address; + let address; + try { + const keyPair = ECPair.fromWIF(this.secret); + if (!keyPair.compressed) { + console.warn('only compressed public keys are good for segwit'); + return false; + } + address = bitcoin.payments.p2wpkh({ + pubkey: keyPair.publicKey, + }).address; + } catch (err) { + return false; + } + this._address = address ?? false; + + return this._address; + } + + static witnessToAddress(witness: string): string | false { + try { + const pubkey = hexToUint8Array(witness); + return ( + bitcoin.payments.p2wpkh({ + pubkey, + network: bitcoin.networks.bitcoin, + }).address ?? false + ); + } catch (_) { + return false; + } + } + + /** + * Converts script pub key to bech32 address if it can. Returns FALSE if it cant. + * + * @param scriptPubKey + * @returns {boolean|string} Either bech32 address or false + */ + static scriptPubKeyToAddress(scriptPubKey: string): string | false { + try { + const scriptPubKey2 = hexToUint8Array(scriptPubKey); + return ( + bitcoin.payments.p2wpkh({ + output: scriptPubKey2, + network: bitcoin.networks.bitcoin, + }).address ?? false + ); + } catch (_) { + return false; + } + } + + createTransaction( + utxos: CreateTransactionUtxo[], + targets: CoinSelectTarget[], + feeRate: number, + changeAddress: string, + sequence: number, + skipSigning = false, + masterFingerprint: number, + ): CreateTransactionResult { + if (targets.length === 0) throw new Error('No destination provided'); + const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate); + sequence = sequence || 0xffffffff; // disable RBF by default + const psbt = new bitcoin.Psbt(); + let c = 0; + const values: Record = {}; + const keyPair = ECPair.fromWIF(this.secret); + + inputs.forEach(input => { + values[c] = input.value; + c++; + + const pubkey = keyPair.publicKey; + const p2wpkh = bitcoin.payments.p2wpkh({ pubkey }); + if (!p2wpkh.output) { + throw new Error('Internal error: no p2wpkh.output during createTransaction()'); + } + + psbt.addInput({ + hash: input.txid, + index: input.vout, + sequence, + witnessUtxo: { + script: p2wpkh.output, + value: BigInt(input.value), + }, + }); + }); + + outputs.forEach(output => { + // if output has no address - this is change output + if (!output.address) { + output.address = changeAddress; + } + + const outputData = { + address: output.address, + value: BigInt(output.value), + }; + + psbt.addOutput(outputData); + }); + + if (!skipSigning) { + // skiping signing related stuff + for (let cc = 0; cc < c; cc++) { + psbt.signInput(cc, keyPair); + } + } + + let tx; + if (!skipSigning) { + tx = psbt.finalizeAllInputs().extractTransaction(); + } + return { tx, inputs, outputs, fee, psbt }; + } + + allowSend() { + return true; + } + + allowSendMax() { + return true; + } + + isSegwit() { + return true; + } + + allowSignVerifyMessage() { + return true; + } +} diff --git a/class/wallets/segwit-p2sh-wallet.js b/class/wallets/segwit-p2sh-wallet.js deleted file mode 100644 index d0e28a558f6..00000000000 --- a/class/wallets/segwit-p2sh-wallet.js +++ /dev/null @@ -1,160 +0,0 @@ -import { LegacyWallet } from './legacy-wallet'; -import { ECPairFactory } from 'ecpair'; -import ecc from '../../blue_modules/noble_ecc'; -const ECPair = ECPairFactory(ecc); -const bitcoin = require('bitcoinjs-lib'); - -/** - * Creates Segwit P2SH Bitcoin address - * @param pubkey - * @param network - * @returns {String} - */ -function pubkeyToP2shSegwitAddress(pubkey, network) { - network = network || bitcoin.networks.bitcoin; - const { address } = bitcoin.payments.p2sh({ - redeem: bitcoin.payments.p2wpkh({ pubkey, network }), - network, - }); - return address; -} - -export class SegwitP2SHWallet extends LegacyWallet { - static type = 'segwitP2SH'; - static typeReadable = 'SegWit (P2SH)'; - static segwitType = 'p2sh(p2wpkh)'; - - static witnessToAddress(witness) { - try { - const pubKey = Buffer.from(witness, 'hex'); - return pubkeyToP2shSegwitAddress(pubKey); - } catch (_) { - return false; - } - } - - /** - * Converts script pub key to p2sh address if it can. Returns FALSE if it cant. - * - * @param scriptPubKey - * @returns {boolean|string} Either p2sh address or false - */ - static scriptPubKeyToAddress(scriptPubKey) { - try { - const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex'); - return bitcoin.payments.p2sh({ - output: scriptPubKey2, - network: bitcoin.networks.bitcoin, - }).address; - } catch (_) { - return false; - } - } - - getAddress() { - if (this._address) return this._address; - let address; - try { - const keyPair = ECPair.fromWIF(this.secret); - const pubKey = keyPair.publicKey; - if (!keyPair.compressed) { - console.warn('only compressed public keys are good for segwit'); - return false; - } - address = pubkeyToP2shSegwitAddress(pubKey); - } catch (err) { - return false; - } - this._address = address; - - return this._address; - } - - /** - * - * @param utxos {Array.<{vout: Number, value: Number, txId: String, address: String, txhex: String, }>} List of spendable utxos - * @param targets {Array.<{value: Number, address: String}>} Where coins are going. If theres only 1 target and that target has no value - this will send MAX to that address (respecting fee rate) - * @param feeRate {Number} satoshi per byte - * @param changeAddress {String} Excessive coins will go back to that address - * @param sequence {Number} Used in RBF - * @param skipSigning {boolean} Whether we should skip signing, use returned `psbt` in that case - * @param masterFingerprint {number} Decimal number of wallet's master fingerprint - * @returns {{outputs: Array, tx: Transaction, inputs: Array, fee: Number, psbt: Psbt}} - */ - createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false, masterFingerprint) { - if (targets.length === 0) throw new Error('No destination provided'); - // compensating for coinselect inability to deal with segwit inputs, and overriding script length for proper vbytes calculation - for (const u of utxos) { - u.script = { length: 50 }; - } - const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress); - sequence = sequence || 0xffffffff; // disable RBF by default - const psbt = new bitcoin.Psbt(); - let c = 0; - const values = {}; - let keyPair; - - inputs.forEach(input => { - if (!skipSigning) { - // skiping signing related stuff - keyPair = ECPair.fromWIF(this.secret); // secret is WIF - } - values[c] = input.value; - c++; - - const pubkey = keyPair.publicKey; - const p2wpkh = bitcoin.payments.p2wpkh({ pubkey }); - const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh }); - - psbt.addInput({ - hash: input.txid, - index: input.vout, - sequence, - witnessUtxo: { - script: p2sh.output, - value: input.value, - }, - redeemScript: p2wpkh.output, - }); - }); - - outputs.forEach(output => { - // if output has no address - this is change output - if (!output.address) { - output.address = changeAddress; - } - - const outputData = { - address: output.address, - value: output.value, - }; - - psbt.addOutput(outputData); - }); - - if (!skipSigning) { - // skiping signing related stuff - for (let cc = 0; cc < c; cc++) { - psbt.signInput(cc, keyPair); - } - } - - let tx; - if (!skipSigning) { - tx = psbt.finalizeAllInputs().extractTransaction(); - } - return { tx, inputs, outputs, fee, psbt }; - } - - allowSendMax() { - return true; - } - - isSegwit() { - return true; - } - - allowSignVerifyMessage() { - return true; - } -} diff --git a/class/wallets/segwit-p2sh-wallet.ts b/class/wallets/segwit-p2sh-wallet.ts new file mode 100644 index 00000000000..4c382fa3e9b --- /dev/null +++ b/class/wallets/segwit-p2sh-wallet.ts @@ -0,0 +1,172 @@ +import * as bitcoin from 'bitcoinjs-lib'; +import { CoinSelectTarget } from 'coinselect'; +import { ECPairFactory } from 'ecpair'; + +import ecc from '../../blue_modules/noble_ecc'; +import { LegacyWallet } from './legacy-wallet'; +import { CreateTransactionResult, CreateTransactionUtxo } from './types'; +import { hexToUint8Array } from '../../blue_modules/uint8array-extras'; + +const ECPair = ECPairFactory(ecc); + +/** + * Creates Segwit P2SH Bitcoin address + * @param pubkey + * @param network + * @returns {String} + */ +function pubkeyToP2shSegwitAddress(pubkey: Uint8Array): string | false { + const { address } = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ pubkey }), + }); + return address ?? false; +} + +export class SegwitP2SHWallet extends LegacyWallet { + static readonly type = 'segwitP2SH'; + static readonly typeReadable = 'SegWit (P2SH)'; + // @ts-ignore: override + public readonly type = SegwitP2SHWallet.type; + // @ts-ignore: override + public readonly typeReadable = SegwitP2SHWallet.typeReadable; + public readonly segwitType = 'p2sh(p2wpkh)'; + + static witnessToAddress(witness: string): string | false { + try { + const pubKey = hexToUint8Array(witness); + return pubkeyToP2shSegwitAddress(pubKey); + } catch (_) { + return false; + } + } + + /** + * Converts script pub key to p2sh address if it can. Returns FALSE if it cant. + * + * @param scriptPubKey + * @returns {boolean|string} Either p2sh address or false + */ + static scriptPubKeyToAddress(scriptPubKey: string): string | false { + try { + const scriptPubKey2 = hexToUint8Array(scriptPubKey); + return ( + bitcoin.payments.p2sh({ + output: scriptPubKey2, + network: bitcoin.networks.bitcoin, + }).address ?? false + ); + } catch (_) { + return false; + } + } + + getAddress(): string | false { + if (this._address) return this._address; + let address; + try { + const keyPair = ECPair.fromWIF(this.secret); + const pubKey = keyPair.publicKey; + if (!keyPair.compressed) { + console.warn('only compressed public keys are good for segwit'); + return false; + } + address = pubkeyToP2shSegwitAddress(pubKey); + } catch (err) { + return false; + } + this._address = address; + + return this._address; + } + + /** + * + * @param utxos {Array.<{vout: Number, value: Number, txid: String, address: String, txhex: String, }>} List of spendable utxos + * @param targets {Array.<{value: Number, address: String}>} Where coins are going. If theres only 1 target and that target has no value - this will send MAX to that address (respecting fee rate) + * @param feeRate {Number} satoshi per byte + * @param changeAddress {String} Excessive coins will go back to that address + * @param sequence {Number} Used in RBF + * @param skipSigning {boolean} Whether we should skip signing, use returned `psbt` in that case + * @param masterFingerprint {number} Decimal number of wallet's master fingerprint + * @returns {{outputs: Array, tx: Transaction, inputs: Array, fee: Number, psbt: Psbt}} + */ + createTransaction( + utxos: CreateTransactionUtxo[], + targets: CoinSelectTarget[], + feeRate: number, + changeAddress: string, + sequence: number, + skipSigning = false, + masterFingerprint: number, + ): CreateTransactionResult { + if (targets.length === 0) throw new Error('No destination provided'); + const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate); + sequence = sequence || 0xffffffff; // disable RBF by default + const psbt = new bitcoin.Psbt(); + let c = 0; + const values: Record = {}; + const keyPair = ECPair.fromWIF(this.secret); + + inputs.forEach(input => { + values[c] = input.value; + c++; + + const pubkey = keyPair.publicKey; + const p2wpkh = bitcoin.payments.p2wpkh({ pubkey }); + const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh }); + if (!p2sh.output) { + throw new Error('Internal error: no p2sh.output during createTransaction()'); + } + + psbt.addInput({ + hash: input.txid, + index: input.vout, + sequence, + witnessUtxo: { + script: p2sh.output, + value: BigInt(input.value), + }, + redeemScript: p2wpkh.output, + }); + }); + + outputs.forEach(output => { + // if output has no address - this is change output + if (!output.address) { + output.address = changeAddress; + } + + const outputData = { + address: output.address, + value: BigInt(output.value), + }; + + psbt.addOutput(outputData); + }); + + if (!skipSigning) { + // skiping signing related stuff + for (let cc = 0; cc < c; cc++) { + psbt.signInput(cc, keyPair); + } + } + + let tx; + if (!skipSigning) { + tx = psbt.finalizeAllInputs().extractTransaction(); + } + return { tx, inputs, outputs, fee, psbt }; + } + + allowSendMax() { + return true; + } + + isSegwit() { + return true; + } + + allowSignVerifyMessage() { + return true; + } +} diff --git a/class/wallets/slip39-wallets.js b/class/wallets/slip39-wallets.js deleted file mode 100644 index a8672ec1a0a..00000000000 --- a/class/wallets/slip39-wallets.js +++ /dev/null @@ -1,102 +0,0 @@ -import slip39 from 'slip39'; -import { WORD_LIST } from 'slip39/dist/slip39_helper'; -import createHash from 'create-hash'; - -import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; -import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet'; -import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; - -// collection of SLIP39 functions -const SLIP39Mixin = { - _getSeed() { - const master = slip39.recoverSecret(this.secret, this.passphrase); - return Buffer.from(master); - }, - - validateMnemonic() { - if (!this.secret.every(m => slip39.validateMnemonic(m))) return false; - - try { - slip39.recoverSecret(this.secret); - } catch (e) { - return false; - } - return true; - }, - - setSecret(newSecret) { - // Try to match words to the default slip39 wordlist and complete partial words - const lookupMap = WORD_LIST.reduce((map, word) => { - const prefix3 = word.substr(0, 3); - const prefix4 = word.substr(0, 4); - - map.set(prefix3, !map.has(prefix3) ? word : false); - map.set(prefix4, !map.has(prefix4) ? word : false); - - return map; - }, new Map()); - - this.secret = newSecret - .trim() - .split('\n') - .filter(s => s) - .map(s => { - let secret = s - .trim() - .toLowerCase() - .replace(/[^a-zA-Z0-9]/g, ' ') - .replace(/\s+/g, ' '); - - secret = secret - .split(' ') - .map(word => lookupMap.get(word) || word) - .join(' '); - - return secret; - }); - return this; - }, - - getID() { - const string2hash = this.secret.sort().join(',') + (this.getPassphrase() || ''); - return createHash('sha256').update(string2hash).digest().toString('hex'); - }, -}; - -export class SLIP39LegacyP2PKHWallet extends HDLegacyP2PKHWallet { - static type = 'SLIP39legacyP2PKH'; - static typeReadable = 'SLIP39 Legacy (P2PKH)'; - - allowBIP47() { - return false; - } - - _getSeed = SLIP39Mixin._getSeed; - validateMnemonic = SLIP39Mixin.validateMnemonic; - setSecret = SLIP39Mixin.setSecret; - getID = SLIP39Mixin.getID; -} - -export class SLIP39SegwitP2SHWallet extends HDSegwitP2SHWallet { - static type = 'SLIP39segwitP2SH'; - static typeReadable = 'SLIP39 SegWit (P2SH)'; - - _getSeed = SLIP39Mixin._getSeed; - validateMnemonic = SLIP39Mixin.validateMnemonic; - setSecret = SLIP39Mixin.setSecret; - getID = SLIP39Mixin.getID; -} - -export class SLIP39SegwitBech32Wallet extends HDSegwitBech32Wallet { - static type = 'SLIP39segwitBech32'; - static typeReadable = 'SLIP39 SegWit (Bech32)'; - - allowBIP47() { - return false; - } - - _getSeed = SLIP39Mixin._getSeed; - validateMnemonic = SLIP39Mixin.validateMnemonic; - setSecret = SLIP39Mixin.setSecret; - getID = SLIP39Mixin.getID; -} diff --git a/class/wallets/slip39-wallets.ts b/class/wallets/slip39-wallets.ts new file mode 100644 index 00000000000..857f948d779 --- /dev/null +++ b/class/wallets/slip39-wallets.ts @@ -0,0 +1,126 @@ +import { sha256 } from '@noble/hashes/sha256'; +import slip39 from 'slip39'; +import { WORD_LIST } from 'slip39/src/slip39_helper'; + +import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; +import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; +import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet'; +import { uint8ArrayToHex } from '../../blue_modules/uint8array-extras'; + +type TWalletThis = Omit & { + secret: string[]; +}; + +// collection of SLIP39 functions +const SLIP39Mixin = { + _getSeed(): Uint8Array { + const self = this as unknown as TWalletThis; + const master = slip39.recoverSecret(self.secret, self.passphrase); + return Uint8Array.from(master); + }, + + validateMnemonic() { + const self = this as unknown as TWalletThis; + if (!self.secret.every(m => slip39.validateMnemonic(m))) return false; + + try { + slip39.recoverSecret(self.secret); + } catch (e) { + return false; + } + return true; + }, + + setSecret(newSecret: string) { + const self = this as unknown as TWalletThis; + // Try to match words to the default slip39 wordlist and complete partial words + const lookupMap = WORD_LIST.reduce((map, word) => { + const prefix3 = word.substr(0, 3); + const prefix4 = word.substr(0, 4); + + map.set(prefix3, !map.has(prefix3) ? word : false); + map.set(prefix4, !map.has(prefix4) ? word : false); + + return map; + }, new Map()); + + self.secret = newSecret + .trim() + .split('\n') + .filter(s => s) + .map(s => { + let secret = s + .trim() + .toLowerCase() + .replace(/[^a-zA-Z0-9]/g, ' ') + .replace(/\s+/g, ' '); + + secret = secret + .split(' ') + .map(word => lookupMap.get(word) || word) + .join(' '); + + return secret; + }); + return self; + }, + + getID() { + const self = this as unknown as TWalletThis; + const string2hash = self.secret.sort().join(',') + (self.getPassphrase() || ''); + return uint8ArrayToHex(sha256(string2hash)); + }, +}; + +export class SLIP39LegacyP2PKHWallet extends HDLegacyP2PKHWallet { + static readonly type = 'SLIP39legacyP2PKH'; + static readonly typeReadable = 'SLIP39 Legacy (P2PKH)'; + // @ts-ignore: override + public readonly type = SLIP39LegacyP2PKHWallet.type; + // @ts-ignore: override + public readonly typeReadable = SLIP39LegacyP2PKHWallet.typeReadable; + + allowBIP47() { + return false; + } + + _getSeed = SLIP39Mixin._getSeed; + validateMnemonic = SLIP39Mixin.validateMnemonic; + // @ts-ignore: this type mismatch + setSecret = SLIP39Mixin.setSecret; + getID = SLIP39Mixin.getID; +} + +export class SLIP39SegwitP2SHWallet extends HDSegwitP2SHWallet { + static readonly type = 'SLIP39segwitP2SH'; + static readonly typeReadable = 'SLIP39 SegWit (P2SH)'; + // @ts-ignore: override + public readonly type = SLIP39SegwitP2SHWallet.type; + // @ts-ignore: override + public readonly typeReadable = SLIP39SegwitP2SHWallet.typeReadable; + + _getSeed = SLIP39Mixin._getSeed; + validateMnemonic = SLIP39Mixin.validateMnemonic; + // @ts-ignore: this type mismatch + setSecret = SLIP39Mixin.setSecret; + getID = SLIP39Mixin.getID; +} + +export class SLIP39SegwitBech32Wallet extends HDSegwitBech32Wallet { + static readonly type = 'SLIP39segwitBech32'; + static readonly typeReadable = 'SLIP39 SegWit (Bech32)'; + // @ts-ignore: override + public readonly type = SLIP39SegwitBech32Wallet.type; + // @ts-ignore: override + public readonly typeReadable = SLIP39SegwitBech32Wallet.typeReadable; + + allowBIP47() { + return false; + } + + _getSeed = SLIP39Mixin._getSeed; + validateMnemonic = SLIP39Mixin.validateMnemonic; + // @ts-ignore: this type mismatch + setSecret = SLIP39Mixin.setSecret; + getID = SLIP39Mixin.getID; +} diff --git a/class/wallets/taproot-wallet.js b/class/wallets/taproot-wallet.js deleted file mode 100644 index 67e47d4989c..00000000000 --- a/class/wallets/taproot-wallet.js +++ /dev/null @@ -1,23 +0,0 @@ -import { SegwitBech32Wallet } from './segwit-bech32-wallet'; -const bitcoin = require('bitcoinjs-lib'); - -export class TaprootWallet extends SegwitBech32Wallet { - static type = 'taproot'; - static typeReadable = 'P2 TR'; - static segwitType = 'p2wpkh'; - - /** - * Converts script pub key to a Taproot address if it can. Returns FALSE if it cant. - * - * @param scriptPubKey - * @returns {boolean|string} Either bech32 address or false - */ - static scriptPubKeyToAddress(scriptPubKey) { - try { - const publicKey = Buffer.from(scriptPubKey, 'hex'); - return bitcoin.address.fromOutputScript(publicKey, bitcoin.networks.bitcoin); - } catch (_) { - return false; - } - } -} diff --git a/class/wallets/taproot-wallet.ts b/class/wallets/taproot-wallet.ts new file mode 100644 index 00000000000..b8ec4e91e05 --- /dev/null +++ b/class/wallets/taproot-wallet.ts @@ -0,0 +1,139 @@ +import * as bitcoin from 'bitcoinjs-lib'; +import { ECPairFactory } from 'ecpair'; + +import ecc from '../../blue_modules/noble_ecc'; + +import { SegwitBech32Wallet } from './segwit-bech32-wallet'; +import { CreateTransactionResult, CreateTransactionUtxo } from './types.ts'; +import { CoinSelectTarget } from 'coinselect'; +import { hexToUint8Array } from '../../blue_modules/uint8array-extras'; +const ECPair = ECPairFactory(ecc); + +export class TaprootWallet extends SegwitBech32Wallet { + static readonly type = 'taproot'; + static readonly typeReadable = 'Taproot (P2TR)'; + // @ts-ignore: override + public readonly type = TaprootWallet.type; + // @ts-ignore: override + public readonly typeReadable = TaprootWallet.typeReadable; + public readonly segwitType = 'p2wpkh'; + + /** + * Converts script pub key to a Taproot address if it can. Returns FALSE if it cant. + * + * @param scriptPubKey + * @returns {boolean|string} Either bech32 address or false + */ + static scriptPubKeyToAddress(scriptPubKey: string): string | false { + try { + const publicKey = hexToUint8Array(scriptPubKey); + return bitcoin.address.fromOutputScript(publicKey, bitcoin.networks.bitcoin); + } catch (_) { + return false; + } + } + + allowSend() { + return true; + } + + allowSendMax() { + return true; + } + + isSegwit() { + return true; + } + + allowSignVerifyMessage() { + return true; + } + + getAddress(): string | false { + if (this._address) return this._address; + let address; + try { + const keyPair = ECPair.fromWIF(this.secret); + if (!keyPair.compressed) { + console.warn('only compressed public keys are good for segwit'); + return false; + } + const xOnlyPubkey = keyPair.publicKey.subarray(1, 33); + address = bitcoin.payments.p2tr({ + internalPubkey: xOnlyPubkey, + }).address; + } catch (err: any) { + console.log(err.message); + return false; + } + this._address = address ?? false; + + return this._address; + } + + createTransaction( + utxos: CreateTransactionUtxo[], + targets: CoinSelectTarget[], + feeRate: number, + changeAddress: string, + sequence: number, + skipSigning = false, + masterFingerprint: number, + ): CreateTransactionResult { + if (targets.length === 0) throw new Error('No destination provided'); + const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate); + sequence = sequence || 0xffffffff; // default if not passed + + // Derive keyPair & x-only pubkey + const keyPair = ECPair.fromWIF(this.secret); + const pubkey = keyPair.publicKey; // compressed: 0x02/03 || X + const xOnlyPub = pubkey.subarray(1, 33); // strip prefix + + // Precompute the P2TR payment (to rebuild scriptPubKey) + const p2tr = bitcoin.payments.p2tr({ + internalPubkey: xOnlyPub, + }); + if (!p2tr.output) throw new Error('Could not build p2tr.output'); + + const psbt = new bitcoin.Psbt(); + + // Add Taproot inputs + inputs.forEach((input, idx) => { + psbt.addInput({ + hash: input.txid, + index: input.vout, + sequence, + witnessUtxo: { + script: p2tr.output!, + value: BigInt(input.value), + }, + // tell PSBT it’s a key-path Taproot spend + tapInternalKey: xOnlyPub, + }); + }); + + // Add outputs + outputs.forEach(output => { + // if output has no address - this is change output + if (!output.address) output.address = changeAddress; + psbt.addOutput({ + address: output.address!, + value: BigInt(output.value), + }); + }); + + let tx; + if (!skipSigning) { + // Sign each input as a Taproot key-path spend + inputs.forEach((_, idx) => { + psbt.signTaprootInput(idx, keyPair.tweak(bitcoin.crypto.taggedHash('TapTweak', xOnlyPub))); + }); + + // Finalize all inputs (will auto-detect Taproot) + psbt.finalizeAllInputs(); + tx = psbt.extractTransaction(); + } + + return { tx, inputs, outputs, fee, psbt }; + } +} diff --git a/class/wallets/types.ts b/class/wallets/types.ts index e4d9b10a423..de413d2a8b5 100644 --- a/class/wallets/types.ts +++ b/class/wallets/types.ts @@ -1,34 +1,53 @@ -import bitcoin from 'bitcoinjs-lib'; -import { CoinSelectOutput, CoinSelectReturnInput } from 'coinselect'; +import * as bitcoin from 'bitcoinjs-lib'; +import { CoinSelectOutput, CoinSelectReturnInput, CoinSelectUtxo } from 'coinselect'; + +import { BitcoinUnit } from '../../models/bitcoinUnits'; +import { HDAezeedWallet } from './hd-aezeed-wallet'; +import { HDLegacyBreadwalletWallet } from './hd-legacy-breadwallet-wallet'; +import { HDLegacyElectrumSeedP2PKHWallet } from './hd-legacy-electrum-seed-p2pkh-wallet'; +import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; +import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; +import { HDSegwitElectrumSeedP2WPKHWallet } from './hd-segwit-electrum-seed-p2wpkh-wallet'; +import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet'; +import { LegacyWallet } from './legacy-wallet'; +import { LightningCustodianWallet } from './lightning-custodian-wallet'; +import { MultisigHDWallet } from './multisig-hd-wallet'; +import { SegwitBech32Wallet } from './segwit-bech32-wallet'; +import { SegwitP2SHWallet } from './segwit-p2sh-wallet'; +import { SLIP39LegacyP2PKHWallet, SLIP39SegwitBech32Wallet, SLIP39SegwitP2SHWallet } from './slip39-wallets'; +import { WatchOnlyWallet } from './watch-only-wallet'; +import { TaprootWallet } from './taproot-wallet.ts'; +import { HDTaprootWallet } from './hd-taproot-wallet.ts'; +import { LightningArkWallet } from './lightning-ark-wallet.ts'; export type Utxo = { // Returned by BlueElectrum height: number; address: string; - txId: string; + txid: string; vout: number; value: number; // Others txhex?: string; - txid?: string; // TODO: same as txId, do we really need it? confirmations?: number; - amount?: number; // TODO: same as value, do we really need it? wif?: string | false; }; /** - * basically the same as coinselect.d.ts/CoinselectUtxo - * and should be unified as soon as bullshit with txid/txId is sorted + * same as coinselect.d.ts/CoinSelectUtxo */ -export type CreateTransactionUtxo = { - txId: string; - txid: string; // TODO: same as txId, do we really need it? - txhex: string; - vout: number; - value: number; +export interface CreateTransactionUtxo extends CoinSelectUtxo {} + +/** + * if address is missing and `script.hex` is set - this is a custom script (like OP_RETURN) + */ +export type CreateTransactionTarget = { + address?: string; + value?: number; script?: { - length: number; + length?: number; // either length or hex should be present + hex?: string; }; }; @@ -63,6 +82,37 @@ export type TransactionOutput = { }; }; +export interface DecodedInvoice { + destination: string; + payment_hash: string; + num_satoshis: number; + timestamp: number; + expiry: number; + description: string; + description_hash: string; + fallback_addr: string; + cltv_expiry: string; + route_hints: any[]; + [key: string]: any; +} + +export type LightningTransaction = { + memo?: string; + type?: 'user_invoice' | 'payment_request' | 'bitcoind_tx' | 'paid_invoice'; + payment_hash?: string | { data: string }; + category?: 'receive'; + timestamp: number; // seconds, not milliseconds + expire_time?: number; + ispaid?: boolean; + walletID?: string; + value?: number; + amt?: number; + fee?: number; + payment_preimage?: string; + payment_request?: string; + description?: string; +}; + export type Transaction = { txid: string; hash: string; @@ -74,9 +124,46 @@ export type Transaction = { inputs: TransactionInput[]; outputs: TransactionOutput[]; blockhash: string; - confirmations?: number; + confirmations: number; time: number; blocktime: number; - received?: number; + timestamp: number; // seconds, not milliseconds value?: number; + + /** + * if known, who is on the other end of the transaction (BIP47 payment code) + */ + counterparty?: string; +}; + +/** + * in some cases we add additional data to each tx object so the code that works with that transaction can find the + * wallet that owns it etc + */ +export type ExtendedTransaction = Transaction & { + walletID: string; + walletPreferredBalanceUnit: BitcoinUnit; }; + +export type TWallet = + | HDAezeedWallet + | HDLegacyBreadwalletWallet + | HDLegacyElectrumSeedP2PKHWallet + | HDLegacyP2PKHWallet + | HDSegwitBech32Wallet + | HDSegwitElectrumSeedP2WPKHWallet + | HDSegwitP2SHWallet + | HDTaprootWallet + | LegacyWallet + | LightningArkWallet + | LightningCustodianWallet + | MultisigHDWallet + | SLIP39LegacyP2PKHWallet + | SLIP39SegwitBech32Wallet + | SLIP39SegwitP2SHWallet + | SegwitBech32Wallet + | SegwitP2SHWallet + | TaprootWallet + | WatchOnlyWallet; + +export type THDWalletForWatchOnly = HDSegwitBech32Wallet | HDSegwitP2SHWallet | HDLegacyP2PKHWallet | HDTaprootWallet; diff --git a/class/wallets/watch-only-wallet.js b/class/wallets/watch-only-wallet.js deleted file mode 100644 index 60f8955ec9d..00000000000 --- a/class/wallets/watch-only-wallet.js +++ /dev/null @@ -1,311 +0,0 @@ -import { LegacyWallet } from './legacy-wallet'; -import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet'; -import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; -import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; -import BIP32Factory from 'bip32'; -import ecc from '../../blue_modules/noble_ecc'; - -const bitcoin = require('bitcoinjs-lib'); -const bip32 = BIP32Factory(ecc); - -export class WatchOnlyWallet extends LegacyWallet { - static type = 'watchOnly'; - static typeReadable = 'Watch-only'; - - constructor() { - super(); - this.use_with_hardware_wallet = false; - this.masterFingerprint = false; - } - - /** - * @inheritDoc - */ - getLastTxFetch() { - if (this._hdWalletInstance) return this._hdWalletInstance.getLastTxFetch(); - return super.getLastTxFetch(); - } - - timeToRefreshTransaction() { - if (this._hdWalletInstance) return this._hdWalletInstance.timeToRefreshTransaction(); - return super.timeToRefreshTransaction(); - } - - timeToRefreshBalance() { - if (this._hdWalletInstance) return this._hdWalletInstance.timeToRefreshBalance(); - return super.timeToRefreshBalance(); - } - - allowSend() { - return this.useWithHardwareWalletEnabled() && this.isHd() && this._hdWalletInstance.allowSend(); - } - - allowSignVerifyMessage() { - return false; - } - - getAddress() { - if (this.isAddressValid(this.secret)) return this.secret; // handling case when there is an XPUB there - if (this._hdWalletInstance) throw new Error('Should not be used in watch-only HD wallets'); - throw new Error('Not initialized'); - } - - valid() { - if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) return this.isXpubValid(); - - try { - bitcoin.address.toOutputScript(this.getAddress()); - return true; - } catch (_) { - return false; - } - } - - /** - * this method creates appropriate HD wallet class, depending on whether we have xpub, ypub or zpub - * as a property of `this`, and in case such property exists - it recreates it and copies data from old one. - * this is needed after serialization/save/load/deserialization procedure. - * - * @return {WatchOnlyWallet} this - */ - init() { - let hdWalletInstance; - if (this.secret.startsWith('xpub')) hdWalletInstance = new HDLegacyP2PKHWallet(); - else if (this.secret.startsWith('ypub')) hdWalletInstance = new HDSegwitP2SHWallet(); - else if (this.secret.startsWith('zpub')) hdWalletInstance = new HDSegwitBech32Wallet(); - else return this; - hdWalletInstance._xpub = this.secret; - - // if derivation path recovered from JSON file it should be moved to hdWalletInstance - if (this._derivationPath) { - hdWalletInstance._derivationPath = this._derivationPath; - } - - if (this._hdWalletInstance) { - // now, porting all properties from old object to new one - for (const k of Object.keys(this._hdWalletInstance)) { - hdWalletInstance[k] = this._hdWalletInstance[k]; - } - - // deleting properties that cant survive serialization/deserialization: - delete hdWalletInstance._node1; - delete hdWalletInstance._node0; - } - this._hdWalletInstance = hdWalletInstance; - - return this; - } - - prepareForSerialization() { - if (this._hdWalletInstance) { - delete this._hdWalletInstance._node0; - delete this._hdWalletInstance._node1; - delete this._hdWalletInstance._bip47_instance; - } - } - - getBalance() { - if (this._hdWalletInstance) return this._hdWalletInstance.getBalance(); - return super.getBalance(); - } - - getTransactions() { - if (this._hdWalletInstance) return this._hdWalletInstance.getTransactions(); - return super.getTransactions(); - } - - async fetchBalance() { - if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) { - if (!this._hdWalletInstance) this.init(); - return this._hdWalletInstance.fetchBalance(); - } else { - // return LegacyWallet.prototype.fetchBalance.call(this); - return super.fetchBalance(); - } - } - - async fetchTransactions() { - if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) { - if (!this._hdWalletInstance) this.init(); - return this._hdWalletInstance.fetchTransactions(); - } else { - // return LegacyWallet.prototype.fetchBalance.call(this); - return super.fetchTransactions(); - } - } - - async getAddressAsync() { - if (this.isAddressValid(this.secret)) return new Promise(resolve => resolve(this.secret)); - if (this._hdWalletInstance) return this._hdWalletInstance.getAddressAsync(); - throw new Error('Not initialized'); - } - - _getExternalAddressByIndex(index) { - if (this._hdWalletInstance) return this._hdWalletInstance._getExternalAddressByIndex(index); - throw new Error('Not initialized'); - } - - _getInternalAddressByIndex(index) { - if (this._hdWalletInstance) return this._hdWalletInstance._getInternalAddressByIndex(index); - throw new Error('Not initialized'); - } - - getNextFreeAddressIndex() { - if (this._hdWalletInstance) return this._hdWalletInstance.next_free_address_index; - throw new Error('Not initialized'); - } - - getNextFreeChangeAddressIndex() { - if (this._hdWalletInstance) return this._hdWalletInstance.next_free_change_address_index; - throw new Error('Not initialized'); - } - - async getChangeAddressAsync() { - if (this._hdWalletInstance) return this._hdWalletInstance.getChangeAddressAsync(); - throw new Error('Not initialized'); - } - - async fetchUtxo() { - if (this._hdWalletInstance) return this._hdWalletInstance.fetchUtxo(); - throw new Error('Not initialized'); - } - - getUtxo(...args) { - if (this._hdWalletInstance) return this._hdWalletInstance.getUtxo(...args); - throw new Error('Not initialized'); - } - - combinePsbt(base64one, base64two) { - if (this._hdWalletInstance) return this._hdWalletInstance.combinePsbt(base64one, base64two); - throw new Error('Not initialized'); - } - - broadcastTx(hex) { - if (this._hdWalletInstance) return this._hdWalletInstance.broadcastTx(hex); - throw new Error('Not initialized'); - } - - /** - * signature of this method is the same ad BIP84 createTransaction, BUT this method should be used to create - * unsinged PSBT to be used with HW wallet (or other external signer) - * @see HDSegwitBech32Wallet.createTransaction - */ - createTransaction(utxos, targets, feeRate, changeAddress, sequence) { - if (this._hdWalletInstance && this.isHd()) { - return this._hdWalletInstance.createTransaction(utxos, targets, feeRate, changeAddress, sequence, true, this.getMasterFingerprint()); - } else { - throw new Error('Not a HD watch-only wallet, cant create PSBT (or just not initialized)'); - } - } - - getMasterFingerprint() { - return this.masterFingerprint; - } - - getMasterFingerprintHex() { - if (!this.masterFingerprint) return '00000000'; - let masterFingerprintHex = Number(this.masterFingerprint).toString(16); - if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte - // poor man's little-endian conversion: - // ¯\_(ツ)_/¯ - return ( - masterFingerprintHex[6] + - masterFingerprintHex[7] + - masterFingerprintHex[4] + - masterFingerprintHex[5] + - masterFingerprintHex[2] + - masterFingerprintHex[3] + - masterFingerprintHex[0] + - masterFingerprintHex[1] - ); - } - - isHd() { - return this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub'); - } - - weOwnAddress(address) { - if (this.isHd()) { - if (this._hdWalletInstance) return this._hdWalletInstance.weOwnAddress(address); - throw new Error('Not initialized'); - } - - if (address && address.startsWith('BC1')) address = address.toLowerCase(); - - return this.getAddress() === address; - } - - allowHodlHodlTrading() { - return this.isHd(); - } - - allowMasterFingerprint() { - return this.getSecret().startsWith('zpub'); - } - - useWithHardwareWalletEnabled() { - return !!this.use_with_hardware_wallet; - } - - setUseWithHardwareWalletEnabled(enabled) { - this.use_with_hardware_wallet = !!enabled; - } - - /** - * @inheritDoc - */ - getAllExternalAddresses() { - if (this._hdWalletInstance) return this._hdWalletInstance.getAllExternalAddresses(); - return super.getAllExternalAddresses(); - } - - isXpubValid() { - let xpub; - - try { - if (this.secret.startsWith('zpub')) { - xpub = this._zpubToXpub(this.secret); - } else if (this.secret.startsWith('ypub')) { - xpub = this.constructor._ypubToXpub(this.secret); - } else { - xpub = this.secret; - } - - const hdNode = bip32.fromBase58(xpub); - hdNode.derive(0); - return true; - } catch (_) {} - - return false; - } - - addressIsChange(...args) { - if (this._hdWalletInstance) return this._hdWalletInstance.addressIsChange(...args); - return super.addressIsChange(...args); - } - - getUTXOMetadata(...args) { - if (this._hdWalletInstance) return this._hdWalletInstance.getUTXOMetadata(...args); - return super.getUTXOMetadata(...args); - } - - setUTXOMetadata(...args) { - if (this._hdWalletInstance) return this._hdWalletInstance.setUTXOMetadata(...args); - return super.setUTXOMetadata(...args); - } - - getDerivationPath(...args) { - if (this._hdWalletInstance) return this._hdWalletInstance.getDerivationPath(...args); - throw new Error("Not a HD watch-only wallet, can't use derivation path"); - } - - setDerivationPath(...args) { - if (this._hdWalletInstance) return this._hdWalletInstance.setDerivationPath(...args); - throw new Error("Not a HD watch-only wallet, can't use derivation path"); - } - - isSegwit() { - if (this._hdWalletInstance) return this._hdWalletInstance.isSegwit(); - return super.isSegwit(); - } -} diff --git a/class/wallets/watch-only-wallet.ts b/class/wallets/watch-only-wallet.ts new file mode 100644 index 00000000000..00777a6b222 --- /dev/null +++ b/class/wallets/watch-only-wallet.ts @@ -0,0 +1,344 @@ +import BIP32Factory from 'bip32'; +import * as bitcoin from 'bitcoinjs-lib'; + +import ecc from '../../blue_modules/noble_ecc'; +import { AbstractWallet } from './abstract-wallet'; +import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; +import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; +import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet'; +import { LegacyWallet } from './legacy-wallet'; +import { THDWalletForWatchOnly } from './types'; +import { HDTaprootWallet } from './hd-taproot-wallet'; + +const bip32 = BIP32Factory(ecc); + +export class WatchOnlyWallet extends LegacyWallet { + static readonly type = 'watchOnly'; + static readonly typeReadable = 'Watch-only'; + // @ts-ignore: override + public readonly type = WatchOnlyWallet.type; + // @ts-ignore: override + public readonly typeReadable = WatchOnlyWallet.typeReadable; + public isWatchOnlyWarningVisible = true; + + public _hdWalletInstance?: THDWalletForWatchOnly; + use_with_hardware_wallet = false; + masterFingerprint: number = 0; + + /** + * @inheritDoc + */ + getLastTxFetch() { + if (this._hdWalletInstance) return this._hdWalletInstance.getLastTxFetch(); + return super.getLastTxFetch(); + } + + timeToRefreshTransaction() { + if (this._hdWalletInstance) return this._hdWalletInstance.timeToRefreshTransaction(); + return super.timeToRefreshTransaction(); + } + + timeToRefreshBalance() { + if (this._hdWalletInstance) return this._hdWalletInstance.timeToRefreshBalance(); + return super.timeToRefreshBalance(); + } + + allowSend() { + return this.useWithHardwareWalletEnabled() && this.isHd() && this._hdWalletInstance!.allowSend(); + } + + allowSignVerifyMessage() { + return false; + } + + getAddress() { + if (this.isAddressValid(this.secret)) return this.secret; // handling case when there is an XPUB there + if (this._hdWalletInstance) throw new Error('Should not be used in watch-only HD wallets'); + throw new Error('Not initialized'); + } + + valid() { + if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) return this.isXpubValid(); + + try { + bitcoin.address.toOutputScript(this.getAddress()); + return true; + } catch (_) { + return false; + } + } + + /** + * this method creates appropriate HD wallet class, depending on whether we have xpub, ypub or zpub + * as a property of `this`, and in case such property exists - it recreates it and copies data from old one. + * this is needed after serialization/save/load/deserialization procedure. + */ + init() { + let hdWalletInstance: THDWalletForWatchOnly; + + // Check script type first (most reliable - parsed from descriptor) + if (this.segwitType === 'p2tr') { + hdWalletInstance = new HDTaprootWallet(); + } else if (this.segwitType === 'p2wpkh') { + hdWalletInstance = new HDSegwitBech32Wallet(); + } else if (this.segwitType === 'p2sh(p2wpkh)') { + hdWalletInstance = new HDSegwitP2SHWallet(); + } else if (this.segwitType === 'p2pkh') { + hdWalletInstance = new HDLegacyP2PKHWallet(); + } + // Fallback to path-based detection (for bare [fingerprint/path]xpub without descriptor wrapper) + else if (this._derivationPath?.startsWith("m/86'")) { + // if path is explicit taproot path - its definately BIP86 + hdWalletInstance = new HDTaprootWallet(); + } else if (this._derivationPath?.startsWith("m/84'")) { + hdWalletInstance = new HDSegwitBech32Wallet(); + } else if (this._derivationPath?.startsWith("m/49'")) { + hdWalletInstance = new HDSegwitP2SHWallet(); + } + // Final fallback to xpub prefix (legacy behavior for bare xpub/ypub/zpub) + else if (this.secret.startsWith('xpub')) { + hdWalletInstance = new HDLegacyP2PKHWallet(); + } else if (this.secret.startsWith('ypub')) hdWalletInstance = new HDSegwitP2SHWallet(); + else if (this.secret.startsWith('zpub')) hdWalletInstance = new HDSegwitBech32Wallet(); + else return this; + hdWalletInstance._xpub = this.secret; + + // if derivation path recovered from JSON file it should be moved to hdWalletInstance + if (this._derivationPath) { + hdWalletInstance._derivationPath = this._derivationPath; + } + + if (this._hdWalletInstance) { + // now, porting all properties from old object to new one + for (const k of Object.keys(this._hdWalletInstance)) { + // @ts-ignore: JS magic here + hdWalletInstance[k] = this._hdWalletInstance[k]; + } + + // deleting properties that cant survive serialization/deserialization: + delete hdWalletInstance._node1; + delete hdWalletInstance._node0; + } + this._hdWalletInstance = hdWalletInstance; + + return this; + } + + prepareForSerialization() { + if (this._hdWalletInstance) { + delete this._hdWalletInstance._node0; + delete this._hdWalletInstance._node1; + delete this._hdWalletInstance._bip47_instance; + } + } + + getBalance() { + if (this._hdWalletInstance) return this._hdWalletInstance.getBalance(); + return super.getBalance(); + } + + getTransactions() { + if (this._hdWalletInstance) return this._hdWalletInstance.getTransactions(); + return super.getTransactions(); + } + + async fetchBalance() { + if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) { + if (!this._hdWalletInstance) this.init(); + if (!this._hdWalletInstance) throw new Error('Internal error: _hdWalletInstance is not initialized'); + return this._hdWalletInstance.fetchBalance(); + } else { + // return LegacyWallet.prototype.fetchBalance.call(this); + return super.fetchBalance(); + } + } + + async fetchTransactions() { + if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) { + if (!this._hdWalletInstance) this.init(); + if (!this._hdWalletInstance) throw new Error('Internal error: _hdWalletInstance is not initialized'); + return this._hdWalletInstance.fetchTransactions(); + } else { + // return LegacyWallet.prototype.fetchBalance.call(this); + return super.fetchTransactions(); + } + } + + async getAddressAsync(): Promise { + if (this.isAddressValid(this.secret)) return new Promise(resolve => resolve(this.secret)); + if (this._hdWalletInstance) return this._hdWalletInstance.getAddressAsync(); + throw new Error('Not initialized'); + } + + _getExternalAddressByIndex(index: number) { + if (this._hdWalletInstance) return this._hdWalletInstance._getExternalAddressByIndex(index); + throw new Error('Not initialized'); + } + + _getInternalAddressByIndex(index: number) { + if (this._hdWalletInstance) return this._hdWalletInstance._getInternalAddressByIndex(index); + throw new Error('Not initialized'); + } + + getNextFreeAddressIndex() { + if (this._hdWalletInstance) return this._hdWalletInstance.next_free_address_index; + throw new Error('Not initialized'); + } + + getNextFreeChangeAddressIndex() { + if (this._hdWalletInstance) return this._hdWalletInstance.next_free_change_address_index; + throw new Error('Not initialized'); + } + + async getChangeAddressAsync() { + if (this._hdWalletInstance) return this._hdWalletInstance.getChangeAddressAsync(); + throw new Error('Not initialized'); + } + + async fetchUtxo() { + if (this._hdWalletInstance) return this._hdWalletInstance.fetchUtxo(); + // Single-address watch-only uses LegacyWallet UTXO + derivation from txs (no HD instance). + return super.fetchUtxo(); + } + + getUtxo(...args: Parameters) { + if (this._hdWalletInstance) return this._hdWalletInstance.getUtxo(...args); + return super.getUtxo(...args); + } + + combinePsbt(...args: Parameters) { + if (this._hdWalletInstance) return this._hdWalletInstance.combinePsbt(...args); + throw new Error('Not initialized'); + } + + broadcastTx(...args: Parameters) { + if (this._hdWalletInstance) return this._hdWalletInstance.broadcastTx(...args); + throw new Error('Not initialized'); + } + + /** + * signature of this method is the same ad BIP84 createTransaction, BUT this method should be used to create + * unsinged PSBT to be used with HW wallet (or other external signer) + */ + createTransaction(...args: Parameters) { + const [utxos, targets, feeRate, changeAddress, sequence] = args; + if (this._hdWalletInstance && this.isHd()) { + const masterFingerprint = this.getMasterFingerprint(); + return this._hdWalletInstance.createTransaction(utxos, targets, feeRate, changeAddress, sequence, true, masterFingerprint); + } else { + throw new Error('Not a HD watch-only wallet, cant create PSBT (or just not initialized)'); + } + } + + getMasterFingerprint() { + return this.masterFingerprint; + } + + getMasterFingerprintHex() { + if (!this.masterFingerprint) return '00000000'; + let masterFingerprintHex = Number(this.masterFingerprint).toString(16); + if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte + // poor man's little-endian conversion: + // ¯\_(ツ)_/¯ + return ( + masterFingerprintHex[6] + + masterFingerprintHex[7] + + masterFingerprintHex[4] + + masterFingerprintHex[5] + + masterFingerprintHex[2] + + masterFingerprintHex[3] + + masterFingerprintHex[0] + + masterFingerprintHex[1] + ); + } + + isHd() { + return this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub'); + } + + weOwnAddress(address: string) { + if (this.isHd()) { + if (this._hdWalletInstance) return this._hdWalletInstance.weOwnAddress(address); + throw new Error('Not initialized'); + } + + if (address && address.startsWith('BC1')) address = address.toLowerCase(); + + return this.getAddress() === address; + } + + allowMasterFingerprint() { + return this.getSecret().startsWith('zpub') || this.getSecret().startsWith('ypub') || this.getSecret().startsWith('xpub'); + } + + useWithHardwareWalletEnabled() { + return !!this.use_with_hardware_wallet; + } + + setUseWithHardwareWalletEnabled(enabled: boolean) { + this.use_with_hardware_wallet = !!enabled; + } + + /** + * @inheritDoc + */ + getAllExternalAddresses() { + if (this._hdWalletInstance) return this._hdWalletInstance.getAllExternalAddresses(); + return super.getAllExternalAddresses(); + } + + isXpubValid() { + let xpub; + + try { + if (this.secret.startsWith('zpub')) { + xpub = this._zpubToXpub(this.secret); + } else if (this.secret.startsWith('ypub')) { + xpub = AbstractWallet._ypubToXpub(this.secret); + } else { + xpub = this.secret; + } + + const hdNode = bip32.fromBase58(xpub); + hdNode.derive(0); + return true; + } catch (_) {} + + return false; + } + + addressIsChange(...args: Parameters) { + if (this._hdWalletInstance) return this._hdWalletInstance.addressIsChange(...args); + return super.addressIsChange(...args); + } + + getUTXOMetadata(...args: Parameters) { + if (this._hdWalletInstance) return this._hdWalletInstance.getUTXOMetadata(...args); + return super.getUTXOMetadata(...args); + } + + setUTXOMetadata(...args: Parameters) { + if (this._hdWalletInstance) return this._hdWalletInstance.setUTXOMetadata(...args); + return super.setUTXOMetadata(...args); + } + + getDerivationPath(...args: Parameters) { + if (this._hdWalletInstance) return this._hdWalletInstance.getDerivationPath(...args); + throw new Error("Not a HD watch-only wallet, can't use derivation path"); + } + + setDerivationPath(...args: Parameters) { + if (this._hdWalletInstance) return this._hdWalletInstance.setDerivationPath(...args); + throw new Error("Not a HD watch-only wallet, can't use derivation path"); + } + + isSegwit(): boolean { + if (this._hdWalletInstance) return this._hdWalletInstance.isSegwit(); + return super.isSegwit(); + } + + wasEverUsed(): Promise { + if (this._hdWalletInstance) return this._hdWalletInstance.wasEverUsed(); + return super.wasEverUsed(); + } +} diff --git a/codegen/NativeEventEmitter.ts b/codegen/NativeEventEmitter.ts new file mode 100644 index 00000000000..30cfdc1b303 --- /dev/null +++ b/codegen/NativeEventEmitter.ts @@ -0,0 +1,14 @@ +import type { TurboModule } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +import type { Double, UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes'; + +export interface Spec extends TurboModule { + addListener(eventName: string): void; + removeListeners(count: Double): void; + getMostRecentUserActivity(): Promise; +} + +const moduleProxy = TurboModuleRegistry.getEnforcing('EventEmitter'); + +export default moduleProxy; diff --git a/codegen/NativeMenuElementsEmitter.ts b/codegen/NativeMenuElementsEmitter.ts new file mode 100644 index 00000000000..7939b1719b1 --- /dev/null +++ b/codegen/NativeMenuElementsEmitter.ts @@ -0,0 +1,18 @@ +import type { TurboModule } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +import type { Double } from 'react-native/Libraries/Types/CodegenTypes'; + +export interface Spec extends TurboModule { + addListener(eventName: string): void; + removeListeners(count: Double): void; + openSettings(): void; + addWalletMenuAction(): void; + importWalletMenuAction(): void; + reloadTransactionsMenuAction(): void; + sharedInstance?(): void; +} + +const moduleProxy = TurboModuleRegistry.getEnforcing('MenuElementsEmitter'); + +export default moduleProxy; diff --git a/codegen/NativeSettingsModule.ts b/codegen/NativeSettingsModule.ts new file mode 100644 index 00000000000..a358114bad4 --- /dev/null +++ b/codegen/NativeSettingsModule.ts @@ -0,0 +1,17 @@ +import { TurboModuleRegistry } from 'react-native'; +import type { TurboModule } from 'react-native'; + +export interface Spec extends TurboModule { + initializeDeviceUID(): Promise; + getDeviceUID(): Promise; + getDeviceUIDCopy(): Promise; + setClearFilesOnLaunch(value: boolean): Promise; + getClearFilesOnLaunch(): Promise; + setDoNotTrack(enabled: boolean): Promise; + getDoNotTrack(): Promise; + openSettings(): Promise; +} + +const nativeModule = TurboModuleRegistry.get('SettingsModule'); + +export default nativeModule; diff --git a/codegen/NativeWidgetHelper.ts b/codegen/NativeWidgetHelper.ts new file mode 100644 index 00000000000..36828bbb96e --- /dev/null +++ b/codegen/NativeWidgetHelper.ts @@ -0,0 +1,10 @@ +import type { TurboModule } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec extends TurboModule { + reloadAllWidgets(): void; +} + +const moduleProxy = TurboModuleRegistry.getEnforcing('WidgetHelper'); + +export default moduleProxy; diff --git a/codegen/SegmentedControlNativeComponent.ts b/codegen/SegmentedControlNativeComponent.ts new file mode 100644 index 00000000000..6d589fadd8c --- /dev/null +++ b/codegen/SegmentedControlNativeComponent.ts @@ -0,0 +1,23 @@ +import type { HostComponent } from 'react-native'; +import type { ViewProps } from 'react-native'; +import type { BubblingEventHandler, Int32, WithDefault } from 'react-native/Libraries/Types/CodegenTypes'; +import { codegenNativeComponent } from 'react-native'; + + +type SegmentedControlChangeEvent = Readonly<{ + selectedIndex: Int32; + target: Int32; +}>; + +export interface NativeProps extends ViewProps { + values?: ReadonlyArray; + selectedIndex?: WithDefault; + enabled?: WithDefault; + backgroundColor?: string | null; + tintColor?: string | null; + textColor?: string | null; + momentary?: WithDefault; + onChange?: BubblingEventHandler | null; +} + +export default codegenNativeComponent('SegmentedControl') as HostComponent; diff --git a/components/AddWalletButton.tsx b/components/AddWalletButton.tsx new file mode 100644 index 00000000000..22babaebd3a --- /dev/null +++ b/components/AddWalletButton.tsx @@ -0,0 +1,66 @@ +import React, { useCallback, useMemo } from 'react'; +import { StyleSheet, GestureResponderEvent, View } from 'react-native'; +import Icon from './Icon'; +import { useTheme } from './themes'; +import ToolTipMenu from './TooltipMenu'; +import { CommonToolTipActions } from '../typings/CommonToolTipActions'; +import loc from '../loc'; +import { useExtendedNavigation } from '../hooks/useExtendedNavigation'; + +type AddWalletButtonProps = { + onPress: (event: GestureResponderEvent) => void; +}; + +const AddWalletButton: React.FC = ({ onPress }) => { + const { colors } = useTheme(); + const navigation = useExtendedNavigation(); + + const onPressMenuItem = useCallback( + (action: string) => { + switch (action) { + case CommonToolTipActions.ImportWallet.id: + navigation.navigate('AddWalletRoot', { screen: 'ImportWallet' }); + break; + default: + break; + } + }, + [navigation], + ); + + const actions = useMemo(() => [CommonToolTipActions.ImportWallet], []); + + return ( + + + + + + ); +}; + +export default AddWalletButton; + +const styles = StyleSheet.create({ + ball: { + width: 32, + height: 32, + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + }, + iconContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/components/AddressInput.js b/components/AddressInput.js deleted file mode 100644 index 2a674534df8..00000000000 --- a/components/AddressInput.js +++ /dev/null @@ -1,151 +0,0 @@ -import React, { useRef } from 'react'; -import PropTypes from 'prop-types'; -import { Text } from 'react-native-elements'; -import { findNodeHandle, Image, Keyboard, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native'; -import { getSystemName } from 'react-native-device-info'; -import { useTheme } from '@react-navigation/native'; - -import loc from '../loc'; -import * as NavigationService from '../NavigationService'; -const fs = require('../blue_modules/fs'); - -const isDesktop = getSystemName() === 'Mac OS X'; - -const AddressInput = ({ - isLoading = false, - address = '', - placeholder = loc.send.details_address, - onChangeText, - onBarScanned, - onBarScannerDismissWithoutData = () => {}, - scanButtonTapped = () => {}, - launchedBy, - editable = true, - inputAccessoryViewID, - onBlur = () => {}, - keyboardType = 'default', -}) => { - const { colors } = useTheme(); - const scanButtonRef = useRef(); - - const stylesHook = StyleSheet.create({ - root: { - borderColor: colors.formBorder, - borderBottomColor: colors.formBorder, - backgroundColor: colors.inputBackgroundColor, - }, - scan: { - backgroundColor: colors.scanLabel, - }, - scanText: { - color: colors.inverseForegroundColor, - }, - }); - - const onBlurEditing = () => { - onBlur(); - Keyboard.dismiss(); - }; - - return ( - - - {editable ? ( - { - await scanButtonTapped(); - Keyboard.dismiss(); - if (isDesktop) { - fs.showActionSheet({ anchor: findNodeHandle(scanButtonRef.current) }).then(onBarScanned); - } else { - NavigationService.navigate('ScanQRCodeRoot', { - screen: 'ScanQRCode', - params: { - launchedBy, - onBarScanned, - onBarScannerDismissWithoutData, - }, - }); - } - }} - accessibilityRole="button" - style={[styles.scan, stylesHook.scan]} - accessibilityLabel={loc.send.details_scan} - accessibilityHint={loc.send.details_scan_hint} - > - - - {loc.send.details_scan} - - - ) : null} - - ); -}; - -const styles = StyleSheet.create({ - root: { - flexDirection: 'row', - borderWidth: 1.0, - borderBottomWidth: 0.5, - minHeight: 44, - height: 44, - marginHorizontal: 20, - alignItems: 'center', - marginVertical: 8, - borderRadius: 4, - }, - input: { - flex: 1, - marginHorizontal: 8, - minHeight: 33, - color: '#81868e', - }, - scan: { - height: 36, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - borderRadius: 4, - paddingVertical: 4, - paddingHorizontal: 8, - marginHorizontal: 4, - }, - scanText: { - marginLeft: 4, - }, -}); - -AddressInput.propTypes = { - isLoading: PropTypes.bool, - onChangeText: PropTypes.func, - onBarScanned: PropTypes.func.isRequired, - launchedBy: PropTypes.string, - address: PropTypes.string, - placeholder: PropTypes.string, - editable: PropTypes.bool, - scanButtonTapped: PropTypes.func, - inputAccessoryViewID: PropTypes.string, - onBarScannerDismissWithoutData: PropTypes.func, - onBlur: PropTypes.func, - keyboardType: PropTypes.string, -}; - -export default AddressInput; diff --git a/components/AddressInput.tsx b/components/AddressInput.tsx new file mode 100644 index 00000000000..af525ec2c0d --- /dev/null +++ b/components/AddressInput.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { StyleProp, StyleSheet, TextInput, View, ViewStyle } from 'react-native'; +import loc from '../loc'; +import { AddressInputScanButton } from './AddressInputScanButton'; +import { useTheme } from './themes'; + +interface AddressInputProps { + isLoading?: boolean; + address?: string; + placeholder?: string; + onChangeText: (text: string) => void; + editable?: boolean; + inputAccessoryViewID?: string; + onFocus?: () => void; + onBlur?: () => void; + testID?: string; + style?: StyleProp; + keyboardType?: + | 'default' + | 'numeric' + | 'email-address' + | 'ascii-capable' + | 'numbers-and-punctuation' + | 'url' + | 'number-pad' + | 'phone-pad' + | 'name-phone-pad' + | 'decimal-pad' + | 'twitter' + | 'web-search' + | 'visible-password'; +} + +const AddressInput = ({ + isLoading = false, + address = '', + testID = 'AddressInput', + placeholder = loc.send.details_address, + onChangeText, + editable = true, + inputAccessoryViewID, + onFocus = () => {}, + onBlur = () => {}, + keyboardType = 'default', + style, +}: AddressInputProps) => { + const { colors } = useTheme(); + const stylesHook = StyleSheet.create({ + root: { + borderColor: colors.formBorder, + borderBottomColor: colors.formBorder, + backgroundColor: colors.inputBackgroundColor, + }, + input: { + color: colors.foregroundColor, + }, + }); + + return ( + + + {editable ? : null} + + ); +}; + +const styles = StyleSheet.create({ + root: { + flexDirection: 'row', + borderWidth: 1.0, + borderBottomWidth: 0.5, + minHeight: 44, + height: 44, + alignItems: 'center', + borderRadius: 4, + }, + input: { + flex: 1, + paddingHorizontal: 8, + minHeight: 33, + fontSize: 15, + lineHeight: 19, + }, +}); + +export default AddressInput; diff --git a/components/AddressInputScanButton.tsx b/components/AddressInputScanButton.tsx new file mode 100644 index 00000000000..b7f83691f5c --- /dev/null +++ b/components/AddressInputScanButton.tsx @@ -0,0 +1,200 @@ +import React, { useCallback, useMemo } from 'react'; +import { Image, Keyboard, Platform, StyleSheet, Text, View } from 'react-native'; +import Clipboard from '@react-native-clipboard/clipboard'; +import ToolTipMenu from './TooltipMenu'; +import loc from '../loc'; +import { showFilePickerAndReadFile, showImagePickerAndReadImage } from '../blue_modules/fs'; +import presentAlert from './Alert'; +import { useTheme } from './themes'; +import { detectQRCodeInImage } from 'react-native-camera-kit-no-google'; +import { CommonToolTipActions } from '../typings/CommonToolTipActions'; +import { useSettings } from '../hooks/context/useSettings'; +import { scanQrHelper } from '../helpers/scan-qr.ts'; + +interface AddressInputScanButtonProps { + isLoading?: boolean; + onChangeText: (text: string) => void; + type?: 'default' | 'link'; + testID?: string; + beforePress?: () => Promise | void; +} + +export const AddressInputScanButton = ({ + isLoading, + onChangeText, + type = 'default', + testID = 'BlueAddressInputScanQrButton', + beforePress, +}: AddressInputScanButtonProps) => { + const { colors } = useTheme(); + const { isClipboardGetContentEnabled } = useSettings(); + + const stylesHook = StyleSheet.create({ + scan: { + backgroundColor: colors.scanLabel, + }, + scanText: { + color: colors.inverseForegroundColor, + }, + }); + + const toolTipOnPress = useCallback(async () => { + if (beforePress) { + await beforePress(); + } + Keyboard.dismiss(); + scanQrHelper().then(onChangeText); + }, [beforePress, onChangeText]); + + const actions = useMemo(() => { + const availableActions = [ + CommonToolTipActions.ChoosePhoto, + CommonToolTipActions.ImportFile, + { + ...CommonToolTipActions.PasteFromClipboard, + hidden: !isClipboardGetContentEnabled, + }, + ]; + + return availableActions; + }, [isClipboardGetContentEnabled]); + + const onMenuItemPressed = useCallback( + async (action: string) => { + switch (action) { + case CommonToolTipActions.PasteFromClipboard.id: + try { + let getImage: string | null = null; + let hasImage = false; + if (Platform.OS === 'android') { + hasImage = true; + } else { + hasImage = await Clipboard.hasImage(); + } + + if (hasImage) { + getImage = await Clipboard.getImage(); + } + + if (getImage) { + try { + const base64Data = getImage.replace(/^data:image\/(png|jpeg|jpg);base64,/, ''); + const result = await detectQRCodeInImage(base64Data); + if (result) { + onChangeText(result); + } else { + presentAlert({ message: loc.send.qr_error_no_qrcode }); + } + } catch (error) { + presentAlert({ message: (error as Error).message }); + } + } else { + const clipboardText = await Clipboard.getString(); + onChangeText(clipboardText); + } + } catch (error) { + presentAlert({ message: (error as Error).message }); + } + break; + case CommonToolTipActions.ChoosePhoto.id: + showImagePickerAndReadImage() + .then(value => { + if (value) { + onChangeText(value); + } + }) + .catch(error => { + presentAlert({ message: error.message }); + }); + break; + case CommonToolTipActions.ImportFile.id: + showFilePickerAndReadFile() + .then(value => { + if (value.data) { + onChangeText(value.data); + } + }) + .catch(error => { + presentAlert({ message: error.message }); + }); + break; + } + Keyboard.dismiss(); + }, + [onChangeText], + ); + + const menuButtonStyle = useMemo(() => (type === 'default' ? [styles.scan, stylesHook.scan] : undefined), [stylesHook.scan, type]); + + return ( + + {type === 'default' ? ( + + + + {loc.send.details_scan} + + + ) : ( + + + {loc.wallets.import_scan_qr} + + + )} + + ); +}; + +AddressInputScanButton.displayName = 'AddressInputScanButton'; + +const styles = StyleSheet.create({ + scan: { + height: 36, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 6, + minWidth: 82, + flexShrink: 0, + borderRadius: 4, + paddingVertical: 4, + paddingHorizontal: 8, + marginHorizontal: 4, + alignSelf: 'center', + }, + scanText: { + marginLeft: 4, + flexShrink: 0, + textAlignVertical: 'center', + }, + scanContent: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + flexShrink: 0, + }, + linkText: { + textAlign: 'center', + fontSize: 16, + flexShrink: 1, + textAlignVertical: 'center', + }, + contentRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + }, +}); diff --git a/components/Alert.js b/components/Alert.js deleted file mode 100644 index 44fcc6a2f24..00000000000 --- a/components/Alert.js +++ /dev/null @@ -1,6 +0,0 @@ -import { Alert } from 'react-native'; -import loc from '../loc'; -const alert = string => { - Alert.alert(loc.alert.default, string); -}; -export default alert; diff --git a/components/Alert.ts b/components/Alert.ts new file mode 100644 index 00000000000..8c2bbc0f2b7 --- /dev/null +++ b/components/Alert.ts @@ -0,0 +1,85 @@ +import { Alert as RNAlert, Platform, ToastAndroid, AlertButton, AlertOptions } from 'react-native'; +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; +import loc from '../loc'; +import { navigationRef } from '../NavigationService'; + +export enum AlertType { + Alert, + Toast, +} + +const presentAlert = (() => { + let lastAlertParams: { + title?: string; + message: string; + type?: AlertType; + hapticFeedback?: HapticFeedbackTypes; + buttons?: AlertButton[]; + options?: AlertOptions; + } | null = null; + + const clearCache = () => { + lastAlertParams = null; + }; + + const showAlert = (title: string | undefined, message: string, buttons: AlertButton[], options: AlertOptions) => { + if (Platform.OS === 'ios' && navigationRef.isReady()) { + RNAlert.alert(title ?? message, title && message ? message : undefined, buttons, options); + } else { + RNAlert.alert(title ?? '', message, buttons, options); + } + }; + + return ({ + title, + message, + type = AlertType.Alert, + hapticFeedback, + buttons = [], + options = { cancelable: false }, + allowRepeat = true, + }: { + title?: string; + message: string; + type?: AlertType; + hapticFeedback?: HapticFeedbackTypes; + buttons?: AlertButton[]; + options?: AlertOptions; + allowRepeat?: boolean; + }) => { + const currentAlertParams = { title, message, type, hapticFeedback, buttons, options }; + + if (!allowRepeat && lastAlertParams && JSON.stringify(lastAlertParams) === JSON.stringify(currentAlertParams)) { + return; + } + + if (JSON.stringify(lastAlertParams) !== JSON.stringify(currentAlertParams)) { + clearCache(); + } + + lastAlertParams = currentAlertParams; + + if (hapticFeedback) { + triggerHapticFeedback(hapticFeedback); + } + + const wrappedButtons: AlertButton[] = buttons.length > 0 ? buttons : [{ text: loc._.ok, onPress: () => {}, style: 'default' }]; + + switch (type) { + case AlertType.Toast: + if (Platform.OS === 'android') { + ToastAndroid.show(message, ToastAndroid.LONG); + clearCache(); + } else { + // For iOS, treat Toast as a normal alert + showAlert(title, message, wrappedButtons, options); + } + break; + default: + showAlert(title, message, wrappedButtons, options); + break; + } + }; +})(); + +export default presentAlert; diff --git a/components/AmountInput.js b/components/AmountInput.js deleted file mode 100644 index 2c67edc528d..00000000000 --- a/components/AmountInput.js +++ /dev/null @@ -1,409 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import BigNumber from 'bignumber.js'; -import { Badge, Icon, Text } from 'react-native-elements'; -import { Image, LayoutAnimation, Pressable, StyleSheet, TextInput, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native'; -import { useTheme } from '@react-navigation/native'; -import confirm from '../helpers/confirm'; -import { BitcoinUnit } from '../models/bitcoinUnits'; -import loc, { formatBalanceWithoutSuffix, formatBalancePlain, removeTrailingZeros } from '../loc'; -import { BlueText } from '../BlueComponents'; -import dayjs from 'dayjs'; -const currency = require('../blue_modules/currency'); -dayjs.extend(require('dayjs/plugin/localizedFormat')); - -class AmountInput extends Component { - static propTypes = { - isLoading: PropTypes.bool, - /** - * amount is a sting thats always in current unit denomination, e.g. '0.001' or '9.43' or '10000' - */ - amount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - /** - * callback that returns currently typed amount, in current denomination, e.g. 0.001 or 10000 or $9.34 - * (btc, sat, fiat) - */ - onChangeText: PropTypes.func.isRequired, - /** - * callback thats fired to notify of currently selected denomination, returns - */ - onAmountUnitChange: PropTypes.func.isRequired, - disabled: PropTypes.bool, - colors: PropTypes.object.isRequired, - pointerEvents: PropTypes.string, - unit: PropTypes.string, - onBlur: PropTypes.func, - onFocus: PropTypes.func, - }; - - /** - * cache of conversions fiat amount => satoshi - * @type {{}} - */ - static conversionCache = {}; - - static getCachedSatoshis = amount => { - return AmountInput.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY] || false; - }; - - static setCachedSatoshis = (amount, sats) => { - AmountInput.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY] = sats; - }; - - constructor() { - super(); - this.state = { mostRecentFetchedRate: Date(), isRateOutdated: false, isRateBeingUpdated: false }; - } - - componentDidMount() { - currency - .mostRecentFetchedRate() - .then(mostRecentFetchedRate => { - this.setState({ mostRecentFetchedRate }); - }) - .finally(() => { - currency.isRateOutdated().then(isRateOutdated => this.setState({ isRateOutdated })); - }); - } - - /** - * 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 - * - * @param previousUnit {string} one of {BitcoinUnit.*} - * @param newUnit {string} one of {BitcoinUnit.*} - */ - onAmountUnitChange(previousUnit, newUnit) { - const amount = this.props.amount || 0; - const log = `${amount}(${previousUnit}) ->`; - let sats = 0; - switch (previousUnit) { - case BitcoinUnit.BTC: - sats = new BigNumber(amount).multipliedBy(100000000).toString(); - break; - case BitcoinUnit.SATS: - sats = amount; - break; - case BitcoinUnit.LOCAL_CURRENCY: - sats = new BigNumber(currency.fiatToBTC(amount)).multipliedBy(100000000).toString(); - break; - } - if (previousUnit === BitcoinUnit.LOCAL_CURRENCY && AmountInput.conversionCache[amount + previousUnit]) { - // cache hit! we reuse old value that supposedly doesnt have rounding errors - sats = AmountInput.conversionCache[amount + previousUnit]; - } - - 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 - AmountInput.conversionCache[newInputValue + newUnit] = amount; - } - this.props.onChangeText(newInputValue); - this.props.onAmountUnitChange(newUnit); - } - - /** - * responsible for cycling currently selected denomination, BTC->SAT->LOCAL_CURRENCY->BTC - */ - changeAmountUnit = () => { - let previousUnit = this.props.unit; - let newUnit; - if (previousUnit === BitcoinUnit.BTC) { - newUnit = BitcoinUnit.SATS; - } else if (previousUnit === BitcoinUnit.SATS) { - newUnit = BitcoinUnit.LOCAL_CURRENCY; - } else if (previousUnit === BitcoinUnit.LOCAL_CURRENCY) { - newUnit = BitcoinUnit.BTC; - } else { - newUnit = BitcoinUnit.BTC; - previousUnit = BitcoinUnit.SATS; - } - this.onAmountUnitChange(previousUnit, newUnit); - }; - - maxLength = () => { - switch (this.props.unit) { - case BitcoinUnit.BTC: - return 11; - case BitcoinUnit.SATS: - return 15; - default: - return 15; - } - }; - - textInput = React.createRef(); - - handleTextInputOnPress = () => { - this.textInput.current.focus(); - }; - - handleChangeText = text => { - text = text.trim(); - if (this.props.unit !== BitcoinUnit.LOCAL_CURRENCY) { - text = text.replace(',', '.'); - const split = text.split('.'); - if (split.length >= 2) { - text = `${parseInt(split[0], 10)}.${split[1]}`; - } else { - text = `${parseInt(split[0], 10)}`; - } - - text = this.props.unit === BitcoinUnit.BTC ? text.replace(/[^0-9.]/g, '') : text.replace(/[^0-9]/g, ''); - - if (text.startsWith('.')) { - text = '0.'; - } - } else if (this.props.unit === BitcoinUnit.LOCAL_CURRENCY) { - text = text.replace(/,/gi, '.'); - if (text.split('.').length > 2) { - // too many dots. stupid code to remove all but first dot: - let rez = ''; - let first = true; - for (const part of text.split('.')) { - rez += part; - if (first) { - rez += '.'; - first = false; - } - } - text = rez; - } - if (text.startsWith('0') && !(text.includes('.') || text.includes(','))) { - text = text.replace(/^(0+)/g, ''); - } - text = text.replace(/[^\d.,-]/g, ''); // remove all but numbers, dots & commas - text = text.replace(/(\..*)\./g, '$1'); - } - this.props.onChangeText(text); - }; - - resetAmount = async () => { - if (await confirm(loc.send.reset_amount, loc.send.reset_amount_confirm)) { - this.props.onChangeText(); - } - }; - - updateRate = () => { - LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); - this.setState({ isRateBeingUpdated: true }, async () => { - try { - await currency.updateExchangeRate(); - currency.mostRecentFetchedRate().then(mostRecentFetchedRate => { - LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); - this.setState({ mostRecentFetchedRate }); - }); - } finally { - LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); - this.setState({ isRateBeingUpdated: false, isRateOutdated: await currency.isRateOutdated() }); - } - }); - }; - - render() { - const { colors, disabled, unit } = this.props; - const amount = this.props.amount || 0; - let secondaryDisplayCurrency = formatBalanceWithoutSuffix(amount, BitcoinUnit.LOCAL_CURRENCY, false); - - // if main display is sat or btc - secondary display is fiat - // if main display is fiat - secondary dislay is btc - let sat; - switch (unit) { - case BitcoinUnit.BTC: - sat = new BigNumber(amount).multipliedBy(100000000).toString(); - secondaryDisplayCurrency = formatBalanceWithoutSuffix(sat, BitcoinUnit.LOCAL_CURRENCY, false); - break; - case BitcoinUnit.SATS: - secondaryDisplayCurrency = formatBalanceWithoutSuffix((isNaN(amount) ? 0 : amount).toString(), BitcoinUnit.LOCAL_CURRENCY, false); - break; - case BitcoinUnit.LOCAL_CURRENCY: - secondaryDisplayCurrency = currency.fiatToBTC(parseFloat(isNaN(amount) ? 0 : amount)); - if (AmountInput.conversionCache[isNaN(amount) ? 0 : amount + BitcoinUnit.LOCAL_CURRENCY]) { - // cache hit! we reuse old value that supposedly doesn't have rounding errors - const sats = AmountInput.conversionCache[isNaN(amount) ? 0 : amount + BitcoinUnit.LOCAL_CURRENCY]; - secondaryDisplayCurrency = currency.satoshiToBTC(sats); - } - break; - } - - if (amount === BitcoinUnit.MAX) secondaryDisplayCurrency = ''; // we don't want to display NaN - - const stylesHook = StyleSheet.create({ - center: { padding: amount === BitcoinUnit.MAX ? 0 : 15 }, - localCurrency: { color: disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2 }, - input: { color: disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2, fontSize: amount.length > 10 ? 20 : 36 }, - cryptoCurrency: { color: disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2 }, - }); - - return ( - this.textInput.focus()} - > - <> - - {!disabled && } - - - {unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && ( - {currency.getCurrencySymbol() + ' '} - )} - {amount !== BitcoinUnit.MAX ? ( - { - if (this.props.onBlur) this.props.onBlur(); - }} - onFocus={() => { - if (this.props.onFocus) this.props.onFocus(); - }} - placeholder="0" - maxLength={this.maxLength()} - ref={textInput => (this.textInput = textInput)} - editable={!this.props.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]} - /> - ) : ( - - {BitcoinUnit.MAX} - - )} - {unit !== BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && ( - {' ' + loc.units[unit]} - )} - - - - {unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX - ? removeTrailingZeros(secondaryDisplayCurrency) - : secondaryDisplayCurrency} - {unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX ? ` ${loc.units[BitcoinUnit.BTC]}` : null} - - - - {!disabled && amount !== BitcoinUnit.MAX && ( - - - - )} - - {this.state.isRateOutdated && ( - - - - - {loc.formatString(loc.send.outdated_rate, { date: dayjs(this.state.mostRecentFetchedRate.LastUpdated).format('l LT') })} - - - - - - - )} - - - ); - } -} - -const styles = StyleSheet.create({ - root: { - flexDirection: 'row', - justifyContent: 'space-between', - }, - center: { - alignSelf: 'center', - }, - flex: { - flex: 1, - }, - spacing8: { - width: 8, - }, - disabledButton: { - opacity: 0.5, - }, - enabledButon: { - opacity: 1, - }, - outdatedRateContainer: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - marginVertical: 8, - }, - container: { - flexDirection: 'row', - alignContent: 'space-between', - justifyContent: 'center', - paddingTop: 16, - paddingBottom: 2, - }, - localCurrency: { - fontSize: 18, - marginHorizontal: 4, - fontWeight: 'bold', - alignSelf: 'center', - justifyContent: 'center', - }, - input: { - fontWeight: 'bold', - }, - cryptoCurrency: { - fontSize: 15, - marginHorizontal: 4, - fontWeight: '600', - alignSelf: 'center', - justifyContent: 'center', - }, - secondaryRoot: { - alignItems: 'center', - marginBottom: 22, - }, - secondaryText: { - fontSize: 16, - color: '#9BA0A9', - fontWeight: '600', - }, - changeAmountUnit: { - alignSelf: 'center', - marginRight: 16, - paddingLeft: 16, - paddingVertical: 16, - }, -}); - -const AmountInputWithStyle = props => { - const { colors } = useTheme(); - - return ; -}; - -// expose static methods -AmountInputWithStyle.conversionCache = AmountInput.conversionCache; -AmountInputWithStyle.getCachedSatoshis = AmountInput.getCachedSatoshis; -AmountInputWithStyle.setCachedSatoshis = AmountInput.setCachedSatoshis; - -export default AmountInputWithStyle; diff --git a/components/AmountInput.tsx b/components/AmountInput.tsx new file mode 100644 index 00000000000..046db4325ef --- /dev/null +++ b/components/AmountInput.tsx @@ -0,0 +1,595 @@ +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 { + Image, + Platform, + Pressable, + StyleSheet, + Text, + TextInput, + TextInputProps, + TextInputSelectionChangeEvent, + TouchableOpacity, + View, +} from 'react-native'; +import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'; + +import { + CurrencyRate, + fiatToBTC, + getCurrencySymbol, + isRateOutdated, + mostRecentFetchedRate, + satoshiToBTC, + updateExchangeRate, +} from '../blue_modules/currency'; +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; +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 } = {}; + +export const getCachedSatoshis = (amount: string): string | undefined => { + return conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY]; +}; + +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 & { + /** + * Whether the input is in a loading state + */ + isLoading?: boolean; + /** + * Whether the input is disabled + */ + disabled?: boolean; + /** + * The current amount value as a string in the current unit denomination + * e.g. '0.001' or '9.43' or '10000' + */ + amount?: string; + /** + * The current unit of the amount (BTC, SATS, LOCAL_CURRENCY) + */ + unit: BitcoinUnit; + /** + * Callback that returns currently typed amount in current denomination + * e.g. 0.001 or 10000 or $9.34 (btc, sat, fiat) + */ + onChangeText: (text: string) => void; + /** + * Callback that's fired to notify of currently selected denomination + * Returns a BitcoinUnit value + */ + onAmountUnitChange: (unit: BitcoinUnit) => void; + /** + * Estimated sendable amount in satoshis when MAX is selected. + * Displayed below the MAX label. Pass null to hide. + */ + maxSendableAmount?: number | null; + /** + * When true, shows ≈ prefix for maxSendableAmount (indicates estimate). + */ + isMaxAmountEstimate?: boolean; +}; + +export const AmountInput: React.FC = props => { + const textInputRef = useRef(null); + const { colors } = useTheme(); + const amount = props.amount || '0'; // internally amount is aways a string with a correct number + const { + onChangeText, + unit, + onAmountUnitChange, + disabled = false, + isLoading = false, + maxSendableAmount, + isMaxAmountEstimate, + style: styleOverride, + ...otherProps + } = props; + const [isRateBeingUpdatedLocal, setIsRateBeingUpdatedLocal] = useState(false); + const [outdatedRefreshRate, setOutdatedRefreshRate] = useState(); + + const maxLength = useMemo(() => { + switch (unit) { + case BitcoinUnit.BTC: + return 11; + case BitcoinUnit.SATS: + return 15; + default: + return 15; + } + }, [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 ''; + } + switch (unit) { + case BitcoinUnit.BTC: { + const sat = new BigNumber(amount).multipliedBy(100000000).toNumber(); + return formatBalanceWithoutSuffix(sat, BitcoinUnit.LOCAL_CURRENCY, false); + } + case BitcoinUnit.SATS: + return formatBalanceWithoutSuffix(Number(amount), BitcoinUnit.LOCAL_CURRENCY, false); + case BitcoinUnit.LOCAL_CURRENCY: { + let res: string = ''; + if (conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY]) { + // cache hit! we reuse old value that supposedly doesn't have rounding errors + const sats = conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY]; + res = satoshiToBTC(Number(sats)); + } else { + res = fiatToBTC(Number(amount)); + } + res = removeTrailingZeros(res); + return `${res} ${loc.units[BitcoinUnit.BTC]}`; + } + } + }, [amount, unit]); + + useEffect(() => { + (async () => { + if (await isRateOutdated()) { + const recent = await mostRecentFetchedRate(); + setOutdatedRefreshRate(recent); + } + })(); + }, []); + + const updateRate = useCallback(async () => { + try { + await updateExchangeRate(); + } finally { + setIsRateBeingUpdatedLocal(false); + if (await isRateOutdated()) { + const recent = await mostRecentFetchedRate(); + setOutdatedRefreshRate(recent); + } else { + setOutdatedRefreshRate(undefined); + } + } + }, []); + + const changeAmountUnit = useCallback(() => { + let previousUnit = unit; + let newUnit; + // cycle through units BTC -> SAT -> LOCAL_CURRENCY -> BTC + if (previousUnit === BitcoinUnit.BTC) { + newUnit = BitcoinUnit.SATS; + } else if (previousUnit === BitcoinUnit.SATS) { + newUnit = BitcoinUnit.LOCAL_CURRENCY; + } else if (previousUnit === BitcoinUnit.LOCAL_CURRENCY) { + newUnit = BitcoinUnit.BTC; + } else { + newUnit = BitcoinUnit.BTC; + previousUnit = BitcoinUnit.SATS; + } + + /** + * 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 + */ + let sats: string = '0'; + switch (previousUnit) { + case BitcoinUnit.BTC: + sats = new BigNumber(amount).multipliedBy(100000000).toString(); + break; + case BitcoinUnit.SATS: + sats = amount; + break; + case BitcoinUnit.LOCAL_CURRENCY: + sats = new BigNumber(fiatToBTC(+amount)).multipliedBy(100000000).toString(); + break; + } + if (previousUnit === BitcoinUnit.LOCAL_CURRENCY && conversionCache[amount + previousUnit]) { + // cache hit! we reuse old value that supposedly doesnt have rounding errors + sats = conversionCache[amount + previousUnit]; + } + + const newInputValue = formatBalancePlain(+sats, newUnit, false); + + if (newUnit === BitcoinUnit.LOCAL_CURRENCY && previousUnit === BitcoinUnit.SATS) { + // we cache conversion, so when we will need reverse conversion there wont be a rounding error + conversionCache[newInputValue + newUnit] = amount; + } + onChangeText(newInputValue); + onAmountUnitChange(newUnit); + }, [amount, onChangeText, onAmountUnitChange, unit]); + + const handleTextInputOnPress = useCallback(() => { + textInputRef?.current?.focus(); + }, []); + + const handleChangeText = useCallback( + (text: string) => { + text = text.trim(); + if (unit !== BitcoinUnit.LOCAL_CURRENCY) { + text = text.replace(',', '.'); + const split = text.split('.'); + if (split.length >= 2) { + text = `${parseInt(split[0], 10)}.${split[1]}`; + } else { + text = `${parseInt(split[0], 10)}`; + } + + text = unit === BitcoinUnit.BTC ? text.replace(/[^0-9.]/g, '') : text.replace(/[^0-9]/g, ''); + } else { + text = text.replace(/,/gi, '.'); + if (text.split('.').length > 2) { + // too many dots. stupid code to remove all but first dot: + let rez = ''; + let first = true; + for (const part of text.split('.')) { + rez += part; + if (first) { + rez += '.'; + first = false; + } + } + text = rez; + } + if (text.startsWith('0') && !(text.includes('.') || text.includes(','))) { + text = text.replace(/^(0+)/g, ''); + } + text = text.replace(/[^\d.,-]/g, ''); // remove all but numbers, dots & commas + text = text.replace(/(\..*)\./g, '$1'); + } + if (text.startsWith('.')) { + text = '0.'; + } + onChangeText(text); + }, + [onChangeText, unit], + ); + + const resetAmount = useCallback(async () => { + if (await confirm(loc.send.reset_amount, loc.send.reset_amount_confirm)) { + onChangeText('0'); + } + }, [onChangeText]); + + const copyMaxEstimate = useCallback(() => { + if (maxSendableAmount == null) return; + const btcValue = removeTrailingZeros(new BigNumber(maxSendableAmount).dividedBy(100000000).toFixed(8)); + Clipboard.setString(btcValue); + triggerHapticFeedback(HapticFeedbackTypes.Selection); + }, [maxSendableAmount]); + + const handleSelectionChange = useCallback( + (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 } }); + } + }, + [amount], + ); + + const isCryptoUnit = unit !== BitcoinUnit.LOCAL_CURRENCY; + + const amountCharacters = useMemo(() => measureAmountText.split(''), [measureAmountText]); + + const displayJustifyContent = useMemo((): 'flex-start' | 'flex-end' | 'center' => { + if (inputTextAlign === 'right') return 'flex-end'; + if (inputTextAlign === 'left') return 'flex-start'; + return 'center'; + }, [inputTextAlign]); + + const inputTextColor = disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2; + const hiddenInputTextColor = Platform.OS === 'android' ? `${inputTextColor}00` : 'transparent'; + + const inputTypography = { + fontSize: inputFontSize, + lineHeight: Math.round(inputFontSize * 1.15), + minHeight: Math.round(inputFontSize * 1.15) + INPUT_VERTICAL_PADDING * 2, + textAlign: inputTextAlign, + ...(isCryptoUnit && { + paddingLeft: INPUT_HORIZONTAL_PADDING + 4, + }), + }; + + const stylesHook = { + container: { + marginLeft: unit === BitcoinUnit.LOCAL_CURRENCY ? 0 : CRYPTO_CONTAINER_OFFSET, + }, + localCurrency: { color: inputTextColor }, + input: { + color: inputTextColor, + ...inputTypography, + }, + inputDisplay: { + justifyContent: displayJustifyContent, + ...(isCryptoUnit && { + paddingLeft: INPUT_HORIZONTAL_PADDING + 4, + }), + }, + inputGlyph: { + color: inputTextColor, + fontSize: inputTypography.fontSize, + lineHeight: inputTypography.lineHeight, + }, + inputTransparent: { + color: hiddenInputTextColor, + }, + cryptoCurrency: { color: inputTextColor }, + }; + + return ( + + + {!disabled && } + + + {unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && ( + {getCurrencySymbol()} + )} + {amount !== BitcoinUnit.MAX ? ( + + + {measureAmountText} + + + {amountCharacters.map((char, index) => ( + + {char} + + ))} + + + + ) : ( + + + {BitcoinUnit.MAX} + + {maxSendableAmount != null && ( + + {(isMaxAmountEstimate ? '≈ ' : '') + + removeTrailingZeros(new BigNumber(maxSendableAmount).dividedBy(100000000).toFixed(8)) + + ' ' + + loc.units[BitcoinUnit.BTC]} + + )} + + )} + {unit !== BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && ( + {loc.units[unit]} + )} + + + + {secondaryDisplayCurrency} + + + + {!disabled && + (amount !== BitcoinUnit.MAX ? ( + + + + ) : ( + + ))} + + {outdatedRefreshRate && ( + + + + {loc.formatString(loc.send.outdated_rate, { date: dayjs(outdatedRefreshRate.LastUpdated).format('l LT') })} + + + + + + )} + + ); +}; + +const styles = StyleSheet.create({ + root: { + flexDirection: 'row', + justifyContent: 'space-between', + }, + flex: { + flex: 1, + overflow: 'visible', + }, + sideRail: { + width: SWAP_ICON_SIZE, + alignItems: 'center', + justifyContent: 'center', + alignSelf: 'center', + }, + spacing8: { + width: 8, + }, + warningBadge: { + width: 10, + height: 10, + borderRadius: 5, + backgroundColor: '#fc990e', + }, + disabledButton: { + opacity: 0.5, + }, + outdatedRateContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + margin: 16, + }, + container: { + flexDirection: 'row', + alignItems: 'center', + alignContent: 'space-between', + justifyContent: 'center', + paddingTop: 16, + paddingBottom: 2, + overflow: 'visible', + }, + localCurrency: { + fontSize: 18, + 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: 2, + }, + inputOverlay: { + ...StyleSheet.absoluteFill, + zIndex: 1, + }, + cryptoCurrency: { + fontSize: 15, + marginLeft: 2, + fontWeight: '600', + alignSelf: 'center', + justifyContent: 'center', + }, + secondaryRoot: { + alignItems: 'center', + marginBottom: 22, + }, + secondaryText: { + fontSize: 16, + color: '#9BA0A9', + fontWeight: '600', + }, + maxEstimate: { + fontSize: 16, + textAlign: 'center', + marginTop: 4, + }, + maxPressable: { + alignItems: 'center', + flexShrink: 0, + }, + maxLabel: { + flexShrink: 0, + }, + changeAmountUnit: { + paddingVertical: 16, + }, +}); diff --git a/components/ArrowPicker.tsx b/components/ArrowPicker.tsx deleted file mode 100644 index 273b9cc2b1a..00000000000 --- a/components/ArrowPicker.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */ -import { StyleSheet, Pressable, View, Keyboard } from 'react-native'; -import { Text, Icon } from 'react-native-elements'; -import React, { useState } from 'react'; -import loc from '../loc'; -import { useTheme } from '@react-navigation/native'; - -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: { - // @ts-ignore: Ignore theme typescript error - color: colors.foregroundColor, - }, - }; - return ( - - { - 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, - ]} - > - {/* -// @ts-ignore: Ignore */} - - - - {props.isItemUnknown ? loc.send.fee_custom : keys[keyIndex]} - - { - 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, - ]} - > - {/* -// @ts-ignore: Ignore */} - - - - ); -}; - -const styles = StyleSheet.create({ - wrapperCustom: { - borderRadius: 8, - padding: 5, - marginLeft: 20, - marginRight: 20, - }, - text: { fontWeight: 'bold', fontSize: 12, textAlign: 'center' }, -}); diff --git a/components/Avatar.tsx b/components/Avatar.tsx new file mode 100644 index 00000000000..73d688b7650 --- /dev/null +++ b/components/Avatar.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Pressable, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; +import Icon, { type IconProps } from './Icon'; + +export interface AvatarProps { + rounded?: boolean; + size: number; + containerStyle: StyleProp; + icon?: Pick; + onPress?: () => void; +} + +const Avatar: React.FC = ({ rounded, size, containerStyle, icon, onPress }) => { + const dimensionStyle = { width: size, height: size, borderRadius: rounded ? size / 2 : 0 } as ViewStyle; + const content = ( + + {icon ? : null} + + ); + + if (onPress) { + return ( + + {content} + + ); + } + + return content; +}; + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + justifyContent: 'center', + }, + pressable: { + alignSelf: 'flex-start', + }, +}); + +export default Avatar; diff --git a/components/Badge.tsx b/components/Badge.tsx new file mode 100644 index 00000000000..18dd770a3e2 --- /dev/null +++ b/components/Badge.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle } from 'react-native'; + +export interface BadgeProps { + value?: string | number | React.ReactNode; + badgeStyle?: StyleProp; + textStyle?: StyleProp; + testID?: string; +} + +const Badge: React.FC = ({ value, badgeStyle, textStyle, testID }) => { + return ( + + {typeof value === 'string' || typeof value === 'number' ? {value} : value} + + ); +}; + +const styles = StyleSheet.create({ + badge: { + minHeight: 18, + paddingHorizontal: 6, + borderRadius: 9, + alignItems: 'center', + justifyContent: 'center', + }, + text: { + fontSize: 12, + fontWeight: '600', + }, +}); + +export default Badge; diff --git a/components/BlueBigCheckmark.tsx b/components/BlueBigCheckmark.tsx new file mode 100644 index 00000000000..80d2d1bade9 --- /dev/null +++ b/components/BlueBigCheckmark.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { StyleSheet, View, ViewProps } from 'react-native'; +import Icon from './Icon'; +import { useTheme } from './themes'; + +interface BlueBigCheckmarkProps extends ViewProps {} + +export function BlueBigCheckmark(props: BlueBigCheckmarkProps) { + const { colors } = useTheme(); + return ( + + + + ); +} + +const styles = StyleSheet.create({ + container: { + backgroundColor: '#ccddf9', + width: 120, + height: 120, + borderRadius: 60, + alignSelf: 'center', + justifyContent: 'center', + marginTop: 0, + marginBottom: 0, + }, +}); diff --git a/components/BlueButtonLink.tsx b/components/BlueButtonLink.tsx new file mode 100644 index 00000000000..351dfffba80 --- /dev/null +++ b/components/BlueButtonLink.tsx @@ -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, BlueButtonLinkProps>((props, ref) => { + const { colors } = useTheme(); + return ( + [styles.blueButtonLink, pressed && styles.pressed]} {...props} ref={ref}> + {props.title} + + ); +}); + +const styles = StyleSheet.create({ + blueButtonLink: { + minWidth: 100, + minHeight: 36, + justifyContent: 'center', + }, + blueButtonLinkText: { + textAlign: 'center', + fontSize: 16, + }, + pressed: { + opacity: 0.6, + }, +}); + +export default BlueButtonLink; diff --git a/components/BlueCard.tsx b/components/BlueCard.tsx new file mode 100644 index 00000000000..c7a9cccffce --- /dev/null +++ b/components/BlueCard.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { StyleSheet, View, ViewProps } from 'react-native'; + +const BlueCard: React.FC = props => { + return ; +}; + +const styles = StyleSheet.create({ + blueCard: { + padding: 20, + }, +}); + +export default BlueCard; diff --git a/components/BlueFormLabel.tsx b/components/BlueFormLabel.tsx new file mode 100644 index 00000000000..42928a0af8b --- /dev/null +++ b/components/BlueFormLabel.tsx @@ -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 = props => { + const { colors } = useTheme(); + const { direction } = useLocale(); + + return ; +}; + +const styles = StyleSheet.create({ + blueFormLabel: { + fontWeight: '400', + marginHorizontal: 20, + }, +}); + +export default BlueFormLabel; diff --git a/components/BlueFormMultiInput.tsx b/components/BlueFormMultiInput.tsx new file mode 100644 index 00000000000..4564286bf54 --- /dev/null +++ b/components/BlueFormMultiInput.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { Platform, StyleSheet, TextInput, TextInputProps } from 'react-native'; + +import { useTheme } from './themes'; + +const BlueFormMultiInput: React.FC = props => { + const { colors } = useTheme(); + const { style, editable, ...restProps } = props; + + return ( + + ); +}; + +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; diff --git a/components/BlueLoading.tsx b/components/BlueLoading.tsx new file mode 100644 index 00000000000..3545cd7b40a --- /dev/null +++ b/components/BlueLoading.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { ActivityIndicator, View, ViewProps, ActivityIndicatorProps, StyleSheet } from 'react-native'; +import { useTheme } from './themes'; + +interface BlueLoadingProps extends ViewProps, Pick {} + +export const BlueLoading: React.FC = props => { + const { color, size, ...otherProps } = props; + const { colors } = useTheme(); + + return ( + + + + ); +}; + +const styles = StyleSheet.create({ + container: { flex: 1, justifyContent: 'center' }, +}); diff --git a/components/BlueSpacing.tsx b/components/BlueSpacing.tsx new file mode 100644 index 00000000000..4789a6e78b1 --- /dev/null +++ b/components/BlueSpacing.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { View, ViewProps, StyleSheet } from 'react-native'; + +interface BlueSpacingProps extends ViewProps { + horizontal?: boolean; // Optional prop to determine if spacing is horizontal +} + +export const BlueSpacing10: React.FC = props => { + const { style, ...otherProps } = props; + return ; +}; + +export const BlueSpacing20: React.FC = props => { + const { horizontal = false, style, ...otherProps } = props; + return ; +}; + +export const BlueSpacing40: React.FC = props => { + const { style, ...otherProps } = props; + return ; +}; + +export const BlueSpacing: React.FC = props => { + const { style, ...otherProps } = props; + return ; +}; + +const styles = StyleSheet.create({ + spacing10: { + height: 10, + }, + spacing20Vertical: { + height: 20, + width: 0, + opacity: 0, + }, + spacing20Horizontal: { + height: 0, + width: 20, + opacity: 0, + }, + spacing40: { + height: 40, + }, + spacing60: { + height: 60, + }, +}); diff --git a/components/BlueText.tsx b/components/BlueText.tsx new file mode 100644 index 00000000000..1c5645fc6ba --- /dev/null +++ b/components/BlueText.tsx @@ -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 = ({ 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 ; +}; + +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; diff --git a/components/BlueTextCentered.tsx b/components/BlueTextCentered.tsx new file mode 100644 index 00000000000..d20b922cc55 --- /dev/null +++ b/components/BlueTextCentered.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { StyleSheet, Text, TextProps } from 'react-native'; + +import { useTheme } from './themes'; + +const BlueTextCentered: React.FC = props => { + const { colors } = useTheme(); + return ; +}; + +const styles = StyleSheet.create({ + blueTextCentered: { + textAlign: 'center', + }, +}); + +export default BlueTextCentered; diff --git a/components/BlurredBalanceView.tsx b/components/BlurredBalanceView.tsx new file mode 100644 index 00000000000..67ca8d6d84b --- /dev/null +++ b/components/BlurredBalanceView.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import Icon from './Icon'; + +export const BlurredBalanceView = () => { + return ( + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + borderRadius: 9, + }, + background: { + backgroundColor: 'rgba(255, 255, 255, 0.5)', + height: 30, + width: 110, + marginRight: 8, + borderRadius: 9, + }, +}); diff --git a/components/BottomModal.js b/components/BottomModal.js deleted file mode 100644 index 40a42979e6c..00000000000 --- a/components/BottomModal.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { StyleSheet, Platform, useWindowDimensions, View } from 'react-native'; -import Modal from 'react-native-modal'; -import { BlueButton, BlueSpacing10 } from '../BlueComponents'; -import loc from '../loc'; -import { useTheme } from '@react-navigation/native'; - -const styles = StyleSheet.create({ - root: { - justifyContent: 'flex-end', - margin: 0, - }, - hasDoneButton: { - padding: 16, - paddingBottom: 24, - }, -}); - -const BottomModal = ({ - onBackButtonPress = undefined, - onBackdropPress = undefined, - onClose, - windowHeight = undefined, - windowWidth = undefined, - doneButton = undefined, - avoidKeyboard = false, - allowBackdropPress = true, - ...props -}) => { - const valueWindowHeight = useWindowDimensions().height; - const valueWindowWidth = useWindowDimensions().width; - const handleBackButtonPress = onBackButtonPress ?? onClose; - const handleBackdropPress = allowBackdropPress ? onBackdropPress ?? onClose : undefined; - const { colors } = useTheme(); - const stylesHook = StyleSheet.create({ - hasDoneButton: { - backgroundColor: colors.elevated, - }, - }); - return ( - - {props.children} - {doneButton && ( - - - - - )} - - ); -}; - -BottomModal.propTypes = { - children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.element), PropTypes.element]), - onBackButtonPress: PropTypes.func, - onBackdropPress: PropTypes.func, - onClose: PropTypes.func, - doneButton: PropTypes.bool, - windowHeight: PropTypes.number, - windowWidth: PropTypes.number, - avoidKeyboard: PropTypes.bool, - allowBackdropPress: PropTypes.bool, -}; - -export default BottomModal; diff --git a/components/Button.js b/components/Button.js deleted file mode 100644 index ac2c1a82ae5..00000000000 --- a/components/Button.js +++ /dev/null @@ -1,85 +0,0 @@ -import React from 'react'; -import { TouchableOpacity, View, Text, StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; -import { useTheme } from '@react-navigation/native'; - -export const ButtonStyle = { default: 'default', destroy: 'destroy', grey: 'grey' }; -const Button = props => { - const { onPress, text = '', disabled = false, buttonStyle = ButtonStyle.default } = props; - const { colors } = useTheme(); - const stylesHook = StyleSheet.create({ - buttonGrey: { - backgroundColor: colors.lightButton, - }, - textGray: { - color: colors.buttonTextColor, - }, - }); - const textStyles = () => { - if (buttonStyle === ButtonStyle.grey) { - return stylesHook.textGray; - } else if (buttonStyle === ButtonStyle.destroy) { - return styles.textDestroy; - } else { - return styles.textDefault; - } - }; - - const buttonStyles = () => { - if (buttonStyle === ButtonStyle.grey) { - return stylesHook.buttonGrey; - } else if (buttonStyle === ButtonStyle.destroy) { - return styles.buttonDestroy; - } else { - return styles.buttonDefault; - } - }; - - const opacity = { opacity: disabled ? 0.5 : 1.0 }; - - return ( - - - {text} - - - ); -}; - -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 Button; -Button.propTypes = { - onPress: PropTypes.func.isRequired, - text: PropTypes.string.isRequired, - disabled: PropTypes.bool, - buttonStyle: PropTypes.string, -}; diff --git a/components/Button.tsx b/components/Button.tsx new file mode 100644 index 00000000000..54c6c88ca48 --- /dev/null +++ b/components/Button.tsx @@ -0,0 +1,100 @@ +import React, { forwardRef } from 'react'; +import { ActivityIndicator, StyleProp, StyleSheet, Text, Pressable, PressableProps, View, ViewStyle, Platform } from 'react-native'; +import Icon, { type IconProps } from './Icon'; + +import { useTheme } from './themes'; + +interface ButtonProps extends PressableProps { + backgroundColor?: string; + buttonTextColor?: string; + disabled?: boolean; + testID?: string; + icon?: Pick & { color: string }; + title?: string; + style?: StyleProp; + onPress?: () => void; + showActivityIndicator?: boolean; +} + +export const Button = forwardRef, ButtonProps>((props, ref) => { + const { colors } = useTheme(); + + let backgroundColor = props.backgroundColor ?? colors.mainColor; + let fontColor = props.buttonTextColor ?? colors.buttonTextColor; + if (props.disabled) { + backgroundColor = colors.buttonDisabledBackgroundColor; + fontColor = colors.buttonDisabledTextColor; + } + + const buttonStyle = { + ...styles.button, + backgroundColor, + borderColor: props.disabled ? colors.buttonDisabledBackgroundColor : 'transparent', + }; + + const textStyle = { + ...styles.text, + color: fontColor, + }; + + const buttonView = props.showActivityIndicator ? ( + + ) : ( + <> + {props.icon && } + {props.title && {props.title}} + + ); + + return props.onPress ? ( + + [Platform.OS === 'ios' && pressed ? styles.pressed : null, buttonStyle, props.style, styles.content]} + accessibilityRole="button" + onPress={props.onPress} + disabled={props.disabled} + {...props} + > + {buttonView} + + + ) : ( + {buttonView} + ); +}); + +const styles = StyleSheet.create({ + button: { + borderWidth: 0.7, + minHeight: 45, + height: 48, + maxHeight: 48, + borderRadius: 25, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 16, + flexGrow: 1, + }, + content: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + }, + text: { + marginHorizontal: 8, + fontSize: 16, + fontWeight: '600', + }, + pressableWrapper: { + overflow: 'hidden', + borderRadius: 25, + }, + pressed: { + opacity: 0.6, + }, +}); + +export default Button; diff --git a/components/CameraScreen.tsx b/components/CameraScreen.tsx new file mode 100644 index 00000000000..43ab2082826 --- /dev/null +++ b/components/CameraScreen.tsx @@ -0,0 +1,264 @@ +import React, { useRef, useState } from 'react'; +import { Animated, StyleSheet, TouchableOpacity, View } from 'react-native'; +import { Camera, CameraApi, CameraType, Orientation } from 'react-native-camera-kit-no-google'; +import { OnOrientationChangeData, OnReadCodeData } from 'react-native-camera-kit-no-google/dist/CameraProps'; + +import { isDesktop } from '../blue_modules/environment'; +import { triggerSelectionHapticFeedback } from '../blue_modules/hapticFeedback'; +import loc from '../loc'; +import Icon from './Icon'; + +interface CameraScreenProps { + onCancelButtonPress: () => void; + showImagePickerButton?: boolean; + showFilePickerButton?: boolean; + onImagePickerButtonPress?: () => void; + onFilePickerButtonPress?: () => void; + onReadCode?: (event: OnReadCodeData) => void; +} + +const CameraScreen: React.FC = ({ + onCancelButtonPress, + showImagePickerButton, + showFilePickerButton, + onImagePickerButtonPress, + onFilePickerButtonPress, + onReadCode, +}) => { + const cameraRef = useRef(null); + const [torchMode, setTorchMode] = useState(false); + const [cameraType, setCameraType] = useState(CameraType.Back); + const [zoom, setZoom] = useState(); + const [orientationAnim] = useState(new Animated.Value(3)); + + const onSwitchCameraPressed = () => { + const direction = cameraType === CameraType.Back ? CameraType.Front : CameraType.Back; + setCameraType(direction); + setZoom(1); // When changing camera type, reset to default zoom for that camera + triggerSelectionHapticFeedback(); + }; + + const onSetTorch = () => { + setTorchMode(!torchMode); + triggerSelectionHapticFeedback(); + }; + + // Counter-rotate the icons to indicate the actual orientation of the captured photo. + // For this example, it'll behave incorrectly since UI orientation is allowed (and already-counter rotates the entire screen) + // For real phone apps, lock your UI orientation using a library like 'react-native-orientation-locker' + const rotateUi = true; + const uiRotation = orientationAnim.interpolate({ + inputRange: [1, 2, 3, 4], + outputRange: ['180deg', '90deg', '0deg', '-90deg'], + }); + const uiRotationStyle = rotateUi ? { transform: [{ rotate: uiRotation }] } : {}; + + function rotateUiTo(rotationValue: number) { + Animated.timing(orientationAnim, { + toValue: rotationValue, + useNativeDriver: true, + duration: 200, + isInteraction: false, + }).start(); + } + + const handleZoom = (e: { nativeEvent: { zoom: number } }) => { + console.debug('zoom', e.nativeEvent.zoom); + setZoom(e.nativeEvent.zoom); + }; + + const handleOrientationChange = (e: OnOrientationChangeData) => { + switch (e.nativeEvent.orientation) { + case Orientation.PORTRAIT_UPSIDE_DOWN: + console.debug('orientationChange', 'PORTRAIT_UPSIDE_DOWN'); + rotateUiTo(1); + break; + case Orientation.LANDSCAPE_LEFT: + console.debug('orientationChange', 'LANDSCAPE_LEFT'); + rotateUiTo(2); + break; + case Orientation.PORTRAIT: + console.debug('orientationChange', 'PORTRAIT'); + rotateUiTo(3); + break; + case Orientation.LANDSCAPE_RIGHT: + console.debug('orientationChange', 'LANDSCAPE_RIGHT'); + rotateUiTo(4); + break; + default: + console.debug('orientationChange', e.nativeEvent); + break; + } + }; + + const handleReadCode = (event: OnReadCodeData) => { + onReadCode?.(event); + }; + + return ( + + {/* Render top buttons only if not desktop as they would not be relevant */} + {!isDesktop && ( + + + + + + + + {showImagePickerButton && ( + + + + + + )} + {showFilePickerButton && ( + + + + + + )} + + + )} + + + + + + {loc._.cancel} + + {isDesktop ? ( + + {showImagePickerButton && ( + + + + + + )} + {showFilePickerButton && ( + + + + + + )} + + ) : ( + + + + + + )} + + + ); +}; + +export default CameraScreen; + +const styles = StyleSheet.create({ + activeTorch: { + backgroundColor: '#fff', + }, + screen: { + height: '100%', + backgroundColor: '#000000', + }, + topButtons: { + padding: 10, + zIndex: 10, + flexDirection: 'row', + justifyContent: 'space-between', + }, + topButton: { + backgroundColor: '#222', + width: 44, + height: 44, + borderRadius: 22, + justifyContent: 'center', + alignItems: 'center', + }, + topButtonImg: { + margin: 10, + width: 24, + height: 24, + }, + cameraContainer: { + justifyContent: 'center', + flex: 1, + }, + cameraPreview: { + width: '100%', + height: '100%', + }, + bottomButtons: { + padding: 10, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + backTextStyle: { + padding: 10, + color: 'white', + fontSize: 20, + }, + rightButtonsContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + bottomButton: { + backgroundColor: '#222', + width: 44, + height: 44, + borderRadius: 22, + justifyContent: 'center', + alignItems: 'center', + marginLeft: 10, + }, + spacing: { + marginLeft: 20, + }, +}); diff --git a/components/CoinsSelected.js b/components/CoinsSelected.js deleted file mode 100644 index 2c7c12550a1..00000000000 --- a/components/CoinsSelected.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; -import { Avatar } from 'react-native-elements'; - -import loc from '../loc'; - -const styles = StyleSheet.create({ - root: { - height: 48, - borderRadius: 8, - backgroundColor: '#3477F6', - flexDirection: 'row', - }, - labelContainer: { - flex: 1, - justifyContent: 'center', - paddingLeft: 16, - }, - labelText: { - color: 'white', - fontWeight: 'bold', - }, - buttonContainer: { - width: 48, - alignItems: 'center', - justifyContent: 'center', - }, - ball: { - width: 26, - height: 26, - borderRadius: 13, - backgroundColor: 'rgba(255, 255, 255, 0.32)', - }, -}); - -const CoinsSelected = ({ number, onContainerPress, onClose }) => ( - - - {loc.formatString(loc.cc.coins_selected, { number })} - - - - - -); - -CoinsSelected.propTypes = { - number: PropTypes.number.isRequired, - onContainerPress: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, -}; - -export default CoinsSelected; diff --git a/components/CoinsSelected.tsx b/components/CoinsSelected.tsx new file mode 100644 index 00000000000..5575acdaa9e --- /dev/null +++ b/components/CoinsSelected.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import Avatar from './Avatar'; + +import loc from '../loc'; + +const styles = StyleSheet.create({ + root: { + height: 48, + borderRadius: 8, + backgroundColor: '#3477F6', + flexDirection: 'row', + }, + labelContainer: { + flex: 1, + justifyContent: 'center', + paddingLeft: 16, + }, + labelText: { + color: 'white', + fontWeight: 'bold', + }, + buttonContainer: { + width: 48, + alignItems: 'center', + justifyContent: 'center', + }, + ball: { + width: 26, + height: 26, + borderRadius: 13, + backgroundColor: 'rgba(255, 255, 255, 0.32)', + }, +}); + +interface CoinsSelectedProps { + number: number; + onContainerPress: () => void; + onClose: () => void; +} + +const CoinsSelected: React.FC = ({ number, onContainerPress, onClose }) => ( + + + {loc.formatString(loc.cc.coins_selected, { number })} + + + + + +); + +export default CoinsSelected; diff --git a/components/Context/SettingsProvider.tsx b/components/Context/SettingsProvider.tsx new file mode 100644 index 00000000000..ec00ae9dc06 --- /dev/null +++ b/components/Context/SettingsProvider.tsx @@ -0,0 +1,398 @@ +import React, { createContext, useCallback, useEffect, useMemo, useState } from 'react'; +import DefaultPreference from 'react-native-default-preference'; +import { isReadClipboardAllowed, setReadClipboardAllowed } from '../../blue_modules/clipboard'; +import { getPreferredCurrency, GROUP_IO_BLUEWALLET, initCurrencyDaemon, setPreferredCurrency } from '../../blue_modules/currency'; +import { clearUseURv1, isURv1Enabled, setUseURv1 } from '../../blue_modules/ur'; +import { BlueApp } from '../../class/blue-app'; +import { saveLanguage, STORAGE_KEY } from '../../loc'; +import { FiatUnit, TFiatUnit } from '../../models/fiatUnit'; +import { + getEnabled as getIsDeviceQuickActionsEnabled, + setEnabled as setIsDeviceQuickActionsEnabled, +} from '../../hooks/useDeviceQuickActions'; +import { getIsHandOffUseEnabled, setIsHandOffUseEnabled } from '../HandOffComponent'; +import { useStorage } from '../../hooks/context/useStorage'; +import { BitcoinUnit } from '../../models/bitcoinUnits'; +import { TotalWalletsBalanceKey, TotalWalletsBalancePreferredUnit } from '../TotalWalletsBalance'; +import { BLOCK_EXPLORERS, getBlockExplorerUrl, saveBlockExplorer, BlockExplorer, normalizeUrl } from '../../models/blockExplorer'; +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; +import { isBalanceDisplayAllowed, setBalanceDisplayAllowed } from '../../hooks/useWidgetCommunication'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const getDoNotTrackStorage = async (): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const doNotTrack = await DefaultPreference.get(BlueApp.DO_NOT_TRACK); + return doNotTrack === '1'; + } catch { + console.error('Error getting DoNotTrack'); + return false; + } +}; + +export const setTotalBalanceViewEnabledStorage = async (value: boolean): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + await DefaultPreference.set(TotalWalletsBalanceKey, value ? 'true' : 'false'); + console.debug('setTotalBalanceViewEnabledStorage value:', value); + } catch (e) { + console.error('Error setting TotalBalanceViewEnabled:', e); + } +}; + +export const getIsTotalBalanceViewEnabled = async (): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const isEnabledValue = (await DefaultPreference.get(TotalWalletsBalanceKey)) ?? 'true'; + console.debug('getIsTotalBalanceViewEnabled', isEnabledValue); + return isEnabledValue === 'true'; + } catch (e) { + console.error('Error getting TotalBalanceViewEnabled:', e); + return true; + } +}; + +export const setTotalBalancePreferredUnitStorageFunc = async (unit: BitcoinUnit): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + await DefaultPreference.set(TotalWalletsBalancePreferredUnit, unit); + } catch (e) { + console.error('Error setting TotalBalancePreferredUnit:', e); + } +}; + +export const getTotalBalancePreferredUnit = async (): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const unit = (await DefaultPreference.get(TotalWalletsBalancePreferredUnit)) as BitcoinUnit | null; + return unit ?? BitcoinUnit.BTC; + } catch (e) { + console.error('Error getting TotalBalancePreferredUnit:', e); + return BitcoinUnit.BTC; + } +}; + +interface SettingsContextType { + preferredFiatCurrency: TFiatUnit; + setPreferredFiatCurrencyStorage: (currency: TFiatUnit) => Promise; + language: string; + setLanguageStorage: (language: string) => Promise; + isHandOffUseEnabled: boolean; + setIsHandOffUseEnabledAsyncStorage: (value: boolean) => Promise; + isPrivacyBlurEnabled: boolean; + setIsPrivacyBlurEnabled: (value: boolean) => void; + isDoNotTrackEnabled: boolean; + setDoNotTrackStorage: (value: boolean) => Promise; + isWidgetBalanceDisplayAllowed: boolean; + setIsWidgetBalanceDisplayAllowedStorage: (value: boolean) => Promise; + isLegacyURv1Enabled: boolean; + setIsLegacyURv1EnabledStorage: (value: boolean) => Promise; + isClipboardGetContentEnabled: boolean; + setIsClipboardGetContentEnabledStorage: (value: boolean) => Promise; + isQuickActionsEnabled: boolean; + setIsQuickActionsEnabledStorage: (value: boolean) => Promise; + isTotalBalanceEnabled: boolean; + setIsTotalBalanceEnabledStorage: (value: boolean) => Promise; + totalBalancePreferredUnit: BitcoinUnit; + setTotalBalancePreferredUnitStorage: (unit: BitcoinUnit) => Promise; + selectedBlockExplorer: BlockExplorer; + setBlockExplorerStorage: (explorer: BlockExplorer) => Promise; + isElectrumDisabled: boolean; + setIsElectrumDisabled: (value: boolean) => void; +} + +const defaultSettingsContext: SettingsContextType = { + preferredFiatCurrency: FiatUnit.USD, + setPreferredFiatCurrencyStorage: async () => {}, + language: 'en', + setLanguageStorage: async () => {}, + isHandOffUseEnabled: false, + setIsHandOffUseEnabledAsyncStorage: async () => {}, + isPrivacyBlurEnabled: true, + setIsPrivacyBlurEnabled: () => {}, + isDoNotTrackEnabled: false, + setDoNotTrackStorage: async () => {}, + isWidgetBalanceDisplayAllowed: true, + setIsWidgetBalanceDisplayAllowedStorage: async () => {}, + isLegacyURv1Enabled: false, + setIsLegacyURv1EnabledStorage: async () => {}, + isClipboardGetContentEnabled: true, + setIsClipboardGetContentEnabledStorage: async () => {}, + isQuickActionsEnabled: true, + setIsQuickActionsEnabledStorage: async () => {}, + isTotalBalanceEnabled: true, + setIsTotalBalanceEnabledStorage: async () => {}, + totalBalancePreferredUnit: BitcoinUnit.BTC, + setTotalBalancePreferredUnitStorage: async () => {}, + selectedBlockExplorer: BLOCK_EXPLORERS.default, + setBlockExplorerStorage: async () => false, + isElectrumDisabled: false, + setIsElectrumDisabled: () => {}, +}; + +export const SettingsContext = createContext(defaultSettingsContext); + +export const SettingsProvider: React.FC<{ children: React.ReactNode }> = React.memo(({ children }: { children: React.ReactNode }) => { + const [preferredFiatCurrency, setPreferredFiatCurrencyState] = useState(FiatUnit.USD); + const [language, setLanguage] = useState('en'); + const [isHandOffUseEnabled, setIsHandOffUseEnabledState] = useState(false); + const [isPrivacyBlurEnabled, setIsPrivacyBlurEnabled] = useState(true); + const [isDoNotTrackEnabled, setIsDoNotTrackEnabled] = useState(false); + const [isWidgetBalanceDisplayAllowed, setIsWidgetBalanceDisplayAllowed] = useState(true); + const [isLegacyURv1Enabled, setIsLegacyURv1Enabled] = useState(false); + const [isClipboardGetContentEnabled, setIsClipboardGetContentEnabled] = useState(true); + const [isQuickActionsEnabled, setIsQuickActionsEnabled] = useState(true); + const [isTotalBalanceEnabled, setIsTotalBalanceEnabled] = useState(true); + const [totalBalancePreferredUnit, setTotalBalancePreferredUnit] = useState(BitcoinUnit.BTC); + const [selectedBlockExplorer, setSelectedBlockExplorer] = useState(BLOCK_EXPLORERS.default); + const [isElectrumDisabled, setIsElectrumDisabled] = useState(true); + + const { walletsInitialized } = useStorage(); + + useEffect(() => { + const loadSettings = async () => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + } catch (e) { + console.error('Error setting preference name:', e); + } + + const promises: Promise[] = [ + BlueElectrum.isDisabled().then(disabled => { + setIsElectrumDisabled(disabled); + }), + getIsHandOffUseEnabled().then(handOff => { + setIsHandOffUseEnabledState(handOff); + }), + AsyncStorage.getItem(STORAGE_KEY).then(lang => { + setLanguage(lang ?? 'en'); + }), + isBalanceDisplayAllowed().then(balanceDisplayAllowed => { + setIsWidgetBalanceDisplayAllowed(balanceDisplayAllowed); + }), + isURv1Enabled().then(urv1Enabled => { + setIsLegacyURv1Enabled(urv1Enabled); + }), + isReadClipboardAllowed().then(clipboardEnabled => { + setIsClipboardGetContentEnabled(clipboardEnabled); + }), + getIsDeviceQuickActionsEnabled().then(quickActionsEnabled => { + setIsQuickActionsEnabled(quickActionsEnabled); + }), + getDoNotTrackStorage().then(doNotTrack => { + setIsDoNotTrackEnabled(doNotTrack); + }), + getIsTotalBalanceViewEnabled().then(totalBalanceEnabled => { + setIsTotalBalanceEnabled(totalBalanceEnabled); + }), + getTotalBalancePreferredUnit().then(preferredUnit => { + setTotalBalancePreferredUnit(preferredUnit); + }), + getBlockExplorerUrl().then(url => { + const predefinedExplorer = Object.values(BLOCK_EXPLORERS).find(explorer => normalizeUrl(explorer.url) === normalizeUrl(url)); + setSelectedBlockExplorer(predefinedExplorer ?? ({ key: 'custom', name: 'Custom', url } as BlockExplorer)); + }), + ]; + + const results = await Promise.allSettled(promises); + + results.forEach((result, index) => { + if (result.status === 'rejected') { + console.error(`Error loading setting ${index}:`, result.reason); + } + }); + }; + + loadSettings(); + }, []); + + useEffect(() => { + initCurrencyDaemon() + .then(getPreferredCurrency) + .then(currency => { + console.debug('SettingsContext currency:', currency); + setPreferredFiatCurrencyState(currency as TFiatUnit); + }) + .catch(e => { + console.error('Error initializing currency daemon or getting preferred currency:', e); + }); + }, []); + + useEffect(() => { + if (walletsInitialized) { + isElectrumDisabled ? BlueElectrum.forceDisconnect() : BlueElectrum.connectMain(); + } + }, [isElectrumDisabled, walletsInitialized]); + + const setPreferredFiatCurrencyStorage = useCallback(async (currency: TFiatUnit): Promise => { + try { + await setPreferredCurrency(currency); + setPreferredFiatCurrencyState(currency); + } catch (e) { + console.error('Error setting preferredFiatCurrency:', e); + } + }, []); + + const setLanguageStorage = useCallback(async (newLanguage: string): Promise => { + try { + await saveLanguage(newLanguage); + setLanguage(newLanguage); + } catch (e) { + console.error('Error setting language:', e); + } + }, []); + + const setDoNotTrackStorage = useCallback(async (value: boolean): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + if (value) { + await DefaultPreference.set(BlueApp.DO_NOT_TRACK, '1'); + } else { + await DefaultPreference.clear(BlueApp.DO_NOT_TRACK); + } + setIsDoNotTrackEnabled(value); + } catch (e) { + console.error('Error setting DoNotTrack:', e); + } + }, []); + + const setIsHandOffUseEnabledAsyncStorage = useCallback(async (value: boolean): Promise => { + try { + console.debug('setIsHandOffUseEnabledAsyncStorage', value); + await setIsHandOffUseEnabled(value); + setIsHandOffUseEnabledState(value); + } catch (e) { + console.error('Error setting isHandOffUseEnabled:', e); + } + }, []); + + const setIsWidgetBalanceDisplayAllowedStorage = useCallback(async (value: boolean): Promise => { + try { + await setBalanceDisplayAllowed(value); + setIsWidgetBalanceDisplayAllowed(value); + } catch (e) { + console.error('Error setting isWidgetBalanceDisplayAllowed:', e); + } + }, []); + + const setIsLegacyURv1EnabledStorage = useCallback(async (value: boolean): Promise => { + try { + if (value) { + await setUseURv1(); + } else { + await clearUseURv1(); + } + setIsLegacyURv1Enabled(value); + } catch (e) { + console.error('Error setting isLegacyURv1Enabled:', e); + } + }, []); + + const setIsClipboardGetContentEnabledStorage = useCallback(async (value: boolean): Promise => { + try { + await setReadClipboardAllowed(value); + setIsClipboardGetContentEnabled(value); + } catch (e) { + console.error('Error setting isClipboardGetContentEnabled:', e); + } + }, []); + + const setIsQuickActionsEnabledStorage = useCallback(async (value: boolean): Promise => { + try { + await setIsDeviceQuickActionsEnabled(value); + setIsQuickActionsEnabled(value); + } catch (e) { + console.error('Error setting isQuickActionsEnabled:', e); + } + }, []); + const setIsTotalBalanceEnabledStorage = useCallback(async (value: boolean): Promise => { + try { + await setTotalBalanceViewEnabledStorage(value); + setIsTotalBalanceEnabled(value); + } catch (e) { + console.error('Error setting isTotalBalanceEnabled:', e); + } + }, []); + + const setTotalBalancePreferredUnitStorage = useCallback(async (unit: BitcoinUnit): Promise => { + try { + await setTotalBalancePreferredUnitStorageFunc(unit); + setTotalBalancePreferredUnit(unit); + } catch (e) { + console.error('Error setting totalBalancePreferredUnit:', e); + } + }, []); + + const setBlockExplorerStorage = useCallback(async (explorer: BlockExplorer): Promise => { + try { + const success = await saveBlockExplorer(explorer.url); + if (success) { + setSelectedBlockExplorer(explorer); + } + return success; + } catch (e) { + console.error('Error setting BlockExplorer:', e); + return false; + } + }, []); + + const value = useMemo( + () => ({ + preferredFiatCurrency, + setPreferredFiatCurrencyStorage, + language, + setLanguageStorage, + isHandOffUseEnabled, + setIsHandOffUseEnabledAsyncStorage, + isPrivacyBlurEnabled, + setIsPrivacyBlurEnabled, + isDoNotTrackEnabled, + setDoNotTrackStorage, + isWidgetBalanceDisplayAllowed, + setIsWidgetBalanceDisplayAllowedStorage, + isLegacyURv1Enabled, + setIsLegacyURv1EnabledStorage, + isClipboardGetContentEnabled, + setIsClipboardGetContentEnabledStorage, + isQuickActionsEnabled, + setIsQuickActionsEnabledStorage, + isTotalBalanceEnabled, + setIsTotalBalanceEnabledStorage, + totalBalancePreferredUnit, + setTotalBalancePreferredUnitStorage, + selectedBlockExplorer, + setBlockExplorerStorage, + isElectrumDisabled, + setIsElectrumDisabled, + }), + [ + preferredFiatCurrency, + setPreferredFiatCurrencyStorage, + language, + setLanguageStorage, + isHandOffUseEnabled, + setIsHandOffUseEnabledAsyncStorage, + isPrivacyBlurEnabled, + setIsPrivacyBlurEnabled, + isDoNotTrackEnabled, + setDoNotTrackStorage, + isWidgetBalanceDisplayAllowed, + setIsWidgetBalanceDisplayAllowedStorage, + isLegacyURv1Enabled, + setIsLegacyURv1EnabledStorage, + isClipboardGetContentEnabled, + setIsClipboardGetContentEnabledStorage, + isQuickActionsEnabled, + setIsQuickActionsEnabledStorage, + isTotalBalanceEnabled, + setIsTotalBalanceEnabledStorage, + totalBalancePreferredUnit, + setTotalBalancePreferredUnitStorage, + selectedBlockExplorer, + setBlockExplorerStorage, + isElectrumDisabled, + ], + ); + + return {children}; +}); diff --git a/components/Context/SizeClassProvider.tsx b/components/Context/SizeClassProvider.tsx new file mode 100644 index 00000000000..b71b069d812 --- /dev/null +++ b/components/Context/SizeClassProvider.tsx @@ -0,0 +1,140 @@ +import React, { createContext, ReactNode, useEffect, useMemo, useState } from 'react'; +import { Dimensions, Platform, useWindowDimensions } from 'react-native'; +import { isDesktop, isTablet } from '../../blue_modules/environment'; +import useAppState from '../../hooks/useAppState'; + +export enum SizeClass { + Compact, + Regular, + Large, +} + +interface ISizeClassContext { + sizeClass: SizeClass; + horizontalSizeClass: SizeClass; + verticalSizeClass: SizeClass; + orientation: 'portrait' | 'landscape'; +} + +const useSizeClassDetection = () => { + const dimensions = useWindowDimensions(); + const [horizontalSizeClass, setHorizontalSizeClass] = useState(SizeClass.Regular); + const [verticalSizeClass, setVerticalSizeClass] = useState(SizeClass.Regular); + const [orientation, setOrientation] = useState<'portrait' | 'landscape'>(dimensions.width < dimensions.height ? 'portrait' : 'landscape'); + + const determineSize = () => { + const { width, height } = Dimensions.get('window'); + const isLandscape = width > height; + setOrientation(isLandscape ? 'landscape' : 'portrait'); + + if (isDesktop) { + setHorizontalSizeClass(SizeClass.Large); + setVerticalSizeClass(SizeClass.Large); + return; + } + + if (Platform.OS === 'ios' && Platform.isPad) { + setHorizontalSizeClass(SizeClass.Regular); + setVerticalSizeClass(SizeClass.Regular); + return; + } + + if (isTablet) { + setHorizontalSizeClass(SizeClass.Regular); + setVerticalSizeClass(SizeClass.Regular); + return; + } + + const aspectRatio = isLandscape ? width / height : height / width; + const screenArea = width * height; + + if (isLandscape) { + setHorizontalSizeClass(aspectRatio >= 1.6 || screenArea >= 250000 ? SizeClass.Regular : SizeClass.Compact); + setVerticalSizeClass(SizeClass.Compact); + } else { + setHorizontalSizeClass(SizeClass.Compact); + setVerticalSizeClass(SizeClass.Regular); + } + }; + + useEffect(() => { + const handleDimensionChange = () => { + determineSize(); + }; + + const dimensionSubscription = Dimensions.addEventListener('change', handleDimensionChange); + + determineSize(); + + return () => { + dimensionSubscription.remove(); + }; + }, []); + + const { currentAppState } = useAppState(); + useEffect(() => { + if (currentAppState === 'active') { + determineSize(); + } + }, [currentAppState]); + + const sizeClass = useMemo(() => { + if ( + (horizontalSizeClass === SizeClass.Large || verticalSizeClass === SizeClass.Large) && + horizontalSizeClass !== SizeClass.Compact && + verticalSizeClass !== SizeClass.Compact + ) { + return SizeClass.Large; + } + + if (horizontalSizeClass === SizeClass.Compact || verticalSizeClass === SizeClass.Compact) { + return SizeClass.Compact; + } + + return SizeClass.Regular; + }, [horizontalSizeClass, verticalSizeClass]); + + useEffect(() => { + console.debug( + `[SizeClass] Size classes updated:`, + `horizontal=${SizeClass[horizontalSizeClass]}`, + `vertical=${SizeClass[verticalSizeClass]}`, + `overall=${SizeClass[sizeClass]}`, + `orientation=${orientation}`, + ); + }, [horizontalSizeClass, verticalSizeClass, sizeClass, orientation]); + + return { + sizeClass, + horizontalSizeClass, + verticalSizeClass, + orientation, + }; +}; + +type SizeClassProviderProps = { + children: ReactNode; +}; + +export const SizeClassContext = createContext({ + sizeClass: SizeClass.Regular, + horizontalSizeClass: SizeClass.Regular, + verticalSizeClass: SizeClass.Regular, + orientation: 'portrait', +}); + +export const SizeClassProvider: React.FC = ({ children }) => { + const { sizeClass, horizontalSizeClass, verticalSizeClass, orientation } = useSizeClassDetection(); + + const contextValue = useMemo( + () => ({ + sizeClass, + horizontalSizeClass, + verticalSizeClass, + orientation, + }), + [sizeClass, horizontalSizeClass, verticalSizeClass, orientation], + ); + + return {children}; +}; diff --git a/components/Context/StorageProvider.tsx b/components/Context/StorageProvider.tsx new file mode 100644 index 00000000000..cca97272460 --- /dev/null +++ b/components/Context/StorageProvider.tsx @@ -0,0 +1,567 @@ +import React, { createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { BlueApp as BlueAppClass, TCounterpartyMetadata, TTXMetadata } from '../../class/blue-app'; +import { LegacyWallet } from '../../class/wallets/legacy-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 { startAndDecrypt } from '../../blue_modules/start-and-decrypt'; +import { isNotificationsEnabled, majorTomToGroundControl, unsubscribe } from '../../blue_modules/notifications'; +import { BitcoinUnit } from '../../models/bitcoinUnits'; +import { navigationRef } from '../../NavigationService'; +import { getScanWasBBQR } from '../../helpers/scan-qr.ts'; +import { setWalletIdMustUseBBQR } from '../../blue_modules/ur'; + +const BlueApp = BlueAppClass.getInstance(); + +// hashmap of timestamps we _started_ refetching some wallet +const _lastTimeTriedToRefetchWallet: { [walletID: string]: number } = {}; + +interface StorageContextType { + wallets: TWallet[]; + setWalletsWithNewOrder: (wallets: TWallet[]) => void; + txMetadata: TTXMetadata; + counterpartyMetadata: TCounterpartyMetadata; + saveToDisk: (force?: boolean) => Promise; + selectedWalletID: () => string | undefined; // Change from string|undefined to a function + addWallet: (wallet: TWallet) => void; + deleteWallet: (wallet: TWallet) => void; + currentSharedCosigner: string; + setSharedCosigner: (cosigner: string) => void; + addAndSaveWallet: (wallet: TWallet) => Promise; + fetchAndSaveWalletTransactions: (walletID: string) => Promise; + walletsInitialized: boolean; + setWalletsInitialized: (initialized: boolean) => void; + refreshAllWalletTransactions: (lastSnappedTo?: number, showUpdateStatusIndicator?: boolean) => Promise; + resetWallets: () => void; + walletTransactionUpdateStatus: WalletTransactionsStatus | string; + setWalletTransactionUpdateStatus: (status: WalletTransactionsStatus | string) => void; + getTransactions: typeof BlueApp.getTransactions; + fetchWalletBalances: typeof BlueApp.fetchWalletBalances; + fetchWalletTransactions: typeof BlueApp.fetchWalletTransactions; + getBalance: typeof BlueApp.getBalance; + isStorageEncrypted: typeof BlueApp.storageIsEncrypted; + startAndDecrypt: typeof startAndDecrypt; + encryptStorage: typeof BlueApp.encryptStorage; + sleep: typeof BlueApp.sleep; + createFakeStorage: typeof BlueApp.createFakeStorage; + decryptStorage: typeof BlueApp.decryptStorage; + isPasswordInUse: typeof BlueApp.isPasswordInUse; + cachedPassword: typeof BlueApp.cachedPassword; + getItem: typeof BlueApp.getItem; + setItem: typeof BlueApp.setItem; + handleWalletDeletion: (walletID: string, forceDelete?: boolean) => Promise; + confirmWalletDeletion: (wallet: any, onConfirmed: () => void) => void; +} + +export enum WalletTransactionsStatus { + NONE = 'NONE', + ALL = 'ALL', +} + +// @ts-ignore default value does not match the type +export const StorageContext = createContext(undefined); + +export const StorageProvider = ({ children }: { children: React.ReactNode }) => { + const txMetadata = useRef(BlueApp.tx_metadata); + const counterpartyMetadata = useRef(BlueApp.counterparty_metadata || {}); // init + + const [wallets, setWallets] = useState([]); + const [walletTransactionUpdateStatus, setWalletTransactionUpdateStatus] = useState( + WalletTransactionsStatus.NONE, + ); + const [walletsInitialized, setWalletsInitialized] = useState(false); + const [currentSharedCosigner, setCurrentSharedCosigner] = useState(''); + + const selectedWalletID = useCallback((): string | undefined => { + if (!navigationRef.current || !navigationRef.current.isReady()) return undefined; + + const screensToCheck = ['LNDCreateInvoice', 'SendDetails', 'WalletTransactions', 'TransactionStatus']; + + const currentRoute = navigationRef.current.getCurrentRoute(); + console.debug('[StorageProvider] Current route:', currentRoute?.name); + + if (currentRoute) { + if (screensToCheck.includes(currentRoute.name) && currentRoute.params) { + const params = currentRoute.params as { walletID?: string }; + if (params.walletID) { + console.debug('[StorageProvider] selectedWalletID from current route:', params.walletID); + return params.walletID; + } + } + } + + const state = navigationRef.current.getState(); + + if (state?.routes) { + for (const screenName of screensToCheck) { + const walletID = findWalletIDInNavigationState(state.routes, screenName); + if (walletID) { + console.debug('[StorageProvider] selectedWalletID from navigation state:', walletID, 'in screen:', screenName); + return walletID; + } + } + + const drawerRoute = state.routes.find(route => route.name === 'DrawerRoot'); + if (drawerRoute?.state?.routes) { + const detailViewStack = drawerRoute.state.routes.find(route => route.name === 'DetailViewStackScreensStack'); + if (detailViewStack?.state?.routes) { + for (const route of detailViewStack.state.routes) { + if (screensToCheck.includes(route.name) && (route.params as { walletID?: string })?.walletID) { + console.debug( + '[StorageProvider] selectedWalletID from drawer navigation:', + (route.params as { walletID?: string })?.walletID, + ); + return (route.params as { walletID?: string })?.walletID; + } + } + } + } + } + + return undefined; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const findWalletIDInNavigationState = (routes: any[], screenName: string): string | undefined => { + for (let i = routes.length - 1; i >= 0; i--) { + const route = routes[i]; + + if (route.name === screenName && (route.params as { walletID?: string }).walletID) { + return (route.params as { walletID?: string }).walletID; + } + + if (route.state?.routes) { + const walletID = findWalletIDInNavigationState(route.state.routes, screenName); + if (walletID) return walletID; + } + + if (route.params?.screen === screenName && route.params?.params?.walletID) { + return route.params.params.walletID; + } + + if (route.name === 'DetailViewStackScreensStack' && route.params?.screen === screenName && route.params?.params?.walletID) { + return route.params.params.walletID; + } + } + + return undefined; + }; + + const saveToDisk = useCallback( + async (force: boolean = false) => { + if (!force && BlueApp.getWallets().length === 0) { + console.debug('Not saving empty wallets array'); + return; + } + BlueApp.tx_metadata = txMetadata.current; + BlueApp.counterparty_metadata = counterpartyMetadata.current; + await BlueApp.saveToDisk(); + const w: TWallet[] = [...BlueApp.getWallets()]; + setWallets(w); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [txMetadata.current, counterpartyMetadata.current], + ); + + const addWallet = useCallback((wallet: TWallet) => { + BlueApp.wallets.push(wallet); + setWallets([...BlueApp.getWallets()]); + }, []); + + const deleteWallet = useCallback((wallet: TWallet) => { + BlueApp.deleteWallet(wallet); + setWallets([...BlueApp.getWallets()]); + }, []); + + const handleWalletDeletion = useCallback( + async (walletID: string, forceDelete = false): Promise => { + console.debug(`handleWalletDeletion: invoked for walletID ${walletID}`); + const wallet = wallets.find(w => w.getID() === walletID); + if (!wallet) { + console.warn(`handleWalletDeletion: wallet not found for ${walletID}`); + return false; + } + + if (forceDelete) { + deleteWallet(wallet); + await saveToDisk(true); + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); + return true; + } + + let isNotificationsSettingsEnabled = false; + try { + isNotificationsSettingsEnabled = await isNotificationsEnabled(); + } catch (error) { + console.error(`handleWalletDeletion: error checking notifications for wallet ${walletID}`, error); + return await new Promise(resolve => { + presentAlert({ + title: loc.errors.error, + message: loc.wallets.details_delete_wallet_error_message, + buttons: [ + { + text: loc.wallets.details_delete_anyway, + onPress: async () => { + const result = await handleWalletDeletion(walletID, true); + resolve(result); + }, + style: 'destructive', + }, + { + text: loc.wallets.list_tryagain, + onPress: async () => { + const result = await handleWalletDeletion(walletID); + resolve(result); + }, + }, + { + text: loc._.cancel, + onPress: () => resolve(false), + style: 'cancel', + }, + ], + options: { cancelable: false }, + }); + }); + } + + try { + if (isNotificationsSettingsEnabled) { + const externalAddresses = wallet.getAllExternalAddresses(); + if (externalAddresses.length > 0) { + console.debug(`handleWalletDeletion: unsubscribing addresses for wallet ${walletID}`); + try { + await unsubscribe(externalAddresses, [], []); + console.debug(`handleWalletDeletion: unsubscribe succeeded for wallet ${walletID}`); + } catch (unsubscribeError) { + console.error(`handleWalletDeletion: unsubscribe failed for wallet ${walletID}`, unsubscribeError); + presentAlert({ + title: loc.errors.error, + message: loc.wallets.details_delete_wallet_error_message, + buttons: [{ text: loc._.ok, onPress: () => {} }], + options: { cancelable: false }, + }); + return false; + } + } + } + deleteWallet(wallet); + console.debug(`handleWalletDeletion: wallet ${walletID} deleted successfully`); + await saveToDisk(true); + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); + return true; + } catch (e: unknown) { + console.error(`handleWalletDeletion: encountered error for wallet ${walletID}`, e); + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); + return await new Promise(resolve => { + presentAlert({ + title: loc.errors.error, + message: loc.wallets.details_delete_wallet_error_message, + buttons: [ + { + text: loc.wallets.details_delete_anyway, + onPress: async () => { + const result = await handleWalletDeletion(walletID, true); + resolve(result); + }, + style: 'destructive', + }, + { + text: loc.wallets.list_tryagain, + onPress: async () => { + const result = await handleWalletDeletion(walletID); + resolve(result); + }, + }, + { + text: loc._.cancel, + onPress: () => resolve(false), + style: 'cancel', + }, + ], + options: { cancelable: false }, + }); + }); + } + }, + [deleteWallet, saveToDisk, wallets], + ); + + const resetWallets = useCallback(() => { + setWallets(BlueApp.getWallets()); + }, []); + + const setWalletsWithNewOrder = useCallback( + (wlts: TWallet[]) => { + BlueApp.wallets = wlts; + saveToDisk(); + }, + [saveToDisk], + ); + + // Initialize wallets + useEffect(() => { + if (walletsInitialized) { + txMetadata.current = BlueApp.tx_metadata; + counterpartyMetadata.current = BlueApp.counterparty_metadata; + setWallets(BlueApp.getWallets()); + } + }, [walletsInitialized]); + + // Add a refresh lock to prevent concurrent refreshes + const refreshingRef = useRef(false); + + const refreshAllWalletTransactions = useCallback( + async (lastSnappedTo?: number, showUpdateStatusIndicator: boolean = true) => { + if (refreshingRef.current) { + console.debug('[refreshAllWalletTransactions] Refresh already in progress'); + return; + } + console.debug('[refreshAllWalletTransactions] Starting refresh'); + refreshingRef.current = true; + + let refreshTimeout: ReturnType | undefined; + + try { + if (showUpdateStatusIndicator) { + console.debug('[refreshAllWalletTransactions] Setting wallet transaction status to ALL'); + 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(); + } + + 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, + ); + const timeoutPromise = new Promise( + (_resolve, reject) => + (refreshTimeout = setTimeout(() => { + console.debug('[refreshAllWalletTransactions] Timeout reached'); + reject(new Error('Timeout reached')); + }, REFRESH_FETCH_PHASE_TIMEOUT_MS)), + ); + + if (typeof BlueApp.fetchSenderPaymentCodes !== 'function') { + console.warn('[refreshAllWalletTransactions] fetchSenderPaymentCodes is not available'); + } + + const paymentCodesPromise = + typeof BlueApp.fetchSenderPaymentCodes === 'function' + ? (async () => { + const codesStart = Date.now(); + console.debug('[refreshAllWalletTransactions] Fetching sender payment codes (parallel)'); + await BlueApp.fetchSenderPaymentCodes(lastSnappedTo); + console.debug('[refreshAllWalletTransactions] fetch payment codes took', (Date.now() - codesStart) / 1000, 'sec'); + })() + : Promise.resolve(); + + console.debug('[refreshAllWalletTransactions] Fetching wallet balances and transactions'); + await Promise.race([ + (async () => { + await Promise.all([ + paymentCodesPromise, + (async () => { + const balanceStart = Date.now(); + await BlueApp.fetchWalletBalances(lastSnappedTo); + console.debug('[refreshAllWalletTransactions] fetch balance took', (Date.now() - balanceStart) / 1000, 'sec'); + + const txStart = Date.now(); + await BlueApp.fetchWalletTransactions(lastSnappedTo); + console.debug('[refreshAllWalletTransactions] fetch tx took', (Date.now() - txStart) / 1000, 'sec'); + })(), + ]); + + console.debug('[refreshAllWalletTransactions] Saving data to disk'); + await saveToDisk(); + })(), + timeoutPromise, + ]); + console.debug('[refreshAllWalletTransactions] Refresh completed successfully'); + } catch (error) { + console.error('[refreshAllWalletTransactions] Error:', error); + } finally { + if (refreshTimeout !== undefined) { + clearTimeout(refreshTimeout); + } + console.debug('[refreshAllWalletTransactions] Resetting wallet transaction status and refresh lock'); + refreshingRef.current = false; + setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE); + } + }, + [saveToDisk], + ); + + const fetchAndSaveWalletTransactions = useCallback( + async (walletID: string) => { + const index = wallets.findIndex(wallet => wallet.getID() === walletID); + let noErr = true; + try { + if (Date.now() - (_lastTimeTriedToRefetchWallet[walletID] || 0) < 5000) { + console.debug('[fetchAndSaveWalletTransactions] Re-fetch wallet happens too fast; NOP'); + return; + } + _lastTimeTriedToRefetchWallet[walletID] = Date.now(); + + await BlueElectrum.waitTillConnected(); + setWalletTransactionUpdateStatus(walletID); + + const balanceStart = Date.now(); + await BlueApp.fetchWalletBalances(index); + const balanceEnd = Date.now(); + console.debug('[fetchAndSaveWalletTransactions] fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec'); + + const txStart = Date.now(); + await BlueApp.fetchWalletTransactions(index); + const txEnd = Date.now(); + console.debug('[fetchAndSaveWalletTransactions] fetch tx took', (txEnd - txStart) / 1000, 'sec'); + } catch (err) { + noErr = false; + console.error('[fetchAndSaveWalletTransactions] Error:', err); + } finally { + setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE); + } + if (noErr) await saveToDisk(); + }, + [saveToDisk, wallets], + ); + + const addAndSaveWallet = useCallback( + async (w: TWallet) => { + if (wallets.some(i => i.getID() === w.getID())) { + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); + presentAlert({ message: 'This wallet has been previously imported.' }); + return; + } + const emptyWalletLabel = new LegacyWallet().getLabel(); + if (w.getLabel() === emptyWalletLabel) w.setLabel(loc.wallets.import_imported + ' ' + w.typeReadable); + w.setUserHasSavedExport(true); + addWallet(w); + 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 + // and saves his wallet to storage, in which case execution lands here, where we check last scan and save walletID + // internally as a marker that this wallet should display animated QR codes in this format + await setWalletIdMustUseBBQR(w.getID()); + } + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); + await saveToDisk(); + + presentAlert({ + hapticFeedback: HapticFeedbackTypes.ImpactHeavy, + message: w.type === WatchOnlyWallet.type ? loc.wallets.import_success_watchonly : loc.wallets.import_success, + }); + + await w.fetchBalance(); + try { + await majorTomToGroundControl(w.getAllExternalAddresses(), [], []); + } catch (error) { + console.warn('Failed to setup notifications:', error); + // Consider if user should be notified of notification setup failure + } + }, + [wallets, addWallet, saveToDisk], + ); + + function confirmWalletDeletion(wallet: any, onConfirmed: () => void) { + triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning); + try { + const balance = formatBalanceWithoutSuffix(wallet.getBalance(), BitcoinUnit.SATS, true); + presentAlert({ + title: loc.wallets.details_delete_wallet, + message: loc.formatString(loc.wallets.details_del_wb_q, { balance }), + buttons: [ + { + text: loc.wallets.details_delete, + onPress: () => { + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); + onConfirmed(); + }, + style: 'destructive', + }, + { + text: loc._.cancel, + onPress: () => {}, + style: 'cancel', + }, + ], + options: { cancelable: false }, + }); + } catch (error) { + // Handle error silently if needed + } + } + + const value: StorageContextType = useMemo( + () => ({ + wallets, + setWalletsWithNewOrder, + txMetadata: txMetadata.current, + counterpartyMetadata: counterpartyMetadata.current, + saveToDisk, + getTransactions: BlueApp.getTransactions, + selectedWalletID, + addWallet, + deleteWallet, + currentSharedCosigner, + setSharedCosigner: setCurrentSharedCosigner, + addAndSaveWallet, + setItem: BlueApp.setItem, + getItem: BlueApp.getItem, + fetchWalletBalances: BlueApp.fetchWalletBalances, + fetchWalletTransactions: BlueApp.fetchWalletTransactions, + fetchAndSaveWalletTransactions, + isStorageEncrypted: BlueApp.storageIsEncrypted, + encryptStorage: BlueApp.encryptStorage, + startAndDecrypt, + cachedPassword: BlueApp.cachedPassword, + getBalance: BlueApp.getBalance, + walletsInitialized, + setWalletsInitialized, + refreshAllWalletTransactions, + sleep: BlueApp.sleep, + createFakeStorage: BlueApp.createFakeStorage, + resetWallets, + decryptStorage: BlueApp.decryptStorage, + isPasswordInUse: BlueApp.isPasswordInUse, + walletTransactionUpdateStatus, + setWalletTransactionUpdateStatus, + handleWalletDeletion, + confirmWalletDeletion, + }), + [ + wallets, + setWalletsWithNewOrder, + saveToDisk, + selectedWalletID, + addWallet, + deleteWallet, + currentSharedCosigner, + addAndSaveWallet, + fetchAndSaveWalletTransactions, + walletsInitialized, + setWalletsInitialized, + refreshAllWalletTransactions, + resetWallets, + walletTransactionUpdateStatus, + handleWalletDeletion, + ], + ); + + return {children}; +}; diff --git a/components/CopyTextToClipboard.tsx b/components/CopyTextToClipboard.tsx new file mode 100644 index 00000000000..d971c8efd33 --- /dev/null +++ b/components/CopyTextToClipboard.tsx @@ -0,0 +1,249 @@ +import Clipboard from '@react-native-clipboard/clipboard'; +import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; +import { StyleSheet, Text, TextProps, TextStyle, TouchableOpacity, View, ViewStyle } from 'react-native'; + +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; +import BlueText from './BlueText'; +import loc from '../loc'; +import { useTheme } from './themes'; + +export type CopyTextToClipboardHandle = { + copy: (options?: { suppressHaptic?: boolean }) => void; +}; + +type CopyTextToClipboardProps = TextProps & { + text: string; + displayText?: string; // Optional text to display instead of the actual text (but still copies the actual text) + truncated?: boolean; + selectable?: boolean; + textAlign?: 'left' | 'center' | 'right' | 'auto' | 'justify'; + containerStyle?: ViewStyle; + isAddress?: boolean; + interactive?: boolean; + buttonTestID?: string; + textTestID?: string; +}; + +const styles = StyleSheet.create({ + defaultTextStyle: { + marginVertical: 32, + fontSize: 15, + color: '#9aa0aa', + textAlign: 'center', + }, + addressDefaultTextStyle: { + fontSize: 15, + color: '#9aa0aa', + textAlign: 'center', + }, + textFillContainer: { + width: '100%', + minWidth: 0, + }, + nonInteractiveContainer: { + justifyContent: 'center', + alignItems: 'center', + }, +}); + +const COPY_FEEDBACK_MS = 1500; + +const CopyTextToClipboard = forwardRef( + ( + { + text, + displayText: displayTextProp, + truncated, + style, + numberOfLines, + ellipsizeMode, + selectable, + textAlign, + containerStyle, + accessibilityLabel, + isAddress, + interactive = true, + buttonTestID = 'CopyTextToClipboard', + textTestID = 'AddressValue', + ...textProps + }, + ref, + ) => { + const [hasTappedText, setHasTappedText] = useState(false); + const initialDisplayText = displayTextProp || text; + const [displayText, setDisplayText] = useState(initialDisplayText); + const isCopiedState = hasTappedText && displayText === loc._.copied; + const { colors } = useTheme(); + const copyResetTimeoutRef = useRef | null>(null); + + const addressSectionStyle = useMemo( + () => ({ + color: colors.alternativeTextColor2, + fontWeight: '500', + }), + [colors.alternativeTextColor2], + ); + + useEffect(() => { + if (!hasTappedText) { + setDisplayText(displayTextProp || text); + } + }, [text, displayTextProp, hasTappedText]); + + useEffect( + () => () => { + if (copyResetTimeoutRef.current) { + clearTimeout(copyResetTimeoutRef.current); + copyResetTimeoutRef.current = null; + } + }, + [], + ); + + const copyToClipboard = useCallback( + (options?: { suppressHaptic?: boolean }) => { + // Don't copy if already showing the copied state, or text is empty / "-" + if (hasTappedText || !text || text === '-') { + return; + } + + if (copyResetTimeoutRef.current) { + clearTimeout(copyResetTimeoutRef.current); + copyResetTimeoutRef.current = null; + } + + setHasTappedText(true); + Clipboard.setString(text); + if (!options?.suppressHaptic) { + triggerHapticFeedback(HapticFeedbackTypes.Selection); + } + setDisplayText(loc._.copied); + copyResetTimeoutRef.current = setTimeout(() => { + copyResetTimeoutRef.current = null; + setHasTappedText(false); + setDisplayText(displayTextProp || text); + }, COPY_FEEDBACK_MS); + }, + [hasTappedText, text, displayTextProp], + ); + + useImperativeHandle(ref, () => ({ copy: copyToClipboard }), [copyToClipboard]); + + /** Single-line value for screen readers / Detox `by.label` when visual text uses newlines or splits (e.g. receive address). */ + const accessibilityLabelResolved = accessibilityLabel ?? (isCopiedState ? loc._.copied : text); + + const mergedTextStyle = style ?? (isAddress ? styles.addressDefaultTextStyle : styles.defaultTextStyle); + const textAlignStyle = textAlign ? { textAlign } : undefined; + const finalNumberOfLines = isCopiedState ? 1 : numberOfLines !== undefined ? numberOfLines : truncated ? 1 : 0; + const finalEllipsizeMode = isCopiedState ? undefined : ellipsizeMode || (truncated ? 'middle' : undefined); + + const textStyleArray = + containerStyle && !isCopiedState ? [mergedTextStyle, styles.textFillContainer, textAlignStyle] : [mergedTextStyle, textAlignStyle]; + + const renderHighlightedAddress = () => { + // While showing the "Copied!" feedback, render plain text without highlights. + if (isCopiedState) { + return ( + + {displayText} + + ); + } + + if (displayText.toLocaleLowerCase().startsWith('bitcoin:')) { + const prefix = displayText.slice(0, 8); // "bitcoin:" + const afterPrefix = displayText.slice(8); + const qIndex = afterPrefix.indexOf('?'); + const addrPart = qIndex === -1 ? afterPrefix : afterPrefix.slice(0, qIndex); + const queryPart = qIndex === -1 ? '' : afterPrefix.slice(qIndex); + const start = addrPart.slice(0, 6); + const middle = addrPart.slice(6, -6); + const end = addrPart.slice(-6); + + return ( + + {prefix} + {start} + {middle} + {end} + {queryPart} + + ); + } + + return ( + + {displayText.slice(0, 6)} + {displayText.slice(6, -6)} + {displayText.slice(-6)} + + ); + }; + + const textContent = isAddress ? ( + renderHighlightedAddress() + ) : ( + + {displayText} + + ); + + if (!interactive) { + return ( + + {textContent} + + ); + } + + return ( + copyToClipboard()} + disabled={hasTappedText || !text || text === '-'} + testID={buttonTestID} + activeOpacity={0.7} + style={containerStyle} + > + {containerStyle ? {textContent} : textContent} + + ); + }, +); + +export default CopyTextToClipboard; diff --git a/components/CopyToClipboardButton.tsx b/components/CopyToClipboardButton.tsx new file mode 100644 index 00000000000..41e8cb69171 --- /dev/null +++ b/components/CopyToClipboardButton.tsx @@ -0,0 +1,30 @@ +import Clipboard from '@react-native-clipboard/clipboard'; +import React from 'react'; +import { StyleSheet, Text, TouchableOpacity } from 'react-native'; + +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; +import loc from '../loc'; + +type CopyToClipboardButtonProps = { + stringToCopy: string; + displayText?: string; +}; + +export const CopyToClipboardButton: React.FC = ({ stringToCopy, displayText }) => { + const onPress = () => { + Clipboard.setString(stringToCopy); + triggerHapticFeedback(HapticFeedbackTypes.Selection); + }; + + return ( + + {displayText && displayText.length > 0 ? displayText : loc.transactions.details_copy} + + ); +}; + +const styles = StyleSheet.create({ + text: { fontSize: 16, fontWeight: '400', color: '#68bbe1' }, +}); + +export default CopyToClipboardButton; diff --git a/components/DevMenu.tsx b/components/DevMenu.tsx new file mode 100644 index 00000000000..2613d82424f --- /dev/null +++ b/components/DevMenu.tsx @@ -0,0 +1,168 @@ +import React, { useEffect } from 'react'; +import { DevSettings, Alert, Platform, AlertButton } from 'react-native'; +import { useStorage } from '../hooks/context/useStorage'; +import { HDSegwitBech32Wallet } from '../class/wallets/hd-segwit-bech32-wallet'; +import { WatchOnlyWallet } from '../class/wallets/watch-only-wallet'; +import Clipboard from '@react-native-clipboard/clipboard'; +import { TWallet } from '../class/wallets/types'; + +const getRandomLabelFromSecret = (secret: string): string => { + const words = secret.split(' '); + const firstWord = words[0]; + const lastWord = words[words.length - 1]; + return `[Developer] ${firstWord} ${lastWord}`; +}; + +const showAlertWithWalletOptions = ( + wallets: TWallet[], + title: string, + message: string, + onWalletSelected: (wallet: TWallet) => void, + filterFn?: (wallet: TWallet) => boolean, +) => { + const filteredWallets = filterFn ? wallets.filter(filterFn) : wallets; + + const showWallet = (index: number) => { + if (index >= filteredWallets.length) return; + const wallet = filteredWallets[index]; + + if (Platform.OS === 'android') { + // Android: Use a limited number of buttons since the alert dialog has a limit + Alert.alert( + `${title}: ${wallet.getLabel()}`, + `${message}\n\nSelected Wallet: ${wallet.getLabel()}\n\nWould you like to select this wallet or see the next one?`, + [ + { + text: 'Select This Wallet', + onPress: () => onWalletSelected(wallet), + }, + { + text: 'Show Next Wallet', + onPress: () => showWallet(index + 1), + }, + { + text: 'Cancel', + style: 'cancel', + }, + ], + { cancelable: true }, + ); + } else { + const options: AlertButton[] = filteredWallets.map(w => ({ + text: w.getLabel(), + onPress: () => onWalletSelected(w), + })); + + options.push({ + text: 'Cancel', + style: 'cancel', + }); + + Alert.alert(title, message, options, { cancelable: true }); + } + }; + + if (filteredWallets.length > 0) { + showWallet(0); + } else { + Alert.alert('No wallets available'); + } +}; + +const DevMenu: React.FC = () => { + const { wallets, addWallet } = useStorage(); + + useEffect(() => { + if (__DEV__) { + // Clear existing Dev Menu items to prevent duplication + DevSettings.addMenuItem('Reset Dev Menu', () => { + DevSettings.reload(); + }); + + DevSettings.addMenuItem('Add New Wallet', async () => { + const wallet = new HDSegwitBech32Wallet(); + await wallet.generate(); + const label = getRandomLabelFromSecret(wallet.getSecret()); + wallet.setLabel(label); + addWallet(wallet); + + Clipboard.setString(wallet.getSecret()); + Alert.alert('New Wallet created!', `Wallet secret copied to clipboard.\nLabel: ${label}`); + }); + + DevSettings.addMenuItem('Copy Wallet Secret', () => { + if (wallets.length === 0) { + Alert.alert('No wallets available'); + return; + } + + showAlertWithWalletOptions(wallets, 'Copy Wallet Secret', 'Select the wallet to copy the secret', wallet => { + Clipboard.setString(wallet.getSecret()); + Alert.alert('Wallet Secret copied to clipboard!'); + }); + }); + + DevSettings.addMenuItem('Copy Wallet ID', () => { + if (wallets.length === 0) { + Alert.alert('No wallets available'); + return; + } + + showAlertWithWalletOptions(wallets, 'Copy Wallet ID', 'Select the wallet to copy the ID', wallet => { + Clipboard.setString(wallet.getID()); + Alert.alert('Wallet ID copied to clipboard!'); + }); + }); + + DevSettings.addMenuItem('Copy Wallet Xpub', () => { + if (wallets.length === 0) { + Alert.alert('No wallets available'); + return; + } + + showAlertWithWalletOptions( + wallets, + 'Copy Wallet Xpub', + 'Select the wallet to copy the Xpub', + wallet => { + const xpub = wallet.getXpub(); + if (xpub) { + Clipboard.setString(xpub); + Alert.alert('Wallet Xpub copied to clipboard!'); + } else { + Alert.alert('This wallet does not have an Xpub.'); + } + }, + wallet => typeof wallet.getXpub === 'function', + ); + }); + + DevSettings.addMenuItem('Purge Wallet Transactions', () => { + if (wallets.length === 0) { + Alert.alert('No wallets available'); + return; + } + + showAlertWithWalletOptions(wallets, 'Purge Wallet Transactions', 'Select the wallet to purge transactions', wallet => { + const msg = 'Transactions purged successfully!'; + + if (wallet.type === HDSegwitBech32Wallet.type) { + wallet._txs_by_external_index = {}; + wallet._txs_by_internal_index = {}; + } + + if (wallet.type === WatchOnlyWallet.type && wallet._hdWalletInstance) { + wallet._hdWalletInstance._txs_by_external_index = {}; + wallet._hdWalletInstance._txs_by_internal_index = {}; + } + + Alert.alert(msg); + }); + }); + } + }, [wallets, addWallet]); + + return null; +}; + +export default DevMenu; diff --git a/components/DismissKeyboardInputAccessory.tsx b/components/DismissKeyboardInputAccessory.tsx new file mode 100644 index 00000000000..c8a886b0807 --- /dev/null +++ b/components/DismissKeyboardInputAccessory.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { InputAccessoryView, Keyboard, Platform, StyleSheet, View } from 'react-native'; +import { useTheme } from './themes'; +import BlueButtonLink from './BlueButtonLink'; +import loc from '../loc'; + +export const DismissKeyboardInputAccessoryViewID = 'DismissKeyboardInputAccessory'; +export const DismissKeyboardInputAccessory: React.FC = () => { + const { colors } = useTheme(); + const styleHooks = StyleSheet.create({ + container: { + backgroundColor: colors.inputBackgroundColor, + }, + }); + + if (Platform.OS !== 'ios') { + return null; + } + + return ( + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + maxHeight: 44, + }, +}); diff --git a/components/Divider.tsx b/components/Divider.tsx new file mode 100644 index 00000000000..bdce6073148 --- /dev/null +++ b/components/Divider.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; + +import { useTheme } from './themes'; + +export interface DividerProps { + style?: StyleProp; + color?: string; +} + +const Divider: React.FC = ({ style, color }) => { + const { colors } = useTheme(); + const backgroundColor = color ?? colors.formBorder; + + return ; +}; + +const styles = StyleSheet.create({ + divider: { + height: StyleSheet.hairlineWidth, + alignSelf: 'stretch', + }, +}); + +export default Divider; diff --git a/components/DoneAndDismissKeyboardInputAccessory.tsx b/components/DoneAndDismissKeyboardInputAccessory.tsx new file mode 100644 index 00000000000..c6a1d598ab1 --- /dev/null +++ b/components/DoneAndDismissKeyboardInputAccessory.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { InputAccessoryView, Keyboard, Platform, StyleSheet, View } from 'react-native'; +import BlueButtonLink from './BlueButtonLink'; +import loc from '../loc'; +import { useTheme } from './themes'; +import Clipboard from '@react-native-clipboard/clipboard'; + +interface DoneAndDismissKeyboardInputAccessoryProps { + onPasteTapped: (clipboard: string) => void; + onClearTapped: () => void; +} +export const DoneAndDismissKeyboardInputAccessoryViewID = 'DoneAndDismissKeyboardInputAccessory'; +export const DoneAndDismissKeyboardInputAccessory: React.FC = props => { + const { colors } = useTheme(); + + const styleHooks = StyleSheet.create({ + container: { + backgroundColor: colors.inputBackgroundColor, + }, + }); + + const onPasteTapped = async () => { + const clipboard = await Clipboard.getString(); + props.onPasteTapped(clipboard); + }; + + const inputView = ( + + + + + + ); + + if (Platform.OS === 'ios') { + return {inputView}; + } else { + return inputView; + } +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + maxHeight: 44, + }, +}); diff --git a/components/DynamicQRCode.js b/components/DynamicQRCode.js deleted file mode 100644 index 67c77905746..00000000000 --- a/components/DynamicQRCode.js +++ /dev/null @@ -1,197 +0,0 @@ -/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */ -import React, { Component } from 'react'; -import { Text } from 'react-native-elements'; -import { Dimensions, LayoutAnimation, StyleSheet, TouchableOpacity, View } from 'react-native'; -import { encodeUR } from '../blue_modules/ur'; -import QRCodeComponent from './QRCodeComponent'; -import { BlueCurrentTheme } from '../components/themes'; -import { BlueSpacing20 } from '../BlueComponents'; -import loc from '../loc'; - -const { height, width } = Dimensions.get('window'); - -export class DynamicQRCode extends Component { - constructor() { - super(); - const qrCodeHeight = height > width ? width - 40 : width / 3; - const qrCodeMaxHeight = 370; - this.state = { - index: 0, - total: 0, - qrCodeHeight: Math.min(qrCodeHeight, qrCodeMaxHeight), - intervalHandler: null, - displayQRCode: true, - }; - } - - fragments = []; - - componentDidMount() { - const { value, capacity = 200, hideControls = true } = this.props; - try { - this.fragments = encodeUR(value, capacity); - this.setState( - { - total: this.fragments.length, - hideControls, - displayQRCode: true, - }, - () => { - this.startAutoMove(); - }, - ); - } catch (e) { - console.log(e); - this.setState({ displayQRCode: false, hideControls }); - } - } - - moveToNextFragment = () => { - const { index, total } = this.state; - if (index === total - 1) { - this.setState({ - index: 0, - }); - } else { - this.setState(state => ({ - index: state.index + 1, - })); - } - }; - - startAutoMove = () => { - if (!this.state.intervalHandler) - this.setState(() => ({ - intervalHandler: setInterval(this.moveToNextFragment, 500), - })); - }; - - stopAutoMove = () => { - clearInterval(this.state.intervalHandler); - this.setState(() => ({ - intervalHandler: null, - })); - }; - - moveToPreviousFragment = () => { - const { index, total } = this.state; - if (index > 0) { - this.setState(state => ({ - index: state.index - 1, - })); - } else { - this.setState(state => ({ - index: total - 1, - })); - } - }; - - onError = () => { - console.log('Data is too large for QR Code.'); - this.setState({ displayQRCode: false }); - }; - - render() { - const currentFragment = this.fragments[this.state.index]; - - if (!currentFragment && this.state.displayQRCode) { - return ( - - {loc.send.dynamic_init} - - ); - } - - return ( - - { - LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); - this.setState(prevState => ({ hideControls: !prevState.hideControls })); - }} - > - {this.state.displayQRCode && ( - - - - )} - - - {!this.state.hideControls && ( - - - - - {loc.formatString(loc._.of, { number: this.state.index + 1, total: this.state.total })} - - - - - - {loc.send.dynamic_prev} - - - {this.state.intervalHandler ? loc.send.dynamic_stop : loc.send.dynamic_start} - - - {loc.send.dynamic_next} - - - - )} - - ); - } -} - -const animatedQRCodeStyle = StyleSheet.create({ - container: { - flex: 1, - flexDirection: 'column', - alignItems: 'center', - }, - qrcodeContainer: { - alignItems: 'center', - justifyContent: 'center', - }, - controller: { - width: '90%', - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - borderRadius: 25, - height: 45, - paddingHorizontal: 18, - }, - button: { - alignItems: 'center', - height: 45, - justifyContent: 'center', - }, - text: { - fontSize: 14, - color: BlueCurrentTheme.colors.foregroundColor, - fontWeight: 'bold', - }, -}); diff --git a/components/DynamicQRCode.tsx b/components/DynamicQRCode.tsx new file mode 100644 index 00000000000..496956feef4 --- /dev/null +++ b/components/DynamicQRCode.tsx @@ -0,0 +1,274 @@ +import React, { Component } from 'react'; +import { Dimensions, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; + +import { encodeUR } from '../blue_modules/ur'; +import { BlueCurrentTheme } from '../components/themes'; +import loc from '../loc'; +import QRCode from './QRCode'; +import { BlueSpacing20 } from './BlueSpacing'; + +const { height, width } = Dimensions.get('window'); + +interface DynamicQRCodeProps { + value: string; + walletID?: string; + capacity?: number; + hideControls?: boolean; +} + +interface DynamicQRCodeState { + index: number; + total: number; + qrCodeHeight: number; + intervalHandler: ReturnType | number | null; + displayQRCode: boolean; + hideControls?: boolean; +} + +export class DynamicQRCode extends Component { + constructor(props: DynamicQRCodeProps) { + super(props); + const qrCodeHeight = height > width ? width - 40 : width / 3; + const qrCodeMaxHeight = 370; + this.state = { + index: 0, + total: 0, + qrCodeHeight: Math.min(qrCodeHeight, qrCodeMaxHeight), + intervalHandler: null, + displayQRCode: true, + }; + } + + fragments: string[] = []; + + componentWillUnmount() { + this.stopAutoMove(); + } + + componentDidMount() { + const { value, capacity = 175, hideControls = true, walletID } = this.props; + try { + this.fragments = encodeUR(value, capacity, walletID ?? null); + this.setState( + { + total: this.fragments.length, + hideControls, + displayQRCode: true, + }, + () => { + this.startAutoMove(); + }, + ); + } catch (e) { + console.log(e); + this.setState({ displayQRCode: false, hideControls }); + } + } + + moveToNextFragment = () => { + const { index, total } = this.state; + if (index === total - 1) { + this.setState({ + index: 0, + }); + } else { + this.setState(state => ({ + index: state.index + 1, + })); + } + }; + + forceUseBBQR = () => { + const { value, capacity = 175, hideControls = true, walletID } = this.props; + console.log({ value, capacity, walletID }); + + try { + this.fragments = encodeUR(value, capacity, walletID ?? null, 'BBQR'); + this.setState({ + total: this.fragments.length, + displayQRCode: true, + }); + } catch (e) { + console.log(e); + this.setState({ displayQRCode: false, hideControls }); + } + }; + + forceUseURv2 = () => { + const { value, capacity = 175, hideControls = true, walletID } = this.props; + console.log({ value, capacity, walletID }); + + try { + this.fragments = encodeUR(value, capacity, walletID ?? null, 'URv2'); + this.setState({ + total: this.fragments.length, + displayQRCode: true, + }); + } catch (e) { + console.log(e); + this.setState({ displayQRCode: false, hideControls }); + } + }; + + startAutoMove = () => { + if (!this.state.intervalHandler) + this.setState(() => ({ + intervalHandler: setInterval(this.moveToNextFragment, 500), + })); + }; + + stopAutoMove = () => { + clearInterval(this.state.intervalHandler as number); + this.setState(() => ({ + intervalHandler: null, + })); + }; + + moveToPreviousFragment = () => { + const { index, total } = this.state; + if (index > 0) { + this.setState(state => ({ + index: state.index - 1, + })); + } else { + this.setState(state => ({ + index: total - 1, + })); + } + }; + + onError = () => { + console.log('Data is too large for QR Code.'); + this.setState({ displayQRCode: false }); + }; + + render() { + const currentFragment = this.fragments[this.state.index]; + + if (!currentFragment && this.state.displayQRCode) { + return ( + + {loc.send.dynamic_init} + + ); + } + + return ( + + { + this.setState(prevState => ({ hideControls: !prevState.hideControls })); + }} + > + {this.state.displayQRCode && ( + + + + )} + + + {!this.state.hideControls && ( + + + + + {loc.formatString(loc._.of, { number: this.state.index + 1, total: this.state.total })} + + + + + + {loc.send.dynamic_prev} + + + {this.state.intervalHandler ? loc.send.dynamic_stop : loc.send.dynamic_start} + + + {loc.send.dynamic_next} + + + + + + Force use BBQR + + + Force use URv2 + + + + )} + + ); + } +} + +const animatedQRCodeStyle = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'column', + alignItems: 'center', + }, + qrcodeContainer: { + alignItems: 'center', + justifyContent: 'center', + }, + controller: { + width: '90%', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + borderRadius: 25, + height: 45, + paddingHorizontal: 18, + }, + controller2: { + flexDirection: 'column', + }, + button: { + alignItems: 'center', + height: 45, + justifyContent: 'center', + }, + buttonPrev: { + width: '25%', + alignItems: 'flex-start', + }, + buttonUseFormat: { + alignItems: 'center', + paddingTop: 25, + }, + buttonStopStart: { + width: '50%', + }, + buttonNext: { + width: '25%', + alignItems: 'flex-end', + }, + text: { + fontSize: 14, + color: BlueCurrentTheme.colors.foregroundColor, + fontWeight: 'bold', + }, +}); diff --git a/components/FloatButtons.js b/components/FloatButtons.js deleted file mode 100644 index 32ff1060e30..00000000000 --- a/components/FloatButtons.js +++ /dev/null @@ -1,138 +0,0 @@ -import React, { useState, useRef, forwardRef } from 'react'; -import PropTypes from 'prop-types'; -import { View, Text, TouchableOpacity, StyleSheet, Dimensions, PixelRatio } from 'react-native'; -import { useTheme } from '@react-navigation/native'; - -const BORDER_RADIUS = 30; -const PADDINGS = 8; -const ICON_MARGIN = 7; - -const cStyles = StyleSheet.create({ - root: { - alignSelf: 'center', - height: '6.3%', - minHeight: 44, - }, - rootAbsolute: { - position: 'absolute', - bottom: 30, - }, - rootInline: {}, - rootPre: { - position: 'absolute', - bottom: -1000, - }, - rootPost: { - borderRadius: BORDER_RADIUS, - flexDirection: 'row', - overflow: 'hidden', - }, -}); - -export const FContainer = forwardRef((props, ref) => { - const [newWidth, setNewWidth] = useState(); - const layoutCalculated = useRef(false); - - const onLayout = event => { - if (layoutCalculated.current) return; - const maxWidth = Dimensions.get('window').width - BORDER_RADIUS - 20; - const { width } = event.nativeEvent.layout; - const withPaddings = Math.ceil(width + PADDINGS * 2); - const len = React.Children.toArray(props.children).filter(Boolean).length; - let newW = withPaddings * len > maxWidth ? Math.floor(maxWidth / len) : withPaddings; - if (len === 1 && newW < 90) newW = 90; // to add Paddings for lonely small button, like Scan on main screen - setNewWidth(newW); - layoutCalculated.current = true; - }; - - return ( - - {newWidth - ? React.Children.toArray(props.children) - .filter(Boolean) - .map((c, index, array) => - React.cloneElement(c, { - width: newWidth, - key: index, - first: index === 0, - last: index === array.length - 1, - }), - ) - : props.children} - - ); -}); - -FContainer.propTypes = { - children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.element), PropTypes.element]), - inline: PropTypes.bool, -}; - -const buttonFontSize = - PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22 - ? 22 - : PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26); - -const bStyles = StyleSheet.create({ - root: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - overflow: 'hidden', - }, - icon: { - alignItems: 'center', - }, - text: { - fontSize: buttonFontSize, - fontWeight: '600', - marginLeft: ICON_MARGIN, - backgroundColor: 'transparent', - }, -}); - -export const FButton = ({ text, icon, width, first, last, ...props }) => { - const { colors } = useTheme(); - const bStylesHook = StyleSheet.create({ - root: { - backgroundColor: colors.buttonBackgroundColor, - }, - text: { - color: colors.buttonAlternativeTextColor, - }, - textDisabled: { - color: colors.formBorder, - }, - }); - const style = {}; - - if (width) { - const paddingLeft = first ? BORDER_RADIUS / 2 : PADDINGS; - const paddingRight = last ? BORDER_RADIUS / 2 : PADDINGS; - style.paddingRight = paddingRight; - style.paddingLeft = paddingLeft; - style.width = width + paddingRight + paddingLeft; - } - - return ( - - {icon} - - {text} - - - ); -}; - -FButton.propTypes = { - text: PropTypes.string, - icon: PropTypes.element, - width: PropTypes.number, - first: PropTypes.bool, - last: PropTypes.bool, - disabled: PropTypes.bool, -}; diff --git a/components/FloatButtons.tsx b/components/FloatButtons.tsx new file mode 100644 index 00000000000..832f7ee7154 --- /dev/null +++ b/components/FloatButtons.tsx @@ -0,0 +1,608 @@ +import React, { forwardRef, ReactNode, useEffect, useRef, useState, useCallback, useMemo } from 'react'; +import { Animated, PixelRatio, StyleSheet, Text, TouchableOpacity, useWindowDimensions, View, StyleProp, TextStyle } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import LinearGradient from 'react-native-linear-gradient'; +import { useTheme } from './themes'; +import { useSizeClass, SizeClass } from '../blue_modules/sizeClass'; +import { isDesktop } from '../blue_modules/environment'; +import debounce from '../blue_modules/debounce'; +import { withAlpha } from './color'; + +const scheduleInNextFrame = (callback: () => void): number => { + return requestAnimationFrame(() => { + // Use a second requestAnimationFrame to ensure we're not in the same frame + requestAnimationFrame(callback); + }); +}; + +const LAYOUT = { + PADDINGS: 30, + ICON_MARGIN: 7, + BUTTON_MARGIN: 10, + MIN_BUTTON_WIDTH: 100, + MIN_BUTTON_WIDTH_LARGE: 130, + DRAWER_WIDTH: 320, + BUTTON_HEIGHT: 52, + CONTAINER_SIDE_MARGIN: 16, + PILL_BORDER_RADIUS: 100, + SINGLE_BUTTON_WIDTH_FACTOR: 0.625, + MAX_BUTTON_FONT_SIZE: 24, + SAFETY_MARGIN: 20, +}; + +const BUTTON_SCALE_PRESSED = 0.96; +const BUTTON_SCALE_ANIMATION_DURATION_MS = 110; + +const useFloatButtonAnimation = (initialHeight: number) => { + const slideAnimation = useRef(new Animated.Value(isDesktop ? 0 : initialHeight)).current; + + useEffect(() => { + if (isDesktop) return; + Animated.spring(slideAnimation, { + toValue: 0, + friction: 7, + tension: 40, + useNativeDriver: true, + }).start(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return { + slideAnimation, + }; +}; + +const useFloatButtonLayout = (width: number, sizeClass: SizeClass) => { + const lastVerticalDecision = useRef(false); + + const shouldUseVerticalLayout = useCallback( + (totalWidthNeeded: number, availableWidth: number, totalChildren: number) => { + if (sizeClass !== SizeClass.Large || totalChildren <= 1) return false; + + const minWidthPerButton = 130; + const totalButtonsWidth = minWidthPerButton * totalChildren; + const totalSpacing = LAYOUT.BUTTON_MARGIN * (totalChildren - 1); + const minRequiredWidth = totalButtonsWidth + totalSpacing; + + const wouldBeTooNarrow = availableWidth < minRequiredWidth; + + if (!lastVerticalDecision.current && wouldBeTooNarrow) { + const shouldSwitch = availableWidth < minRequiredWidth * 0.9; + lastVerticalDecision.current = shouldSwitch; + return shouldSwitch; + } + + if (lastVerticalDecision.current && !wouldBeTooNarrow) { + const shouldSwitchBack = availableWidth > minRequiredWidth * 1.2; + lastVerticalDecision.current = !shouldSwitchBack; + return !shouldSwitchBack; + } + + return lastVerticalDecision.current; + }, + [sizeClass], + ); + + const calculateButtonWidth = useCallback( + (containerWidth: number, totalChildren: number): number => { + if (containerWidth <= 0) return 0; + + const drawerOffset = sizeClass === SizeClass.Large ? LAYOUT.DRAWER_WIDTH : 0; + const availableWidth = width - drawerOffset - LAYOUT.CONTAINER_SIDE_MARGIN * 2; + + const contentWidth = Math.ceil(containerWidth); + const buttonWidth = contentWidth + LAYOUT.PADDINGS * 2; + const totalButtonWidth = buttonWidth * totalChildren; + const totalSpacersWidth = (totalChildren - 1) * LAYOUT.BUTTON_MARGIN; + const totalWidthNeeded = totalButtonWidth + totalSpacersWidth + LAYOUT.SAFETY_MARGIN; + + const effectiveMinButtonWidth = + sizeClass === SizeClass.Large + ? LAYOUT.MIN_BUTTON_WIDTH_LARGE + : sizeClass === SizeClass.Regular + ? LAYOUT.MIN_BUTTON_WIDTH + : LAYOUT.MIN_BUTTON_WIDTH * 0.85; + + const shouldBeVertical = shouldUseVerticalLayout(totalWidthNeeded, availableWidth, totalChildren); + + let calculatedWidth; + + if (shouldBeVertical) { + calculatedWidth = sizeClass === SizeClass.Large ? availableWidth - LAYOUT.CONTAINER_SIDE_MARGIN * 2 : availableWidth; + } else { + if (totalWidthNeeded > availableWidth) { + const availableWidthPerButton = (availableWidth - totalSpacersWidth) / totalChildren; + calculatedWidth = Math.floor(availableWidthPerButton) - LAYOUT.PADDINGS * 2; + } else { + calculatedWidth = Math.max(contentWidth, effectiveMinButtonWidth); + } + } + + if (totalChildren === 1 && !shouldBeVertical) { + const singleButtonMaxWidth = availableWidth * (sizeClass === SizeClass.Compact ? 0.7 : LAYOUT.SINGLE_BUTTON_WIDTH_FACTOR); + const effectiveSingleMinWidth = sizeClass === SizeClass.Large ? LAYOUT.MIN_BUTTON_WIDTH * 1.2 : LAYOUT.MIN_BUTTON_WIDTH; + + calculatedWidth = Math.max( + effectiveSingleMinWidth - LAYOUT.PADDINGS * 2, + Math.min(calculatedWidth, singleButtonMaxWidth - LAYOUT.PADDINGS * 2), + ); + } + + return Math.floor(calculatedWidth); + }, + [width, sizeClass, shouldUseVerticalLayout], + ); + + const calculateVisualParameters = useCallback( + (calculatedWidth: number, totalChildren: number) => { + const drawerOffset = sizeClass === SizeClass.Large ? LAYOUT.DRAWER_WIDTH : 0; + const availableWidth = width - drawerOffset - LAYOUT.CONTAINER_SIDE_MARGIN * 2; + + const buttonWidth = Math.max(calculatedWidth, LAYOUT.MIN_BUTTON_WIDTH_LARGE) + LAYOUT.PADDINGS * 2; + const totalButtonWidth = buttonWidth * totalChildren; + const totalSpacersWidth = (totalChildren - 1) * LAYOUT.BUTTON_MARGIN; + const totalWidthNeeded = totalButtonWidth + totalSpacersWidth; + + const shouldBeVertical = shouldUseVerticalLayout(totalWidthNeeded, availableWidth, totalChildren); + + const buttonRadius = LAYOUT.PILL_BORDER_RADIUS; + + return { buttonRadius, shouldBeVertical }; + }, + [width, sizeClass, shouldUseVerticalLayout], + ); + + const calculateContainerHeight = useCallback((childrenCount: number, isVerticalLayout: boolean) => { + if (!isVerticalLayout) return { height: '8%', minHeight: LAYOUT.BUTTON_HEIGHT }; + + const totalButtonsHeight = childrenCount * LAYOUT.BUTTON_HEIGHT; + const totalMarginsHeight = (childrenCount - 1) * LAYOUT.BUTTON_MARGIN; + const calculatedHeight = totalButtonsHeight + totalMarginsHeight; + + return { height: calculatedHeight }; + }, []); + + const calculateButtonFontSize = useMemo(() => { + const divisor = sizeClass === SizeClass.Large ? 22 : sizeClass === SizeClass.Regular ? 24 : 28; + const baseSize = PixelRatio.roundToNearestPixel(width / divisor); + return Math.min(LAYOUT.MAX_BUTTON_FONT_SIZE, baseSize); + }, [width, sizeClass]); + + return { + calculateButtonWidth, + calculateVisualParameters, + calculateContainerHeight, + buttonFontSize: calculateButtonFontSize, + }; +}; + +const containerStyles = StyleSheet.create({ + root: { + alignSelf: 'center', + height: '8%', + minHeight: LAYOUT.BUTTON_HEIGHT, + marginHorizontal: LAYOUT.CONTAINER_SIDE_MARGIN, + }, + rootAbsolute: { + position: 'absolute', + }, + rootInline: {}, + rootPre: { + position: 'absolute', + bottom: -1000, + }, + rootPost: { + flexDirection: 'row', + overflow: 'hidden', + }, + rootPostVertical: { + flexDirection: 'column', + overflow: 'hidden', + }, + childWrapper: { + width: '100%', + }, +}); + +const buttonStyles = StyleSheet.create({ + root: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', + }, + iconContainer: { + alignItems: 'center', + justifyContent: 'center', + minWidth: 24, + minHeight: 24, + overflow: 'visible', + alignSelf: 'center', + }, + centeredText: { + textAlign: 'center', + textAlignVertical: 'center', + }, +}); + +const buttonContentStaticStyles = StyleSheet.create({ + root: { + height: LAYOUT.BUTTON_HEIGHT, + overflow: 'hidden', + justifyContent: 'center', + }, + marginRight: { + marginRight: LAYOUT.BUTTON_MARGIN, + }, + marginBottom: { + marginBottom: LAYOUT.BUTTON_MARGIN, + }, + textBase: { + fontWeight: '600', + marginLeft: LAYOUT.ICON_MARGIN, + backgroundColor: 'transparent', + textAlign: 'center', + textAlignVertical: 'center', + }, + contentContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: '100%', + }, +}); + +interface FContainerProps { + children: ReactNode | ReactNode[]; + inline?: boolean; +} + +interface FButtonProps { + text: string; + icon: ReactNode; + width?: number; + last?: boolean; + singleChild?: boolean; + isVertical?: boolean; + borderRadius?: number; + fontSize?: number; + disabled?: boolean; + testID?: string; + onPress: () => void; + onLongPress?: () => void; +} + +interface ButtonContentProps { + icon: ReactNode; + text: string; + textStyle: StyleProp; +} + +const getScaledIconSize = (fontSize: number): number => { + return Math.max(Math.round(fontSize * 1.2), 16); +}; + +const ButtonContent = ({ icon, text, textStyle }: ButtonContentProps) => { + const computedStyle = StyleSheet.flatten(textStyle); + const fontSize = computedStyle.fontSize || LAYOUT.MAX_BUTTON_FONT_SIZE; + const iconSize = getScaledIconSize(Number(fontSize)); + + let scaledIcon; + + if (React.isValidElement(icon)) { + const iconElement = icon as React.ReactElement; + + scaledIcon = React.cloneElement( + iconElement as React.ReactElement, + { + ...(iconElement.props as Record), + size: iconSize, + width: iconSize, + height: iconSize, + } as any, + ); + } else { + scaledIcon = icon; + } + + return ( + + {scaledIcon} + + {text} + + + ); +}; + +export const FButton = ({ + text, + icon, + width, + last, + singleChild, + isVertical, + borderRadius = LAYOUT.PILL_BORDER_RADIUS, + fontSize = LAYOUT.MAX_BUTTON_FONT_SIZE, + testID, + ...props +}: FButtonProps) => { + const { colors } = useTheme(); + const scale = useRef(new Animated.Value(1)).current; + + const animateScaleTo = useCallback( + (toValue: number) => { + Animated.timing(scale, { + toValue, + duration: BUTTON_SCALE_ANIMATION_DURATION_MS, + useNativeDriver: true, + }).start(); + }, + [scale], + ); + + const customButtonStyles = useMemo(() => { + const baseStyles = { ...buttonContentStaticStyles.root }; + return { + root: { + ...baseStyles, + backgroundColor: colors.buttonBackgroundColor, + }, + text: { + color: colors.buttonAlternativeTextColor, + fontSize, + }, + textDisabled: { + color: colors.formBorder, + }, + marginRight: buttonContentStaticStyles.marginRight, + marginBottom: buttonContentStaticStyles.marginBottom, + textBase: buttonContentStaticStyles.textBase, + }; + }, [colors, fontSize]); + + const style: Record = {}; + const additionalStyles = !last ? (isVertical ? customButtonStyles.marginBottom : customButtonStyles.marginRight) : {}; + + if (width) { + style.paddingHorizontal = LAYOUT.PADDINGS; + if (singleChild && !isVertical) { + style.width = width * LAYOUT.SINGLE_BUTTON_WIDTH_FACTOR + LAYOUT.PADDINGS * 2; + } else { + style.width = isVertical ? '100%' : width + LAYOUT.PADDINGS * 2; + } + } + + const textStyle = [customButtonStyles.textBase, props.disabled ? customButtonStyles.textDisabled : customButtonStyles.text]; + + const handlePressIn = useCallback(() => { + if (props.disabled) return; + animateScaleTo(BUTTON_SCALE_PRESSED); + }, [animateScaleTo, props.disabled]); + + const handlePressOut = useCallback(() => { + animateScaleTo(1); + }, [animateScaleTo]); + + return ( + + + + + + ); +}; + +export const FContainer = forwardRef((props, ref) => { + const insets = useSafeAreaInsets(); + const { height, width } = useWindowDimensions(); + const { sizeClass } = useSizeClass(); + + const childrenCount = React.Children.toArray(props.children).filter(Boolean).length; + + const initialLayoutWidth = useMemo(() => { + const drawerOffset = sizeClass === SizeClass.Large ? LAYOUT.DRAWER_WIDTH : 0; + return Math.max(0, Math.ceil(width - drawerOffset - LAYOUT.CONTAINER_SIDE_MARGIN * 2)); + }, [width, sizeClass]); + + const [layoutReady, setLayoutReady] = useState(() => initialLayoutWidth > 0); + const { calculateButtonWidth, calculateVisualParameters, calculateContainerHeight, buttonFontSize } = useFloatButtonLayout( + width, + sizeClass, + ); + + // Compute initial geometry up-front so the slide-in animation starts at the final (computed) size, + // avoiding a visible "big-to-small" jump during the entrance animation. + const initialGeometry = useMemo(() => { + if (initialLayoutWidth <= 0) { + return { + calculatedWidth: undefined as number | undefined, + shouldBeVertical: false, + buttonRadius: LAYOUT.PILL_BORDER_RADIUS, + }; + } + const calculatedWidth = calculateButtonWidth(initialLayoutWidth, childrenCount); + const { buttonRadius, shouldBeVertical } = calculateVisualParameters(calculatedWidth, childrenCount); + return { calculatedWidth, shouldBeVertical, buttonRadius }; + }, [initialLayoutWidth, calculateButtonWidth, calculateVisualParameters, childrenCount]); + + const [newWidth, setNewWidth] = useState(() => initialGeometry.calculatedWidth); + const [isVertical, setIsVertical] = useState(() => initialGeometry.shouldBeVertical); + const [buttonBorderRadius, setButtonBorderRadius] = useState(() => initialGeometry.buttonRadius); + + const latest = useRef({ newWidth, isVertical, buttonBorderRadius }); + latest.current = { newWidth, isVertical, buttonBorderRadius }; + + const layoutWidth = useRef(initialLayoutWidth); + // Avoid running the animation on the very first layout calculation. + // We already set initial geometry, so we can skip this first pass to prevent redundant state churn. + const isFirstLayoutCalculation = useRef(true); + + const bottomInsets = useMemo( + () => ({ + bottom: insets.bottom ? insets.bottom + 10 : 30, + }), + [insets.bottom], + ); + + const { slideAnimation } = useFloatButtonAnimation(height); + + const handleBorderRadiusAnimation = useCallback((buttonRadius: number, shouldBeVertical: boolean, calculatedWidth: number) => { + setNewWidth(calculatedWidth); + setIsVertical(shouldBeVertical); + setButtonBorderRadius(buttonRadius); + }, []); + + const calculateLayout = useCallback(() => { + if (!layoutReady || layoutWidth.current <= 0) return; + + scheduleInNextFrame(() => { + const calculatedWidth = calculateButtonWidth(layoutWidth.current, childrenCount); + const { buttonRadius, shouldBeVertical } = calculateVisualParameters(calculatedWidth, childrenCount); + + if (isFirstLayoutCalculation.current) { + isFirstLayoutCalculation.current = false; + return; + } + + const prev = latest.current; + const widthDelta = Math.abs((prev.newWidth ?? 0) - calculatedWidth); + const buttonRadiusDelta = Math.abs(buttonRadius - prev.buttonBorderRadius); + + const widthEps = childrenCount === 1 ? 1 : 2; + const radiusEps = 0.5; + if (shouldBeVertical === prev.isVertical) { + if (widthDelta <= widthEps && buttonRadiusDelta <= radiusEps) return; + } + + if (shouldBeVertical !== prev.isVertical || widthDelta > widthEps) { + handleBorderRadiusAnimation(buttonRadius, shouldBeVertical, calculatedWidth); + } else { + setNewWidth(calculatedWidth); + setIsVertical(shouldBeVertical); + setButtonBorderRadius(buttonRadius); + } + }); + }, [ + layoutReady, + calculateButtonWidth, + calculateVisualParameters, + handleBorderRadiusAnimation, + childrenCount, + setNewWidth, + setIsVertical, + setButtonBorderRadius, + ]); + + const debouncedCalculateLayout = useMemo(() => debounce(calculateLayout, 16), [calculateLayout]); + + useEffect(() => { + debouncedCalculateLayout(); + }, [debouncedCalculateLayout, width, height, childrenCount, sizeClass]); + + const onLayout = (event: { nativeEvent: { layout: { width: number } } }) => { + const { width: currentLayoutWidth } = event.nativeEvent.layout; + + if (currentLayoutWidth > 0) { + if (Math.abs(layoutWidth.current - currentLayoutWidth) > 2) { + layoutWidth.current = currentLayoutWidth; + } + + if (!layoutReady) { + setLayoutReady(true); + } + } + }; + + const renderChild = (child: ReactNode, index: number, array: ReactNode[]): ReactNode => { + if (typeof child === 'string') { + return ( + + + {child} + + + ); + } + + const isSingleChild = array.length === 1; + + return React.cloneElement(child as React.ReactElement, { + width: effectiveNewWidth, + key: index, + last: index === array.length - 1, + singleChild: isSingleChild, + isVertical, + borderRadius: buttonBorderRadius, + fontSize: buttonFontSize, + }); + }; + + const containerHeight = useMemo( + () => calculateContainerHeight(childrenCount, isVertical), + [calculateContainerHeight, childrenCount, isVertical], + ); + + const effectiveNewWidth = newWidth ?? layoutWidth.current; + + const combinedStyles = useMemo( + () => [ + containerStyles.root, + props.inline ? containerStyles.rootInline : containerStyles.rootAbsolute, + bottomInsets, + effectiveNewWidth ? (isVertical ? containerStyles.rootPostVertical : containerStyles.rootPost) : containerStyles.rootPre, + isVertical ? containerHeight : null, + { transform: [{ translateY: slideAnimation }] }, + ], + [props.inline, bottomInsets, effectiveNewWidth, isVertical, containerHeight, slideAnimation], + ); + + return ( + + {layoutReady ? React.Children.toArray(props.children).filter(Boolean).map(renderChild) : props.children} + + ); +}); + +const BOTTOM_FADE_HEIGHT = 50; + +const bottomFadeStyles = StyleSheet.create({ + wrapper: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + }, +}); + +const BOTTOM_FADE_GRADIENT_START = { x: 0.5, y: 1 }; +const BOTTOM_FADE_GRADIENT_END = { x: 0.5, y: 0 }; + +export const FloatButtonsBottomFade = React.memo(() => { + const insets = useSafeAreaInsets(); + const { colors } = useTheme(); + + const heightStyle = useMemo(() => ({ height: BOTTOM_FADE_HEIGHT + insets.bottom }), [insets.bottom]); + const gradientColors = useMemo(() => [colors.background, withAlpha(colors.background, 0)], [colors.background]); + + return ( + + + + ); +}); diff --git a/components/HandOffComponent.ios.tsx b/components/HandOffComponent.ios.tsx new file mode 100644 index 00000000000..e0155319658 --- /dev/null +++ b/components/HandOffComponent.ios.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import DefaultPreference from 'react-native-default-preference'; +// @ts-ignore: Handoff is not typed +import Handoff from 'react-native-handoff'; +import { useSettings } from '../hooks/context/useSettings'; +import { GROUP_IO_BLUEWALLET } from '../blue_modules/currency'; +import { BlueApp } from '../class/blue-app'; +import { HandOffComponentProps } from './types'; + +const HandOffComponent: React.FC = props => { + const { isHandOffUseEnabled } = useSettings(); + if (!props || !props.type || !props.userInfo || Object.keys(props.userInfo).length === 0) { + console.debug('HandOffComponent: Missing required type or userInfo data'); + return null; + } + const userInfo = JSON.stringify(props.userInfo); + console.debug(`HandOffComponent is rendering. Type: ${props.type}, UserInfo: ${userInfo}...`); + return isHandOffUseEnabled ? : null; +}; + +const MemoizedHandOffComponent = React.memo(HandOffComponent); + +export const setIsHandOffUseEnabled = async (value: boolean) => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + await DefaultPreference.set(BlueApp.HANDOFF_STORAGE_KEY, value.toString()); + console.debug('setIsHandOffUseEnabled', value); + } catch (error) { + console.error('Error setting handoff enabled status:', error); + throw error; // Propagate error to caller + } +}; + +export const getIsHandOffUseEnabled = async (): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + const isEnabledValue = await DefaultPreference.get(BlueApp.HANDOFF_STORAGE_KEY); + const result = isEnabledValue === 'true'; + console.debug('getIsHandOffUseEnabled', result); + return result; + } catch (error) { + console.error('Error getting handoff enabled status:', error); + return false; + } +}; + +export default MemoizedHandOffComponent; diff --git a/components/HandOffComponent.tsx b/components/HandOffComponent.tsx new file mode 100644 index 00000000000..12787f7df99 --- /dev/null +++ b/components/HandOffComponent.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { HandOffComponentProps } from './types'; + +const HandOffComponent: React.FC = props => { + console.debug('HandOffComponent render.'); + return null; +}; + +export const setIsHandOffUseEnabled = async (value: boolean) => {}; + +export const getIsHandOffUseEnabled = async (): Promise => { + return false; +}; + +export default HandOffComponent; diff --git a/components/Header.tsx b/components/Header.tsx new file mode 100644 index 00000000000..eef8335e32f --- /dev/null +++ b/components/Header.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import { useTheme } from './themes'; +import AddWalletButton from './AddWalletButton'; + +interface HeaderProps { + leftText: string; + isDrawerList?: boolean; + onNewWalletPress?: () => void; +} + +export const Header: React.FC = ({ leftText, isDrawerList, onNewWalletPress }) => { + const { colors } = useTheme(); + const styleWithProps = StyleSheet.create({ + root: { + backgroundColor: isDrawerList ? colors.elevated : colors.background, + borderTopColor: isDrawerList ? colors.elevated : colors.background, + borderBottomColor: isDrawerList ? colors.elevated : colors.background, + }, + text: { + color: colors.foregroundColor, + }, + }); + + return ( + + {leftText} + {onNewWalletPress && } + + ); +}; + +const styles = StyleSheet.create({ + root: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 16, + marginBottom: 8, + }, + text: { + textAlign: 'left', + fontWeight: 'bold', + fontSize: 34, + }, +}); diff --git a/components/HeaderMenuButton.tsx b/components/HeaderMenuButton.tsx new file mode 100644 index 00000000000..d46fe16e357 --- /dev/null +++ b/components/HeaderMenuButton.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { Pressable, Platform, StyleSheet } from 'react-native'; +import ToolTipMenu from './TooltipMenu'; +import { useTheme } from './themes'; +import Icon from './Icon'; +import { Action } from './types'; + +interface HeaderMenuButtonProps { + onPressMenuItem: (id: string) => void; + actions?: Action[] | Action[][]; + disabled?: boolean; + title?: string; +} + +const HeaderMenuButton: React.FC = ({ onPressMenuItem, actions, disabled, title }) => { + const { colors } = useTheme(); + const styleProps = Platform.OS === 'android' ? { iconStyle: { transform: [{ rotate: '90deg' }] } } : {}; + + if (!actions || actions.length === 0) { + return ( + [styles.buttonCenter, pressed && styles.pressed]} + > + + + ); + } + + const menuActions = Array.isArray(actions[0]) ? (actions as Action[][]) : (actions as Action[]); + + return ( + + + + ); +}; + +const styles = StyleSheet.create({ + buttonCenter: { + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 8, + paddingVertical: 4, + minWidth: 44, + minHeight: 44, + }, + pressed: { opacity: 0.5 }, +}); + +export default HeaderMenuButton; diff --git a/components/HeaderRightButton.tsx b/components/HeaderRightButton.tsx new file mode 100644 index 00000000000..19f8a391e76 --- /dev/null +++ b/components/HeaderRightButton.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { StyleSheet, Text, TouchableOpacity } from 'react-native'; + +import { useTheme } from './themes'; + +interface HeaderRightButtonProps { + disabled: boolean; + onPress?: () => void; + title: string; + testID?: string; +} + +const HeaderRightButton: React.FC = ({ disabled, onPress, title, testID }) => { + const { colors } = useTheme(); + const opacity = disabled ? 0.5 : 1; + return ( + + {title} + + ); +}; + +const styles = StyleSheet.create({ + save: { + alignItems: 'center', + justifyContent: 'center', + alignSelf: 'center', + flexDirection: 'row', + minWidth: 80, + paddingHorizontal: 12, + borderRadius: 8, + height: 34, + }, + saveText: { + fontSize: 15, + fontWeight: '600', + }, +}); + +export default HeaderRightButton; diff --git a/components/HighlightedText.tsx b/components/HighlightedText.tsx new file mode 100644 index 00000000000..a9169aaa32f --- /dev/null +++ b/components/HighlightedText.tsx @@ -0,0 +1,196 @@ +import React, { useCallback, useMemo, useEffect, useState } from 'react'; +import { Text, Animated, StyleSheet, Platform, TextStyle } from 'react-native'; +import useBounceAnimation from '../hooks/useBounceAnimation'; + +interface HighlightedTextProps { + text: string; + query: string; + numberOfLines?: number; + style?: TextStyle | TextStyle[]; + highlightStyle?: TextStyle; + bounceAnim?: Animated.Value; + caseSensitive?: boolean; + highlightOnlyFirstMatch?: boolean; +} + +interface TextPart { + text: string; + isMatch: boolean; +} + +const HighlightedText: React.FC = ({ + text, + query, + numberOfLines, + style, + highlightStyle, + bounceAnim: externalBounceAnim, + caseSensitive = false, + highlightOnlyFirstMatch = false, +}) => { + const internalBounceAnim = useBounceAnimation(query); + const bounceAnim = externalBounceAnim || internalBounceAnim; + const [queryKey, setQueryKey] = useState(''); + + useEffect(() => { + setQueryKey(query); + }, [query]); + + const baseTextStyle = useMemo(() => { + if (!style) return {}; + + if (Array.isArray(style)) { + return style.reduce((acc, curr) => ({ ...acc, ...curr }), {}); + } + + return style; + }, [style]); + + const highlightedStyle = useMemo( + () => ({ + ...styles.highlight, + ...(highlightStyle || {}), + fontSize: baseTextStyle.fontSize, + fontFamily: baseTextStyle.fontFamily, + fontWeight: baseTextStyle.fontWeight || '600', + lineHeight: baseTextStyle.lineHeight, + letterSpacing: baseTextStyle.letterSpacing, + transform: Platform.OS === 'ios' ? [{ scale: bounceAnim }] : undefined, + }), + [bounceAnim, highlightStyle, baseTextStyle], + ); + + // Create a style for non-highlighted text parts that ensures it looks the same as original text + const nonHighlightedStyle = useMemo( + () => ({ + ...baseTextStyle, // Copy all original text properties + }), + [baseTextStyle], + ); + + const renderTextPart = useCallback( + (part: TextPart, index: number) => { + if (part.isMatch) { + return ( + + + {part.text} + + + ); + } + + return ( + + {part.text} + + ); + }, + [queryKey, highlightedStyle, bounceAnim, nonHighlightedStyle], + ); + + const textParts = useMemo((): TextPart[] => { + if (!query) { + return [{ text, isMatch: false }]; + } + + try { + const searchQueryText = caseSensitive ? query : query.toLowerCase(); + const processedText = caseSensitive ? text : text.toLowerCase(); + + if (searchQueryText.trim() === '') { + return [{ text, isMatch: false }]; + } + + const parts: TextPart[] = []; + let lastIndex = 0; + let searchStartIndex = 0; + + while (true) { + const matchIndex = processedText.indexOf(searchQueryText, searchStartIndex); + + if (matchIndex === -1) { + break; + } + + if (matchIndex > lastIndex) { + parts.push({ + text: text.substring(lastIndex, matchIndex), + isMatch: false, + }); + } + + parts.push({ + text: text.substring(matchIndex, matchIndex + searchQueryText.length), + isMatch: true, + }); + + lastIndex = matchIndex + searchQueryText.length; + searchStartIndex = lastIndex; + + if (highlightOnlyFirstMatch) { + break; + } + } + + if (lastIndex < text.length) { + parts.push({ + text: text.substring(lastIndex), + isMatch: false, + }); + } + + return parts.length > 0 ? parts : [{ text, isMatch: false }]; + } catch (e) { + return [{ text, isMatch: false }]; + } + }, [text, query, caseSensitive, highlightOnlyFirstMatch]); + + if (textParts.length === 1 && !textParts[0].isMatch) { + return ( + + {text} + + ); + } + + return ( + + {textParts.map(renderTextPart)} + + ); +}; + +const styles = StyleSheet.create({ + text: { + fontSize: 16, + }, + highlightContainer: { + overflow: 'hidden', + margin: 0, + padding: 0, + }, + highlight: { + fontWeight: '600', + borderRadius: 4, + borderWidth: 1, + paddingHorizontal: 3, + paddingVertical: 1, + marginHorizontal: 1, + overflow: 'hidden', + textDecorationLine: Platform.OS === 'android' ? 'underline' : 'none', + backgroundColor: '#FFF5C0', + color: '#000000', + borderColor: 'rgba(255, 255, 255, 0.5)', + shadowColor: '#000000', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.2, + shadowRadius: 1, + elevation: 2, + }, +}); + +export default HighlightedText; diff --git a/components/Icon.tsx b/components/Icon.tsx new file mode 100644 index 00000000000..f3da932e4f2 --- /dev/null +++ b/components/Icon.tsx @@ -0,0 +1,103 @@ +import React from 'react'; +import { Pressable, StyleProp, TextStyle, View, ViewStyle } from 'react-native'; +import Entypo from '@react-native-vector-icons/entypo'; +import FontAwesome from '@react-native-vector-icons/fontawesome'; +import FontAwesome6 from '@react-native-vector-icons/fontawesome6'; +import Ionicons from '@react-native-vector-icons/ionicons'; +import MaterialDesignIcons from '@react-native-vector-icons/material-design-icons'; +import MaterialIcons from '@react-native-vector-icons/material-icons'; + +export type FontAwesomeIconName = React.ComponentProps['name']; +export type FontAwesome6IconName = React.ComponentProps['name']; +export type IonIconName = React.ComponentProps['name']; +export type MaterialIconName = React.ComponentProps['name']; +export type MaterialDesignIconName = React.ComponentProps['name']; +export type EntypoIconName = React.ComponentProps['name']; + +type IconType = 'font-awesome' | 'font-awesome-6' | 'ionicons' | 'material' | 'material-community' | 'entypo'; +type IconComponentType = React.ComponentType; + +type IconNameFor = T extends 'font-awesome' + ? FontAwesomeIconName + : T extends 'font-awesome-6' + ? FontAwesome6IconName + : T extends 'ionicons' + ? IonIconName + : T extends 'material' + ? MaterialIconName + : T extends 'material-community' + ? MaterialDesignIconName + : T extends 'entypo' + ? EntypoIconName + : never; + +export interface IconProps { + name: IconNameFor; + type?: T; + /** + * @default 24 + */ + size?: number; + color?: string; + style?: StyleProp; + iconStyle?: T extends 'font-awesome-6' ? 'solid' | 'brand' | 'regular' : StyleProp; + containerStyle?: StyleProp; + onPress?: () => void; + accessibilityLabel?: string; + testID?: string; +} + +const ICON_COMPONENTS: Record = { + 'font-awesome': FontAwesome, + 'font-awesome-6': FontAwesome6, + ionicons: Ionicons, + material: MaterialIcons, + 'material-community': MaterialDesignIcons, + entypo: Entypo, +}; + +const Icon = ({ + name, + type, + size = 24, + color, + style, + iconStyle, + containerStyle, + onPress, + accessibilityLabel, + testID, +}: IconProps): React.ReactElement | null => { + const IconComponent = ICON_COMPONENTS[type ?? 'font-awesome'] as React.ComponentType; + const isFa6 = type === 'font-awesome-6'; + const fa6IconStyle = isFa6 ? (typeof iconStyle === 'string' ? iconStyle : 'solid') : undefined; + const mergedStyle = isFa6 ? style : [style, iconStyle]; + + const content = ( + + ); + + if (onPress) { + return ( + + {content} + + ); + } + + if (containerStyle) { + return {content}; + } + + return content; +}; + +export default Icon; diff --git a/components/InputAccessoryAllFunds.js b/components/InputAccessoryAllFunds.js deleted file mode 100644 index 4f7ec698fc1..00000000000 --- a/components/InputAccessoryAllFunds.js +++ /dev/null @@ -1,126 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Text } from 'react-native-elements'; -import { InputAccessoryView, StyleSheet, Keyboard, Platform, View } from 'react-native'; -import { useTheme } from '@react-navigation/native'; - -import loc from '../loc'; -import { BitcoinUnit } from '../models/bitcoinUnits'; -import { BlueButtonLink } from '../BlueComponents'; - -const InputAccessoryAllFunds = ({ balance, canUseAll, onUseAllPressed }) => { - const { colors } = useTheme(); - - const stylesHook = StyleSheet.create({ - root: { - backgroundColor: colors.inputBackgroundColor, - }, - totalLabel: { - color: colors.alternativeTextColor, - }, - totalCanNot: { - color: colors.alternativeTextColor, - }, - }); - - const inputView = ( - - - {loc.send.input_total} - {canUseAll ? ( - - ) : ( - - {balance} {BitcoinUnit.BTC} - - )} - - - - - - ); - - if (Platform.OS === 'ios') { - return {inputView}; - } - - // androidPlaceholder View is needed to force shrink screen (KeyboardAvoidingView) where this component is used - return ( - <> - - {inputView} - - ); -}; - -InputAccessoryAllFunds.InputAccessoryViewID = 'useMaxInputAccessoryViewID'; - -InputAccessoryAllFunds.propTypes = { - balance: PropTypes.string.isRequired, - canUseAll: PropTypes.bool.isRequired, - onUseAllPressed: PropTypes.func.isRequired, -}; - -const styles = StyleSheet.create({ - root: { - flex: 1, - flexDirection: 'row', - maxHeight: 44, - justifyContent: 'space-between', - alignItems: 'center', - }, - left: { - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'flex-start', - }, - totalLabel: { - fontSize: 16, - marginLeft: 8, - marginRight: 0, - paddingRight: 0, - paddingLeft: 0, - paddingTop: 12, - paddingBottom: 12, - }, - totalCan: { - marginLeft: 8, - paddingRight: 0, - paddingLeft: 0, - paddingTop: 12, - paddingBottom: 12, - }, - totalCanNot: { - fontSize: 16, - marginLeft: 8, - marginRight: 0, - paddingRight: 0, - paddingLeft: 0, - paddingTop: 12, - paddingBottom: 12, - }, - right: { - flexDirection: 'row', - justifyContent: 'flex-end', - alignItems: 'flex-end', - }, - done: { - paddingRight: 8, - paddingLeft: 0, - paddingTop: 12, - paddingBottom: 12, - }, - androidPlaceholder: { - height: 44, - }, - androidAbsolute: { - height: 44, - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - }, -}); - -export default InputAccessoryAllFunds; diff --git a/components/InputAccessoryAllFunds.tsx b/components/InputAccessoryAllFunds.tsx new file mode 100644 index 00000000000..0d9acfd1f21 --- /dev/null +++ b/components/InputAccessoryAllFunds.tsx @@ -0,0 +1,123 @@ +import React from 'react'; +import { InputAccessoryView, Keyboard, Platform, StyleSheet, Text, View } from 'react-native'; +import BlueButtonLink from './BlueButtonLink'; +import loc from '../loc'; +import { BitcoinUnit } from '../models/bitcoinUnits'; +import { useTheme } from './themes'; + +interface InputAccessoryAllFundsProps { + balance: string; + canUseAll: boolean; + onUseAllPressed: () => void; +} + +const InputAccessoryAllFunds: React.FC = ({ balance, canUseAll, onUseAllPressed }) => { + const { colors } = useTheme(); + + const stylesHook = StyleSheet.create({ + root: { + backgroundColor: colors.inputBackgroundColor, + }, + totalLabel: { + color: colors.alternativeTextColor, + }, + totalCanNot: { + color: colors.alternativeTextColor, + }, + }); + + const inputView = ( + + + {loc.send.input_total} + {canUseAll ? ( + + ) : ( + + {balance} {BitcoinUnit.BTC} + + )} + + + + + + ); + + if (Platform.OS === 'ios') { + return {inputView}; + } + + // androidPlaceholder View is needed to force shrink screen (KeyboardAvoidingView) where this component is used + return ( + <> + + {inputView} + + ); +}; + +export const InputAccessoryAllFundsAccessoryViewID = 'useMaxInputAccessoryViewID'; + +const styles = StyleSheet.create({ + root: { + flex: 1, + flexDirection: 'row', + maxHeight: 44, + justifyContent: 'space-between', + alignItems: 'center', + }, + left: { + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'flex-start', + }, + totalLabel: { + fontSize: 16, + marginLeft: 8, + marginRight: 0, + paddingRight: 0, + paddingLeft: 0, + paddingTop: 12, + paddingBottom: 12, + }, + totalCan: { + marginLeft: 8, + paddingRight: 0, + paddingLeft: 0, + paddingTop: 12, + paddingBottom: 12, + }, + totalCanNot: { + fontSize: 16, + marginLeft: 8, + marginRight: 0, + paddingRight: 0, + paddingLeft: 0, + paddingTop: 12, + paddingBottom: 12, + }, + right: { + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'flex-end', + }, + done: { + paddingRight: 8, + paddingLeft: 0, + paddingTop: 12, + paddingBottom: 12, + }, + androidPlaceholder: { + height: 44, + }, + androidAbsolute: { + height: 44, + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + }, +}); + +export default InputAccessoryAllFunds; diff --git a/components/LNNodeBar.js b/components/LNNodeBar.js deleted file mode 100644 index 9fb7b97a829..00000000000 --- a/components/LNNodeBar.js +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; -import loc, { formatBalanceWithoutSuffix } from '../loc'; -import PropTypes from 'prop-types'; -import { BitcoinUnit } from '../models/bitcoinUnits'; -import { useTheme } from '@react-navigation/native'; - -export const LNNodeBar = props => { - const { canReceive = 0, canSend = 0, nodeAlias = '', disabled = false, itemPriceUnit = BitcoinUnit.SATS } = props; - const { colors } = useTheme(); - const opacity = { opacity: disabled ? 0.5 : 1.0 }; - const canSendBarFlex = { - flex: canReceive > 0 && canSend > 0 ? Math.abs(canSend / (canReceive + canSend)) * 1.0 : 1.0, - }; - const stylesHook = StyleSheet.create({ - nodeAlias: { - color: colors.alternativeTextColor2, - }, - }); - return ( - - {nodeAlias.trim().length > 0 && {nodeAlias}} - - - - - - - - - {loc.lnd.can_send.toUpperCase()} - {formatBalanceWithoutSuffix(canSend, itemPriceUnit, true).toString()} - - - {loc.lnd.can_receive.toUpperCase()} - {formatBalanceWithoutSuffix(canReceive, itemPriceUnit, true).toString()} - - - - ); -}; - -export default LNNodeBar; - -LNNodeBar.propTypes = { - canReceive: PropTypes.number.isRequired, - canSend: PropTypes.number.isRequired, - nodeAlias: PropTypes.string, - disabled: PropTypes.bool, - itemPriceUnit: PropTypes.string, -}; -const styles = StyleSheet.create({ - root: { - flex: 1, - }, - containerBottomText: { - flexDirection: 'row', - justifyContent: 'space-between', - marginTop: 16, - }, - nodeAlias: { - marginVertical: 16, - }, - canSendBar: { - height: 14, - maxHeight: 14, - backgroundColor: '#4E6CF5', - borderRadius: 6, - }, - canReceiveBar: { backgroundColor: '#57B996', borderRadius: 6, height: 14, maxHeight: 14 }, - fullFlexDirectionRow: { - flexDirection: 'row', - flex: 1, - }, - containerBottomLeftText: {}, - containerBottomRightText: {}, - titleText: { - color: '#9AA0AA', - }, - canReceive: { - color: '#57B996', - textAlign: 'right', - }, - canSend: { - color: '#4E6CF5', - textAlign: 'left', - }, -}); diff --git a/components/LdkButton.js b/components/LdkButton.js deleted file mode 100644 index f040a227cff..00000000000 --- a/components/LdkButton.js +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */ -import { useTheme } from '@react-navigation/native'; -import { Image, TouchableOpacity, View } from 'react-native'; -import { Text } from 'react-native-elements'; -import React from 'react'; - -export const LdkButton = props => { - const { colors } = useTheme(); - return ( - - - - - - - - {props.text || '?'} - {props.subtext || '?'} - - - - - ); -}; diff --git a/components/ListItem.tsx b/components/ListItem.tsx new file mode 100644 index 00000000000..73aa55283c3 --- /dev/null +++ b/components/ListItem.tsx @@ -0,0 +1,217 @@ +import React, { useMemo } from 'react'; +import { Pressable, StyleProp, StyleSheet, Switch, SwitchProps, Text, TextStyle, View, ViewStyle } from 'react-native'; +import { useLocale } from '@react-navigation/native'; + +import Icon from './Icon'; +import { useTheme } from './themes'; + +interface ListItemProps { + leftAvatar?: React.JSX.Element; + containerStyle?: StyleProp; + noFeedback?: boolean; + bottomDivider?: boolean; + testID?: string; + switchTestID?: string; + onPress?: () => void; + disabled?: boolean; + switch?: SwitchProps; + title: string; + titleStyle?: StyleProp; + subtitle?: string | React.ReactNode; + subtitleNumberOfLines?: number; + rightTitle?: string; + rightTitleStyle?: StyleProp; + rightSubtitle?: string | React.ReactNode; + rightSubtitleStyle?: StyleProp; + chevron?: boolean; + checkmark?: boolean; + isLoading?: boolean; +} + +const ListItem: React.FC = React.memo( + ({ + leftAvatar, + containerStyle, + noFeedback = false, + bottomDivider = true, + testID, + switchTestID, + onPress, + disabled, + switch: switchProps, + title, + titleStyle, + subtitle, + subtitleNumberOfLines, + rightTitle, + rightTitleStyle, + rightSubtitle, + rightSubtitleStyle, + chevron, + checkmark, + isLoading, + }: ListItemProps) => { + const { colors } = useTheme(); + const { direction } = useLocale(); + const isRtl = direction === 'rtl'; + const stylesHook = StyleSheet.create({ + title: { + color: disabled ? colors.buttonDisabledTextColor : colors.foregroundColor, + fontSize: 16, + fontWeight: '500', + writingDirection: direction, + }, + rightMemoText: { + textAlign: direction === 'rtl' ? 'left' : 'right', + }, + subtitle: { + flexWrap: 'wrap', + writingDirection: direction, + color: colors.alternativeTextColor, + fontWeight: '400', + paddingVertical: switchProps ? 8 : 0, + lineHeight: 20, + fontSize: 14, + marginTop: 2, + }, + + containerStyle: { + backgroundColor: colors.background, + }, + divider: { + borderBottomWidth: bottomDivider ? StyleSheet.hairlineWidth : 0, + borderBottomColor: colors.formBorder, + }, + }); + + const memoizedSwitchProps = useMemo(() => { + return switchProps ? { ...switchProps } : undefined; + }, [switchProps]); + const resolvedSwitchTestID = switchTestID ?? memoizedSwitchProps?.testID; + const enableFeedback = !noFeedback && !!onPress && !disabled; + + const renderContent = () => ( + + {leftAvatar && ( + + {leftAvatar} + + + )} + + + {title} + + {subtitle ? ( + + {subtitle} + + ) : null} + + + {rightTitle || rightSubtitle ? ( + + {rightTitle ? ( + + {rightTitle} + + ) : null} + {rightSubtitle != null && rightSubtitle !== '' ? ( + + + {rightSubtitle} + + + ) : null} + + ) : null} + {chevron ? ( + + ) : null} + {switchProps ? ( + + ) : null} + {checkmark ? ( + + + + ) : null} + + ); + + if (!onPress) { + return ( + + {renderContent()} + + ); + } + + return ( + [ + stylesHook.containerStyle, + stylesHook.divider, + containerStyle, + disabled && styles.disabled, + enableFeedback && pressed && styles.pressed, + ]} + > + {renderContent()} + + ); + }, +); + +export default ListItem; + +const styles = StyleSheet.create({ + margin16: { + marginLeft: 16, + }, + width16: { width: 16 }, + contentRow: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 12, + paddingHorizontal: 16, + }, + content: { + flex: 1, + justifyContent: 'center', + }, + leftAvatarContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + rightColumn: { + marginStart: 8, + minWidth: 0, + alignItems: 'flex-end', + }, + rightMemoWrapper: { + flexShrink: 1, + minWidth: 0, + }, + checkmarkContainer: { + marginLeft: 8, + }, + disabled: { + opacity: 0.6, + }, + pressed: { + opacity: 0.6, + }, +}); diff --git a/components/ManageWalletsListItem.tsx b/components/ManageWalletsListItem.tsx new file mode 100644 index 00000000000..dae7ccfe7eb --- /dev/null +++ b/components/ManageWalletsListItem.tsx @@ -0,0 +1,438 @@ +import React, { useCallback, useState, useEffect, useRef } from 'react'; +import { StyleSheet, ViewStyle, ActivityIndicator, Platform, Animated, View, Text, Pressable } from 'react-native'; +import { useLocale } from '@react-navigation/native'; +import { Swipeable } from 'react-native-gesture-handler'; +import { ExtendedTransaction, LightningTransaction, Transaction, TWallet } from '../class/wallets/types'; +import loc from '../loc'; +import { TransactionListItem } from './TransactionListItem'; +import { useTheme } from './themes'; +import { BitcoinUnit } from '../models/bitcoinUnits'; +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; +import { AddressItem } from './addresses/AddressItem'; +import { ItemType, AddressItemData } from '../models/itemTypes'; +import { LightningCustodianWallet } from '../class/wallets/lightning-custodian-wallet'; +import { LightningArkWallet } from '../class/wallets/lightning-ark-wallet'; +import { MultisigHDWallet } from '../class/wallets/multisig-hd-wallet'; +import { AbstractHDElectrumWallet } from '../class/wallets/abstract-hd-electrum-wallet'; +import { WatchOnlyWallet } from '../class/wallets/watch-only-wallet'; +import WalletListItem from './WalletListItem'; + +const getHdElectrumWallet = (wallet: TWallet): AbstractHDElectrumWallet | undefined => { + const w: unknown = wallet; + if (w instanceof AbstractHDElectrumWallet) return w; + if (w instanceof WatchOnlyWallet) { + const inner: unknown = w._hdWalletInstance; + if (inner instanceof AbstractHDElectrumWallet) return inner; + } + return undefined; +}; + +const getWalletIconImage = (walletType: string, direction: string) => { + switch (walletType) { + case LightningCustodianWallet.type: + case LightningArkWallet.type: + return direction === 'rtl' ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png'); + case MultisigHDWallet.type: + return direction === 'rtl' ? require('../img/vault-shape-rtl.png') : require('../img/vault-shape.png'); + default: + return direction === 'rtl' ? require('../img/btc-shape-rtl.png') : require('../img/btc-shape.png'); + } +}; + +interface WalletItem { + type: ItemType.WalletSection; + data: TWallet; +} + +interface TransactionItem { + type: ItemType.TransactionSection; + data: ExtendedTransaction & LightningTransaction; +} + +interface AddressItem { + type: ItemType.AddressSection; + data: AddressItemData; +} + +type Item = WalletItem | TransactionItem | AddressItem; + +interface ManageWalletsListItemProps { + item: Item; + isDraggingDisabled: boolean; + handleToggleHideBalance: (wallet: TWallet) => void; + state: { wallets: TWallet[]; searchQuery: string; isSearchFocused?: boolean }; + navigateToWallet: (wallet: TWallet) => void; + navigateToAddress: (address: string, walletID: string) => void; + renderHighlightedText: (text: string, query: string) => React.ReactElement; + isActive: boolean; + globalDragActive: boolean; + drag?: () => void; + isPlaceHolder?: boolean; + onPressIn?: () => void; + onPressOut?: () => void; + style?: ViewStyle; +} + +const ManageWalletsListItem: React.FC = ({ + item, + isDraggingDisabled, + drag, + state, + isPlaceHolder = false, + navigateToWallet, + navigateToAddress, + renderHighlightedText, + onPressIn, + onPressOut, + handleToggleHideBalance, + isActive, + globalDragActive, + style, +}) => { + const { colors, dark } = useTheme(); + const { direction } = useLocale(); + const [isLoading, setIsLoading] = useState(false); + + const prevIsActive = useRef(isActive); + const swipeableRef = useRef(null); + const swipeInProgressRef = useRef(false); + + useEffect(() => { + if (isActive !== prevIsActive.current) { + triggerHapticFeedback(HapticFeedbackTypes.ImpactMedium); + } + prevIsActive.current = isActive; + }, [isActive]); + + const onPress = useCallback(() => { + if (swipeInProgressRef.current) return; + if (item.type === ItemType.WalletSection) { + setIsLoading(true); + navigateToWallet(item.data); + setIsLoading(false); + } else if (item.type === ItemType.AddressSection) { + navigateToAddress(item.data.address, item.data.walletID); + } + }, [item, navigateToWallet, navigateToAddress]); + + const startDrag = useCallback(() => { + if (swipeInProgressRef.current) { + swipeableRef.current?.close?.(); + return; + } + triggerHapticFeedback(HapticFeedbackTypes.ImpactMedium); + if (drag) { + drag(); + } + }, [drag]); + + if (isLoading) { + return ; + } + + if (item.type === ItemType.WalletSection) { + const wallet = item.data; + const titleColor = dark ? colors.foregroundColor : colors.darkGray; + const iconImage = getWalletIconImage(wallet.type, direction); + + const canSwipe = !isActive && !globalDragActive; + const isHidden = !!wallet.hideBalance; + + const onToggle = () => { + handleToggleHideBalance(wallet); + swipeableRef.current?.close?.(); + }; + + const renderRightActions = () => ( + + [ + styles.rightAction, + { backgroundColor: colors.buttonBackgroundColor }, + pressed && styles.rightActionPressed, + ]} + onPress={onToggle} + accessibilityRole="button" + > + + {isHidden ? loc.wallets.swipe_balance_show : loc.wallets.swipe_balance_hide} + + + + ); + + const content = ( + + ); + + if (!canSwipe) return content; + + return ( + { + swipeableRef.current = r; + }} + onSwipeableWillOpen={() => { + swipeInProgressRef.current = true; + }} + onSwipeableWillClose={() => { + swipeInProgressRef.current = false; + }} + onSwipeableClose={() => { + swipeInProgressRef.current = false; + }} + renderRightActions={renderRightActions} + friction={2} + rightThreshold={40} + overshootRight={false} + > + {content} + + ); + } else if (item.type === ItemType.TransactionSection && item.data) { + try { + const w = state.wallets.find(wallet => wallet.getTransactions()?.some((tx: Transaction) => tx.hash === item.data.hash)); + + const walletID = w ? w.getID() : ''; + + const transactionStyle = { + borderLeftWidth: 2, + borderLeftColor: colors.brandingColor, + backgroundColor: colors.background, + background: colors.background, + }; + + return ( + + ); + } catch (e) { + console.warn('Error rendering transaction item:', e); + return null; + } + } else if (item.type === ItemType.AddressSection) { + const wallet = state.wallets.find(w => w.getID() === item.data.walletID); + if (!wallet) return null; + + const addressItemProps = { + item: { + key: item.data.address, + index: item.data.index, + address: item.data.address, + isInternal: item.data.isInternal, + balance: 0, + transactions: 0, + }, + balanceUnit: wallet.getPreferredBalanceUnit() || BitcoinUnit.BTC, + walletID: item.data.walletID, + allowSignVerifyMessage: wallet.allowSignVerifyMessage(), + onPress: () => navigateToAddress(item.data.address, item.data.walletID), + searchQuery: state.searchQuery, + renderHighlightedText, + }; + + return ; + } + + return null; +}; + +// WalletGroupItem component to handle displaying wallet and related search results +interface WalletGroupProps { + wallet: TWallet; + transactions: TransactionItem[]; + addresses: AddressItem[]; + state: { wallets: TWallet[]; searchQuery: string }; + navigateToWallet: (wallet: TWallet) => void; + navigateToAddress: (address: string, walletID: string) => void; + renderHighlightedText: (text: string, query: string) => React.ReactElement; +} + +const WalletGroupComponent: React.FC = ({ + wallet, + transactions, + addresses, + state, + navigateToWallet, + navigateToAddress, + renderHighlightedText, +}) => { + const { colors, dark } = useTheme(); + const { direction } = useLocale(); + const [expanded] = useState(true); + const fadeAnim = useRef(new Animated.Value(0)).current; + const hdElectrum = getHdElectrumWallet(wallet); + + useEffect(() => { + Animated.timing(fadeAnim, { + toValue: 1, + duration: 300, + useNativeDriver: true, + }).start(); + }, [fadeAnim]); + + const cardRadius = 16; + const cardShadowStyle: ViewStyle = { + marginHorizontal: 16, + marginVertical: 10, + borderRadius: cardRadius, + ...Platform.select({ + ios: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.1, + shadowRadius: 4, + }, + android: { + elevation: 2, + }, + }), + }; + + const cardBorderColor = dark ? colors.lightBorder : colors.borderTopColor; + + const cardInnerStyle: ViewStyle = { + borderRadius: cardRadius, + overflow: 'hidden' as const, + backgroundColor: colors.elevated, + borderWidth: StyleSheet.hairlineWidth, + borderColor: cardBorderColor, + }; + + const childItemsContainerStyle = { + backgroundColor: colors.elevated, + }; + + const childItemStyle = (): ViewStyle => ({ + backgroundColor: colors.elevated, + }); + + const walletHeaderBackgroundColor = dark ? colors.elevated : '#F9F9F9'; + + const dividerStyle = [styles.itemDivider, { backgroundColor: cardBorderColor }]; + + const onWalletPress = useCallback(() => { + navigateToWallet(wallet); + }, [navigateToWallet, wallet]); + + const titleColor = dark ? colors.foregroundColor : colors.darkGray; + const iconImage = getWalletIconImage(wallet.type, direction); + + const renderAddress = (address: AddressItem, index: number) => { + const computedBalance = hdElectrum?.getBalanceForExternalIndex(address.data.index) ?? 0; + const computedTransactions = hdElectrum?.getTransactionCountForExternalIndex(address.data.index) ?? 0; + + return ( + + + navigateToAddress(address.data.address, address.data.walletID)} + searchQuery={state.searchQuery} + renderHighlightedText={renderHighlightedText} + /> + + {index < addresses.length - 1 && } + + ); + }; + + return ( + + + + + + {expanded && ( + + {transactions.length > 0 && ( + <> + {transactions.map((transaction, index) => ( + + + + + {index < transactions.length - 1 && } + + ))} + + )} + + {addresses.length > 0 && <>{addresses.map(renderAddress)}} + + )} + + + + ); +}; + +const styles = StyleSheet.create({ + itemDivider: { + height: 1, + width: '100%', + }, + rightActionsContainer: { + justifyContent: 'center', + alignItems: 'flex-end', + }, + rightAction: { + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 18, + height: '100%', + }, + rightActionPressed: { + opacity: 0.85, + }, + rightActionText: { + fontSize: 15, + fontWeight: '600', + }, +}); + +export { WalletGroupComponent }; +export default ManageWalletsListItem; diff --git a/components/MultipleStepsListItem.js b/components/MultipleStepsListItem.js deleted file mode 100644 index 9fde9226eae..00000000000 --- a/components/MultipleStepsListItem.js +++ /dev/null @@ -1,243 +0,0 @@ -import React from 'react'; -import { useTheme } from '@react-navigation/native'; -import { View, StyleSheet, Text, TouchableOpacity, ActivityIndicator } from 'react-native'; -import PropTypes from 'prop-types'; -import { Icon } from 'react-native-elements'; -export const MultipleStepsListItemDashType = Object.freeze({ none: 0, top: 1, bottom: 2, topAndBottom: 3 }); -export const MultipleStepsListItemButtohType = Object.freeze({ partial: 0, full: 1 }); - -const MultipleStepsListItem = props => { - const { colors } = useTheme(); - const { - showActivityIndicator = false, - dashes = MultipleStepsListItemDashType.none, - circledText = '', - leftText = '', - checked = false, - } = props; - const stylesHook = StyleSheet.create({ - provideKeyButton: { - backgroundColor: colors.buttonDisabledBackgroundColor, - }, - provideKeyButtonText: { - color: colors.buttonTextColor, - }, - vaultKeyCircle: { - backgroundColor: colors.buttonDisabledBackgroundColor, - }, - vaultKeyText: { - color: colors.alternativeTextColor, - }, - vaultKeyCircleSuccess: { - backgroundColor: colors.msSuccessBG, - }, - rowPartialLeftText: { - color: colors.alternativeTextColor, - }, - }); - - const renderDashes = () => { - switch (dashes) { - case MultipleStepsListItemDashType.topAndBottom: - return { - width: 1, - borderStyle: 'dashed', - borderWidth: 0.8, - borderColor: '#c4c4c4', - top: 0, - bottom: 0, - marginLeft: 20, - position: 'absolute', - }; - case MultipleStepsListItemDashType.bottom: - return { - width: 1, - borderStyle: 'dashed', - borderWidth: 0.8, - borderColor: '#c4c4c4', - top: '50%', - bottom: 0, - marginLeft: 20, - position: 'absolute', - }; - case MultipleStepsListItemDashType.top: - return { - width: 1, - borderStyle: 'dashed', - borderWidth: 0.8, - borderColor: '#c4c4c4', - top: 0, - bottom: '50%', - marginLeft: 20, - position: 'absolute', - }; - default: - return {}; - } - }; - const buttonOpacity = { opacity: props.button?.disabled ? 0.5 : 1.0 }; - const rightButtonOpacity = { opacity: props.rightButton?.disabled ? 0.5 : 1.0 }; - return ( - - - - - {checked ? ( - - - - ) : circledText.length > 0 ? ( - - - {circledText} - - - ) : null} - {!showActivityIndicator && leftText.length > 0 && ( - - {leftText} - - )} - {showActivityIndicator && } - - {!showActivityIndicator && props.button && ( - <> - {props.button.buttonType === undefined || - (props.button.buttonType === MultipleStepsListItemButtohType.full && ( - - {props.button.text} - - ))} - {props.button.buttonType === MultipleStepsListItemButtohType.partial && ( - - - {props.button.leftText} - - - - {props.button.text} - - - - )} - - )} - {!showActivityIndicator && props.rightButton && checked && ( - - - {props.rightButton.text} - - - )} - - - ); -}; - -MultipleStepsListItem.propTypes = { - circledText: PropTypes.string, - checked: PropTypes.bool, - leftText: PropTypes.string, - showActivityIndicator: PropTypes.bool, - dashes: PropTypes.number, - button: PropTypes.shape({ - text: PropTypes.string, - onPress: PropTypes.func, - disabled: PropTypes.bool, - buttonType: PropTypes.number, - leftText: PropTypes.string, - }), - rightButton: PropTypes.shape({ - text: PropTypes.string, - onPress: PropTypes.func, - disabled: PropTypes.bool, - }), -}; - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - marginBottom: 16, - flex: 1, - justifyContent: 'space-between', - }, - buttonPartialContainer: { - borderRadius: 8, - borderColor: '#EEF0F4', - borderWidth: 1, - height: 48, - flex: 1, - justifyContent: 'space-between', - flexDirection: 'row', - alignItems: 'center', - paddingLeft: 16, - paddingRight: 8, - paddingVertical: 5, - marginLeft: 40, - }, - rowPartialRightButton: { - height: 36, - borderRadius: 8, - alignSelf: 'flex-end', - minWidth: 64, - justifyContent: 'center', - }, - itemKeyUnprovidedWrapper: { flexDirection: 'row' }, - vaultKeyCircle: { - width: 42, - height: 42, - borderRadius: 25, - justifyContent: 'center', - alignItems: 'center', - alignSelf: 'center', - }, - vaultKeyText: { fontSize: 18, fontWeight: 'bold' }, - vaultKeyTextWrapper: { justifyContent: 'center', alignContent: 'flex-start', paddingLeft: 16 }, - provideKeyButton: { - marginLeft: 40, - height: 48, - borderRadius: 8, - flex: 1, - justifyContent: 'center', - paddingHorizontal: 16, - }, - rightButton: { - borderRadius: 8, - textAlign: 'center', - }, - rightButtonContainer: { - alignContent: 'center', - justifyContent: 'center', - }, - activityIndicator: { - marginLeft: 40, - }, - provideKeyButtonText: { fontWeight: '600', fontSize: 15 }, - vaultKeyCircleSuccess: { - width: 42, - height: 42, - borderRadius: 25, - justifyContent: 'center', - alignItems: 'center', - }, - rowPartialLeftText: { - textAlign: 'center', - }, -}); - -export default MultipleStepsListItem; diff --git a/components/MultipleStepsListItem.tsx b/components/MultipleStepsListItem.tsx new file mode 100644 index 00000000000..e99d8581d88 --- /dev/null +++ b/components/MultipleStepsListItem.tsx @@ -0,0 +1,322 @@ +import React, { useRef } from 'react'; +import { + ActivityIndicator, + findNodeHandle, + GestureResponderEvent, + Platform, + StyleProp, + StyleSheet, + Text, + Pressable, + View, + ViewStyle, +} from 'react-native'; +import Icon from './Icon'; +import ActionSheet from '../screen/ActionSheet'; +import { useTheme } from './themes'; +import { ActionSheetOptions } from '../screen/ActionSheet.common'; + +export enum MultipleStepsListItemDashType { + None = 0, + Top = 1, + Bottom = 2, + TopAndBottom = 3, +} + +export enum MultipleStepsListItemButtonType { + Partial = 0, + Full = 1, +} + +interface MultipleStepsListItemProps { + circledText?: string; + checked?: boolean; + leftText?: string; + showActivityIndicator?: boolean; + isActionSheet?: boolean; + actionSheetOptions?: ActionSheetOptions; + dashes?: MultipleStepsListItemDashType; + button?: { + text?: string; + onPress?: (e: GestureResponderEvent | number) => void; + disabled?: boolean; + buttonType?: MultipleStepsListItemButtonType; + leftText?: string; + showActivityIndicator?: boolean; + testID?: string; + }; + rightButton?: { + text?: string; + onPress?: () => void; + disabled?: boolean; + showActivityIndicator?: boolean; + }; +} + +const MultipleStepsListItem = (props: MultipleStepsListItemProps) => { + const { colors } = useTheme(); + const { + showActivityIndicator = false, + dashes = MultipleStepsListItemDashType.None, + circledText = '', + leftText = '', + checked = false, + isActionSheet = false, + actionSheetOptions = null, // Default to null or appropriate default + } = props; + const stylesHook = StyleSheet.create({ + provideKeyButton: { + backgroundColor: colors.buttonDisabledBackgroundColor, + }, + provideKeyButtonText: { + color: colors.buttonTextColor, + }, + vaultKeyCircle: { + backgroundColor: colors.buttonDisabledBackgroundColor, + }, + vaultKeyText: { + color: colors.alternativeTextColor, + }, + vaultKeyCircleSuccess: { + backgroundColor: colors.msSuccessBG, + }, + rowPartialLeftText: { + color: colors.alternativeTextColor, + }, + }); + const selfRef = useRef(null); // Create a ref for the component itself + + const handleOnPressForActionSheet = () => { + if (isActionSheet && actionSheetOptions) { + // Clone options to modify them + let modifiedOptions = { ...actionSheetOptions }; + + // Use 'selfRef' if the component uses its own ref, or 'ref' if it's using forwarded ref + const anchor = findNodeHandle(selfRef.current); + + if (anchor) { + // Attach the anchor only if it exists + modifiedOptions = { ...modifiedOptions, anchor }; + } + + ActionSheet.showActionSheetWithOptions(modifiedOptions, buttonIndex => { + // Call the original onPress function, if provided, and not cancelled + if (buttonIndex !== -1 && props.button?.onPress) { + props.button.onPress(buttonIndex); + } + }); + } + }; + + const renderDashes = (): StyleProp => { + switch (dashes) { + case MultipleStepsListItemDashType.TopAndBottom: + return { + width: 1, + borderStyle: 'dashed', + borderWidth: 0.8, + borderColor: '#c4c4c4', + top: 0, + bottom: 0, + marginLeft: 20, + position: 'absolute', + }; + case MultipleStepsListItemDashType.Bottom: + return { + width: 1, + borderStyle: 'dashed', + borderWidth: 0.8, + borderColor: '#c4c4c4', + top: '50%', + bottom: 0, + marginLeft: 20, + position: 'absolute', + }; + case MultipleStepsListItemDashType.Top: + return { + width: 1, + borderStyle: 'dashed', + borderWidth: 0.8, + borderColor: '#c4c4c4', + top: 0, + bottom: '50%', + marginLeft: 20, + position: 'absolute', + }; + default: + return {}; + } + }; + const buttonOpacity = { opacity: props.button?.disabled ? 0.5 : 1.0 }; + const rightButtonOpacity = { opacity: props.rightButton?.disabled ? 0.5 : 1.0 }; + const onPress = isActionSheet ? handleOnPressForActionSheet : props.button?.onPress; + return ( + + + + + {checked ? ( + + + + ) : circledText.length > 0 ? ( + + + {circledText} + + + ) : null} + {!showActivityIndicator && leftText.length > 0 && ( + + {leftText} + + )} + {showActivityIndicator && } + + {!showActivityIndicator && props.button && ( + <> + {props.button.buttonType === undefined || + (props.button.buttonType === MultipleStepsListItemButtonType.Full && ( + [ + Platform.OS === 'ios' && pressed ? styles.pressed : null, + styles.provideKeyButton, + stylesHook.provideKeyButton, + buttonOpacity, + ]} + onPress={onPress} + > + {props.button.text} + + ))} + {props.button.buttonType === MultipleStepsListItemButtonType.Partial && ( + + + {props.button.leftText} + + [ + Platform.OS === 'ios' && pressed ? styles.pressed : null, + styles.rowPartialRightButton, + stylesHook.provideKeyButton, + rightButtonOpacity, + ]} + onPress={onPress} + > + {props.button.showActivityIndicator ? ( + + ) : ( + + {props.button.text} + + )} + + + )} + + )} + {!showActivityIndicator && props.rightButton && checked && ( + + [pressed && styles.pressed, styles.rightButton]} + onPress={props.rightButton.onPress} + > + {props.rightButton.showActivityIndicator ? ( + + ) : ( + {props.rightButton.text} + )} + + + )} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + marginBottom: 16, + flex: 1, + justifyContent: 'space-between', + }, + buttonPartialContainer: { + borderRadius: 8, + borderColor: '#EEF0F4', + borderWidth: 1, + height: 48, + flex: 1, + justifyContent: 'space-between', + flexDirection: 'row', + alignItems: 'center', + paddingLeft: 16, + paddingRight: 8, + paddingVertical: 5, + marginLeft: 40, + }, + rowPartialRightButton: { + height: 36, + borderRadius: 8, + alignSelf: 'flex-end', + minWidth: 64, + justifyContent: 'center', + }, + itemKeyUnprovidedWrapper: { flexDirection: 'row' }, + vaultKeyCircle: { + width: 42, + height: 42, + borderRadius: 25, + justifyContent: 'center', + alignItems: 'center', + alignSelf: 'center', + }, + vaultKeyText: { fontSize: 18, fontWeight: 'bold' }, + vaultKeyTextWrapper: { justifyContent: 'center', alignContent: 'flex-start', paddingLeft: 16 }, + provideKeyButton: { + marginLeft: 40, + height: 48, + borderRadius: 8, + flex: 1, + justifyContent: 'center', + paddingHorizontal: 16, + }, + rightButton: { + borderRadius: 8, + textAlign: 'center', + }, + rightButtonContainer: { + alignContent: 'center', + justifyContent: 'center', + }, + activityIndicator: { + marginLeft: 40, + }, + provideKeyButtonText: { fontWeight: '600', fontSize: 15 }, + vaultKeyCircleSuccess: { + width: 42, + height: 42, + borderRadius: 25, + justifyContent: 'center', + alignItems: 'center', + }, + rowPartialLeftText: { + textAlign: 'center', + }, + pressed: { + opacity: 0.6, + }, +}); + +export default MultipleStepsListItem; diff --git a/components/PasswordInput.tsx b/components/PasswordInput.tsx new file mode 100644 index 00000000000..d2b3a6c25d9 --- /dev/null +++ b/components/PasswordInput.tsx @@ -0,0 +1,217 @@ +import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react'; +import { Animated, Easing, StyleSheet, TextInput, View } from 'react-native'; +import { useTheme } from './themes'; +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; +import loc from '../loc'; + +export interface PasswordInputHandle { + focus: () => void; + blur: () => void; + clear: () => void; + showError: () => void; + showSuccess: () => void; + reset: () => void; + getValue: () => string; +} + +interface PasswordInputProps { + onSubmit: (password: string) => void; + placeholder?: string; + disabled?: boolean; + onChangeText?: (text: string) => void; +} + +export const PasswordInput = forwardRef( + ({ onSubmit, placeholder = loc._.enter_password, disabled = false, onChangeText }, ref) => { + const [password, setPassword] = useState(''); + const [isSuccess, setIsSuccess] = useState(false); + const inputRef = useRef(null); + const shakeAnimation = useRef(new Animated.Value(0)).current; + const checkmarkScale = useRef(new Animated.Value(0)).current; + const checkmarkOpacity = useRef(new Animated.Value(0)).current; + const { colors } = useTheme(); + + useImperativeHandle(ref, () => ({ + focus: () => { + inputRef.current?.focus(); + }, + blur: () => inputRef.current?.blur(), + clear: () => setPassword(''), + getValue: () => password, + showError: () => { + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); + setIsSuccess(false); + + // macOS-style shake animation - quick and snappy + Animated.sequence([ + Animated.timing(shakeAnimation, { + toValue: 10, + duration: 50, + easing: Easing.out(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(shakeAnimation, { + toValue: -10, + duration: 50, + easing: Easing.out(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(shakeAnimation, { + toValue: 8, + duration: 45, + easing: Easing.out(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(shakeAnimation, { + toValue: 0, + duration: 45, + easing: Easing.out(Easing.quad), + useNativeDriver: true, + }), + ]).start(() => { + // Clear password after shake + setPassword(''); + }); + }, + showSuccess: () => { + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); + setIsSuccess(true); + + // Dismiss keyboard on success + inputRef.current?.blur(); + + // Quick pop-in animation for checkmark + checkmarkScale.setValue(0); + checkmarkOpacity.setValue(0); + + Animated.parallel([ + Animated.spring(checkmarkScale, { + toValue: 1, + tension: 100, + friction: 5, + useNativeDriver: true, + }), + Animated.timing(checkmarkOpacity, { + toValue: 1, + duration: 200, + useNativeDriver: true, + }), + ]).start(); + }, + reset: () => { + setPassword(''); + setIsSuccess(false); + shakeAnimation.setValue(0); + checkmarkScale.setValue(0); + checkmarkOpacity.setValue(0); + }, + })); + + const handleSubmit = () => { + if (password.trim() && !isSuccess) { + onSubmit(password); + } + }; + + const stylesHook = StyleSheet.create({ + container: { + borderColor: isSuccess ? colors.successColor : colors.formBorder, + backgroundColor: colors.inputBackgroundColor, + }, + input: { + color: colors.foregroundColor, + }, + checkmark: { + color: colors.successColor, + }, + }); + + return ( + + { + setPassword(text); + onChangeText?.(text); + }} + clearButtonMode={isSuccess ? 'never' : 'while-editing'} + placeholder={placeholder} + placeholderTextColor={colors.alternativeTextColor} + secureTextEntry + autoCapitalize="none" + autoCorrect={false} + editable={!isSuccess} + onSubmitEditing={handleSubmit} + returnKeyType="done" + enablesReturnKeyAutomatically={true} + /> + + {isSuccess && ( + + + + + + )} + + ); + }, +); + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + borderRadius: 8, + borderWidth: 2, + paddingHorizontal: 16, + minHeight: 54, + width: '100%', + }, + input: { + flex: 1, + fontSize: 16, + paddingVertical: 12, + }, + checkmarkContainer: { + marginLeft: 12, + justifyContent: 'center', + alignItems: 'center', + }, + checkmarkCircle: { + width: 24, + height: 24, + borderRadius: 12, + justifyContent: 'center', + alignItems: 'center', + }, + checkmark: { + width: 8, + height: 14, + borderBottomWidth: 3, + borderRightWidth: 3, + transform: [{ rotate: '45deg' }, { translateY: -2 }], + }, +}); + +PasswordInput.displayName = 'PasswordInput'; diff --git a/components/QRCode.tsx b/components/QRCode.tsx new file mode 100644 index 00000000000..2014b1ebedf --- /dev/null +++ b/components/QRCode.tsx @@ -0,0 +1,337 @@ +import Clipboard from '@react-native-clipboard/clipboard'; +import { encodeQR } from 'qr'; +import React, { useCallback, useMemo, useRef } from 'react'; +import { Platform, StyleSheet, View, ViewStyle } from 'react-native'; +import Share from 'react-native-share'; +import Svg, { Defs, Image as SvgImage, LinearGradient, Path, Rect, Stop } from 'react-native-svg'; + +import loc from '../loc'; +import { ActionIcons } from '../typings/ActionIcons'; +import ToolTipMenu from './TooltipMenu'; +import { Action } from './types'; + +type ErrorCorrectionLevel = 'H' | 'Q' | 'M' | 'L'; + +interface QRCodeProps { + value: string; + size: number; + isLogoRendered?: boolean; + isMenuAvailable?: boolean; + logoSize?: number; + ecl?: ErrorCorrectionLevel; + onError?: (error?: unknown) => void; +} + +const GRADIENT_ID = 'qrgrad'; +const GRADIENT_STOP_1 = '#0c2550'; +const GRADIENT_STOP_2 = '#1e3a8a'; +const BACKGROUND = '#FFFFFF'; +const LOGO_BACKGROUND = '#FFFFFF'; + +const eclMap: Record = { + L: 'low', + M: 'medium', + Q: 'quartile', + H: 'high', +}; + +const actionIcons: { [key: string]: ActionIcons } = { + Copy: { iconValue: 'doc.on.doc' }, + Share: { iconValue: 'square.and.arrow.up' }, +}; + +const actionKeys = { Copy: 'copy', Share: 'share' }; + +// Copy-as-image is iOS/macOS-only — @react-native-clipboard/clipboard's setImage +// is not implemented on Android. Android users still get text-copy via the +// dedicated CopyTextToClipboard control rendered next to the QR on every screen. +const menuActions: Action[] = + Platform.OS === 'ios' || Platform.OS === 'macos' + ? [ + { id: actionKeys.Copy, text: loc.transactions.details_copy, icon: actionIcons.Copy }, + { id: actionKeys.Share, text: loc.receive.details_share, icon: actionIcons.Share }, + ] + : [{ id: actionKeys.Share, text: loc.receive.details_share, icon: actionIcons.Share }]; + +const roundUpToOdd = (n: number): number => { + const rounded = Math.ceil(n); + return rounded % 2 === 0 ? rounded + 1 : rounded; +}; + +const MATRIX_CACHE_MAX = 128; +const matrixCache = new Map(); + +const getCachedMatrix = (value: string, ecl: ErrorCorrectionLevel): boolean[][] => { + const key = `${ecl}|${value}`; + const hit = matrixCache.get(key); + if (hit) { + matrixCache.delete(key); + matrixCache.set(key, hit); + return hit; + } + const m = encodeQR(value, 'raw', { ecc: eclMap[ecl], border: 0 }); + matrixCache.set(key, m); + if (matrixCache.size > MATRIX_CACHE_MAX) { + const first = matrixCache.keys().next().value; + if (first !== undefined) matrixCache.delete(first); + } + return m; +}; + +type RenderPlan = { + N: number; + cell: number; + dataPath: string; + finderOrigins: Array<[number, number]>; + logoCells: number; + logoStart: number; +}; + +const PLAN_CACHE_MAX = 64; +const planCache = new Map(); + +const getCachedPlan = (value: string, ecl: ErrorCorrectionLevel, size: number, isLogoRendered: boolean, logoSize: number): RenderPlan => { + const key = `${ecl}|${size}|${isLogoRendered ? 'L' + logoSize : 'NL'}|${value}`; + const hit = planCache.get(key); + if (hit) { + planCache.delete(key); + planCache.set(key, hit); + return hit; + } + + const matrix = getCachedMatrix(value, ecl); + const N = matrix.length; + const cell = size / (N + 2); + + let logoCells = 0; + let logoStart = 0; + if (isLogoRendered) { + const desired = (logoSize + cell) / cell; + logoCells = Math.min(roundUpToOdd(desired), N); + logoStart = Math.floor((N - logoCells) / 2); + } + const logoEnd = logoStart + logoCells; + + const finderOrigins: Array<[number, number]> = + N >= 7 + ? [ + [0, 0], + [0, N - 7], + [N - 7, 0], + ] + : []; + const isInsideFinder = (r: number, c: number): boolean => + finderOrigins.some(([fr, fc]) => r >= fr && r < fr + 7 && c >= fc && c < fc + 7); + + let dataPath = ''; + for (let r = 0; r < N; r++) { + for (let c = 0; c < N; c++) { + if (!matrix[r][c]) continue; + if (isLogoRendered && r >= logoStart && r < logoEnd && c >= logoStart && c < logoEnd) continue; + if (isInsideFinder(r, c)) continue; + dataPath += `M${(c + 1) * cell} ${(r + 1) * cell}h${cell}v${cell}h-${cell}z`; + } + } + + const plan: RenderPlan = { N, cell, dataPath, finderOrigins, logoCells, logoStart }; + planCache.set(key, plan); + if (planCache.size > PLAN_CACHE_MAX) { + const first = planCache.keys().next().value; + if (first !== undefined) planCache.delete(first); + } + return plan; +}; + +const QRCode: React.FC = ({ + value = '', + size, + isLogoRendered = true, + isMenuAvailable = true, + logoSize = 90, + ecl = 'H', + onError, +}) => { + const svgRef = useRef(null); + + const plan = useMemo(() => { + try { + return getCachedPlan(value, ecl, size, isLogoRendered, logoSize); + } catch (e) { + onError?.(e); + return null; + } + }, [value, ecl, size, isLogoRendered, logoSize, onError]); + + const handleCopy = useCallback(() => { + if (!svgRef.current) return; + svgRef.current.toDataURL((data: string) => { + if (data) Clipboard.setImage(data); + }); + }, []); + + const handleShare = useCallback(() => { + if (!svgRef.current) return; + svgRef.current.toDataURL((data: string) => { + if (!data) { + console.warn('QRCode: toDataURL returned empty data'); + return; + } + const cleaned = data.replace(/(\r\n|\n|\r)/gm, ''); + Share.open({ + url: `data:image/png;base64,${cleaned}`, + type: 'image/png', + filename: 'qrcode', + failOnCancel: false, + // Workaround for Android FileProvider crash with data: URLs since react-native-share@12.1.1. + // Accepted at runtime but missing from ShareOptions types as of 12.2.6. + // See https://github.com/react-native-share/react-native-share/issues/1683 + // @ts-expect-error - useInternalStorage missing from ShareOptions type + useInternalStorage: true, + }).catch((error: Error) => console.warn('QRCode share failed:', error)); + }); + }, []); + + const onPressMenuItem = useCallback( + (id: string) => { + if (id === actionKeys.Copy) handleCopy(); + else if (id === actionKeys.Share) handleShare(); + }, + [handleCopy, handleShare], + ); + + const stylesHook = StyleSheet.create({ + placeholder: { width: size, height: size, backgroundColor: BACKGROUND }, + }); + + const qrButtonStyle: ViewStyle = { + width: size, + height: size, + justifyContent: 'center', + alignItems: 'center', + }; + + const renderQR = useMemo(() => { + if (!plan) return null; + const { cell, dataPath, finderOrigins, logoCells, logoStart } = plan; + 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( + , + , + , + ); + }); + + const backdropX = (logoStart + 1) * cell; + const backdropY = (logoStart + 1) * cell; + const backdropSize = logoCells * cell; + const logoCenter = size / 2; + + return ( + + + + + + + + + {dataPath ? : null} + {finderShapes} + {isLogoRendered && logoCells > 0 && ( + <> + + + + )} + + ); + }, [plan, size, isLogoRendered, logoSize]); + + const content = renderQR ?? ; + + return ( + + {isMenuAvailable ? ( + + {content} + + ) : ( + content + )} + + ); +}; + +export default QRCode; + +const styles = StyleSheet.create({ + container: { alignItems: 'center', justifyContent: 'center' }, +}); diff --git a/components/QRCodeComponent.tsx b/components/QRCodeComponent.tsx deleted file mode 100644 index 4af4d21fbc2..00000000000 --- a/components/QRCodeComponent.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import React, { useRef } from 'react'; -import { View, StyleSheet, Platform } from 'react-native'; -import QRCode from 'react-native-qrcode-svg'; -import ToolTipMenu from './TooltipMenu'; -import Share from 'react-native-share'; -import loc from '../loc'; -import Clipboard from '@react-native-clipboard/clipboard'; -import { useTheme } from '@react-navigation/native'; - -interface QRCodeComponentProps { - value: string; - isLogoRendered?: boolean; - isMenuAvailable?: boolean; - logoSize?: number; - size?: number; - ecl?: 'H' | 'Q' | 'M' | 'L'; - onError?: () => void; -} - -interface ActionIcons { - iconType: 'SYSTEM'; - iconValue: string; -} - -interface ActionType { - Share: 'share'; - Copy: 'copy'; -} - -interface Action { - id: string; - text: string; - icon: ActionIcons; -} - -const actionKeys: ActionType = { - Share: 'share', - Copy: 'copy', -}; - -interface ActionIcons { - iconType: 'SYSTEM'; - iconValue: string; -} - -const actionIcons: { [key: string]: ActionIcons } = { - Share: { - iconType: 'SYSTEM', - iconValue: 'square.and.arrow.up', - }, - Copy: { - iconType: 'SYSTEM', - iconValue: 'doc.on.doc', - }, -}; - -const QRCodeComponent: React.FC = ({ - value = '', - isLogoRendered = true, - isMenuAvailable = true, - logoSize = 90, - size = 300, - ecl = 'H', - onError = () => {}, -}) => { - const qrCode = useRef(); - const { colors } = useTheme(); - - const handleShareQRCode = () => { - qrCode.current.toDataURL((data: string) => { - data = data.replace(/(\r\n|\n|\r)/gm, ''); - const shareImageBase64 = { - url: `data:image/png;base64,${data}`, - }; - Share.open(shareImageBase64).catch((error: any) => console.log(error)); - }); - }; - - const onPressMenuItem = (id: string) => { - if (id === actionKeys.Share) { - handleShareQRCode(); - } else if (id === actionKeys.Copy) { - qrCode.current.toDataURL(Clipboard.setImage); - } - }; - - const menuActions = (): Action[] => { - const actions: Action[] = []; - if (Platform.OS === 'ios' || Platform.OS === 'macos') { - actions.push({ - id: actionKeys.Copy, - text: loc.transactions.details_copy, - icon: actionIcons.Copy, - }); - } - actions.push({ - id: actionKeys.Share, - text: loc.receive.details_share, - icon: actionIcons.Share, - }); - return actions; - }; - - const renderQRCode = ( - (qrCode.current = c)} - onError={onError} - /> - ); - - return ( - - {isMenuAvailable ? ( - - {renderQRCode} - - ) : ( - renderQRCode - )} - - ); -}; - -export default QRCodeComponent; - -const styles = StyleSheet.create({ - qrCodeContainer: { borderWidth: 6, borderRadius: 8, borderColor: '#FFFFFF' }, -}); diff --git a/components/ReplaceFeeSuggestions.tsx b/components/ReplaceFeeSuggestions.tsx new file mode 100644 index 00000000000..8ed1f86d41a --- /dev/null +++ b/components/ReplaceFeeSuggestions.tsx @@ -0,0 +1,215 @@ +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { View, Text, TextInput, TouchableOpacity, Keyboard, StyleSheet } from 'react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import BlueText from './BlueText'; +import loc, { formatStringAddTwoWhiteSpaces } from '../loc'; +import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from '../models/networkTransactionFees'; +import { useTheme } from './themes'; +import { DismissKeyboardInputAccessory, DismissKeyboardInputAccessoryViewID } from './DismissKeyboardInputAccessory'; + +interface ReplaceFeeSuggestionsProps { + onFeeSelected: (fee: number) => void; + transactionMinimum?: number; +} + +const ReplaceFeeSuggestions: React.FC = ({ onFeeSelected, transactionMinimum = 1 }) => { + const [networkFees, setNetworkFees] = useState(null); + const [selectedFeeType, setSelectedFeeType] = useState(NetworkTransactionFeeType.FAST); + const [customFeeValue, setCustomFeeValue] = useState('1'); + const customTextInput = useRef(null); + const { colors } = useTheme(); + const stylesHook = StyleSheet.create({ + activeButton: { + backgroundColor: colors.incomingBackgroundColor, + }, + buttonText: { + color: colors.successColor, + }, + timeContainer: { + backgroundColor: colors.successColor, + }, + timeText: { + color: colors.background, + }, + rateText: { + color: colors.successColor, + }, + customFeeInput: { + backgroundColor: colors.inputBackgroundColor, + borderBottomColor: colors.formBorder, + borderColor: colors.formBorder, + }, + alternativeText: { + color: colors.alternativeTextColor, + }, + }); + + const fetchNetworkFees = useCallback(async () => { + try { + const cachedNetworkTransactionFees = JSON.parse((await AsyncStorage.getItem(NetworkTransactionFee.StorageKey)) || '{}'); + + if (cachedNetworkTransactionFees && 'fastestFee' in cachedNetworkTransactionFees) { + setNetworkFees(cachedNetworkTransactionFees); + onFeeSelected(cachedNetworkTransactionFees.fastestFee); + setSelectedFeeType(NetworkTransactionFeeType.FAST); + } + } catch (_) {} + const fees = await NetworkTransactionFees.recommendedFees(); + setNetworkFees(fees); + onFeeSelected(fees.fastestFee); + setSelectedFeeType(NetworkTransactionFeeType.FAST); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + fetchNetworkFees(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleFeeSelection = (feeType: NetworkTransactionFeeType) => { + if (feeType !== NetworkTransactionFeeType.CUSTOM) { + Keyboard.dismiss(); + } + if (networkFees) { + let selectedFee: number; + switch (feeType) { + case NetworkTransactionFeeType.FAST: + selectedFee = networkFees.fastestFee; + break; + case NetworkTransactionFeeType.MEDIUM: + selectedFee = networkFees.mediumFee; + break; + case NetworkTransactionFeeType.SLOW: + selectedFee = networkFees.slowFee; + break; + case NetworkTransactionFeeType.CUSTOM: + selectedFee = Number(customFeeValue); + break; + } + onFeeSelected(selectedFee); + setSelectedFeeType(feeType); + } + }; + + const handleCustomFeeChange = (customFee: string) => { + const sanitizedFee = customFee.replace(/[^0-9]/g, ''); + setCustomFeeValue(sanitizedFee); + handleFeeSelection(NetworkTransactionFeeType.CUSTOM); + }; + + return ( + + {networkFees && + [ + { + label: loc.send.fee_fast, + time: loc.send.fee_10m, + type: NetworkTransactionFeeType.FAST, + rate: networkFees.fastestFee, + active: selectedFeeType === NetworkTransactionFeeType.FAST, + }, + { + label: formatStringAddTwoWhiteSpaces(loc.send.fee_medium), + time: loc.send.fee_3h, + type: NetworkTransactionFeeType.MEDIUM, + rate: networkFees.mediumFee, + active: selectedFeeType === NetworkTransactionFeeType.MEDIUM, + }, + { + label: loc.send.fee_slow, + time: loc.send.fee_1d, + type: NetworkTransactionFeeType.SLOW, + rate: networkFees.slowFee, + active: selectedFeeType === NetworkTransactionFeeType.SLOW, + }, + ].map(({ label, type, time, rate, active }) => ( + handleFeeSelection(type)} + style={[styles.button, active && stylesHook.activeButton]} + > + + {label} + + ~{time} + + + + {rate} sat/byte + + + ))} + customTextInput.current?.focus()} + style={[styles.button, selectedFeeType === NetworkTransactionFeeType.CUSTOM && stylesHook.activeButton]} + > + + {formatStringAddTwoWhiteSpaces(loc.send.fee_custom)} + + + handleCustomFeeChange(customFeeValue)} + placeholder={loc.send.fee_satvbyte} + placeholderTextColor="#81868e" + inputAccessoryViewID={DismissKeyboardInputAccessoryViewID} + /> + + sat/byte + + + {loc.formatString(loc.send.fee_replace_minvb, { min: transactionMinimum })} + + ); +}; + +const styles = StyleSheet.create({ + button: { + paddingHorizontal: 16, + paddingVertical: 8, + marginBottom: 10, + borderRadius: 8, + }, + buttonContent: { + justifyContent: 'space-between', + flexDirection: 'row', + alignItems: 'center', + }, + buttonText: { + fontSize: 22, + fontWeight: '600', + }, + timeContainer: { + borderRadius: 5, + paddingHorizontal: 6, + paddingVertical: 3, + }, + rateContainer: { + justifyContent: 'flex-end', + flexDirection: 'row', + alignItems: 'center', + }, + customFeeInputContainer: { + marginTop: 5, + }, + customFeeInput: { + borderBottomWidth: 0.5, + borderRadius: 4, + borderWidth: 1.0, + color: '#81868e', + flex: 1, + marginRight: 10, + minHeight: 33, + paddingRight: 5, + paddingLeft: 5, + }, +}); + +export default ReplaceFeeSuggestions; diff --git a/components/SafeArea.tsx b/components/SafeArea.tsx new file mode 100644 index 00000000000..dcecc8ddbd7 --- /dev/null +++ b/components/SafeArea.tsx @@ -0,0 +1,48 @@ +import React, { useMemo } from 'react'; +import { StyleSheet, ViewProps, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { useTheme } from './themes'; + +interface SafeAreaProps extends ViewProps { + floatingButtonHeight?: number; + orientation?: 'portrait' | 'landscape'; + ignoreTopInset?: boolean; +} + +const SafeArea = (props: SafeAreaProps) => { + const { style, floatingButtonHeight, ignoreTopInset = false, ...otherProps } = props; + const { colors } = useTheme(); + const insets = useSafeAreaInsets(); + + const padding = useMemo( + () => + props.orientation === 'portrait' + ? { + paddingTop: ignoreTopInset ? 0 : insets.top, + paddingBottom: insets.bottom, + } + : { + paddingTop: ignoreTopInset ? 0 : insets.top, + paddingBottom: insets.bottom + (floatingButtonHeight ?? 0), + paddingLeft: insets.left, + paddingRight: insets.right, + }, + [insets, props.orientation, floatingButtonHeight, ignoreTopInset], + ); + + const componentStyle = useMemo(() => { + return StyleSheet.compose( + { + flex: 1, + backgroundColor: colors.background, + ...padding, + }, + style, + ); + }, [colors.background, padding, style]); + + return ; +}; + +export default SafeArea; diff --git a/components/SafeAreaFlatList.tsx b/components/SafeAreaFlatList.tsx new file mode 100644 index 00000000000..97085597eb6 --- /dev/null +++ b/components/SafeAreaFlatList.tsx @@ -0,0 +1,47 @@ +import React, { useMemo } from 'react'; +import { StyleSheet, FlatList, FlatListProps } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { useTheme } from './themes'; + +interface SafeAreaFlatListProps extends FlatListProps { + headerHeight?: number; + floatingButtonHeight?: number; +} + +const SafeAreaFlatList = (props: SafeAreaFlatListProps) => { + const { style, contentContainerStyle, headerHeight = 0, floatingButtonHeight = 0, ...otherProps } = props; + const { colors } = useTheme(); + const insets = useSafeAreaInsets(); + + const componentStyle = useMemo(() => { + return StyleSheet.compose({ flex: 1, backgroundColor: colors.background }, style); + }, [colors.background, style]); + + const contentStyle = useMemo(() => { + // Calculate top padding + const topPadding = (() => { + // If explicit headerHeight is provided, use it + if (headerHeight > 0) { + return headerHeight; + } + // iOS safe area handling is done via ListHeaderComponent typically + // Android screens should explicitly pass headerHeight if needed + return 0; + })(); + + return StyleSheet.compose( + { + paddingBottom: insets.bottom + floatingButtonHeight, + paddingLeft: insets.left, + paddingRight: insets.right, + paddingTop: topPadding, + }, + contentContainerStyle, + ); + }, [insets, contentContainerStyle, headerHeight, floatingButtonHeight]); + + return ; +}; + +export default SafeAreaFlatList; diff --git a/components/SafeAreaScrollView.tsx b/components/SafeAreaScrollView.tsx new file mode 100644 index 00000000000..b033ed44327 --- /dev/null +++ b/components/SafeAreaScrollView.tsx @@ -0,0 +1,66 @@ +import React, { useMemo, forwardRef } from 'react'; +import { StyleSheet, ScrollView, ScrollViewProps } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { useTheme } from './themes'; + +interface SafeAreaScrollViewProps extends ScrollViewProps { + floatingButtonHeight?: number; + headerHeight?: number; // Additional header height to account for (e.g., when headerTransparent is true) +} + +const SafeAreaScrollView = forwardRef((props, ref) => { + const { style, contentContainerStyle, floatingButtonHeight = 0, headerHeight = 0, ...otherProps } = props; + const { colors } = useTheme(); + const insets = useSafeAreaInsets(); + + const componentStyle = useMemo(() => { + return StyleSheet.compose({ flex: 1, backgroundColor: colors.background }, style); + }, [colors.background, style]); + + const contentStyle = useMemo(() => { + // Calculate base inset paddings with proper typing + const basePadding: { + paddingBottom: number; + paddingTop: number; + paddingLeft?: number; + paddingRight?: number; + } = { + paddingBottom: insets.bottom + floatingButtonHeight, // Add extra padding for the floating button + paddingTop: (() => { + // If explicit headerHeight is provided, use it + if (headerHeight > 0) { + return headerHeight; + } + // iOS safe area or no status bar + return insets.top > 0 ? 5 : 0; + })(), + }; + + // Only add horizontal paddings if they aren't explicitly defined in contentContainerStyle + if (!StyleSheet.flatten(contentContainerStyle)?.paddingHorizontal && !StyleSheet.flatten(contentContainerStyle)?.paddingLeft) { + basePadding.paddingLeft = insets.left; + } + + if (!StyleSheet.flatten(contentContainerStyle)?.paddingHorizontal && !StyleSheet.flatten(contentContainerStyle)?.paddingRight) { + basePadding.paddingRight = insets.right; + } + + // Now compose with contentContainerStyle to ensure passed styles override defaults + return StyleSheet.compose(basePadding, contentContainerStyle); + }, [insets, contentContainerStyle, floatingButtonHeight, headerHeight]); + + return ( + + ); +}); + +export default SafeAreaScrollView; diff --git a/components/SafeAreaSectionList.tsx b/components/SafeAreaSectionList.tsx new file mode 100644 index 00000000000..674838bac89 --- /dev/null +++ b/components/SafeAreaSectionList.tsx @@ -0,0 +1,65 @@ +import React, { useMemo } from 'react'; +import { StyleSheet, SectionList, SectionListProps, Platform, StatusBar } from 'react-native'; + +import { useTheme } from './themes'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +interface SafeAreaSectionListProps extends SectionListProps { + floatingButtonHeight?: number; + ignoreTopInset?: boolean; + headerHeight?: number; // Additional header height to account for (e.g., when headerTransparent is true) +} + +const SafeAreaSectionList = (props: SafeAreaSectionListProps) => { + const { style, contentContainerStyle, floatingButtonHeight = 0, ignoreTopInset = false, headerHeight = 0, ...otherProps } = props; + const { colors } = useTheme(); + const insets = useSafeAreaInsets(); + + const componentStyle = useMemo(() => { + return StyleSheet.compose({ flex: 1, backgroundColor: colors.background }, style); + }, [colors.background, style]); + + const contentStyle = useMemo(() => { + // Calculate top padding + const topPadding = (() => { + // If ignoreTopInset is true, don't apply any top padding + if (ignoreTopInset) { + return 0; + } + // If explicit headerHeight is provided, use it + if (headerHeight > 0) { + return headerHeight; + } + // On Android with transparent headers, we need to account for header height + if (Platform.OS === 'android' && insets.top > 0) { + return 56 + (StatusBar.currentHeight || insets.top); + } + // iOS safe area + return insets.top; + })(); + + return StyleSheet.compose( + { + paddingBottom: insets.bottom + floatingButtonHeight, // Add extra padding for the floating button + paddingRight: insets.right, + paddingLeft: insets.left, + paddingTop: topPadding, + }, + contentContainerStyle, + ); + }, [insets, contentContainerStyle, floatingButtonHeight, ignoreTopInset, headerHeight]); + + return ( + + ); +}; + +export default SafeAreaSectionList; diff --git a/components/SaveFileButton.tsx b/components/SaveFileButton.tsx new file mode 100644 index 00000000000..fcd449f5b65 --- /dev/null +++ b/components/SaveFileButton.tsx @@ -0,0 +1,66 @@ +import React, { ReactNode, useCallback } from 'react'; +import { StyleProp, TouchableOpacityProps, ViewStyle } from 'react-native'; + +import * as fs from '../blue_modules/fs'; +import loc from '../loc'; +import { ActionIcons } from '../typings/ActionIcons'; +import ToolTipMenu from './TooltipMenu'; +import { Action } from './types'; + +interface SaveFileButtonProps extends TouchableOpacityProps { + fileName: string; + fileContent: string; + children?: ReactNode; + style?: StyleProp; + afterOnPress?: () => void; + beforeOnPress?: (() => Promise) | (() => void); +} + +const SaveFileButton: React.FC = ({ fileName, fileContent, children, style, beforeOnPress, afterOnPress }) => { + const handlePressMenuItem = useCallback( + async (actionId: string) => { + if (beforeOnPress) { + await beforeOnPress(); + } + const action = actions.find(a => a.id === actionId); + + if (action?.id === 'save') { + await fs.writeFileAndExport(fileName, fileContent, false).finally(() => { + afterOnPress?.(); + }); + } else if (action?.id === 'share') { + await fs.writeFileAndExport(fileName, fileContent, true).finally(() => { + afterOnPress?.(); + }); + } + }, + [afterOnPress, beforeOnPress, fileContent, fileName], + ); + + return ( + + {children} + + ); +}; + +export default SaveFileButton; + +const actionIcons: { [key: string]: ActionIcons } = { + Share: { + iconValue: 'square.and.arrow.up', + }, + Save: { + iconValue: 'square.and.arrow.down', + }, +}; +const actions: Action[] = [ + { id: 'save', text: loc._.save, icon: actionIcons.Save }, + { id: 'share', text: loc.receive.details_share, icon: actionIcons.Share }, +]; diff --git a/components/SecondButton.tsx b/components/SecondButton.tsx new file mode 100644 index 00000000000..cec04e75180 --- /dev/null +++ b/components/SecondButton.tsx @@ -0,0 +1,80 @@ +import React, { forwardRef } from 'react'; +import { StyleSheet, Text, TouchableOpacity, View, ActivityIndicator } from 'react-native'; +import Icon, { type IconProps } from './Icon'; + +import { useTheme } from './themes'; + +type IconButtonProps = Pick & { color: string }; + +type SecondButtonProps = { + backgroundColor?: string; + disabled?: boolean; + icon?: IconButtonProps; + title: string; + textColor?: string; + onPress?: () => void; + loading?: boolean; + testID?: string; +}; + +export const SecondButton = forwardRef, SecondButtonProps>((props, ref) => { + const { colors } = useTheme(); + let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.buttonGrayBackgroundColor; + let fontColor = props.textColor ?? colors.secondButtonTextColor; + if (props.disabled === true) { + backgroundColor = colors.buttonDisabledBackgroundColor; + fontColor = colors.buttonDisabledTextColor; + } + + const buttonView = props.loading ? ( + + ) : ( + + {props.icon && } + {props.title && {props.title}} + + ); + + return props.onPress ? ( + + {buttonView} + + ) : ( + {buttonView} + ); +}); + +const styles = StyleSheet.create({ + button: { + minHeight: 45, + height: 48, + maxHeight: 48, + borderRadius: 7, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 16, + flexGrow: 1, + }, + content: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + }, + text: { + marginHorizontal: 8, + fontSize: 16, + fontWeight: '600', + }, + view: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/components/SeedWords.tsx b/components/SeedWords.tsx new file mode 100644 index 00000000000..6d39589a0ff --- /dev/null +++ b/components/SeedWords.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; + +import { useTheme } from './themes'; +import { useLocale } from '@react-navigation/native'; + +const SeedWords = ({ seed }: { seed: string }) => { + const words = seed.split(/\s/); + const { colors } = useTheme(); + const { direction } = useLocale(); + + const stylesHook = StyleSheet.create({ + word: { + backgroundColor: colors.inputBackgroundColor, + }, + wortText: { + color: colors.labelText, + }, + secret: { + flexDirection: direction === 'rtl' ? 'row-reverse' : 'row', + }, + }); + + return ( + + {words.map((secret, index) => { + const text = `${index + 1}. ${secret} `; + return ( + + + {text} + + + ); + })} + + {seed} + + + ); +}; + +const styles = StyleSheet.create({ + word: { + marginRight: 8, + marginBottom: 8, + paddingTop: 6, + paddingBottom: 6, + paddingLeft: 8, + paddingRight: 8, + borderRadius: 4, + }, + wortText: { + fontWeight: 'bold', + textAlign: 'left', + fontSize: 17, + }, + secret: { + flexWrap: 'wrap', + justifyContent: 'center', + }, + hiddenText: { + height: 0, + width: 0, + }, +}); + +export default SeedWords; diff --git a/components/SegmentedControl.tsx b/components/SegmentedControl.tsx new file mode 100644 index 00000000000..6b1f5de863c --- /dev/null +++ b/components/SegmentedControl.tsx @@ -0,0 +1,60 @@ +import React, { useCallback } from 'react'; +import { View, StyleSheet, NativeSyntheticEvent } from 'react-native'; +import NativeSegmentedControl from '../codegen/SegmentedControlNativeComponent'; + +interface SegmentedControlProps { + values: string[]; + selectedIndex: number; + onChange: (index: number) => void; + testID?: string; +} + +interface SegmentedControlEvent { + selectedIndex: number; +} + +const SegmentedControl: React.FC = ({ values, selectedIndex, onChange, testID }) => { + const handleChange = useCallback( + (event: NativeSyntheticEvent) => { + if (event?.nativeEvent?.selectedIndex !== undefined) { + onChange(event.nativeEvent.selectedIndex); + } + }, + [onChange], + ); + + if (!Array.isArray(values) || values.length === 0) { + return null; + } + + return ( + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + width: '100%', + marginHorizontal: 0, + marginBottom: 18, + minHeight: 40, + }, + segmentedControl: { + height: 40, + }, +}); + +export default SegmentedControl; diff --git a/components/SettingsBlockExplorerCustomUrlListItem.tsx b/components/SettingsBlockExplorerCustomUrlListItem.tsx new file mode 100644 index 00000000000..936ee93607b --- /dev/null +++ b/components/SettingsBlockExplorerCustomUrlListItem.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { StyleSheet, TextInput, View } from 'react-native'; +import { useTheme } from './themes'; +import loc from '../loc'; +import { SettingsCard, SettingsListItem, isAndroid } from './platform'; + +interface SettingsBlockExplorerCustomUrlItemProps { + isCustomEnabled: boolean; + onSwitchToggle: (value: boolean) => void; + customUrl: string; + onCustomUrlChange: (url: string) => void; + onSubmitCustomUrl: () => void; + inputRef?: React.RefObject; +} + +const SettingsBlockExplorerCustomUrlItem: React.FC = ({ + isCustomEnabled, + onSwitchToggle, + customUrl, + onCustomUrlChange, + onSubmitCustomUrl, + inputRef, +}) => { + const { colors } = useTheme(); + const horizontalPadding = isAndroid ? 20 : 16; + + return ( + + + + {isCustomEnabled && ( + + + + + + + + )} + + ); +}; + +export default SettingsBlockExplorerCustomUrlItem; + +const styles = StyleSheet.create({ + inputCardWrapper: { + marginTop: isAndroid ? 12 : 10, + }, + uriContainer: { + flexDirection: 'row', + borderWidth: 1, + borderRadius: isAndroid ? 4 : 8, + marginVertical: isAndroid ? 12 : 10, + marginHorizontal: isAndroid ? 12 : 10, + paddingHorizontal: isAndroid ? 10 : 12, + alignItems: 'center', + minHeight: 44, + }, + uriText: { + flex: 1, + minHeight: 36, + }, +}); diff --git a/components/SquareButton.js b/components/SquareButton.js deleted file mode 100644 index 85e85214d43..00000000000 --- a/components/SquareButton.js +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */ -import React, { forwardRef } from 'react'; -import { TouchableOpacity, View, Text } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; - -export const SquareButton = forwardRef((props, ref) => { - const { colors } = useTheme(); - let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.buttonBlueBackgroundColor; - let fontColor = colors.buttonTextColor; - if (props.disabled === true) { - backgroundColor = colors.buttonDisabledBackgroundColor; - fontColor = colors.buttonDisabledTextColor; - } - - return ( - - - {props.icon && } - {props.title && {props.title}} - - - ); -}); diff --git a/components/SquareButton.tsx b/components/SquareButton.tsx new file mode 100644 index 00000000000..8fce329b326 --- /dev/null +++ b/components/SquareButton.tsx @@ -0,0 +1,48 @@ +import React, { forwardRef } from 'react'; +import { StyleProp, StyleSheet, Text, TouchableOpacity, View, ViewStyle } from 'react-native'; + +import { useTheme } from './themes'; + +interface SquareButtonProps { + title: string; + onPress?: () => void; + style?: StyleProp; + testID?: string; +} + +export const SquareButton = forwardRef, SquareButtonProps>((props, ref) => { + const { title, onPress, style, testID } = props; + const { colors } = useTheme(); + + const hookStyles = StyleSheet.create({ + text: { + color: colors.buttonTextColor, + }, + }); + + const buttonView = ( + + {title} + + ); + + return onPress ? ( + + {buttonView} + + ) : ( + {buttonView} + ); +}); + +const styles = StyleSheet.create({ + contentContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + }, + text: { + marginHorizontal: 8, + fontSize: 16, + }, +}); diff --git a/components/SquareEnumeratedWords.js b/components/SquareEnumeratedWords.js deleted file mode 100644 index 680ab840e67..00000000000 --- a/components/SquareEnumeratedWords.js +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; -import { useTheme } from '@react-navigation/native'; -import { TouchableOpacity, Text, StyleSheet, View } from 'react-native'; -import PropTypes from 'prop-types'; - -export const SquareEnumeratedWordsContentAlign = Object.freeze({ left: 'flex-start', center: 'center', right: 'flex-end' }); -const SquareEnumeratedWords = props => { - const { - entries = ['Empty entries prop. Please provide an array of strings'], - appendNumber = true, - contentAlign = SquareEnumeratedWordsContentAlign.center, - } = props; - const { colors } = useTheme(); - const stylesHook = StyleSheet.create({ - entryTextContainer: { - backgroundColor: colors.inputBackgroundColor, - }, - entryText: { - color: colors.labelText, - }, - }); - - const renderSecret = () => { - const component = []; - const entriesObject = entries.entries(); - for (const [index, secret] of entriesObject) { - if (entries.length > 1) { - const text = appendNumber ? `${index + 1}. ${secret} ` : `${secret} `; - component.push( - - - {text} - - , - ); - } else { - component.push( - - - {secret} - - , - ); - } - } - return component; - }; - - return {renderSecret()}; -}; - -const styles = StyleSheet.create({ - entryTextContainer: { - marginRight: 8, - marginBottom: 8, - paddingTop: 6, - paddingBottom: 6, - paddingLeft: 8, - paddingRight: 8, - borderRadius: 4, - }, - entryText: { - fontWeight: 'bold', - textAlign: 'left', - }, - container: { - flexDirection: 'row', - flexWrap: 'wrap', - }, -}); -SquareEnumeratedWords.propTypes = { - entries: PropTypes.arrayOf(PropTypes.string), - contentAlign: PropTypes.string, - appendNumber: PropTypes.bool, -}; - -export default SquareEnumeratedWords; diff --git a/components/SquareEnumeratedWords.tsx b/components/SquareEnumeratedWords.tsx new file mode 100644 index 00000000000..b2667fbbc04 --- /dev/null +++ b/components/SquareEnumeratedWords.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; + +import { useTheme } from './themes'; + +type ContentAlignType = 'flex-start' | 'center' | 'flex-end'; +export const SquareEnumeratedWordsContentAlign: Record = Object.freeze({ + left: 'flex-start', + center: 'center', + right: 'flex-end', +}); + +interface SquareEnumeratedWordsProps { + entries: string[]; + appendNumber: boolean; + contentAlign: ContentAlignType; +} + +const SquareEnumeratedWords: React.FC = ({ entries, appendNumber, contentAlign }) => { + const { colors } = useTheme(); + const stylesHook = StyleSheet.create({ + entryTextContainer: { + backgroundColor: colors.inputBackgroundColor, + }, + entryText: { + color: colors.labelText, + }, + container: { + justifyContent: contentAlign, + }, + }); + + const renderSecret = () => { + const component = []; + const entriesObject = entries.entries(); + for (const [index, secret] of entriesObject) { + if (entries.length > 1) { + const text = appendNumber ? `${index + 1}. ${secret} ` : `${secret} `; + component.push( + + + {text} + + , + ); + } else { + component.push( + + + {secret} + + , + ); + } + } + return component; + }; + + return {renderSecret()}; +}; + +const styles = StyleSheet.create({ + entryTextContainer: { + marginRight: 8, + marginBottom: 8, + paddingTop: 6, + paddingBottom: 6, + paddingLeft: 8, + paddingRight: 8, + borderRadius: 4, + }, + entryText: { + fontWeight: 'bold', + textAlign: 'left', + }, + container: { + flexDirection: 'row', + flexWrap: 'wrap', + }, +}); + +export default SquareEnumeratedWords; diff --git a/components/Tabs.tsx b/components/Tabs.tsx new file mode 100644 index 00000000000..fd2c0e8efbf --- /dev/null +++ b/components/Tabs.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { StyleSheet, TouchableOpacity, View } from 'react-native'; + +import { useTheme } from './themes'; + +const tabsStyles = StyleSheet.create({ + root: { + flexDirection: 'row', + height: 50, + borderColor: '#e3e3e3', + borderBottomWidth: 1, + }, + tabRoot: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + borderColor: 'white', + borderBottomWidth: 2, + }, + activeTabRoot: { + borderColor: 'transparent', + borderBottomWidth: 2, + }, + marginBottom: { + marginBottom: 30, + }, +}); + +interface TabProps { + active: boolean; +} + +interface TabsProps { + active: number; + onSwitch: (index: number) => void; + tabs: React.ComponentType[]; + isIpad?: boolean; +} + +export const Tabs: React.FC = ({ active, onSwitch, tabs, isIpad = false }) => { + const { colors } = useTheme(); + return ( + + {tabs.map((Tab, i) => ( + onSwitch(i)} + style={[tabsStyles.tabRoot, active === i && { ...tabsStyles.activeTabRoot, borderColor: colors.buttonAlternativeTextColor }]} + > + + + ))} + + ); +}; diff --git a/components/TipBox.tsx b/components/TipBox.tsx new file mode 100644 index 00000000000..3911e1cd73f --- /dev/null +++ b/components/TipBox.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { View, StyleSheet, ViewStyle } from 'react-native'; +import { useTheme } from './themes'; +import BlueText from './BlueText'; +interface TipBoxProps { + number?: string; + title?: string; + description?: string; + additionalDescription?: string; + containerStyle?: ViewStyle; +} + +const TipBox: React.FC = ({ number, title, description, additionalDescription, containerStyle }) => { + const { colors } = useTheme(); + const stylesHook = StyleSheet.create({ + tipBox: { + backgroundColor: colors.ballOutgoingExpired, + borderRadius: 12, + padding: 16, + marginBottom: 24, + ...containerStyle, + }, + tipHeader: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: number || title ? 16 : 0, + }, + tipHeaderText: { + marginLeft: 4, + flex: 1, + }, + description: { + marginBottom: additionalDescription ? 16 : 0, + }, + }); + + return ( + + {(number || title) && ( + + {number && ( + + {number} + + )} + {title && ( + + {title} + + )} + + )} + {description && {description}} + {additionalDescription && {additionalDescription}} + + ); +}; + +const styles = StyleSheet.create({ + vaultKeyCircle: { + width: 32, + height: 32, + justifyContent: 'center', + alignItems: 'center', + }, + vaultKeyText: { + fontSize: 18, + fontWeight: 'bold', + }, +}); + +export default TipBox; diff --git a/components/TooltipMenu.android.js b/components/TooltipMenu.android.js deleted file mode 100644 index 8b83f3e30c8..00000000000 --- a/components/TooltipMenu.android.js +++ /dev/null @@ -1,61 +0,0 @@ -import React, { useRef, useEffect, forwardRef } from 'react'; -import PropTypes from 'prop-types'; -import { TouchableOpacity } from 'react-native'; -import showPopupMenu from '../blue_modules/showPopupMenu'; - -const ToolTipMenu = (props, ref) => { - const menuRef = useRef(); - const disabled = props.disabled ?? false; - const isMenuPrimaryAction = props.isMenuPrimaryAction ?? false; - - const buttonStyle = props.buttonStyle ?? {}; - const handleToolTipSelection = selection => { - props.onPressMenuItem(selection.id); - }; - - useEffect(() => { - if (ref && ref.current) { - ref.current.dismissMenu = dismissMenu; - } - }, [ref]); - - const dismissMenu = () => { - console.log('dismissMenu Not implemented'); - }; - - const showMenu = () => { - const menu = []; - for (const actions of props.actions) { - if (Array.isArray(actions)) { - for (const actionToMap of actions) { - menu.push({ id: actionToMap.id, label: actionToMap.text }); - } - } else { - menu.push({ id: actions.id, label: actions.text }); - } - } - - showPopupMenu(menu, handleToolTipSelection, menuRef.current); - }; - - return ( - - {props.children} - - ); -}; - -export default forwardRef(ToolTipMenu); -ToolTipMenu.propTypes = { - actions: PropTypes.object.isRequired, - children: PropTypes.node.isRequired, - onPressMenuItem: PropTypes.func.isRequired, - isMenuPrimaryAction: PropTypes.bool, - onPress: PropTypes.func, - disabled: PropTypes.bool, -}; diff --git a/components/TooltipMenu.helpers.ts b/components/TooltipMenu.helpers.ts new file mode 100644 index 00000000000..be8bb70634d --- /dev/null +++ b/components/TooltipMenu.helpers.ts @@ -0,0 +1,115 @@ +import { ContextMenuAction } from 'react-native-context-menu-view'; +import { Action } from './types'; + +export type Platform = 'ios' | 'android'; + +// Mirrors the structure of the items we hand to the native ContextMenu, with +// the original Action.id at every node. We walk this in lockstep with the +// indexPath the native side returns on press, so two actions sharing the same +// visible text (think localized "Copy") cannot collide. +export type IdNode = { + id?: string; + children?: IdNode[]; +}; + +export const normalizeMenuState = (menuState?: Action['menuState']): boolean | undefined => { + if (menuState === undefined) return undefined; + return menuState === 'mixed' ? true : Boolean(menuState); +}; + +const mapLeaf = (action: Action, platform: Platform): { item: ContextMenuAction; idNode: IdNode } | null => { + if (!action?.id || action.hidden) return null; + + const subResults = (action.subactions ?? []) + .map(sub => mapLeaf(sub, platform)) + .filter((r): r is { item: ContextMenuAction; idNode: IdNode } => r !== null); + + const item: ContextMenuAction = { + title: action.text, + subtitle: action.subtitle, + systemIcon: platform === 'ios' ? (action.icon?.iconValue ?? action.image) : undefined, + icon: platform === 'android' ? (action.icon?.iconValue ?? action.image) : undefined, + iconColor: typeof action.imageColor === 'string' ? action.imageColor : undefined, + destructive: Boolean(action.destructive), + disabled: Boolean(action.disabled), + inlineChildren: platform === 'ios' ? action.displayInline : undefined, + }; + + const selected = normalizeMenuState(action.menuState); + if (selected !== undefined) item.selected = selected; + if (subResults.length > 0) item.actions = subResults.map(r => r.item); + + const idNode: IdNode = { id: String(action.id) }; + if (subResults.length > 0) idNode.children = subResults.map(r => r.idNode); + + return { item, idNode }; +}; + +/** + * Build the items array passed to the native ContextMenu and a parallel + * id-tree of the exact same shape. Returning both in a single pass guarantees + * they cannot drift apart. + * + * iOS preserves grouping (with synthetic inline parents); Android flattens + * because its native menu doesn't render inline groups. + */ +export const buildMenu = (actions: Action[] | Action[][], platform: Platform): { items: ContextMenuAction[]; ids: IdNode[] } => { + const items: ContextMenuAction[] = []; + const ids: IdNode[] = []; + + if (platform === 'ios') { + for (const group of actions) { + if (Array.isArray(group)) { + const inline = group.map(a => mapLeaf(a, platform)).filter((r): r is { item: ContextMenuAction; idNode: IdNode } => r !== null); + if (inline.length === 0) continue; + items.push({ + title: '', + actions: inline.map(r => r.item), + inlineChildren: true, + } as ContextMenuAction); + // Synthetic inline group: no id of its own, only carries children. + ids.push({ children: inline.map(r => r.idNode) }); + } else { + const r = mapLeaf(group, platform); + if (r) { + items.push(r.item); + ids.push(r.idNode); + } + } + } + return { items, ids }; + } + + for (const action of actions.flat()) { + const r = mapLeaf(action, platform); + if (r) { + items.push(r.item); + ids.push(r.idNode); + } + } + return { items, ids }; +}; + +/** + * Resolve the original Action.id for a press event by walking the id-tree + * with the path delivered by the native side. + * + * Path semantics: + * - iOS: indexPath is always populated, full path from root. + * - Android top-level: only `index` is set; we synthesize `[index]`. + * - Android submenu: indexPath is `[parentIndex, childIndex]`. + */ +export const lookupId = (ids: IdNode[], path: readonly number[]): string | undefined => { + if (path.length === 0) return undefined; + + let nodes: IdNode[] | undefined = ids; + let node: IdNode | undefined; + + for (const i of path) { + if (!nodes || i < 0 || i >= nodes.length) return undefined; + node = nodes[i]; + nodes = node.children; + } + + return node?.id; +}; diff --git a/components/TooltipMenu.ios.js b/components/TooltipMenu.ios.js deleted file mode 100644 index caf51d2fb2b..00000000000 --- a/components/TooltipMenu.ios.js +++ /dev/null @@ -1,114 +0,0 @@ -import React, { forwardRef } from 'react'; -import { ContextMenuView, ContextMenuButton } from 'react-native-ios-context-menu'; -import PropTypes from 'prop-types'; -import QRCodeComponent from './QRCodeComponent'; -import { TouchableOpacity } from 'react-native'; - -const ToolTipMenu = (props, ref) => { - const menuItemMapped = ({ action, menuOptions }) => { - const item = { - actionKey: action.id, - actionTitle: action.text, - icon: action.icon, - menuOptions, - menuTitle: action.menuTitle, - }; - item.menuState = action.menuStateOn ? 'on' : 'off'; - - if (action.disabled) { - item.menuAttributes = ['disabled']; - } - return item; - }; - - const menuItems = props.actions.map(action => { - if (Array.isArray(action)) { - const mapped = []; - for (const actionToMap of action) { - mapped.push(menuItemMapped({ action: actionToMap })); - } - const submenu = { - menuOptions: ['displayInline'], - menuItems: mapped, - menuTitle: '', - }; - return submenu; - } else { - return menuItemMapped({ action }); - } - }); - const menuTitle = props.title ?? ''; - const isButton = !!props.isButton; - const isMenuPrimaryAction = props.isMenuPrimaryAction ? props.isMenuPrimaryAction : false; - const previewQRCode = props.previewQRCode ?? false; - const previewValue = props.previewValue; - const disabled = props.disabled ?? false; - - const buttonStyle = props.buttonStyle; - return isButton ? ( - { - props.onPressMenuItem(nativeEvent.actionKey); - }} - isMenuPrimaryAction={isMenuPrimaryAction} - menuConfig={{ - menuTitle, - menuItems, - }} - style={buttonStyle} - > - {props.onPress ? ( - - {props.children} - - ) : ( - props.children - )} - - ) : ( - { - props.onPressMenuItem(nativeEvent.actionKey); - }} - menuConfig={{ - menuTitle, - menuItems, - }} - {...(previewQRCode - ? { - previewConfig: { - previewType: 'CUSTOM', - backgroundColor: 'white', - }, - renderPreview: () => , - } - : {})} - > - {props.onPress ? ( - - {props.children} - - ) : ( - props.children - )} - - ); -}; - -export default forwardRef(ToolTipMenu); -ToolTipMenu.propTypes = { - actions: PropTypes.object.isRequired, - title: PropTypes.string, - children: PropTypes.node.isRequired, - onPressMenuItem: PropTypes.func.isRequired, - isMenuPrimaryAction: PropTypes.bool, - isButton: PropTypes.bool, - previewQRCode: PropTypes.bool, - onPress: PropTypes.func, - previewValue: PropTypes.string, - disabled: PropTypes.bool, -}; diff --git a/components/TooltipMenu.js b/components/TooltipMenu.js deleted file mode 100644 index e57a12552f3..00000000000 --- a/components/TooltipMenu.js +++ /dev/null @@ -1,5 +0,0 @@ -const ToolTipMenu = props => { - return props.children; -}; - -export default ToolTipMenu; diff --git a/components/TooltipMenu.tsx b/components/TooltipMenu.tsx new file mode 100644 index 00000000000..245d4ea39db --- /dev/null +++ b/components/TooltipMenu.tsx @@ -0,0 +1,129 @@ +import React, { useCallback, useMemo } from 'react'; +import { NativeSyntheticEvent, Platform, Pressable, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; +import ContextMenu, { ContextMenuOnPressNativeEvent } from 'react-native-context-menu-view'; +import { ToolTipMenuProps } from './types'; +import { useSettings } from '../hooks/context/useSettings'; +import { buildMenu, lookupId } from './TooltipMenu.helpers'; + +const ToolTipMenu = (props: ToolTipMenuProps) => { + const { + title = '', + shouldOpenOnLongPress = true, + disabled = false, + onPress, + isButton = false, + buttonStyle, + onPressMenuItem, + children, + actions, + accessibilityLabel, + accessibilityHint, + accessibilityRole, + accessibilityState, + testID, + style, + enableAndroidRipple = true, + } = props; + + const { language } = useSettings(); + + const { items, ids } = useMemo(() => buildMenu(actions, Platform.OS as 'ios' | 'android'), [actions]); + + const handlePressMenuItem = useCallback( + (e: NativeSyntheticEvent) => { + const { name, indexPath, index } = e.nativeEvent; + const path = indexPath?.length ? indexPath : typeof index === 'number' ? [index] : []; + const id = lookupId(ids, path); + if (id !== undefined) onPressMenuItem(id); + else if (name) onPressMenuItem(name); // last-resort fallback + }, + [ids, onPressMenuItem], + ); + + if (disabled || actions.length === 0) return null; + + // The native ContextMenu is the single source of truth for opening the menu: + // - Android: ContextMenuView's GestureDetector handles tap (dropdown mode) + // and long-press, then opens the popup itself. + // - iOS: UIContextMenuInteraction is attached to the first React child, with + // `showsMenuAsPrimaryAction` for tap-to-open in dropdown mode. + // + // We wrap in a Pressable ONLY when the caller wants a separate `onPress` + // (a short-tap action that does something OTHER than open the menu). Adding + // any extra Pressable handler is unnecessary and on Android races with the + // native gesture detector — usePressability always returns true from + // onStartShouldSetResponder, so the JS responder system claims the touch + // and dispatches ACTION_CANCEL to the child native view, leaving the menu + // unopened. There is no escape hatch for that — Pressable cannot be + // configured to skip responder claiming. + // + // Trade-off: dropdown buttons without onPress (HeaderMenuButton et al.) + // get no Android ripple. The menu opening (≈100ms) is the feedback. We + // accept this rather than reintroduce the gesture-cancel race. + const wrapInPressable = Boolean(onPress); + + const buttonShellStyle: StyleProp = isButton ? styles.button : undefined; + const visibleStyle = StyleSheet.flatten([buttonShellStyle, style, buttonStyle]); + + const menu = ( + + {children} + + ); + + if (!wrapInPressable) { + // Wrap the native ContextMenu in a plain View that carries `testID` and the + // accessibility props. On iOS, react-native-context-menu-view propagates + // the accessibility identifier across multiple descendants of its native + // host, so attaching `testID` directly to ContextMenu makes Detox match + // multiple views (`Multiple elements found for "MATCHER(id == ...)"`). + // A plain View gives Detox a single, deterministic match and—unlike + // Pressable—never claims the JS responder, so it does not reintroduce the + // Android gesture-cancel race documented above. + return ( + + {menu} + + ); + } + + return ( + + StyleSheet.flatten([visibleStyle, pressed && enableAndroidRipple && Platform.OS === 'android' ? styles.pressed : null]) + } + accessibilityLabel={accessibilityLabel} + accessibilityHint={accessibilityHint} + accessibilityRole={accessibilityRole} + accessibilityState={accessibilityState} + accessibilityLanguage={language} + testID={testID} + hitSlop={8} + > + {menu} + + ); +}; + +export default ToolTipMenu; + +const styles = StyleSheet.create({ + button: { alignSelf: 'center' }, + menuFlex: { flex: 1 }, + pressed: { opacity: 0.6 }, +}); diff --git a/components/TotalWalletsBalance.tsx b/components/TotalWalletsBalance.tsx new file mode 100644 index 00000000000..521e72e2c86 --- /dev/null +++ b/components/TotalWalletsBalance.tsx @@ -0,0 +1,135 @@ +import React, { useMemo, useCallback } from 'react'; +import { TouchableOpacity, Text, StyleSheet, View } from 'react-native'; +import { useStorage } from '../hooks/context/useStorage'; +import loc, { formatBalanceWithoutSuffix } from '../loc'; +import { BitcoinUnit } from '../models/bitcoinUnits'; +import ToolTipMenu from './TooltipMenu'; +import { CommonToolTipActions } from '../typings/CommonToolTipActions'; +import { useSettings } from '../hooks/context/useSettings'; +import Clipboard from '@react-native-clipboard/clipboard'; +import { useTheme } from './themes'; + +export const TotalWalletsBalancePreferredUnit = 'TotalWalletsBalancePreferredUnit'; +export const TotalWalletsBalanceKey = 'TotalWalletsBalance'; + +const TotalWalletsBalance: React.FC = React.memo(() => { + const { wallets } = useStorage(); + const { + preferredFiatCurrency, + isTotalBalanceEnabled, + setIsTotalBalanceEnabledStorage, + totalBalancePreferredUnit, + setTotalBalancePreferredUnitStorage, + } = useSettings(); + const { colors } = useTheme(); + + const totalBalanceFormatted = useMemo(() => { + const totalBalance = wallets.reduce((prev, curr) => { + return curr.hideBalance ? prev : prev + (curr.getBalance() || 0); + }, 0); + return formatBalanceWithoutSuffix(totalBalance, totalBalancePreferredUnit, true); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [wallets, totalBalancePreferredUnit, preferredFiatCurrency]); + + const toolTipActions = useMemo( + () => [ + { + id: 'viewInActions', + text: '', + displayInline: true, + subactions: [ + { + ...CommonToolTipActions.ViewInFiat, + text: loc.formatString(loc.total_balance_view.display_in_fiat, { currency: preferredFiatCurrency.endPointKey }), + hidden: totalBalancePreferredUnit === BitcoinUnit.LOCAL_CURRENCY, + }, + { ...CommonToolTipActions.ViewInSats, hidden: totalBalancePreferredUnit === BitcoinUnit.SATS }, + { ...CommonToolTipActions.ViewInBitcoin, hidden: totalBalancePreferredUnit === BitcoinUnit.BTC }, + ], + }, + CommonToolTipActions.CopyAmount, + CommonToolTipActions.Hide, + ], + [preferredFiatCurrency, totalBalancePreferredUnit], + ); + + const onPressMenuItem = useCallback( + async (id: string) => { + switch (id) { + case CommonToolTipActions.ViewInFiat.id: + await setTotalBalancePreferredUnitStorage(BitcoinUnit.LOCAL_CURRENCY); + break; + case CommonToolTipActions.ViewInSats.id: + await setTotalBalancePreferredUnitStorage(BitcoinUnit.SATS); + break; + case CommonToolTipActions.ViewInBitcoin.id: + await setTotalBalancePreferredUnitStorage(BitcoinUnit.BTC); + break; + case CommonToolTipActions.Hide.id: + await setIsTotalBalanceEnabledStorage(false); + break; + case CommonToolTipActions.CopyAmount.id: + Clipboard.setString(totalBalanceFormatted.toString()); + break; + default: + break; + } + }, + [setIsTotalBalanceEnabledStorage, totalBalanceFormatted, setTotalBalancePreferredUnitStorage], + ); + + const handleBalanceOnPress = useCallback(async () => { + const nextUnit = + totalBalancePreferredUnit === BitcoinUnit.BTC + ? BitcoinUnit.SATS + : totalBalancePreferredUnit === BitcoinUnit.SATS + ? BitcoinUnit.LOCAL_CURRENCY + : BitcoinUnit.BTC; + await setTotalBalancePreferredUnitStorage(nextUnit); + }, [totalBalancePreferredUnit, setTotalBalancePreferredUnitStorage]); + + if (!isTotalBalanceEnabled) return null; + + return ( + + + {loc.wallets.total_balance} + + + {totalBalanceFormatted}{' '} + {totalBalancePreferredUnit !== BitcoinUnit.LOCAL_CURRENCY && ( + {totalBalancePreferredUnit} + )} + + + + + ); +}); + +const styles = StyleSheet.create({ + menuContainer: { + alignSelf: 'stretch', + }, + container: { + flexDirection: 'column', + alignItems: 'flex-start', + paddingHorizontal: 16, + paddingVertical: 8, + }, + label: { + fontSize: 14, + marginBottom: 2, + color: '#9BA0A9', + }, + balance: { + fontSize: 32, + fontWeight: 'bold', + }, + currency: { + fontSize: 18, + fontWeight: 'bold', + }, +}); + +export default TotalWalletsBalance; diff --git a/components/TransactionListItem.js b/components/TransactionListItem.js deleted file mode 100644 index faf79b1c0a4..00000000000 --- a/components/TransactionListItem.js +++ /dev/null @@ -1,378 +0,0 @@ -/* eslint react/prop-types: "off" */ -import React, { useState, useMemo, useCallback, useContext, useEffect, useRef } from 'react'; -import { Linking, StyleSheet, View } from 'react-native'; -import Clipboard from '@react-native-clipboard/clipboard'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { useNavigation, useTheme } from '@react-navigation/native'; - -import { BitcoinUnit } from '../models/bitcoinUnits'; -import * as NavigationService from '../NavigationService'; -import loc, { formatBalanceWithoutSuffix, transactionTimeToReadable } from '../loc'; -import Lnurl from '../class/lnurl'; -import { BlueStorageContext } from '../blue_modules/storage-context'; -import ToolTipMenu from './TooltipMenu'; -import { BlueListItem } from '../BlueComponents'; -import TransactionExpiredIcon from '../components/icons/TransactionExpiredIcon'; -import TransactionIncomingIcon from '../components/icons/TransactionIncomingIcon'; -import TransactionOffchainIcon from '../components/icons/TransactionOffchainIcon'; -import TransactionOffchainIncomingIcon from '../components/icons/TransactionOffchainIncomingIcon'; -import TransactionOnchainIcon from '../components/icons/TransactionOnchainIcon'; -import TransactionOutgoingIcon from '../components/icons/TransactionOutgoingIcon'; -import TransactionPendingIcon from '../components/icons/TransactionPendingIcon'; - -export const TransactionListItem = React.memo(({ item, itemPriceUnit = BitcoinUnit.BTC, walletID }) => { - const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1); - const { colors } = useTheme(); - const { navigate } = useNavigation(); - const menuRef = useRef(); - const { txMetadata, wallets, preferredFiatCurrency, language } = useContext(BlueStorageContext); - const containerStyle = useMemo( - () => ({ - backgroundColor: 'transparent', - borderBottomColor: colors.lightBorder, - paddingTop: 16, - paddingBottom: 16, - paddingRight: 0, - }), - [colors.lightBorder], - ); - - const title = useMemo(() => { - if (item.confirmations === 0) { - return loc.transactions.pending; - } else { - return transactionTimeToReadable(item.received); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [item.confirmations, item.received, language]); - const txMemo = txMetadata[item.hash]?.memo ?? ''; - const subtitle = useMemo(() => { - let sub = item.confirmations < 7 ? loc.formatString(loc.transactions.list_conf, { number: item.confirmations }) : ''; - if (sub !== '') sub += ' '; - sub += txMemo; - if (item.memo) sub += item.memo; - return sub || null; - }, [txMemo, item.confirmations, item.memo]); - - const rowTitle = useMemo(() => { - if (item.type === 'user_invoice' || item.type === 'payment_request') { - if (isNaN(item.value)) { - item.value = '0'; - } - const currentDate = new Date(); - const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise - const invoiceExpiration = item.timestamp + item.expire_time; - - if (invoiceExpiration > now) { - return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString(); - } else if (invoiceExpiration < now) { - if (item.ispaid) { - return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString(); - } else { - return loc.lnd.expired; - } - } - } else { - return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [item, itemPriceUnit, preferredFiatCurrency]); - - const rowTitleStyle = useMemo(() => { - let color = colors.successColor; - - if (item.type === 'user_invoice' || item.type === 'payment_request') { - const currentDate = new Date(); - const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise - const invoiceExpiration = item.timestamp + item.expire_time; - - if (invoiceExpiration > now) { - color = colors.successColor; - } else if (invoiceExpiration < now) { - if (item.ispaid) { - color = colors.successColor; - } else { - color = '#9AA0AA'; - } - } - } else if (item.value / 100000000 < 0) { - color = colors.foregroundColor; - } - - return { - color, - fontSize: 14, - fontWeight: '600', - textAlign: 'right', - width: 96, - }; - }, [item, colors.foregroundColor, colors.successColor]); - - const avatar = useMemo(() => { - // is it lightning refill tx? - if (item.category === 'receive' && item.confirmations < 3) { - return ( - - - - ); - } - - if (item.type && item.type === 'bitcoind_tx') { - return ( - - - - ); - } - if (item.type === 'paid_invoice') { - // is it lightning offchain payment? - return ( - - - - ); - } - - if (item.type === 'user_invoice' || item.type === 'payment_request') { - if (!item.ispaid) { - const currentDate = new Date(); - const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise - const invoiceExpiration = item.timestamp + item.expire_time; - if (invoiceExpiration < now) { - return ( - - - - ); - } - } else { - return ( - - - - ); - } - } - - if (!item.confirmations) { - return ( - - - - ); - } else if (item.value < 0) { - return ( - - - - ); - } else { - return ( - - - - ); - } - }, [item]); - - useEffect(() => { - setSubtitleNumberOfLines(1); - }, [subtitle]); - - const onPress = useCallback(async () => { - menuRef?.current?.dismissMenu(); - if (item.hash) { - navigate('TransactionStatus', { hash: item.hash, walletID }); - } else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') { - const lightningWallet = wallets.filter(wallet => wallet?.getID() === item.walletID); - if (lightningWallet.length === 1) { - try { - // is it a successful lnurl-pay? - const LN = new Lnurl(false, AsyncStorage); - let paymentHash = item.payment_hash; - if (typeof paymentHash === 'object') { - paymentHash = Buffer.from(paymentHash.data).toString('hex'); - } - const loaded = await LN.loadSuccessfulPayment(paymentHash); - if (loaded) { - NavigationService.navigate('ScanLndInvoiceRoot', { - screen: 'LnurlPaySuccess', - params: { - paymentHash, - justPaid: false, - fromWalletID: lightningWallet[0].getID(), - }, - }); - return; - } - } catch (e) { - console.log(e); - } - - navigate('LNDViewInvoice', { - invoice: item, - walletID: lightningWallet[0].getID(), - }); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [item, wallets]); - - const handleOnExpandNote = useCallback(() => { - setSubtitleNumberOfLines(0); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [subtitle]); - - const subtitleProps = useMemo(() => ({ numberOfLines: subtitleNumberOfLines }), [subtitleNumberOfLines]); - - const handleOnCopyAmountTap = useCallback(() => Clipboard.setString(rowTitle.replace(/[\s\\-]/g, '')), [rowTitle]); - const handleOnCopyTransactionID = useCallback(() => Clipboard.setString(item.hash), [item.hash]); - const handleOnCopyNote = useCallback(() => Clipboard.setString(subtitle), [subtitle]); - const handleOnViewOnBlockExplorer = useCallback(() => { - const url = `https://mempool.space/tx/${item.hash}`; - Linking.canOpenURL(url).then(supported => { - if (supported) { - Linking.openURL(url); - } - }); - }, [item.hash]); - const handleCopyOpenInBlockExplorerPress = useCallback(() => { - Clipboard.setString(`https://mempool.space/tx/${item.hash}`); - }, [item.hash]); - - const onToolTipPress = useCallback( - id => { - if (id === TransactionListItem.actionKeys.CopyAmount) { - handleOnCopyAmountTap(); - } else if (id === TransactionListItem.actionKeys.CopyNote) { - handleOnCopyNote(); - } else if (id === TransactionListItem.actionKeys.OpenInBlockExplorer) { - handleOnViewOnBlockExplorer(); - } else if (id === TransactionListItem.actionKeys.ExpandNote) { - handleOnExpandNote(); - } else if (id === TransactionListItem.actionKeys.CopyBlockExplorerLink) { - handleCopyOpenInBlockExplorerPress(); - } else if (id === TransactionListItem.actionKeys.CopyTXID) { - handleOnCopyTransactionID(); - } - }, - [ - handleCopyOpenInBlockExplorerPress, - handleOnCopyAmountTap, - handleOnCopyNote, - handleOnCopyTransactionID, - handleOnExpandNote, - handleOnViewOnBlockExplorer, - ], - ); - - const toolTipActions = useMemo(() => { - const actions = []; - if (rowTitle !== loc.lnd.expired) { - actions.push({ - id: TransactionListItem.actionKeys.CopyAmount, - text: loc.transactions.details_copy_amount, - icon: TransactionListItem.actionIcons.Clipboard, - }); - } - - if (subtitle) { - actions.push({ - id: TransactionListItem.actionKeys.CopyNote, - text: loc.transactions.details_copy_note, - icon: TransactionListItem.actionIcons.Clipboard, - }); - } - if (item.hash) { - actions.push( - { - id: TransactionListItem.actionKeys.CopyTXID, - text: loc.transactions.details_copy_txid, - icon: TransactionListItem.actionIcons.Clipboard, - }, - { - id: TransactionListItem.actionKeys.CopyBlockExplorerLink, - text: loc.transactions.details_copy_block_explorer_link, - icon: TransactionListItem.actionIcons.Clipboard, - }, - [ - { - id: TransactionListItem.actionKeys.OpenInBlockExplorer, - text: loc.transactions.details_show_in_block_explorer, - icon: TransactionListItem.actionIcons.Link, - }, - ], - ); - } - - if (subtitle && subtitleNumberOfLines === 1) { - actions.push([ - { - id: TransactionListItem.actionKeys.ExpandNote, - text: loc.transactions.expand_note, - icon: TransactionListItem.actionIcons.Note, - }, - ]); - } - - return actions; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [item.hash, subtitle, rowTitle, subtitleNumberOfLines, txMetadata]); - - return ( - - - - - - ); -}); - -TransactionListItem.actionKeys = { - CopyTXID: 'copyTX_ID', - CopyBlockExplorerLink: 'copy_blockExplorer', - ExpandNote: 'expandNote', - OpenInBlockExplorer: 'open_in_blockExplorer', - CopyAmount: 'copyAmount', - CopyNote: 'copyNote', -}; - -TransactionListItem.actionIcons = { - Eye: { - iconType: 'SYSTEM', - iconValue: 'eye', - }, - EyeSlash: { - iconType: 'SYSTEM', - iconValue: 'eye.slash', - }, - Clipboard: { - iconType: 'SYSTEM', - iconValue: 'doc.on.doc', - }, - Link: { - iconType: 'SYSTEM', - iconValue: 'link', - }, - Note: { - iconType: 'SYSTEM', - iconValue: 'note.text', - }, -}; - -const styles = StyleSheet.create({ - iconWidth: { width: 25 }, - container: { marginHorizontal: 4 }, -}); diff --git a/components/TransactionListItem.tsx b/components/TransactionListItem.tsx new file mode 100644 index 00000000000..3caa73f5dc0 --- /dev/null +++ b/components/TransactionListItem.tsx @@ -0,0 +1,520 @@ +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 Lnurl from '../class/lnurl'; +import { LightningTransaction, Transaction } from '../class/wallets/types'; +import TransactionExpiredIcon from '../components/icons/TransactionExpiredIcon'; +import TransactionIncomingIcon from '../components/icons/TransactionIncomingIcon'; +import TransactionOffchainIcon from '../components/icons/TransactionOffchainIcon'; +import TransactionOffchainIncomingIcon from '../components/icons/TransactionOffchainIncomingIcon'; +import TransactionOnchainIcon from '../components/icons/TransactionOnchainIcon'; +import TransactionOutgoingIcon from '../components/icons/TransactionOutgoingIcon'; +import TransactionPendingIcon from '../components/icons/TransactionPendingIcon'; +import loc, { formatBalanceWithoutSuffix, formatTransactionListDate, transactionTimeToReadable } from '../loc'; +import { BitcoinUnit } from '../models/bitcoinUnits'; +import { useSettings } from '../hooks/context/useSettings'; +import { useTheme } from './themes'; +import { Action } from './types'; +import { useExtendedNavigation } from '../hooks/useExtendedNavigation'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { DetailViewStackParamList } from '../navigation/DetailViewStackParamList'; +import { useStorage } from '../hooks/context/useStorage'; +import ToolTipMenu from './TooltipMenu'; +import { CommonToolTipActions } from '../typings/CommonToolTipActions'; +import { pop } from '../NavigationService'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { uint8ArrayToHex } from '../blue_modules/uint8array-extras'; +import ListItem from './ListItem'; + +const styles = StyleSheet.create({ + dateLine: { + fontSize: 13, + }, + fullWidthButton: { + width: '100%', + alignSelf: 'stretch', + }, + row: { + flexDirection: 'row', + alignItems: 'center', + width: '100%', + }, + avatarContainer: { + marginRight: 12, + alignItems: 'center', + justifyContent: 'center', + }, + textContainer: { + flex: 1, + paddingRight: 8, + }, + title: { + fontSize: 16, + fontWeight: '500', + }, + subtitle: { + fontSize: 14, + lineHeight: 20, + }, + rightColumn: { + marginLeft: 8, + alignItems: 'flex-end', + justifyContent: 'center', + }, + rightTitle: { + textAlign: 'right', + }, + animatedScaleContainer: { + width: '100%', + }, +}); + +type AnimatedPressableRowProps = { + onPress: () => void; + children: React.ReactNode; + accessibilityLabel: string; +}; + +const AnimatedPressableRow: React.FC = ({ onPress, children, accessibilityLabel }) => { + const scaleAnim = useRef(new Animated.Value(1)).current; + + const animateTo = useCallback( + (toValue: number) => { + Animated.timing(scaleAnim, { + toValue, + duration: 120, + easing: Easing.out(Easing.cubic), + useNativeDriver: true, + }).start(); + }, + [scaleAnim], + ); + + return ( + animateTo(0.97)} + onPressOut={() => animateTo(1)} + accessibilityRole="button" + accessibilityLabel={accessibilityLabel} + > + {children} + + ); +}; + +interface TransactionListItemProps { + itemPriceUnit: BitcoinUnit; + walletID: string; + item: Transaction & LightningTransaction; // using type intersection to have less issues with ts + searchQuery?: string; + style?: ViewStyle; + renderHighlightedText?: (text: string, query: string) => React.ReactElement; + onPress?: () => void; + disableNavigation?: boolean; +} + +type NavigationProps = NativeStackNavigationProp; + +const TransactionListItemComponent: React.FC = ({ + item, + itemPriceUnit, + walletID, + searchQuery, + style, + renderHighlightedText, + onPress: customOnPress, + disableNavigation = false, +}: TransactionListItemProps) => { + const { colors } = useTheme(); + const { navigate } = useExtendedNavigation(); + const { txMetadata, counterpartyMetadata, wallets } = useStorage(); + const { language, selectedBlockExplorer } = useSettings(); + const insets = useSafeAreaInsets(); + const containerStyle = useMemo( + () => ({ + backgroundColor: colors.background, + borderBottomColor: colors.lightBorder, + }), + [colors.background, colors.lightBorder], + ); + + const combinedStyle = useMemo(() => [containerStyle, style], [containerStyle, style]); + + const shortenContactName = (name: string): string => { + if (name.length < 16) return name; + return name.substr(0, 7) + '...' + name.substr(name.length - 7, 7); + }; + + let counterparty; + if (item.counterparty) { + counterparty = counterpartyMetadata?.[item.counterparty]?.label ?? item.counterparty; + } + const txMemo = (counterparty ? `[${shortenContactName(counterparty)}] ` : '') + (txMetadata[item.hash]?.memo ?? ''); + const noteForCopy = (txMemo || item.memo || '').trim() || undefined; + + const listTitleKey = useMemo((): 'pending' | 'sent' | 'received' => { + 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'; + if (item.type === 'user_invoice' || item.type === 'payment_request') { + if (!item.ispaid) return 'pending'; + return 'received'; + } + if (!item.confirmations) return 'pending'; + return item.value! < 0 ? 'sent' : 'received'; + }, [item.category, item.confirmations, item.type, item.value, item.ispaid]); + + const listTitle = useMemo(() => { + if (listTitleKey === 'pending') return loc.transactions.pending; + if (listTitleKey === 'sent') return loc.transactions.list_title_sent; + return loc.transactions.list_title_received; + }, [listTitleKey]); + + const isPending = listTitleKey === 'pending'; + + const dateLine = useMemo(() => { + if (isPending) return transactionTimeToReadable(item.timestamp); + return formatTransactionListDate(item.timestamp * 1000); + // language in deps so date format updates when locale changes (formatters use global locale) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isPending, item.timestamp, language]); + + const formattedAmount = useMemo(() => { + return formatBalanceWithoutSuffix(item.value, itemPriceUnit, true).toString(); + }, [item.value, itemPriceUnit]); + + const rowTitle = useMemo(() => { + if (item.type === 'user_invoice' || item.type === 'payment_request') { + const currentDate = new Date(); + const now = Math.floor(currentDate.getTime() / 1000); + const invoiceExpiration = item.timestamp! + item.expire_time!; + if (invoiceExpiration > now || item.ispaid) { + return formattedAmount; + } else { + return loc.lnd.expired; + } + } + return formattedAmount; + }, [item, formattedAmount]); + + const rowTitleStyle = useMemo(() => { + let color = colors.successColor; + + if (item.type === 'user_invoice' || item.type === 'payment_request') { + const currentDate = new Date(); + const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise + const invoiceExpiration = item.timestamp! + item.expire_time!; + + if (invoiceExpiration > now) { + color = colors.successColor; + } else if (invoiceExpiration < now) { + if (item.ispaid) { + color = colors.successColor; + } else { + color = '#9AA0AA'; + } + } + } else if (item.value! / 100000000 < 0) { + color = colors.foregroundColor; + } + + return { + color, + fontSize: 14, + fontWeight: '600' as TextStyle['fontWeight'], + textAlign: 'right', + paddingRight: insets.right, + paddingLeft: insets.left, + } as TextStyle; + }, [ + colors.successColor, + colors.foregroundColor, + item.type, + item.value, + item.timestamp, + item.expire_time, + item.ispaid, + insets.right, + insets.left, + ]); + + const determineTransactionTypeAndAvatar = () => { + if (item.category === 'receive' && item.confirmations! < 3) { + return { + label: loc.transactions.pending_transaction, + icon: , + }; + } + + if (item.type && item.type === 'bitcoind_tx') { + return { + label: loc.transactions.onchain, + icon: , + }; + } + + if (item.type === 'paid_invoice') { + return { + label: loc.transactions.offchain, + icon: , + }; + } + + if (item.type === 'user_invoice' || item.type === 'payment_request') { + const currentDate = new Date(); + const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise + const invoiceExpiration = item.timestamp! + item.expire_time!; + if (!item.ispaid && invoiceExpiration < now) { + return { + label: loc.transactions.expired_transaction, + icon: , + }; + } else if (!item.ispaid) { + return { + label: loc.transactions.expired_transaction, + icon: , + }; + } else { + return { + label: loc.transactions.incoming_transaction, + icon: , + }; + } + } + + if (!item.confirmations) { + return { + label: loc.transactions.pending_transaction, + icon: , + }; + } else if (item.value! < 0) { + return { + label: loc.transactions.outgoing_transaction, + icon: , + }; + } else { + return { + label: loc.transactions.incoming_transaction, + icon: , + }; + } + }; + + const { label: transactionTypeLabel, icon: avatar } = determineTransactionTypeAndAvatar(); + + const amountWithUnit = useMemo(() => { + const unitSuffix = itemPriceUnit === BitcoinUnit.BTC || itemPriceUnit === BitcoinUnit.SATS ? ` ${itemPriceUnit}` : ' '; + return `${formattedAmount}${unitSuffix}`; + }, [formattedAmount, itemPriceUnit]); + + const onPress = useCallback(async () => { + // If a custom onPress handler was provided, use it and return + if (customOnPress) { + customOnPress(); + if (disableNavigation) return; + } + + if (item.hash) { + if (renderHighlightedText) { + pop(); + } + navigate('TransactionStatus', { hash: item.hash, walletID, tx: item }); + } else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') { + const lightningWallet = wallets.filter(wallet => wallet?.getID() === item.walletID); + if (lightningWallet.length === 1) { + try { + // is it a successful lnurl-pay? + const LN = new Lnurl(false, AsyncStorage); + const rawPaymentHash = item.payment_hash; + if (!rawPaymentHash) throw new Error('Missing payment hash'); + const normalizedPaymentHash = + typeof rawPaymentHash === 'string' ? rawPaymentHash : uint8ArrayToHex(new Uint8Array((rawPaymentHash as any).data)); + const loaded = await LN.loadSuccessfulPayment(normalizedPaymentHash); + if (loaded) { + navigate('ScanLNDInvoiceRoot', { + screen: 'LnurlPaySuccess', + params: { + paymentHash: normalizedPaymentHash, + justPaid: false, + fromWalletID: lightningWallet[0].getID(), + }, + }); + return; + } + } catch (e) { + console.debug(e); + } + + navigate('LNDViewInvoice', { + invoice: item, + walletID: lightningWallet[0].getID(), + }); + } + } else { + console.log('cant handle press'); + } + }, [item, renderHighlightedText, navigate, walletID, wallets, customOnPress, disableNavigation]); + + const handleOnDetailsPress = useCallback(() => { + if (walletID && item && item.hash) { + navigate('TransactionStatus', { hash: item.hash, walletID, tx: item }); + } else { + const lightningWallet = wallets.find(wallet => wallet?.getID() === item.walletID); + if (lightningWallet) { + navigate('LNDViewInvoice', { + invoice: item, + walletID: lightningWallet.getID(), + }); + } + } + }, [item, navigate, walletID, wallets]); + + const handleOnCopyAmountTap = useCallback(() => Clipboard.setString(rowTitle.replace(/[\s\\-]/g, '')), [rowTitle]); + const handleOnCopyTransactionID = useCallback(() => Clipboard.setString(item.hash), [item.hash]); + const handleOnCopyNote = useCallback(() => Clipboard.setString(noteForCopy ?? ''), [noteForCopy]); + const handleOnViewOnBlockExplorer = useCallback(() => { + const url = `${selectedBlockExplorer.url}/tx/${item.hash}`; + Linking.canOpenURL(url).then(supported => { + if (supported) { + Linking.openURL(url); + } + }); + }, [item.hash, selectedBlockExplorer]); + const handleCopyOpenInBlockExplorerPress = useCallback(() => { + Clipboard.setString(`${selectedBlockExplorer.url}/tx/${item.hash}`); + }, [item.hash, selectedBlockExplorer]); + + const onToolTipPress = useCallback( + (id: any) => { + if (id === CommonToolTipActions.CopyAmount.id) { + handleOnCopyAmountTap(); + } else if (id === CommonToolTipActions.CopyNote.id) { + handleOnCopyNote(); + } else if (id === CommonToolTipActions.OpenInBlockExplorer.id) { + handleOnViewOnBlockExplorer(); + } else if (id === CommonToolTipActions.CopyBlockExplorerLink.id) { + handleCopyOpenInBlockExplorerPress(); + } else if (id === CommonToolTipActions.CopyTXID.id) { + handleOnCopyTransactionID(); + } else if (id === CommonToolTipActions.Details.id) { + handleOnDetailsPress(); + } + }, + [ + handleCopyOpenInBlockExplorerPress, + handleOnCopyAmountTap, + handleOnCopyNote, + handleOnCopyTransactionID, + handleOnDetailsPress, + handleOnViewOnBlockExplorer, + ], + ); + const toolTipActions = useMemo((): Action[] => { + const actions: (Action | Action[])[] = [ + { + ...CommonToolTipActions.CopyAmount, + hidden: rowTitle === loc.lnd.expired, + }, + { + ...CommonToolTipActions.CopyNote, + hidden: !noteForCopy, + }, + { + ...CommonToolTipActions.CopyTXID, + hidden: !item.hash, + }, + { + ...CommonToolTipActions.CopyBlockExplorerLink, + hidden: !item.hash, + }, + [{ ...CommonToolTipActions.OpenInBlockExplorer, hidden: !item.hash }, CommonToolTipActions.Details], + ]; + + return actions as Action[]; + }, [rowTitle, noteForCopy, item.hash]); + + const title = listTitle; + const subtitle = dateLine; + const subtitleNumberOfLines: number = 1; + + const titleStyle = useMemo(() => ({ color: colors.foregroundColor }), [colors.foregroundColor]); + const subtitleStyle = useMemo(() => ({ color: colors.alternativeTextColor }), [colors.alternativeTextColor]); + + const subtitleContent = useMemo(() => { + if (!subtitle) return null; + const maxLines = subtitleNumberOfLines === 0 ? undefined : subtitleNumberOfLines; + + if (renderHighlightedText && searchQuery) { + const highlighted = renderHighlightedText(subtitle, searchQuery); + if (React.isValidElement(highlighted)) { + const highlightedElement = highlighted as React.ReactElement<{ numberOfLines?: number; style?: TextStyle | TextStyle[] }>; + const existingStyle = highlightedElement.props?.style; + const mergedStyle: TextStyle[] = ( + Array.isArray(existingStyle) + ? [styles.subtitle, subtitleStyle, ...existingStyle] + : [styles.subtitle, subtitleStyle, existingStyle] + ).filter(Boolean) as TextStyle[]; + + return React.cloneElement(highlightedElement, { + numberOfLines: maxLines, + style: mergedStyle, + }); + } + return highlighted; + } + + return ( + + {subtitle} + + ); + }, [subtitle, subtitleNumberOfLines, renderHighlightedText, searchQuery, subtitleStyle]); + + return ( + + + {/* @ts-ignore - Context menu wrapper types can be overly strict about child element props */} + {dateLine}} + chevron={false} + rightTitle={rowTitle} + rightTitleStyle={rowTitleStyle} + rightSubtitle={noteForCopy} + rightSubtitleStyle={styles.rightColumn} + containerStyle={combinedStyle} + testID="TransactionListItem" + accessibilityRole="button" + accessibilityLabel={`${transactionTypeLabel}, ${amountWithUnit}, ${subtitle ?? title}`} + > + + {avatar} + + + {title} + + {subtitleContent} + + + + {rowTitle} + + + + + + + ); +}; + +export const TransactionListItem = memo(TransactionListItemComponent); diff --git a/components/TransactionPendingIconBig.js b/components/TransactionPendingIconBig.js deleted file mode 100644 index 483051b6fe0..00000000000 --- a/components/TransactionPendingIconBig.js +++ /dev/null @@ -1,32 +0,0 @@ -/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */ -import { useTheme } from '@react-navigation/native'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import React from 'react'; - -export const TransactionPendingIconBig = props => { - const { colors } = useTheme(); - - const stylesBlueIconHooks = StyleSheet.create({ - ball: { - backgroundColor: colors.buttonBackgroundColor, - }, - ball2: { - width: 150, - height: 150, - borderRadius: 75, - }, - boxIncoming: { - position: 'relative', - }, - }); - return ( - - - - - - - - ); -}; diff --git a/components/TransactionPendingIconBig.tsx b/components/TransactionPendingIconBig.tsx new file mode 100644 index 00000000000..fcb43d60f9f --- /dev/null +++ b/components/TransactionPendingIconBig.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import Icon from './Icon'; + +import { useTheme } from './themes'; + +export const TransactionPendingIconBig: React.FC = () => { + const { colors } = useTheme(); + + const hookStyles = StyleSheet.create({ + ball: { + backgroundColor: colors.buttonBackgroundColor, + }, + }); + + return ( + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + boxIncoming: { + position: 'relative', + }, + ball: { + width: 150, + height: 150, + borderRadius: 75, + }, + iconStyle: { + left: 0, + top: 25, + }, +}); diff --git a/components/TransactionsNavigationHeader.tsx b/components/TransactionsNavigationHeader.tsx index 76bdc8e26a9..841fbfe6c54 100644 --- a/components/TransactionsNavigationHeader.tsx +++ b/components/TransactionsNavigationHeader.tsx @@ -1,99 +1,84 @@ -import React, { useState, useEffect, useRef, useContext, useCallback, useMemo } from 'react'; -import { Image, Text, TouchableOpacity, View, I18nManager, StyleSheet } from 'react-native'; +import React, { useCallback, useEffect, useMemo, useRef, 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 LinearGradient from 'react-native-linear-gradient'; -import { AbstractWallet, HDSegwitBech32Wallet, LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class'; -import { BitcoinUnit } from '../models/bitcoinUnits'; +import { LightningArkWallet } from '../class/wallets/lightning-ark-wallet'; +import { LightningCustodianWallet } from '../class/wallets/lightning-custodian-wallet'; +import { MultisigHDWallet } from '../class/wallets/multisig-hd-wallet'; import WalletGradient from '../class/wallet-gradient'; -import Biometric from '../class/biometrics'; -import loc, { formatBalance } from '../loc'; -import { BlueStorageContext } from '../blue_modules/storage-context'; +import { TWallet } from '../class/wallets/types'; +import loc, { formatBalance, formatBalanceWithoutSuffix } from '../loc'; +import { BitcoinUnit } from '../models/bitcoinUnits'; +import { FiatUnit } from '../models/fiatUnit'; +import { BlurredBalanceView } from './BlurredBalanceView'; +import { useSettings } from '../hooks/context/useSettings'; import ToolTipMenu from './TooltipMenu'; -import { BluePrivateBalance } from '../BlueComponents'; +import useAnimateOnChange from '../hooks/useAnimateOnChange'; +import { useLocale } from '@react-navigation/native'; interface TransactionsNavigationHeaderProps { - wallet: AbstractWallet; - onWalletUnitChange?: (wallet: any) => void; - navigation: { - navigate: (route: string, params?: any) => void; - goBack: () => void; - }; - onManageFundsPressed?: (id: string) => void; // Add a type definition for this prop - actionKeys: { - CopyToClipboard: 'copyToClipboard'; - WalletBalanceVisibility: 'walletBalanceVisibility'; - Refill: 'refill'; - RefillWithExternalWallet: 'qrcode'; - }; + wallet: TWallet; + unit: BitcoinUnit; + onWalletUnitChange: (unit: BitcoinUnit) => void; + onManageFundsPressed?: (id?: string) => void; + onWalletBalanceVisibilityChange?: (isShouldBeVisible: boolean) => void; + unitSwitching?: boolean; } const TransactionsNavigationHeader: React.FC = ({ - // @ts-ignore: Ugh - wallet: initialWallet, - // @ts-ignore: Ugh + wallet, onWalletUnitChange, - // @ts-ignore: Ugh - navigation, - // @ts-ignore: Ugh onManageFundsPressed, + onWalletBalanceVisibilityChange, + unit = BitcoinUnit.BTC, + unitSwitching = false, }) => { - const [wallet, setWallet] = useState(initialWallet); - const [allowOnchainAddress, setAllowOnchainAddress] = useState(false); - - const context = useContext(BlueStorageContext); - const menuRef = useRef(null); + const { hideBalance } = wallet; + 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(undefined); const verifyIfWalletAllowsOnchainAddress = useCallback(() => { - if (wallet.type === LightningCustodianWallet.type) { + if (isLightningWallet) { wallet .allowOnchainAddress() .then((value: boolean) => setAllowOnchainAddress(value)) - .catch((e: any) => { - console.log('This Lndhub wallet does not have an onchain address API.'); + .catch(() => { + console.error('This LNDhub wallet does not have an onchain address API.'); setAllowOnchainAddress(false); }); } - }, [wallet]); + }, [isLightningWallet, wallet]); + + useEffect(() => { + setAllowOnchainAddress(isLightningWallet); + }, [isLightningWallet]); useEffect(() => { verifyIfWalletAllowsOnchainAddress(); }, [wallet, verifyIfWalletAllowsOnchainAddress]); - const handleCopyPress = () => { - Clipboard.setString(formatBalance(wallet.getBalance(), wallet.getPreferredBalanceUnit()).toString()); - }; - - const updateWalletVisibility = (w: AbstractWallet, newHideBalance: boolean) => { - w.hideBalance = newHideBalance; - return w; - }; - - const handleBalanceVisibility = async () => { - // @ts-ignore: Gotta update this class - const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled(); - - if (isBiometricsEnabled && wallet.hideBalance) { - // @ts-ignore: Ugh - if (!(await Biometric.unlockWithBiometrics())) { - return navigation.goBack(); - } + const handleCopyPress = useCallback(() => { + const value = formatBalance(wallet.getBalance(), unit); + if (value) { + Clipboard.setString(value); } + }, [unit, wallet]); - const updatedWallet = updateWalletVisibility(wallet, !wallet.hideBalance); - setWallet(updatedWallet); - context.saveToDisk(); - }; - - const updateWalletWithNewUnit = (w: AbstractWallet, newPreferredUnit: BitcoinUnit) => { - w.preferredBalanceUnit = newPreferredUnit; - return w; - }; + const handleBalanceVisibility = useCallback(() => { + onWalletBalanceVisibilityChange?.(!hideBalance); + }, [onWalletBalanceVisibilityChange, hideBalance]); const changeWalletBalanceUnit = () => { - // @ts-ignore: Ugh - menuRef.current?.dismissMenu(); 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) { @@ -102,188 +87,209 @@ const TransactionsNavigationHeader: React.FC newWalletPreferredUnit = BitcoinUnit.BTC; } - const updatedWallet = updateWalletWithNewUnit(wallet, newWalletPreferredUnit); - setWallet(updatedWallet); - onWalletUnitChange?.(updatedWallet); + console.debug('[UnitSwitch/UI] next unit resolved', { walletID: wallet.getID?.(), next: newWalletPreferredUnit }); + onWalletUnitChange(newWalletPreferredUnit); }; - const handleManageFundsPressed = () => { - onManageFundsPressed?.(actionKeys.Refill); - }; + const handleManageFundsPressed = useCallback( + (actionKeyID?: string) => { + if (onManageFundsPressed) { + onManageFundsPressed(actionKeyID); + } + }, + [onManageFundsPressed], + ); - const handleOnPaymentCodeButtonPressed = () => { - navigation.navigate('PaymentCodeRoot', { - screen: 'PaymentCode', - params: { paymentCode: (wallet as HDSegwitBech32Wallet).getBIP47PaymentCode() }, - }); - }; + const onPressMenuItem = useCallback( + (id: string) => { + if (id === 'walletBalanceVisibility') { + handleBalanceVisibility(); + } else if (id === 'copyToClipboard') { + handleCopyPress(); + } + }, + [handleBalanceVisibility, handleCopyPress], + ); + + const toolTipActions = useMemo(() => { + return [ + { + id: actionKeys.Refill, + text: loc.lnd.refill, + icon: actionIcons.Refill, + }, + { + id: actionKeys.RefillWithExternalWallet, + text: loc.lnd.refill_external, + icon: actionIcons.RefillWithExternalWallet, + }, + ]; + }, []); - const onPressMenuItem = (id: string) => { - if (id === 'walletBalanceVisibility') { - handleBalanceVisibility(); - } else if (id === 'copyToClipboard') { - handleCopyPress(); + const currentBalance = wallet ? wallet.getBalance() : 0; + const formattedBalance = useMemo(() => { + return unit === BitcoinUnit.LOCAL_CURRENCY + ? formatBalance(currentBalance, unit, true) + : formatBalanceWithoutSuffix(currentBalance, unit, true); + }, [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; } - }; - const balance = useMemo(() => { - const hideBalance = wallet.hideBalance; - const balanceUnit = wallet.getPreferredBalanceUnit(); - const balanceFormatted = formatBalance(wallet.getBalance(), balanceUnit, true).toString(); - return !hideBalance && balanceFormatted; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [wallet.hideBalance, wallet.getPreferredBalanceUnit()]); + 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', + text: loc.transactions.details_balance_show, + icon: { + iconValue: 'eye', + }, + }, + ] + : [ + { + id: 'walletBalanceVisibility', + text: loc.transactions.details_balance_hide, + icon: { + iconValue: 'eye.slash', + }, + }, + { + id: 'copyToClipboard', + text: loc.transactions.details_copy, + icon: { + iconValue: 'doc.on.doc', + }, + }, + ]; + }, [hideBalance]); + + useEffect(() => { + console.debug('[UnitSwitch/UI] render state', { + walletID: wallet.getID?.(), + unit, + hideBalance, + preferredFiat: preferredFiatCurrency?.endPointKey, + switching: unitSwitching, + }); + }, [wallet, unit, hideBalance, preferredFiatCurrency, unitSwitching]); return ( - - { - switch (wallet.type) { - case LightningLdkWallet.type: - case LightningCustodianWallet.type: - return I18nManager.isRTL ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png'); - case MultisigHDWallet.type: - return I18nManager.isRTL ? require('../img/vault-shape-rtl.png') : require('../img/vault-shape.png'); - default: - return I18nManager.isRTL ? require('../img/btc-shape-rtl.png') : require('../img/btc-shape.png'); - } - })()} - style={styles.chainIcon} - /> - - {wallet.getLabel()} - - - - {wallet.hideBalance ? ( - - ) : ( - + + + {wallet.getLabel()} + + + + - {balance} - + + {hideBalance ? ( + + ) : ( + + + {balance} + + + )} + + + + + {unit === BitcoinUnit.LOCAL_CURRENCY ? (preferredFiatCurrency?.endPointKey ?? FiatUnit.USD) : unit} + + + + {(wallet.type === LightningCustodianWallet.type || wallet.type === LightningArkWallet.type) && allowOnchainAddress && ( + + + {loc.lnd.title} + + )} - - {wallet.type === LightningCustodianWallet.type && allowOnchainAddress && ( - - {loc.lnd.title} - - )} - - {wallet.allowBIP47() && wallet.isBIP47Enabled() && ( - - - {loc.bip47.payment_code} - - - )} - {wallet.type === LightningLdkWallet.type && ( - - - {loc.lnd.title} - - - )} - {wallet.type === MultisigHDWallet.type && ( - - + {wallet.type === MultisigHDWallet.type && ( + handleManageFundsPressed()}> {loc.multisig.manage_keys} - - - )} + + )} + ); }; const styles = StyleSheet.create({ lineaderGradient: { - padding: 15, minHeight: 140, - justifyContent: 'center', + justifyContent: 'flex-start', + }, + contentContainer: { + padding: 15, }, - chainIcon: { - width: 99, - height: 94, - position: 'absolute', - bottom: 0, - right: 0, + balanceSection: { + flexDirection: 'column', + alignItems: 'flex-start', }, walletLabel: { backgroundColor: 'transparent', fontSize: 19, color: '#fff', - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + marginBottom: 10, }, walletBalance: { - backgroundColor: 'transparent', - fontWeight: 'bold', - fontSize: 36, - color: '#fff', - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + flexShrink: 1, + marginRight: 6, }, manageFundsButton: { marginTop: 14, @@ -295,40 +301,62 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', }, + manageFundsSection: { + width: '100%', + alignItems: 'flex-start', + }, manageFundsButtonText: { fontWeight: '500', fontSize: 14, color: '#FFFFFF', padding: 12, }, + walletBalanceAndUnitContainer: { + flexDirection: 'row', + alignItems: 'center', + paddingRight: 10, // Ensure there's some padding to the right + }, + walletBalanceText: { + color: '#fff', + fontWeight: 'bold', + fontSize: 36, + flexShrink: 1, // Allow the text to shrink if there's not enough space + }, + walletPreferredUnitView: { + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.25)', + borderRadius: 8, + minHeight: 35, + minWidth: 65, + }, + walletPreferredUnitText: { + color: '#fff', + fontWeight: '600', + }, }); export const actionKeys = { CopyToClipboard: 'copyToClipboard', WalletBalanceVisibility: 'walletBalanceVisibility', Refill: 'refill', - RefillWithExternalWallet: 'qrcode', + RefillWithExternalWallet: 'refillWithExternalWallet', }; export const actionIcons = { Eye: { - iconType: 'SYSTEM', iconValue: 'eye', }, EyeSlash: { - iconType: 'SYSTEM', iconValue: 'eye.slash', }, Clipboard: { - iconType: 'SYSTEM', iconValue: 'doc.on.doc', }, Refill: { - iconType: 'SYSTEM', iconValue: 'goforward.plus', }, RefillWithExternalWallet: { - iconType: 'SYSTEM', iconValue: 'qrcode', }, }; diff --git a/components/WalletButton.tsx b/components/WalletButton.tsx new file mode 100644 index 00000000000..fa4917eb883 --- /dev/null +++ b/components/WalletButton.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import { ColorValue, DimensionValue, Image, ImageSourcePropType, StyleSheet, Text, Pressable, View } from 'react-native'; +import { useLocale } from '@react-navigation/native'; + +import loc from '../loc'; +import { Theme, useTheme } from './themes'; + +interface ButtonDetails { + image: ImageSourcePropType; + title: string; + explain: string; + borderColorActive: keyof Theme['colors']; +} + +interface WalletButtonProps { + buttonType: keyof typeof buttonDetails; + testID?: string; + onPress: () => void; + size: { + width: DimensionValue | undefined; + height: DimensionValue | undefined; + }; + active: boolean; +} + +const buttonDetails: Record = { + Bitcoin: { + image: require('../img/addWallet/bitcoin.png'), + title: loc.wallets.add_bitcoin, + explain: loc.wallets.add_bitcoin_explain, + borderColorActive: 'newBlue', + }, + Vault: { + image: require('../img/addWallet/vault.png'), + title: loc.multisig.multisig_vault, + explain: loc.multisig.multisig_vault_explain, + borderColorActive: 'foregroundColor', + }, + Lightning: { + image: require('../img/addWallet/lightning.png'), + title: loc.wallets.add_lightning, + explain: loc.wallets.add_lightning_explain, + borderColorActive: 'lnborderColor', + }, + LightningArk: { + image: require('../img/addWallet/lightning.png'), + title: loc.wallets.add_lightning, + explain: loc.wallets.add_lightning_explain + '\nPowered by Arkade', + borderColorActive: 'lnborderColor', + }, +}; + +const WalletButton: React.FC = ({ buttonType, testID, onPress, size, active }) => { + const details = buttonDetails[buttonType]; + const { colors } = useTheme(); + const { direction } = useLocale(); + const borderColor = active ? colors[details.borderColorActive] : colors.buttonDisabledBackgroundColor; + const stylesHook = StyleSheet.create({ + buttonContainer: { + borderColor: borderColor as ColorValue, + backgroundColor: colors.buttonDisabledBackgroundColor, + minWidth: size.width, + minHeight: size.height, + height: size.height, + }, + textTitle: { + color: colors[details.borderColorActive] as ColorValue, + fontWeight: 'bold', + fontSize: 18, + writingDirection: direction, + }, + textExplain: { + color: colors.alternativeTextColor, + fontSize: 13, + fontWeight: '500', + writingDirection: direction, + }, + }); + + return ( + [pressed && styles.pressed, styles.touchable]} + > + + + + + {details.title} + {details.explain} + + + + + ); +}; + +const styles = StyleSheet.create({ + touchable: { + flex: 1, + marginBottom: 8, + }, + container: { + borderWidth: 1.5, + borderRadius: 8, + }, + content: { + marginHorizontal: 16, + marginVertical: 10, + flexDirection: 'row', + alignItems: 'center', + }, + image: { + width: 34, + height: 34, + marginRight: 8, + }, + textContainer: { + flex: 1, + }, + pressed: { + opacity: 0.6, + }, +}); + +export default WalletButton; diff --git a/components/WalletListItem.tsx b/components/WalletListItem.tsx new file mode 100644 index 00000000000..9da5b53be56 --- /dev/null +++ b/components/WalletListItem.tsx @@ -0,0 +1,180 @@ +import React, { useMemo } from 'react'; +import { ImageBackground, ImageSourcePropType, StyleSheet, Text, View, ViewStyle, TextStyle, Pressable } from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import { useLocale } from '@react-navigation/native'; +import { useTheme } from './themes'; +import HighlightedText from './HighlightedText'; +import { TWallet } from '../class/wallets/types'; +import WalletGradient from '../class/wallet-gradient'; +import { formatBalance } from '../loc'; + +type Props = { + wallet: TWallet; + iconImage: ImageSourcePropType; + onPress: () => void; + searchQuery: string; + borderBottomColor: string; + backgroundColor: string; + titleColor: string; + onLongPress?: () => void; + delayLongPress?: number; + onPressIn?: () => void; + onPressOut?: () => void; + isActive?: boolean; + containerStyle?: ViewStyle; + balanceColor?: string; +}; + +const WalletListItem: React.FC = ({ + wallet, + iconImage, + onPress, + onLongPress, + delayLongPress = 120, + onPressIn, + onPressOut, + searchQuery, + isActive, + containerStyle, + borderBottomColor, + backgroundColor, + titleColor, + balanceColor, +}) => { + const { colors, dark } = useTheme(); + const { direction } = useLocale(); + + const walletLabel = wallet.getLabel(); + const gradientColors = WalletGradient.gradientsFor(wallet.type); + + const resolvedTitleColor = titleColor ?? (dark ? colors.foregroundColor : colors.darkGray); + const resolvedBalanceColor = balanceColor ?? colors.alternativeTextColor; + const resolvedBorderBottomColor = borderBottomColor ?? colors.lightBorder; + const resolvedBackgroundColor = backgroundColor ?? colors.background; + + const titleTextStyle = useMemo( + () => [styles.listItemLabel, { color: resolvedTitleColor, writingDirection: direction }] as TextStyle[], + [direction, resolvedTitleColor], + ); + const balanceTextStyle = useMemo( + () => [styles.listItemBalance, { color: resolvedBalanceColor, writingDirection: direction }] as TextStyle[], + [direction, resolvedBalanceColor], + ); + + const balance = useMemo(() => { + if (wallet.hideBalance) return ''; + return formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true); + }, [wallet]); + + const highlightStyle = useMemo(() => { + if (dark) { + return StyleSheet.flatten([styles.highlightDark, { color: resolvedTitleColor }]); + } + + // On light backgrounds, HighlightedText's default white-ish border can be hard to see. + return StyleSheet.flatten([styles.highlightLight, { color: resolvedTitleColor }]); + }, [dark, resolvedTitleColor]); + + return ( + [ + styles.listItem, + { + backgroundColor: isActive ? colors.lightButton : resolvedBackgroundColor, + borderBottomColor: resolvedBorderBottomColor, + }, + pressed && styles.pressed, + containerStyle, + ]} + onPress={onPress} + onLongPress={onLongPress} + delayLongPress={onLongPress ? delayLongPress : undefined} + onPressIn={onPressIn} + onPressOut={onPressOut} + accessibilityRole="button" + testID={walletLabel} + > + + + + + {searchQuery ? ( + + ) : ( + + {walletLabel} + + )} + + {wallet.hideBalance ? ( + + + + ) : ( + + {balance} + + )} + + + ); +}; + +const styles = StyleSheet.create({ + listItem: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 12, + paddingHorizontal: 16, + borderBottomWidth: StyleSheet.hairlineWidth, + }, + iconBox: { + width: 46, + height: 46, + borderRadius: 10, + justifyContent: 'center', + alignItems: 'center', + overflow: 'hidden', + }, + iconImage: { + width: 46, + height: 46, + resizeMode: 'center', + }, + listItemContent: { + flex: 1, + marginLeft: 14, + justifyContent: 'center', + }, + listItemLabel: { + fontSize: 17, + fontWeight: '600', + marginBottom: 2, + }, + listItemBalance: { + fontSize: 14, + }, + pressed: { + opacity: 0.85, + }, + hiddenBalance: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 2, + }, + hiddenBalanceBar: { + backgroundColor: 'rgba(150, 150, 150, 0.25)', + height: 10, + width: 66, + borderRadius: 5, + }, + highlightDark: { + backgroundColor: 'rgba(255, 245, 192, 0.22)', + borderColor: 'rgba(255, 255, 255, 0.25)', + }, + highlightLight: { + borderColor: 'rgba(0, 0, 0, 0.12)', + }, +}); + +export default WalletListItem; diff --git a/components/WalletToImport.js b/components/WalletToImport.js deleted file mode 100644 index bfd14dfc538..00000000000 --- a/components/WalletToImport.js +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Text } from 'react-native-elements'; -import { I18nManager, StyleSheet, TouchableOpacity, View } from 'react-native'; -import { useTheme } from '@react-navigation/native'; - -const WalletToImport = ({ title, subtitle, active, onPress }) => { - const { colors } = useTheme(); - - const stylesHooks = StyleSheet.create({ - root: { - borderColor: active ? colors.newBlue : colors.buttonDisabledBackgroundColor, - backgroundColor: colors.buttonDisabledBackgroundColor, - }, - title: { - color: colors.newBlue, - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', - }, - subtitle: { - color: colors.alternativeTextColor, - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', - }, - }); - - return ( - - - {title} - {subtitle} - - - ); -}; - -const styles = StyleSheet.create({ - root: { - alignItems: 'stretch', - borderRadius: 8, - borderWidth: 1.5, - flexDirection: 'column', - justifyContent: 'center', - marginBottom: 8, - minWidth: '100%', - paddingHorizontal: 16, - paddingVertical: 10, - }, - title: { - fontWeight: 'bold', - fontSize: 15, - paddingBottom: 3, - }, - subtitle: { - fontSize: 13, - fontWeight: '500', - }, -}); - -WalletToImport.propTypes = { - title: PropTypes.string, - subtitle: PropTypes.string, - active: PropTypes.bool, - onPress: PropTypes.func, -}; - -export default WalletToImport; diff --git a/components/WalletToImport.tsx b/components/WalletToImport.tsx new file mode 100644 index 00000000000..becce011711 --- /dev/null +++ b/components/WalletToImport.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { useLocale } from '@react-navigation/native'; + +import { useTheme } from './themes'; + +interface WalletToImportProp { + title: string; + subtitle: string; + active: boolean; + onPress: () => void; +} + +const WalletToImport: React.FC = ({ title, subtitle, active, onPress }) => { + const { colors } = useTheme(); + const { direction } = useLocale(); + + const stylesHooks = StyleSheet.create({ + root: { + borderColor: active ? colors.newBlue : colors.buttonDisabledBackgroundColor, + backgroundColor: colors.buttonDisabledBackgroundColor, + }, + title: { + color: colors.newBlue, + writingDirection: direction, + }, + subtitle: { + color: colors.alternativeTextColor, + writingDirection: direction, + }, + }); + + return ( + + + {title} + {subtitle} + + + ); +}; + +const styles = StyleSheet.create({ + root: { + alignItems: 'stretch', + borderRadius: 8, + borderWidth: 1.5, + flexDirection: 'column', + justifyContent: 'center', + marginBottom: 8, + minWidth: '100%', + paddingHorizontal: 16, + paddingVertical: 10, + }, + title: { + fontWeight: 'bold', + fontSize: 15, + paddingBottom: 3, + }, + subtitle: { + fontSize: 13, + fontWeight: '500', + }, +}); + +export default WalletToImport; diff --git a/components/WalletsCarousel.js b/components/WalletsCarousel.js deleted file mode 100644 index 65afac571f7..00000000000 --- a/components/WalletsCarousel.js +++ /dev/null @@ -1,357 +0,0 @@ -import React, { useRef, useCallback, useImperativeHandle, forwardRef, useContext } from 'react'; -import PropTypes from 'prop-types'; -import { - Animated, - Image, - I18nManager, - Platform, - StyleSheet, - Text, - TouchableOpacity, - useWindowDimensions, - View, - Dimensions, - FlatList, - Pressable, -} from 'react-native'; -import { useTheme } from '@react-navigation/native'; -import LinearGradient from 'react-native-linear-gradient'; -import loc, { formatBalance, transactionTimeToReadable } from '../loc'; -import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class'; -import WalletGradient from '../class/wallet-gradient'; -import { BluePrivateBalance } from '../BlueComponents'; -import { BlueStorageContext } from '../blue_modules/storage-context'; -import { isHandset, isTablet, isDesktop } from '../blue_modules/environment'; - -const nStyles = StyleSheet.create({ - container: { - borderRadius: 10, - minHeight: Platform.OS === 'ios' ? 164 : 181, - justifyContent: 'center', - alignItems: 'flex-start', - }, - addAWAllet: { - fontWeight: '600', - fontSize: 24, - marginBottom: 4, - }, - addLine: { - fontSize: 13, - }, - button: { - marginTop: 12, - backgroundColor: '#007AFF', - paddingHorizontal: 32, - paddingVertical: 12, - borderRadius: 8, - }, - buttonText: { - fontWeight: '500', - }, -}); - -const NewWalletPanel = ({ onPress }) => { - const { colors } = useTheme(); - const { width } = useWindowDimensions(); - const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; - const isLargeScreen = Platform.OS === 'android' ? isTablet() : (width >= Dimensions.get('screen').width / 2 && isTablet()) || isDesktop; - const nStylesHooks = StyleSheet.create({ - container: isLargeScreen - ? { - paddingHorizontal: 24, - marginVertical: 16, - } - : { paddingVertical: 16, paddingHorizontal: 24 }, - }); - - return ( - - - {loc.wallets.list_create_a_wallet} - {loc.wallets.list_create_a_wallet_text} - - {loc.wallets.list_create_a_button} - - - - ); -}; - -NewWalletPanel.propTypes = { - onPress: PropTypes.func.isRequired, -}; - -const iStyles = StyleSheet.create({ - root: { paddingRight: 20 }, - rootLargeDevice: { marginVertical: 20 }, - grad: { - padding: 15, - borderRadius: 12, - minHeight: 164, - elevation: 5, - }, - image: { - width: 99, - height: 94, - position: 'absolute', - bottom: 0, - right: 0, - }, - br: { - backgroundColor: 'transparent', - }, - label: { - backgroundColor: 'transparent', - fontSize: 19, - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', - }, - balance: { - backgroundColor: 'transparent', - fontWeight: 'bold', - fontSize: 36, - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', - }, - latestTx: { - backgroundColor: 'transparent', - fontSize: 13, - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', - }, - latestTxTime: { - backgroundColor: 'transparent', - fontWeight: 'bold', - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', - fontSize: 16, - }, -}); - -const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedWallet }) => { - const scaleValue = new Animated.Value(1.0); - const { colors } = useTheme(); - const { walletTransactionUpdateStatus } = useContext(BlueStorageContext); - const { width } = useWindowDimensions(); - const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; - const isLargeScreen = Platform.OS === 'android' ? isTablet() : (width >= Dimensions.get('screen').width / 2 && isTablet()) || isDesktop; - const onPressedIn = () => { - const props = { duration: 50 }; - props.useNativeDriver = true; - props.toValue = 0.9; - Animated.spring(scaleValue, props).start(); - }; - - const onPressedOut = () => { - const props = { duration: 50 }; - props.useNativeDriver = true; - props.toValue = 1.0; - Animated.spring(scaleValue, props).start(); - }; - - const opacity = isSelectedWallet === false ? 0.5 : 1.0; - let image; - switch (item.type) { - case LightningLdkWallet.type: - case LightningCustodianWallet.type: - image = I18nManager.isRTL ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png'); - break; - case MultisigHDWallet.type: - image = I18nManager.isRTL ? require('../img/vault-shape-rtl.png') : require('../img/vault-shape.png'); - break; - default: - image = I18nManager.isRTL ? require('../img/btc-shape-rtl.png') : require('../img/btc-shape.png'); - } - - const latestTransactionText = - walletTransactionUpdateStatus === true || walletTransactionUpdateStatus === item.getID() - ? loc.transactions.updating - : item.getBalance() !== 0 && item.getLatestTransactionTime() === 0 - ? loc.wallets.pull_to_refresh - : item.getTransactions().find(tx => tx.confirmations === 0) - ? loc.transactions.pending - : transactionTimeToReadable(item.getLatestTransactionTime()); - - const balance = !item.hideBalance && formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true); - - return ( - - { - onPressedOut(); - onPress(item); - onPressedOut(); - }} - > - - - - - {item.getLabel()} - - {item.hideBalance ? ( - - ) : ( - - {balance} - - )} - - - {loc.wallets.list_latest_transaction} - - - - {latestTransactionText} - - - - - ); -}; - -WalletCarouselItem.propTypes = { - item: PropTypes.any, - index: PropTypes.number.isRequired, - onPress: PropTypes.func.isRequired, - handleLongPress: PropTypes.func.isRequired, - isSelectedWallet: PropTypes.bool, -}; - -const cStyles = StyleSheet.create({ - content: { - paddingTop: 16, - }, - contentLargeScreen: { - paddingHorizontal: 16, - }, - separatorStyle: { - width: 16, - height: 20, - }, -}); - -const ListHeaderComponent = () => ; - -const WalletsCarousel = forwardRef((props, ref) => { - const { preferredFiatCurrency, language } = useContext(BlueStorageContext); - const renderItem = useCallback( - ({ item, index }) => - item ? ( - - ) : ( - - ), - // eslint-disable-next-line react-hooks/exhaustive-deps - [props.horizontal, props.selectedWallet, props.handleLongPress, props.onPress, preferredFiatCurrency, language], - ); - const flatListRef = useRef(); - - useImperativeHandle(ref, () => ({ - scrollToItem: ({ item }) => { - setTimeout(() => { - flatListRef?.current?.scrollToItem({ item, viewOffset: 16 }); - }, 300); - }, - scrollToIndex: ({ index }) => { - setTimeout(() => { - flatListRef?.current?.scrollToIndex({ index, viewOffset: 16 }); - }, 300); - }, - })); - - const onScrollToIndexFailed = error => { - console.log('onScrollToIndexFailed'); - console.log(error); - flatListRef.current.scrollToOffset({ offset: error.averageItemLength * error.index, animated: true }); - setTimeout(() => { - if (props.data.length !== 0 && flatListRef.current !== null) { - flatListRef.current.scrollToIndex({ index: error.index, animated: true }); - } - }, 100); - }; - - const { width } = useWindowDimensions(); - const sliderHeight = 195; - const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; - return isHandset ? ( - index.toString()} - showsVerticalScrollIndicator={false} - pagingEnabled - disableIntervalMomentum={isHandset} - snapToInterval={itemWidth} // Adjust to your content width - decelerationRate="fast" - contentContainerStyle={props.horizontal ? cStyles.content : cStyles.contentLargeScreen} - directionalLockEnabled - showsHorizontalScrollIndicator={false} - initialNumToRender={10} - ListHeaderComponent={ListHeaderComponent} - style={props.horizontal ? { minHeight: sliderHeight + 9 } : {}} - onScrollToIndexFailed={onScrollToIndexFailed} - {...props} - /> - ) : ( - - {props.data.map((item, index) => - item ? ( - - ) : ( - - ), - )} - - ); -}); - -WalletsCarousel.propTypes = { - horizontal: PropTypes.bool, - selectedWallet: PropTypes.string, - onPress: PropTypes.func.isRequired, - handleLongPress: PropTypes.func.isRequired, - data: PropTypes.array, -}; - -export default WalletsCarousel; diff --git a/components/WalletsCarousel.tsx b/components/WalletsCarousel.tsx new file mode 100644 index 00000000000..df00acb2b11 --- /dev/null +++ b/components/WalletsCarousel.tsx @@ -0,0 +1,898 @@ +import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useEffect } from 'react'; +import { + FlatList, + ImageBackground, + Platform, + Pressable, + StyleSheet, + Text, + View, + useWindowDimensions, + FlatListProps, + ListRenderItemInfo, + ViewStyle, +} from 'react-native'; +import Animated, { + Easing, + FadeIn, + FadeOut, + LinearTransition, + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, +} from 'react-native-reanimated'; +import LinearGradient from 'react-native-linear-gradient'; +import { LightningArkWallet } from '../class/wallets/lightning-ark-wallet'; +import { LightningCustodianWallet } from '../class/wallets/lightning-custodian-wallet'; +import { MultisigHDWallet } from '../class/wallets/multisig-hd-wallet'; +import WalletGradient from '../class/wallet-gradient'; +import { useSizeClass, SizeClass } from '../blue_modules/sizeClass'; +import loc, { formatBalance, transactionTimeToReadable } from '../loc'; +import { BlurredBalanceView } from './BlurredBalanceView'; +import { useTheme } from './themes'; +import { Transaction, TWallet } from '../class/wallets/types'; +import { BlueSpacing10 } from './BlueSpacing'; +import { useLocale } from '@react-navigation/native'; + +export const WALLET_CAROUSEL_HEADER_WIDTH = 16; + +export const getWalletCarouselItemWidth = (screenWidth: number) => Math.round(screenWidth * 0.82 > 375 ? 375 : screenWidth * 0.82); + +interface NewWalletPanelProps { + onPress: () => void; +} + +const nStyles = StyleSheet.create({ + container: { + borderRadius: 10, + minHeight: Platform.OS === 'ios' ? 164 : 181, + justifyContent: 'center', + alignItems: 'flex-start', + }, + addAWAllet: { + fontWeight: '600', + fontSize: 24, + marginBottom: 4, + }, + addLine: { + fontSize: 13, + }, + button: { + marginTop: 12, + backgroundColor: '#007AFF', + paddingHorizontal: 32, + paddingVertical: 12, + borderRadius: 8, + }, + buttonText: { + fontWeight: '500', + }, +}); + +const NewWalletPanel: React.FC = ({ onPress }) => { + const { colors } = useTheme(); + const { width } = useWindowDimensions(); + const itemWidth = getWalletCarouselItemWidth(width); + const { isLarge } = useSizeClass(); + const nStylesHooks = StyleSheet.create({ + container: isLarge + ? { + paddingHorizontal: 24, + marginVertical: 16, + } + : { paddingVertical: 16, paddingHorizontal: 24 }, + }); + + const scale = useSharedValue(1); + + const animatedScaleStyle = useAnimatedStyle(() => ({ + transform: [{ scale: scale.value }], + })); + + const handlePressIn = useCallback(() => { + scale.value = withSpring(0.97, { damping: 14, stiffness: 180 }); + }, [scale]); + + const handlePressOut = useCallback(() => { + scale.value = withSpring(1, { damping: 14, stiffness: 180 }); + }, [scale]); + + return ( + [ + isLarge ? {} : { width: itemWidth * 1.2 }, + { + opacity: pressed ? 0.9 : 1.0, + }, + ]} + accessibilityRole="button" + accessibilityLabel={loc.wallets.list_create_a_wallet} + > + + {loc.wallets.list_create_a_wallet} + {loc.wallets.list_create_a_wallet_text} + + {loc.wallets.list_create_a_button} + + + + ); +}; + +interface WalletCarouselItemProps { + item: TWallet; + hideBalance: boolean; + onPress: (item: TWallet) => void; + handleLongPress?: () => void; + isSelectedWallet?: boolean; + customStyle?: ViewStyle; + horizontal?: boolean; + isPlaceHolder?: boolean; + searchQuery?: string; + renderHighlightedText?: (text: string, query: string) => React.ReactElement; + animationsEnabled?: boolean; + onPressIn?: () => void; + onPressOut?: () => void; + isNewWallet?: boolean; + isExiting?: boolean; + isDraggingActive?: boolean; + dragActiveScale?: number; + sizeVariant?: 'default' | 'compact'; +} + +const iStyles = StyleSheet.create({ + root: { paddingRight: 20 }, + rootLargeDevice: { marginVertical: 20 }, + grad: { + borderRadius: 12, + minHeight: 164, + overflow: 'hidden', + }, + gradCompact: { + borderRadius: 10, + minHeight: 132, + overflow: 'hidden', + }, + gradContent: { + padding: 15, + }, + gradContentCompact: { + padding: 12, + }, + balanceContainer: { + height: 40, + }, + balanceContainerCompact: { + height: 32, + }, + image: { + width: 99, + height: 94, + position: 'absolute', + bottom: 0, + right: 0, + }, + imageCompact: { + width: 78, + height: 74, + }, + br: { + backgroundColor: 'transparent', + }, + label: { + backgroundColor: 'transparent', + fontSize: 19, + }, + labelCompact: { + fontSize: 16, + }, + balance: { + backgroundColor: 'transparent', + fontWeight: 'bold', + fontSize: 36, + }, + balanceCompact: { + fontSize: 28, + lineHeight: 34, + }, + latestTx: { + backgroundColor: 'transparent', + fontSize: 13, + }, + latestTxCompact: { + fontSize: 12, + }, + latestTxTime: { + backgroundColor: 'transparent', + fontWeight: 'bold', + fontSize: 16, + }, + latestTxTimeCompact: { + fontSize: 14, + }, + shadowContainer: { + ...Platform.select({ + ios: { + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 25 / 100, + shadowRadius: 8, + borderRadius: 12, + }, + android: { + elevation: 8, + borderRadius: 12, + }, + }), + }, + shadowContainerCompact: { + ...Platform.select({ + ios: { + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 20 / 100, + shadowRadius: 6, + borderRadius: 10, + }, + android: { + elevation: 6, + borderRadius: 10, + }, + }), + }, +}); + +export const WalletCarouselItem: React.FC = React.memo( + ({ + item, + hideBalance, + onPress, + handleLongPress, + isSelectedWallet, + customStyle, + horizontal, + searchQuery, + renderHighlightedText, + animationsEnabled = true, + isPlaceHolder = false, + onPressIn, + onPressOut, + isNewWallet = false, + isExiting = false, + isDraggingActive = false, + dragActiveScale = 1.02, + sizeVariant = 'default', + }: WalletCarouselItemProps) => { + const walletLabel = item.getLabel ? item.getLabel() : ''; + const pressScale = useSharedValue(1.0); + const dragScale = useSharedValue(isDraggingActive ? dragActiveScale : 1.0); + const opacityValue = useSharedValue(isSelectedWallet === false ? 0.5 : 1.0); + const translateYValue = useSharedValue(isNewWallet ? 20 : 0); + const balanceOpacity = useSharedValue(1); + const balanceTranslateY = useSharedValue(0); + const { colors } = useTheme(); + const { width } = useWindowDimensions(); + const itemWidth = getWalletCarouselItemWidth(width); + const { sizeClass } = useSizeClass(); + const isCompact = sizeVariant === 'compact'; + const { direction } = useLocale(); + const previousBalance = useRef(undefined); + const balance = !hideBalance && formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true); + const safeBalance = balance || undefined; + + const animatePressScale = useCallback( + (toValue: number) => { + pressScale.value = withSpring(toValue, { damping: 13, stiffness: 180, mass: 0.9 }); + }, + [pressScale], + ); + + useEffect(() => { + dragScale.value = withSpring(isDraggingActive ? dragActiveScale : 1, { damping: 16, stiffness: 200, mass: 1 }); + }, [isDraggingActive, dragActiveScale, dragScale]); + + useEffect(() => { + if (!animationsEnabled) return; + + const targetOpacity = isSelectedWallet === false ? 0.5 : 1.0; + opacityValue.value = withSpring(targetOpacity, { damping: 18, stiffness: 240 }); + }, [isSelectedWallet, opacityValue, animationsEnabled]); + + const onPressedIn = useCallback(() => { + if (animationsEnabled) { + animatePressScale(0.97); + } + if (onPressIn) onPressIn(); + }, [animatePressScale, animationsEnabled, onPressIn]); + + const onPressedOut = useCallback(() => { + if (animationsEnabled) { + animatePressScale(1.0); + } + if (onPressOut) onPressOut(); + }, [animatePressScale, animationsEnabled, onPressOut]); + + const handlePress = useCallback(() => { + onPress(item); + }, [item, onPress]); + + useEffect(() => { + if (isNewWallet && animationsEnabled) { + translateYValue.value = withTiming(0, { duration: 300 }); + opacityValue.value = withSpring(isSelectedWallet === false ? 0.5 : 1.0, { damping: 18, stiffness: 240 }); + } + }, [isNewWallet, animationsEnabled, translateYValue, opacityValue, isSelectedWallet]); + + useEffect(() => { + if (!animationsEnabled) { + previousBalance.current = safeBalance; + return; + } + + if (previousBalance.current !== undefined && previousBalance.current !== safeBalance) { + // Subtle currency-like transition on balance updates. + balanceOpacity.value = 0; + balanceTranslateY.value = 6; + balanceOpacity.value = withTiming(1, { duration: 180 }); + balanceTranslateY.value = withSpring(0, { damping: 16, stiffness: 220 }); + } + + previousBalance.current = safeBalance; + }, [safeBalance, animationsEnabled, balanceOpacity, balanceTranslateY]); + + useEffect(() => { + if (isExiting && animationsEnabled) { + translateYValue.value = withTiming(-20, { duration: 200 }); + opacityValue.value = withTiming(0, { duration: 200 }); + } + }, [isExiting, animationsEnabled, translateYValue, opacityValue]); + + const animatedCardStyle = useAnimatedStyle(() => ({ + opacity: opacityValue.value, + transform: [{ scale: pressScale.value * dragScale.value }, { translateY: translateYValue.value }], + })); + + const animatedBalanceStyle = useAnimatedStyle(() => ({ + opacity: balanceOpacity.value, + transform: [{ translateY: balanceTranslateY.value }], + })); + + let image; + switch (item.type) { + case LightningCustodianWallet.type: + case LightningArkWallet.type: + image = direction === 'rtl' ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png'); + break; + case MultisigHDWallet.type: + image = direction === 'rtl' ? require('../img/vault-shape-rtl.png') : require('../img/vault-shape.png'); + break; + default: + image = direction === 'rtl' ? require('../img/btc-shape-rtl.png') : require('../img/btc-shape.png'); + } + + let latestTransactionText; + + if (item.getBalance() !== 0 && item.getLatestTransactionTime() === 0) { + latestTransactionText = loc.wallets.pull_to_refresh; + } else if (item.getTransactions().find((tx: Transaction) => tx.confirmations === 0)) { + latestTransactionText = loc.transactions.pending; + } else { + latestTransactionText = transactionTimeToReadable(item.getLatestTransactionTime()); + } + + return ( + + { + if (handleLongPress) handleLongPress(); + }} + onPress={handlePress} + delayHoverIn={0} + delayHoverOut={0} + > + + + + + + {!isPlaceHolder && ( + <> + + {renderHighlightedText ? renderHighlightedText(walletLabel, searchQuery || '') : walletLabel} + + + {hideBalance ? ( + <> + + + + ) : ( + + {`${balance} `} + + )} + + + + {loc.wallets.list_latest_transaction} + + + {latestTransactionText} + + + )} + + + + + + ); + }, +); + +interface WalletsCarouselProps extends Partial> { + horizontal?: boolean; + isFlatList?: boolean; + selectedWallet?: string; + onPress: (item: TWallet) => void; + onNewWalletPress?: () => void; + handleLongPress?: () => void; + data: TWallet[]; + scrollEnabled?: boolean; + searchQuery?: string; + renderHighlightedText?: (text: string, query: string) => React.ReactElement; + animateChanges?: boolean; +} + +type FlatListRefType = FlatList & { + scrollToEnd(params?: { animated?: boolean | null }): void; + scrollToIndex(params: { animated?: boolean | null; index: number; viewOffset?: number; viewPosition?: number }): void; + scrollToItem(params: { animated?: boolean | null; item: TWallet; viewPosition?: number }): void; + scrollToOffset(params: { animated?: boolean | null; offset: number }): void; + recordInteraction(): void; + flashScrollIndicators(): void; + getNativeScrollRef(): View; +}; + +const styles = StyleSheet.create({ + listHeaderSeparator: { + width: WALLET_CAROUSEL_HEADER_WIDTH, + height: 20, + }, +}); + +const ListHeaderSeparator = () => ; + +const WalletsCarousel = forwardRef((props, ref) => { + const { + horizontal = true, + data, + handleLongPress, + onPress, + selectedWallet, + scrollEnabled = true, + onNewWalletPress, + searchQuery, + renderHighlightedText, + isFlatList = true, + animateChanges = false, + } = props; + + const { width } = useWindowDimensions(); + const itemWidth = React.useMemo(() => getWalletCarouselItemWidth(width), [width]); + const snapInterval = React.useMemo(() => itemWidth, [itemWidth]); + const snapOffsets = React.useMemo(() => { + if (!horizontal) return undefined; + const cardsCount = data.length + (onNewWalletPress ? 1 : 0); + // Keep every card aligned with the first card's resting position. + return Array.from({ length: cardsCount }, (_, index) => index * snapInterval); + }, [horizontal, data.length, onNewWalletPress, snapInterval]); + const layoutTransition = useMemo(() => LinearTransition.duration(240).easing(Easing.inOut(Easing.quad)), []); + const enteringTransition = useMemo(() => FadeIn.duration(180), []); + const exitingTransition = useMemo(() => FadeOut.duration(150), []); + + const prevWalletIds = useRef([]); + const newWalletsMap = useRef>({}); + const lastAddedWalletId = useRef(null); + const hasFocusedRef = useRef(false); + const scrollTimeoutRef = useRef(null); + const isInitialMount = useRef(true); + + const flatListRef = useRef>(null); + const walletRefs = useRef>>({}); + + const { sizeClass } = useSizeClass(); + + useImperativeHandle(ref, (): any => { + if (isFlatList) { + return { + scrollToEnd: (params: { animated?: boolean | null | undefined } | undefined) => flatListRef.current?.scrollToEnd(params), + scrollToIndex: (params: { + animated?: boolean | null | undefined; + index: number; + viewOffset?: number | undefined; + viewPosition?: number | undefined; + }) => flatListRef.current?.scrollToIndex(params), + scrollToItem: (params: { + animated?: boolean | null | undefined; + item: any; + viewOffset?: number | undefined; + viewPosition?: number | undefined; + }) => flatListRef.current?.scrollToItem(params), + scrollToOffset: (params: { animated?: boolean | null | undefined; offset: number }) => flatListRef.current?.scrollToOffset(params), + recordInteraction: () => flatListRef.current?.recordInteraction(), + flashScrollIndicators: () => flatListRef.current?.flashScrollIndicators(), + getNativeScrollRef: () => flatListRef.current?.getNativeScrollRef(), + }; + } else { + // For non-FlatList mode, we'll return simpler methods to get/set information + // but not actually handle scrolling (leaving that to the parent drawer) + return { + scrollToEnd: () => console.debug('[WalletsCarousel] scrollToEnd not implemented for non-FlatList'), + scrollToIndex: () => console.debug('[WalletsCarousel] scrollToIndex not implemented for non-FlatList'), + scrollToItem: () => console.debug('[WalletsCarousel] scrollToItem not implemented for non-FlatList'), + scrollToOffset: () => console.debug('[WalletsCarousel] scrollToOffset not implemented for non-FlatList'), + recordInteraction: () => {}, + flashScrollIndicators: () => {}, + getNativeScrollRef: () => null, + // Add a method to get position information about a wallet + getWalletPosition: (walletId: string) => { + const walletRef = walletRefs.current[walletId]; + if (walletRef?.current) { + return new Promise<{ x: number; y: number; width: number; height: number }>(resolve => { + walletRef.current?.measure((x: number, y: number, widthVal: number, heightVal: number, pageX: number, pageY: number) => { + resolve({ x: pageX, y: pageY, width: widthVal, height: heightVal }); + }); + }); + } + return Promise.resolve(null); + }, + }; + } + }, [isFlatList]); + + useEffect(() => { + return () => { + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + } + }; + }, []); + + useEffect(() => { + data.forEach(wallet => { + if (!walletRefs.current[wallet.getID()]) { + walletRefs.current[wallet.getID()] = { current: null }; + } + }); + }, [data]); + + const scrollToWalletById = useCallback( + (walletId: string, animated = true) => { + if (!walletId) return; + + console.debug('[WalletsCarousel] Attempting to scroll to wallet:', walletId); + + if (isFlatList && flatListRef.current) { + const walletIndex = data.findIndex(wallet => wallet.getID() === walletId); + if (walletIndex !== -1) { + try { + console.debug('[WalletsCarousel] Found wallet at index:', walletIndex, 'horizontal:', horizontal); + flatListRef.current.scrollToIndex({ + index: walletIndex, + animated, + viewPosition: 0.5, // Center the wallet in the view + }); + } catch (error) { + 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 + flatListRef.current.scrollToOffset({ + offset: itemSize * walletIndex, + animated, + }); + } + } + } else if (!isFlatList) { + // For non-FlatList, just log the attempt + // The parent DrawerContentScrollView should handle this + const walletIndex = data.findIndex(wallet => wallet.getID() === walletId); + console.debug( + '[WalletsCarousel] Would scroll to wallet index:', + walletIndex, + 'but leaving scrolling to parent DrawerContentScrollView', + ); + } + }, + [data, isFlatList, itemWidth, horizontal], + ); + + useEffect(() => { + if (animateChanges) { + const currentWalletIds = data.map(wallet => wallet.getID()); + + // Skip auto-scrolling on initial mount + if (isInitialMount.current) { + isInitialMount.current = false; + prevWalletIds.current = currentWalletIds; + return; + } + + // Handle wallet additions + const addedWallets = currentWalletIds.filter(id => !prevWalletIds.current.includes(id)); + if (addedWallets.length > 0) { + // Track last added wallet for animations and scrolling + lastAddedWalletId.current = addedWallets[addedWallets.length - 1]; + + addedWallets.forEach(id => { + newWalletsMap.current[id] = true; + }); + + // Auto-scroll to new wallet after mount (no condition, always scroll) + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + } + + scrollTimeoutRef.current = setTimeout(() => { + // Add null check before calling scrollToWalletById + if (lastAddedWalletId.current !== null) { + scrollToWalletById(lastAddedWalletId.current, true); + } + }, 300); + } + + // Update refs for next comparison + prevWalletIds.current = currentWalletIds; + + // Clear animation states + if (addedWallets.length > 0) { + setTimeout(() => { + addedWallets.forEach(id => { + delete newWalletsMap.current[id]; + }); + lastAddedWalletId.current = null; + }, 2000); + } + } + }, [data, animateChanges, scrollToWalletById]); + + const onScrollToIndexFailed = (error: { averageItemLength: number; index: number }): void => { + console.debug('onScrollToIndexFailed', error); + flatListRef.current?.scrollToOffset({ offset: error.averageItemLength * error.index, animated: true }); + setTimeout(() => { + if (data.length !== 0 && flatListRef.current !== null) { + flatListRef.current.scrollToIndex({ index: error.index, animated: true }); + } + }, 100); + }; + + const renderItem = useCallback( + ({ item }: ListRenderItemInfo) => { + if (!item) return null; + const content = ( + + ); + + if (!animateChanges) return content; + + return ( + + {content} + + ); + }, + [ + horizontal, + selectedWallet, + handleLongPress, + onPress, + searchQuery, + renderHighlightedText, + animateChanges, + layoutTransition, + enteringTransition, + exitingTransition, + ], + ); + + const keyExtractor = useCallback((item: TWallet, index: number) => (item?.getID ? item.getID() : index.toString()), []); + + const sliderHeight = 195; + + useEffect(() => { + return () => { + hasFocusedRef.current = false; + }; + }, []); + + const renderNonFlatListWallets = useCallback(() => { + return data.map(item => { + if (!item) return null; + + const content = ( + { + // Keep existing ref object in map + walletRefs.current[item.getID()] ??= { current: null }; + walletRefs.current[item.getID()].current = node; + }} + onLayout={() => { + if (walletRefs.current[item.getID()]?.current && newWalletsMap.current[item.getID()]) { + walletRefs.current[item.getID()].current?.measure( + (x: number, y: number, widthVal: number, heightVal: number, pageX: number, pageY: number) => { + console.debug(`[WalletsCarousel] New wallet ${item.getID()} positioned at y=${y}, pageY=${pageY}`); + }, + ); + } + }} + > + + + ); + + if (!animateChanges) return content; + + return ( + + {content} + + ); + }); + }, [ + data, + horizontal, + selectedWallet, + handleLongPress, + onPress, + props.searchQuery, + props.renderHighlightedText, + animateChanges, + layoutTransition, + enteringTransition, + exitingTransition, + ]); + + useEffect(() => { + // We check the current values inside the effect, but don't include them as dependencies + if (!isFlatList && lastAddedWalletId.current !== null && !isInitialMount.current) { + // Use a slightly longer delay to ensure the ScrollView has fully rendered + const scrollDelay = setTimeout(() => { + console.debug('[WalletsCarousel] Attempting delayed scroll to:', lastAddedWalletId.current); + if (lastAddedWalletId.current !== null) { + scrollToWalletById(lastAddedWalletId.current, true); + } + }, 500); + + return () => clearTimeout(scrollDelay); + } + }, [isFlatList, scrollToWalletById]); // Remove ref.current values from dependency array + + const cStyles = StyleSheet.create({ + content: { + paddingTop: 16, + }, + contentLargeScreen: { + paddingHorizontal: sizeClass === SizeClass.Large ? 16 : 12, + }, + }); + + return isFlatList ? ( + : null} + {...props} + /> + ) : ( + + {renderNonFlatListWallets()} + {onNewWalletPress && } + + ); +}); + +export default WalletsCarousel; diff --git a/components/WatchOnlyWarning.tsx b/components/WatchOnlyWarning.tsx new file mode 100644 index 00000000000..850a544f907 --- /dev/null +++ b/components/WatchOnlyWarning.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { Image, Text, TouchableOpacity, View, StyleSheet, useColorScheme } from 'react-native'; +import Icon from './Icon'; +import loc from '../loc'; + +interface Props { + handleDismiss: () => void; +} + +const WatchOnlyWarning: React.FC = ({ handleDismiss }) => { + const colorScheme = useColorScheme(); + const isDark = colorScheme === 'dark'; + + return ( + + + + + {loc.transactions.watchOnlyWarningTitle} + + + + + + {loc.transactions.watchOnlyWarningDescription} + + ); +}; + +const styles = StyleSheet.create({ + container: { + padding: 16, + margin: 16, + borderRadius: 12, + }, + containerLight: { + backgroundColor: '#fc990e', + }, + containerDark: { + backgroundColor: '#7e4a05', + }, + header: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 8, + }, + title: { + flex: 1, + color: '#FFFFFF', + fontWeight: 'bold', + fontSize: 16, + marginLeft: 8, + textAlign: 'left', + }, + dismissButton: { + backgroundColor: 'rgba(255, 255, 255, 0.2)', + borderRadius: 14, + width: 28, + height: 28, + justifyContent: 'center', + alignItems: 'center', + marginLeft: 8, + }, + dismissIcon: { + width: 14, + height: 14, + resizeMode: 'contain', + }, + description: { + color: '#FFFFFF', + textAlign: 'left', + lineHeight: 20, + fontWeight: '600', + }, +}); + +export default WatchOnlyWarning; diff --git a/components/addresses/AddressItem.js b/components/addresses/AddressItem.js deleted file mode 100644 index 300ba0ee5b9..00000000000 --- a/components/addresses/AddressItem.js +++ /dev/null @@ -1,193 +0,0 @@ -import React, { useRef } from 'react'; -import { StyleSheet, Text, View } from 'react-native'; -import { useNavigation, useTheme } from '@react-navigation/native'; -import { ListItem } from 'react-native-elements'; -import PropTypes from 'prop-types'; -import { AddressTypeBadge } from './AddressTypeBadge'; -import loc, { formatBalance } from '../../loc'; -import TooltipMenu from '../TooltipMenu'; -import Clipboard from '@react-native-clipboard/clipboard'; -import Share from 'react-native-share'; - -const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }) => { - const { colors } = useTheme(); - - const hasTransactions = item.transactions > 0; - - const stylesHook = StyleSheet.create({ - container: { - borderBottomColor: colors.lightBorder, - backgroundColor: colors.elevated, - }, - list: { - color: colors.buttonTextColor, - }, - index: { - color: colors.alternativeTextColor, - }, - balance: { - color: colors.alternativeTextColor, - }, - address: { - color: hasTransactions ? colors.darkGray : colors.buttonTextColor, - }, - }); - - const { navigate } = useNavigation(); - - const menuRef = useRef(); - const navigateToReceive = () => { - menuRef.current?.dismissMenu(); - navigate('ReceiveDetailsRoot', { - screen: 'ReceiveDetails', - params: { - walletID, - address: item.address, - }, - }); - }; - - const navigateToSignVerify = () => { - menuRef.current?.dismissMenu(); - navigate('SignVerifyRoot', { - screen: 'SignVerify', - params: { - walletID, - address: item.address, - }, - }); - }; - - const balance = formatBalance(item.balance, balanceUnit, true); - - const handleCopyPress = () => { - Clipboard.setString(item.address); - }; - - const handleSharePress = () => { - Share.open({ message: item.address }).catch(error => console.log(error)); - }; - - const onToolTipPress = id => { - if (id === AddressItem.actionKeys.CopyToClipboard) { - handleCopyPress(); - } else if (id === AddressItem.actionKeys.Share) { - handleSharePress(); - } else if (id === AddressItem.actionKeys.SignVerify) { - navigateToSignVerify(); - } - }; - - const getAvailableActions = () => { - const actions = [ - { - id: AddressItem.actionKeys.CopyToClipboard, - text: loc.transactions.details_copy, - icon: AddressItem.actionIcons.Clipboard, - }, - { - id: AddressItem.actionKeys.Share, - text: loc.receive.details_share, - icon: AddressItem.actionIcons.Share, - }, - ]; - - if (allowSignVerifyMessage) { - actions.push({ - id: AddressItem.actionKeys.SignVerify, - text: loc.addresses.sign_title, - icon: AddressItem.actionIcons.Signature, - }); - } - - return actions; - }; - - const render = () => { - return ( - - - - - {item.index + 1}{' '} - {item.address} - - - {balance} - - - - - - {loc.addresses.transactions}: {item.transactions} - - - - - ); - }; - - return render(); -}; - -AddressItem.actionKeys = { - Share: 'share', - CopyToClipboard: 'copyToClipboard', - SignVerify: 'signVerify', -}; - -AddressItem.actionIcons = { - Signature: { - iconType: 'SYSTEM', - iconValue: 'signature', - }, - Share: { - iconType: 'SYSTEM', - iconValue: 'square.and.arrow.up', - }, - Clipboard: { - iconType: 'SYSTEM', - iconValue: 'doc.on.doc', - }, -}; - -const styles = StyleSheet.create({ - address: { - fontWeight: 'bold', - marginHorizontal: 40, - }, - index: { - fontSize: 15, - }, - balance: { - marginTop: 8, - marginLeft: 14, - }, - subtitle: { - flex: 1, - flexDirection: 'row', - justifyContent: 'space-between', - width: '100%', - }, -}); - -AddressItem.propTypes = { - item: PropTypes.shape({ - key: PropTypes.string, - index: PropTypes.number, - address: PropTypes.string, - isInternal: PropTypes.bool, - transactions: PropTypes.number, - balance: PropTypes.number, - }), - balanceUnit: PropTypes.string, -}; -export { AddressItem }; diff --git a/components/addresses/AddressItem.tsx b/components/addresses/AddressItem.tsx new file mode 100644 index 00000000000..b27d19d3525 --- /dev/null +++ b/components/addresses/AddressItem.tsx @@ -0,0 +1,272 @@ +import React, { useMemo, useCallback, useEffect, useRef } from 'react'; +import Clipboard from '@react-native-clipboard/clipboard'; +import { StyleSheet, Text, View } from 'react-native'; +import Share from 'react-native-share'; +import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; +import confirm from '../../helpers/confirm'; +import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics'; +import loc, { formatBalance } from '../../loc'; +import { BitcoinUnit } from '../../models/bitcoinUnits'; +import presentAlert from '../Alert'; +import { useTheme } from '../themes'; +import { AddressTypeBadge } from './AddressTypeBadge'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList'; +import { useStorage } from '../../hooks/context/useStorage'; +import ToolTipMenu from '../TooltipMenu'; +import { CommonToolTipActions } from '../../typings/CommonToolTipActions'; +import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; +import HighlightedText from '../HighlightedText'; +import { useSharedValue, withSpring, withTiming } from 'react-native-reanimated'; + +interface AddressItemProps { + item: any; + balanceUnit: BitcoinUnit; + walletID: string; + allowSignVerifyMessage: boolean; + onPress?: () => void; // example: ManageWallets uses this + searchQuery?: string; + renderHighlightedText?: (text: string, query: string) => React.ReactElement; +} + +type NavigationProps = NativeStackNavigationProp; + +const AddressItem = ({ + item, + balanceUnit, + walletID, + allowSignVerifyMessage, + onPress, + searchQuery = '', + renderHighlightedText, +}: AddressItemProps) => { + const { wallets } = useStorage(); + const { colors, dark } = useTheme(); + const { isBiometricUseCapableAndEnabled } = useBiometrics(); + const balanceOpacity = useSharedValue(1); + const balanceTranslateY = useSharedValue(0); + const previousBalance = useRef(undefined); + + const hasTransactions = item.transactions > 0; + + const stylesHook = StyleSheet.create({ + container: { + borderBottomColor: colors.lightBorder, + backgroundColor: colors.elevated, + borderBottomWidth: StyleSheet.hairlineWidth, + }, + + index: { + color: colors.alternativeTextColor, + }, + balance: { + color: colors.alternativeTextColor, + }, + address: { + color: dark ? colors.foregroundColor : colors.darkGray, + }, + }); + + const { navigate } = useExtendedNavigation(); + + const navigateToReceive = useCallback(() => { + if (onPress) { + onPress(); + } else { + navigate('ReceiveDetails', { + walletID, + address: item.address, + }); + } + }, [navigate, walletID, item.address, onPress]); + + const navigateToSignVerify = useCallback(() => { + navigate('SignVerifyRoot', { + screen: 'SignVerify', + params: { + walletID, + address: item.address, + }, + }); + }, [navigate, walletID, item.address]); + + const menuActions = useMemo( + () => [ + CommonToolTipActions.CopyTXID, + CommonToolTipActions.Share, + { + ...CommonToolTipActions.SignVerify, + hidden: !allowSignVerifyMessage, + }, + { + ...CommonToolTipActions.ExportPrivateKey, + hidden: !allowSignVerifyMessage, + }, + ], + [allowSignVerifyMessage], + ); + + const balance = formatBalance(item.balance, balanceUnit, true); + + useEffect(() => { + if (previousBalance.current !== undefined && previousBalance.current !== balance) { + balanceOpacity.value = 0; + balanceTranslateY.value = 6; + balanceOpacity.value = withTiming(1, { duration: 180 }); + balanceTranslateY.value = withSpring(0, { damping: 16, stiffness: 220 }); + } + + previousBalance.current = balance; + }, [balance, balanceOpacity, balanceTranslateY]); + + const handleCopyPress = useCallback(() => { + Clipboard.setString(item.address); + }, [item.address]); + + const handleSharePress = useCallback(() => { + Share.open({ message: item.address }).catch(error => console.log(error)); + }, [item.address]); + + const handleCopyPrivkeyPress = useCallback(() => { + const wallet = wallets.find(w => w.getID() === walletID); + if (!wallet) { + presentAlert({ message: 'Internal error: cant find wallet' }); + return; + } + + try { + const wif = wallet._getWIFbyAddress(item.address); + if (!wif) { + presentAlert({ message: 'Internal error: cant get WIF from the wallet' }); + return; + } + triggerHapticFeedback(HapticFeedbackTypes.Selection); + Clipboard.setString(wif); + } catch (error: any) { + presentAlert({ message: error.message }); + } + }, [wallets, walletID, item.address]); + + const onToolTipPress = useCallback( + async (id: string) => { + if (id === CommonToolTipActions.CopyTXID.id) { + handleCopyPress(); + } else if (id === CommonToolTipActions.Share.id) { + handleSharePress(); + } else if (id === CommonToolTipActions.SignVerify.id) { + navigateToSignVerify(); + } else if (id === CommonToolTipActions.ExportPrivateKey.id) { + if (await confirm(loc.addresses.sensitive_private_key)) { + if (await isBiometricUseCapableAndEnabled()) { + if (!(await unlockWithBiometrics())) { + return; + } + } + handleCopyPrivkeyPress(); + } + } + }, + [handleCopyPress, handleSharePress, navigateToSignVerify, handleCopyPrivkeyPress, isBiometricUseCapableAndEnabled], + ); + + // Render address with highlighting if a search query is provided + const renderAddressContent = () => { + if (searchQuery && searchQuery.length > 0) { + if (renderHighlightedText) { + return renderHighlightedText(item.address, searchQuery); + } + return ( + + ); + } + + return ( + + {item.address} + + ); + }; + + return ( + + + + + {item.index} + + + {renderAddressContent()} + {balance} + + + + + + {loc.addresses.transactions}: {item.transactions ?? 0} + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 16, + paddingVertical: 12, + borderBottomWidth: StyleSheet.hairlineWidth, + }, + address: { + fontWeight: 'bold', + marginHorizontal: 4, + }, + tooltipButton: { + width: '100%', + alignSelf: 'stretch', + }, + index: { + fontSize: 15, + fontWeight: '600', + }, + balance: { + marginTop: 6, + fontWeight: '600', + }, + row: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + }, + leftSection: { + marginRight: 10, + paddingTop: 2, + }, + middleSection: { + flex: 1, + paddingRight: 12, + }, + rightContainer: { + justifyContent: 'center', + alignItems: 'flex-end', + minWidth: 96, + paddingLeft: 8, + }, +}); + +export { AddressItem }; diff --git a/components/addresses/AddressTypeBadge.js b/components/addresses/AddressTypeBadge.js deleted file mode 100644 index 8b60d1ad2a4..00000000000 --- a/components/addresses/AddressTypeBadge.js +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { useTheme } from '@react-navigation/native'; -import { StyleSheet, View, Text } from 'react-native'; -import loc, { formatStringAddTwoWhiteSpaces } from '../../loc'; - -const styles = StyleSheet.create({ - container: { - paddingVertical: 4, - paddingHorizontal: 10, - borderRadius: 20, - alignSelf: 'flex-end', - }, - badgeText: { - fontSize: 12, - textAlign: 'center', - }, -}); - -const AddressTypeBadge = ({ isInternal, hasTransactions }) => { - const { colors } = useTheme(); - - const stylesHook = StyleSheet.create({ - changeBadge: { backgroundColor: colors.changeBackground }, - receiveBadge: { backgroundColor: colors.receiveBackground }, - usedBadge: { backgroundColor: colors.buttonDisabledBackgroundColor }, - changeText: { color: colors.changeText }, - receiveText: { color: colors.receiveText }, - usedText: { color: colors.alternativeTextColor }, - }); - - const badgeLabel = hasTransactions - ? loc.addresses.type_used - : isInternal - ? formatStringAddTwoWhiteSpaces(loc.addresses.type_change) - : formatStringAddTwoWhiteSpaces(loc.addresses.type_receive); - - // eslint-disable-next-line prettier/prettier - const badgeStyle = hasTransactions - ? stylesHook.usedBadge - : isInternal - ? stylesHook.changeBadge - : stylesHook.receiveBadge; - - // eslint-disable-next-line prettier/prettier - const textStyle = hasTransactions - ? stylesHook.usedText - : isInternal - ? stylesHook.changeText - : stylesHook.receiveText; - - return ( - - {badgeLabel} - - ); -}; - -AddressTypeBadge.propTypes = { - isInternal: PropTypes.bool, - hasTransactions: PropTypes.bool, -}; - -export { AddressTypeBadge }; diff --git a/components/addresses/AddressTypeBadge.tsx b/components/addresses/AddressTypeBadge.tsx new file mode 100644 index 00000000000..05b8f647d1c --- /dev/null +++ b/components/addresses/AddressTypeBadge.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; + +import loc, { formatStringAddTwoWhiteSpaces } from '../../loc'; +import { useTheme } from '../themes'; + +type Props = { isInternal: boolean; hasTransactions: boolean }; + +export const AddressTypeBadge: React.FC = ({ isInternal, hasTransactions }) => { + const { colors } = useTheme(); + + const stylesHook = StyleSheet.create({ + changeBadge: { backgroundColor: colors.changeBackground }, + receiveBadge: { backgroundColor: colors.receiveBackground }, + usedBadge: { backgroundColor: colors.buttonDisabledBackgroundColor }, + changeText: { color: colors.changeText }, + receiveText: { color: colors.receiveText }, + usedText: { color: colors.alternativeTextColor }, + }); + + const badgeLabel = hasTransactions + ? loc.addresses.type_used + : isInternal + ? formatStringAddTwoWhiteSpaces(loc.addresses.type_change) + : formatStringAddTwoWhiteSpaces(loc.addresses.type_receive); + + // eslint-disable-next-line prettier/prettier + const badgeStyle = hasTransactions + ? stylesHook.usedBadge + : isInternal + ? stylesHook.changeBadge + : stylesHook.receiveBadge; + + // eslint-disable-next-line prettier/prettier + const textStyle = hasTransactions + ? stylesHook.usedText + : isInternal + ? stylesHook.changeText + : stylesHook.receiveText; + + return ( + + {badgeLabel} + + ); +}; + +const styles = StyleSheet.create({ + container: { + paddingVertical: 4, + paddingHorizontal: 10, + borderRadius: 20, + alignSelf: 'flex-end', + }, + badgeText: { + fontSize: 12, + textAlign: 'center', + }, +}); diff --git a/components/addresses/AddressTypeTabs.js b/components/addresses/AddressTypeTabs.js deleted file mode 100644 index a7a2da63b0e..00000000000 --- a/components/addresses/AddressTypeTabs.js +++ /dev/null @@ -1,96 +0,0 @@ -import { useTheme } from '@react-navigation/native'; -import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; -import loc from '../../loc'; - -export const TABS = { - EXTERNAL: 'receive', - INTERNAL: 'change', -}; - -const AddressTypeTabs = ({ currentTab, setCurrentTab }) => { - const { colors } = useTheme(); - - const stylesHook = StyleSheet.create({ - activeTab: { - backgroundColor: colors.modal, - }, - activeText: { - fontWeight: 'bold', - color: colors.foregroundColor, - }, - inactiveTab: { - fontWeight: 'normal', - color: colors.foregroundColor, - }, - backTabs: { - backgroundColor: colors.buttonDisabledBackgroundColor, - }, - }); - - const tabs = Object.entries(TABS).map(([key, value]) => { - return { - key, - value, - name: loc.addresses[`type_${value}`], - }; - }); - - const changeToTab = tabKey => { - if (tabKey in TABS) { - setCurrentTab(TABS[tabKey]); - } - }; - - const render = () => { - const tabsButtons = tabs.map(tab => { - const isActive = tab.value === currentTab; - - const tabStyle = isActive ? stylesHook.activeTab : stylesHook.inactiveTab; - const textStyle = isActive ? stylesHook.activeText : stylesHook.inactiveTab; - - return ( - changeToTab(tab.key)} style={[styles.tab, tabStyle]}> - changeToTab(tab.key)} style={textStyle}> - {tab.name} - - - ); - }); - - return ( - - - {tabsButtons} - - - ); - }; - - return render(); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - flexDirection: 'row', - justifyContent: 'center', - }, - backTabs: { - padding: 4, - marginVertical: 8, - borderRadius: 8, - }, - tabs: { - flex: 1, - flexDirection: 'row', - justifyContent: 'center', - }, - tab: { - borderRadius: 6, - paddingVertical: 8, - paddingHorizontal: 16, - }, -}); - -export { AddressTypeTabs }; diff --git a/components/color.ts b/components/color.ts new file mode 100644 index 00000000000..68994e340d8 --- /dev/null +++ b/components/color.ts @@ -0,0 +1,32 @@ +const HEX6_RE = /^[0-9a-fA-F]{6}$/; + +/** Apply alpha to `#RGB`/`#RRGGBB`/`#RRGGBBAA` (input alpha ignored). Other formats returned unchanged. */ +export const withAlpha = (color: string, alpha: number): string => { + const a = Math.max(0, Math.min(1, alpha)); + + if (color.startsWith('#')) { + const hex = color.slice(1); + const normalized = + hex.length === 3 + ? hex + .split('') + .map(c => c + c) + .join('') + : hex.length === 8 + ? hex.slice(0, 6) + : hex; + + if (normalized.length === 6 && HEX6_RE.test(normalized)) { + const r = parseInt(normalized.slice(0, 2), 16); + const g = parseInt(normalized.slice(2, 4), 16); + const b = parseInt(normalized.slice(4, 6), 16); + return `rgba(${r},${g},${b},${a})`; + } + } + + if (__DEV__) { + console.warn(`[withAlpha] unsupported color format: ${String(color)}`); + } + + return color; +}; diff --git a/components/handoff.tsx b/components/handoff.tsx deleted file mode 100644 index cd8abdadf89..00000000000 --- a/components/handoff.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { useContext } from 'react'; -// @ts-ignore: react-native-handoff is not in the type definition -import Handoff from 'react-native-handoff'; -import { BlueStorageContext } from '../blue_modules/storage-context'; - -interface HandoffComponentProps { - url?: string; -} - -interface HandoffComponentWithActivityTypes extends React.FC { - activityTypes: { - ReceiveOnchain: string; - Xpub: string; - ViewInBlockExplorer: string; - }; -} - -const HandoffComponent: HandoffComponentWithActivityTypes = props => { - const { isHandOffUseEnabled } = useContext(BlueStorageContext); - - if (isHandOffUseEnabled) { - return ; - } - return null; -}; - -const activityTypes = { - ReceiveOnchain: 'io.bluewallet.bluewallet.receiveonchain', - Xpub: 'io.bluewallet.bluewallet.xpub', - ViewInBlockExplorer: 'io.bluewallet.bluewallet.blockexplorer', -}; - -HandoffComponent.activityTypes = activityTypes; - -export default HandoffComponent; diff --git a/components/icons/PlusIcon.js b/components/icons/PlusIcon.js deleted file mode 100644 index 122349e5a1f..00000000000 --- a/components/icons/PlusIcon.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { StyleSheet } from 'react-native'; -import { Avatar } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; - -const styles = StyleSheet.create({ - ball: { - width: 30, - height: 30, - borderRadius: 15, - }, -}); - -const PlusIcon = props => { - const { colors } = useTheme(); - const stylesHook = StyleSheet.create({ - ball: { - backgroundColor: colors.buttonBackgroundColor, - }, - }); - - return ( - - ); -}; - -export default PlusIcon; diff --git a/components/icons/SettingsButton.tsx b/components/icons/SettingsButton.tsx new file mode 100644 index 00000000000..b3c2dbfebc9 --- /dev/null +++ b/components/icons/SettingsButton.tsx @@ -0,0 +1,65 @@ +import React, { useCallback, useMemo } from 'react'; +import { StyleSheet, View } from 'react-native'; +import Icon from '../Icon'; +import { useTheme } from '../themes'; +import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; +import loc from '../../loc'; +import ToolTipMenu from '../TooltipMenu'; +import { CommonToolTipActions } from '../../typings/CommonToolTipActions'; + +const SettingsButton = () => { + const { colors } = useTheme(); + const { navigate } = useExtendedNavigation(); + const onPress = () => { + navigate('Settings'); + }; + + const onPressMenuItem = useCallback( + (menuItem: string) => { + switch (menuItem) { + case CommonToolTipActions.ManageWallet.id: + navigate('ManageWallets'); + break; + default: + break; + } + }, + [navigate], + ); + + const actions = useMemo(() => [CommonToolTipActions.ManageWallet], []); + return ( + + + + + + ); +}; + +export default SettingsButton; + +const style = StyleSheet.create({ + buttonStyle: { + width: 32, + height: 32, + borderRadius: 16, + justifyContent: 'center', + alignItems: 'center', + }, + iconContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/components/icons/TransactionExpiredIcon.js b/components/icons/TransactionExpiredIcon.js deleted file mode 100644 index 37cc9c7f73e..00000000000 --- a/components/icons/TransactionExpiredIcon.js +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; - -const styles = StyleSheet.create({ - boxIncoming: { - position: 'relative', - }, - ballOutgoingExpired: { - width: 30, - height: 30, - borderRadius: 15, - justifyContent: 'center', - }, - icon: { - left: 0, - top: 0, - }, -}); - -const TransactionExpiredIcon = props => { - const { colors } = useTheme(); - const stylesHooks = StyleSheet.create({ - ballOutgoingExpired: { - backgroundColor: colors.ballOutgoingExpired, - }, - }); - - return ( - - - - - - ); -}; - -export default TransactionExpiredIcon; diff --git a/components/icons/TransactionExpiredIcon.tsx b/components/icons/TransactionExpiredIcon.tsx new file mode 100644 index 00000000000..d82f2e78274 --- /dev/null +++ b/components/icons/TransactionExpiredIcon.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import Icon from '../Icon'; + +import { useTheme } from '../themes'; + +const styles = StyleSheet.create({ + boxIncoming: { + position: 'relative', + } as ViewStyle, + ballOutgoingExpired: { + width: 30, + height: 30, + borderRadius: 15, + justifyContent: 'center', + alignItems: 'center', + } as ViewStyle, +}); + +const TransactionExpiredIcon: React.FC = () => { + const { colors } = useTheme(); + + const stylesHooks = StyleSheet.create({ + ballOutgoingExpired: { + backgroundColor: colors.ballOutgoingExpired, + }, + }); + + return ( + + + + + + ); +}; + +export default TransactionExpiredIcon; diff --git a/components/icons/TransactionIncomingIcon.js b/components/icons/TransactionIncomingIcon.js deleted file mode 100644 index 65aa45eea02..00000000000 --- a/components/icons/TransactionIncomingIcon.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; - -const styles = StyleSheet.create({ - boxIncoming: { - position: 'relative', - }, - ballIncoming: { - width: 30, - height: 30, - borderRadius: 15, - transform: [{ rotate: '-45deg' }], - justifyContent: 'center', - }, -}); - -const TransactionIncomingIcon = props => { - const { colors } = useTheme(); - const stylesHooks = StyleSheet.create({ - ballIncoming: { - backgroundColor: colors.ballReceive, - }, - }); - - return ( - - - - - - ); -}; - -export default TransactionIncomingIcon; diff --git a/components/icons/TransactionIncomingIcon.tsx b/components/icons/TransactionIncomingIcon.tsx new file mode 100644 index 00000000000..4b2b5495576 --- /dev/null +++ b/components/icons/TransactionIncomingIcon.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import Icon from '../Icon'; + +import { useTheme } from '../themes'; + +const styles = StyleSheet.create({ + boxIncoming: { + position: 'relative', + } as ViewStyle, + ballIncoming: { + width: 30, + height: 30, + borderRadius: 15, + transform: [{ rotate: '-45deg' }], + justifyContent: 'center', + alignItems: 'center', + } as ViewStyle, +}); + +const TransactionIncomingIcon: React.FC = () => { + const { colors } = useTheme(); + + const stylesHooks = StyleSheet.create({ + ballIncoming: { + backgroundColor: colors.ballReceive, + }, + }); + + return ( + + + + + + ); +}; + +export default TransactionIncomingIcon; diff --git a/components/icons/TransactionOffchainIcon.js b/components/icons/TransactionOffchainIcon.js deleted file mode 100644 index a1f78e9c8b4..00000000000 --- a/components/icons/TransactionOffchainIcon.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; - -const styles = StyleSheet.create({ - boxIncoming: { - position: 'relative', - }, - ballOutgoingWithoutRotate: { - width: 30, - height: 30, - borderRadius: 15, - }, - icon: { - left: 0, - marginTop: 6, - }, -}); - -const TransactionOffchainIcon = props => { - const { colors } = useTheme(); - const stylesHooks = StyleSheet.create({ - ballOutgoingWithoutRotate: { - backgroundColor: colors.ballOutgoing, - }, - }); - - return ( - - - - - - ); -}; - -export default TransactionOffchainIcon; diff --git a/components/icons/TransactionOffchainIcon.tsx b/components/icons/TransactionOffchainIcon.tsx new file mode 100644 index 00000000000..56a3ffcf623 --- /dev/null +++ b/components/icons/TransactionOffchainIcon.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import Icon from '../Icon'; + +import { useTheme } from '../themes'; + +const styles = StyleSheet.create({ + boxIncoming: { + position: 'relative', + } as ViewStyle, + ballOutgoingWithoutRotate: { + width: 30, + height: 30, + borderRadius: 15, + justifyContent: 'center', + alignItems: 'center', + } as ViewStyle, +}); + +const TransactionOffchainIcon: React.FC = () => { + const { colors } = useTheme(); + + const stylesHooks = StyleSheet.create({ + ballOutgoingWithoutRotate: { + backgroundColor: colors.ballOutgoing, + }, + }); + + return ( + + + + + + ); +}; + +export default TransactionOffchainIcon; diff --git a/components/icons/TransactionOffchainIncomingIcon.js b/components/icons/TransactionOffchainIncomingIcon.js deleted file mode 100644 index d78359fcf48..00000000000 --- a/components/icons/TransactionOffchainIncomingIcon.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; - -const styles = StyleSheet.create({ - boxIncoming: { - position: 'relative', - }, - ballIncomingWithoutRotate: { - width: 30, - height: 30, - borderRadius: 15, - }, - icon: { - left: 0, - marginTop: 6, - }, -}); - -const TransactionOffchainIncomingIcon = props => { - const { colors } = useTheme(); - const stylesHooks = StyleSheet.create({ - ballIncomingWithoutRotate: { - backgroundColor: colors.ballReceive, - }, - }); - - return ( - - - - - - ); -}; - -export default TransactionOffchainIncomingIcon; diff --git a/components/icons/TransactionOffchainIncomingIcon.tsx b/components/icons/TransactionOffchainIncomingIcon.tsx new file mode 100644 index 00000000000..f077ec54aab --- /dev/null +++ b/components/icons/TransactionOffchainIncomingIcon.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import Icon from '../Icon'; + +import { useTheme } from '../themes'; + +const styles = StyleSheet.create({ + boxIncoming: { + position: 'relative', + } as ViewStyle, + ballIncomingWithoutRotate: { + width: 30, + height: 30, + borderRadius: 15, + justifyContent: 'center', + alignItems: 'center', + } as ViewStyle, +}); + +const TransactionOffchainIncomingIcon: React.FC = () => { + const { colors } = useTheme(); + + const stylesHooks = StyleSheet.create({ + ballIncomingWithoutRotate: { + backgroundColor: colors.ballReceive, + }, + }); + + return ( + + + + + + ); +}; + +export default TransactionOffchainIncomingIcon; diff --git a/components/icons/TransactionOnchainIcon.js b/components/icons/TransactionOnchainIcon.js deleted file mode 100644 index 785e425e1c0..00000000000 --- a/components/icons/TransactionOnchainIcon.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; - -const styles = StyleSheet.create({ - boxIncoming: { - position: 'relative', - }, - ballIncoming: { - width: 30, - height: 30, - borderRadius: 15, - transform: [{ rotate: '-45deg' }], - justifyContent: 'center', - }, - icon: { - left: 0, - top: 0, - transform: [{ rotate: '-45deg' }], - }, -}); - -const TransactionOnchainIcon = props => { - const { colors } = useTheme(); - const stylesBlueIconHooks = StyleSheet.create({ - ballIncoming: { - backgroundColor: colors.ballReceive, - }, - }); - - return ( - - - - - - ); -}; - -export default TransactionOnchainIcon; diff --git a/components/icons/TransactionOnchainIcon.tsx b/components/icons/TransactionOnchainIcon.tsx new file mode 100644 index 00000000000..ae4d375f729 --- /dev/null +++ b/components/icons/TransactionOnchainIcon.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import Icon from '../Icon'; + +import { useTheme } from '../themes'; + +const styles = StyleSheet.create({ + boxIncoming: { + position: 'relative', + } as ViewStyle, + ballIncoming: { + width: 30, + height: 30, + borderRadius: 15, + transform: [{ rotate: '-45deg' }], + justifyContent: 'center', + alignItems: 'center', + } as ViewStyle, +}); + +const TransactionOnchainIcon: React.FC = () => { + const { colors } = useTheme(); + + const stylesBlueIconHooks = StyleSheet.create({ + ballIncoming: { + backgroundColor: colors.ballReceive, + }, + }); + + return ( + + + + + + ); +}; + +export default TransactionOnchainIcon; diff --git a/components/icons/TransactionOutgoingIcon.js b/components/icons/TransactionOutgoingIcon.js deleted file mode 100644 index 2ae3c92bd8f..00000000000 --- a/components/icons/TransactionOutgoingIcon.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; - -const styles = StyleSheet.create({ - boxIncoming: { - position: 'relative', - }, - ballOutgoing: { - width: 30, - height: 30, - borderRadius: 15, - transform: [{ rotate: '225deg' }], - justifyContent: 'center', - }, -}); - -const TransactionOutgoingIcon = props => { - const { colors } = useTheme(); - const stylesBlueIconHooks = StyleSheet.create({ - ballOutgoing: { - backgroundColor: colors.ballOutgoing, - }, - }); - - return ( - - - - - - ); -}; - -export default TransactionOutgoingIcon; diff --git a/components/icons/TransactionOutgoingIcon.tsx b/components/icons/TransactionOutgoingIcon.tsx new file mode 100644 index 00000000000..17de2efde35 --- /dev/null +++ b/components/icons/TransactionOutgoingIcon.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import Icon from '../Icon'; + +import { useTheme } from '../themes'; + +const styles = StyleSheet.create({ + boxIncoming: { + position: 'relative', + } as ViewStyle, + ballOutgoing: { + width: 30, + height: 30, + borderRadius: 15, + transform: [{ rotate: '225deg' }], + justifyContent: 'center', + alignItems: 'center', + } as ViewStyle, +}); + +const TransactionOutgoingIcon: React.FC = () => { + const { colors } = useTheme(); + + const stylesBlueIconHooks = StyleSheet.create({ + ballOutgoing: { + backgroundColor: colors.ballOutgoing, + }, + }); + + return ( + + + + + + ); +}; + +export default TransactionOutgoingIcon; diff --git a/components/icons/TransactionPendingIcon.js b/components/icons/TransactionPendingIcon.js deleted file mode 100644 index ddd5c9c6398..00000000000 --- a/components/icons/TransactionPendingIcon.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; - -const styles = StyleSheet.create({ - boxIncoming: { - position: 'relative', - }, - ball: { - width: 30, - height: 30, - borderRadius: 15, - }, - icon: { - left: 0, - top: 7, - }, -}); - -const TransactionPendingIcon = props => { - const { colors } = useTheme(); - const stylesHook = StyleSheet.create({ - ball: { - backgroundColor: colors.buttonBackgroundColor, - }, - }); - - return ( - - - - - - ); -}; - -export default TransactionPendingIcon; diff --git a/components/icons/TransactionPendingIcon.tsx b/components/icons/TransactionPendingIcon.tsx new file mode 100644 index 00000000000..b2289153333 --- /dev/null +++ b/components/icons/TransactionPendingIcon.tsx @@ -0,0 +1,54 @@ +import React, { useRef } from 'react'; +import { StyleSheet, View } from 'react-native'; +import LottieView from 'lottie-react-native'; + +import { useTheme } from '../themes'; + +const styles = StyleSheet.create({ + boxIncoming: { + position: 'relative', + }, + ball: { + width: 32, + height: 32, + borderRadius: 16, + justifyContent: 'center', + alignItems: 'center', + }, + lottie: { + width: 20, + height: 20, + alignSelf: 'center', + }, +}); + +const TransactionPendingIcon: React.FC = () => { + const { colors } = useTheme(); + const lottieRef = useRef(null); + + const stylesHook = StyleSheet.create({ + ball: { + backgroundColor: colors.transactionPendingIconBackground, + }, + }); + + const pendingAnimation = require('../../img/pending.json'); + + return ( + + + + + + ); +}; + +export default TransactionPendingIcon; diff --git a/components/navigationStyle.tsx b/components/navigationStyle.tsx index f312242e1ca..6e02cb19e8a 100644 --- a/components/navigationStyle.tsx +++ b/components/navigationStyle.tsx @@ -1,7 +1,9 @@ +import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import React from 'react'; -import { Image, Keyboard, TouchableOpacity, StyleSheet } from 'react-native'; -import { Theme } from './themes'; +import { Image, Keyboard, Platform, StyleSheet, TouchableOpacity } from 'react-native'; + import loc from '../loc'; +import { Theme } from './themes'; const styles = StyleSheet.create({ button: { @@ -10,57 +12,104 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', }, + buttonFormSheet: { + justifyContent: 'center', + alignItems: 'center', + width: 30, + height: 30, + borderRadius: 15, + marginLeft: 22, + }, }); -type NavigationOptions = { - headerStyle?: { - borderBottomWidth: number; - elevation: number; - shadowOpacity?: number; - shadowOffset: { height?: number; width?: number }; - }; - headerTitleStyle?: { - fontWeight: string; - color: string; - }; - headerLeft?: (() => React.ReactElement) | null; - headerRight?: (() => React.ReactElement) | null; - headerBackTitleVisible?: false; - headerTintColor?: string; - title?: string; -}; +enum CloseButtonPosition { + None = 'None', + Left = 'Left', + Right = 'Right', +} -type OptionsFormatter = (options: NavigationOptions, deps: { theme: Theme; navigation: any; route: any }) => NavigationOptions; +type OptionsFormatter = ( + options: NativeStackNavigationOptions, + deps: { theme: Theme; navigation: any; route: any }, +) => NativeStackNavigationOptions; -export type NavigationOptionsGetter = (theme: Theme) => (deps: { navigation: any; route: any }) => NavigationOptions; +export type NavigationOptionsGetter = (theme: Theme) => (deps: { navigation: any; route: any }) => NativeStackNavigationOptions; + +const getCloseButtonPosition = ( + closeButtonPosition: CloseButtonPosition | undefined, + isFirstRouteInStack: boolean, + isModal: boolean, +): CloseButtonPosition => { + if (closeButtonPosition !== undefined) { + return closeButtonPosition; + } + if (isFirstRouteInStack && isModal) { + return CloseButtonPosition.Right; + } + return CloseButtonPosition.None; +}; + +const getHandleCloseAction = ( + onCloseButtonPressed: ((args: { navigation: any; route: any }) => void) | undefined, + navigation: any, + route: any, +) => { + if (onCloseButtonPressed) { + return () => onCloseButtonPressed({ navigation, route }); + } + return () => { + Keyboard.dismiss(); + navigation.goBack(null); + }; +}; const navigationStyle = ( { - closeButton = false, - closeButtonFunc, + closeButtonPosition, + closeButtonIfFirstInStack, + onCloseButtonPressed, ...opts - }: NavigationOptions & { - closeButton?: boolean; - - closeButtonFunc?: (deps: { navigation: any; route: any }) => React.ReactElement; + }: 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, + formatter?: OptionsFormatter, ): NavigationOptionsGetter => { return theme => ({ navigation, route }) => { + const isFirstRouteInStack = navigation.getState().index === 0; + const isModal = route.params?.presentation === 'modal' || route.params?.presentation === 'transparentModal'; + const isFormSheet = route.params?.presentation === 'formSheet'; + + const closeButton = + closeButtonIfFirstInStack && isFirstRouteInStack + ? closeButtonIfFirstInStack + : getCloseButtonPosition(closeButtonPosition, isFirstRouteInStack, isModal); + const handleClose = getHandleCloseAction(onCloseButtonPressed, navigation, route); + let headerRight; - if (closeButton) { - const handleClose = closeButtonFunc - ? () => closeButtonFunc({ navigation, route }) - : () => { - Keyboard.dismiss(); - navigation.goBack(null); - }; + let headerLeft; + + if (closeButton === CloseButtonPosition.Right) { headerRight = () => ( + + + ); + } else if (closeButton === CloseButtonPosition.Left) { + headerLeft = () => ( + @@ -68,64 +117,30 @@ const navigationStyle = ( ); } - - let options: NavigationOptions = { - headerStyle: { - borderBottomWidth: 0, - elevation: 0, - shadowOpacity: 0, - shadowOffset: { height: 0, width: 0 }, - }, + const baseHeaderStyle = { + headerShadowVisible: false, headerTitleStyle: { - fontWeight: '600', + fontWeight: '600' as const, color: theme.colors.foregroundColor, }, - headerRight, - headerBackTitleVisible: false, headerTintColor: theme.colors.foregroundColor, - ...opts, + headerBackButtonDisplayMode: 'minimal', }; + const isLeftCloseButtonAndroid = closeButton === CloseButtonPosition.Left && Platform.OS === 'android'; - if (formatter) { - options = formatter(options, { theme, navigation, route }); - } - - return options; - }; -}; + const leftCloseButtonStyle = isLeftCloseButtonAndroid ? { headerBackImageSource: theme.closeImage } : { headerLeft }; -export default navigationStyle; + // statusBarStyle: auto is not supported on Android, so we get it based on the theme.barStyle + const statusBarStyle: NativeStackNavigationOptions['statusBarStyle'] = + opts.statusBarStyle && opts.statusBarStyle !== 'auto' ? opts.statusBarStyle : theme.barStyle === 'light-content' ? 'light' : 'dark'; -export const navigationStyleTx = (opts: NavigationOptions, formatter: OptionsFormatter): NavigationOptionsGetter => { - return theme => - ({ navigation, route }) => { - let options: NavigationOptions = { - headerStyle: { - borderBottomWidth: 0, - elevation: 0, - shadowOffset: { height: 0, width: 0 }, - }, - headerTitleStyle: { - fontWeight: '600', - color: theme.colors.foregroundColor, - }, - // headerBackTitle: null, - headerBackTitleVisible: false, - headerTintColor: theme.colors.foregroundColor, - headerLeft: () => ( - { - Keyboard.dismiss(); - navigation.goBack(null); - }} - > - - - ), + let options: NativeStackNavigationOptions = { + ...baseHeaderStyle, + ...leftCloseButtonStyle, + headerBackButtonDisplayMode: 'minimal', + headerRight, ...opts, + statusBarStyle, }; if (formatter) { @@ -135,3 +150,6 @@ export const navigationStyleTx = (opts: NavigationOptions, formatter: OptionsFor return options; }; }; + +export default navigationStyle; +export { CloseButtonPosition }; diff --git a/components/platform/index.tsx b/components/platform/index.tsx new file mode 100644 index 00000000000..05a23eb9f27 --- /dev/null +++ b/components/platform/index.tsx @@ -0,0 +1,749 @@ +import React, { forwardRef, useMemo } from 'react'; +import { + ActivityIndicator, + FlatListProps, + I18nManager, + Platform, + ScrollView, + ScrollViewProps, + StatusBar, + StyleSheet, + Switch, + SwitchProps, + Text, + TextProps, + TouchableNativeFeedback, + TouchableOpacity, + useWindowDimensions, + View, + ViewProps, +} from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import MaterialIcon from '@react-native-vector-icons/material-icons'; +import Ionicons from '@react-native-vector-icons/ionicons'; +import MaterialDesignIcons from '@react-native-vector-icons/material-design-icons'; +import FontAwesome6 from '@react-native-vector-icons/fontawesome6'; + +import SafeAreaFlatList from '../SafeAreaFlatList'; +import SafeAreaScrollView from '../SafeAreaScrollView'; +import { platformColors, useTheme } from '../themes'; +export { platformColors } from '../themes'; + +export const isAndroid = Platform.OS === 'android'; +const isIOS = Platform.OS === 'ios'; + +export const platformSizing = { + horizontalPadding: isIOS ? 16 : 20, + verticalPadding: isIOS ? 12 : 8, + sectionSpacing: isIOS ? 32 : 16, + basePadding: 16, + contentContainerMarginHorizontal: isIOS ? 16 : 0, + contentContainerPaddingHorizontal: isIOS ? 0 : 16, + firstSectionContainerPaddingTop: isIOS ? 16 : 8, + sectionContainerMarginBottom: isIOS ? 4 : 16, + itemMinHeight: 44, + containerBorderRadius: isIOS ? 10 : 4, + iconContainerBorderRadius: isIOS ? 6 : 0, + titleFontSize: isIOS ? 17 : 16, + subtitleFontSize: isIOS ? 15 : 14, + titleFontWeight: isIOS ? ('600' as const) : ('500' as const), + subtitleFontWeight: '400' as const, + subtitlePaddingVertical: 2, + subtitleLineHeight: 20, + iconSize: isIOS ? 22 : 24, + iconContainerSize: isIOS ? 28 : 24, + iconInnerSize: isIOS ? 20 : 22, + leftIconMarginLeft: 0, + leftIconMarginRight: 12, + containerPaddingVertical: isIOS ? 12 : 8, + containerElevation: isIOS ? 0 : 2, + containerMarginVertical: 0, +}; + +export const platformLayout = { + showIconBackground: isIOS, + showElevation: isAndroid, + rippleEffect: isAndroid, + showBorderRadius: true, + showBorderBottom: isIOS, + useRoundedListItems: isIOS, + cardShadow: isAndroid + ? {} + : { + shadowColor: '#000', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.1, + shadowRadius: 2, + }, +}; + +export const getSettingsCardColor = ( + colors: { lightButton?: string; modal?: string; elevated?: string; background?: string }, + dark?: boolean, +): string => + dark + ? (colors.modal ?? colors.elevated ?? colors.background ?? '#000000') + : (colors.lightButton ?? colors.modal ?? colors.elevated ?? colors.background ?? '#ffffff'); + +export const getSettingsRowBackgroundColor = ( + colors: { lightButton?: string; modal?: string; elevated?: string; background?: string }, + dark?: boolean, +): string => (isIOS && !dark ? (colors.background ?? '#ffffff') : getSettingsCardColor(colors, dark)); + +export const getSettingsHeaderOptions = ( + title: string, + colors: { + text?: string; + foregroundColor?: string; + background?: string; + lightButton?: string; + modal?: string; + elevated?: string; + } = platformColors, + dark?: boolean, +) => { + const headerTextColor = 'text' in colors ? colors.text : colors.foregroundColor; + const defaultBackgroundColor = 'background' in colors ? colors.background : platformColors.background; + const cardColor = colors.lightButton ?? colors.modal ?? colors.elevated ?? defaultBackgroundColor; + const headerBackgroundColor = isIOS ? (dark ? defaultBackgroundColor : cardColor) : defaultBackgroundColor; + + return { + title, + headerLargeTitle: isIOS, + headerLargeTitleStyle: isIOS ? { color: headerTextColor } : undefined, + headerTitleStyle: { color: headerTextColor }, + headerBackButtonDisplayMode: 'minimal' as const, + headerBackTitle: '', + headerBackVisible: true, + headerTransparent: false, + headerBlurEffect: undefined, + headerStyle: { backgroundColor: headerBackgroundColor }, + }; +}; + +const getSettingsHeaderHeight = (insetsTop?: number) => { + if (!isAndroid) return 0; + return 56 + (StatusBar.currentHeight ?? insetsTop ?? 24); +}; + +export const SettingsText: React.FC = ({ style, ...rest }) => { + const themeStyles = usePlatformStyles(); + return ; +}; + +export const SettingsSubtitle: React.FC = ({ style, ...rest }) => { + const themeStyles = usePlatformStyles(); + return ; +}; + +interface SettingsSectionProps extends ViewProps { + compact?: boolean; + horizontalInset?: boolean; +} + +export const SettingsSection: React.FC = ({ style, compact, horizontalInset = true, ...rest }) => { + const themeStyles = usePlatformStyles(); + return ( + + ); +}; + +interface SettingsSectionHeaderProps extends ViewProps { + title: string; +} + +export const SettingsSectionHeader: React.FC = ({ title, style, ...rest }) => { + const themeStyles = usePlatformStyles(); + return ( + + {title} + + ); +}; + +interface SettingsCardProps extends ViewProps { + compact?: boolean; +} + +export const SettingsCard: React.FC = ({ style, compact, ...rest }) => { + const themeStyles = usePlatformStyles(); + return ; +}; + +interface SettingsScrollViewProps extends Omit { + contentContainerStyle?: ScrollViewProps['contentContainerStyle']; + headerHeight?: number; + floatingButtonHeight?: number; +} + +export const SettingsScrollView = forwardRef((props, ref) => { + const { contentContainerStyle, headerHeight, floatingButtonHeight, style, ...rest } = props; + const { colors, dark } = useTheme(); + const insets = useSafeAreaInsets(); + const resolvedHeaderHeight = useMemo(() => headerHeight ?? getSettingsHeaderHeight(insets.top), [headerHeight, insets.top]); + const cardColor = colors.lightButton ?? colors.modal ?? colors.elevated ?? colors.background; + const screenBackgroundColor = isIOS && !dark ? cardColor : colors.background; + + return ( + + ); +}); + +SettingsScrollView.displayName = 'SettingsScrollView'; + +interface SettingsFlatListProps extends Omit, 'contentContainerStyle'> { + contentContainerStyle?: FlatListProps['contentContainerStyle']; + headerHeight?: number; + floatingButtonHeight?: number; +} + +export const SettingsFlatList = (props: SettingsFlatListProps) => { + const { contentContainerStyle, headerHeight, floatingButtonHeight, style, ...rest } = props; + const { colors, dark } = useTheme(); + const insets = useSafeAreaInsets(); + const resolvedHeaderHeight = useMemo(() => headerHeight ?? getSettingsHeaderHeight(insets.top), [headerHeight, insets.top]); + const cardColor = colors.lightButton ?? colors.modal ?? colors.elevated ?? colors.background; + const screenBackgroundColor = isIOS && !dark ? cardColor : colors.background; + + return ( + + ); +}; + +export type SettingsIconName = + | 'settings' + | 'currency' + | 'language' + | 'security' + | 'network' + | 'tools' + | 'about' + | 'notifications' + | 'lightning' + | 'blockExplorer' + | 'electrum' + | 'licensing' + | 'releaseNotes' + | 'selfTest' + | 'performance' + | 'github' + | 'search' + | 'paperPlane' + | 'key'; + +type SettingsListItemPosition = 'single' | 'first' | 'middle' | 'last'; + +interface IconProps { + name: string; + type?: string; + color?: string; + backgroundColor?: string; +} + +const resolveSettingsIconComponent = (type?: string): React.ComponentType => { + switch (type) { + case 'ionicon': + return Ionicons; + case 'material': + return MaterialIcon; + case 'material-community': + return MaterialDesignIcons; + default: + return FontAwesome6; + } +}; + +const renderVectorIcon = (icon: IconProps) => { + const IconComponent = resolveSettingsIconComponent(icon.type); + return ; +}; + +export interface SettingsListItemProps { + title: string; + subtitle?: string | React.ReactNode; + onPress?: () => void; + testID?: string; + chevron?: boolean; + switch?: SwitchProps; + isLoading?: boolean; + Component?: React.ComponentType | React.ElementType; + rightTitle?: string; + disabled?: boolean; + checkmark?: boolean; + accessibilityLabel?: string; + accessibilityHint?: string; + iconName?: SettingsIconName; + leftIcon?: IconProps | React.ReactElement; + position?: SettingsListItemPosition; + spacingTop?: boolean; + itemBackgroundColor?: string; +} + +const isIconProps = (icon: IconProps | React.ReactElement): icon is IconProps => 'name' in icon; + +const usePlatformStyles = () => { + const { colors, dark } = useTheme(); + return useMemo(() => { + const card = dark + ? (colors.modal ?? colors.elevated ?? colors.background) + : (colors.lightButton ?? colors.modal ?? colors.elevated ?? colors.background); + const itemSurface = isIOS && !dark ? colors.background : card; + const secondaryText = colors.alternativeTextColor ?? colors.darkGray; + + return { + text: { color: colors.foregroundColor, fontSize: platformSizing.titleFontSize }, + subtitle: { color: secondaryText, fontSize: platformSizing.subtitleFontSize, marginTop: isAndroid ? 5 : 2 }, + section: { marginTop: isAndroid ? 16 : 8, marginBottom: platformSizing.sectionSpacing / 2 }, + sectionCompact: { marginTop: isAndroid ? 8 : 4, marginBottom: 8 }, + sectionInset: { marginHorizontal: isAndroid ? 0 : platformSizing.horizontalPadding }, + sectionHeaderContainer: { + marginTop: platformSizing.sectionSpacing, + marginBottom: 8, + paddingHorizontal: platformSizing.horizontalPadding, + }, + sectionHeaderText: { + fontSize: isAndroid ? platformSizing.subtitleFontSize : 13, + fontWeight: isAndroid ? '500' : '400', + color: secondaryText, + }, + card: { + backgroundColor: isAndroid ? colors.background : itemSurface, + borderRadius: isAndroid ? 0 : platformSizing.containerBorderRadius, + paddingHorizontal: isAndroid ? platformSizing.horizontalPadding : 0, + paddingVertical: isAndroid ? platformSizing.verticalPadding : 0, + overflow: isAndroid ? 'visible' : 'hidden', + ...(isAndroid ? {} : { elevation: 1 }), + }, + cardCompact: { + paddingVertical: isAndroid ? platformSizing.verticalPadding : 0, + paddingHorizontal: isAndroid ? platformSizing.horizontalPadding : 0, + }, + listItemContainer: { backgroundColor: isAndroid ? colors.background : itemSurface }, + listItemContainerIOS: { marginHorizontal: platformSizing.horizontalPadding }, + listItemNoGap: { marginVertical: 0 }, + listItemContainerAndroid: { minHeight: 56 }, + listItemFirst: { + borderTopLeftRadius: platformSizing.containerBorderRadius * 1.5, + borderTopRightRadius: platformSizing.containerBorderRadius * 1.5, + }, + listItemLast: { + borderBottomLeftRadius: platformSizing.containerBorderRadius * 1.5, + borderBottomRightRadius: platformSizing.containerBorderRadius * 1.5, + }, + listItemSpacingTop: { marginTop: isAndroid ? platformSizing.sectionSpacing : 12 }, + } as const; + }, [colors, dark]); +}; + +const getIconConfig = (name: SettingsIconName, dark: boolean): IconProps => { + const configs: Record = { + settings: { + ios: { name: 'settings-outline', type: 'ionicon', color: dark ? '#FFFFFF' : '#5F6368', backgroundColor: 'rgba(142, 142, 147, 0.12)' }, + android: { name: 'settings', type: 'material', color: dark ? '#FFFFFF' : '#5F6368' }, + }, + currency: { + ios: { name: 'cash-outline', type: 'ionicon', color: dark ? '#7EE0A4' : '#0F9D58', backgroundColor: 'rgba(52, 199, 89, 0.12)' }, + android: { name: 'attach-money', type: 'material', color: dark ? '#7EE0A4' : '#0F9D58' }, + }, + language: { + ios: { name: 'language-outline', type: 'ionicon', color: dark ? '#FFD580' : '#F4B400', backgroundColor: 'rgba(255, 149, 0, 0.12)' }, + android: { name: 'language', type: 'material', color: dark ? '#FFD580' : '#F4B400' }, + }, + security: { + ios: { + name: 'shield-checkmark-outline', + type: 'ionicon', + color: dark ? '#FF8E8E' : '#DB4437', + backgroundColor: 'rgba(255, 59, 48, 0.12)', + }, + android: { name: 'security', type: 'material', color: dark ? '#FF8E8E' : '#DB4437' }, + }, + network: { + ios: { name: 'globe-outline', type: 'ionicon', color: dark ? '#82B1FF' : '#1A73E8', backgroundColor: 'rgba(0, 122, 255, 0.12)' }, + android: { name: 'public', type: 'material', color: dark ? '#82B1FF' : '#1A73E8' }, + }, + tools: { + ios: { + name: 'construct-outline', + type: 'ionicon', + color: dark ? '#D0BCFF' : '#673AB7', + backgroundColor: 'rgba(142, 142, 147, 0.12)', + }, + android: { name: 'build', type: 'material', color: dark ? '#D0BCFF' : '#673AB7' }, + }, + about: { + ios: { + name: 'information-circle-outline', + type: 'ionicon', + color: dark ? '#FFFFFF' : '#5F6368', + backgroundColor: 'rgba(142, 142, 147, 0.12)', + }, + android: { name: 'info', type: 'material', color: dark ? '#FFFFFF' : '#5F6368' }, + }, + notifications: { + ios: { + name: 'notifications-outline', + type: 'ionicon', + color: dark ? '#82B1FF' : '#1A73E8', + backgroundColor: 'rgba(142, 142, 147, 0.12)', + }, + android: { name: 'notifications', type: 'material', color: dark ? '#82B1FF' : '#1A73E8' }, + }, + lightning: { + ios: { name: 'flash-outline', type: 'ionicon', color: dark ? '#FFD580' : '#F4B400', backgroundColor: 'rgba(255, 149, 0, 0.12)' }, + android: { name: 'flash-on', type: 'material', color: dark ? '#FFD580' : '#F4B400' }, + }, + blockExplorer: { + ios: { name: 'search-outline', type: 'ionicon', color: dark ? '#82B1FF' : '#1A73E8', backgroundColor: 'rgba(0, 122, 255, 0.12)' }, + android: { name: 'search', type: 'material', color: dark ? '#82B1FF' : '#1A73E8' }, + }, + electrum: { + ios: { name: 'server-outline', type: 'ionicon', color: dark ? '#69F0AE' : '#0F9D58', backgroundColor: 'rgba(52, 199, 89, 0.12)' }, + android: { name: 'storage', type: 'material', color: dark ? '#69F0AE' : '#0F9D58' }, + }, + licensing: { + ios: { + name: 'shield-checkmark-outline', + type: 'ionicon', + color: dark ? '#FFFFFF' : '#24292e', + backgroundColor: 'rgba(142, 142, 147, 0.12)', + }, + android: { name: 'verified-user', type: 'material', color: dark ? '#FFFFFF' : '#24292e' }, + }, + releaseNotes: { + ios: { + name: 'document-text-outline', + type: 'ionicon', + color: dark ? '#FFFFFF' : '#9AA0AA', + backgroundColor: 'rgba(142, 142, 147, 0.12)', + }, + android: { name: 'description', type: 'material', color: dark ? '#FFFFFF' : '#9AA0AA' }, + }, + selfTest: { + ios: { name: 'flask-outline', type: 'ionicon', color: dark ? '#FFFFFF' : '#FC0D44', backgroundColor: 'rgba(142, 142, 147, 0.12)' }, + android: { name: 'flask-outline', type: 'material-community', color: dark ? '#FFFFFF' : '#FC0D44' }, + }, + performance: { + ios: { + name: 'speedometer-outline', + type: 'ionicon', + color: dark ? '#FFFFFF' : '#FC0D44', + backgroundColor: 'rgba(142, 142, 147, 0.12)', + }, + android: { name: 'speedometer', type: 'material-community', color: dark ? '#FFFFFF' : '#FC0D44' }, + }, + github: { + ios: { name: 'logo-github', type: 'ionicon', color: dark ? '#FFFFFF' : '#24292e', backgroundColor: 'rgba(24, 23, 23, 0.1)' }, + android: { name: 'code', type: 'material', color: dark ? '#FFFFFF' : '#24292e' }, + }, + search: { + ios: { name: 'search-outline', type: 'ionicon', color: dark ? '#82B1FF' : '#1A73E8', backgroundColor: 'rgba(0, 122, 255, 0.12)' }, + android: { name: 'search', type: 'material', color: dark ? '#82B1FF' : '#1A73E8' }, + }, + paperPlane: { + ios: { + name: 'paper-plane-outline', + type: 'ionicon', + color: dark ? '#82B1FF' : '#1A73E8', + backgroundColor: 'rgba(0, 122, 255, 0.12)', + }, + android: { name: 'send', type: 'material', color: dark ? '#82B1FF' : '#1A73E8' }, + }, + key: { + ios: { name: 'key-outline', type: 'ionicon', color: dark ? '#69F0AE' : '#0F9D58', backgroundColor: 'rgba(52, 199, 89, 0.12)' }, + android: { name: 'vpn-key', type: 'material', color: dark ? '#69F0AE' : '#0F9D58' }, + }, + }; + + const config = configs[name] ?? configs.settings; + return isIOS ? config.ios : config.android; +}; + +export const SettingsListItem: React.FC = ({ + title, + subtitle, + onPress, + testID, + chevron, + switch: switchProps, + isLoading, + Component, + rightTitle, + disabled, + checkmark, + accessibilityLabel, + accessibilityHint, + iconName, + leftIcon, + position = 'middle', + spacingTop, + itemBackgroundColor, +}) => { + const theme = useTheme(); + const { colors: themeColors, dark } = theme; + const { fontScale } = useWindowDimensions(); + + const themeStyles = usePlatformStyles(); + const cardColor = dark + ? (themeColors.modal ?? themeColors.elevated ?? themeColors.background) + : (themeColors.lightButton ?? themeColors.modal ?? themeColors.elevated ?? themeColors.background); + const defaultItemBackgroundColor = isIOS && !dark ? themeColors.background : cardColor; + const resolvedItemBackgroundColor = itemBackgroundColor ?? defaultItemBackgroundColor; + const resolvedIcon = leftIcon ?? (iconName ? getIconConfig(iconName, dark) : undefined); + const isSingle = position === 'single'; + const isFirst = position === 'first' || isSingle; + const isLast = position === 'last' || isSingle; + const resolvedAccessibilityLabel = accessibilityLabel ?? title; + const resolvedAccessibilityHint = accessibilityHint ?? (typeof subtitle === 'string' ? subtitle : undefined); + + const minHeight = Math.max(platformSizing.itemMinHeight, platformSizing.itemMinHeight * fontScale); + + const dynamicStyles = useMemo( + () => + ({ + title: { + color: themeColors.foregroundColor, + fontSize: platformSizing.titleFontSize, + fontWeight: platformSizing.titleFontWeight, + writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + }, + subtitle: { + flexWrap: 'wrap', + writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + color: themeColors.alternativeTextColor ?? themeColors.darkGray, + fontWeight: platformSizing.subtitleFontWeight, + paddingVertical: platformSizing.subtitlePaddingVertical, + lineHeight: platformSizing.subtitleLineHeight * fontScale, + fontSize: platformSizing.subtitleFontSize, + }, + container: { + backgroundColor: resolvedItemBackgroundColor, + paddingVertical: isAndroid ? 8 : platformSizing.containerPaddingVertical, + paddingHorizontal: platformSizing.horizontalPadding, + minHeight, + borderBottomWidth: platformLayout.showBorderBottom && !isLast ? StyleSheet.hairlineWidth : 0, + borderBottomColor: + platformLayout.showBorderBottom && !isLast ? (themeColors.lightBorder ?? themeColors.borderTopColor) : 'transparent', + elevation: platformLayout.showElevation ? platformSizing.containerElevation : 0, + marginVertical: isAndroid ? 0 : platformSizing.containerMarginVertical, + }, + chevron: { + color: themeColors.alternativeTextColor ?? themeColors.darkGray, + opacity: 0.7, + }, + }) as const, + [fontScale, minHeight, isLast, resolvedItemBackgroundColor, themeColors], + ); + + const containerStyle = [ + themeStyles.listItemContainer, + themeStyles.listItemNoGap, + isAndroid && themeStyles.listItemContainerAndroid, + isIOS && themeStyles.listItemContainerIOS, + isIOS && isFirst && themeStyles.listItemFirst, + isIOS && isLast && themeStyles.listItemLast, + spacingTop && themeStyles.listItemSpacingTop, + ]; + + const borderRadius = platformSizing.containerBorderRadius * 1.5; + const dynamicContainerStyle = platformLayout.useRoundedListItems + ? { + borderRadius: 0, + overflow: 'hidden' as const, + ...(isFirst && { borderTopLeftRadius: borderRadius, borderTopRightRadius: borderRadius }), + ...(isLast && { borderBottomLeftRadius: borderRadius, borderBottomRightRadius: borderRadius }), + ...(isFirst && isLast && { borderRadius }), + } + : { + borderRadius: 0, + elevation: platformLayout.showElevation ? platformSizing.containerElevation : 0, + marginVertical: isAndroid ? 0 : 1, + backgroundColor: cardColor, + }; + + const outerContainerStyle = [ + containerStyle, + dynamicContainerStyle, + { + backgroundColor: dynamicStyles.container.backgroundColor, + borderBottomWidth: dynamicStyles.container.borderBottomWidth, + borderBottomColor: dynamicStyles.container.borderBottomColor, + elevation: dynamicStyles.container.elevation, + marginVertical: dynamicStyles.container.marginVertical, + }, + ]; + + const innerContainerStyle = [ + dynamicStyles.container, + { + backgroundColor: dynamicStyles.container.backgroundColor, + borderBottomWidth: 0, + borderBottomColor: 'transparent', + borderRadius: 0, + elevation: 0, + marginVertical: 0, + }, + ]; + + const memoizedSwitchProps = useMemo(() => (switchProps ? { ...switchProps } : undefined), [switchProps]); + + const accessibilityProps = { + accessible: true, + accessibilityRole: switchProps ? ('switch' as const) : ('button' as const), + accessibilityLabel: resolvedAccessibilityLabel, + accessibilityHint: resolvedAccessibilityHint, + accessibilityState: { disabled, checked: switchProps?.value }, + }; + + const renderLeftIcon = () => { + if (!resolvedIcon) return null; + if (!isIconProps(resolvedIcon)) { + return ( + + {resolvedIcon} + + ); + } + return ( + + {renderVectorIcon(resolvedIcon)} + + ); + }; + + const renderContent = () => ( + <> + {renderLeftIcon()} + + + {title} + + {subtitle && ( + + {subtitle} + + )} + + {rightTitle && ( + + + {rightTitle} + + + )} + {isLoading ? ( + + ) : ( + <> + {chevron && ( + + )} + {switchProps && ( + + )} + {checkmark && ( + + )} + + )} + + ); + + if (isAndroid && platformLayout.rippleEffect && onPress) { + return ( + + {renderContent()} + + ); + } + + return ( + + + {(() => { + const Container = (onPress ? (Component ?? TouchableOpacity) : View) as React.ElementType; + return ( + + {renderContent()} + + ); + })()} + + + ); +}; + +const staticStyles = StyleSheet.create({ + contentContainer: { paddingHorizontal: 0 }, + iconContainerBase: { alignItems: 'center', justifyContent: 'center' }, + flexGrow: { flexGrow: 1, flexShrink: 1 }, + margin8: { margin: 8 }, + touchableContent: { flexDirection: 'row', alignItems: 'center' }, + switchMargin: { marginLeft: 16 }, + checkmarkIcon: { marginLeft: 8 }, + transformRTL: { transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }] }, + androidRippleContainer: { overflow: 'hidden', flexDirection: 'row', alignItems: 'center' }, +}); diff --git a/components/themes.ts b/components/themes.ts index 802d8b00210..e1906c810ac 100644 --- a/components/themes.ts +++ b/components/themes.ts @@ -1,4 +1,4 @@ -import { DefaultTheme, DarkTheme, useTheme as useThemeBase } from '@react-navigation/native'; +import { DarkTheme, DefaultTheme, useTheme as useThemeBase } from '@react-navigation/native'; import { Appearance } from 'react-native'; export const BlueDefaultTheme = { @@ -8,12 +8,14 @@ export const BlueDefaultTheme = { scanImage: require('../img/scan.png'), colors: { ...DefaultTheme.colors, + borderWidth: 0.5, brandingColor: '#ffffff', customHeader: '#ffffff', foregroundColor: '#0c2550', borderTopColor: 'rgba(0, 0, 0, 0.1)', buttonBackgroundColor: '#ccddf9', buttonTextColor: '#0c2550', + secondButtonTextColor: '#50555C', buttonAlternativeTextColor: '#2f5fb3', buttonDisabledBackgroundColor: '#eef0f4', buttonDisabledTextColor: '#9aa0aa', @@ -22,12 +24,14 @@ export const BlueDefaultTheme = { alternativeTextColor: '#9aa0aa', alternativeTextColor2: '#0f5cc0', buttonBlueBackgroundColor: '#ccddf9', + buttonGrayBackgroundColor: '#EEEEEE', incomingBackgroundColor: '#d2f8d6', incomingForegroundColor: '#37c0a1', outgoingBackgroundColor: '#f8d2d2', outgoingForegroundColor: '#d0021b', successColor: '#37c0a1', failedColor: '#ff0000', + placeholderTextColor: '#81868e', shadowColor: '#000000', inverseForegroundColor: '#ffffff', hdborderColor: '#68BBE1', @@ -35,9 +39,9 @@ export const BlueDefaultTheme = { lnborderColor: '#FFB600', lnbackgroundColor: '#FFFAEF', background: '#FFFFFF', - lightButton: '#eef0f4', - ballReceive: '#d2f8d6', - ballOutgoing: '#f8d2d2', + lightButton: 'rgba(0, 0, 0, 0.05)', + ballReceive: 'rgba(31, 221, 26, 0.2)', + ballOutgoing: 'rgba(234, 51, 47, 0.2)', lightBorder: '#ededed', ballOutgoingExpired: '#EEF0F4', modal: '#ffffff', @@ -65,6 +69,18 @@ export const BlueDefaultTheme = { changeText: '#F38C47', receiveBackground: '#D1F9D6', receiveText: '#37C0A1', + androidRippleColor: '#CCCCCC', + transactionPendingColor: '#2757C6', + transactionPendingBackgroundColor: '#DBEFFD', + transactionPendingIconBackground: 'rgba(0, 60, 240, 0.1)', + transactionPendingAnimationColor: '#2757C6', + transactionStateBumpButtonBackground: 'rgba(255, 255, 255, 0.4)', + transactionStateCancelButtonBackground: 'rgba(0, 0, 0, 0.05)', + transactionSentColor: '#BF2828', + transactionReceivedColor: '#63BDA2', + cardSectionBackground: '#F9F9F9', + cardSectionHeaderBackground: '#F2F2F2', + cardBorderColor: 'rgba(0, 0, 0, 0.05)', }, }; @@ -81,6 +97,7 @@ export const BlueDarkTheme: Theme = { customHeader: '#000000', brandingColor: '#000000', borderTopColor: '#9aa0aa', + background: '#000000', foregroundColor: '#ffffff', buttonDisabledBackgroundColor: '#3A3A3C', buttonBackgroundColor: '#3A3A3C', @@ -89,8 +106,8 @@ export const BlueDarkTheme: Theme = { buttonAlternativeTextColor: '#ffffff', alternativeTextColor: '#9aa0aa', alternativeTextColor2: '#0A84FF', - ballReceive: '#202020', - ballOutgoing: '#202020', + ballReceive: 'rgba(31, 221, 26, 0.2)', + ballOutgoing: 'rgba(234, 51, 47, 0.2)', lightBorder: '#313030', ballOutgoingExpired: '#202020', modal: '#202020', @@ -120,12 +137,37 @@ export const BlueDarkTheme: Theme = { changeText: '#F38C47', receiveBackground: 'rgba(210,248,214,.2)', receiveText: '#37C0A1', + outgoingBackgroundColor: 'rgba(187, 6, 6, 0.2)', + outgoingForegroundColor: '#FC6D6D', + incomingBackgroundColor: 'rgba(5, 159, 54, 0.3)', + incomingForegroundColor: '#37C0A1', + transactionPendingIconBackground: 'rgba(90, 158, 255, 0.3)', + transactionPendingAnimationColor: '#5A9EFF', + androidRippleColor: '#444444', + transactionPendingColor: '#5A9EFF', + transactionPendingBackgroundColor: 'rgba(10, 132, 255, 0.15)', + transactionStateBumpButtonBackground: 'rgba(255, 255, 255, 0.15)', + transactionStateCancelButtonBackground: 'rgba(255, 255, 255, 0.08)', + transactionSentColor: '#FC6D6D', + transactionReceivedColor: '#37C0A1', + cardSectionBackground: '#1C1C1E', + cardSectionHeaderBackground: '#2C2C2E', + cardBorderColor: 'rgba(255, 255, 255, 0.08)', }, }; // Casting theme value to get autocompletion export const useTheme = (): Theme => useThemeBase() as Theme; +export const platformColors = { + background: BlueDefaultTheme.colors.background, + card: BlueDefaultTheme.colors.modal ?? BlueDefaultTheme.colors.elevated ?? BlueDefaultTheme.colors.background, + text: BlueDefaultTheme.colors.foregroundColor, + secondaryText: BlueDefaultTheme.colors.alternativeTextColor ?? BlueDefaultTheme.colors.darkGray, + separator: BlueDefaultTheme.colors.lightBorder ?? BlueDefaultTheme.colors.borderTopColor, + chevron: BlueDefaultTheme.colors.alternativeTextColor ?? BlueDefaultTheme.colors.darkGray, +}; + export class BlueCurrentTheme { static colors: Theme['colors']; static closeImage: Theme['closeImage']; @@ -136,6 +178,13 @@ export class BlueCurrentTheme { BlueCurrentTheme.colors = isColorSchemeDark ? BlueDarkTheme.colors : BlueDefaultTheme.colors; BlueCurrentTheme.closeImage = isColorSchemeDark ? BlueDarkTheme.closeImage : BlueDefaultTheme.closeImage; BlueCurrentTheme.scanImage = isColorSchemeDark ? BlueDarkTheme.scanImage : BlueDefaultTheme.scanImage; + const colors = BlueCurrentTheme.colors; + platformColors.background = colors.background; + platformColors.card = colors.modal ?? colors.elevated ?? colors.background; + platformColors.text = colors.foregroundColor; + platformColors.secondaryText = colors.alternativeTextColor ?? colors.darkGray; + platformColors.separator = colors.lightBorder ?? colors.borderTopColor; + platformColors.chevron = colors.alternativeTextColor ?? colors.darkGray; } } diff --git a/components/types.ts b/components/types.ts new file mode 100644 index 00000000000..3544355cd82 --- /dev/null +++ b/components/types.ts @@ -0,0 +1,57 @@ +import { AccessibilityRole, ViewStyle, ColorValue, GestureResponderEvent } from 'react-native'; + +export interface Action { + id: string | number; + text: string; + icon?: { + iconValue: string; + }; + menuTitle?: string; + subtitle?: string; + menuState?: 'mixed' | boolean | undefined; + displayInline?: boolean; // Indicates if subactions should be displayed inline or nested (iOS only) + image?: string; + imageColor?: ColorValue; + destructive?: boolean; + hidden?: boolean; + disabled?: boolean; + subactions?: Action[]; // Nested/Inline actions (subactions) within an action +} + +export interface ToolTipMenuProps { + actions: Action[] | Action[][]; + children: React.ReactNode; + enableAndroidRipple?: boolean; + onPressMenuItem: (id: string) => void; + title?: string; + // When true (default) the menu opens on long-press; when false it opens + // on a single tap (dropdown mode). + shouldOpenOnLongPress?: boolean; + // Hint that the trigger should be styled like a button (e.g. center it). + isButton?: boolean; + // Optional short-tap action. When provided, the trigger is wrapped in a + // Pressable so that a quick tap fires this callback while a long-press + // (or single tap in dropdown mode) still opens the native menu. + onPress?: (event: GestureResponderEvent) => void; + accessibilityRole?: AccessibilityRole; + disabled?: boolean; + testID?: string; + style?: ViewStyle | ViewStyle[]; + accessibilityLabel?: string; + accessibilityHint?: string; + accessibilityState?: object; + buttonStyle?: ViewStyle | ViewStyle[]; +} + +export enum HandOffActivityType { + ReceiveOnchain = 'io.bluewallet.bluewallet.receiveonchain', + Xpub = 'io.bluewallet.bluewallet.xpub', + ViewInBlockExplorer = 'io.bluewallet.bluewallet.blockexplorer', +} + +export interface HandOffComponentProps { + url?: string; + title?: string; + type: HandOffActivityType; + userInfo?: object; +} diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 00000000000..18e61f786d0 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,3 @@ +app_identifier("io.bluewallet.bluewallet") +apple_id(ENV["APPLE_ID"]) # Your Apple email ID +itc_team_id(ENV["ITC_TEAM_ID"]) # App Store Connect Team ID \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 00000000000..3c226c7e0e3 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,1740 @@ +# Define app identifiers once for reuse across lanes +def app_identifiers + [ + "io.bluewallet.bluewallet", + "io.bluewallet.bluewallet.watch", + "io.bluewallet.bluewallet.watch.extension", + "io.bluewallet.bluewallet.Stickers", + "io.bluewallet.bluewallet.MarketWidget" + ] +end + +def app_store_state_readable(state) + states = { + "DEVELOPER_REJECTED" => "Developer Rejected", + "PREPARE_FOR_SUBMISSION" => "Prepare for Submission", + "WAITING_FOR_REVIEW" => "Waiting for Review", + "IN_REVIEW" => "In Review", + "PENDING_DEVELOPER_RELEASE" => "Pending Developer Release", + "READY_FOR_SALE" => "Ready for Sale", + "REJECTED" => "Rejected", + "METADATA_REJECTED" => "Metadata Rejected" + } + + states[state] || state +end + +require 'securerandom' + +default_platform(:android) +PROJECT_ROOT = File.expand_path("..", __dir__) +project_root = PROJECT_ROOT + +module AndroidHelpers + module_function + + def require_env!(keys) + missing = Array(keys).select { |key| ENV[key].nil? || ENV[key].empty? } + UI.user_error!("Missing required env vars: #{missing.join(', ')}") unless missing.empty? + end + + def project_root + PROJECT_ROOT + end + + def keystore_paths + { + hex: File.join(project_root, 'bluewallet-release-key.keystore.hex'), + file: File.join(project_root, 'android', 'bluewallet-release-key.keystore'), + } + end + + def write_keystore_from_hex!(hex_value, paths) + UI.user_error!("KEYSTORE_FILE_HEX environment variable is missing") if hex_value.nil? || hex_value.empty? + File.write(paths[:hex], hex_value) + Actions.sh("xxd -plain -revert #{paths[:hex]} > #{paths[:file]}") do |status| + UI.user_error!("Error reverting hex to keystore") unless status.success? + end + File.delete(paths[:hex]) + end + + def version_name_and_update_code!(build_gradle_path, build_number) + gradle_contents = File.read(build_gradle_path) + File.write(build_gradle_path, gradle_contents.gsub(/versionCode\s+\d+/, "versionCode #{build_number}")) + version_name = File.read(build_gradle_path)[/versionName\s+"([^"]+)"/, 1] + UI.user_error!("Failed to extract versionName from #{build_gradle_path}") if version_name.nil? || version_name.empty? + version_name + end + + def branch_name + raw = ENV['GITHUB_HEAD_REF'] || ENV['GITHUB_REF_NAME'] || `git rev-parse --abbrev-ref HEAD`.strip + sanitized = raw.to_s.gsub(/[^a-zA-Z0-9_-]/, '_') + sanitized.empty? ? 'master' : sanitized + end + + def apk_name(version_name, build_number, branch) + branch != 'master' ? "BlueWallet-#{version_name}-#{build_number}-#{branch}.apk" : "BlueWallet-#{version_name}-#{build_number}.apk" + end + + def apksigner_path + sdk_root = ENV['ANDROID_HOME'] || ENV['ANDROID_SDK_ROOT'] + Dir.glob(File.join(sdk_root.to_s, 'build-tools', '*', 'apksigner')).sort.last + end + + def gradle_log_path + path = File.join(project_root, 'fastlane', 'logs', 'gradle-build.log') + FileUtils.mkdir_p(File.dirname(path)) + path + end + + def assemble_release!(log_path:) + Actions.sh("cd android && ./gradlew assembleRelease --no-daemon --stacktrace --console=plain | tee #{log_path}") do |status| + UI.user_error!("Gradle assembleRelease failed") unless status.success? + end + end + + def resolve_apk_paths(version_name:, build_number:, branch_name:) + apk_dir = File.join(project_root, 'android', 'app', 'build', 'outputs', 'apk', 'release') + unsigned = File.join(apk_dir, 'app-release-unsigned.apk') + fallback = File.join(apk_dir, 'app-release.apk') + signed = File.join(apk_dir, apk_name(version_name, build_number, branch_name)) + { unsigned: unsigned, fallback: fallback, signed: signed } + end + + def finalize_apk!(paths) + candidate = File.exist?(paths[:unsigned]) ? paths[:unsigned] : paths[:fallback] + UI.user_error!("Unsigned APK not found at path: #{paths[:unsigned]} or #{paths[:fallback]}") unless File.exist?(candidate) + FileUtils.mv(candidate, paths[:signed]) + paths[:signed] + end + + def sign_apk!(apk_path, keystore_path, keystore_password) + signer = apksigner_path + UI.user_error!("apksigner not found in Android build-tools") if signer.nil? || signer.empty? + Actions.sh("#{signer} sign --ks #{keystore_path} --ks-pass=pass:#{keystore_password} #{apk_path}") + end + + def write_github_output(hash) + return unless ENV['GITHUB_OUTPUT'] && !ENV['GITHUB_OUTPUT'].empty? + + File.open(ENV['GITHUB_OUTPUT'], 'a') do |f| + hash.each { |key, value| f.puts("#{key}=#{value}") } + end + end + + def ensure_temp_credentials!(auto: false) + return unless auto + + temp_dir = Dir.mktmpdir('bw-temp-keystore') + keystore_path = File.join(temp_dir, 'temp.keystore') + password = "temp#{SecureRandom.hex(6)}" + alias_name = "bluewallet-temp" + + Actions.sh( + "keytool -genkeypair -v -keystore #{keystore_path} -storepass #{password} -keypass #{password} " \ + "-alias #{alias_name} -keyalg RSA -keysize 2048 -validity 10000 " \ + "-dname \"CN=Temp,O=BlueWallet,OU=CI,L=NY,ST=NY,C=US\"" + ) do |status| + UI.user_error!("Failed to create temporary keystore with keytool") unless status.success? + end + + keystore_hex = File.binread(keystore_path).unpack1('H*') + ENV['KEYSTORE_FILE_HEX'] = keystore_hex + ENV['KEYSTORE_PASSWORD'] = password + end +end + +# Add session caching for App Store Connect +def cached_app_store_connect_login + # Skip if already authenticated and token is not expired + if defined?(Spaceship::ConnectAPI) && Spaceship::ConnectAPI.token && !Spaceship::ConnectAPI.token.expired? + UI.message("Using existing App Store Connect session") + return true + end + + UI.message("Logging in to App Store Connect...") + + # Try API key authentication first + api_key_path = ENV["APPLE_API_KEY_PATH"] || "./appstore_api_key.json" + + if File.exist?(api_key_path) && ENV["APPLE_API_KEY_ID"] && ENV["APPLE_API_ISSUER_ID"] + UI.message("Using API key authentication for App Store Connect") + api_key = app_store_connect_api_key( + key_id: ENV["APPLE_API_KEY_ID"], + issuer_id: ENV["APPLE_API_ISSUER_ID"], + key_filepath: api_key_path, + duration: 1200, # 20 minute session + in_house: false + ) + + # Store the API key in lane context for reuse + ENV["SPACESHIP_CONNECT_API_KEY"] = api_key + + # Force App Store Connect API to use this key + Spaceship::ConnectAPI.token = api_key + + UI.success("Successfully authenticated with App Store Connect API Key") + return true + elsif ENV["FASTLANE_USER"] && ENV["FASTLANE_PASSWORD"] + UI.message("Using username/password from environment variables") + # Use credentials from environment variables + Spaceship::ConnectAPI.login( + use_portal: true, + use_tunes: true, + portal_team_id: ENV["TEAM_ID"], + tunes_team_id: ENV["ITC_TEAM_ID"] + ) + UI.success("Successfully authenticated with Apple ID") + return true + else + UI.message("Using interactive username/password authentication") + # Last resort - interactive login + Spaceship::ConnectAPI.login( + use_portal: true, + use_tunes: true + ) + UI.success("Successfully authenticated with Apple ID") + return true + end +end + +before_all do |lane, options| + if ENV['SKIP_APP_STORE_CONNECT_AUTH'] == '1' + UI.message('Skipping App Store Connect authentication (SKIP_APP_STORE_CONNECT_AUTH=1)') + next + end + + skip_auth_lanes = ['register_devices_from_txt', 'build_catalyst_app_lane', 'install_pods', 'clear_derived_data_lane'] + lane_name = lane.to_s + should_skip_auth = skip_auth_lanes.any? { |skip_lane| lane_name == skip_lane || lane_name.end_with?(" #{skip_lane}") } + + # Check if we need App Store Connect for this lane + unless should_skip_auth + begin + # Try to authenticate once at the beginning + require 'spaceship' + cached_app_store_connect_login + rescue => ex + UI.error("Authentication failed: #{ex.message}") + # Continue anyway as some lanes might not need authentication + end + end +end + +# =========================== +# Android Lanes +# =========================== + +platform :android do + + desc "Prepare the keystore file" + lane :prepare_keystore do + Dir.chdir(PROJECT_ROOT) do + paths = AndroidHelpers.keystore_paths + AndroidHelpers.write_keystore_from_hex!(ENV['KEYSTORE_FILE_HEX'], paths) + UI.message("Keystore created successfully.") + end + end + + desc "Update version, build number, and sign APK" + lane :update_version_build_and_sign_apk do |options| + Dir.chdir(PROJECT_ROOT) do + AndroidHelpers.ensure_temp_credentials!(auto: options[:auto_credentials]) + AndroidHelpers.require_env!(%w[BUILD_NUMBER KEYSTORE_PASSWORD KEYSTORE_FILE_HEX]) + + keystore_paths = AndroidHelpers.keystore_paths + AndroidHelpers.write_keystore_from_hex!(ENV['KEYSTORE_FILE_HEX'], keystore_paths) + + build_gradle_path = File.join('android', 'app', 'build.gradle') + version_name = AndroidHelpers.version_name_and_update_code!(build_gradle_path, ENV['BUILD_NUMBER']) + branch = AndroidHelpers.branch_name + apk_paths = AndroidHelpers.resolve_apk_paths(version_name: version_name, build_number: ENV['BUILD_NUMBER'], branch_name: branch) + + UI.message("Building APK...") + AndroidHelpers.assemble_release!(log_path: AndroidHelpers.gradle_log_path) + UI.message("APK build completed.") + + signed_apk_path = AndroidHelpers.finalize_apk!(apk_paths) + ENV['APK_OUTPUT_PATH'] = File.expand_path(signed_apk_path) + + UI.message("Signing APK with apksigner...") + AndroidHelpers.sign_apk!(signed_apk_path, keystore_paths[:file], ENV['KEYSTORE_PASSWORD']) + UI.message("APK signed successfully: #{signed_apk_path}") + + FileUtils.rm_f(keystore_paths[:file]) + end + end + + desc "Build and sign release APK" + lane :build_release_apk do |options| + Dir.chdir(PROJECT_ROOT) do + # Allow caller to pass a build_number; otherwise fall back to env or timestamp + build_number = options[:build_number] || ENV['BUILD_NUMBER'] || Time.now.to_i.to_s + ENV['BUILD_NUMBER'] = build_number + + AndroidHelpers.ensure_temp_credentials!(auto: options[:auto_credentials]) + AndroidHelpers.require_env!(%w[KEYSTORE_FILE_HEX KEYSTORE_PASSWORD]) + + keystore_paths = AndroidHelpers.keystore_paths + AndroidHelpers.write_keystore_from_hex!(ENV['KEYSTORE_FILE_HEX'], keystore_paths) + + build_gradle_path = File.join('android', 'app', 'build.gradle') + version_name = AndroidHelpers.version_name_and_update_code!(build_gradle_path, build_number) + branch = AndroidHelpers.branch_name + + UI.message("Building release APK...") + AndroidHelpers.assemble_release!(log_path: AndroidHelpers.gradle_log_path) + + apk_paths = AndroidHelpers.resolve_apk_paths(version_name: version_name, build_number: build_number, branch_name: branch) + signed_apk_path = AndroidHelpers.finalize_apk!(apk_paths) + + UI.message("Signing APK with apksigner...") + AndroidHelpers.sign_apk!(signed_apk_path, keystore_paths[:file], ENV['KEYSTORE_PASSWORD']) + UI.success("APK signed successfully: #{signed_apk_path}") + + FileUtils.rm_f(keystore_paths[:file]) + + apk_absolute_path = File.expand_path(signed_apk_path) + ENV['APK_OUTPUT_PATH'] = apk_absolute_path + ENV['APK_VERSION_NAME'] = version_name + ENV['APK_VERSION_CODE'] = build_number + + AndroidHelpers.write_github_output( + apk_output_path: apk_absolute_path, + apk_version_name: version_name, + apk_version_code: build_number, + ) + end + end + + desc "Upload APK to BrowserStack and post result as PR comment" + lane :upload_to_browserstack_and_comment do + Dir.chdir(PROJECT_ROOT) do + apk_path = ENV['APK_PATH'] + if apk_path.nil? || apk_path.empty? + UI.message("No APK path provided, searching for APK...") + apk_path = `find ./ -name "*.apk"`.strip + UI.user_error!("No APK file found") if apk_path.nil? || apk_path.empty? + end + + UI.message("Uploading APK to BrowserStack: #{apk_path}...") + upload_to_browserstack_app_live( + file_path: apk_path, + browserstack_username: ENV['BROWSERSTACK_USERNAME'], + browserstack_access_key: ENV['BROWSERSTACK_ACCESS_KEY'] + ) + + app_url = ENV['BROWSERSTACK_LIVE_APP_ID'] + UI.user_error!("BrowserStack upload failed, no app URL returned") if app_url.nil? || app_url.empty? + + apk_filename = File.basename(apk_path) + apk_download_url = ENV['APK_OUTPUT_PATH'] + browserstack_hashed_id = app_url.gsub('bs://', '') + pr_number = ENV['GITHUB_PR_NUMBER'] + + comment_identifier = '### APK Successfully Uploaded to BrowserStack' + + comment = <<~COMMENT + #{comment_identifier} + + You can test it on the following devices: + + - [Google Pixel 9 (Android 15)](https://app-live.browserstack.com/dashboard#os=android&os_version=15.0&device=Google+Pixel+8&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome) + - [Google Pixel 8 (Android 14)](https://app-live.browserstack.com/dashboard#os=android&os_version=14.0&device=Google+Pixel+8&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome) + - [Google Pixel 7 (Android 13)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Google+Pixel+7&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome) + - [Google Pixel 5 (Android 12)](https://app-live.browserstack.com/dashboard#os=android&os_version=12.0&device=Google+Pixel+5&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome) + - [Google Pixel 3a (Android 9)](https://app-live.browserstack.com/dashboard#os=android&os_version=9.0&device=Google+Pixel+3a&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome) + + - [Samsung Galaxy Z Fold 6 (Android 14)](https://app-live.browserstack.com/dashboard#os=android&os_version=14.0&device=Samsung+Galaxy+Z+Fold+6&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome) + - [Samsung Galaxy Z Fold 5 (Android 13)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Samsung+Galaxy+Z+Fold+5&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome) + - [Samsung Galaxy Tab S9 (Android 13)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Samsung+Galaxy+Tab+S9&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome) + - [Samsung Galaxy Note 9 (Android 8.1)](https://app-live.browserstack.com/dashboard#os=android&os_version=8.1&device=Samsung+Galaxy+Note+9&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome) + + - [OnePlus 11R (Android 13)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=OnePlus+11R&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome) + **Filename**: [#{apk_filename}](#{apk_download_url}) + **BrowserStack App URL**: #{app_url} + COMMENT + + if pr_number + begin + repo = ENV['GITHUB_REPOSITORY'] + repo_owner, repo_name = repo.split('/') + + UI.message("Fetching existing comments for PR ##{pr_number}...") + + comments_json = `gh api -X GET /repos/#{repo_owner}/#{repo_name}/issues/#{pr_number}/comments` + comments = JSON.parse(comments_json) + + comments.each do |comment| + if comment['body'].start_with?(comment_identifier) + comment_id = comment['id'] + UI.message("Deleting previous comment ID: #{comment_id}...") + `gh api -X DELETE /repos/#{repo_owner}/#{repo_name}/issues/comments/#{comment_id}` + UI.success("Deleted comment ID: #{comment_id}") + end + end + + rescue => e + UI.error("Failed to delete previous comments: #{e.message}") + end + else + UI.important("No PR number found. Skipping deletion of previous comments.") + end + + if pr_number + begin + escaped_comment = comment.gsub("'", "'\\''") + sh("GH_TOKEN=#{ENV['GH_TOKEN']} gh pr comment #{pr_number} --body '#{escaped_comment}'") + UI.success("Posted new comment to PR ##{pr_number}") + rescue => e + UI.error("Failed to post comment to PR: #{e.message}") + end + else + UI.important("No PR number found. Skipping PR comment.") + end + end + end +end + + +# =========================== +# iOS Lanes +# =========================== + +platform :ios do + # Add helper methods for error handling and retries + def ensure_env_vars(vars) + vars.each do |var| + UI.user_error!("#{var} environment variable is missing") if ENV[var].nil? || ENV[var].empty? + end + end + + def log_success(message) + UI.success("✅ #{message}") + end + + def log_error(message) + UI.error("❌ #{message}") + end + + # Method to safely call actions with retry logic + def with_retry(max_attempts = 3, action_name = "") + attempts = 0 + begin + attempts += 1 + yield + rescue => e + if attempts < max_attempts + wait_time = 10 * attempts + log_error("Attempt #{attempts}/#{max_attempts} for #{action_name} failed: #{e.message}") + UI.message("Retrying in #{wait_time} seconds...") + sleep(wait_time) + retry + else + log_error("#{action_name} failed after #{max_attempts} attempts: #{e.message}") + raise e + end + end + end + + desc "Register new devices from a file" + lane :register_devices_from_txt do + UI.message("Registering new devices from file...") + + csv_path = "../../devices.txt" # Update this with the actual path to your file + + # Register devices using the devices_file parameter + register_devices( + devices_file: csv_path + ) + + UI.message("Devices registered successfully.") + + # Update provisioning profiles for all app identifiers + app_identifiers.each do |app_identifier| + match( + type: "development", + app_identifier: app_identifier, + readonly: false, # Regenerate provisioning profile if needed + force_for_new_devices: true, + clone_branch_directly: true + ) + end + + UI.message("Development provisioning profiles updated.") + end + + desc "Create a temporary keychain" + lane :create_temp_keychain do + UI.message("Creating a temporary keychain...") + + create_keychain( + name: "temp_keychain", + password: ENV["KEYCHAIN_PASSWORD"], + default_keychain: true, + unlock: true, + timeout: 3600, + lock_when_sleeps: true + ) + + UI.message("Temporary keychain created successfully.") + end + + desc "Synchronize certificates and provisioning profiles" + lane :setup_provisioning_profiles do + required_vars = ["GIT_ACCESS_TOKEN", "GIT_URL", "ITC_TEAM_ID", "ITC_TEAM_NAME", "KEYCHAIN_PASSWORD"] + ensure_env_vars(required_vars) + + UI.message("Setting up provisioning profiles...") + + # Iterate over app identifiers to fetch provisioning profiles + app_identifiers.each do |app_identifier| + with_retry(3, "Fetching provisioning profile for #{app_identifier}") do + UI.message("Fetching provisioning profile for #{app_identifier}...") + match( + git_basic_authorization: ENV["GIT_ACCESS_TOKEN"], + git_url: ENV["GIT_URL"], + type: "appstore", + clone_branch_directly: true, + platform: "ios", + app_identifier: app_identifier, + team_id: ENV["ITC_TEAM_ID"], + team_name: ENV["ITC_TEAM_NAME"], + readonly: true, + keychain_name: "temp_keychain", + keychain_password: ENV["KEYCHAIN_PASSWORD"] + ) + log_success("Successfully fetched provisioning profile for #{app_identifier}") + end + end + + log_success("All provisioning profiles set up") + end + + # Only these targets support Mac Catalyst (watch/stickers do not) + def catalyst_app_identifiers + [ + "io.bluewallet.bluewallet", + "io.bluewallet.bluewallet.MarketWidget" + ] + end + + desc "Fetch development certificates and provisioning profiles for Mac Catalyst" + lane :fetch_dev_profiles_catalyst do + match( + type: "development", + platform: "catalyst", + app_identifier: catalyst_app_identifiers, + readonly: true, + clone_branch_directly: true + ) + end + + desc "Fetch App Store certificates and provisioning profiles for Mac Catalyst" + lane :fetch_appstore_profiles_catalyst do + match( + type: "appstore", + platform: "catalyst", + app_identifier: catalyst_app_identifiers, + readonly: true, + clone_branch_directly: true + ) + end + + desc "Create provisioning profiles for Mac Catalyst (first-time setup)" + lane :setup_catalyst_provisioning_profiles do + catalyst_app_identifiers.each do |app_identifier| + match( + type: "development", + platform: "catalyst", + app_identifier: app_identifier, + readonly: false, + force_for_new_devices: true, + clone_branch_directly: true + ) + + match( + type: "appstore", + platform: "catalyst", + app_identifier: app_identifier, + readonly: false, + clone_branch_directly: true + ) + end + end + + desc "Clear derived data" + lane :clear_derived_data_lane do + UI.message("Clearing derived data...") + clear_derived_data + end + + desc "Increment build number" + lane :increment_build_number_lane do + UI.message("Incrementing build number to current timestamp...") + + # Set the new build number + increment_build_number( + xcodeproj: "ios/BlueWallet.xcodeproj", + build_number: ENV["NEW_BUILD_NUMBER"] + ) + + UI.message("Build number set to: #{ENV['NEW_BUILD_NUMBER']}") + end + + desc "Install CocoaPods dependencies" + lane :install_pods do + UI.message("Installing CocoaPods dependencies...") + cocoapods(podfile: "ios/Podfile", + try_repo_update_on_error: true, + repo_update: true, + + clean_install: true) + end + + desc "Build Mac Catalyst app, handle code signing, create DMG with multilingual README, and set GitHub outputs" + lane :build_catalyst_app_lane do + Dir.chdir(project_root) do + UI.message("Building Mac Catalyst application from: #{Dir.pwd}") + + workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace") + derived_data_path = File.join(project_root, "ios", "build", "catalyst-derived-data") + output_dir = File.join(project_root, "ios", "build", "catalyst-output") + + if ENV['SKIP_CLEAR_DERIVED_DATA'] == '1' + UI.message('Skipping clear_derived_data_lane (SKIP_CLEAR_DERIVED_DATA=1)') + else + clear_derived_data_lane + end + FileUtils.mkdir_p(derived_data_path) + FileUtils.mkdir_p(output_dir) + + # Only these targets support Mac Catalyst + catalyst_identifiers = [ + "io.bluewallet.bluewallet", + "io.bluewallet.bluewallet.MarketWidget" + ] + + has_signing_creds = ENV['CATALYST_SIGNING_IDENTITY'] && !ENV['CATALYST_SIGNING_IDENTITY'].empty? && + ENV['CATALYST_TEAM_ID'] && !ENV['CATALYST_TEAM_ID'].empty? + has_match_creds = ENV['GIT_URL'] && !ENV['GIT_URL'].empty? && + ENV['GIT_ACCESS_TOKEN'] && !ENV['GIT_ACCESS_TOKEN'].empty? + should_sign = has_signing_creds && has_match_creds && ENV['CATALYST_SKIP_CODESIGNING'] != '1' + signing_identity = ENV['CATALYST_SIGNING_IDENTITY'] || "Apple Distribution" + + xcargs_str = "ARCHS=arm64 ONLY_ACTIVE_ARCH=YES" + if should_sign + UI.message("Setting up Mac Catalyst provisioning profiles via match...") + team_id = ENV['CATALYST_TEAM_ID'] + match_readonly = ENV['MATCH_READONLY'] != 'false' + + # Create/fetch provisioning profiles for catalyst targets + catalyst_identifiers.each do |app_id| + match( + type: "appstore", + platform: "catalyst", + app_identifier: app_id, + team_id: team_id, + git_url: ENV['GIT_URL'], + git_basic_authorization: ENV['GIT_ACCESS_TOKEN'], + readonly: match_readonly, + clone_branch_directly: true, + keychain_name: ENV['KEYCHAIN_NAME'] || "login", + keychain_password: ENV['KEYCHAIN_PASSWORD'] || "" + ) + end + + xcargs_str += " DEVELOPMENT_TEAM=#{team_id}" + xcargs_str += " CODE_SIGN_IDENTITY=\"#{signing_identity}\"" + xcargs_str += " CODE_SIGN_STYLE=Manual" + + # Set provisioning profile specifiers per catalyst target + catalyst_identifiers.each do |app_id| + profile_name = "match AppStore #{app_id} catalyst" + # Convert bundle ID to xcodebuild target setting key + # e.g., io.bluewallet.bluewallet -> PROVISIONING_PROFILE_SPECIFIER for that target + xcargs_str += " PROVISIONING_PROFILE_SPECIFIER_#{app_id.gsub('.', '_')}=\"#{profile_name}\"" + end + + UI.success("Provisioning profiles configured for Mac Catalyst") + else + # Disable code signing entirely so xcodebuild doesn't look for provisioning profiles + xcargs_str += " CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO" + UI.message("No signing credentials provided — building without code signing") + end + + build_app( + scheme: "BlueWallet", + workspace: workspace_path, + configuration: "Release", + destination: "generic/platform=macOS,variant=Mac Catalyst", + xcargs: xcargs_str, + clean: true, + skip_codesigning: !should_sign, + skip_package_ipa: true, + derived_data_path: derived_data_path, + buildlog_path: File.join(project_root, "ios", "build_logs") + ) + + archive_path = lane_context[SharedValues::XCODEBUILD_ARCHIVE] + if archive_path.nil? || archive_path.empty? + archive_path = Dir.glob(File.join(Dir.home, "Library/Developer/Xcode/Archives/**/*.xcarchive")).max_by { |path| File.mtime(path) } + end + + candidate_paths = [] + candidate_paths << File.join(archive_path, "Products/Applications/BlueWallet.app") if archive_path + candidate_paths << Dir.glob(File.join(derived_data_path, "Build/Products/Release-maccatalyst/*.app")).first + candidate_paths << Dir.glob(File.join(derived_data_path, "Build/Intermediates.noindex/ArchiveIntermediates/BlueWallet/BuildProductsPath/Release-maccatalyst/*.app")).first + + catalyst_app_path = candidate_paths.compact.find { |path| File.exist?(path) } + UI.user_error!("Mac Catalyst app was not found after build") if catalyst_app_path.nil? + UI.success("Mac Catalyst app found at: #{catalyst_app_path}") + + if should_sign && ENV['CATALYST_SIGNING_IDENTITY'] && !ENV['CATALYST_SIGNING_IDENTITY'].empty? + UI.message("Re-signing app with: #{signing_identity}") + sh("codesign", + "--deep", + "--force", + "--options", "runtime", + "--sign", signing_identity, + catalyst_app_path) + sh("codesign", + "--verify", + "--deep", + "--strict", + catalyst_app_path) + UI.success("App signed and verified") + else + # Ad-hoc sign so macOS doesn't treat the app as "damaged" + UI.message("Ad-hoc signing app to avoid Gatekeeper quarantine issues...") + sh("codesign", + "--deep", + "--force", + "--sign", "-", + catalyst_app_path) + UI.success("App ad-hoc signed") + end + + dmg_path = File.join(output_dir, "BlueWallet-Mac-Catalyst.dmg") + UI.message("Creating DMG at: #{dmg_path}") + + dmg_staging = File.join(output_dir, "dmg-staging") + FileUtils.rm_rf(dmg_staging) + FileUtils.mkdir_p(dmg_staging) + FileUtils.cp_r(catalyst_app_path, dmg_staging) + + sh("ln", "-s", "/Applications", "#{dmg_staging}/Applications") + + # Add README with first-launch instructions + readme_path = File.join(dmg_staging, "README - Read Before Opening.txt") + File.write(readme_path, <<~README) + ╔══════════════════════════════════════════════════════════════╗ + ║ BlueWallet for macOS ║ + ╚══════════════════════════════════════════════════════════════╝ + + ═══ ENGLISH ═══════════════════════════════════════════════════ + + INSTALLATION: Drag "BlueWallet.app" into "Applications". + + FIRST LAUNCH — macOS may show a warning. To open: + • Right-click the app → Open → click "Open" in the dialog. + • Or: System Settings → Privacy & Security → Open Anyway. + • Or (Terminal): xattr -cr /Applications/BlueWallet.app + You only need to do this once. + + ═══ ESPAÑOL ════════════════════════════════════════════════════ + + INSTALACIÓN: Arrastra "BlueWallet.app" a "Aplicaciones". + + PRIMER INICIO — macOS puede mostrar una advertencia. Para abrir: + • Haz clic derecho en la app → Abrir → clic en "Abrir". + • O: Ajustes del Sistema → Privacidad y Seguridad → Abrir igualmente. + • O (Terminal): xattr -cr /Applications/BlueWallet.app + Solo necesitas hacerlo una vez. + + ═══ 中文 ═══════════════════════════════════════════════════════ + + 安装:将 "BlueWallet.app" 拖入 "应用程序" 文件夹。 + + 首次启动 — macOS 可能会显示警告。打开方法: + • 右键点击应用 → 打开 → 在对话框中点击"打开"。 + • 或:系统设置 → 隐私与安全性 → 仍要打开。 + • 或(终端):xattr -cr /Applications/BlueWallet.app + 只需操作一次。 + + ═══ PORTUGUÊS ══════════════════════════════════════════════════ + + INSTALAÇÃO: Arraste "BlueWallet.app" para "Aplicativos". + + PRIMEIRA ABERTURA — o macOS pode exibir um aviso. Para abrir: + • Clique com o botão direito → Abrir → clique em "Abrir". + • Ou: Ajustes do Sistema → Privacidade e Segurança → Abrir Mesmo Assim. + • Ou (Terminal): xattr -cr /Applications/BlueWallet.app + Você só precisa fazer isso uma vez. + + ═══ РУССКИЙ ════════════════════════════════════════════════════ + + УСТАНОВКА: Перетащите "BlueWallet.app" в "Программы". + + ПЕРВЫЙ ЗАПУСК — macOS может показать предупреждение. Чтобы открыть: + • Нажмите правой кнопкой → Открыть → нажмите «Открыть». + • Или: Системные настройки → Конфиденциальность → Подтвердить открытие. + • Или (Терминал): xattr -cr /Applications/BlueWallet.app + Это нужно сделать только один раз. + + ═══ 日本語 ═════════════════════════════════════════════════════ + + インストール:「BlueWallet.app」を「アプリケーション」にドラッグ。 + + 初回起動 — macOS が警告を表示する場合があります。開くには: + • アプリを右クリック →「開く」→ ダイアログで「開く」をクリック。 + • または:システム設定 → プライバシーとセキュリティ →「このまま開く」。 + • または(ターミナル):xattr -cr /Applications/BlueWallet.app + この操作は一度だけ必要です。 + + ═══ DEUTSCH ════════════════════════════════════════════════════ + + INSTALLATION: Ziehe „BlueWallet.app" in den Ordner „Programme". + + ERSTER START — macOS zeigt möglicherweise eine Warnung. Zum Öffnen: + • Rechtsklick auf die App → Öffnen → „Öffnen" klicken. + • Oder: Systemeinstellungen → Datenschutz & Sicherheit → Dennoch öffnen. + • Oder (Terminal): xattr -cr /Applications/BlueWallet.app + Dies ist nur beim ersten Mal nötig. + + ═══ FRANÇAIS ═══════════════════════════════════════════════════ + + INSTALLATION : Glissez « BlueWallet.app » dans « Applications ». + + PREMIER LANCEMENT — macOS peut afficher un avertissement. Pour ouvrir : + • Clic droit sur l'app → Ouvrir → cliquez sur « Ouvrir ». + • Ou : Réglages Système → Confidentialité et sécurité → Ouvrir quand même. + • Ou (Terminal) : xattr -cr /Applications/BlueWallet.app + Cette opération n'est nécessaire qu'une seule fois. + + ═══ العربية ════════════════════════════════════════════════════ + + التثبيت: اسحب "BlueWallet.app" إلى مجلد "التطبيقات". + + التشغيل الأول — قد يعرض macOS تحذيرًا. لفتح التطبيق: + • انقر بزر الماوس الأيمن → افتح → انقر "فتح" في مربع الحوار. + • أو: إعدادات النظام → الخصوصية والأمان → فتح على أي حال. + • أو (الطرفية): xattr -cr /Applications/BlueWallet.app + تحتاج للقيام بذلك مرة واحدة فقط. + + ──────────────────────────────────────────────────────────────── + https://bluewallet.io + README + UI.message("Added multilingual README with first-launch instructions to DMG") + + FileUtils.rm_f(dmg_path) + sh("hdiutil", "create", "-volname", "BlueWallet", "-srcfolder", dmg_staging, "-ov", "-format", "UDZO", dmg_path) + UI.user_error!("DMG was not created at #{dmg_path}") unless File.exist?(dmg_path) + UI.success("DMG created at: #{dmg_path}") + + FileUtils.rm_rf(dmg_staging) + + ENV['CATALYST_APP_PATH'] = catalyst_app_path + ENV['CATALYST_DMG_PATH'] = dmg_path + if ENV['GITHUB_OUTPUT'] + File.open(ENV['GITHUB_OUTPUT'], 'a') do |f| + f.puts "catalyst_app_path=#{catalyst_app_path}" + f.puts "catalyst_dmg_path=#{dmg_path}" + end + end + + UI.success("macOS app built at: #{catalyst_app_path}") + UI.success("macOS DMG at: #{dmg_path}") + end + end + + desc "Upload Mac Catalyst app to TestFlight" + lane :upload_catalyst_to_testflight do + Dir.chdir(project_root) do + # Locate the xcarchive + archive_path = lane_context[SharedValues::XCODEBUILD_ARCHIVE] + if archive_path.nil? || archive_path.empty? + archive_path = Dir.glob(File.join(Dir.home, "Library/Developer/Xcode/Archives/**/*.xcarchive")).max_by { |path| File.mtime(path) } + end + UI.user_error!("No xcarchive found for TestFlight upload") if archive_path.nil? || !File.exist?(archive_path) + UI.message("Using archive: #{archive_path}") + + output_dir = File.join(project_root, "ios", "build", "catalyst-output") + FileUtils.mkdir_p(output_dir) + + # Export the archive as a .pkg for Mac Catalyst + team_id = ENV['CATALYST_TEAM_ID'] || ENV['TEAM_ID'] + export_plist_path = File.join(output_dir, "ExportOptions.plist") + + # Build provisioning profiles mapping for manual signing + profiles_xml = catalyst_app_identifiers.map do |app_id| + profile_name = "match AppStore #{app_id} catalyst" + "\t\t\t#{app_id}\n\t\t\t#{profile_name}" + end.join("\n") + + File.write(export_plist_path, <<~PLIST) + + + + + method + app-store + destination + upload + teamID + #{team_id} + signingStyle + manual + provisioningProfiles + +#{profiles_xml} + + + + PLIST + + pkg_path = File.join(output_dir, "BlueWallet.pkg") + sh("xcodebuild", + "-exportArchive", + "-archivePath", archive_path, + "-exportOptionsPlist", export_plist_path, + "-exportPath", output_dir) + + # Find the exported pkg + exported_pkg = Dir.glob(File.join(output_dir, "*.pkg")).first + UI.user_error!("No .pkg found after export") if exported_pkg.nil? || !File.exist?(exported_pkg) + UI.success("Exported pkg: #{exported_pkg}") + + # Build changelog + branch_name = ENV['BRANCH_NAME'] || "unknown-branch" + last_commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found" + changelog = "Build Information:\n" + changelog += "- Branch: #{branch_name}\n" if branch_name != 'master' + changelog += "- Commit: #{last_commit_message}\n" + + # Upload to TestFlight + upload_to_testflight( + api_key_path: "./appstore_api_key.json", + pkg: exported_pkg, + skip_waiting_for_build_processing: true, + changelog: changelog + ) + + UI.success("Successfully uploaded Mac Catalyst app to TestFlight!") + end + end + + + desc "Upload IPA to TestFlight" + lane :upload_to_testflight_lane do + + branch_name = ENV['BRANCH_NAME'] || "unknown-branch" + last_commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found" + + + changelog = <<~CHANGELOG + Build Information: + CHANGELOG + + # Include the branch name only if it is not 'master' + if branch_name != 'master' + changelog += <<~CHANGELOG + - Branch: #{branch_name} + CHANGELOG + end + + changelog += <<~CHANGELOG + - Commit: #{last_commit_message} + CHANGELOG + + ipa_path = ENV['IPA_OUTPUT_PATH'] + + if ipa_path.nil? || ipa_path.empty? || !File.exist?(ipa_path) + UI.user_error!("IPA file not found at path: #{ipa_path}") + end + + UI.message("Uploading IPA to TestFlight from path: #{ipa_path}") + UI.message("Changelog:\n#{changelog}") + + + upload_to_testflight( + api_key_path: "./appstore_api_key.json", + ipa: ipa_path, + skip_waiting_for_build_processing: true, + changelog: changelog + ) + + UI.success("Successfully uploaded IPA to TestFlight!") +end + +desc "Upload iOS source maps to Bugsnag" +lane :upload_bugsnag_sourcemaps do + bugsnag_api_key = ENV['BUGSNAG_API_KEY'] + bugsnag_release_stage = ENV['BUGSNAG_RELEASE_STAGE'] || "production" + version = ENV['PROJECT_VERSION'] + build_number = ENV['NEW_BUILD_NUMBER'] + + UI.user_error!("BUGSNAG_API_KEY environment variable is missing") if bugsnag_api_key.nil? + UI.user_error!("PROJECT_VERSION environment variable is missing") if version.nil? + UI.user_error!("NEW_BUILD_NUMBER environment variable is missing") if build_number.nil? + + ios_sourcemap = "./ios/build/Build/Products/Release-iphonesimulator/main.jsbundle.map" + + if File.exist?(ios_sourcemap) + UI.message("Uploading iOS source map to Bugsnag...") + bugsnag_sourcemaps_upload( + api_key: bugsnag_api_key, + source_map: ios_sourcemap, + minified_file: "./ios/main.jsbundle", + code_bundle_id: "#{version}-#{build_number}", + release_stage: bugsnag_release_stage, + app_version: version + ) + UI.success("iOS source map uploaded successfully.") + else + UI.error("iOS source map not found at #{ios_sourcemap}") + end +end + + desc "Build the iOS app" + lane :build_app_lane do + Dir.chdir(project_root) do + UI.message("Building the application from: #{Dir.pwd}") + + workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace") + export_options_path = File.join(project_root, "ios", "export_options.plist") + + clear_derived_data_lane + + UI.message("\033[1;34m==================== FASTLANE BUILD DEBUG ====================\033[0m") + + # Comprehensive environment check + UI.message("\033[1;36mEnvironment Analysis:\033[0m") + UI.message(" Project Root: #{project_root}") + UI.message(" Current Directory: #{Dir.pwd}") + UI.message(" Ruby Version: #{RUBY_VERSION}") + UI.message(" Fastlane Version: #{Fastlane::VERSION}") + UI.message(" Build Mode: Release (App Store)") + + # Ensure we're using the correct Xcode installation + UI.message("\033[1;36mXcode Configuration:\033[0m") + begin + sh("sudo xcode-select -s /Applications/Xcode.app") rescue nil + xcode_path = sh('xcode-select -p', log: false).strip + xcode_version = sh('xcodebuild -version', log: false).strip + UI.message(" Active Xcode Path: #{xcode_path}") + UI.message(" Xcode Version: #{xcode_version}") + + # Check if Xcode path is valid + if File.exist?(File.join(xcode_path, "usr/bin/xcodebuild")) + UI.success(" \033[1;32mXcode installation is valid\033[0m") + else + UI.error(" \033[1;31mXcode installation appears invalid\033[0m") + end + rescue => e + UI.error(" \033[1;31mError checking Xcode: #{e.message}\033[0m") + end + + # Project structure analysis + UI.message("\033[1;36mProject Structure Analysis:\033[0m") + %w[ + ios/BlueWallet.xcworkspace + ios/BlueWallet.xcodeproj + ios/export_options.plist + ios/Podfile + ios/Podfile.lock + ].each do |file_path| + full_path = File.join(project_root, file_path) + if File.exist?(full_path) + UI.message(" \033[1;32m#{file_path} exists\033[0m") + if file_path.end_with?('.plist') + UI.message(" Content preview:") + content = File.read(full_path).lines.first(10).join.strip + UI.message(" #{content[0..200]}...") + end + else + UI.error(" \033[1;31m#{file_path} missing\033[0m") + end + end + + # Environment variables check + UI.message("\033[1;36mEnvironment Variables:\033[0m") + %w[PROJECT_VERSION NEW_BUILD_NUMBER].each do |var| + value = ENV[var] + if value && !value.empty? + UI.message(" \033[1;32m#{var}: #{value}\033[0m") + else + UI.error(" \033[1;31m#{var}: Not set or empty\033[0m") + end + end + + # Determine which iOS version to use + UI.message("\033[1;36miOS Version Analysis:\033[0m") + ios_version = determine_ios_version + UI.message(" Selected iOS version: #{ios_version}") + + UI.message("\033[1;36mBuild Configuration:\033[0m") + UI.message(" Workspace: #{workspace_path}") + UI.message(" Export options: #{export_options_path}") + UI.message(" Output directory: #{File.join(project_root, 'ios', 'build')}") + UI.message(" Build type: Release (generic/platform=iOS)") + + # Comprehensive destination analysis + UI.message("\033[1;33mBuild Destinations Analysis:\033[0m") + begin + destinations_output = sh("xcodebuild -workspace '#{workspace_path}' -scheme BlueWallet -showdestinations", log: false) + UI.message(" Available destinations:") + destinations_output.lines.each_with_index do |line, index| + UI.message(" #{index + 1}. #{line.strip}") if line.strip.length > 0 + end + + # Analyze destination types + ios_destinations = destinations_output.scan(/platform:iOS[^}]*/).length + catalyst_destinations = destinations_output.scan(/Mac Catalyst/).length + + UI.message(" \033[1;36mDestination Summary:\033[0m") + UI.message(" iOS destinations: #{ios_destinations}") + UI.message(" Mac Catalyst destinations: #{catalyst_destinations}") + + if ios_destinations > 0 + UI.success(" \033[1;32miOS destinations available for release build\033[0m") + else + UI.important(" \033[1;33mNo iOS destinations found - may need to create simulators\033[0m") + end + rescue => e + UI.error(" \033[1;31mFailed to get destinations: #{e.message}\033[0m") + destinations_output = "Failed to get destinations: #{e.message}" + end + + # Simulator runtime check for development/debugging + UI.message("\033[1;33mSimulator Runtime Check (for debugging):\033[0m") + begin + runtimes = sh("xcrun simctl list runtimes", log: false) + ios_runtimes = runtimes.scan(/iOS ([0-9.]+)/).flatten + UI.message(" Available iOS runtimes: #{ios_runtimes.join(', ')}") + + devices = sh("xcrun simctl list devices iOS", log: false) + iphone_count = devices.scan(/iPhone/).length + UI.message(" Available iPhone simulators: #{iphone_count}") + rescue => e + UI.error(" \033[1;31mError checking simulators: #{e.message}\033[0m") + end + + # Define the IPA output path before building + ipa_directory = File.join(project_root, "ios", "build") + ipa_name = "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa" + ipa_path = File.join(ipa_directory, ipa_name) + + UI.message("\033[1;36mBuild Output Configuration:\033[0m") + UI.message(" IPA Directory: #{ipa_directory}") + UI.message(" IPA Name: #{ipa_name}") + UI.message(" Full IPA Path: #{ipa_path}") + + # Ensure build directory exists + FileUtils.mkdir_p(ipa_directory) unless Dir.exist?(ipa_directory) + + begin + UI.message("🚀 Starting iOS Build Process...") + UI.message(" Build Parameters:") + UI.message(" Scheme: BlueWallet") + UI.message(" Workspace: #{workspace_path}") + UI.message(" Export Method: app-store") + UI.message(" Export Options: #{export_options_path}") + UI.message(" Output Directory: #{ipa_directory}") + UI.message(" Output Name: #{ipa_name}") + UI.message(" Build Logs: #{File.join(project_root, 'ios', 'build_logs')}") + UI.message(" Destination: generic/platform=iOS") + + # Pre-build validation + UI.message("🔍 Pre-Build Validation:") + + # Check workspace + if File.exist?(workspace_path) + UI.success(" ✅ Workspace exists") + else + UI.user_error!(" ❌ Workspace not found: #{workspace_path}") + end + + # Check export options + if File.exist?(export_options_path) + UI.success(" ✅ Export options exist") + # Read and validate export options + begin + export_content = File.read(export_options_path) + UI.message(" Export options preview:") + export_content.lines.first(5).each { |line| UI.message(" #{line.strip}") } + rescue => e + UI.error(" ⚠️ Could not read export options: #{e.message}") + end + else + UI.user_error!(" ❌ Export options not found: #{export_options_path}") + end + + build_ios_app( + scheme: "BlueWallet", + workspace: workspace_path, + export_method: "app-store", + export_options: export_options_path, + output_directory: ipa_directory, + output_name: ipa_name, + buildlog_path: File.join(project_root, "ios", "build_logs") + # Removed explicit destination - let Xcode determine the best destination for release builds + ) + + UI.success("\033[1;32miOS release build completed successfully!\033[0m") + + rescue => build_error + UI.error("\033[1;31miOS release build failed!\033[0m") + UI.error(" Error Class: #{build_error.class}") + UI.error(" Error Message: #{build_error.message}") + UI.error(" \033[1;31mError Backtrace:\033[0m") + build_error.backtrace.first(5).each { |line| UI.error(" #{line}") } + + UI.message("\033[1;33mPost-Build Debugging:\033[0m") + + # Check for partial build artifacts + build_logs_dir = File.join(project_root, "ios", "build_logs") + if Dir.exist?(build_logs_dir) + UI.message(" \033[1;36mBuild logs directory contents:\033[0m") + Dir.entries(build_logs_dir).each do |file| + next if file.start_with?('.') + file_path = File.join(build_logs_dir, file) + size = File.size(file_path) rescue 0 + UI.message(" #{file} (#{size} bytes)") + end + end + + # Check for xcarchive files + archives = Dir.glob(File.join(Dir.home, "Library/Developer/Xcode/Archives/**/*.xcarchive")) + if archives.any? + UI.message(" \033[1;36mRecent archives found:\033[0m") + archives.last(3).each { |archive| UI.message(" #{archive}") } + end + + # If iOS build fails, check if we can build using available destinations + if destinations_output.include?("Any iOS Device") + UI.message("Retrying build without explicit destination...") + build_ios_app( + scheme: "BlueWallet", + workspace: workspace_path, + export_method: "app-store", + export_options: export_options_path, + output_directory: ipa_directory, + output_name: ipa_name, + buildlog_path: File.join(project_root, "ios", "build_logs") + ) + else + UI.user_error!("build_ios_app failed: #{build_error.message}") + end + end + + # Check for IPA path from both our defined path and fastlane's context + ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH] || ipa_path + + # Ensure the directory exists + FileUtils.mkdir_p(File.dirname(ipa_path)) unless Dir.exist?(File.dirname(ipa_path)) + + if ipa_path && File.exist?(ipa_path) + UI.message("IPA successfully found at: #{ipa_path}") + else + # Try to find any IPA file as fallback + Dir.chdir(project_root) do + fallback_ipa = Dir.glob("**/*.ipa").first + if fallback_ipa + ipa_path = File.join(project_root, fallback_ipa) + UI.message("Found fallback IPA at: #{ipa_path}") + else + UI.user_error!("No IPA file found after build") + end + end + end + + # Set both environment variable and GitHub Actions output + ENV['IPA_OUTPUT_PATH'] = ipa_path + # Set both standard output format and the newer GITHUB_OUTPUT format + sh("echo 'ipa_output_path=#{ipa_path}' >> $GITHUB_OUTPUT") if ENV['GITHUB_OUTPUT'] + sh("echo ::set-output name=ipa_output_path::#{ipa_path}") + + # Also write path to a file that can be read by subsequent steps + ipa_path_file = "#{ipa_directory}/ipa_path.txt" + File.write(ipa_path_file, ipa_path) + UI.success("Saved IPA path to: #{ipa_path_file}") + end + end + + desc "Delete temporary keychain" + lane :delete_temp_keychain do + UI.message("Deleting temporary keychain...") + + delete_keychain( + name: "temp_keychain" + ) if File.exist?(File.expand_path("~/Library/Keychains/temp_keychain-db")) + + UI.message("Temporary keychain deleted successfully.") + end + + # Helper method to determine which iOS version to use + # Updated for macOS-15 compatibility (defaults to iOS 17.5 for broader compatibility) + private_lane :determine_ios_version do + UI.message("\033[1;33mDetermining iOS Version for Release Build:\033[0m") + + begin + runtimes_output = sh("xcrun simctl list runtimes 2>&1", log: false) + UI.message(" \033[1;32mSuccessfully retrieved simulator runtimes\033[0m") + + # Debug: Show all runtimes + UI.message(" \033[1;36mAll available runtimes:\033[0m") + runtimes_output.lines.first(10).each_with_index do |line, index| + UI.message(" #{index + 1}. #{line.strip}") + end + + rescue => e + UI.error(" \033[1;31mFailed to get simulator runtimes: #{e.message}\033[0m") + runtimes_output = "" + end + + if runtimes_output.include?("iOS") + UI.message(" \033[1;36miOS runtimes detected\033[0m") + + begin + ios_versions = runtimes_output.scan(/iOS ([0-9.]+)/) + .flatten + .map { |v| Gem::Version.new(v) } + .sort + .reverse + + UI.message(" \033[1;36mParsed iOS versions:\033[0m") + ios_versions.each_with_index do |version, index| + UI.message(" #{index + 1}. iOS #{version}") + end + + if ios_versions.any? + latest_version = ios_versions.first.to_s + UI.success(" \033[1;32mSelected iOS version for release: #{latest_version}\033[0m") + + # Additional validation + if Gem::Version.new(latest_version) >= Gem::Version.new("17.0") + UI.success(" \033[1;32mVersion is compatible for release builds (17.0+)\033[0m") + else + UI.important(" \033[1;33mVersion is older than 17.0, may have compatibility issues\033[0m") + end + + latest_version + else + UI.important(" \033[1;33mNo iOS versions could be parsed from runtime output\033[0m") + UI.message(" \033[1;36mUsing fallback version for release: 17.5\033[0m") + "17.5" + end + rescue => e + UI.error(" \033[1;31mError parsing iOS versions: #{e.message}\033[0m") + UI.message(" \033[1;36mUsing fallback version for release: 17.5\033[0m") + "17.5" + end + else + UI.important(" \033[1;33mNo iOS runtimes found in simulator list\033[0m") + UI.message(" \033[1;36mRuntime output preview:\033[0m") + runtimes_output.lines.first(5).each { |line| UI.message(" #{line.strip}") } + UI.message(" \033[1;36mUsing fallback version for release: 17.5\033[0m") + "17.5" + end + end +end + +# =========================== +# Global Lanes +# =========================== + +desc "Deploy to TestFlight" +lane :deploy do |options| + UI.message("Starting deployment process...") + + update_wwdr_certificate + setup_app_store_connect_api_key + setup_provisioning_profiles + clear_derived_data_lane + increment_build_number_lane + + unless File.directory?("Pods") + install_pods + end + + build_app_lane + upload_to_testflight_lane + + delete_keychain(name: "temp_keychain") + + last_commit = last_git_commit + already_built_flag = ".already_built_#{last_commit[:sha]}" + File.write(already_built_flag, Time.now.to_s) +end + +desc "Update release notes for App Store versions (iOS or Mac Catalyst)" +lane :release_notes do |options| + require 'spaceship' + + app = Spaceship::ConnectAPI::App.find(app_identifiers.first) + + UI.user_error!("Could not find the app with identifier: #{app_identifiers.first}") unless app + + platform_option = options[:platform] + + if platform_option + platform = case platform_option.to_s.downcase + when "ios" + UI.message("Using platform from options: iOS") + Spaceship::ConnectAPI::Platform::IOS + when "catalyst", "mac_catalyst", "mac-catalyst" + UI.message("Using platform from options: Mac Catalyst") + UI.message("Using Spaceship::ConnectAPI::Platform::MAC_OS for Mac Catalyst") + Spaceship::ConnectAPI::Platform::MAC_OS + else + UI.user_error!("Invalid platform option: #{platform_option}") + end + else + platform_selection = UI.select("Select platform for release notes:", ["iOS", "Mac Catalyst"]) + + platform = case platform_selection + when "iOS" + UI.message("Selected platform: iOS") + Spaceship::ConnectAPI::Platform::IOS + when "Mac Catalyst" + UI.message("Selected platform: Mac Catalyst") + UI.message("Using Spaceship::ConnectAPI::Platform::MAC_OS for Mac Catalyst") + Spaceship::ConnectAPI::Platform::MAC_OS + else + UI.user_error!("Invalid platform selection") + end + end + + retries = 5 + begin + rejected_version = nil + UI.message("Checking for Developer Rejected version for platform: #{platform}") + + begin + filter = { + appStoreState: "DEVELOPER_REJECTED", + platformString: platform.to_s + } + + app.get_app_store_versions(filter: filter).each do |version| + rejected_version = version + UI.message("Found rejected version: #{version.version_string}") + break + end + rescue => e + UI.error("Error fetching Developer Rejected versions: #{e.message}") + UI.message("Debug info: Platform type: #{platform.class}, Value: #{platform}") + end + + if rejected_version + UI.success("Found 'Developer Rejected' version: #{rejected_version.version_string}. This will be the target for updates.") + prepare_version = rejected_version + else + UI.message("No Developer Rejected version found. Checking for version in edit mode or waiting for review...") + + begin + prepare_version = app.get_edit_app_store_version(platform: platform) + if prepare_version + UI.message("Found version in edit mode: #{prepare_version.version_string}") + else + UI.message("No version in edit mode found") + end + rescue => e + UI.error("Error fetching edit app store version: #{e.message}") + prepare_version = nil + end + + if prepare_version.nil? + UI.message("Checking for version in Waiting for Review status...") + begin + waiting_filter = { + platformString: platform.to_s, + appStoreState: "WAITING_FOR_REVIEW" + } + + waiting_versions = app.get_app_store_versions(filter: waiting_filter) + if waiting_versions && !waiting_versions.empty? + prepare_version = waiting_versions.first + UI.success("Found version in Waiting for Review status: #{prepare_version.version_string}") + else + UI.message("No version in Waiting for Review status found") + end + rescue => e + UI.error("Error fetching Waiting for Review versions: #{e.message}") + end + end + + if prepare_version.nil? + UI.message("Looking for any in-flight version...") + begin + all_versions = app.get_app_store_versions(filter: { platformString: platform.to_s }) + UI.message("Found #{all_versions.count} versions for platform #{platform}:") + + all_versions.each do |version| + state = app_store_state_readable(version.app_store_state) + UI.message(" - Version: #{version.version_string}, State: #{state}") + end + + editable_states = ["PREPARE_FOR_SUBMISSION", "WAITING_FOR_REVIEW", "REJECTED", "METADATA_REJECTED", "DEVELOPER_REJECTED"] + editable_version = all_versions.find { |v| editable_states.include?(v.app_store_state) } + + if editable_version + prepare_version = editable_version + UI.success("Using editable version: #{prepare_version.version_string} (#{app_store_state_readable(prepare_version.app_store_state)})") + elsif all_versions.count > 0 + latest_version = all_versions.sort_by { |v| Gem::Version.new(v.version_string) }.last + UI.message("Latest version: #{latest_version.version_string} (#{app_store_state_readable(latest_version.app_store_state)})") + end + rescue => e + UI.error("Error listing versions: #{e.message}") + end + end + + if prepare_version.nil? + UI.message("No editable version found.") + + create_new_version = UI.confirm("Would you like to create a new version?") + + if create_new_version + begin + UI.message("Fetching latest version for platform: #{platform}") + latest_version = app.get_latest_version(platform: platform) + if latest_version + UI.message("Latest version: #{latest_version.version_string}") + new_version_number = (latest_version.version_string.to_f + 0.1).round(1).to_s + else + UI.message("No latest version found. Using 1.0 as base") + new_version_number = "1.0" + end + + UI.message("Creating new version: #{new_version_number} for platform: #{platform_selection || platform_option}") + prepare_version = app.create_version!(platform: platform, version_string: new_version_number) + UI.message("Created new version: #{new_version_number}") + rescue => e + UI.error("Failed to create new version: #{e.message}") + UI.user_error!("Failed to create version. Make sure your app is configured for Mac Catalyst in App Store Connect.") + end + else + UI.user_error!("No editable version found and user chose not to create one. Aborting.") + end + else + UI.message("Using version #{prepare_version.version_string} in state: #{app_store_state_readable(prepare_version.app_store_state)}") + end + end + rescue => e + retries -= 1 + if retries > 0 + delay = 20 + UI.message("Cannot find app version info... Retrying after #{delay} seconds (remaining: #{retries})") + UI.error("Error details: #{e.message}") + sleep(delay) + retry + else + UI.user_error!("Failed to fetch or create the app version: #{e.message}") + end + end + + localized_metadata = prepare_version.get_app_store_version_localizations + enabled_locales = localized_metadata.map(&:locale) + release_notes_text = options[:release_notes] + + if release_notes_text.nil? || release_notes_text.strip.empty? + existing_release_notes = nil + + en_us_localization = localized_metadata.find { |loc| loc.locale == 'en-US' } + if en_us_localization && en_us_localization.whats_new && !en_us_localization.whats_new.strip.empty? + existing_release_notes = en_us_localization.whats_new + UI.success("Found existing release notes in App Store Connect!") + else + localized_metadata.each do |loc| + if loc.whats_new && !loc.whats_new.strip.empty? + existing_release_notes = loc.whats_new + UI.success("Found existing release notes in App Store Connect for locale: #{loc.locale}") + break + end + end + end + + ios_release_notes_path = "metadata/ios/en-US/release_notes.txt" + project_release_notes_path = "../release-notes.txt" + + ios_notes_exist = File.exist?(ios_release_notes_path) + project_notes_exist = File.exist?(project_release_notes_path) + + options_list = [] + + if existing_release_notes + options_list << "View/Edit existing App Store notes" + end + + options_list += [ + "Enter manually", + "Use clipboard content" + ] + + if project_notes_exist + options_list << "Use release-notes.txt file" + end + + if ios_notes_exist + options_list << "Use iOS metadata release notes" + end + + selection = UI.select("Select a source for release notes:", options_list) + + case selection + when "View/Edit existing App Store notes" + UI.message("Existing release notes:") + UI.message("-" * 50) + UI.message(existing_release_notes) + UI.message("-" * 50) + + edit_choice = UI.select("Do you want to edit these notes or use as-is?:", [ + "Use as-is", + "Edit notes" + ]) + + if edit_choice == "Edit notes" + require 'tempfile' + temp_file = Tempfile.new('release_notes') + temp_file.write(existing_release_notes) + temp_file.close + + editor = ENV['EDITOR'] || 'nano' + system("#{editor} #{temp_file.path}") + + release_notes_text = File.read(temp_file.path) + temp_file.unlink + + UI.message("Edited release notes:") + UI.message("-" * 50) + UI.message(release_notes_text.length > 500 ? "#{release_notes_text[0..500]}..." : release_notes_text) + UI.message("-" * 50) + + unless UI.confirm("Use these edited notes?") + UI.user_error!("User canceled edited notes. Aborting.") + end + else + release_notes_text = existing_release_notes + end + + when "Enter manually" + release_notes_text = UI.input("Enter the release notes:") + if release_notes_text.nil? || release_notes_text.strip.empty? + UI.user_error!("No release notes provided. Aborting.") + end + + when "Use clipboard content" + require 'open3' + stdout, stderr, status = Open3.capture3("pbpaste") + + if !status.success? || stdout.strip.empty? + UI.user_error!("Failed to get clipboard content or clipboard is empty") + end + + UI.message("Clipboard content preview:") + UI.message("-" * 50) + UI.message(stdout.length > 500 ? "#{stdout[0..500]}..." : stdout) + UI.message("-" * 50) + + unless UI.confirm("Use this clipboard content for release notes?") + UI.user_error!("User canceled clipboard content usage. Aborting.") + end + + release_notes_text = stdout + + when "Use iOS metadata release notes" + release_notes_text = File.read(ios_release_notes_path) + + UI.message("iOS metadata release notes preview:") + UI.message("-" * 50) + UI.message(release_notes_text.length > 500 ? "#{release_notes_text[0..500]}..." : release_notes_text) + UI.message("-" * 50) + + unless UI.confirm("Use this content from iOS metadata release notes?") + UI.user_error!("User canceled file content usage. Aborting.") + end + + when "Use release-notes.txt file" + release_notes_path = "../release-notes.txt" + + unless File.exist?(release_notes_path) + UI.error("Release notes file does not exist at path: #{release_notes_path}") + UI.user_error!("No release-notes.txt file found. Aborting.") + end + + release_notes_text = File.read(release_notes_path) + + UI.message("release-notes.txt content preview:") + UI.message("-" * 50) + UI.message(release_notes_text.length > 500 ? "#{release_notes_text[0..500]}..." : release_notes_text) + UI.message("-" * 50) + + unless UI.confirm("Use this content from release-notes.txt?") + UI.user_error!("User canceled file content usage. Aborting.") + end + end + end + + if release_notes_text.nil? || release_notes_text.strip.empty? + UI.user_error!("No release notes content available. Aborting.") + end + + localized_release_notes = { + 'en-US' => release_notes_text, + 'ar-SA' => release_notes_text, + 'zh-Hans' => release_notes_text, + 'hr' => release_notes_text, + 'da' => release_notes_text, + 'nl-NL' => release_notes_text, + 'fi' => release_notes_text, + 'fr-FR' => release_notes_text, + 'de-DE' => release_notes_text, + 'el' => release_notes_text, + 'he' => release_notes_text, + 'hu' => release_notes_text, + 'it' => release_notes_text, + 'ja' => release_notes_text, + 'ms' => release_notes_text, + 'nb' => release_notes_text, + 'no' => release_notes_text, + 'pl' => release_notes_text, + 'pt-BR' => release_notes_text, + 'pt-PT' => release_notes_text, + 'ro' => release_notes_text, + 'ru' => release_notes_text, + 'es-MX' => release_notes_text, + 'es-ES' => release_notes_text, + 'sv' => release_notes_text, + 'th' => release_notes_text, + } + + if platform == Spaceship::ConnectAPI::Platform::MAC_OS + UI.message("Mac Catalyst selected - using only en-US localization") + localized_release_notes = { 'en-US' => release_notes_text } + end + + localized_release_notes = localized_release_notes.select { |locale, _| enabled_locales.include?(locale) } + + UI.message("Review the following release notes updates:") + localized_release_notes.each do |locale, notes| + UI.message("Locale: #{locale} - Notes: #{notes}") + end + + force_yes = options && options.is_a?(Hash) && options[:force_yes] == true + + unless force_yes + confirm = UI.confirm("Do you want to proceed with these release notes updates?") + UI.user_error!("User aborted the lane.") unless confirm + end + + localized_release_notes.each do |locale, notes| + app_store_version_localization = localized_metadata.find { |loc| loc.locale == locale } + if app_store_version_localization + app_store_version_localization.update(attributes: { "whats_new" => notes }) + else + UI.error("No localization found for locale #{locale}") + end + end +end diff --git a/fastlane/Matchfile b/fastlane/Matchfile new file mode 100644 index 00000000000..9772ca474d4 --- /dev/null +++ b/fastlane/Matchfile @@ -0,0 +1,41 @@ +# Matchfile + +# URL of the Git repository to store the certificates +git_url(ENV["GIT_URL"]) + +# Define the type of match to run +# Default to "appstore" but can be overridden +type(ENV["MATCH_TYPE"] || "appstore") + +# App identifiers for all BlueWallet apps +app_identifier([ + "io.bluewallet.bluewallet", + "io.bluewallet.bluewallet.watch", + "io.bluewallet.bluewallet.watch.extension", + "io.bluewallet.bluewallet.Stickers", + "io.bluewallet.bluewallet.MarketWidget" +]) + +# Your Apple Developer account email address +username(ENV["APPLE_ID"]) + +# The ID of your Apple Developer team +team_id(ENV["ITC_TEAM_ID"]) + +# Set readonly based on environment (default to true for safety) +# Set to false explicitly when new profiles need to be created +readonly(ENV["MATCH_READONLY"] == "false" ? false : true) + +# Define the platform to use +platform("ios") + +# Git basic authentication through access token +# This is useful for CI/CD environments where SSH keys aren't available +git_basic_authorization(ENV["GIT_ACCESS_TOKEN"]) + +# Storage mode (git by default) +storage_mode("git") + +# Optional: The Git branch that is used for match +# Default is 'master' +# branch("main") diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile new file mode 100644 index 00000000000..29820960deb --- /dev/null +++ b/fastlane/Pluginfile @@ -0,0 +1,7 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-browserstack' +gem 'fastlane-plugin-bugsnag_sourcemaps_upload' +gem "fastlane-plugin-bugsnag" diff --git a/ios/fastlane/metadata/app_icon.jpg b/fastlane/metadata/ios/app_icon.jpg similarity index 100% rename from ios/fastlane/metadata/app_icon.jpg rename to fastlane/metadata/ios/app_icon.jpg diff --git a/ios/fastlane/metadata/ar-SA/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/ar-SA/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/ar-SA/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/ar-SA/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/ar-SA/description.txt b/fastlane/metadata/ios/ar-SA/description.txt similarity index 100% rename from ios/fastlane/metadata/ar-SA/description.txt rename to fastlane/metadata/ios/ar-SA/description.txt diff --git a/ios/fastlane/metadata/ar-SA/keywords.txt b/fastlane/metadata/ios/ar-SA/keywords.txt similarity index 100% rename from ios/fastlane/metadata/ar-SA/keywords.txt rename to fastlane/metadata/ios/ar-SA/keywords.txt diff --git a/ios/fastlane/metadata/ar-SA/marketing_url.txt b/fastlane/metadata/ios/ar-SA/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/ar-SA/marketing_url.txt rename to fastlane/metadata/ios/ar-SA/marketing_url.txt diff --git a/ios/fastlane/metadata/ar-SA/name.txt b/fastlane/metadata/ios/ar-SA/name.txt similarity index 100% rename from ios/fastlane/metadata/ar-SA/name.txt rename to fastlane/metadata/ios/ar-SA/name.txt diff --git a/ios/fastlane/metadata/ar-SA/privacy_url.txt b/fastlane/metadata/ios/ar-SA/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/ar-SA/privacy_url.txt rename to fastlane/metadata/ios/ar-SA/privacy_url.txt diff --git a/ios/fastlane/metadata/ar-SA/promotional_text.txt b/fastlane/metadata/ios/ar-SA/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/ar-SA/promotional_text.txt rename to fastlane/metadata/ios/ar-SA/promotional_text.txt diff --git a/ios/fastlane/metadata/ar-SA/release_notes.txt b/fastlane/metadata/ios/ar-SA/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/ar-SA/release_notes.txt rename to fastlane/metadata/ios/ar-SA/release_notes.txt diff --git a/ios/fastlane/metadata/ar-SA/subtitle.txt b/fastlane/metadata/ios/ar-SA/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/ar-SA/subtitle.txt rename to fastlane/metadata/ios/ar-SA/subtitle.txt diff --git a/ios/fastlane/metadata/ar-SA/support_url.txt b/fastlane/metadata/ios/ar-SA/support_url.txt similarity index 100% rename from ios/fastlane/metadata/ar-SA/support_url.txt rename to fastlane/metadata/ios/ar-SA/support_url.txt diff --git a/fastlane/metadata/ios/copyright.txt b/fastlane/metadata/ios/copyright.txt new file mode 100644 index 00000000000..611e60f6ca0 --- /dev/null +++ b/fastlane/metadata/ios/copyright.txt @@ -0,0 +1 @@ +2024 BlueWallet Services S.R.L. diff --git a/ios/fastlane/metadata/da/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/da/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/da/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/da/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/da/description.txt b/fastlane/metadata/ios/da/description.txt similarity index 100% rename from ios/fastlane/metadata/da/description.txt rename to fastlane/metadata/ios/da/description.txt diff --git a/ios/fastlane/metadata/da/keywords.txt b/fastlane/metadata/ios/da/keywords.txt similarity index 100% rename from ios/fastlane/metadata/da/keywords.txt rename to fastlane/metadata/ios/da/keywords.txt diff --git a/ios/fastlane/metadata/da/marketing_url.txt b/fastlane/metadata/ios/da/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/da/marketing_url.txt rename to fastlane/metadata/ios/da/marketing_url.txt diff --git a/ios/fastlane/metadata/da/name.txt b/fastlane/metadata/ios/da/name.txt similarity index 100% rename from ios/fastlane/metadata/da/name.txt rename to fastlane/metadata/ios/da/name.txt diff --git a/ios/fastlane/metadata/da/privacy_url.txt b/fastlane/metadata/ios/da/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/da/privacy_url.txt rename to fastlane/metadata/ios/da/privacy_url.txt diff --git a/ios/fastlane/metadata/da/promotional_text.txt b/fastlane/metadata/ios/da/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/da/promotional_text.txt rename to fastlane/metadata/ios/da/promotional_text.txt diff --git a/ios/fastlane/metadata/da/release_notes.txt b/fastlane/metadata/ios/da/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/da/release_notes.txt rename to fastlane/metadata/ios/da/release_notes.txt diff --git a/ios/fastlane/metadata/da/subtitle.txt b/fastlane/metadata/ios/da/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/da/subtitle.txt rename to fastlane/metadata/ios/da/subtitle.txt diff --git a/ios/fastlane/metadata/da/support_url.txt b/fastlane/metadata/ios/da/support_url.txt similarity index 100% rename from ios/fastlane/metadata/da/support_url.txt rename to fastlane/metadata/ios/da/support_url.txt diff --git a/ios/fastlane/metadata/de-DE/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/de-DE/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/de-DE/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/de-DE/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/de-DE/description.txt b/fastlane/metadata/ios/de-DE/description.txt similarity index 100% rename from ios/fastlane/metadata/de-DE/description.txt rename to fastlane/metadata/ios/de-DE/description.txt diff --git a/ios/fastlane/metadata/de-DE/keywords.txt b/fastlane/metadata/ios/de-DE/keywords.txt similarity index 100% rename from ios/fastlane/metadata/de-DE/keywords.txt rename to fastlane/metadata/ios/de-DE/keywords.txt diff --git a/ios/fastlane/metadata/de-DE/marketing_url.txt b/fastlane/metadata/ios/de-DE/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/de-DE/marketing_url.txt rename to fastlane/metadata/ios/de-DE/marketing_url.txt diff --git a/fastlane/metadata/ios/de-DE/name.txt b/fastlane/metadata/ios/de-DE/name.txt new file mode 100644 index 00000000000..265118bec8e --- /dev/null +++ b/fastlane/metadata/ios/de-DE/name.txt @@ -0,0 +1 @@ +BlueWallet - Bitcoin Wallet diff --git a/ios/fastlane/metadata/de-DE/privacy_url.txt b/fastlane/metadata/ios/de-DE/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/de-DE/privacy_url.txt rename to fastlane/metadata/ios/de-DE/privacy_url.txt diff --git a/ios/fastlane/metadata/de-DE/promotional_text.txt b/fastlane/metadata/ios/de-DE/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/de-DE/promotional_text.txt rename to fastlane/metadata/ios/de-DE/promotional_text.txt diff --git a/ios/fastlane/metadata/de-DE/release_notes.txt b/fastlane/metadata/ios/de-DE/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/de-DE/release_notes.txt rename to fastlane/metadata/ios/de-DE/release_notes.txt diff --git a/ios/fastlane/metadata/de-DE/subtitle.txt b/fastlane/metadata/ios/de-DE/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/de-DE/subtitle.txt rename to fastlane/metadata/ios/de-DE/subtitle.txt diff --git a/ios/fastlane/metadata/de-DE/support_url.txt b/fastlane/metadata/ios/de-DE/support_url.txt similarity index 100% rename from ios/fastlane/metadata/de-DE/support_url.txt rename to fastlane/metadata/ios/de-DE/support_url.txt diff --git a/ios/fastlane/metadata/en-US/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/en-US/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/en-US/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/en-US/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/en-US/description.txt b/fastlane/metadata/ios/en-US/description.txt similarity index 100% rename from ios/fastlane/metadata/en-US/description.txt rename to fastlane/metadata/ios/en-US/description.txt diff --git a/ios/fastlane/metadata/en-US/keywords.txt b/fastlane/metadata/ios/en-US/keywords.txt similarity index 100% rename from ios/fastlane/metadata/en-US/keywords.txt rename to fastlane/metadata/ios/en-US/keywords.txt diff --git a/ios/fastlane/metadata/en-US/marketing_url.txt b/fastlane/metadata/ios/en-US/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/en-US/marketing_url.txt rename to fastlane/metadata/ios/en-US/marketing_url.txt diff --git a/ios/fastlane/metadata/en-US/name.txt b/fastlane/metadata/ios/en-US/name.txt similarity index 100% rename from ios/fastlane/metadata/en-US/name.txt rename to fastlane/metadata/ios/en-US/name.txt diff --git a/ios/fastlane/metadata/en-US/privacy_url.txt b/fastlane/metadata/ios/en-US/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/en-US/privacy_url.txt rename to fastlane/metadata/ios/en-US/privacy_url.txt diff --git a/ios/fastlane/metadata/en-US/promotional_text.txt b/fastlane/metadata/ios/en-US/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/en-US/promotional_text.txt rename to fastlane/metadata/ios/en-US/promotional_text.txt diff --git a/ios/fastlane/metadata/en-US/release_notes.txt b/fastlane/metadata/ios/en-US/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/en-US/release_notes.txt rename to fastlane/metadata/ios/en-US/release_notes.txt diff --git a/ios/fastlane/metadata/en-US/subtitle.txt b/fastlane/metadata/ios/en-US/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/en-US/subtitle.txt rename to fastlane/metadata/ios/en-US/subtitle.txt diff --git a/ios/fastlane/metadata/en-US/support_url.txt b/fastlane/metadata/ios/en-US/support_url.txt similarity index 100% rename from ios/fastlane/metadata/en-US/support_url.txt rename to fastlane/metadata/ios/en-US/support_url.txt diff --git a/ios/fastlane/metadata/es-ES/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/es-ES/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/es-ES/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/es-ES/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/es-ES/description.txt b/fastlane/metadata/ios/es-ES/description.txt similarity index 100% rename from ios/fastlane/metadata/es-ES/description.txt rename to fastlane/metadata/ios/es-ES/description.txt diff --git a/ios/fastlane/metadata/es-ES/keywords.txt b/fastlane/metadata/ios/es-ES/keywords.txt similarity index 100% rename from ios/fastlane/metadata/es-ES/keywords.txt rename to fastlane/metadata/ios/es-ES/keywords.txt diff --git a/ios/fastlane/metadata/es-ES/marketing_url.txt b/fastlane/metadata/ios/es-ES/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/es-ES/marketing_url.txt rename to fastlane/metadata/ios/es-ES/marketing_url.txt diff --git a/ios/fastlane/metadata/es-ES/name.txt b/fastlane/metadata/ios/es-ES/name.txt similarity index 100% rename from ios/fastlane/metadata/es-ES/name.txt rename to fastlane/metadata/ios/es-ES/name.txt diff --git a/ios/fastlane/metadata/es-ES/privacy_url.txt b/fastlane/metadata/ios/es-ES/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/es-ES/privacy_url.txt rename to fastlane/metadata/ios/es-ES/privacy_url.txt diff --git a/ios/fastlane/metadata/es-ES/promotional_text.txt b/fastlane/metadata/ios/es-ES/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/es-ES/promotional_text.txt rename to fastlane/metadata/ios/es-ES/promotional_text.txt diff --git a/ios/fastlane/metadata/es-ES/release_notes.txt b/fastlane/metadata/ios/es-ES/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/es-ES/release_notes.txt rename to fastlane/metadata/ios/es-ES/release_notes.txt diff --git a/ios/fastlane/metadata/es-ES/subtitle.txt b/fastlane/metadata/ios/es-ES/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/es-ES/subtitle.txt rename to fastlane/metadata/ios/es-ES/subtitle.txt diff --git a/ios/fastlane/metadata/es-ES/support_url.txt b/fastlane/metadata/ios/es-ES/support_url.txt similarity index 100% rename from ios/fastlane/metadata/es-ES/support_url.txt rename to fastlane/metadata/ios/es-ES/support_url.txt diff --git a/ios/fastlane/metadata/es-MX/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/es-MX/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/es-MX/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/es-MX/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/es-MX/description.txt b/fastlane/metadata/ios/es-MX/description.txt similarity index 100% rename from ios/fastlane/metadata/es-MX/description.txt rename to fastlane/metadata/ios/es-MX/description.txt diff --git a/ios/fastlane/metadata/es-MX/keywords.txt b/fastlane/metadata/ios/es-MX/keywords.txt similarity index 100% rename from ios/fastlane/metadata/es-MX/keywords.txt rename to fastlane/metadata/ios/es-MX/keywords.txt diff --git a/ios/fastlane/metadata/es-MX/marketing_url.txt b/fastlane/metadata/ios/es-MX/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/es-MX/marketing_url.txt rename to fastlane/metadata/ios/es-MX/marketing_url.txt diff --git a/ios/fastlane/metadata/es-MX/name.txt b/fastlane/metadata/ios/es-MX/name.txt similarity index 100% rename from ios/fastlane/metadata/es-MX/name.txt rename to fastlane/metadata/ios/es-MX/name.txt diff --git a/ios/fastlane/metadata/es-MX/privacy_url.txt b/fastlane/metadata/ios/es-MX/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/es-MX/privacy_url.txt rename to fastlane/metadata/ios/es-MX/privacy_url.txt diff --git a/ios/fastlane/metadata/es-MX/promotional_text.txt b/fastlane/metadata/ios/es-MX/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/es-MX/promotional_text.txt rename to fastlane/metadata/ios/es-MX/promotional_text.txt diff --git a/ios/fastlane/metadata/es-MX/release_notes.txt b/fastlane/metadata/ios/es-MX/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/es-MX/release_notes.txt rename to fastlane/metadata/ios/es-MX/release_notes.txt diff --git a/ios/fastlane/metadata/es-MX/subtitle.txt b/fastlane/metadata/ios/es-MX/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/es-MX/subtitle.txt rename to fastlane/metadata/ios/es-MX/subtitle.txt diff --git a/ios/fastlane/metadata/es-MX/support_url.txt b/fastlane/metadata/ios/es-MX/support_url.txt similarity index 100% rename from ios/fastlane/metadata/es-MX/support_url.txt rename to fastlane/metadata/ios/es-MX/support_url.txt diff --git a/ios/fastlane/metadata/fi/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/fi/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/fi/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/fi/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/fi/description.txt b/fastlane/metadata/ios/fi/description.txt similarity index 100% rename from ios/fastlane/metadata/fi/description.txt rename to fastlane/metadata/ios/fi/description.txt diff --git a/ios/fastlane/metadata/fi/keywords.txt b/fastlane/metadata/ios/fi/keywords.txt similarity index 100% rename from ios/fastlane/metadata/fi/keywords.txt rename to fastlane/metadata/ios/fi/keywords.txt diff --git a/ios/fastlane/metadata/fi/marketing_url.txt b/fastlane/metadata/ios/fi/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/fi/marketing_url.txt rename to fastlane/metadata/ios/fi/marketing_url.txt diff --git a/ios/fastlane/metadata/fi/name.txt b/fastlane/metadata/ios/fi/name.txt similarity index 100% rename from ios/fastlane/metadata/fi/name.txt rename to fastlane/metadata/ios/fi/name.txt diff --git a/ios/fastlane/metadata/fi/privacy_url.txt b/fastlane/metadata/ios/fi/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/fi/privacy_url.txt rename to fastlane/metadata/ios/fi/privacy_url.txt diff --git a/ios/fastlane/metadata/fi/promotional_text.txt b/fastlane/metadata/ios/fi/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/fi/promotional_text.txt rename to fastlane/metadata/ios/fi/promotional_text.txt diff --git a/ios/fastlane/metadata/fi/release_notes.txt b/fastlane/metadata/ios/fi/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/fi/release_notes.txt rename to fastlane/metadata/ios/fi/release_notes.txt diff --git a/ios/fastlane/metadata/fi/subtitle.txt b/fastlane/metadata/ios/fi/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/fi/subtitle.txt rename to fastlane/metadata/ios/fi/subtitle.txt diff --git a/ios/fastlane/metadata/fi/support_url.txt b/fastlane/metadata/ios/fi/support_url.txt similarity index 100% rename from ios/fastlane/metadata/fi/support_url.txt rename to fastlane/metadata/ios/fi/support_url.txt diff --git a/ios/fastlane/metadata/fr-FR/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/fr-FR/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/fr-FR/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/fr-FR/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/fr-FR/description.txt b/fastlane/metadata/ios/fr-FR/description.txt similarity index 100% rename from ios/fastlane/metadata/fr-FR/description.txt rename to fastlane/metadata/ios/fr-FR/description.txt diff --git a/ios/fastlane/metadata/fr-FR/keywords.txt b/fastlane/metadata/ios/fr-FR/keywords.txt similarity index 100% rename from ios/fastlane/metadata/fr-FR/keywords.txt rename to fastlane/metadata/ios/fr-FR/keywords.txt diff --git a/ios/fastlane/metadata/fr-FR/marketing_url.txt b/fastlane/metadata/ios/fr-FR/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/fr-FR/marketing_url.txt rename to fastlane/metadata/ios/fr-FR/marketing_url.txt diff --git a/ios/fastlane/metadata/fr-FR/name.txt b/fastlane/metadata/ios/fr-FR/name.txt similarity index 100% rename from ios/fastlane/metadata/fr-FR/name.txt rename to fastlane/metadata/ios/fr-FR/name.txt diff --git a/ios/fastlane/metadata/fr-FR/privacy_url.txt b/fastlane/metadata/ios/fr-FR/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/fr-FR/privacy_url.txt rename to fastlane/metadata/ios/fr-FR/privacy_url.txt diff --git a/ios/fastlane/metadata/fr-FR/promotional_text.txt b/fastlane/metadata/ios/fr-FR/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/fr-FR/promotional_text.txt rename to fastlane/metadata/ios/fr-FR/promotional_text.txt diff --git a/ios/fastlane/metadata/fr-FR/release_notes.txt b/fastlane/metadata/ios/fr-FR/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/fr-FR/release_notes.txt rename to fastlane/metadata/ios/fr-FR/release_notes.txt diff --git a/ios/fastlane/metadata/fr-FR/subtitle.txt b/fastlane/metadata/ios/fr-FR/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/fr-FR/subtitle.txt rename to fastlane/metadata/ios/fr-FR/subtitle.txt diff --git a/ios/fastlane/metadata/fr-FR/support_url.txt b/fastlane/metadata/ios/fr-FR/support_url.txt similarity index 100% rename from ios/fastlane/metadata/fr-FR/support_url.txt rename to fastlane/metadata/ios/fr-FR/support_url.txt diff --git a/ios/fastlane/metadata/he/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/he/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/he/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/he/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/he/description.txt b/fastlane/metadata/ios/he/description.txt similarity index 100% rename from ios/fastlane/metadata/he/description.txt rename to fastlane/metadata/ios/he/description.txt diff --git a/ios/fastlane/metadata/he/keywords.txt b/fastlane/metadata/ios/he/keywords.txt similarity index 100% rename from ios/fastlane/metadata/he/keywords.txt rename to fastlane/metadata/ios/he/keywords.txt diff --git a/ios/fastlane/metadata/he/marketing_url.txt b/fastlane/metadata/ios/he/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/he/marketing_url.txt rename to fastlane/metadata/ios/he/marketing_url.txt diff --git a/ios/fastlane/metadata/he/name.txt b/fastlane/metadata/ios/he/name.txt similarity index 100% rename from ios/fastlane/metadata/he/name.txt rename to fastlane/metadata/ios/he/name.txt diff --git a/ios/fastlane/metadata/he/privacy_url.txt b/fastlane/metadata/ios/he/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/he/privacy_url.txt rename to fastlane/metadata/ios/he/privacy_url.txt diff --git a/ios/fastlane/metadata/he/promotional_text.txt b/fastlane/metadata/ios/he/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/he/promotional_text.txt rename to fastlane/metadata/ios/he/promotional_text.txt diff --git a/ios/fastlane/metadata/he/release_notes.txt b/fastlane/metadata/ios/he/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/he/release_notes.txt rename to fastlane/metadata/ios/he/release_notes.txt diff --git a/ios/fastlane/metadata/he/subtitle.txt b/fastlane/metadata/ios/he/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/he/subtitle.txt rename to fastlane/metadata/ios/he/subtitle.txt diff --git a/ios/fastlane/metadata/he/support_url.txt b/fastlane/metadata/ios/he/support_url.txt similarity index 100% rename from ios/fastlane/metadata/he/support_url.txt rename to fastlane/metadata/ios/he/support_url.txt diff --git a/ios/fastlane/metadata/hu/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/hu/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/hu/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/hu/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/hu/description.txt b/fastlane/metadata/ios/hu/description.txt similarity index 100% rename from ios/fastlane/metadata/hu/description.txt rename to fastlane/metadata/ios/hu/description.txt diff --git a/ios/fastlane/metadata/hu/keywords.txt b/fastlane/metadata/ios/hu/keywords.txt similarity index 100% rename from ios/fastlane/metadata/hu/keywords.txt rename to fastlane/metadata/ios/hu/keywords.txt diff --git a/ios/fastlane/metadata/hu/marketing_url.txt b/fastlane/metadata/ios/hu/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/hu/marketing_url.txt rename to fastlane/metadata/ios/hu/marketing_url.txt diff --git a/ios/fastlane/metadata/hu/name.txt b/fastlane/metadata/ios/hu/name.txt similarity index 100% rename from ios/fastlane/metadata/hu/name.txt rename to fastlane/metadata/ios/hu/name.txt diff --git a/ios/fastlane/metadata/hu/privacy_url.txt b/fastlane/metadata/ios/hu/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/hu/privacy_url.txt rename to fastlane/metadata/ios/hu/privacy_url.txt diff --git a/ios/fastlane/metadata/hu/promotional_text.txt b/fastlane/metadata/ios/hu/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/hu/promotional_text.txt rename to fastlane/metadata/ios/hu/promotional_text.txt diff --git a/ios/fastlane/metadata/hu/release_notes.txt b/fastlane/metadata/ios/hu/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/hu/release_notes.txt rename to fastlane/metadata/ios/hu/release_notes.txt diff --git a/ios/fastlane/metadata/hu/subtitle.txt b/fastlane/metadata/ios/hu/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/hu/subtitle.txt rename to fastlane/metadata/ios/hu/subtitle.txt diff --git a/ios/fastlane/metadata/hu/support_url.txt b/fastlane/metadata/ios/hu/support_url.txt similarity index 100% rename from ios/fastlane/metadata/hu/support_url.txt rename to fastlane/metadata/ios/hu/support_url.txt diff --git a/ios/fastlane/metadata/it/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/it/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/it/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/it/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/it/description.txt b/fastlane/metadata/ios/it/description.txt similarity index 100% rename from ios/fastlane/metadata/it/description.txt rename to fastlane/metadata/ios/it/description.txt diff --git a/ios/fastlane/metadata/it/keywords.txt b/fastlane/metadata/ios/it/keywords.txt similarity index 100% rename from ios/fastlane/metadata/it/keywords.txt rename to fastlane/metadata/ios/it/keywords.txt diff --git a/ios/fastlane/metadata/it/marketing_url.txt b/fastlane/metadata/ios/it/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/it/marketing_url.txt rename to fastlane/metadata/ios/it/marketing_url.txt diff --git a/ios/fastlane/metadata/it/name.txt b/fastlane/metadata/ios/it/name.txt similarity index 100% rename from ios/fastlane/metadata/it/name.txt rename to fastlane/metadata/ios/it/name.txt diff --git a/ios/fastlane/metadata/it/privacy_url.txt b/fastlane/metadata/ios/it/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/it/privacy_url.txt rename to fastlane/metadata/ios/it/privacy_url.txt diff --git a/ios/fastlane/metadata/it/promotional_text.txt b/fastlane/metadata/ios/it/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/it/promotional_text.txt rename to fastlane/metadata/ios/it/promotional_text.txt diff --git a/ios/fastlane/metadata/it/release_notes.txt b/fastlane/metadata/ios/it/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/it/release_notes.txt rename to fastlane/metadata/ios/it/release_notes.txt diff --git a/ios/fastlane/metadata/it/subtitle.txt b/fastlane/metadata/ios/it/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/it/subtitle.txt rename to fastlane/metadata/ios/it/subtitle.txt diff --git a/ios/fastlane/metadata/it/support_url.txt b/fastlane/metadata/ios/it/support_url.txt similarity index 100% rename from ios/fastlane/metadata/it/support_url.txt rename to fastlane/metadata/ios/it/support_url.txt diff --git a/fastlane/metadata/ios/ja/description.txt b/fastlane/metadata/ios/ja/description.txt new file mode 100644 index 00000000000..c70504381a1 --- /dev/null +++ b/fastlane/metadata/ios/ja/description.txt @@ -0,0 +1,45 @@ +BlueWalletは、セキュリティと使いやすさに特化した、ビットコインの保管・送金・受取に使える便利なウォレットです。 + +BlueWalletがあれば、ビットコインウォレットに、あなただけのプライベートキーをかけられます。BlueWalletは、コミュニティのためにビットコインユーザーが作った特製ビットコインウォレットです。 + +あなたのポケットから金融システムに革命を。世界中の誰とでも手軽な取引が可能に。 + +BlueWalletなら、無料で好きなだけビットコインウォレットを作成することも、すでにお使いのウォレットをインポートすることも、簡単に素早く行えます。 + +_____ + +BlueWalletの特長 + +1 – 堅牢なセキュリティ構造 + +【オープンソース】 +MITライセンス付き、ReactNative製。あなただけのセキュリティの構築・管理が可能に。 + +【情報隠蔽技術】 +アクセスの開示を余儀なく強要された場合は、相手にフェイクのビットコインウォレット用のパスワードを解読させます。 + +【完全な暗号化】 +iOSのマルチレイヤー暗号化はもちろん、追加のパスワードですべてを暗号化します。 + +【フルノード】 +Electrumを通じて、お持ちのビットコインをフルノードに接続します。 + +【コールドストレージ】 +ハードウェアウォレットに接続し、お持ちのビットコインをコールドストレージに保管できます。 + +2 – 徹底されたユーザーエクスペリエンス + +【コントロールはあなたの手に】 +あなただけのプライベートキーが、お使いの端末以外に保存されることはありません。キーの管理権は、あなただけのものです。 + +【案件によって異なる手数料】 +手数料は、1Satoshiごとに異なり、ユーザーによって定義されます。 + +【手数料の上書き(Replace-By-Fee)】 +RBFシステムで、手数料を増やす(BIP125)ことで、取引を高速化します。 + +【閲覧専用ウォレット】 +閲覧専用ウォレットを駆使することで、ハードウェアに触れることなく、コールドストレージを監視できます。 + +【ライトニングネットワーク】 +設定の手間を省いて、ライトニングウォレットを使えます。優れたビットコインユーザーエクスペリエンスを楽しみながら、圧倒的な低価格で、高速取引を実現します。 diff --git a/fastlane/metadata/ios/ja/keywords.txt b/fastlane/metadata/ios/ja/keywords.txt new file mode 100644 index 00000000000..dab2d7b0fe7 --- /dev/null +++ b/fastlane/metadata/ios/ja/keywords.txt @@ -0,0 +1 @@ +ビットコイン,ウォレット,ビットコインウォレット,ブロックチェーン,btc,仮想通貨,暗号資産,electrum,イーサリアム diff --git a/ios/fastlane/metadata/ja/name.txt b/fastlane/metadata/ios/ja/name.txt similarity index 100% rename from ios/fastlane/metadata/ja/name.txt rename to fastlane/metadata/ios/ja/name.txt diff --git a/fastlane/metadata/ios/ja/promotional_text.txt b/fastlane/metadata/ios/ja/promotional_text.txt new file mode 100644 index 00000000000..00041d8d2d7 --- /dev/null +++ b/fastlane/metadata/ios/ja/promotional_text.txt @@ -0,0 +1,10 @@ +特長 + +* オープンソース +* 完全な暗号化 +* 情報隠蔽技術 +* 案件によって異なる手数料 +* 手数料の上書き(Replace-By-Fee) +* SegWit +* 閲覧専用(Sentinel)ウォレット +* ライトニングネットワーク diff --git a/ios/fastlane/metadata/ms/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/ms/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/ms/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/ms/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/ms/description.txt b/fastlane/metadata/ios/ms/description.txt similarity index 100% rename from ios/fastlane/metadata/ms/description.txt rename to fastlane/metadata/ios/ms/description.txt diff --git a/ios/fastlane/metadata/ms/keywords.txt b/fastlane/metadata/ios/ms/keywords.txt similarity index 100% rename from ios/fastlane/metadata/ms/keywords.txt rename to fastlane/metadata/ios/ms/keywords.txt diff --git a/ios/fastlane/metadata/ms/marketing_url.txt b/fastlane/metadata/ios/ms/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/ms/marketing_url.txt rename to fastlane/metadata/ios/ms/marketing_url.txt diff --git a/ios/fastlane/metadata/ms/name.txt b/fastlane/metadata/ios/ms/name.txt similarity index 100% rename from ios/fastlane/metadata/ms/name.txt rename to fastlane/metadata/ios/ms/name.txt diff --git a/ios/fastlane/metadata/ms/privacy_url.txt b/fastlane/metadata/ios/ms/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/ms/privacy_url.txt rename to fastlane/metadata/ios/ms/privacy_url.txt diff --git a/ios/fastlane/metadata/ms/promotional_text.txt b/fastlane/metadata/ios/ms/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/ms/promotional_text.txt rename to fastlane/metadata/ios/ms/promotional_text.txt diff --git a/ios/fastlane/metadata/ms/release_notes.txt b/fastlane/metadata/ios/ms/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/ms/release_notes.txt rename to fastlane/metadata/ios/ms/release_notes.txt diff --git a/ios/fastlane/metadata/ms/subtitle.txt b/fastlane/metadata/ios/ms/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/ms/subtitle.txt rename to fastlane/metadata/ios/ms/subtitle.txt diff --git a/ios/fastlane/metadata/ms/support_url.txt b/fastlane/metadata/ios/ms/support_url.txt similarity index 100% rename from ios/fastlane/metadata/ms/support_url.txt rename to fastlane/metadata/ios/ms/support_url.txt diff --git a/ios/fastlane/metadata/nl-NL/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/nl-NL/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/nl-NL/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/nl-NL/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/nl-NL/description.txt b/fastlane/metadata/ios/nl-NL/description.txt similarity index 100% rename from ios/fastlane/metadata/nl-NL/description.txt rename to fastlane/metadata/ios/nl-NL/description.txt diff --git a/ios/fastlane/metadata/nl-NL/keywords.txt b/fastlane/metadata/ios/nl-NL/keywords.txt similarity index 100% rename from ios/fastlane/metadata/nl-NL/keywords.txt rename to fastlane/metadata/ios/nl-NL/keywords.txt diff --git a/ios/fastlane/metadata/nl-NL/marketing_url.txt b/fastlane/metadata/ios/nl-NL/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/nl-NL/marketing_url.txt rename to fastlane/metadata/ios/nl-NL/marketing_url.txt diff --git a/ios/fastlane/metadata/nl-NL/name.txt b/fastlane/metadata/ios/nl-NL/name.txt similarity index 100% rename from ios/fastlane/metadata/nl-NL/name.txt rename to fastlane/metadata/ios/nl-NL/name.txt diff --git a/ios/fastlane/metadata/nl-NL/privacy_url.txt b/fastlane/metadata/ios/nl-NL/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/nl-NL/privacy_url.txt rename to fastlane/metadata/ios/nl-NL/privacy_url.txt diff --git a/ios/fastlane/metadata/nl-NL/promotional_text.txt b/fastlane/metadata/ios/nl-NL/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/nl-NL/promotional_text.txt rename to fastlane/metadata/ios/nl-NL/promotional_text.txt diff --git a/ios/fastlane/metadata/nl-NL/release_notes.txt b/fastlane/metadata/ios/nl-NL/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/nl-NL/release_notes.txt rename to fastlane/metadata/ios/nl-NL/release_notes.txt diff --git a/ios/fastlane/metadata/nl-NL/subtitle.txt b/fastlane/metadata/ios/nl-NL/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/nl-NL/subtitle.txt rename to fastlane/metadata/ios/nl-NL/subtitle.txt diff --git a/ios/fastlane/metadata/nl-NL/support_url.txt b/fastlane/metadata/ios/nl-NL/support_url.txt similarity index 100% rename from ios/fastlane/metadata/nl-NL/support_url.txt rename to fastlane/metadata/ios/nl-NL/support_url.txt diff --git a/ios/fastlane/metadata/no/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/no/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/no/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/no/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/no/description.txt b/fastlane/metadata/ios/no/description.txt similarity index 100% rename from ios/fastlane/metadata/no/description.txt rename to fastlane/metadata/ios/no/description.txt diff --git a/ios/fastlane/metadata/no/keywords.txt b/fastlane/metadata/ios/no/keywords.txt similarity index 100% rename from ios/fastlane/metadata/no/keywords.txt rename to fastlane/metadata/ios/no/keywords.txt diff --git a/ios/fastlane/metadata/no/marketing_url.txt b/fastlane/metadata/ios/no/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/no/marketing_url.txt rename to fastlane/metadata/ios/no/marketing_url.txt diff --git a/ios/fastlane/metadata/no/name.txt b/fastlane/metadata/ios/no/name.txt similarity index 100% rename from ios/fastlane/metadata/no/name.txt rename to fastlane/metadata/ios/no/name.txt diff --git a/ios/fastlane/metadata/no/privacy_url.txt b/fastlane/metadata/ios/no/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/no/privacy_url.txt rename to fastlane/metadata/ios/no/privacy_url.txt diff --git a/ios/fastlane/metadata/no/promotional_text.txt b/fastlane/metadata/ios/no/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/no/promotional_text.txt rename to fastlane/metadata/ios/no/promotional_text.txt diff --git a/ios/fastlane/metadata/no/release_notes.txt b/fastlane/metadata/ios/no/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/no/release_notes.txt rename to fastlane/metadata/ios/no/release_notes.txt diff --git a/ios/fastlane/metadata/no/subtitle.txt b/fastlane/metadata/ios/no/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/no/subtitle.txt rename to fastlane/metadata/ios/no/subtitle.txt diff --git a/ios/fastlane/metadata/no/support_url.txt b/fastlane/metadata/ios/no/support_url.txt similarity index 100% rename from ios/fastlane/metadata/no/support_url.txt rename to fastlane/metadata/ios/no/support_url.txt diff --git a/ios/fastlane/metadata/pl/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/pl/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/pl/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/pl/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/pl/description.txt b/fastlane/metadata/ios/pl/description.txt similarity index 100% rename from ios/fastlane/metadata/pl/description.txt rename to fastlane/metadata/ios/pl/description.txt diff --git a/ios/fastlane/metadata/pl/keywords.txt b/fastlane/metadata/ios/pl/keywords.txt similarity index 100% rename from ios/fastlane/metadata/pl/keywords.txt rename to fastlane/metadata/ios/pl/keywords.txt diff --git a/ios/fastlane/metadata/pl/marketing_url.txt b/fastlane/metadata/ios/pl/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/pl/marketing_url.txt rename to fastlane/metadata/ios/pl/marketing_url.txt diff --git a/ios/fastlane/metadata/pl/name.txt b/fastlane/metadata/ios/pl/name.txt similarity index 100% rename from ios/fastlane/metadata/pl/name.txt rename to fastlane/metadata/ios/pl/name.txt diff --git a/ios/fastlane/metadata/pl/privacy_url.txt b/fastlane/metadata/ios/pl/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/pl/privacy_url.txt rename to fastlane/metadata/ios/pl/privacy_url.txt diff --git a/ios/fastlane/metadata/pl/promotional_text.txt b/fastlane/metadata/ios/pl/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/pl/promotional_text.txt rename to fastlane/metadata/ios/pl/promotional_text.txt diff --git a/ios/fastlane/metadata/pl/release_notes.txt b/fastlane/metadata/ios/pl/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/pl/release_notes.txt rename to fastlane/metadata/ios/pl/release_notes.txt diff --git a/ios/fastlane/metadata/pl/subtitle.txt b/fastlane/metadata/ios/pl/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/pl/subtitle.txt rename to fastlane/metadata/ios/pl/subtitle.txt diff --git a/ios/fastlane/metadata/pl/support_url.txt b/fastlane/metadata/ios/pl/support_url.txt similarity index 100% rename from ios/fastlane/metadata/pl/support_url.txt rename to fastlane/metadata/ios/pl/support_url.txt diff --git a/ios/fastlane/metadata/primary_category.txt b/fastlane/metadata/ios/primary_category.txt similarity index 100% rename from ios/fastlane/metadata/primary_category.txt rename to fastlane/metadata/ios/primary_category.txt diff --git a/ios/fastlane/metadata/primary_first_sub_category.txt b/fastlane/metadata/ios/primary_first_sub_category.txt similarity index 100% rename from ios/fastlane/metadata/primary_first_sub_category.txt rename to fastlane/metadata/ios/primary_first_sub_category.txt diff --git a/ios/fastlane/metadata/primary_second_sub_category.txt b/fastlane/metadata/ios/primary_second_sub_category.txt similarity index 100% rename from ios/fastlane/metadata/primary_second_sub_category.txt rename to fastlane/metadata/ios/primary_second_sub_category.txt diff --git a/ios/fastlane/metadata/pt-BR/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/pt-BR/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/pt-BR/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/pt-BR/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/pt-BR/description.txt b/fastlane/metadata/ios/pt-BR/description.txt similarity index 100% rename from ios/fastlane/metadata/pt-BR/description.txt rename to fastlane/metadata/ios/pt-BR/description.txt diff --git a/ios/fastlane/metadata/pt-BR/keywords.txt b/fastlane/metadata/ios/pt-BR/keywords.txt similarity index 100% rename from ios/fastlane/metadata/pt-BR/keywords.txt rename to fastlane/metadata/ios/pt-BR/keywords.txt diff --git a/ios/fastlane/metadata/pt-BR/marketing_url.txt b/fastlane/metadata/ios/pt-BR/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/pt-BR/marketing_url.txt rename to fastlane/metadata/ios/pt-BR/marketing_url.txt diff --git a/ios/fastlane/metadata/pt-BR/name.txt b/fastlane/metadata/ios/pt-BR/name.txt similarity index 100% rename from ios/fastlane/metadata/pt-BR/name.txt rename to fastlane/metadata/ios/pt-BR/name.txt diff --git a/ios/fastlane/metadata/pt-BR/privacy_url.txt b/fastlane/metadata/ios/pt-BR/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/pt-BR/privacy_url.txt rename to fastlane/metadata/ios/pt-BR/privacy_url.txt diff --git a/ios/fastlane/metadata/pt-BR/promotional_text.txt b/fastlane/metadata/ios/pt-BR/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/pt-BR/promotional_text.txt rename to fastlane/metadata/ios/pt-BR/promotional_text.txt diff --git a/ios/fastlane/metadata/pt-BR/release_notes.txt b/fastlane/metadata/ios/pt-BR/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/pt-BR/release_notes.txt rename to fastlane/metadata/ios/pt-BR/release_notes.txt diff --git a/ios/fastlane/metadata/pt-BR/subtitle.txt b/fastlane/metadata/ios/pt-BR/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/pt-BR/subtitle.txt rename to fastlane/metadata/ios/pt-BR/subtitle.txt diff --git a/ios/fastlane/metadata/pt-BR/support_url.txt b/fastlane/metadata/ios/pt-BR/support_url.txt similarity index 100% rename from ios/fastlane/metadata/pt-BR/support_url.txt rename to fastlane/metadata/ios/pt-BR/support_url.txt diff --git a/ios/fastlane/metadata/pt-PT/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/pt-PT/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/pt-PT/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/pt-PT/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/pt-PT/description.txt b/fastlane/metadata/ios/pt-PT/description.txt similarity index 100% rename from ios/fastlane/metadata/pt-PT/description.txt rename to fastlane/metadata/ios/pt-PT/description.txt diff --git a/ios/fastlane/metadata/pt-PT/keywords.txt b/fastlane/metadata/ios/pt-PT/keywords.txt similarity index 100% rename from ios/fastlane/metadata/pt-PT/keywords.txt rename to fastlane/metadata/ios/pt-PT/keywords.txt diff --git a/ios/fastlane/metadata/pt-PT/marketing_url.txt b/fastlane/metadata/ios/pt-PT/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/pt-PT/marketing_url.txt rename to fastlane/metadata/ios/pt-PT/marketing_url.txt diff --git a/ios/fastlane/metadata/pt-PT/name.txt b/fastlane/metadata/ios/pt-PT/name.txt similarity index 100% rename from ios/fastlane/metadata/pt-PT/name.txt rename to fastlane/metadata/ios/pt-PT/name.txt diff --git a/ios/fastlane/metadata/pt-PT/privacy_url.txt b/fastlane/metadata/ios/pt-PT/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/pt-PT/privacy_url.txt rename to fastlane/metadata/ios/pt-PT/privacy_url.txt diff --git a/ios/fastlane/metadata/pt-PT/promotional_text.txt b/fastlane/metadata/ios/pt-PT/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/pt-PT/promotional_text.txt rename to fastlane/metadata/ios/pt-PT/promotional_text.txt diff --git a/ios/fastlane/metadata/pt-PT/release_notes.txt b/fastlane/metadata/ios/pt-PT/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/pt-PT/release_notes.txt rename to fastlane/metadata/ios/pt-PT/release_notes.txt diff --git a/ios/fastlane/metadata/pt-PT/subtitle.txt b/fastlane/metadata/ios/pt-PT/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/pt-PT/subtitle.txt rename to fastlane/metadata/ios/pt-PT/subtitle.txt diff --git a/ios/fastlane/metadata/pt-PT/support_url.txt b/fastlane/metadata/ios/pt-PT/support_url.txt similarity index 100% rename from ios/fastlane/metadata/pt-PT/support_url.txt rename to fastlane/metadata/ios/pt-PT/support_url.txt diff --git a/ios/fastlane/metadata/review_information/demo_password.txt b/fastlane/metadata/ios/review_information/demo_password.txt similarity index 100% rename from ios/fastlane/metadata/review_information/demo_password.txt rename to fastlane/metadata/ios/review_information/demo_password.txt diff --git a/ios/fastlane/metadata/review_information/demo_user.txt b/fastlane/metadata/ios/review_information/demo_user.txt similarity index 100% rename from ios/fastlane/metadata/review_information/demo_user.txt rename to fastlane/metadata/ios/review_information/demo_user.txt diff --git a/ios/fastlane/metadata/review_information/email_address.txt b/fastlane/metadata/ios/review_information/email_address.txt similarity index 100% rename from ios/fastlane/metadata/review_information/email_address.txt rename to fastlane/metadata/ios/review_information/email_address.txt diff --git a/ios/fastlane/metadata/review_information/first_name.txt b/fastlane/metadata/ios/review_information/first_name.txt similarity index 100% rename from ios/fastlane/metadata/review_information/first_name.txt rename to fastlane/metadata/ios/review_information/first_name.txt diff --git a/ios/fastlane/metadata/review_information/last_name.txt b/fastlane/metadata/ios/review_information/last_name.txt similarity index 100% rename from ios/fastlane/metadata/review_information/last_name.txt rename to fastlane/metadata/ios/review_information/last_name.txt diff --git a/ios/fastlane/metadata/review_information/notes.txt b/fastlane/metadata/ios/review_information/notes.txt similarity index 100% rename from ios/fastlane/metadata/review_information/notes.txt rename to fastlane/metadata/ios/review_information/notes.txt diff --git a/ios/fastlane/metadata/review_information/phone_number.txt b/fastlane/metadata/ios/review_information/phone_number.txt similarity index 100% rename from ios/fastlane/metadata/review_information/phone_number.txt rename to fastlane/metadata/ios/review_information/phone_number.txt diff --git a/ios/fastlane/metadata/ro/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/ro/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/ro/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/ro/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/ro/description.txt b/fastlane/metadata/ios/ro/description.txt similarity index 100% rename from ios/fastlane/metadata/ro/description.txt rename to fastlane/metadata/ios/ro/description.txt diff --git a/ios/fastlane/metadata/ro/keywords.txt b/fastlane/metadata/ios/ro/keywords.txt similarity index 100% rename from ios/fastlane/metadata/ro/keywords.txt rename to fastlane/metadata/ios/ro/keywords.txt diff --git a/ios/fastlane/metadata/ro/marketing_url.txt b/fastlane/metadata/ios/ro/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/ro/marketing_url.txt rename to fastlane/metadata/ios/ro/marketing_url.txt diff --git a/ios/fastlane/metadata/ro/name.txt b/fastlane/metadata/ios/ro/name.txt similarity index 100% rename from ios/fastlane/metadata/ro/name.txt rename to fastlane/metadata/ios/ro/name.txt diff --git a/ios/fastlane/metadata/ro/privacy_url.txt b/fastlane/metadata/ios/ro/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/ro/privacy_url.txt rename to fastlane/metadata/ios/ro/privacy_url.txt diff --git a/ios/fastlane/metadata/ro/promotional_text.txt b/fastlane/metadata/ios/ro/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/ro/promotional_text.txt rename to fastlane/metadata/ios/ro/promotional_text.txt diff --git a/ios/fastlane/metadata/ro/release_notes.txt b/fastlane/metadata/ios/ro/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/ro/release_notes.txt rename to fastlane/metadata/ios/ro/release_notes.txt diff --git a/ios/fastlane/metadata/ro/subtitle.txt b/fastlane/metadata/ios/ro/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/ro/subtitle.txt rename to fastlane/metadata/ios/ro/subtitle.txt diff --git a/ios/fastlane/metadata/ro/support_url.txt b/fastlane/metadata/ios/ro/support_url.txt similarity index 100% rename from ios/fastlane/metadata/ro/support_url.txt rename to fastlane/metadata/ios/ro/support_url.txt diff --git a/ios/fastlane/metadata/ru/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/ru/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/ru/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/ru/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/ru/description.txt b/fastlane/metadata/ios/ru/description.txt similarity index 100% rename from ios/fastlane/metadata/ru/description.txt rename to fastlane/metadata/ios/ru/description.txt diff --git a/ios/fastlane/metadata/ru/keywords.txt b/fastlane/metadata/ios/ru/keywords.txt similarity index 100% rename from ios/fastlane/metadata/ru/keywords.txt rename to fastlane/metadata/ios/ru/keywords.txt diff --git a/ios/fastlane/metadata/ru/marketing_url.txt b/fastlane/metadata/ios/ru/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/ru/marketing_url.txt rename to fastlane/metadata/ios/ru/marketing_url.txt diff --git a/ios/fastlane/metadata/ru/name.txt b/fastlane/metadata/ios/ru/name.txt similarity index 100% rename from ios/fastlane/metadata/ru/name.txt rename to fastlane/metadata/ios/ru/name.txt diff --git a/ios/fastlane/metadata/ru/privacy_url.txt b/fastlane/metadata/ios/ru/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/ru/privacy_url.txt rename to fastlane/metadata/ios/ru/privacy_url.txt diff --git a/ios/fastlane/metadata/ru/promotional_text.txt b/fastlane/metadata/ios/ru/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/ru/promotional_text.txt rename to fastlane/metadata/ios/ru/promotional_text.txt diff --git a/ios/fastlane/metadata/ru/release_notes.txt b/fastlane/metadata/ios/ru/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/ru/release_notes.txt rename to fastlane/metadata/ios/ru/release_notes.txt diff --git a/ios/fastlane/metadata/ru/subtitle.txt b/fastlane/metadata/ios/ru/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/ru/subtitle.txt rename to fastlane/metadata/ios/ru/subtitle.txt diff --git a/ios/fastlane/metadata/ru/support_url.txt b/fastlane/metadata/ios/ru/support_url.txt similarity index 100% rename from ios/fastlane/metadata/ru/support_url.txt rename to fastlane/metadata/ios/ru/support_url.txt diff --git a/ios/fastlane/metadata/secondary_category.txt b/fastlane/metadata/ios/secondary_category.txt similarity index 100% rename from ios/fastlane/metadata/secondary_category.txt rename to fastlane/metadata/ios/secondary_category.txt diff --git a/ios/fastlane/metadata/secondary_first_sub_category.txt b/fastlane/metadata/ios/secondary_first_sub_category.txt similarity index 100% rename from ios/fastlane/metadata/secondary_first_sub_category.txt rename to fastlane/metadata/ios/secondary_first_sub_category.txt diff --git a/ios/fastlane/metadata/secondary_second_sub_category.txt b/fastlane/metadata/ios/secondary_second_sub_category.txt similarity index 100% rename from ios/fastlane/metadata/secondary_second_sub_category.txt rename to fastlane/metadata/ios/secondary_second_sub_category.txt diff --git a/ios/fastlane/metadata/sv/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/sv/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/sv/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/sv/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/sv/description.txt b/fastlane/metadata/ios/sv/description.txt similarity index 100% rename from ios/fastlane/metadata/sv/description.txt rename to fastlane/metadata/ios/sv/description.txt diff --git a/ios/fastlane/metadata/sv/keywords.txt b/fastlane/metadata/ios/sv/keywords.txt similarity index 100% rename from ios/fastlane/metadata/sv/keywords.txt rename to fastlane/metadata/ios/sv/keywords.txt diff --git a/ios/fastlane/metadata/sv/marketing_url.txt b/fastlane/metadata/ios/sv/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/sv/marketing_url.txt rename to fastlane/metadata/ios/sv/marketing_url.txt diff --git a/ios/fastlane/metadata/sv/name.txt b/fastlane/metadata/ios/sv/name.txt similarity index 100% rename from ios/fastlane/metadata/sv/name.txt rename to fastlane/metadata/ios/sv/name.txt diff --git a/ios/fastlane/metadata/sv/privacy_url.txt b/fastlane/metadata/ios/sv/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/sv/privacy_url.txt rename to fastlane/metadata/ios/sv/privacy_url.txt diff --git a/ios/fastlane/metadata/sv/promotional_text.txt b/fastlane/metadata/ios/sv/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/sv/promotional_text.txt rename to fastlane/metadata/ios/sv/promotional_text.txt diff --git a/ios/fastlane/metadata/sv/release_notes.txt b/fastlane/metadata/ios/sv/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/sv/release_notes.txt rename to fastlane/metadata/ios/sv/release_notes.txt diff --git a/ios/fastlane/metadata/sv/subtitle.txt b/fastlane/metadata/ios/sv/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/sv/subtitle.txt rename to fastlane/metadata/ios/sv/subtitle.txt diff --git a/ios/fastlane/metadata/sv/support_url.txt b/fastlane/metadata/ios/sv/support_url.txt similarity index 100% rename from ios/fastlane/metadata/sv/support_url.txt rename to fastlane/metadata/ios/sv/support_url.txt diff --git a/ios/fastlane/metadata/th/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/th/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/th/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/th/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/th/description.txt b/fastlane/metadata/ios/th/description.txt similarity index 100% rename from ios/fastlane/metadata/th/description.txt rename to fastlane/metadata/ios/th/description.txt diff --git a/ios/fastlane/metadata/th/keywords.txt b/fastlane/metadata/ios/th/keywords.txt similarity index 100% rename from ios/fastlane/metadata/th/keywords.txt rename to fastlane/metadata/ios/th/keywords.txt diff --git a/ios/fastlane/metadata/th/marketing_url.txt b/fastlane/metadata/ios/th/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/th/marketing_url.txt rename to fastlane/metadata/ios/th/marketing_url.txt diff --git a/ios/fastlane/metadata/th/name.txt b/fastlane/metadata/ios/th/name.txt similarity index 100% rename from ios/fastlane/metadata/th/name.txt rename to fastlane/metadata/ios/th/name.txt diff --git a/ios/fastlane/metadata/th/privacy_url.txt b/fastlane/metadata/ios/th/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/th/privacy_url.txt rename to fastlane/metadata/ios/th/privacy_url.txt diff --git a/ios/fastlane/metadata/th/promotional_text.txt b/fastlane/metadata/ios/th/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/th/promotional_text.txt rename to fastlane/metadata/ios/th/promotional_text.txt diff --git a/ios/fastlane/metadata/th/release_notes.txt b/fastlane/metadata/ios/th/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/th/release_notes.txt rename to fastlane/metadata/ios/th/release_notes.txt diff --git a/ios/fastlane/metadata/th/subtitle.txt b/fastlane/metadata/ios/th/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/th/subtitle.txt rename to fastlane/metadata/ios/th/subtitle.txt diff --git a/ios/fastlane/metadata/th/support_url.txt b/fastlane/metadata/ios/th/support_url.txt similarity index 100% rename from ios/fastlane/metadata/th/support_url.txt rename to fastlane/metadata/ios/th/support_url.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/address_line1.txt b/fastlane/metadata/ios/trade_representative_contact_information/address_line1.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/address_line1.txt rename to fastlane/metadata/ios/trade_representative_contact_information/address_line1.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/address_line2.txt b/fastlane/metadata/ios/trade_representative_contact_information/address_line2.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/address_line2.txt rename to fastlane/metadata/ios/trade_representative_contact_information/address_line2.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/address_line3.txt b/fastlane/metadata/ios/trade_representative_contact_information/address_line3.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/address_line3.txt rename to fastlane/metadata/ios/trade_representative_contact_information/address_line3.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/city_name.txt b/fastlane/metadata/ios/trade_representative_contact_information/city_name.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/city_name.txt rename to fastlane/metadata/ios/trade_representative_contact_information/city_name.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/country.txt b/fastlane/metadata/ios/trade_representative_contact_information/country.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/country.txt rename to fastlane/metadata/ios/trade_representative_contact_information/country.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/email_address.txt b/fastlane/metadata/ios/trade_representative_contact_information/email_address.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/email_address.txt rename to fastlane/metadata/ios/trade_representative_contact_information/email_address.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/first_name.txt b/fastlane/metadata/ios/trade_representative_contact_information/first_name.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/first_name.txt rename to fastlane/metadata/ios/trade_representative_contact_information/first_name.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/is_displayed_on_app_store.txt b/fastlane/metadata/ios/trade_representative_contact_information/is_displayed_on_app_store.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/is_displayed_on_app_store.txt rename to fastlane/metadata/ios/trade_representative_contact_information/is_displayed_on_app_store.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/last_name.txt b/fastlane/metadata/ios/trade_representative_contact_information/last_name.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/last_name.txt rename to fastlane/metadata/ios/trade_representative_contact_information/last_name.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/phone_number.txt b/fastlane/metadata/ios/trade_representative_contact_information/phone_number.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/phone_number.txt rename to fastlane/metadata/ios/trade_representative_contact_information/phone_number.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/postal_code.txt b/fastlane/metadata/ios/trade_representative_contact_information/postal_code.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/postal_code.txt rename to fastlane/metadata/ios/trade_representative_contact_information/postal_code.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/state.txt b/fastlane/metadata/ios/trade_representative_contact_information/state.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/state.txt rename to fastlane/metadata/ios/trade_representative_contact_information/state.txt diff --git a/ios/fastlane/metadata/trade_representative_contact_information/trade_name.txt b/fastlane/metadata/ios/trade_representative_contact_information/trade_name.txt similarity index 100% rename from ios/fastlane/metadata/trade_representative_contact_information/trade_name.txt rename to fastlane/metadata/ios/trade_representative_contact_information/trade_name.txt diff --git a/ios/fastlane/metadata/watch_icon.jpg b/fastlane/metadata/ios/watch_icon.jpg similarity index 100% rename from ios/fastlane/metadata/watch_icon.jpg rename to fastlane/metadata/ios/watch_icon.jpg diff --git a/ios/fastlane/metadata/zh-Hans/apple_tv_privacy_policy.txt b/fastlane/metadata/ios/zh-Hans/apple_tv_privacy_policy.txt similarity index 100% rename from ios/fastlane/metadata/zh-Hans/apple_tv_privacy_policy.txt rename to fastlane/metadata/ios/zh-Hans/apple_tv_privacy_policy.txt diff --git a/ios/fastlane/metadata/zh-Hans/description.txt b/fastlane/metadata/ios/zh-Hans/description.txt similarity index 100% rename from ios/fastlane/metadata/zh-Hans/description.txt rename to fastlane/metadata/ios/zh-Hans/description.txt diff --git a/ios/fastlane/metadata/zh-Hans/keywords.txt b/fastlane/metadata/ios/zh-Hans/keywords.txt similarity index 100% rename from ios/fastlane/metadata/zh-Hans/keywords.txt rename to fastlane/metadata/ios/zh-Hans/keywords.txt diff --git a/ios/fastlane/metadata/zh-Hans/marketing_url.txt b/fastlane/metadata/ios/zh-Hans/marketing_url.txt similarity index 100% rename from ios/fastlane/metadata/zh-Hans/marketing_url.txt rename to fastlane/metadata/ios/zh-Hans/marketing_url.txt diff --git a/ios/fastlane/metadata/zh-Hans/name.txt b/fastlane/metadata/ios/zh-Hans/name.txt similarity index 100% rename from ios/fastlane/metadata/zh-Hans/name.txt rename to fastlane/metadata/ios/zh-Hans/name.txt diff --git a/ios/fastlane/metadata/zh-Hans/privacy_url.txt b/fastlane/metadata/ios/zh-Hans/privacy_url.txt similarity index 100% rename from ios/fastlane/metadata/zh-Hans/privacy_url.txt rename to fastlane/metadata/ios/zh-Hans/privacy_url.txt diff --git a/ios/fastlane/metadata/zh-Hans/promotional_text.txt b/fastlane/metadata/ios/zh-Hans/promotional_text.txt similarity index 100% rename from ios/fastlane/metadata/zh-Hans/promotional_text.txt rename to fastlane/metadata/ios/zh-Hans/promotional_text.txt diff --git a/ios/fastlane/metadata/zh-Hans/release_notes.txt b/fastlane/metadata/ios/zh-Hans/release_notes.txt similarity index 100% rename from ios/fastlane/metadata/zh-Hans/release_notes.txt rename to fastlane/metadata/ios/zh-Hans/release_notes.txt diff --git a/ios/fastlane/metadata/zh-Hans/subtitle.txt b/fastlane/metadata/ios/zh-Hans/subtitle.txt similarity index 100% rename from ios/fastlane/metadata/zh-Hans/subtitle.txt rename to fastlane/metadata/ios/zh-Hans/subtitle.txt diff --git a/ios/fastlane/metadata/zh-Hans/support_url.txt b/fastlane/metadata/ios/zh-Hans/support_url.txt similarity index 100% rename from ios/fastlane/metadata/zh-Hans/support_url.txt rename to fastlane/metadata/ios/zh-Hans/support_url.txt diff --git a/gesture-handler.js b/gesture-handler.js new file mode 100644 index 00000000000..d76ad9a20f2 --- /dev/null +++ b/gesture-handler.js @@ -0,0 +1 @@ +// Don't import react-native-gesture-handler on web diff --git a/gesture-handler.native.js b/gesture-handler.native.js new file mode 100644 index 00000000000..d025d51ef56 --- /dev/null +++ b/gesture-handler.native.js @@ -0,0 +1,2 @@ +// Only import react-native-gesture-handler on native platforms +import 'react-native-gesture-handler'; diff --git a/helpers/confirm.ts b/helpers/confirm.ts index 2aa49c33dd8..fefecb64962 100644 --- a/helpers/confirm.ts +++ b/helpers/confirm.ts @@ -10,7 +10,7 @@ import loc from '../loc'; * * @return {Promise} */ -module.exports = function (title = 'Are you sure?', text = ''): Promise { +export default function (title = 'Are you sure?', text = ''): Promise { return new Promise(resolve => { Alert.alert( title, @@ -30,4 +30,4 @@ module.exports = function (title = 'Are you sure?', text = ''): Promise { cancelable: false }, ); }); -}; +} diff --git a/helpers/lndHub.ts b/helpers/lndHub.ts new file mode 100644 index 00000000000..e7489b0fb04 --- /dev/null +++ b/helpers/lndHub.ts @@ -0,0 +1,49 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import DefaultPreference from 'react-native-default-preference'; +import { BlueApp } from '../class/blue-app'; +import { GROUP_IO_BLUEWALLET } from '../blue_modules/currency'; + +// Function to get the value from DefaultPreference first, then fallback to AsyncStorage +// as DefaultPreference uses truly native storage. +// If found in AsyncStorage, migrate it to DefaultPreference and remove it from AsyncStorage. +export const getLNDHub = async (): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + let value = await DefaultPreference.get(BlueApp.LNDHUB) as string | null; + + // If not found, check AsyncStorage and migrate it to DefaultPreference + if (!value) { + value = await AsyncStorage.getItem(BlueApp.LNDHUB); + + if (value) { + await DefaultPreference.set(BlueApp.LNDHUB, value); + await AsyncStorage.removeItem(BlueApp.LNDHUB); + console.log('Migrated LNDHub value from AsyncStorage to DefaultPreference'); + } + } + + return value ?? undefined; + } catch (error) { + console.error('Error getting LNDHub preference:', (error as Error).message); + return undefined; + } +}; + +export const setLNDHub = async (value: string): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + await DefaultPreference.set(BlueApp.LNDHUB, value); + } catch (error) { + console.error('Error setting LNDHub preference:', error); + } +}; + +export const clearLNDHub = async (): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + await DefaultPreference.clear(BlueApp.LNDHUB); + await AsyncStorage.removeItem(BlueApp.LNDHUB); + } catch (error) { + console.error('Error clearing LNDHub preference:', error); + } +}; \ No newline at end of file diff --git a/helpers/presentWalletExportReminder.ts b/helpers/presentWalletExportReminder.ts new file mode 100644 index 00000000000..a68bab05daa --- /dev/null +++ b/helpers/presentWalletExportReminder.ts @@ -0,0 +1,17 @@ +import { Alert } from 'react-native'; +import loc from '../loc'; + +export const presentWalletExportReminder = (): Promise => { + return new Promise((resolve, reject) => { + Alert.alert( + loc.wallets.details_title, + loc.pleasebackup.ask, + [ + { text: loc.pleasebackup.ask_yes, onPress: () => resolve(), style: 'default' }, + { text: loc.pleasebackup.ask_no, onPress: () => reject(new Error('User has denied saving the wallet backup.')) }, + { text: loc._.cancel, style: 'cancel' }, + ], + { cancelable: true }, + ); + }); +}; diff --git a/helpers/prompt.ts b/helpers/prompt.ts index 1d3c105b58f..be1642498ba 100644 --- a/helpers/prompt.ts +++ b/helpers/prompt.ts @@ -2,14 +2,18 @@ import { Platform } from 'react-native'; import prompt from 'react-native-prompt-android'; import loc from '../loc'; -module.exports = ( - title: string, - text: string, - isCancelable = true, - type: PromptType | PromptTypeIOS | PromptTypeAndroid = 'secure-text', - isOKDestructive = false, - continueButtonText = loc._.ok, -): Promise => { +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 => { + 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') { @@ -18,7 +22,7 @@ module.exports = ( } return new Promise((resolve, reject) => { - const buttons: Array = isCancelable + const buttons: Array = cancelable ? [ { text: loc._.cancel, @@ -33,7 +37,7 @@ module.exports = ( console.log('OK Pressed'); resolve(password); }, - style: isOKDestructive ? 'destructive' : 'default', + style: destructive ? 'destructive' : 'default', }, ] : [ @@ -46,11 +50,12 @@ module.exports = ( }, ]; - prompt(title, text, buttons, { + 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, + ...(defaultValue !== undefined && { defaultValue }), }); }); }; diff --git a/helpers/scan-qr.ts b/helpers/scan-qr.ts index 433bbed633c..a638acca891 100644 --- a/helpers/scan-qr.ts +++ b/helpers/scan-qr.ts @@ -1,39 +1,37 @@ -/** - * Helper function that navigates to ScanQR screen, and returns promise that will resolve with the result of a scan, - * and then navigates back. If QRCode scan was closed, promise resolves to null. - * - * @param navigateFunc {function} - * @param currentScreenName {string} - * @param showFileImportButton {boolean} - * - * @return {Promise} - */ -module.exports = function scanQrHelper( - navigateFunc: (scr: string, params?: any) => void, - currentScreenName: string, - showFileImportButton = true, -): Promise { - return new Promise(resolve => { - const params = { - showFileImportButton: Boolean(showFileImportButton), - onBarScanned: (data: any) => {}, - onDismiss: () => {}, - }; +import { Platform } from 'react-native'; +import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions'; +import { navigationRef } from '../NavigationService.ts'; + +let scanWasBBQR = false; - params.onBarScanned = function (data: any) { - setTimeout(() => resolve(data.data || data), 1); - navigateFunc(currentScreenName); - }; +const isCameraAuthorizationStatusGranted = async () => { + const status = await check(Platform.OS === 'android' ? PERMISSIONS.ANDROID.CAMERA : PERMISSIONS.IOS.CAMERA); + return status === RESULTS.GRANTED; +}; - params.onDismiss = function () { - setTimeout(() => resolve(null), 1); - }; +const requestCameraAuthorization = () => { + return request(Platform.OS === 'android' ? PERMISSIONS.ANDROID.CAMERA : PERMISSIONS.IOS.CAMERA); +}; - navigateFunc('ScanQRCodeRoot', { - screen: 'ScanQRCode', - params, - }); +const scanQrHelper = async (): Promise => { + await requestCameraAuthorization(); + return new Promise(resolve => { + if (navigationRef.isReady()) { + navigationRef.navigate('ScanQRCode', { + showFileImportButton: true, + onBarScanned: (data: string, useBBQR: true) => { + // this is not a flag of most recent BBQR format, its a flag if in a lifetime or app there was a BBQR scan + scanWasBBQR = scanWasBBQR || useBBQR; + resolve(data); + }, + }); + } }); }; -export {}; +const getScanWasBBQR = () => scanWasBBQR; +const resetScanWasBBQR = () => { + scanWasBBQR = false; +}; + +export { isCameraAuthorizationStatusGranted, requestCameraAuthorization, scanQrHelper, getScanWasBBQR, resetScanWasBBQR }; diff --git a/helpers/select-wallet.ts b/helpers/select-wallet.ts index 4838eac007a..7086a730d37 100644 --- a/helpers/select-wallet.ts +++ b/helpers/select-wallet.ts @@ -2,47 +2,47 @@ * Helper function to select wallet. * Navigates to selector screen, and then navigates back while resolving promise with selected wallet. * - * @param navigateFunc {function} Function that does navigatino should be passed from outside + * @param navigation - return value of useExtendedNavigation, so inside helper we can navigate to selector screen and back * @param currentScreenName {string} Current screen name, so we know to what screen to get back to - * @param chainType {string} One of `Chain.` constant to be used to filter wallet pannels to show + * @param chainType {string} One of `Chain.` constant to be used to filter wallet panels to show * @param availableWallets {array} Wallets to be present in selector. If set, overrides `chainType` * @param noWalletExplanationText {string} Text that is displayed when there are no wallets to select from * - * @returns {Promise} + * @returns {Promise} */ -import { AbstractWallet } from '../class'; +import { TWallet } from '../class/wallets/types'; +import { useExtendedNavigation } from '../hooks/useExtendedNavigation'; -module.exports = function ( - navigateFunc: (scr: string, params?: any) => void, +export default function ( + navigation: ReturnType, currentScreenName: string, chainType: string | null, - availableWallets?: AbstractWallet[], + availableWallets?: TWallet[], noWalletExplanationText = '', -): Promise { +): Promise { return new Promise((resolve, reject) => { if (!currentScreenName) return reject(new Error('currentScreenName is not provided')); const params: { chainType: string | null; - availableWallets?: AbstractWallet[]; + availableWallets?: TWallet[]; noWalletExplanationText?: string; - onWalletSelect: (selectedWallet: AbstractWallet) => void; + onWalletSelect: (selectedWallet: TWallet) => void; } = { chainType: null, - onWalletSelect: (selectedWallet: AbstractWallet) => {}, + onWalletSelect: (selectedWallet: TWallet) => {}, }; if (chainType) params.chainType = chainType; if (availableWallets) params.availableWallets = availableWallets; if (noWalletExplanationText) params.noWalletExplanationText = noWalletExplanationText; - params.onWalletSelect = function (selectedWallet: AbstractWallet) { + params.onWalletSelect = function (selectedWallet: TWallet) { if (!selectedWallet) return; - setTimeout(() => resolve(selectedWallet), 1); - console.warn('trying to navigate back to', currentScreenName); - navigateFunc(currentScreenName); + setTimeout(() => resolve(selectedWallet), 100); + navigation.goBack(); }; - navigateFunc('SelectWallet', params); + navigation.navigate('SelectWallet', params); }); -}; +} diff --git a/hooks/context/useSettings.ts b/hooks/context/useSettings.ts new file mode 100644 index 00000000000..2af1959e0c4 --- /dev/null +++ b/hooks/context/useSettings.ts @@ -0,0 +1,4 @@ +import { useContext } from 'react'; +import { SettingsContext } from '../../components/Context/SettingsProvider'; + +export const useSettings = () => useContext(SettingsContext); diff --git a/hooks/context/useStorage.ts b/hooks/context/useStorage.ts new file mode 100644 index 00000000000..4394e3d157e --- /dev/null +++ b/hooks/context/useStorage.ts @@ -0,0 +1,4 @@ +import { useContext } from 'react'; +import { StorageContext } from '../../components/Context/StorageProvider'; + +export const useStorage = () => useContext(StorageContext); diff --git a/hooks/useAnimateOnChange.ts b/hooks/useAnimateOnChange.ts new file mode 100644 index 00000000000..70b648b6555 --- /dev/null +++ b/hooks/useAnimateOnChange.ts @@ -0,0 +1,37 @@ +import { useAnimatedReaction, useAnimatedStyle, useSharedValue, withTiming, Easing, interpolate } from 'react-native-reanimated'; + +const useAnimateOnChange = (value: T) => { + const progress = useSharedValue(1); + + useAnimatedReaction( + () => { + return value; + }, + (current, previous) => { + if (previous === null || previous === undefined) { + return; + } + + if (current !== previous) { + progress.value = 0; + progress.value = withTiming(1, { duration: 220, easing: Easing.out(Easing.quad) }); + } + }, + [value], + ); + + const animatedStyle = useAnimatedStyle(() => { + return { + opacity: interpolate(progress.value, [0, 1], [0.85, 1]), + transform: [ + { + scale: interpolate(progress.value, [0, 1], [0.98, 1]), + }, + ], + }; + }); + + return animatedStyle; +}; + +export default useAnimateOnChange; diff --git a/hooks/useAppState.ts b/hooks/useAppState.ts new file mode 100644 index 00000000000..566ebe6be27 --- /dev/null +++ b/hooks/useAppState.ts @@ -0,0 +1,24 @@ +import { useState, useEffect, useRef } from 'react'; +import { AppState, AppStateStatus } from 'react-native'; + +const useAppState = (): { currentAppState: AppStateStatus, previousAppState: AppStateStatus | null } => { + const [currentAppState, setCurrentAppState] = useState(AppState.currentState); + const previousAppState = useRef(null); + + useEffect(() => { + const handleAppStateChange = (nextAppState: AppStateStatus) => { + previousAppState.current = currentAppState; + setCurrentAppState(nextAppState); + }; + + const subscription = AppState.addEventListener('change', handleAppStateChange); + + return () => { + subscription.remove(); + }; + }, [currentAppState]); + + return { currentAppState, previousAppState: previousAppState.current }; +}; + +export default useAppState; \ No newline at end of file diff --git a/hooks/useAsyncPromise.ts b/hooks/useAsyncPromise.ts new file mode 100644 index 00000000000..4d3a4689028 --- /dev/null +++ b/hooks/useAsyncPromise.ts @@ -0,0 +1,40 @@ +import { useState, useEffect } from 'react'; + +/** + * A custom React hook that accepts a promise and returns the resolved value and any errors that occur. + * + * @template T - The type of the resolved value. + * @param {() => Promise} promiseFn - A function that returns the promise to be resolved. + * @returns {{ data: T | null, error: Error | null, loading: boolean }} - An object with the resolved data, any error, and loading state. + */ +function useAsyncPromise(promiseFn: () => Promise) { + const [data, setData] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let isMounted = true; + + promiseFn() + .then(result => { + if (isMounted) { + setData(result); + setLoading(false); + } + }) + .catch((err: Error) => { + if (isMounted) { + setError(err); + setLoading(false); + } + }); + + return () => { + isMounted = false; + }; + }, [promiseFn]); + + return { data, error, loading }; +} + +export default useAsyncPromise; diff --git a/hooks/useBiometrics.ts b/hooks/useBiometrics.ts new file mode 100644 index 00000000000..da563b9ecc1 --- /dev/null +++ b/hooks/useBiometrics.ts @@ -0,0 +1,195 @@ +import { useState, useEffect, useCallback } from 'react'; +import { Alert, Platform } from 'react-native'; +import ReactNativeBiometrics, { BiometryTypes as RNBiometryTypes } from 'react-native-biometrics'; +import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store'; +import loc from '../loc'; +import * as NavigationService from '../NavigationService'; +import presentAlert from '../components/Alert'; +import { useStorage } from './context/useStorage'; + +const STORAGEKEY = 'Biometrics'; +const rnBiometrics = new ReactNativeBiometrics({ allowDeviceCredentials: true }); + +const FaceID = 'Face ID'; +const TouchID = 'Touch ID'; +const Biometrics = 'Biometrics'; + +const clearKeychain = async () => { + try { + console.debug('Wiping keychain'); + console.debug('Wiping key: data'); + await RNSecureKeyStore.set('data', JSON.stringify({ data: { wallets: [] } }), { + accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY, + }); + console.debug('Wiped key: data'); + console.debug('Wiping key: data_encrypted'); + await RNSecureKeyStore.set('data_encrypted', '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY }); + console.debug('Wiped key: data_encrypted'); + console.debug('Wiping key: STORAGEKEY'); + await RNSecureKeyStore.set(STORAGEKEY, '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY }); + console.debug('Wiped key: STORAGEKEY'); + NavigationService.reset(); + } catch (error: any) { + console.warn(error); + presentAlert({ message: error.message }); + } +}; + +const unlockWithBiometrics = async () => { + try { + const { available } = await rnBiometrics.isSensorAvailable(); + if (!available) { + return false; + } + + return new Promise(resolve => { + rnBiometrics + .simplePrompt({ promptMessage: loc.settings.biom_conf_identity }) + .then((result: { success: any }) => { + if (result.success) { + resolve(true); + } else { + console.debug('Biometrics authentication failed'); + resolve(false); + } + }) + .catch((error: Error) => { + console.debug('Biometrics authentication error'); + presentAlert({ message: error.message }); + resolve(false); + }); + }); + } catch (e: Error | any) { + console.debug('Biometrics authentication error', e); + presentAlert({ message: e.message }); + return false; + } +}; + +const showKeychainWipeAlert = () => { + if (Platform.OS === 'ios') { + Alert.alert( + loc.settings.encrypt_tstorage, + loc.settings.biom_10times, + [ + { + text: loc._.cancel, + onPress: () => { + console.debug('Cancel Pressed'); + }, + style: 'cancel', + }, + { + text: loc._.ok, + onPress: async () => { + const { available } = await rnBiometrics.isSensorAvailable(); + if (!available) { + presentAlert({ message: loc.settings.biom_no_passcode }); + return; + } + const isAuthenticated = await unlockWithBiometrics(); + if (isAuthenticated) { + Alert.alert( + loc.settings.encrypt_tstorage, + loc.settings.biom_remove_decrypt, + [ + { text: loc._.cancel, style: 'cancel' }, + { + text: loc._.ok, + style: 'destructive', + onPress: async () => await clearKeychain(), + }, + ], + { cancelable: false }, + ); + } + }, + style: 'default', + }, + ], + { cancelable: false }, + ); + } +}; + +const useBiometrics = () => { + const { getItem, setItem } = useStorage(); + const [biometricEnabled, setBiometricEnabled] = useState(false); + const [deviceBiometricType, setDeviceBiometricType] = useState<'TouchID' | 'FaceID' | 'Biometrics' | undefined>(undefined); + + useEffect(() => { + const fetchBiometricEnabledStatus = async () => { + const enabled = await isBiometricUseEnabled(); + setBiometricEnabled(enabled); + + const biometricType = await type(); + setDeviceBiometricType(biometricType); + }; + + fetchBiometricEnabledStatus(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const isDeviceBiometricCapable = useCallback(async () => { + try { + const { available } = await rnBiometrics.isSensorAvailable(); + return available; + } catch (e) { + console.debug('Biometrics isDeviceBiometricCapable failed'); + console.debug(e); + setBiometricUseEnabled(false); + } + return false; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const type = useCallback(async () => { + try { + const { available, biometryType } = await rnBiometrics.isSensorAvailable(); + if (!available) { + return undefined; + } + + return biometryType; + } catch (e) { + console.debug('Biometrics biometricType failed'); + console.debug(e); + return undefined; + } + }, []); + + const isBiometricUseEnabled = useCallback(async () => { + try { + const enabledBiometrics = await getItem(STORAGEKEY); + return !!enabledBiometrics; + } catch (_) {} + + return false; + }, [getItem]); + + const isBiometricUseCapableAndEnabled = useCallback(async () => { + const isEnabled = await isBiometricUseEnabled(); + const isCapable = await isDeviceBiometricCapable(); + return isEnabled && isCapable; + }, [isBiometricUseEnabled, isDeviceBiometricCapable]); + + const setBiometricUseEnabled = useCallback( + async (value: boolean) => { + await setItem(STORAGEKEY, value === true ? '1' : ''); + setBiometricEnabled(value); + }, + [setItem], + ); + + return { + isDeviceBiometricCapable, + deviceBiometricType, + isBiometricUseEnabled, + isBiometricUseCapableAndEnabled, + setBiometricUseEnabled, + clearKeychain, + biometricEnabled, + }; +}; + +export { FaceID, TouchID, Biometrics, RNBiometryTypes as BiometricType, useBiometrics, showKeychainWipeAlert, unlockWithBiometrics }; diff --git a/hooks/useBounceAnimation.ts b/hooks/useBounceAnimation.ts new file mode 100644 index 00000000000..38efa49072a --- /dev/null +++ b/hooks/useBounceAnimation.ts @@ -0,0 +1,26 @@ +import { useEffect, useRef } from 'react'; +import { Animated } from 'react-native'; + +const useBounceAnimation = (query: string) => { + const bounceAnim = useRef(new Animated.Value(1.0)).current; + + useEffect(() => { + if (query) { + Animated.timing(bounceAnim, { + toValue: 1.08, // Reduced from 1.2 to 1.08 for more subtle animation + duration: 150, + useNativeDriver: true, + }).start(() => { + Animated.timing(bounceAnim, { + toValue: 1.0, + duration: 150, + useNativeDriver: true, + }).start(); + }); + } + }, [bounceAnim, query]); + + return bounceAnim; +}; + +export default useBounceAnimation; diff --git a/hooks/useCompanionListeners.ts b/hooks/useCompanionListeners.ts new file mode 100644 index 00000000000..19c11eb7e53 --- /dev/null +++ b/hooks/useCompanionListeners.ts @@ -0,0 +1,340 @@ +import { CommonActions } from '@react-navigation/native'; +import { useCallback, useEffect, useRef } from 'react'; +import { AppState, AppStateStatus, Linking } from 'react-native'; +import { getClipboardContent } from '../blue_modules/clipboard'; +import { updateExchangeRate } from '../blue_modules/currency'; +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; +import { + clearStoredNotifications, + getDeliveredNotifications, + getStoredNotifications, + initializeNotifications, + removeAllDeliveredNotifications, + setApplicationIconBadgeNumber, +} from '../blue_modules/notifications'; +import { LightningCustodianWallet } from '../class/wallets/lightning-custodian-wallet'; +import DeeplinkSchemaMatch from '../class/deeplink-schema-match'; +import loc from '../loc'; +import { Chain } from '../models/bitcoinUnits'; +import { navigationRef } from '../NavigationService'; +import ActionSheet from '../screen/ActionSheet'; +import { useStorage } from './context/useStorage'; +import { detectQRCodeInImage } from 'react-native-camera-kit-no-google'; +import RNFS from 'react-native-fs'; +import presentAlert from '../components/Alert'; +import useWidgetCommunication from './useWidgetCommunication'; +import useWatchConnectivity from './useWatchConnectivity'; +import useDeviceQuickActions from './useDeviceQuickActions'; +import useHandoffListener from './useHandoffListener'; +import useMenuElements from './useMenuElements'; +import { useExtendedNavigation } from './useExtendedNavigation'; + +const ClipboardContentType = Object.freeze({ + BITCOIN: 'BITCOIN', + LIGHTNING: 'LIGHTNING', +}); + +/** + * Hook that initializes all companion listeners and functionality without rendering a component + */ +const useCompanionListeners = (skipIfNotInitialized = true) => { + const { + wallets, + addWallet, + saveToDisk, + fetchAndSaveWalletTransactions, + refreshAllWalletTransactions, + setSharedCosigner, + walletsInitialized, + } = useStorage(); + const appState = useRef(AppState.currentState); + const clipboardContent = useRef(undefined); + const navigation = useExtendedNavigation(); + + // We need to call hooks unconditionally before any conditional logic + // We'll use this check inside the effects to conditionally run logic + const shouldActivateListeners = !skipIfNotInitialized || walletsInitialized; + + // Initialize other hooks regardless of activation status + // They'll handle their own conditional logic internally + useWatchConnectivity(); + useWidgetCommunication(); + useMenuElements(); + useDeviceQuickActions(); + useHandoffListener(); + + const processPushNotifications = useCallback(async () => { + if (!shouldActivateListeners) return false; + + await new Promise(resolve => setTimeout(resolve, 200)); + try { + const notifications2process = await getStoredNotifications(); + await clearStoredNotifications(); + setApplicationIconBadgeNumber(0); + + const deliveredNotifications = await getDeliveredNotifications(); + setTimeout(async () => { + try { + removeAllDeliveredNotifications(); + } catch (error) { + console.error('Failed to remove delivered notifications:', error); + } + }, 5000); + + // Process notifications + for (const payload of notifications2process) { + const wasTapped = payload.foreground === false || (payload.foreground === true && payload.userInteraction); + + console.log('processing push notification:', payload); + let wallet; + switch (+payload.type) { + case 2: + case 3: + wallet = wallets.find(w => w.weOwnAddress(payload.address)); + break; + case 1: + case 4: + wallet = wallets.find(w => w.weOwnTransaction(payload.txid || payload.hash)); + break; + } + + if (wallet) { + const walletID = wallet.getID(); + fetchAndSaveWalletTransactions(walletID); + if (wasTapped) { + if (payload.type !== 3 || wallet.chain === Chain.OFFCHAIN) { + navigation.navigate('WalletTransactions', { + walletID, + walletType: wallet.type, + }); + } else { + navigation.navigate('ReceiveDetails', { + walletID, + address: payload.address, + }); + } + + return true; + } + } else { + console.log('could not find wallet while processing push notification, NOP'); + } + } + + if (deliveredNotifications.length > 0) { + for (const payload of deliveredNotifications) { + const wasTapped = payload.foreground === false || (payload.foreground === true && payload.userInteraction); + + console.log('processing push notification:', payload); + let wallet; + switch (+payload.type) { + case 2: + case 3: + wallet = wallets.find(w => w.weOwnAddress(payload.address)); + break; + case 1: + case 4: + wallet = wallets.find(w => w.weOwnTransaction(payload.txid || payload.hash)); + break; + } + + if (wallet) { + const walletID = wallet.getID(); + fetchAndSaveWalletTransactions(walletID); + if (wasTapped) { + if (payload.type !== 3 || wallet.chain === Chain.OFFCHAIN) { + navigationRef.dispatch( + CommonActions.navigate({ + name: 'WalletTransactions', + params: { + walletID, + walletType: wallet.type, + }, + }), + ); + } else { + navigationRef.dispatch( + CommonActions.navigate({ + name: 'ReceiveDetails', + params: { + walletID, + address: payload.address, + }, + }), + ); + } + + return true; + } + } else { + console.log('could not find wallet while processing push notification, NOP'); + } + } + } + + if (deliveredNotifications.length > 0) { + refreshAllWalletTransactions(); + } + } catch (error) { + console.error('Failed to process push notifications:', error); + } + return false; + }, [shouldActivateListeners, wallets, fetchAndSaveWalletTransactions, navigation, refreshAllWalletTransactions]); + + useEffect(() => { + if (!shouldActivateListeners) return; + + initializeNotifications(processPushNotifications); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [shouldActivateListeners]); + + const handleOpenURL = useCallback( + async (event: { url: string }): Promise => { + if (!shouldActivateListeners) return; + + try { + if (!event.url) return; + let decodedUrl: string; + try { + decodedUrl = decodeURIComponent(event.url); + } catch (e) { + console.error('Failed to decode URL, using original', e); + decodedUrl = event.url; + } + const fileName = decodedUrl.split('/').pop()?.toLowerCase() || ''; + if (/\.(jpe?g|png)$/i.test(fileName)) { + let base64: string; + try { + base64 = await RNFS.readFile(decodedUrl, 'base64'); + } catch { + base64 = await RNFS.readFile(decodedUrl.replace(/^file:\/\//, ''), 'base64'); + } + const qrValue = await detectQRCodeInImage(base64); + if (!qrValue) { + 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, + }, + ); + } else { + DeeplinkSchemaMatch.navigationRouteFor(event, (value: [string, any]) => navigationRef.navigate(...value), { + wallets, + addWallet, + saveToDisk, + setSharedCosigner, + }); + } + } catch (err: any) { + console.error('Error in handleOpenURL:', err); + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); + presentAlert({ message: err.message || loc.send.qr_error_no_qrcode }); + } + }, + [wallets, addWallet, saveToDisk, setSharedCosigner, shouldActivateListeners], + ); + + const showClipboardAlert = useCallback( + ({ contentType }: { contentType: undefined | string }) => { + if (!shouldActivateListeners) return; + + triggerHapticFeedback(HapticFeedbackTypes.ImpactLight); + getClipboardContent().then(clipboard => { + if (!clipboard) return; + ActionSheet.showActionSheetWithOptions( + { + title: loc._.clipboard, + message: contentType === ClipboardContentType.BITCOIN ? loc.wallets.clipboard_bitcoin : loc.wallets.clipboard_lightning, + options: [loc._.cancel, loc._.continue], + cancelButtonIndex: 0, + }, + buttonIndex => { + switch (buttonIndex) { + case 0: + break; + case 1: + handleOpenURL({ url: clipboard }); + break; + } + }, + ); + }); + }, + [handleOpenURL, shouldActivateListeners], + ); + + const handleAppStateChange = useCallback( + async (nextAppState: AppStateStatus | undefined) => { + if (!shouldActivateListeners || wallets.length === 0) return; + + if ((appState.current.match(/inactive|background/) && nextAppState === 'active') || nextAppState === undefined) { + updateExchangeRate(); + const processed = await processPushNotifications(); + if (processed) return; + const clipboard = await getClipboardContent(); + if (!clipboard) return; + const isAddressFromStoredWallet = wallets.some(wallet => { + if (wallet.chain === Chain.ONCHAIN) { + return wallet.isAddressValid && wallet.isAddressValid(clipboard) && wallet.weOwnAddress(clipboard); + } else { + return (wallet as LightningCustodianWallet).isInvoiceGeneratedByWallet(clipboard) || wallet.weOwnAddress(clipboard); + } + }); + const isBitcoinAddress = DeeplinkSchemaMatch.isBitcoinAddress(clipboard); + const isLightningInvoice = DeeplinkSchemaMatch.isLightningInvoice(clipboard); + const isLNURL = DeeplinkSchemaMatch.isLnUrl(clipboard); + const isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(clipboard); + if ( + !isAddressFromStoredWallet && + clipboardContent.current !== clipboard && + (isBitcoinAddress || isLightningInvoice || isLNURL || isBothBitcoinAndLightning) + ) { + let contentType; + if (isBitcoinAddress) { + contentType = ClipboardContentType.BITCOIN; + } else if (isLightningInvoice || isLNURL) { + contentType = ClipboardContentType.LIGHTNING; + } else if (isBothBitcoinAndLightning) { + contentType = ClipboardContentType.BITCOIN; + } + showClipboardAlert({ contentType }); + } + clipboardContent.current = clipboard; + } + if (nextAppState) { + appState.current = nextAppState; + } + }, + [processPushNotifications, showClipboardAlert, wallets, shouldActivateListeners], + ); + + const addListeners = useCallback(() => { + if (!shouldActivateListeners) return { urlSubscription: null, appStateSubscription: null }; + + const urlSubscription = Linking.addEventListener('url', handleOpenURL); + const appStateSubscription = AppState.addEventListener('change', handleAppStateChange); + + return { + urlSubscription, + appStateSubscription, + }; + }, [handleOpenURL, handleAppStateChange, shouldActivateListeners]); + + useEffect(() => { + const subscriptions = addListeners(); + + return () => { + subscriptions.urlSubscription?.remove?.(); + subscriptions.appStateSubscription?.remove?.(); + }; + }, [addListeners]); +}; + +export default useCompanionListeners; diff --git a/hooks/useDebounce.ts b/hooks/useDebounce.ts new file mode 100644 index 00000000000..cfedcd4b709 --- /dev/null +++ b/hooks/useDebounce.ts @@ -0,0 +1,27 @@ +import { useState, useEffect, useMemo } from 'react'; +import debounce from '../blue_modules/debounce'; + +// Overload signatures +function useDebounce any>(callback: T, delay: number): T; +function useDebounce(value: T, delay: number): T; + +function useDebounce(value: T, delay: number): T { + const isFn = typeof value === 'function'; + + const debouncedFunction = useMemo(() => { + return isFn ? debounce(value as unknown as (...args: any[]) => any, delay) : null; + }, [isFn, value, delay]); + + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + if (!isFn) { + const handler = setTimeout(() => setDebouncedValue(value), delay); + return () => clearTimeout(handler); + } + }, [isFn, value, delay]); + + return isFn ? (debouncedFunction as unknown as T) : debouncedValue; +} + +export default useDebounce; diff --git a/hooks/useDeviceQuickActions.ts b/hooks/useDeviceQuickActions.ts new file mode 100644 index 00000000000..fd5d9bbb5db --- /dev/null +++ b/hooks/useDeviceQuickActions.ts @@ -0,0 +1,169 @@ +import { useEffect } from 'react'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { CommonActions } from '@react-navigation/native'; +import { DeviceEventEmitter, Linking, Platform } from 'react-native'; +import QuickActions, { ShortcutItem } from 'react-native-quick-actions'; +import DeeplinkSchemaMatch from '../class/deeplink-schema-match'; +import { TWallet } from '../class/wallets/types'; +import { formatBalance } from '../loc'; +import * as NavigationService from '../NavigationService'; +import { useSettings } from '../hooks/context/useSettings'; +import { useStorage } from '../hooks/context/useStorage'; + +const DeviceQuickActionsStorageKey = 'DeviceQuickActionsEnabled'; + +export async function setEnabled(enabled: boolean = true): Promise { + await AsyncStorage.setItem(DeviceQuickActionsStorageKey, JSON.stringify(enabled)); +} + +export async function getEnabled(): Promise { + try { + const isEnabled = await AsyncStorage.getItem(DeviceQuickActionsStorageKey); + if (isEnabled === null) { + await setEnabled(true); + return true; + } + return !!JSON.parse(isEnabled); + } catch { + return true; + } +} + +const useDeviceQuickActions = () => { + const { wallets, walletsInitialized, isStorageEncrypted, addWallet, saveToDisk, setSharedCosigner } = useStorage(); + const { preferredFiatCurrency, isQuickActionsEnabled } = useSettings(); + + useEffect(() => { + if (walletsInitialized) { + isStorageEncrypted() + .then(value => { + if (value) { + removeShortcuts(); + } else { + setQuickActions(); + } + }) + .catch(() => removeShortcuts()); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [wallets, walletsInitialized, preferredFiatCurrency, isStorageEncrypted]); + + useEffect(() => { + if (walletsInitialized) { + DeviceEventEmitter.addListener('quickActionShortcut', walletQuickActions); + popInitialShortcutAction() + .then(popInitialAction) + .catch(error => { + console.error('Failed to process initial quick action:', error); + }); + return () => DeviceEventEmitter.removeAllListeners('quickActionShortcut'); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [walletsInitialized]); + + useEffect(() => { + if (walletsInitialized) { + if (isQuickActionsEnabled) { + setQuickActions(); + } else { + removeShortcuts(); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isQuickActionsEnabled, walletsInitialized]); + + const popInitialShortcutAction = async (): Promise => { + const data = await QuickActions.popInitialAction(); + return data; + }; + + const popInitialAction = async (data: any): Promise => { + try { + if (data) { + const wallet = wallets.find(w => w.getID() === data.userInfo.url.split('wallet/')[1]); + if (wallet) { + NavigationService.dispatch( + CommonActions.navigate({ + name: 'WalletTransactions', + params: { + walletID: wallet.getID(), + walletType: wallet.type, + }, + }), + ); + } + } else { + const url = await Linking.getInitialURL(); + if (url && DeeplinkSchemaMatch.hasSchema(url)) { + handleOpenURL({ url }); + } + } + } catch (error) { + console.error('Failed to handle initial quick action/deeplink:', error); + } + }; + + const handleOpenURL = (event: { url: string }): void => { + DeeplinkSchemaMatch.navigationRouteFor(event, (value: [string, any]) => NavigationService.navigate(...value), { + wallets, + addWallet, + saveToDisk, + setSharedCosigner, + }); + }; + + const walletQuickActions = (data: any): void => { + const wallet = wallets.find(w => w.getID() === data.userInfo.url.split('wallet/')[1]); + if (wallet) { + NavigationService.dispatch( + CommonActions.navigate({ + name: 'WalletTransactions', + params: { + walletID: wallet.getID(), + walletType: wallet.type, + }, + }), + ); + } + }; + + const removeShortcuts = async (): Promise => { + if (Platform.OS === 'android') { + QuickActions.clearShortcutItems(); + } else { + // @ts-ignore: Fix later + QuickActions.setShortcutItems([{ type: 'EmptyWallets', title: '' }]); + } + }; + + const setQuickActions = async (): Promise => { + if (await getEnabled()) { + QuickActions.isSupported((error: null, _supported: any) => { + if (error === null) { + const shortcutItems: ShortcutItem[] = wallets.slice(0, 4).map((wallet, index) => ({ + type: 'Wallets', + title: wallet.getLabel(), + subtitle: + wallet.hideBalance || wallet.getBalance() <= 0 + ? '' + : formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true), + userInfo: { + url: `bluewallet://wallet/${wallet.getID()}`, + }, + icon: Platform.select({ + android: 'quickactions', + ios: index === 0 ? 'Favorite' : 'Bookmark', + }) || 'quickactions', + })); + QuickActions.setShortcutItems(shortcutItems); + } + }); + } else { + removeShortcuts(); + } + }; + + return { popInitialAction }; +} + +export default useDeviceQuickActions; diff --git a/hooks/useDeviceQuickActions.windows.ts b/hooks/useDeviceQuickActions.windows.ts new file mode 100644 index 00000000000..f87b43d1450 --- /dev/null +++ b/hooks/useDeviceQuickActions.windows.ts @@ -0,0 +1,17 @@ + +export const DeviceQuickActionsStorageKey = 'DeviceQuickActionsEnabled'; + +export const setEnabled = (): void => {}; + +export const getEnabled = async (): Promise => { + return false; +}; + +const useDeviceQuickActions = () => { + + const popInitialAction = (): void => {}; + return { popInitialAction }; +}; + + +export default useDeviceQuickActions; diff --git a/hooks/useExtendedNavigation.ts b/hooks/useExtendedNavigation.ts new file mode 100644 index 00000000000..34582b64e72 --- /dev/null +++ b/hooks/useExtendedNavigation.ts @@ -0,0 +1,177 @@ +import { useNavigation, NavigationProp, ParamListBase, CommonActions } from '@react-navigation/native'; +import { navigationRef } from '../NavigationService'; +import { presentWalletExportReminder } from '../helpers/presentWalletExportReminder'; +import { unlockWithBiometrics, useBiometrics } from './useBiometrics'; +import { useStorage } from './context/useStorage'; +import { requestCameraAuthorization } from '../helpers/scan-qr'; +import { useCallback, useMemo } from 'react'; + +// List of screens that require biometrics +const requiresBiometrics = ['WalletExport', 'WalletXpub', 'ViewEditMultisigCosigners', 'ExportMultisigCoordinationSetupRoot']; + +// List of screens that require wallet export to be saved +const requiresWalletExportIsSaved = ['ReceiveDetails', 'WalletAddresses']; + +export const useExtendedNavigation = >(): T & { + navigateToWalletsList: () => void; +} => { + const originalNavigation = useNavigation(); + const { wallets, saveToDisk } = useStorage(); + const { isBiometricUseEnabled } = useBiometrics(); + + const enhancedNavigate = useCallback( + ( + ...args: + | [string] + | [string, object | undefined] + | [string, object | undefined, { merge?: boolean }] + | [{ name: string; params?: object; path?: string; merge?: boolean }] + ) => { + let screenOrOptions: any; + let params: any; + let options: { merge?: boolean } | undefined; + + if (typeof args[0] === 'string') { + screenOrOptions = args[0]; + params = args[1]; + options = args[2]; + } else { + screenOrOptions = args[0]; + } + let screenName: string; + if (typeof screenOrOptions === 'string') { + screenName = screenOrOptions; + } else if (typeof screenOrOptions === 'object' && 'name' in screenOrOptions) { + screenName = screenOrOptions.name; + params = screenOrOptions.params; // Assign params from object if present + } else { + throw new Error('Invalid navigation options'); + } + + const isRequiresBiometrics = requiresBiometrics.includes(screenName); + const isRequiresWalletExportIsSaved = requiresWalletExportIsSaved.includes(screenName); + + const proceedWithNavigation = () => { + console.log('Proceeding with navigation to', screenName); + + // Navigation logic based on current route and target screen + if (navigationRef.current?.isReady()) { + // Get the current route - we need to know which navigator we're in + const currentRoute = navigationRef.current.getCurrentRoute(); + const currentRouteName = currentRoute?.name; + + // Handle specific cases for nested navigation + if (currentRouteName === 'DrawerRoot') { + // If we're in DrawerRoot and trying to navigate to a screen that exists in DetailViewStackScreensStack + originalNavigation.navigate('DrawerRoot', { + screen: 'DetailViewStackScreensStack', + params: { + screen: screenName, + params, + }, + }); + } else { + // Normal navigation + if (typeof screenOrOptions === 'string') { + originalNavigation.navigate({ name: screenOrOptions, params, merge: options?.merge }); + } else { + originalNavigation.navigate({ ...screenOrOptions, params, merge: options?.merge }); + } + } + } + }; + + (async () => { + // Skip checks for ScanQRCode screen + const currentRouteName = navigationRef.current?.getCurrentRoute()?.name; + if (currentRouteName === 'ScanQRCode') { + proceedWithNavigation(); + return; + } + + if (isRequiresBiometrics) { + const isBiometricsEnabled = await isBiometricUseEnabled(); + if (isBiometricsEnabled) { + const isAuthenticated = await unlockWithBiometrics(); + if (isAuthenticated) { + proceedWithNavigation(); + return; + } else { + console.error('Biometric authentication failed'); + return; + } + } + } + if (isRequiresWalletExportIsSaved) { + console.log('Checking if wallet export is saved'); + let walletID: string | undefined; + if (params && params.walletID) { + walletID = params.walletID; + } else if (params && params.params && params.params.walletID) { + walletID = params.params.walletID; + } + if (!walletID) { + proceedWithNavigation(); + return; + } + const wallet = wallets.find(w => w.getID() === walletID); + if (wallet && !wallet.getUserHasSavedExport()) { + try { + await presentWalletExportReminder(); + wallet.setUserHasSavedExport(true); + await saveToDisk(); + proceedWithNavigation(); + } catch (error) { + originalNavigation.navigate('WalletExport', { walletID }); + } + return; + } + } + + if (screenName === 'ScanQRCode') { + await requestCameraAuthorization(); + } + proceedWithNavigation(); + })(); + }, + [originalNavigation, isBiometricUseEnabled, wallets, saveToDisk], + ); + + const navigateToWalletsList = useCallback(() => { + if (navigationRef.isReady()) { + navigationRef.dispatch( + CommonActions.reset({ + index: 0, + routes: [ + { + name: 'DrawerRoot', + state: { + routes: [ + { + name: 'DetailViewStackScreensStack', + state: { + routes: [ + { + name: 'WalletsList', + }, + ], + }, + }, + ], + }, + }, + ], + }) + ); + } +}, []); + + return useMemo( + () => ({ + ...originalNavigation, + navigate: enhancedNavigate, + navigateToWalletsList, + }), + [originalNavigation, enhancedNavigate, navigateToWalletsList], + ) as T & { navigateToWalletsList: () => void }; +}; \ No newline at end of file diff --git a/hooks/useHandoffListener.ios.ts b/hooks/useHandoffListener.ios.ts new file mode 100644 index 00000000000..53c37f46ba8 --- /dev/null +++ b/hooks/useHandoffListener.ios.ts @@ -0,0 +1,68 @@ +import { useEffect, useCallback } from 'react'; +import { NativeEventEmitter } from 'react-native'; +import EventEmitterModule from '../blue_modules/NativeEventEmitter'; +import { useStorage } from '../hooks/context/useStorage'; +import { useExtendedNavigation } from '../hooks/useExtendedNavigation'; +import { HandOffActivityType } from '../components/types'; +import { useSettings } from './context/useSettings'; + +interface UserActivityData { + activityType: HandOffActivityType; + userInfo: { + address?: string; + xpub?: string; + }; +} + +const eventEmitter = EventEmitterModule ? new NativeEventEmitter(EventEmitterModule as any) : null; + +const useHandoffListener = () => { + const { walletsInitialized } = useStorage(); + const { isHandOffUseEnabled } = useSettings(); + const { navigate } = useExtendedNavigation(); + + const handleUserActivity = useCallback( + (data: UserActivityData) => { + if (!data || !data.activityType) { + console.debug(`Invalid handoff data received: ${data ? JSON.stringify(data) : 'No data provided'}`); + return; + } + const { activityType, userInfo } = data; + const modifiedUserInfo = { ...(userInfo || {}), type: activityType }; + try { + if (activityType === HandOffActivityType.ReceiveOnchain && modifiedUserInfo.address) { + navigate( 'ReceiveDetails', { address: modifiedUserInfo.address, type: activityType }, + ); + } else if (activityType === HandOffActivityType.Xpub && modifiedUserInfo.xpub) { + navigate('WalletXpub', { xpub: modifiedUserInfo.xpub, type: activityType }); + } else { + console.debug(`Unhandled or incomplete activity type/data: ${activityType}`, modifiedUserInfo); + } + } catch (error) { + console.error('Error handling user activity:', error); + } + }, + [navigate], + ); + + useEffect(() => { + if (!walletsInitialized || !isHandOffUseEnabled) return; + + const activitySubscription = eventEmitter?.addListener('onUserActivityOpen', handleUserActivity); + + if (EventEmitterModule && (EventEmitterModule as any).getMostRecentUserActivity) { + (EventEmitterModule as any) + .getMostRecentUserActivity() + .then(handleUserActivity) + .catch(() => console.debug('No valid user activity object received')); + } else { + console.debug('EventEmitter native module is not available.'); + } + + return () => { + activitySubscription?.remove(); + }; + }, [walletsInitialized, isHandOffUseEnabled, handleUserActivity]); +}; + +export default useHandoffListener; diff --git a/hooks/useHandoffListener.ts b/hooks/useHandoffListener.ts new file mode 100644 index 00000000000..f0666c853c1 --- /dev/null +++ b/hooks/useHandoffListener.ts @@ -0,0 +1,3 @@ +const useHandoffListener = () => {}; + +export default useHandoffListener; diff --git a/hooks/useKeyboard.ts b/hooks/useKeyboard.ts new file mode 100644 index 00000000000..1baf9f42f84 --- /dev/null +++ b/hooks/useKeyboard.ts @@ -0,0 +1,54 @@ +import { useState, useEffect } from 'react'; +import { Keyboard, KeyboardEvent, Platform } from 'react-native'; + +interface KeyboardInfo { + isVisible: boolean; + height: number; +} + +interface UseKeyboardProps { + onKeyboardDidShow?: () => void; + onKeyboardDidHide?: () => void; +} + +export const useKeyboard = ({ onKeyboardDidShow, onKeyboardDidHide }: UseKeyboardProps = {}): KeyboardInfo => { + const [keyboardInfo, setKeyboardInfo] = useState({ + isVisible: false, + height: 0, + }); + + useEffect(() => { + const handleKeyboardDidShow = (event: KeyboardEvent) => { + setKeyboardInfo({ + isVisible: true, + height: event.endCoordinates.height, + }); + if (onKeyboardDidShow) { + onKeyboardDidShow(); + } + }; + + const handleKeyboardDidHide = () => { + setKeyboardInfo({ + isVisible: false, + height: 0, + }); + if (onKeyboardDidHide) { + onKeyboardDidHide(); + } + }; + + const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'; + const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide'; + + const showSubscription = Keyboard.addListener(showEvent, handleKeyboardDidShow); + const hideSubscription = Keyboard.addListener(hideEvent, handleKeyboardDidHide); + + return () => { + showSubscription.remove(); + hideSubscription.remove(); + }; + }, [onKeyboardDidShow, onKeyboardDidHide]); + + return keyboardInfo; +}; diff --git a/hooks/useMenuElements.ios.ts b/hooks/useMenuElements.ios.ts new file mode 100644 index 00000000000..d9c923f83c7 --- /dev/null +++ b/hooks/useMenuElements.ios.ts @@ -0,0 +1,93 @@ +import { useEffect, useCallback, useRef } from 'react'; +import { NativeEventEmitter, Platform } from 'react-native'; +import MenuElementsEmitter from '../blue_modules/NativeMenuElementsEmitter'; +import { navigationRef } from '../NavigationService'; + +type MenuActionHandler = () => void; + +let eventEmitter: NativeEventEmitter | null = null; +const handlerRegistry = new Map(); + +try { + if (Platform.OS === 'ios' && MenuElementsEmitter) { + eventEmitter = new NativeEventEmitter(MenuElementsEmitter as any); + if (typeof (MenuElementsEmitter as any).sharedInstance === 'function') { + (MenuElementsEmitter as any).sharedInstance(); + } + } +} catch (error) { + console.warn('Failed to initialize menu emitter:', error); + eventEmitter = null; +} + +interface MenuElementsHook { + registerTransactionsHandler: (handler: MenuActionHandler, screenKey?: string) => boolean; + unregisterTransactionsHandler: (screenKey: string) => void; + isMenuElementsSupported: boolean; +} + +const useMenuElements = (): MenuElementsHook => { + const initialized = useRef(false); + + useEffect(() => { + if (!initialized.current && eventEmitter) { + initialized.current = true; + + eventEmitter.addListener('openSettings', () => { + if (navigationRef.isReady()) { + navigationRef.navigate('Settings'); + } + }); + + eventEmitter.addListener('addWalletMenuAction', () => { + if (navigationRef.isReady()) { + navigationRef.navigate('AddWalletRoot'); + } + }); + + eventEmitter.addListener('importWalletMenuAction', () => { + if (navigationRef.isReady()) { + navigationRef.navigate('AddWalletRoot', { screen: 'ImportWallet' }); + } + }); + + eventEmitter.addListener('reloadTransactionsMenuAction', () => { + if (!navigationRef.isReady()) return; + + const currentRoute = navigationRef.getCurrentRoute(); + if (!currentRoute) return; + + const screenName = currentRoute.name; + const params = (currentRoute.params as { walletID?: string }) || {}; + const walletID = params.walletID; + const specificKey = walletID ? `${screenName}-${walletID}` : null; + + const handler = (specificKey ? handlerRegistry.get(specificKey) : undefined) || handlerRegistry.get(screenName); + + if (typeof handler === 'function') { + handler(); + } + }); + } + }, []); + + const registerTransactionsHandler = useCallback((handler: MenuActionHandler, screenKey?: string): boolean => { + if (typeof handler !== 'function') return false; + const key = screenKey || navigationRef.current?.getCurrentRoute()?.name; + if (!key) return false; + handlerRegistry.set(key, handler); + return true; + }, []); + + const unregisterTransactionsHandler = useCallback((screenKey: string): void => { + if (screenKey) handlerRegistry.delete(screenKey); + }, []); + + return { + registerTransactionsHandler, + unregisterTransactionsHandler, + isMenuElementsSupported: !!eventEmitter, + }; +}; + +export default useMenuElements; diff --git a/hooks/useMenuElements.ts b/hooks/useMenuElements.ts new file mode 100644 index 00000000000..863fc4125ff --- /dev/null +++ b/hooks/useMenuElements.ts @@ -0,0 +1,29 @@ +import { useCallback } from 'react'; + +type MenuActionHandler = () => void; + +interface MenuElementsHook { + registerTransactionsHandler: (handler: MenuActionHandler, screenKey?: string) => boolean; + unregisterTransactionsHandler: (screenKey: string) => void; + isMenuElementsSupported: boolean; +} + +// Default implementation for platforms other than iOS +const useMenuElements = (): MenuElementsHook => { + const registerTransactionsHandler = useCallback((_handler: MenuActionHandler, _screenKey?: string): boolean => { + // Non-functional stub for non-iOS platforms + return false; + }, []); + + const unregisterTransactionsHandler = useCallback((_screenKey: string): void => { + // No-op for non-supported platforms + }, []); + + return { + registerTransactionsHandler, + unregisterTransactionsHandler, + isMenuElementsSupported: false, // Not supported on platforms other than iOS + }; +}; + +export default useMenuElements; diff --git a/hooks/useOnAppLaunch.ts b/hooks/useOnAppLaunch.ts new file mode 100644 index 00000000000..44e13b06f07 --- /dev/null +++ b/hooks/useOnAppLaunch.ts @@ -0,0 +1,69 @@ +import { useCallback } from 'react'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { TWallet } from '../class/wallets/types'; +import { useStorage } from './context/useStorage'; + +const useOnAppLaunch = () => { + const STORAGE_KEY = 'ONAPP_LAUNCH_SELECTED_DEFAULT_WALLET_KEY'; + const { wallets } = useStorage(); + + const getSelectedDefaultWallet = useCallback(async (): Promise => { + let selectedWallet: TWallet | undefined; + try { + const selectedWalletID = await AsyncStorage.getItem(STORAGE_KEY); + console.log('Selected wallet ID:', selectedWalletID); + if (selectedWalletID !== null) { + selectedWallet = wallets.find((wallet: TWallet) => wallet.getID() === selectedWalletID); + if (!selectedWallet) { + await AsyncStorage.removeItem(STORAGE_KEY); + return undefined; + } + } else { + return undefined; + } + } catch (_e) { + return undefined; + } + return selectedWallet.getID(); + }, [STORAGE_KEY, wallets]); + + const setSelectedDefaultWallet = useCallback( + async (value: string): Promise => { + await AsyncStorage.setItem(STORAGE_KEY, value); + }, + [STORAGE_KEY], + ); // No external dependencies + + const isViewAllWalletsEnabled = useCallback(async (): Promise => { + try { + const selectedDefaultWallet = await AsyncStorage.getItem(STORAGE_KEY); + return selectedDefaultWallet === '' || selectedDefaultWallet === null; + } catch (_e) { + return true; + } + }, [STORAGE_KEY]); // No external dependencies + + const setViewAllWalletsEnabled = useCallback( + async (value: boolean): Promise => { + if (!value) { + const selectedDefaultWallet = await getSelectedDefaultWallet(); + if (!selectedDefaultWallet) { + const firstWallet = wallets[0]; + await setSelectedDefaultWallet(firstWallet.getID()); + } + } else { + await AsyncStorage.setItem(STORAGE_KEY, ''); + } + }, + [STORAGE_KEY, getSelectedDefaultWallet, setSelectedDefaultWallet, wallets], + ); + + return { + isViewAllWalletsEnabled, + setViewAllWalletsEnabled, + getSelectedDefaultWallet, + setSelectedDefaultWallet, + }; +}; + +export default useOnAppLaunch; diff --git a/hooks/useScreenProtect.ts b/hooks/useScreenProtect.ts new file mode 100644 index 00000000000..e0a7411798d --- /dev/null +++ b/hooks/useScreenProtect.ts @@ -0,0 +1,26 @@ +import { CaptureProtection } from 'react-native-capture-protection'; +import { isDesktop } from '../blue_modules/environment'; +import { useCallback } from 'react'; + +export const useScreenProtect = () => { + const enableScreenProtect = useCallback(async () => { + if (isDesktop) return; + await CaptureProtection.prevent(); + }, []); + + const disableScreenProtect = useCallback(async () => { + if (isDesktop) return; + await CaptureProtection.allow(); + }, []); + + const isScreenBeingRecorded = useCallback(async () => { + if (isDesktop) return false; + return await CaptureProtection.isScreenRecording(); + }, []); + + return { + enableScreenProtect, + disableScreenProtect, + isScreenBeingRecorded, + }; +}; diff --git a/hooks/useSizeClass.ts b/hooks/useSizeClass.ts new file mode 100644 index 00000000000..5e472560327 --- /dev/null +++ b/hooks/useSizeClass.ts @@ -0,0 +1,9 @@ +import { useSizeClass as useSizeClassOriginal, SizeClass } from '../blue_modules/sizeClass'; +import type { SizeClassInfo } from '../blue_modules/sizeClass'; + +export { SizeClass }; +export type { SizeClassInfo }; + +export const useSizeClass = useSizeClassOriginal; + +export const useIsLargeScreen = useSizeClassOriginal; diff --git a/hooks/useWalletSubscribe.tsx b/hooks/useWalletSubscribe.tsx new file mode 100644 index 00000000000..98e5d2c7830 --- /dev/null +++ b/hooks/useWalletSubscribe.tsx @@ -0,0 +1,39 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; +import { useStorage } from './context/useStorage'; +import { TWallet } from '../class/wallets/types'; + +/** + * A React hook that provides a proxied wallet instance that automatically updates when new transactions are fetched. + */ +const useWalletSubscribe = (walletID: string): TWallet => { + const { wallets } = useStorage(); + + // get wallet by ID or used cached wallet + const previousWallet = useRef(undefined); + const origWallet = wallets.find(w => w.getID() === walletID) ?? previousWallet.current; + if (!origWallet) { + throw new Error(`Wallet with ID ${walletID} not found`); + } + previousWallet.current = origWallet; + + const [lastTxFetch, setLastTxFetch] = useState(origWallet.getLastTxFetch()); + + const walletProxy = useMemo(() => { + return new Proxy(origWallet, {}); + // force update when lastTxFetch changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [lastTxFetch, origWallet]); + + // check every second for getLastTxFetch + useEffect(() => { + const interval = setInterval(() => { + setLastTxFetch(origWallet.getLastTxFetch()); + }, 1000); + + return () => clearInterval(interval); + }, [origWallet]); + + return walletProxy; +}; + +export default useWalletSubscribe; diff --git a/hooks/useWatchConnectivity.ios.ts b/hooks/useWatchConnectivity.ios.ts new file mode 100644 index 00000000000..1b9bfa2ace0 --- /dev/null +++ b/hooks/useWatchConnectivity.ios.ts @@ -0,0 +1,280 @@ +import { useCallback, useEffect, useRef } from 'react'; +import { + transferCurrentComplicationUserInfo, + updateApplicationContext, + useInstalled, + usePaired, + useReachability, + watchEvents, +} from 'react-native-watch-connectivity'; +import { MultisigHDWallet } from '../class/wallets/multisig-hd-wallet'; +import loc from '../loc'; +import { Chain } from '../models/bitcoinUnits'; +import { FiatUnit } from '../models/fiatUnit'; +import { useSettings } from '../hooks/context/useSettings'; +import { useStorage } from '../hooks/context/useStorage'; +import { isNotificationsEnabled, majorTomToGroundControl } from '../blue_modules/notifications'; +import { LightningTransaction, Transaction } from '../class/wallets/types'; + +interface Message { + request?: string; + message?: string; + walletIndex?: number; + amount?: number; + description?: string; + hideBalance?: boolean; +} + +interface Reply { + (response: Record): void; +} + +interface LightningInvoiceCreateRequest { + walletIndex: number; + amount: number; + description?: string; +} + +export function useWatchConnectivity() { + const { walletsInitialized, wallets, fetchWalletTransactions, saveToDisk, txMetadata } = useStorage(); + const { preferredFiatCurrency } = useSettings(); + const isReachable = useReachability(); + const isInstalled = useInstalled(); + const isPaired = usePaired(); + + const messagesListenerActive = useRef(false); + const lastPreferredCurrency = useRef(FiatUnit.USD.endPointKey); + + const createContextPayload = () => ({ + randomID: `${Date.now()}${Math.floor(Math.random() * 1000)}`, + }); + + useEffect(() => { + if (!isInstalled || !isPaired || !walletsInitialized || !isReachable) return; + + const contextPayload = createContextPayload(); + try { + updateApplicationContext(contextPayload); + console.debug('Transferred user info:', contextPayload); + } catch (error) { + console.error('Failed to transfer user info:', error); + } + }, [isReachable, walletsInitialized, isInstalled, isPaired]); + + useEffect(() => { + if (!isInstalled || !isPaired || !walletsInitialized || !isReachable || !preferredFiatCurrency) return; + + if (lastPreferredCurrency.current !== preferredFiatCurrency.endPointKey) { + try { + const currencyPayload = { preferredFiatCurrency: preferredFiatCurrency.endPointKey }; + transferCurrentComplicationUserInfo(currencyPayload); + lastPreferredCurrency.current = preferredFiatCurrency.endPointKey; + console.debug('Apple Watch: updated preferred fiat currency', currencyPayload); + } catch (error) { + console.error('Error updating preferredFiatCurrency on watch:', error); + } + } else { + console.debug('WatchConnectivity: preferred currency has not changed'); + } + }, [preferredFiatCurrency, walletsInitialized, isReachable, isInstalled, isPaired]); + + const handleLightningInvoiceCreateRequest = useCallback( + async ({ walletIndex, amount, description = loc.lnd.placeholder }: LightningInvoiceCreateRequest): Promise => { + const wallet = wallets[walletIndex]; + if (wallet.allowReceive() && amount > 0) { + try { + if ('addInvoice' in wallet) { + const invoiceRequest = await wallet.addInvoice(amount, description); + if (await isNotificationsEnabled()) { + const decoded = await wallet.decodeInvoice(invoiceRequest); + majorTomToGroundControl([], [decoded.payment_hash], []); + return invoiceRequest; + } + console.debug('Created Lightning invoice:', { invoiceRequest }); + return invoiceRequest; + } + } catch (invoiceError) { + console.error('Error creating invoice:', invoiceError); + } + } + }, + [wallets], + ); + + const constructWalletsToSendToWatch = useCallback(async () => { + if (!Array.isArray(wallets) || !walletsInitialized) return; + + const walletsToProcess = await Promise.allSettled( + wallets.map(async wallet => { + try { + const receiveAddress = wallet.chain === Chain.ONCHAIN ? await wallet.getAddressAsync() : wallet.getAddress(); + const transactions: Partial[] = wallet + .getTransactions() + .slice(0, 10) + .map((transaction: Transaction & LightningTransaction) => ({ + type: determineTransactionType(transaction), + amount: transaction.value ?? 0, + memo: + 'hash' in (transaction as Transaction) + ? txMetadata[(transaction as Transaction).hash]?.memo || transaction.memo || '' + : transaction.memo || '', + time: transaction.timestamp ?? transaction.time, + })); + + const walletData = { + label: wallet.getLabel(), + balance: Number(wallet.getBalance()), + type: wallet.type, + preferredBalanceUnit: wallet.getPreferredBalanceUnit(), + receiveAddress, + transactions, + chain: wallet.chain, + hideBalance: wallet.hideBalance ? 1 : 0, + ...(wallet.chain === Chain.ONCHAIN && + wallet.type !== MultisigHDWallet.type && { + xpub: wallet.getXpub() || wallet.getSecret(), + }), + ...(wallet.allowBIP47() && + wallet.isBIP47Enabled() && + 'getBIP47PaymentCode' in wallet && { paymentCode: wallet.getBIP47PaymentCode() }), + }; + + console.debug('Constructed wallet data for watch:', { + label: walletData.label, + type: walletData.type, + preferredBalanceUnit: walletData.preferredBalanceUnit, + transactionCount: transactions.length, + }); + return walletData; + } catch (error) { + console.error('Failed to construct wallet data:', error); + return null; + } + }), + ); + + const processedWallets = walletsToProcess + .filter(result => result.status === 'fulfilled' && result.value !== null) + .map(result => (result as PromiseFulfilledResult).value); + + console.debug('Constructed wallets to process for Apple Watch:', { + walletCount: processedWallets.length, + walletLabels: processedWallets.map(wallet => wallet.label), + }); + return { wallets: processedWallets, randomID: `${Date.now()}${Math.floor(Math.random() * 1000)}` }; + }, [wallets, walletsInitialized, txMetadata]); + + const determineTransactionType = (transaction: Transaction & LightningTransaction): string => { + const confirmations = (transaction as Transaction).confirmations ?? 0; + if (confirmations < 3) { + return 'pending_transaction'; + } + + if (transaction.type === 'bitcoind_tx') { + return 'onchain'; + } + + if (transaction.type === 'paid_invoice') { + return 'offchain'; + } + + if (transaction.type === 'user_invoice' || transaction.type === 'payment_request') { + const currentDate = new Date(); + const now = Math.floor(currentDate.getTime() / 1000); + const timestamp = transaction.timestamp ?? 0; + const expireTime = transaction.expire_time ?? 0; + const invoiceExpiration = timestamp + expireTime; + if (!transaction.ispaid && invoiceExpiration < now) { + return 'expired_transaction'; + } else { + return 'incoming_transaction'; + } + } + + if ((transaction.value ?? 0) < 0) { + return 'outgoing_transaction'; + } else { + return 'incoming_transaction'; + } + }; + + const handleMessages = useCallback( + async (message: Message, reply: Reply) => { + console.debug('Received message from Apple Watch:', message); + try { + if (message.request === 'createInvoice' && typeof message.walletIndex === 'number' && typeof message.amount === 'number') { + const createInvoiceRequest = await handleLightningInvoiceCreateRequest({ + walletIndex: message.walletIndex, + amount: message.amount, + description: message.description, + }); + reply({ invoicePaymentRequest: createInvoiceRequest }); + } else if (message.message === 'sendApplicationContext') { + const walletsToProcess = await constructWalletsToSendToWatch(); + if (walletsToProcess) { + updateApplicationContext(walletsToProcess); + console.debug('Transferred user info on request:', walletsToProcess); + } + } else if (message.message === 'fetchTransactions') { + await fetchWalletTransactions(); + await saveToDisk(); + reply({}); + } else if ( + message.message === 'hideBalance' && + typeof message.walletIndex === 'number' && + typeof message.hideBalance === 'boolean' && + message.walletIndex >= 0 && + message.walletIndex < wallets.length + ) { + wallets[message.walletIndex].hideBalance = message.hideBalance; + await saveToDisk(); + reply({}); + } + } catch (error) { + console.error('Error handling message:', error); + reply({}); + } + }, + [fetchWalletTransactions, saveToDisk, wallets, constructWalletsToSendToWatch, handleLightningInvoiceCreateRequest], + ); + + useEffect(() => { + if (!isInstalled || !isPaired || !walletsInitialized) return; + + const sendWalletData = async () => { + try { + const walletsToProcess = await constructWalletsToSendToWatch(); + if (walletsToProcess) { + updateApplicationContext(walletsToProcess); + console.debug('Apple Watch: sent wallet data via transferUserInfo', walletsToProcess); + } + } catch (error) { + console.error('Failed to send wallets to watch:', error); + } + }; + sendWalletData(); + }, [walletsInitialized, isInstalled, isPaired, constructWalletsToSendToWatch]); + + useEffect(() => { + if (!isInstalled) return; + + const unsubscribe = watchEvents.addListener('message', (message: any) => { + if (message.request === 'wakeUpApp') { + console.debug('Received wake-up request from Apple Watch'); + } else { + handleMessages(message, () => {}); + } + }); + + messagesListenerActive.current = true; + console.debug('Message listener set up for Apple Watch'); + + return () => { + unsubscribe(); + messagesListenerActive.current = false; + console.debug('Message listener for Apple Watch cleaned up'); + }; + }, [isInstalled, handleMessages]); +} + +export default useWatchConnectivity; \ No newline at end of file diff --git a/hooks/useWatchConnectivity.ts b/hooks/useWatchConnectivity.ts new file mode 100644 index 00000000000..b1dad68ef52 --- /dev/null +++ b/hooks/useWatchConnectivity.ts @@ -0,0 +1,2 @@ +const useWatchConnectivity = () => {}; +export default useWatchConnectivity; diff --git a/hooks/useWidgetCommunication.ios.ts b/hooks/useWidgetCommunication.ios.ts new file mode 100644 index 00000000000..6987817ad85 --- /dev/null +++ b/hooks/useWidgetCommunication.ios.ts @@ -0,0 +1,175 @@ +import { useEffect, useRef } from 'react'; +import DefaultPreference from 'react-native-default-preference'; +import { Transaction, TWallet } from '../class/wallets/types'; +import { useSettings } from '../hooks/context/useSettings'; +import { useStorage } from '../hooks/context/useStorage'; +import { GROUP_IO_BLUEWALLET } from '../blue_modules/currency'; +import debounce from '../blue_modules/debounce'; + +enum WidgetCommunicationKeys { + AllWalletsSatoshiBalance = 'WidgetCommunicationAllWalletsSatoshiBalance', + AllWalletsLatestTransactionTime = 'WidgetCommunicationAllWalletsLatestTransactionTime', + DisplayBalanceAllowed = 'WidgetCommunicationDisplayBalanceAllowed', + LatestTransactionIsUnconfirmed = 'WidgetCommunicationLatestTransactionIsUnconfirmed', +} + +const WIDGET_ENABLED = '1'; +const WIDGET_DISABLED = '0'; +const WIDGET_CLEARED_VALUE = '0'; + +const secondsToMilliseconds = (seconds: number): number => seconds * 1000; + +DefaultPreference.setName(GROUP_IO_BLUEWALLET); + +export const isBalanceDisplayAllowed = async (): Promise => { + try { + const displayBalance = await DefaultPreference.get(WidgetCommunicationKeys.DisplayBalanceAllowed); + if (displayBalance === WIDGET_ENABLED) { + return true; + } else if (displayBalance === WIDGET_DISABLED) { + return false; + } else { + // Preference not set, initialize to enabled by default + await DefaultPreference.set(WidgetCommunicationKeys.DisplayBalanceAllowed, WIDGET_ENABLED); + return true; + } + } catch (error) { + console.error('Failed to get DisplayBalanceAllowed:', error); + return true; + } +}; + +export const setBalanceDisplayAllowed = async (allowed: boolean): Promise => { + try { + if (allowed) { + await DefaultPreference.set(WidgetCommunicationKeys.DisplayBalanceAllowed, WIDGET_ENABLED); + } else { + await DefaultPreference.set(WidgetCommunicationKeys.DisplayBalanceAllowed, WIDGET_DISABLED); + // Clear widget data immediately when disabling + await Promise.all([ + DefaultPreference.set(WidgetCommunicationKeys.AllWalletsSatoshiBalance, WIDGET_CLEARED_VALUE), + DefaultPreference.set(WidgetCommunicationKeys.AllWalletsLatestTransactionTime, WIDGET_CLEARED_VALUE), + ]); + } + console.debug('setBalanceDisplayAllowed:', allowed); + } catch (error) { + console.error('Failed to set DisplayBalanceAllowed:', error); + } +}; + +export const calculateBalanceAndTransactionTime = async ( + wallets: TWallet[], + walletsInitialized: boolean, +): Promise<{ + allWalletsBalance: number; + latestTransactionTime: number | string; +}> => { + if (!walletsInitialized || !(await isBalanceDisplayAllowed())) { + return { allWalletsBalance: 0, latestTransactionTime: 0 }; + } + + const results = await Promise.allSettled( + wallets.map(async wallet => { + if (wallet.hideBalance) return { balance: 0, latestTransactionTime: 0 }; + + const balance = await wallet.getBalance(); + const transactions: Transaction[] = await wallet.getTransactions(); + const confirmedTransactions = transactions.filter(t => t.confirmations > 0); + const latestTransactionTime = + confirmedTransactions.length > 0 + ? secondsToMilliseconds(Math.max(...confirmedTransactions.map(t => t.timestamp || t.time || 0))) + : WidgetCommunicationKeys.LatestTransactionIsUnconfirmed; + + return { balance, latestTransactionTime }; + }), + ); + + const allWalletsBalance = results.reduce((acc, result) => acc + (result.status === 'fulfilled' ? result.value.balance : 0), 0); + const latestTransactionTime = results.reduce( + (max, result) => + result.status === 'fulfilled' && typeof result.value.latestTransactionTime === 'number' && result.value.latestTransactionTime > max + ? result.value.latestTransactionTime + : max, + 0, + ); + + return { allWalletsBalance, latestTransactionTime }; +}; + +export const syncWidgetBalanceWithWallets = async ( + wallets: TWallet[], + walletsInitialized: boolean, + cachedBalance: { current: number }, + cachedLatestTransactionTime: { current: number | string }, +): Promise => { + try { + const { allWalletsBalance, latestTransactionTime } = await calculateBalanceAndTransactionTime(wallets, walletsInitialized); + + if (cachedBalance.current !== allWalletsBalance || cachedLatestTransactionTime.current !== latestTransactionTime) { + await Promise.all([ + DefaultPreference.set(WidgetCommunicationKeys.AllWalletsSatoshiBalance, String(allWalletsBalance)), + DefaultPreference.set(WidgetCommunicationKeys.AllWalletsLatestTransactionTime, String(latestTransactionTime)), + ]); + + cachedBalance.current = allWalletsBalance; + cachedLatestTransactionTime.current = latestTransactionTime; + } + } catch (error) { + console.error('Failed to sync widget balance with wallets:', error); + } +}; + +const debouncedSyncWidgetBalanceWithWallets = debounce( + async ( + wallets: TWallet[], + walletsInitialized: boolean, + cachedBalance: { current: number }, + cachedLatestTransactionTime: { current: number | string }, + ) => { + await syncWidgetBalanceWithWallets(wallets, walletsInitialized, cachedBalance, cachedLatestTransactionTime); + }, + 500, +); + +const useWidgetCommunication = (): void => { + const { wallets, walletsInitialized } = useStorage(); + const { isWidgetBalanceDisplayAllowed } = useSettings(); + const cachedBalance = useRef(0); + const cachedLatestTransactionTime = useRef(0); + + // Handle widget data clearing when the setting is disabled + useEffect(() => { + const clearWidgetData = async () => { + if (walletsInitialized && !isWidgetBalanceDisplayAllowed) { + try { + await Promise.all([ + DefaultPreference.set(WidgetCommunicationKeys.AllWalletsSatoshiBalance, WIDGET_CLEARED_VALUE), + DefaultPreference.set(WidgetCommunicationKeys.AllWalletsLatestTransactionTime, WIDGET_CLEARED_VALUE), + ]); + cachedBalance.current = 0; + cachedLatestTransactionTime.current = 0; + console.debug('Widget data cleared due to setting being disabled'); + } catch (error) { + console.error('Failed to clear widget data:', error); + } + } + }; + + clearWidgetData(); + }, [isWidgetBalanceDisplayAllowed, walletsInitialized]); + + // Sync widget data when wallets change or setting is enabled + useEffect(() => { + if (walletsInitialized) { + debouncedSyncWidgetBalanceWithWallets(wallets, walletsInitialized, cachedBalance, cachedLatestTransactionTime); + } + }, [wallets, walletsInitialized, isWidgetBalanceDisplayAllowed]); + + useEffect(() => { + return () => { + debouncedSyncWidgetBalanceWithWallets.cancel(); + }; + }, []); +}; + +export default useWidgetCommunication; diff --git a/hooks/useWidgetCommunication.ts b/hooks/useWidgetCommunication.ts new file mode 100644 index 00000000000..488fa01d55d --- /dev/null +++ b/hooks/useWidgetCommunication.ts @@ -0,0 +1,9 @@ +const useWidgetCommunication = (): void => {}; + +export const isBalanceDisplayAllowed = async (): Promise => { + return true; +}; + +export const setBalanceDisplayAllowed = async (_allowed: boolean): Promise => {}; + +export default useWidgetCommunication; diff --git a/img/Search/drag.tsx b/img/Search/drag.tsx new file mode 100644 index 00000000000..a40698a61f9 --- /dev/null +++ b/img/Search/drag.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import Svg, { Path, Defs, ClipPath, Rect, G } from 'react-native-svg'; + +interface DragIconProps { + width?: number; + height?: number; + color?: string; +} + +const DragIcon: React.FC = ({ width = 19, height = 38, color = '#82858D' }) => ( + + + + + + + + + + +); + +export default DragIcon; diff --git a/img/addWallet/bitcoin.png b/img/addWallet/bitcoin.png index f3af4e7aea7..677b833493a 100644 Binary files a/img/addWallet/bitcoin.png and b/img/addWallet/bitcoin.png differ diff --git a/img/addWallet/bitcoin@2x.png b/img/addWallet/bitcoin@2x.png index 0540be9ca4f..67d98ea44ae 100644 Binary files a/img/addWallet/bitcoin@2x.png and b/img/addWallet/bitcoin@2x.png differ diff --git a/img/addWallet/bitcoin@3x.png b/img/addWallet/bitcoin@3x.png index 9555ad1542b..267562decbb 100644 Binary files a/img/addWallet/bitcoin@3x.png and b/img/addWallet/bitcoin@3x.png differ diff --git a/img/addWallet/lightning.png b/img/addWallet/lightning.png index 9cc3e194c4c..6fe6df59c65 100644 Binary files a/img/addWallet/lightning.png and b/img/addWallet/lightning.png differ diff --git a/img/addWallet/lightning@2x.png b/img/addWallet/lightning@2x.png index 893c82eefef..81a35744e36 100644 Binary files a/img/addWallet/lightning@2x.png and b/img/addWallet/lightning@2x.png differ diff --git a/img/addWallet/lightning@3x.png b/img/addWallet/lightning@3x.png index c70e4bfbbc6..00750555055 100644 Binary files a/img/addWallet/lightning@3x.png and b/img/addWallet/lightning@3x.png differ diff --git a/img/addWallet/vault.png b/img/addWallet/vault.png index a31b86d837e..211acea6052 100644 Binary files a/img/addWallet/vault.png and b/img/addWallet/vault.png differ diff --git a/img/addWallet/vault@2x.png b/img/addWallet/vault@2x.png index ee6e21a9bce..b15a723bfe1 100644 Binary files a/img/addWallet/vault@2x.png and b/img/addWallet/vault@2x.png differ diff --git a/img/addWallet/vault@3x.png b/img/addWallet/vault@3x.png index 34cfa360954..1a6008e8c64 100644 Binary files a/img/addWallet/vault@3x.png and b/img/addWallet/vault@3x.png differ diff --git a/img/addWallet/vault_main.png b/img/addWallet/vault_main.png index aeaa1632490..50e279a2f78 100644 Binary files a/img/addWallet/vault_main.png and b/img/addWallet/vault_main.png differ diff --git a/img/addWallet/vault_main@2x.png b/img/addWallet/vault_main@2x.png index 70e6d38ee88..e648b25c166 100644 Binary files a/img/addWallet/vault_main@2x.png and b/img/addWallet/vault_main@2x.png differ diff --git a/img/addWallet/vault_main@3x.png b/img/addWallet/vault_main@3x.png index 035205331e6..f7d16ba464f 100644 Binary files a/img/addWallet/vault_main@3x.png and b/img/addWallet/vault_main@3x.png differ diff --git a/img/bluewalletsplash.json b/img/bluewalletsplash.json deleted file mode 100644 index a4b85e2fa4e..00000000000 --- a/img/bluewalletsplash.json +++ /dev/null @@ -1 +0,0 @@ -{"ip":0,"fr":60,"v":"5.1.20","assets":[],"layers":[{"ty":4,"nm":"blue","ip":0,"st":0,"ind":5,"hix":1,"ks":{"o":{"a":1,"k":[{"t":0,"s":[0],"e":[0],"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"t":88,"s":[0],"e":[100],"i":{"x":[0.515],"y":[0.955]},"o":{"x":[0.455],"y":[0.03]}},{"t":132}]},"or":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[21.5,8.5,0]},"p":{"s":true,"x":{"a":0,"k":294},"y":{"a":0,"k":231}},"rx":{"a":0,"k":0},"ry":{"a":0,"k":0},"rz":{"a":0,"k":0},"s":{"a":0,"k":[100,100]}},"shapes":[{"ty":"gr","nm":"blue shape group","it":[{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[71.1621094,96.1743164],[67.6757812,94.2260742],[67.4912109,94.2260742],[67.4912109,96],[64.5996094,96],[64.5996094,80.3730469],[67.5834961,80.3730469],[67.5834961,86.4946289],[67.7680664,86.4946289],[71.1621094,84.5053711],[75.7456055,90.3398438]],"i":[[2.8403319999999894,0],[0.6049804999999964,1.230468799999997],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-1.548339900000002,0],[0,-3.6503907000000027]],"o":[[-1.5996094000000056,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0.5742188000000112,-1.240722699999992],[2.8608397999999937,0],[0,3.6298828000000043]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[70.1264648,87.0073242],[67.5527344,90.3500977],[70.1264648,93.6826172],[72.6796875,90.3398438]],"i":[[1.5791016000000013,0],[0.010253899999995042,-2.0610352000000063],[-1.579101499999993,0],[0,2.0712890000000073]],"o":[[-1.568847599999998,0],[0.010253899999995042,2.0507811999999888],[1.5893555000000106,0],[0,-2.061035199999992]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[77.7401952,96],[77.7401952,80.3730469],[80.7240819,80.3730469],[80.7240819,96]],"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[93.5160349,84.6899414],[93.5160349,96],[90.6244333,96],[90.6244333,94.1850586],[90.439863,94.1850586],[87.1381052,96.2460938],[83.1800974,92.0625],[83.1800974,84.6899414],[86.1639841,84.6899414],[86.1639841,91.293457],[88.245527,93.6518555],[90.5321481,91.2114258],[90.5321481,84.6899414]],"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[1.712402300000008,0],[0,2.625],[0,0],[0,0],[0,0],[-1.3740233999999987,0],[0,1.5073241999999993],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[-0.5332031000000086,1.3125],[-2.4404296999999957,0],[0,0],[0,0],[0,0],[0,1.558593799999997],[1.4868165000000033,0],[0,0],[0,0]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[100.863164,86.7304688],[98.4022261,89.1606445],[103.221562,89.1606445]],"i":[[1.3945310000000006,0],[0.10253910000000133,-1.466308600000005],[0,0]],"o":[[-1.3842776999999984,0],[0,0],[-0.06152300000000821,-1.4970703000000043]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[103.283085,92.7802734],[106.061894,92.7802734],[100.893925,96.2460938],[95.4183394,90.4013672],[100.85291,84.4438477],[106.154179,90.1552734],[106.154179,91.0678711],[98.3919722,91.0678711],[98.3919722,91.2216797],[100.975957,93.9492188]],"i":[[-0.3178709999999967,0.7485352000000063],[0,0],[2.7685550000000063,0],[0,3.6708983999999987],[-3.3632814999999994,0],[0,-3.5888671999999957],[0,0],[0,0],[0,0],[-1.5585941999999875,0]],"o":[[0,0],[-0.4511719999999997,2.1328125],[-3.4453121999999894,0],[0,-3.681152400000002],[3.332519000000005,0],[0,0],[0,0],[0,0],[0.04101560000000859,1.6816406000000086],[1.1791990000000112,0]]}}},{"ty":"st","o":{"a":0,"k":0},"w":{"a":0,"k":0},"c":{"a":0,"k":[0,0,0,0]},"lc":3,"lj":1,"ml":1},{"ty":"fl","o":{"a":0,"k":100},"r":1,"c":{"a":0,"k":[1,1,1,1]}},{"ty":"tr","o":{"a":0,"k":100},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[-64,-80]},"r":{"a":0,"k":0}}]}],"op":132},{"ty":4,"nm":"wallet","ip":0,"st":0,"ind":4,"hix":2,"ks":{"o":{"a":0,"k":0},"or":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[29.5,8.5,0]},"p":{"s":true,"x":{"a":0,"k":291},"y":{"a":0,"k":235.5}},"rx":{"a":0,"k":0},"ry":{"a":0,"k":0},"rz":{"a":0,"k":0},"s":{"a":0,"k":[100,100,100]}},"shapes":[],"op":132},{"ty":4,"nm":"small","ip":0,"st":0,"ind":3,"hix":3,"ks":{"o":{"a":1,"k":[{"t":0,"s":[0],"e":[100],"i":{"x":[0.675],"y":[0.19]},"o":{"x":[0.55],"y":[0.055]}},{"t":40}]},"or":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[60,32,0]},"p":{"s":true,"x":{"a":0,"k":275},"y":{"a":1,"k":[{"t":0,"s":[267.5],"e":[228],"i":{"x":[0.265],"y":[1.5]},"o":{"x":[0.68],"y":[-0.55]}},{"t":40}]}},"rx":{"a":0,"k":0},"ry":{"a":0,"k":0},"rz":{"a":0,"k":0},"s":{"a":0,"k":[100,100,100]}},"shapes":[{"ty":"gr","nm":"small shape group","it":[{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[30.3055543,56.484375],[89.6944457,56.484375],[106.331298,60.053101],[116.431274,70.1530769],[120,86.7899293],[120,89.6944457],[116.431274,106.331298],[106.331298,116.431274],[89.6944457,120],[30.3055543,120],[13.6687019,116.431274],[3.56872596,106.331298],[-4.39256841e-16,89.6944457],[4.39256841e-16,86.7899293],[3.56872596,70.1530769],[13.6687019,60.053101]],"i":[[-7.174513700000002,0],[0,0],[-4.354167000000004,-2.3286339],[-2.328634000000008,-4.354166599999999],[0,-7.1745136999999914],[0,0],[2.328633999999994,-4.354167000000004],[4.35416699999999,-2.328634000000008],[7.1745136999999914,0],[0,0],[4.354166699999999,2.328633999999994],[2.3286338200000003,4.35416699999999],[8.78624526e-16,7.1745136999999914],[0,0],[-2.3286338100000004,4.354166699999993],[-4.354166640000001,2.3286337999999986]],"o":[[0,0],[7.1745136999999914,0],[4.35416699999999,2.3286337999999986],[2.328633999999994,4.354166699999993],[0,0],[0,7.1745136999999914],[-2.328634000000008,4.35416699999999],[-4.354167000000004,2.328633999999994],[0,0],[-7.174513700000002,0],[-4.354166640000001,-2.328634000000008],[-2.3286338100000004,-4.354167000000004],[0,0],[-8.78624526e-16,-7.1745136999999914],[2.3286338200000003,-4.354166599999999],[4.354166699999999,-2.3286339]]}}},{"ty":"st","o":{"a":0,"k":0},"w":{"a":0,"k":0},"c":{"a":0,"k":[0,0,0,0]},"lc":3,"lj":1,"ml":1},{"ty":"gf","o":{"a":0,"k":100},"r":2,"g":{"p":2,"k":{"a":0,"k":[0,0.5450980392156862,0.8431372549019608,0.9764705882352941,1,0.40784313725490196,0.7333333333333333,0.8823529411764706]}},"t":1,"s":{"a":0,"k":[60,3.2704118520000005]},"e":{"a":0,"k":[60,120]}},{"ty":"tr","o":{"a":0,"k":100},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[0,-56]},"r":{"a":0,"k":0}}]}],"op":132},{"ty":4,"nm":"medium","ip":0,"st":0,"ind":2,"hix":4,"ks":{"o":{"a":1,"k":[{"t":0,"s":[0],"e":[0],"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"t":27,"s":[0],"e":[100],"i":{"x":[0.675],"y":[0.19]},"o":{"x":[0.55],"y":[0.055]}},{"t":64}]},"or":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[60,47,0]},"p":{"s":true,"x":{"a":0,"k":275},"y":{"a":1,"k":[{"t":0,"s":[274],"e":[274],"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"t":27,"s":[274],"e":[213],"i":{"x":[0.265],"y":[1.5]},"o":{"x":[0.68],"y":[-0.55]}},{"t":64}]}},"rx":{"a":0,"k":0},"ry":{"a":0,"k":0},"rz":{"a":0,"k":0},"s":{"a":0,"k":[100,100,100]}},"shapes":[{"ty":"gr","nm":"medium shape group","it":[{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[34.2519038,26.484375],[85.7480962,26.484375],[106.331298,30.053101],[116.431274,40.1530769],[120,60.7362788],[120,85.7480962],[116.431274,106.331298],[106.331298,116.431274],[85.7480962,120],[34.2519038,120],[13.6687019,116.431274],[3.56872596,106.331298],[-9.22545274e-16,85.7480962],[9.22545274e-16,60.7362788],[3.56872596,40.1530769],[13.6687019,30.053101]],"i":[[-11.9101331,0],[0,0],[-4.354167000000004,-2.3286339000000034],[-2.328634000000008,-4.354166599999999],[0,-11.910133100000003],[0,0],[2.328633999999994,-4.354167000000004],[4.35416699999999,-2.328634000000008],[11.910133099999996,0],[0,0],[4.354166699999999,2.328633999999994],[2.3286338200000003,4.35416699999999],[1.458570646e-15,11.910133099999996],[0,0],[-2.3286338100000004,4.3541667],[-4.354166640000001,2.328633799999995]],"o":[[0,0],[11.910133099999996,0],[4.35416699999999,2.328633799999995],[2.328633999999994,4.3541667],[0,0],[0,11.910133099999996],[-2.328634000000008,4.35416699999999],[-4.354167000000004,2.328633999999994],[0,0],[-11.9101331,0],[-4.354166640000001,-2.328634000000008],[-2.3286338100000004,-4.354167000000004],[0,0],[-1.458570646e-15,-11.910133100000003],[2.3286338200000003,-4.354166599999999],[4.354166699999999,-2.3286339000000034]]}}},{"ty":"st","o":{"a":0,"k":0},"w":{"a":0,"k":0},"c":{"a":0,"k":[0,0,0,0]},"lc":3,"lj":1,"ml":1},{"ty":"gf","o":{"a":0,"k":100},"r":2,"g":{"p":2,"k":{"a":0,"k":[0,0.24705882352941178,0.47058823529411764,0.8627450980392157,1,0.1843137254901961,0.37254901960784315,0.7019607843137254]}},"t":1,"s":{"a":0,"k":[60,0]},"e":{"a":0,"k":[60,117.46116324]}},{"ty":"tr","o":{"a":0,"k":100},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[0,-26]},"r":{"a":0,"k":0}}]}],"op":132},{"ty":4,"nm":"big","ip":0,"st":0,"ind":1,"hix":5,"ks":{"o":{"a":1,"k":[{"t":0,"s":[0],"e":[0],"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"t":49,"s":[0],"e":[100],"i":{"x":[0.675],"y":[0.19]},"o":{"x":[0.55],"y":[0.055]}},{"t":88}]},"or":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[60,60,0]},"p":{"s":true,"x":{"a":0,"k":275},"y":{"a":1,"k":[{"t":0,"s":[287],"e":[287],"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"t":49,"s":[287],"e":[200],"i":{"x":[0.265],"y":[1.5]},"o":{"x":[0.68],"y":[-0.55]}},{"t":88}]}},"rx":{"a":0,"k":0},"ry":{"a":0,"k":0},"rz":{"a":0,"k":0},"s":{"a":0,"k":[100,100,100]}},"shapes":[{"ty":"gr","nm":"big shape group","it":[{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[34.2519038,-1.38381791e-15],[85.7480962,1.38381791e-15],[106.331298,3.56872596],[116.431274,13.6687019],[120,34.2519038],[120,85.7480962],[116.431274,106.331298],[106.331298,116.431274],[85.7480962,120],[34.2519038,120],[13.6687019,116.431274],[3.56872596,106.331298],[-9.22545274e-16,85.7480962],[9.22545274e-16,34.2519038],[3.56872596,13.6687019],[13.6687019,3.56872596]],"i":[[-11.9101331,2.187855967e-15],[0,0],[-4.354167000000004,-2.3286338100000004],[-2.328634000000008,-4.354166640000001],[0,-11.9101331],[0,0],[2.328633999999994,-4.354167000000004],[4.35416699999999,-2.328634000000008],[11.910133099999996,0],[0,0],[4.354166699999999,2.328633999999994],[2.3286338200000003,4.35416699999999],[1.458570646e-15,11.910133099999996],[0,0],[-2.3286338100000004,4.354166699999999],[-4.354166640000001,2.3286338200000003]],"o":[[0,0],[11.910133099999996,-2.187855967e-15],[4.35416699999999,2.3286338200000003],[2.328633999999994,4.354166699999999],[0,0],[0,11.910133099999996],[-2.328634000000008,4.35416699999999],[-4.354167000000004,2.328633999999994],[0,0],[-11.9101331,0],[-4.354166640000001,-2.328634000000008],[-2.3286338100000004,-4.354167000000004],[0,0],[-1.458570646e-15,-11.9101331],[2.3286338200000003,-4.354166640000001],[4.354166699999999,-2.3286338100000004]]}}},{"ty":"st","o":{"a":0,"k":0},"w":{"a":0,"k":0},"c":{"a":0,"k":[0,0,0,0]},"lc":3,"lj":1,"ml":1},{"ty":"gf","o":{"a":0,"k":100},"r":2,"g":{"p":2,"k":{"a":0,"k":[0,0.09019607843137255,0.27450980392156865,0.592156862745098,1,0.047058823529411764,0.1450980392156863,0.3137254901960784]}},"t":1,"s":{"a":0,"k":[60,3.405888732]},"e":{"a":0,"k":[60,120]}},{"ty":"tr","o":{"a":0,"k":100},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0}}]}],"op":132}],"op":132,"w":550,"h":400} \ No newline at end of file diff --git a/img/btc-shape-rtl.png b/img/btc-shape-rtl.png index a1e01957114..9ff0b0abb6c 100644 Binary files a/img/btc-shape-rtl.png and b/img/btc-shape-rtl.png differ diff --git a/img/btc-shape.png b/img/btc-shape.png index 943fa2e38b7..ffdc7216312 100644 Binary files a/img/btc-shape.png and b/img/btc-shape.png differ diff --git a/img/close-white.png b/img/close-white.png index cb86897ea0a..d0b88f33c51 100644 Binary files a/img/close-white.png and b/img/close-white.png differ diff --git a/img/close-white@2x.png b/img/close-white@2x.png index ef648c6b705..b53e5fbb4f3 100644 Binary files a/img/close-white@2x.png and b/img/close-white@2x.png differ diff --git a/img/close-white@3x.png b/img/close-white@3x.png index 46c13afb9cf..ec04a1a0acb 100644 Binary files a/img/close-white@3x.png and b/img/close-white@3x.png differ diff --git a/img/close.png b/img/close.png old mode 100755 new mode 100644 index 5b2e38c880d..cfb7a807f6c Binary files a/img/close.png and b/img/close.png differ diff --git a/img/close@2x.png b/img/close@2x.png old mode 100755 new mode 100644 index 6f8179fd831..8f82ffdea82 Binary files a/img/close@2x.png and b/img/close@2x.png differ diff --git a/img/close@3x.png b/img/close@3x.png old mode 100755 new mode 100644 index 0532d6c5f24..3e4c2227e18 Binary files a/img/close@3x.png and b/img/close@3x.png differ diff --git a/img/faceid-dark.png b/img/faceid-dark.png deleted file mode 100644 index ff50919d759..00000000000 Binary files a/img/faceid-dark.png and /dev/null differ diff --git a/img/faceid-default.png b/img/faceid-default.png deleted file mode 100644 index 5a145828662..00000000000 Binary files a/img/faceid-default.png and /dev/null differ diff --git a/img/hodlhodl-default-avatar.png b/img/hodlhodl-default-avatar.png deleted file mode 100644 index 590a2d5ecbb..00000000000 Binary files a/img/hodlhodl-default-avatar.png and /dev/null differ diff --git a/img/icon.png b/img/icon.png index ec541528a9a..4c4d57e85fb 100644 Binary files a/img/icon.png and b/img/icon.png differ diff --git a/img/icon@2x.png b/img/icon@2x.png index a72c80addc6..23c1109bac5 100644 Binary files a/img/icon@2x.png and b/img/icon@2x.png differ diff --git a/img/icon@3x.png b/img/icon@3x.png index 5727bf33172..84755259c91 100644 Binary files a/img/icon@3x.png and b/img/icon@3x.png differ diff --git a/img/lnd-shape-rtl.png b/img/lnd-shape-rtl.png index 000a0188965..5c860c38c5a 100644 Binary files a/img/lnd-shape-rtl.png and b/img/lnd-shape-rtl.png differ diff --git a/img/lnd-shape.png b/img/lnd-shape.png index 5af49ac2111..d0369712158 100644 Binary files a/img/lnd-shape.png and b/img/lnd-shape.png differ diff --git a/img/mshelp/mshelp-intro.png b/img/mshelp/mshelp-intro.png index 88c40f9909c..d40e7e17007 100644 Binary files a/img/mshelp/mshelp-intro.png and b/img/mshelp/mshelp-intro.png differ diff --git a/img/mshelp/mshelp-intro@2x.png b/img/mshelp/mshelp-intro@2x.png index 3705d7b7571..b476a336d6b 100644 Binary files a/img/mshelp/mshelp-intro@2x.png and b/img/mshelp/mshelp-intro@2x.png differ diff --git a/img/mshelp/mshelp-intro@3x.png b/img/mshelp/mshelp-intro@3x.png index f8128d54dd1..472762eda79 100644 Binary files a/img/mshelp/mshelp-intro@3x.png and b/img/mshelp/mshelp-intro@3x.png differ diff --git a/img/mshelp/tip2.png b/img/mshelp/tip2.png index 02e95435d46..3549728109f 100644 Binary files a/img/mshelp/tip2.png and b/img/mshelp/tip2.png differ diff --git a/img/mshelp/tip2@2x.png b/img/mshelp/tip2@2x.png index e6951fd0661..894d73546c6 100644 Binary files a/img/mshelp/tip2@2x.png and b/img/mshelp/tip2@2x.png differ diff --git a/img/mshelp/tip2@3x.png b/img/mshelp/tip2@3x.png index 1ff9c239dba..98432ce83a3 100644 Binary files a/img/mshelp/tip2@3x.png and b/img/mshelp/tip2@3x.png differ diff --git a/img/mshelp/tip3.png b/img/mshelp/tip3.png index 8a952f7a049..27252ba519f 100644 Binary files a/img/mshelp/tip3.png and b/img/mshelp/tip3.png differ diff --git a/img/mshelp/tip3@2x.png b/img/mshelp/tip3@2x.png index 50d1cdc87bb..c511d1cc194 100644 Binary files a/img/mshelp/tip3@2x.png and b/img/mshelp/tip3@2x.png differ diff --git a/img/mshelp/tip3@3x.png b/img/mshelp/tip3@3x.png index 0d21bf37b37..2c8bf89e461 100644 Binary files a/img/mshelp/tip3@3x.png and b/img/mshelp/tip3@3x.png differ diff --git a/img/mshelp/tip4.png b/img/mshelp/tip4.png index f9744c9fde1..e8445152d3e 100644 Binary files a/img/mshelp/tip4.png and b/img/mshelp/tip4.png differ diff --git a/img/mshelp/tip4@2x.png b/img/mshelp/tip4@2x.png index e402e11c780..8fcc0f7665b 100644 Binary files a/img/mshelp/tip4@2x.png and b/img/mshelp/tip4@2x.png differ diff --git a/img/mshelp/tip4@3x.png b/img/mshelp/tip4@3x.png index 9964ff4f8bf..fd71e153b61 100644 Binary files a/img/mshelp/tip4@3x.png and b/img/mshelp/tip4@3x.png differ diff --git a/img/mshelp/tip5.png b/img/mshelp/tip5.png index 7e2e1fa40ce..2b0827295f0 100644 Binary files a/img/mshelp/tip5.png and b/img/mshelp/tip5.png differ diff --git a/img/mshelp/tip5@2x.png b/img/mshelp/tip5@2x.png index 50d66de1206..0117a052e25 100644 Binary files a/img/mshelp/tip5@2x.png and b/img/mshelp/tip5@2x.png differ diff --git a/img/mshelp/tip5@3x.png b/img/mshelp/tip5@3x.png index 046ebf19b81..265d37dd9eb 100644 Binary files a/img/mshelp/tip5@3x.png and b/img/mshelp/tip5@3x.png differ diff --git a/img/pending.json b/img/pending.json new file mode 100644 index 00000000000..6b4db9cd648 --- /dev/null +++ b/img/pending.json @@ -0,0 +1 @@ +{"v":"5.7.5","fr":100,"ip":0,"op":300,"w":16,"h":16,"ddd":0,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"lottielab.com","sr":1,"ks":{"o":{"a":0,"k":100,"ix":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[0,5.4],[6.17,0]],"i":[[0,0],[-2.057,1.799]],"o":[[2.057,-1.799],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[12,13.51],"ix":2},"a":{"a":0,"k":[0,5.4],"ix":2},"r":{"a":1,"k":[{"t":0,"s":[80],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":300,"s":[440]}],"ix":2},"o":{"a":0,"k":100,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[0,6.83],[9.65,0]],"i":[[0,0],[-3.217,2.278]],"o":[[3.217,-2.278],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[12,13.51],"ix":2},"a":{"a":0,"k":[0,6.83],"ix":2},"r":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":300,"s":[720]}],"ix":2},"o":{"a":0,"k":100,"ix":2}}]},{"ty":"fl","c":{"a":0,"k":[0.153,0.342,0.78],"ix":2},"o":{"a":0,"k":100,"ix":2},"r":1,"bm":0},{"ty":"tr","o":{"a":0,"k":100,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[0,5.4],[6.17,0]],"i":[[0,0],[-2.057,1.799]],"o":[[2.057,-1.799],[0,0]]}}},{"ty":"st","c":{"a":0,"k":[0.153,0.342,0.78],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":3,"ix":2},"lc":2,"lj":2,"ml":4,"d":[{"n":"o","nm":"offset","v":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":0,"k":[12,13.51],"ix":2},"a":{"a":0,"k":[0,5.4],"ix":2},"r":{"a":1,"k":[{"t":0,"s":[80],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":300,"s":[440]}],"ix":2},"o":{"a":0,"k":100,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[0,6.83],[9.65,0]],"i":[[0,0],[-3.217,2.278]],"o":[[3.217,-2.278],[0,0]]}}},{"ty":"st","c":{"a":0,"k":[0.153,0.342,0.78],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":3,"ix":2},"lc":2,"lj":2,"ml":4,"d":[{"n":"o","nm":"offset","v":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":0,"k":[12,13.51],"ix":2},"a":{"a":0,"k":[0,6.83],"ix":2},"r":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":300,"s":[720]}],"ix":2},"o":{"a":0,"k":100,"ix":2}}]},{"ty":"tr","p":{"a":0,"k":[9.43,7.85],"ix":2},"a":{"a":0,"k":[16.83,13.13],"ix":2},"s":{"a":0,"k":[29.7,38.61],"ix":2},"o":{"a":0,"k":100,"ix":2}}]}],"ip":0,"op":301,"st":0},{"ddd":0,"ind":2,"ty":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[7.5,13.5],[1.5,7.5],[7.5,1.5],[13.5,7.5],[7.5,13.5],[7.5,13.5]],"i":[[0,0],[0,3.315],[-3.315,0],[0,-3.315],[3.315,0],[0,0]],"o":[[-3.315,0],[0,-3.315],[3.315,0],[0,3.315],[0,0],[0,0]]}}},{"ty":"st","c":{"a":0,"k":[0.153,0.342,0.78],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":1,"ix":2},"lc":1,"lj":1,"ml":4},{"ty":"fl","c":{"a":0,"k":[0,0.236,0.942],"ix":2},"o":{"a":0,"k":10,"ix":2},"r":1,"bm":0},{"ty":"tr","p":{"a":0,"k":[8,8],"ix":2},"a":{"a":0,"k":[7.5,7.5],"ix":2},"o":{"a":0,"k":100,"ix":2}}]}],"ip":0,"op":301,"st":0}]} \ No newline at end of file diff --git a/img/round-compare-arrows-24-px.png b/img/round-compare-arrows-24-px.png old mode 100755 new mode 100644 index c081c2a4ddf..6d81664c5ae Binary files a/img/round-compare-arrows-24-px.png and b/img/round-compare-arrows-24-px.png differ diff --git a/img/round-compare-arrows-24-px@2x.png b/img/round-compare-arrows-24-px@2x.png old mode 100755 new mode 100644 index c0d35bcd649..3914215853f Binary files a/img/round-compare-arrows-24-px@2x.png and b/img/round-compare-arrows-24-px@2x.png differ diff --git a/img/round-compare-arrows-24-px@3x.png b/img/round-compare-arrows-24-px@3x.png old mode 100755 new mode 100644 index 6296b84e720..d2c04e4641d Binary files a/img/round-compare-arrows-24-px@3x.png and b/img/round-compare-arrows-24-px@3x.png differ diff --git a/img/scan-white.png b/img/scan-white.png index 2602b23c83d..4111cb1203d 100644 Binary files a/img/scan-white.png and b/img/scan-white.png differ diff --git a/img/scan-white@2x.png b/img/scan-white@2x.png index 1547fdd110a..f428036ded1 100644 Binary files a/img/scan-white@2x.png and b/img/scan-white@2x.png differ diff --git a/img/scan-white@3x.png b/img/scan-white@3x.png index f11c274d71f..b89166c2f74 100644 Binary files a/img/scan-white@3x.png and b/img/scan-white@3x.png differ diff --git a/img/scan.png b/img/scan.png old mode 100755 new mode 100644 index 4963942a703..a3fdf1ec546 Binary files a/img/scan.png and b/img/scan.png differ diff --git a/img/scan@2x.png b/img/scan@2x.png old mode 100755 new mode 100644 index fa957c8e7c5..238ee84a436 Binary files a/img/scan@2x.png and b/img/scan@2x.png differ diff --git a/img/scan@3x.png b/img/scan@3x.png old mode 100755 new mode 100644 index 077973a3fdd..58ae85d3d3e Binary files a/img/scan@3x.png and b/img/scan@3x.png differ diff --git a/img/splash/splash.png b/img/splash/splash.png deleted file mode 100644 index cfcf6d35e22..00000000000 Binary files a/img/splash/splash.png and /dev/null differ diff --git a/img/splash/splash@2x.png b/img/splash/splash@2x.png deleted file mode 100644 index cfcf6d35e22..00000000000 Binary files a/img/splash/splash@2x.png and /dev/null differ diff --git a/img/splash/splash@3x.png b/img/splash/splash@3x.png deleted file mode 100644 index 86d9fe298d0..00000000000 Binary files a/img/splash/splash@3x.png and /dev/null differ diff --git a/img/vault-shape-rtl.png b/img/vault-shape-rtl.png index 0df10fd982d..c1221844662 100644 Binary files a/img/vault-shape-rtl.png and b/img/vault-shape-rtl.png differ diff --git a/img/vault-shape.png b/img/vault-shape.png index 9e66ced3348..ea2f2562dd0 100644 Binary files a/img/vault-shape.png and b/img/vault-shape.png differ diff --git a/index.js b/index.js index 15b8d9fc576..3fa2e813922 100644 --- a/index.js +++ b/index.js @@ -1,25 +1,34 @@ -import React, { useEffect } from 'react'; +import './bugsnag'; +import './gesture-handler'; +import 'react-native-get-random-values'; import './shim.js'; -import { AppRegistry } from 'react-native'; + +import React, { useEffect } from 'react'; +import { AppRegistry, LogBox } from 'react-native'; + import App from './App'; -import { BlueStorageProvider } from './blue_modules/storage-context'; +import { restoreSavedPreferredFiatCurrencyAndExchangeFromStorage } from './blue_modules/currency'; -const A = require('./blue_modules/analytics'); if (!Error.captureStackTrace) { // captureStackTrace is only available when debugging Error.captureStackTrace = () => {}; } +LogBox.ignoreLogs([ + 'Require cycle:', + 'Battery state `unknown` and monitoring disabled, this is normal for simulators and tvOS.', + 'Open debugger to view warnings.', + 'Non-serializable values were found in the navigation state', +]); + const BlueAppComponent = () => { useEffect(() => { - A(A.ENUM.INIT); + restoreSavedPreferredFiatCurrencyAndExchangeFromStorage().catch(error => { + console.error('Failed to restore preferred currency and exchange rates on startup:', error); + }); }, []); - return ( - - - - ); + return ; }; AppRegistry.registerComponent('BlueWallet', () => BlueAppComponent); diff --git a/ios/BlueWallet-Bridging-Header.h b/ios/BlueWallet-Bridging-Header.h index 1b2cb5d6d09..04328fe71aa 100644 --- a/ios/BlueWallet-Bridging-Header.h +++ b/ios/BlueWallet-Bridging-Header.h @@ -1,4 +1,13 @@ // -// Use this file to import your target's public headers that you would like to expose to Swift. +// BlueWallet-Bridging-Header.h +// BlueWallet +// +// Created by Marcos Rodriguez on 4/4/25. +// Copyright © 2025 BlueWallet. All rights reserved. // +#import "RNNotifications.h" +#import "RNQuickActionManager.h" +#import "NativeEventEmitterSpec.h" +#import "NativeMenuElementsEmitterSpec.h" +#import "NativeWidgetHelperSpec.h" diff --git a/ios/BlueWallet-tvOS/Info.plist b/ios/BlueWallet-tvOS/Info.plist deleted file mode 100644 index 2fb6a11c2c3..00000000000 --- a/ios/BlueWallet-tvOS/Info.plist +++ /dev/null @@ -1,54 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - NSLocationWhenInUseUsageDescription - - NSAppTransportSecurity - - - NSExceptionDomains - - localhost - - NSExceptionAllowsInsecureHTTPLoads - - - - - - diff --git a/ios/BlueWallet-tvOSTests/Info.plist b/ios/BlueWallet-tvOSTests/Info.plist deleted file mode 100644 index 886825ccc9b..00000000000 --- a/ios/BlueWallet-tvOSTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index 977ca66b95f..98ed73665a5 100644 --- a/ios/BlueWallet.xcodeproj/project.pbxproj +++ b/ios/BlueWallet.xcodeproj/project.pbxproj @@ -3,35 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 63; objects = { /* Begin PBXBuildFile section */ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; - 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 32B5A32A2334450100F8D608 /* Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5A3292334450100F8D608 /* Bridge.swift */; }; + 17CDA0718F42DB2CE856C872 /* libPods-BlueWallet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 040819EDF8BD9C50A9C83E24 /* libPods-BlueWallet.a */; }; 32F0A29A2311DBB20095C559 /* ComplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F0A2992311DBB20095C559 /* ComplicationController.swift */; }; 6D2A6464258BA92D0092292B /* Stickers.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D2A6463258BA92D0092292B /* Stickers.xcassets */; }; - 6D2A6468258BA92D0092292B /* Stickers.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6D2A6461258BA92C0092292B /* Stickers.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D32C5C52596CE3A008C077C /* EventEmitter.m */; }; - 6D4AF15925D21172009DD853 /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; }; - 6D4AF16325D21185009DD853 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; }; - 6D4AF16D25D21192009DD853 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; }; + 6D2A6468258BA92D0092292B /* Stickers.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 6D2A6461258BA92C0092292B /* Stickers.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 6D4AF15925D21172009DD853 /* MarketAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */; }; + 6D4AF16D25D21192009DD853 /* Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */; }; 6D4AF17825D211A3009DD853 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; }; 6D4AF18425D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; }; 6DD4109D266CADF10087DE03 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */; }; 6DD4109E266CADF10087DE03 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */; }; 6DD410A1266CADF10087DE03 /* Widgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD410A0266CADF10087DE03 /* Widgets.swift */; }; - 6DD410A7266CADF40087DE03 /* WidgetsExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6DD4109C266CADF10087DE03 /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 6DD410A7266CADF40087DE03 /* WidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 6DD4109C266CADF10087DE03 /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 6DD410AC266CAE470087DE03 /* PriceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA4BC255872E3009312A5 /* PriceWidget.swift */; }; - 6DD410AE266CAF1F0087DE03 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; }; 6DD410AF266CAF5C0087DE03 /* WalletInformationAndMarketWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E06254BA347007B5B82 /* WalletInformationAndMarketWidget.swift */; }; 6DD410B0266CAF5C0087DE03 /* WalletInformationWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4AB1254FB59C00E9F9AA /* WalletInformationWidget.swift */; }; - 6DD410B1266CAF5C0087DE03 /* WidgetAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */; }; + 6DD410B1266CAF5C0087DE03 /* MarketAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* MarketAPI+Electrum.swift */; }; 6DD410B2266CAF5C0087DE03 /* WalletInformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F2225525053003792DF /* WalletInformationView.swift */; }; 6DD410B3266CAF5C0087DE03 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */; }; - 6DD410B4266CAF5C0087DE03 /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; }; - 6DD410B5266CAF5C0087DE03 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; }; + 6DD410B4266CAF5C0087DE03 /* MarketAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */; }; 6DD410B6266CAF5C0087DE03 /* PriceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5272558EC52009312A5 /* PriceView.swift */; }; 6DD410B7266CAF5C0087DE03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D9A2E08254BA348007B5B82 /* Assets.xcassets */; }; 6DD410B8266CAF5C0087DE03 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; }; @@ -39,7 +34,7 @@ 6DD410BA266CAF5C0087DE03 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; }; 6DD410BB266CAF5C0087DE03 /* MarketView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F17255226DA003792DF /* MarketView.swift */; }; 6DD410BE266CAF5C0087DE03 /* SendReceiveButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F3425526311003792DF /* SendReceiveButtons.swift */; }; - 6DD410BF266CB13D0087DE03 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; }; + 6DD410BF266CB13D0087DE03 /* Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */; }; 6DD410C0266CB1460087DE03 /* MarketWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9946622555A660000E52E8 /* MarketWidget.swift */; }; 6DF25A9F249DB97E001D06F5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6DF25A9E249DB97E001D06F5 /* LaunchScreen.storyboard */; }; 6DFC807024EA0B6C007B8700 /* EFQRCode in Frameworks */ = {isa = PBXBuildFile; productRef = 6DFC806F24EA0B6C007B8700 /* EFQRCode */; }; @@ -48,11 +43,9 @@ 782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B9D9B3A7B2CB4255876B67AF /* libz.tbd */; }; 849047CA2702A32A008EE567 /* Handoff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849047C92702A32A008EE567 /* Handoff.swift */; }; 84E05A842721191B001A0D3A /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 84E05A832721191B001A0D3A /* Settings.bundle */; }; - 906451CAD44154C2950030EC /* libPods-BlueWallet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 731973BA0AC6EA78962CE5B6 /* libPods-BlueWallet.a */; }; + B409AB062D71E07500BA06F8 /* MenuElementsEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B409AB052D71E07500BA06F8 /* MenuElementsEmitter.swift */; }; B40D4E34225841EC00428FCC /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E32225841EC00428FCC /* Interface.storyboard */; }; B40D4E36225841ED00428FCC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; }; - B40D4E3D225841ED00428FCC /* BlueWalletWatch Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = B40D4E3C225841ED00428FCC /* BlueWalletWatch Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - B40D4E44225841ED00428FCC /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E43225841ED00428FCC /* ExtensionDelegate.swift */; }; B40D4E46225841ED00428FCC /* NotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E45225841ED00428FCC /* NotificationController.swift */; }; B40D4E4D225841ED00428FCC /* BlueWalletWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = B40D4E30225841EC00428FCC /* BlueWalletWatch.app */; platformFilter = ios; }; B40D4E5D2258425500428FCC /* InterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E552258425400428FCC /* InterfaceController.swift */; }; @@ -60,18 +53,119 @@ B40D4E602258425500428FCC /* SpecifyInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E582258425400428FCC /* SpecifyInterfaceController.swift */; }; B40D4E632258425500428FCC /* ReceiveInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E5B2258425500428FCC /* ReceiveInterfaceController.swift */; }; B40D4E642258425500428FCC /* WalletDetailsInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E5C2258425500428FCC /* WalletDetailsInterfaceController.swift */; }; - B40D4E682258426B00428FCC /* KeychainSwiftDistrib.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E672258426B00428FCC /* KeychainSwiftDistrib.swift */; }; B40FC3FA29CCD1D00007EBAC /* SwiftTCPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40FC3F829CCD1AC0007EBAC /* SwiftTCPClient.swift */; }; + B41B76852B66B2FF002C48D5 /* Bugsnag in Frameworks */ = {isa = PBXBuildFile; productRef = B41B76842B66B2FF002C48D5 /* Bugsnag */; }; + B41B76872B66B2FF002C48D5 /* BugsnagNetworkRequestPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = B41B76862B66B2FF002C48D5 /* BugsnagNetworkRequestPlugin */; }; + B41C2E562BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B41C2E552BB3DCB8000FE097 /* PrivacyInfo.xcprivacy */; }; + B41C2E572BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B41C2E552BB3DCB8000FE097 /* PrivacyInfo.xcprivacy */; }; + B41C2E582BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B41C2E552BB3DCB8000FE097 /* PrivacyInfo.xcprivacy */; }; B43D0378225847C500FBAA95 /* WalletGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0372225847C500FBAA95 /* WalletGradient.swift */; }; B43D0379225847C500FBAA95 /* WatchDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0373225847C500FBAA95 /* WatchDataSource.swift */; }; B43D037A225847C500FBAA95 /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0374225847C500FBAA95 /* Transaction.swift */; }; B43D037B225847C500FBAA95 /* TransactionTableRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0375225847C500FBAA95 /* TransactionTableRow.swift */; }; B43D037C225847C500FBAA95 /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0376225847C500FBAA95 /* Wallet.swift */; }; B43D037D225847C500FBAA95 /* WalletInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0377225847C500FBAA95 /* WalletInformation.swift */; }; - B461B852299599F800E431AA /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = B461B851299599F800E431AA /* AppDelegate.mm */; }; + B44033BF2BCC32F800162242 /* BitcoinUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033BE2BCC32F800162242 /* BitcoinUnit.swift */; }; + B44033C02BCC32F800162242 /* BitcoinUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033BE2BCC32F800162242 /* BitcoinUnit.swift */; }; + B44033C12BCC32F800162242 /* BitcoinUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033BE2BCC32F800162242 /* BitcoinUnit.swift */; }; + B44033C42BCC332400162242 /* Balance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C32BCC332400162242 /* Balance.swift */; }; + B44033C52BCC332400162242 /* Balance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C32BCC332400162242 /* Balance.swift */; }; + B44033C62BCC332400162242 /* Balance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C32BCC332400162242 /* Balance.swift */; }; + B44033CA2BCC350A00162242 /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C92BCC350A00162242 /* Currency.swift */; }; + B44033CB2BCC350A00162242 /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C92BCC350A00162242 /* Currency.swift */; }; + B44033CC2BCC350A00162242 /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C92BCC350A00162242 /* Currency.swift */; }; + B44033CE2BCC352900162242 /* UserDefaultsGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */; }; + B44033D02BCC352F00162242 /* UserDefaultsGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */; }; + B44033D32BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033D22BCC368800162242 /* UserDefaultsGroupKey.swift */; }; + B44033D42BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033D22BCC368800162242 /* UserDefaultsGroupKey.swift */; }; + B44033D52BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033D22BCC368800162242 /* UserDefaultsGroupKey.swift */; }; + B44033D82BCC369500162242 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; }; + B44033DA2BCC369A00162242 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */; }; + B44033DD2BCC36C300162242 /* LatestTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033DC2BCC36C300162242 /* LatestTransaction.swift */; }; + B44033DE2BCC36C300162242 /* LatestTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033DC2BCC36C300162242 /* LatestTransaction.swift */; }; + B44033DF2BCC36C300162242 /* LatestTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033DC2BCC36C300162242 /* LatestTransaction.swift */; }; + B44033E22BCC36CB00162242 /* Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */; }; + B44033E42BCC36FF00162242 /* WalletData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E32BCC36FF00162242 /* WalletData.swift */; }; + B44033E52BCC36FF00162242 /* WalletData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E32BCC36FF00162242 /* WalletData.swift */; }; + B44033E62BCC36FF00162242 /* WalletData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E32BCC36FF00162242 /* WalletData.swift */; }; + B44033EA2BCC371A00162242 /* MarketData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E82BCC371A00162242 /* MarketData.swift */; }; + B44033EB2BCC371A00162242 /* MarketData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E82BCC371A00162242 /* MarketData.swift */; }; + B44033EE2BCC374500162242 /* Numeric+abbreviated.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033ED2BCC374500162242 /* Numeric+abbreviated.swift */; }; + B44033EF2BCC374500162242 /* Numeric+abbreviated.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033ED2BCC374500162242 /* Numeric+abbreviated.swift */; }; + B44033F02BCC374500162242 /* Numeric+abbreviated.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033ED2BCC374500162242 /* Numeric+abbreviated.swift */; }; + B44033F42BCC377F00162242 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F32BCC377F00162242 /* WidgetData.swift */; }; + B44033F52BCC377F00162242 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F32BCC377F00162242 /* WidgetData.swift */; }; + B44033F62BCC377F00162242 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F32BCC377F00162242 /* WidgetData.swift */; }; + B44033F92BCC379200162242 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F82BCC379200162242 /* WidgetDataStore.swift */; }; + B44033FA2BCC379200162242 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F82BCC379200162242 /* WidgetDataStore.swift */; }; + B44033FB2BCC379200162242 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F82BCC379200162242 /* WidgetDataStore.swift */; }; + B44033FE2BCC37D700162242 /* MarketAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */; }; + B44034002BCC37F800162242 /* Bundle+decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033FF2BCC37F800162242 /* Bundle+decode.swift */; }; + B44034012BCC37F800162242 /* Bundle+decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033FF2BCC37F800162242 /* Bundle+decode.swift */; }; + B44034022BCC37F800162242 /* Bundle+decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033FF2BCC37F800162242 /* Bundle+decode.swift */; }; + B44034052BCC389200162242 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; }; + B44034072BCC38A000162242 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; }; + B440340F2BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; }; + B44034102BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; }; + B44034112BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; }; + B450109C2C0FCD8A00619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; }; + B450109D2C0FCD9F00619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; }; + B450109F2C0FCDA500619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; }; + B4549F362B82B10D002E3153 /* ci_post_clone.sh in Resources */ = {isa = PBXBuildFile; fileRef = B4549F352B82B10D002E3153 /* ci_post_clone.sh */; }; + B461B852299599F800E431AA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B461B851299599F800E431AA /* AppDelegate.swift */; }; + B4742E972CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; }; + B4742E982CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; }; + B4742E992CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; }; + B4742E9A2CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; }; + B4742E9B2CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; }; + B4793DBB2CEDACBD00C92C2E /* Chain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4793DBA2CEDACBD00C92C2E /* Chain.swift */; }; + B4793DBC2CEDACBD00C92C2E /* Chain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4793DBA2CEDACBD00C92C2E /* Chain.swift */; }; + B4793DBD2CEDACBD00C92C2E /* Chain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4793DBA2CEDACBD00C92C2E /* Chain.swift */; }; + B4793DBF2CEDACDA00C92C2E /* TransactionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4793DBE2CEDACDA00C92C2E /* TransactionType.swift */; }; + B4793DC12CEDACE700C92C2E /* WalletType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4793DC02CEDACE700C92C2E /* WalletType.swift */; }; + B4793DC32CEDAD4400C92C2E /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4793DC22CEDAD4400C92C2E /* KeychainHelper.swift */; }; + B4793DC42CEDAD4400C92C2E /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4793DC22CEDAD4400C92C2E /* KeychainHelper.swift */; }; + B4793DC52CEDAD4400C92C2E /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4793DC22CEDAD4400C92C2E /* KeychainHelper.swift */; }; + B48630D62CCEE67100A8425C /* PriceWidgetProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B48630D52CCEE67100A8425C /* PriceWidgetProvider.swift */; }; + B48630DD2CCEE7AC00A8425C /* PriceWidgetEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B48630DC2CCEE7AC00A8425C /* PriceWidgetEntry.swift */; }; + B48630DE2CCEE7AC00A8425C /* PriceWidgetEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B48630DC2CCEE7AC00A8425C /* PriceWidgetEntry.swift */; }; + B48630E02CCEE7C800A8425C /* PriceWidgetEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B48630DF2CCEE7C800A8425C /* PriceWidgetEntryView.swift */; }; + B48630E12CCEE7C800A8425C /* PriceWidgetEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B48630DF2CCEE7C800A8425C /* PriceWidgetEntryView.swift */; }; + B48630E52CCEE8B800A8425C /* PriceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5272558EC52009312A5 /* PriceView.swift */; }; + B48630E72CCEE91900A8425C /* PriceWidgetProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B48630D52CCEE67100A8425C /* PriceWidgetProvider.swift */; }; + B48630E82CCEE92400A8425C /* PriceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA4BC255872E3009312A5 /* PriceWidget.swift */; }; + B48630EA2CCEED8400A8425C /* PriceIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B48630D02CCEE3B300A8425C /* PriceIntent.swift */; }; + B48630EC2CCEEEA700A8425C /* WalletAppShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B48630EB2CCEEEA700A8425C /* WalletAppShortcuts.swift */; }; + B48630ED2CCEEEB000A8425C /* WalletAppShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B48630EB2CCEEEA700A8425C /* WalletAppShortcuts.swift */; }; + B48630EE2CCEEEE900A8425C /* PriceIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B48630D02CCEE3B300A8425C /* PriceIntent.swift */; }; + B49A28BB2CD18999006B08E4 /* CompactPriceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49A28BA2CD18999006B08E4 /* CompactPriceView.swift */; }; + B49A28BC2CD18999006B08E4 /* CompactPriceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49A28BA2CD18999006B08E4 /* CompactPriceView.swift */; }; + B49A28BE2CD189B0006B08E4 /* FiatUnitEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49A28BD2CD189B0006B08E4 /* FiatUnitEnum.swift */; }; + B49A28BF2CD18A9A006B08E4 /* FiatUnitEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49A28BD2CD189B0006B08E4 /* FiatUnitEnum.swift */; }; + B49A28C52CD1A894006B08E4 /* MarketData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E82BCC371A00162242 /* MarketData.swift */; }; + B4AA75242DAA339E00CF5CBE /* MenuElementsEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = B4AA75232DAA339E00CF5CBE /* MenuElementsEmitter.m */; }; + B4AB225D2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; }; + B4AB225E2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; }; + B4B1A4622BFA73110072E3BB /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */; }; + B4B1A4642BFA73110072E3BB /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */; }; + B4B3EC222D69FF6C00327F3D /* SegmentedControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B3EC202D69FF6C00327F3D /* SegmentedControlView.swift */; }; + B4B3EC252D69FF8700327F3D /* EventEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B3EC232D69FF8700327F3D /* EventEmitter.swift */; }; + B4B3EF102D6AFF6C003270A0 /* SegmentedControlManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B3EF002D6AFF6C003270A0 /* SegmentedControlManager.swift */; }; + B4B3EF202D6CFF6C003270A0 /* SegmentedControlBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = B4B3EF1F2D6CFF6C003270A0 /* SegmentedControlBridge.m */; }; + B4D0B2622C1DEA11006B6B1B /* ReceivePageInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2612C1DEA11006B6B1B /* ReceivePageInterfaceController.swift */; }; + B4D0B2642C1DEA99006B6B1B /* ReceiveType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2632C1DEA99006B6B1B /* ReceiveType.swift */; }; + B4D0B2662C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2652C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift */; }; + B4D0B2682C1DED67006B6B1B /* ReceiveMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */; }; + B4D59C1A2D8BAFE300B7025B /* EFQRCode in Frameworks */ = {isa = PBXBuildFile; productRef = B4D59C192D8BAFE300B7025B /* EFQRCode */; }; + B4D59C1C2D8BAFE300B7025B /* Bugsnag in Frameworks */ = {isa = PBXBuildFile; productRef = B4D59C1B2D8BAFE300B7025B /* Bugsnag */; }; + B4D59C1E2D8BAFE300B7025B /* BugsnagNetworkRequestPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = B4D59C1D2D8BAFE300B7025B /* BugsnagNetworkRequestPlugin */; }; + B4D59C212D8BB42100B7025B /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D59C202D8BB41F00B7025B /* File.swift */; }; + B4D59C272D8C5D6F00B7025B /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D59C262D8C5D6E00B7025B /* main.swift */; }; B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; }; - E5D4794B26781FC0007838C1 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; }; - E5D4794C26781FC1007838C1 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; }; + B4EFF73B2C3F6C5E0095D655 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EFF73A2C3F6C5E0095D655 /* MockData.swift */; }; + B4F0A4A22FA1BC0000AAAA01 /* WidgetHelper.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4F0A4A12FA1BC0000AAAA00 /* WidgetHelper.mm */; }; + B4F0A4A42FA1BC0000AAAA03 /* EventEmitter.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4F0A4A32FA1BC0000AAAA02 /* EventEmitter.mm */; }; + C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -96,13 +190,6 @@ remoteGlobalIDString = 6DD4109B266CADF10087DE03; remoteInfo = WidgetsExtension; }; - B40D4E3E225841ED00428FCC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B40D4E3B225841ED00428FCC; - remoteInfo = "BlueWalletWatch Extension"; - }; B40D4E4B225841ED00428FCC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; @@ -113,16 +200,16 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 3271B0B6236E2E0700DA766F /* Embed App Extensions */ = { + 3271B0B6236E2E0700DA766F /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( - 6D2A6468258BA92D0092292B /* Stickers.appex in Embed App Extensions */, - 6DD410A7266CADF40087DE03 /* WidgetsExtension.appex in Embed App Extensions */, + 6D2A6468258BA92D0092292B /* Stickers.appex in Embed Foundation Extensions */, + 6DD410A7266CADF40087DE03 /* WidgetsExtension.appex in Embed Foundation Extensions */, ); - name = "Embed App Extensions"; + name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; B40D4E2D225841C300428FCC /* Embed Watch Content */ = { @@ -136,51 +223,25 @@ name = "Embed Watch Content"; runOnlyForDeploymentPostprocessing = 0; }; - B40D4E51225841ED00428FCC /* Embed App Extensions */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 13; - files = ( - B40D4E3D225841ED00428FCC /* BlueWalletWatch Extension.appex in Embed App Extensions */, - ); - name = "Embed App Extensions"; - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 00E356F21AD99517003FC87E /* BlueWalletTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BlueWalletTests.m; sourceTree = ""; }; - 04466491BA2D4876A71222FC /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = ""; }; + 040819EDF8BD9C50A9C83E24 /* libPods-BlueWallet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BlueWallet.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* BlueWallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BlueWallet.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = BlueWallet/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = BlueWallet/Info.plist; sourceTree = ""; }; - 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = BlueWallet/main.m; sourceTree = ""; }; + 1AE7FA8B4A18928E917F42D1 /* Pods-BlueWallet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlueWallet.debug.xcconfig"; path = "Target Support Files/Pods-BlueWallet/Pods-BlueWallet.debug.xcconfig"; sourceTree = ""; }; 1DD63E4B5C8344BB9880C9EC /* libReactNativePermissions.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libReactNativePermissions.a; sourceTree = ""; }; 253243E162CE4822BF3A3B7D /* libRNRandomBytes-tvOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = "libRNRandomBytes-tvOS.a"; sourceTree = ""; }; 2654894D4DE44A4C8F71773D /* CoreData.framework */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 2D16E6891FA4F8E400B85C8A /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 2FCC2CD6FF4448229D0CE0F3 /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialCommunityIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = ""; }; 3271B0AA236E2E0700DA766F /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; - 32B5A3282334450100F8D608 /* BlueWallet-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BlueWallet-Bridging-Header.h"; sourceTree = ""; }; - 32B5A3292334450100F8D608 /* Bridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bridge.swift; sourceTree = ""; }; 32C7944323B8879D00BE2AFA /* BlueWalletRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = BlueWalletRelease.entitlements; path = BlueWallet/BlueWalletRelease.entitlements; sourceTree = ""; }; - 32F0A24F2310B0700095C559 /* BlueWalletWatch Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "BlueWalletWatch Extension.entitlements"; sourceTree = ""; }; 32F0A2502310B0910095C559 /* BlueWallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = BlueWallet.entitlements; path = BlueWallet/BlueWallet.entitlements; sourceTree = ""; }; 32F0A2992311DBB20095C559 /* ComplicationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComplicationController.swift; sourceTree = ""; }; - 334051161886419EA186F4BA /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = ""; }; - 367FA8CEB35BC9431019D98A /* Pods-MarketWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MarketWidgetExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MarketWidgetExtension/Pods-MarketWidgetExtension.debug.xcconfig"; sourceTree = ""; }; 3703B10AAB374CF896CCC2EA /* libBVLinearGradient.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libBVLinearGradient.a; sourceTree = ""; }; - 3F7F1B8332C6439793D55B45 /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = ""; }; - 41BD3AC9FD81723B68A63C12 /* libPods-MarketWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MarketWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 44BC9E3EE0E9476A830CCCB9 /* Entypo.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Entypo.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"; sourceTree = ""; }; - 47564776A7A3427DB36C087D /* FontAwesome5_Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Regular.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf"; sourceTree = ""; }; - 47C436B1EF23484B8181DBEA /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = ""; }; - 4D746BBE67E84684848246E2 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = ""; }; 4F12F501B686459183E0BE0D /* libRNVectorIcons.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNVectorIcons.a; sourceTree = ""; }; - 5A8F67CF29564E41882ECEF8 /* FontAwesome5_Brands.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Brands.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf"; sourceTree = ""; }; 6A65D81712444D37BA152B06 /* libRNRandomBytes.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNRandomBytes.a; sourceTree = ""; }; 6D203C2025D4ED2500493AD1 /* BlueWalletWatch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BlueWalletWatch.entitlements; sourceTree = ""; }; 6D294A7324D510AC0039E22B /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/Interface.strings; sourceTree = ""; }; @@ -209,8 +270,6 @@ 6D2A6463258BA92D0092292B /* Stickers.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Stickers.xcassets; sourceTree = ""; }; 6D2A6465258BA92D0092292B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6D2AA8072568B8F40090B089 /* FiatUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiatUnit.swift; sourceTree = ""; }; - 6D32C5C42596CE2F008C077C /* EventEmitter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EventEmitter.h; sourceTree = ""; }; - 6D32C5C52596CE3A008C077C /* EventEmitter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EventEmitter.m; sourceTree = ""; }; 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 6D4AF18225D215D0009DD853 /* BlueWalletWatch-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BlueWalletWatch-Bridging-Header.h"; sourceTree = ""; }; @@ -219,62 +278,51 @@ 6D641F2225525053003792DF /* WalletInformationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInformationView.swift; sourceTree = ""; }; 6D641F3425526311003792DF /* SendReceiveButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendReceiveButtons.swift; sourceTree = ""; }; 6D6CA4BC255872E3009312A5 /* PriceWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceWidget.swift; sourceTree = ""; }; - 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WidgetAPI+Electrum.swift"; sourceTree = ""; }; + 6D6CA5142558EBA3009312A5 /* MarketAPI+Electrum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MarketAPI+Electrum.swift"; sourceTree = ""; }; 6D6CA5272558EC52009312A5 /* PriceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceView.swift; sourceTree = ""; }; 6D9946622555A660000E52E8 /* MarketWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketWidget.swift; sourceTree = ""; }; 6D9A2E06254BA347007B5B82 /* WalletInformationAndMarketWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInformationAndMarketWidget.swift; sourceTree = ""; }; 6D9A2E08254BA348007B5B82 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetAPI.swift; sourceTree = ""; }; - 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetDataStore.swift; sourceTree = ""; }; + 6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketAPI.swift; sourceTree = ""; }; 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsGroup.swift; sourceTree = ""; }; 6DD4109C266CADF10087DE03 /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 6DD410A0266CADF10087DE03 /* Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widgets.swift; sourceTree = ""; }; 6DD410A4266CADF40087DE03 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = fiatUnits.json; path = ../../../../models/fiatUnits.json; sourceTree = ""; }; 6DD410C3266CCB780087DE03 /* WidgetsExtension.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = WidgetsExtension.entitlements; sourceTree = SOURCE_ROOT; }; 6DEB4AB1254FB59C00E9F9AA /* WalletInformationWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInformationWidget.swift; sourceTree = ""; }; - 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; + 6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Placeholders.swift; sourceTree = ""; }; 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; 6DF25A9E249DB97E001D06F5 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 6DFC807124EA2FA9007B8700 /* ViewQRCodefaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewQRCodefaceController.swift; sourceTree = ""; }; 6EB3338E347F4AFAA8C85C04 /* libRNDeviceInfo-tvOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = "libRNDeviceInfo-tvOS.a"; sourceTree = ""; }; 70C9C17A3F52430B99582AF4 /* libRNCamera.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCamera.a; sourceTree = ""; }; - 731973BA0AC6EA78962CE5B6 /* libPods-BlueWallet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BlueWallet.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 78A87E7251D94144A71A2F67 /* FontAwesome5_Solid.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Solid.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf"; sourceTree = ""; }; 7B468CC34D5B41F3950078EF /* libsqlite3.0.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.0.tbd; path = usr/lib/libsqlite3.0.tbd; sourceTree = SDKROOT; }; - 7BAA8F97E61B677D33CF1944 /* Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-WalletInformationAndMarketWidgetExtension/Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig"; sourceTree = ""; }; 8448882949434D41A054C0B2 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ToolTipMenuTests.xctest; sourceTree = ""; }; 849047C92702A32A008EE567 /* Handoff.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Handoff.swift; sourceTree = ""; }; 84E05A832721191B001A0D3A /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; 8637D4B5E14D443A9031DA95 /* libRNFS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFS.a; sourceTree = ""; }; 90F86BC5194548CA87D729A9 /* libToolTipMenu.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libToolTipMenu.a; sourceTree = ""; }; + 93D74F9C8EE7B4443A49594C /* Pods-BlueWallet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlueWallet.release.xcconfig"; path = "Target Support Files/Pods-BlueWallet/Pods-BlueWallet.release.xcconfig"; sourceTree = ""; }; 94565BFC6A0C4235B3EC7B01 /* libRNSVG.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSVG.a; sourceTree = ""; }; 95208B2A05884A76B5BB99C0 /* libRCTGoogleAnalyticsBridge.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTGoogleAnalyticsBridge.a; sourceTree = ""; }; - 98455D960744E4E5DD50BA87 /* libPods-WalletInformationAndMarketWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-WalletInformationAndMarketWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 9B3A324B70BC8C6D9314FD4F /* Pods-BlueWallet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlueWallet.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BlueWallet/Pods-BlueWallet.debug.xcconfig"; sourceTree = ""; }; 9DF4E6C040764E4BA1ACC1EB /* libTcpSockets.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libTcpSockets.a; sourceTree = ""; }; 9F1F51A83D044F3BB26A35FC /* libRNSVG-tvOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = "libRNSVG-tvOS.a"; sourceTree = ""; }; A7C4B1FDAD264618BAF8C335 /* libRNCWebView.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCWebView.a; sourceTree = ""; }; - A9166D490AEF4938BD6621CF /* Feather.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Feather.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = ""; }; - AA7DCFB2C7887DF26EDB5710 /* Pods-WalletInformationAndMarketWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WalletInformationAndMarketWidgetExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-WalletInformationAndMarketWidgetExtension/Pods-WalletInformationAndMarketWidgetExtension.debug.xcconfig"; sourceTree = ""; }; AB2325650CE04F018697ACFE /* libRNReactNativeHapticFeedback.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNReactNativeHapticFeedback.a; sourceTree = ""; }; + B409AB052D71E07500BA06F8 /* MenuElementsEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MenuElementsEmitter.swift; path = MenuElementsEmitter/MenuElementsEmitter.swift; sourceTree = SOURCE_ROOT; }; B40D4E30225841EC00428FCC /* BlueWalletWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BlueWalletWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; B40D4E33225841EC00428FCC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = ""; }; B40D4E35225841ED00428FCC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B40D4E37225841ED00428FCC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B40D4E3C225841ED00428FCC /* BlueWalletWatch Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "BlueWalletWatch Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; - B40D4E43225841ED00428FCC /* ExtensionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDelegate.swift; sourceTree = ""; }; B40D4E45225841ED00428FCC /* NotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationController.swift; sourceTree = ""; }; - B40D4E49225841ED00428FCC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B40D4E4A225841ED00428FCC /* PushNotificationPayload.apns */ = {isa = PBXFileReference; lastKnownFileType = text; path = PushNotificationPayload.apns; sourceTree = ""; }; B40D4E552258425400428FCC /* InterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterfaceController.swift; sourceTree = ""; }; B40D4E562258425400428FCC /* NumericKeypadInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumericKeypadInterfaceController.swift; sourceTree = ""; }; B40D4E582258425400428FCC /* SpecifyInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpecifyInterfaceController.swift; sourceTree = ""; }; B40D4E5B2258425500428FCC /* ReceiveInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReceiveInterfaceController.swift; sourceTree = ""; }; B40D4E5C2258425500428FCC /* WalletDetailsInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletDetailsInterfaceController.swift; sourceTree = ""; }; - B40D4E672258426B00428FCC /* KeychainSwiftDistrib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainSwiftDistrib.swift; sourceTree = SOURCE_ROOT; }; B40FC3F829CCD1AC0007EBAC /* SwiftTCPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTCPClient.swift; sourceTree = ""; }; - B43B69B8225C462E00925B1E /* libPods-RCTLinking.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libPods-RCTLinking.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + B41C2E552BB3DCB8000FE097 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; B43B69BA225C46D800925B1E /* libRCTLinking.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libRCTLinking.a; sourceTree = BUILT_PRODUCTS_DIR; }; B43D0372225847C500FBAA95 /* WalletGradient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletGradient.swift; sourceTree = ""; }; B43D0373225847C500FBAA95 /* WatchDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchDataSource.swift; sourceTree = ""; }; @@ -283,26 +331,69 @@ B43D0376225847C500FBAA95 /* Wallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = ""; }; B43D0377225847C500FBAA95 /* WalletInformation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletInformation.swift; sourceTree = ""; }; B43D046E22584C1B00FBAA95 /* libRNWatch.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libRNWatch.a; sourceTree = BUILT_PRODUCTS_DIR; }; - B459EE96941AE09BCB547DC0 /* Pods-BlueWallet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlueWallet.release.xcconfig"; path = "Pods/Target Support Files/Pods-BlueWallet/Pods-BlueWallet.release.xcconfig"; sourceTree = ""; }; - B461B850299599F800E431AA /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = BlueWallet/AppDelegate.h; sourceTree = ""; }; - B461B851299599F800E431AA /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = BlueWallet/AppDelegate.mm; sourceTree = ""; }; + B44033BE2BCC32F800162242 /* BitcoinUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitcoinUnit.swift; sourceTree = ""; }; + B44033C32BCC332400162242 /* Balance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Balance.swift; sourceTree = ""; }; + B44033C92BCC350A00162242 /* Currency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Currency.swift; sourceTree = ""; }; + B44033D22BCC368800162242 /* UserDefaultsGroupKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsGroupKey.swift; sourceTree = ""; }; + B44033DC2BCC36C300162242 /* LatestTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestTransaction.swift; sourceTree = ""; }; + B44033E32BCC36FF00162242 /* WalletData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletData.swift; sourceTree = ""; }; + B44033E82BCC371A00162242 /* MarketData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketData.swift; sourceTree = ""; }; + B44033ED2BCC374500162242 /* Numeric+abbreviated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Numeric+abbreviated.swift"; sourceTree = ""; }; + B44033F32BCC377F00162242 /* WidgetData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetData.swift; sourceTree = ""; }; + B44033F82BCC379200162242 /* WidgetDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataStore.swift; sourceTree = ""; }; + B44033FF2BCC37F800162242 /* Bundle+decode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+decode.swift"; sourceTree = ""; }; + B440340E2BCC40A400162242 /* fiatUnits.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = fiatUnits.json; path = ../../../models/fiatUnits.json; sourceTree = ""; }; + B450109B2C0FCD8A00619044 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; + B4549F352B82B10D002E3153 /* ci_post_clone.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = ""; }; + B461B851299599F800E431AA /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = BlueWallet/AppDelegate.swift; sourceTree = ""; }; + B4742E962CCDBE8300380EEE /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + B4742E9C2CCDC31300380EEE /* en_US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en_US; path = en_US.lproj/Interface.strings; sourceTree = ""; }; + B4793DBA2CEDACBD00C92C2E /* Chain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chain.swift; sourceTree = ""; }; + B4793DBE2CEDACDA00C92C2E /* TransactionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionType.swift; sourceTree = ""; }; + B4793DC02CEDACE700C92C2E /* WalletType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletType.swift; sourceTree = ""; }; + B4793DC22CEDAD4400C92C2E /* KeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainHelper.swift; sourceTree = ""; }; + B47B21EB2B2128B8001F6690 /* BlueWalletUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueWalletUITests.swift; sourceTree = ""; }; + B48283572DA0DE02007EEC62 /* BlueWallet-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BlueWallet-Bridging-Header.h"; sourceTree = ""; }; + B48630D02CCEE3B300A8425C /* PriceIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceIntent.swift; sourceTree = ""; }; + B48630D52CCEE67100A8425C /* PriceWidgetProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceWidgetProvider.swift; sourceTree = ""; }; + B48630DC2CCEE7AC00A8425C /* PriceWidgetEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceWidgetEntry.swift; sourceTree = ""; }; + B48630DF2CCEE7C800A8425C /* PriceWidgetEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceWidgetEntryView.swift; sourceTree = ""; }; + B48630EB2CCEEEA700A8425C /* WalletAppShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletAppShortcuts.swift; sourceTree = ""; }; + B49038D82B8FBAD300A8164A /* BlueWalletUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueWalletUITest.swift; sourceTree = ""; }; + B49A28BA2CD18999006B08E4 /* CompactPriceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactPriceView.swift; sourceTree = ""; }; + B49A28BD2CD189B0006B08E4 /* FiatUnitEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiatUnitEnum.swift; sourceTree = ""; }; + B49D99932EFE2F3500A718AC /* NativeWidgetHelperSpec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NativeWidgetHelperSpec.h; sourceTree = ""; }; + B4AA75232DAA339E00CF5CBE /* MenuElementsEmitter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MenuElementsEmitter.m; sourceTree = ""; }; + B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLParserDelegate.swift; sourceTree = ""; }; + B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetHelper.swift; sourceTree = ""; }; + B4B31A352C77BBA000663334 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Interface.strings; sourceTree = ""; }; + B4B3EC202D69FF6C00327F3D /* SegmentedControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SegmentedControlView.swift; path = ../blue_modules/Views/SegmentedControl/ios/SegmentedControlView.swift; sourceTree = SOURCE_ROOT; }; + B4B3EC232D69FF8700327F3D /* EventEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventEmitter.swift; sourceTree = ""; }; + B4B3EF002D6AFF6C003270A0 /* SegmentedControlManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SegmentedControlManager.swift; path = ../blue_modules/Views/SegmentedControl/ios/SegmentedControlManager.swift; sourceTree = SOURCE_ROOT; }; + B4B3EF1F2D6CFF6C003270A0 /* SegmentedControlBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SegmentedControlBridge.m; path = ../blue_modules/Views/SegmentedControl/ios/SegmentedControlBridge.m; sourceTree = SOURCE_ROOT; }; + B4D0B2612C1DEA11006B6B1B /* ReceivePageInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceivePageInterfaceController.swift; sourceTree = ""; }; + B4D0B2632C1DEA99006B6B1B /* ReceiveType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveType.swift; sourceTree = ""; }; + B4D0B2652C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveInterfaceMode.swift; sourceTree = ""; }; + B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveMethod.swift; sourceTree = ""; }; B4D3235A177F4580BA52F2F9 /* libRNCSlider.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCSlider.a; sourceTree = ""; }; + B4D59C202D8BB41F00B7025B /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; }; + B4D59C262D8C5D6E00B7025B /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + B4EFF73A2C3F6C5E0095D655 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = ""; }; + B4F0A4A12FA1BC0000AAAA00 /* WidgetHelper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = WidgetHelper.mm; sourceTree = ""; }; + B4F0A4A32FA1BC0000AAAA02 /* EventEmitter.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = EventEmitter.mm; sourceTree = ""; }; + B4F0A4A52FA1BC0000AAAA05 /* NativeEventEmitterSpec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NativeEventEmitterSpec.h; sourceTree = ""; }; + B4F0A4A62FA1BC0000AAAA06 /* NativeMenuElementsEmitterSpec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NativeMenuElementsEmitterSpec.h; sourceTree = ""; }; B642AFB13483418CAB6FF25E /* libRCTQRCodeLocalImage.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTQRCodeLocalImage.a; sourceTree = ""; }; B9D9B3A7B2CB4255876B67AF /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; BBA99996E6FA4B49ACE0BEFA /* libRNRate.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNRate.a; sourceTree = ""; }; - C4496FB303574862B40A878A /* AntDesign.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = AntDesign.ttf; path = "../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf"; sourceTree = ""; }; - CA741BA794714D3F80251AC9 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = ""; }; CD746B955C55410793BB72C0 /* libRNGestureHandler.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNGestureHandler.a; sourceTree = ""; }; - CF4A4D7AAD974D67A2D62B3E /* MaterialIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"; sourceTree = ""; }; E6B44173A8854B6D85D7F933 /* libRNVectorIcons-tvOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = "libRNVectorIcons-tvOS.a"; sourceTree = ""; }; - E8E8CE89B3D142C6A8A56C34 /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; F6F53AFC25FB422485CB22D6 /* SystemConfiguration.framework */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; FC63C7054F1C4FDFB7A830E5 /* libRCTPrivacySnapshot.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTPrivacySnapshot.a; sourceTree = ""; }; FC98DC24A81A463AB8B2E6B1 /* libRNImagePicker.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNImagePicker.a; sourceTree = ""; }; FD7977067E1A496F94D8B1B7 /* libRNDeviceInfo.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNDeviceInfo.a; sourceTree = ""; }; - FF45EB303C9601ED114589A4 /* Pods-MarketWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MarketWidgetExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-MarketWidgetExtension/Pods-MarketWidgetExtension.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -310,9 +401,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 906451CAD44154C2950030EC /* libPods-BlueWallet.a in Frameworks */, 782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */, 764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */, + C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */, + 17CDA0718F42DB2CE856C872 /* libPods-BlueWallet.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -320,6 +412,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B4D59C1C2D8BAFE300B7025B /* Bugsnag in Frameworks */, + B4D59C1E2D8BAFE300B7025B /* BugsnagNetworkRequestPlugin in Frameworks */, + B41B76872B66B2FF002C48D5 /* BugsnagNetworkRequestPlugin in Frameworks */, + B41B76852B66B2FF002C48D5 /* Bugsnag in Frameworks */, + B4D59C1A2D8BAFE300B7025B /* EFQRCode in Frameworks */, + 6DFC807024EA0B6C007B8700 /* EFQRCode in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -339,22 +437,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - B40D4E39225841ED00428FCC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 6DFC807024EA0B6C007B8700 /* EFQRCode in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 00E356EF1AD99517003FC87E /* BlueWalletTests */ = { isa = PBXGroup; children = ( - 00E356F21AD99517003FC87E /* BlueWalletTests.m */, 00E356F01AD99517003FC87E /* Supporting Files */, + B49038D82B8FBAD300A8164A /* BlueWalletUITest.swift */, + B4EFF73A2C3F6C5E0095D655 /* MockData.swift */, ); path = BlueWalletTests; sourceTree = ""; @@ -370,20 +461,15 @@ 13B07FAE1A68108700A75B9A /* BlueWallet */ = { isa = PBXGroup; children = ( - B461B850299599F800E431AA /* AppDelegate.h */, - B461B851299599F800E431AA /* AppDelegate.mm */, + B461B851299599F800E431AA /* AppDelegate.swift */, 32C7944323B8879D00BE2AFA /* BlueWalletRelease.entitlements */, 32F0A2502310B0910095C559 /* BlueWallet.entitlements */, - 008F07F21AC5B25A0029DE68 /* main.jsbundle */, 13B07FB51A68108700A75B9A /* Images.xcassets */, 13B07FB61A68108700A75B9A /* Info.plist */, - 13B07FB71A68108700A75B9A /* main.m */, - 32B5A3292334450100F8D608 /* Bridge.swift */, - 32B5A3282334450100F8D608 /* BlueWallet-Bridging-Header.h */, 6DF25A9E249DB97E001D06F5 /* LaunchScreen.storyboard */, - 6D32C5C42596CE2F008C077C /* EventEmitter.h */, - 6D32C5C52596CE3A008C077C /* EventEmitter.m */, 84E05A832721191B001A0D3A /* Settings.bundle */, + B4742E962CCDBE8300380EEE /* Localizable.xcstrings */, + B48283572DA0DE02007EEC62 /* BlueWallet-Bridging-Header.h */, ); name = BlueWallet; sourceTree = ""; @@ -392,7 +478,6 @@ isa = PBXGroup; children = ( B43B69BA225C46D800925B1E /* libRCTLinking.a */, - B43B69B8225C462E00925B1E /* libPods-RCTLinking.a */, B43D046E22584C1B00FBAA95 /* libRNWatch.a */, ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED2971642150620600B7C4FE /* JavaScriptCore.framework */, @@ -401,12 +486,10 @@ F6F53AFC25FB422485CB22D6 /* SystemConfiguration.framework */, B9D9B3A7B2CB4255876B67AF /* libz.tbd */, 7B468CC34D5B41F3950078EF /* libsqlite3.0.tbd */, - 731973BA0AC6EA78962CE5B6 /* libPods-BlueWallet.a */, 3271B0AA236E2E0700DA766F /* NotificationCenter.framework */, 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */, 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */, - 98455D960744E4E5DD50BA87 /* libPods-WalletInformationAndMarketWidgetExtension.a */, - 41BD3AC9FD81723B68A63C12 /* libPods-MarketWidgetExtension.a */, + 040819EDF8BD9C50A9C83E24 /* libPods-BlueWallet.a */, ); name = Frameworks; sourceTree = ""; @@ -414,21 +497,6 @@ 4B0CACE36C3348E1BCEA92C8 /* Resources */ = { isa = PBXGroup; children = ( - C4496FB303574862B40A878A /* AntDesign.ttf */, - 44BC9E3EE0E9476A830CCCB9 /* Entypo.ttf */, - 3F7F1B8332C6439793D55B45 /* EvilIcons.ttf */, - A9166D490AEF4938BD6621CF /* Feather.ttf */, - 334051161886419EA186F4BA /* FontAwesome.ttf */, - 5A8F67CF29564E41882ECEF8 /* FontAwesome5_Brands.ttf */, - 47564776A7A3427DB36C087D /* FontAwesome5_Regular.ttf */, - 78A87E7251D94144A71A2F67 /* FontAwesome5_Solid.ttf */, - 04466491BA2D4876A71222FC /* Foundation.ttf */, - CA741BA794714D3F80251AC9 /* Ionicons.ttf */, - 2FCC2CD6FF4448229D0CE0F3 /* MaterialCommunityIcons.ttf */, - CF4A4D7AAD974D67A2D62B3E /* MaterialIcons.ttf */, - E8E8CE89B3D142C6A8A56C34 /* Octicons.ttf */, - 4D746BBE67E84684848246E2 /* SimpleLineIcons.ttf */, - 47C436B1EF23484B8181DBEA /* Zocial.ttf */, ); name = Resources; sourceTree = ""; @@ -445,7 +513,8 @@ 6D2AA8062568B8E50090B089 /* Fiat */ = { isa = PBXGroup; children = ( - 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */, + B440340E2BCC40A400162242 /* fiatUnits.json */, + B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */, 6D2AA8072568B8F40090B089 /* FiatUnit.swift */, ); path = Fiat; @@ -454,6 +523,11 @@ 6D6CA4BB255872E3009312A5 /* PriceWidget */ = { isa = PBXGroup; children = ( + B49A28BA2CD18999006B08E4 /* CompactPriceView.swift */, + B48630DF2CCEE7C800A8425C /* PriceWidgetEntryView.swift */, + B48630DC2CCEE7AC00A8425C /* PriceWidgetEntry.swift */, + B48630D52CCEE67100A8425C /* PriceWidgetProvider.swift */, + B48630D02CCEE3B300A8425C /* PriceIntent.swift */, 6D6CA4BC255872E3009312A5 /* PriceWidget.swift */, ); path = PriceWidget; @@ -478,6 +552,7 @@ 6DD4109F266CADF10087DE03 /* Widgets */ = { isa = PBXGroup; children = ( + B48630EB2CCEEEA700A8425C /* WalletAppShortcuts.swift */, 6DD410C3266CCB780087DE03 /* WidgetsExtension.entitlements */, 6DD410A0266CADF10087DE03 /* Widgets.swift */, 6DD410A4266CADF40087DE03 /* Info.plist */, @@ -503,16 +578,8 @@ 6DEB4BC1254FB98300E9F9AA /* Shared */ = { isa = PBXGroup; children = ( - 6D2AA8062568B8E50090B089 /* Fiat */, + B49A28BD2CD189B0006B08E4 /* FiatUnitEnum.swift */, 6DEB4DD82552260200E9F9AA /* Views */, - 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */, - 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */, - 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */, - 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */, - 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */, - 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */, - 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */, - 6D4AF18225D215D0009DD853 /* BlueWalletWatch-Bridging-Header.h */, B40FC3F829CCD1AC0007EBAC /* SwiftTCPClient.swift */, ); path = Shared; @@ -532,17 +599,24 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + B49D99932EFE2F3500A718AC /* NativeWidgetHelperSpec.h */, + B4F0A4A52FA1BC0000AAAA05 /* NativeEventEmitterSpec.h */, + B4F0A4A62FA1BC0000AAAA06 /* NativeMenuElementsEmitterSpec.h */, + B45010A12C1504E900619044 /* Components */, + B44033C82BCC34AC00162242 /* Shared */, + B41C2E552BB3DCB8000FE097 /* PrivacyInfo.xcprivacy */, + B4549F2E2B80FEA1002E3153 /* ci_scripts */, 13B07FAE1A68108700A75B9A /* BlueWallet */, 00E356EF1AD99517003FC87E /* BlueWalletTests */, B40D4E31225841EC00428FCC /* BlueWalletWatch */, - B40D4E40225841ED00428FCC /* BlueWalletWatch Extension */, 6D2A6462258BA92C0092292B /* Stickers */, 6DD4109F266CADF10087DE03 /* Widgets */, + B47B21EA2B2128B8001F6690 /* BlueWalletUITests */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, B40FE50A21FAD228005D5578 /* Recovered References */, 4B0CACE36C3348E1BCEA92C8 /* Resources */, - A9B365F08E5E8EADC056DBC4 /* Pods */, + FAA856B639C61E61D2CF90A8 /* Pods */, ); indentWidth = 2; sourceTree = ""; @@ -554,45 +628,19 @@ children = ( 13B07F961A680F5B00A75B9A /* BlueWallet.app */, B40D4E30225841EC00428FCC /* BlueWalletWatch.app */, - B40D4E3C225841ED00428FCC /* BlueWalletWatch Extension.appex */, 6D2A6461258BA92C0092292B /* Stickers.appex */, 6DD4109C266CADF10087DE03 /* WidgetsExtension.appex */, ); name = Products; sourceTree = ""; }; - A9B365F08E5E8EADC056DBC4 /* Pods */ = { - isa = PBXGroup; - children = ( - 9B3A324B70BC8C6D9314FD4F /* Pods-BlueWallet.debug.xcconfig */, - B459EE96941AE09BCB547DC0 /* Pods-BlueWallet.release.xcconfig */, - 367FA8CEB35BC9431019D98A /* Pods-MarketWidgetExtension.debug.xcconfig */, - FF45EB303C9601ED114589A4 /* Pods-MarketWidgetExtension.release.xcconfig */, - AA7DCFB2C7887DF26EDB5710 /* Pods-WalletInformationAndMarketWidgetExtension.debug.xcconfig */, - 7BAA8F97E61B677D33CF1944 /* Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; B40D4E31225841EC00428FCC /* BlueWalletWatch */ = { isa = PBXGroup; children = ( - 6D203C2025D4ED2500493AD1 /* BlueWalletWatch.entitlements */, - B40D4E32225841EC00428FCC /* Interface.storyboard */, - B40D4E35225841ED00428FCC /* Assets.xcassets */, - B40D4E37225841ED00428FCC /* Info.plist */, - ); - path = BlueWalletWatch; - sourceTree = ""; - }; - B40D4E40225841ED00428FCC /* BlueWalletWatch Extension */ = { - isa = PBXGroup; - children = ( - 32F0A24F2310B0700095C559 /* BlueWalletWatch Extension.entitlements */, + B4D59C262D8C5D6E00B7025B /* main.swift */, + B4D59C202D8BB41F00B7025B /* File.swift */, B43D03242258474500FBAA95 /* Objects */, - B40D4E672258426B00428FCC /* KeychainSwiftDistrib.swift */, 32F0A2992311DBB20095C559 /* ComplicationController.swift */, - B40D4E43225841ED00428FCC /* ExtensionDelegate.swift */, B40D4E45225841ED00428FCC /* NotificationController.swift */, B40D4E552258425400428FCC /* InterfaceController.swift */, B40D4E562258425400428FCC /* NumericKeypadInterfaceController.swift */, @@ -600,10 +648,15 @@ 6DFC807124EA2FA9007B8700 /* ViewQRCodefaceController.swift */, B40D4E582258425400428FCC /* SpecifyInterfaceController.swift */, B40D4E5C2258425500428FCC /* WalletDetailsInterfaceController.swift */, - B40D4E49225841ED00428FCC /* Info.plist */, B40D4E4A225841ED00428FCC /* PushNotificationPayload.apns */, + B4D0B2612C1DEA11006B6B1B /* ReceivePageInterfaceController.swift */, + 6D4AF18225D215D0009DD853 /* BlueWalletWatch-Bridging-Header.h */, + 6D203C2025D4ED2500493AD1 /* BlueWalletWatch.entitlements */, + B40D4E32225841EC00428FCC /* Interface.storyboard */, + B40D4E35225841ED00428FCC /* Assets.xcassets */, + B40D4E37225841ED00428FCC /* Info.plist */, ); - path = "BlueWalletWatch Extension"; + path = BlueWalletWatch; sourceTree = ""; }; B40FE50A21FAD228005D5578 /* Recovered References */ = { @@ -640,6 +693,8 @@ B43D03242258474500FBAA95 /* Objects */ = { isa = PBXGroup; children = ( + B4793DC02CEDACE700C92C2E /* WalletType.swift */, + B4793DBE2CEDACDA00C92C2E /* TransactionType.swift */, B43D0374225847C500FBAA95 /* Transaction.swift */, B43D0375225847C500FBAA95 /* TransactionTableRow.swift */, B43D0376225847C500FBAA95 /* Wallet.swift */, @@ -647,10 +702,90 @@ B43D0377225847C500FBAA95 /* WalletInformation.swift */, B43D0373225847C500FBAA95 /* WatchDataSource.swift */, 849047C92702A32A008EE567 /* Handoff.swift */, + B4D0B2632C1DEA99006B6B1B /* ReceiveType.swift */, + B4D0B2652C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift */, + B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */, ); path = Objects; sourceTree = ""; }; + B44033C82BCC34AC00162242 /* Shared */ = { + isa = PBXGroup; + children = ( + B4793DBA2CEDACBD00C92C2E /* Chain.swift */, + B450109A2C0FCD7E00619044 /* Utilities */, + 6D2AA8062568B8E50090B089 /* Fiat */, + 6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */, + 6D6CA5142558EBA3009312A5 /* MarketAPI+Electrum.swift */, + B44033C92BCC350A00162242 /* Currency.swift */, + 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */, + 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */, + 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */, + B44033D22BCC368800162242 /* UserDefaultsGroupKey.swift */, + B44033DC2BCC36C300162242 /* LatestTransaction.swift */, + B44033E32BCC36FF00162242 /* WalletData.swift */, + B44033E82BCC371A00162242 /* MarketData.swift */, + B44033ED2BCC374500162242 /* Numeric+abbreviated.swift */, + B44033F32BCC377F00162242 /* WidgetData.swift */, + B44033F82BCC379200162242 /* WidgetDataStore.swift */, + B44033FF2BCC37F800162242 /* Bundle+decode.swift */, + 6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */, + B44033BE2BCC32F800162242 /* BitcoinUnit.swift */, + B44033C32BCC332400162242 /* Balance.swift */, + ); + path = Shared; + sourceTree = ""; + }; + B450109A2C0FCD7E00619044 /* Utilities */ = { + isa = PBXGroup; + children = ( + B4793DC22CEDAD4400C92C2E /* KeychainHelper.swift */, + B450109B2C0FCD8A00619044 /* Utilities.swift */, + ); + path = Utilities; + sourceTree = ""; + }; + B45010A12C1504E900619044 /* Components */ = { + isa = PBXGroup; + children = ( + B4AA75232DAA339E00CF5CBE /* MenuElementsEmitter.m */, + B4B3EC232D69FF8700327F3D /* EventEmitter.swift */, + B4F0A4A32FA1BC0000AAAA02 /* EventEmitter.mm */, + B409AB052D71E07500BA06F8 /* MenuElementsEmitter.swift */, + B4B3EC202D69FF6C00327F3D /* SegmentedControlView.swift */, + B4B3EF002D6AFF6C003270A0 /* SegmentedControlManager.swift */, + B4B3EF1F2D6CFF6C003270A0 /* SegmentedControlBridge.m */, + B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */, + B4F0A4A12FA1BC0000AAAA00 /* WidgetHelper.mm */, + ); + path = Components; + sourceTree = ""; + }; + B4549F2E2B80FEA1002E3153 /* ci_scripts */ = { + isa = PBXGroup; + children = ( + B4549F352B82B10D002E3153 /* ci_post_clone.sh */, + ); + path = ci_scripts; + sourceTree = ""; + }; + B47B21EA2B2128B8001F6690 /* BlueWalletUITests */ = { + isa = PBXGroup; + children = ( + B47B21EB2B2128B8001F6690 /* BlueWalletUITests.swift */, + ); + path = BlueWalletUITests; + sourceTree = ""; + }; + FAA856B639C61E61D2CF90A8 /* Pods */ = { + isa = PBXGroup; + children = ( + 1AE7FA8B4A18928E917F42D1 /* Pods-BlueWallet.debug.xcconfig */, + 93D74F9C8EE7B4443A49594C /* Pods-BlueWallet.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -658,17 +793,17 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "BlueWallet" */; buildPhases = ( - 6F7747C31A9EE6DDC5108476 /* [CP] Check Pods Manifest.lock */, + 3B467A525D105B531AB91B81 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, B40D4E2D225841C300428FCC /* Embed Watch Content */, - 3271B0B6236E2E0700DA766F /* Embed App Extensions */, - C18D00A61007A84C9887DEDE /* [CP] Copy Pods Resources */, - 68CD4C52AC5B27E333599B5C /* [CP] Embed Pods Frameworks */, + 3271B0B6236E2E0700DA766F /* Embed Foundation Extensions */, A8D9893AE3CD454A9094B651 /* Upload source maps to Bugsnag */, 4B36CFF6FE55027DCA5CB6E1 /* Upload Bugsnag dSYM */, + BFE56A9A22A21E360BF7A1EC /* [CP] Embed Pods Frameworks */, + D0E81659D2FBFDD27024CF05 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -722,38 +857,17 @@ buildConfigurationList = B40D4E52225841ED00428FCC /* Build configuration list for PBXNativeTarget "BlueWalletWatch" */; buildPhases = ( B40D4E2E225841EC00428FCC /* Resources */, - B40D4E51225841ED00428FCC /* Embed App Extensions */, 421830728822A20A50D8A07C /* Frameworks */, + B40D4E38225841ED00428FCC /* Sources */, ); buildRules = ( ); dependencies = ( - B40D4E3F225841ED00428FCC /* PBXTargetDependency */, ); name = BlueWalletWatch; productName = BlueWalletWatch; productReference = B40D4E30225841EC00428FCC /* BlueWalletWatch.app */; - productType = "com.apple.product-type.application.watchapp2"; - }; - B40D4E3B225841ED00428FCC /* BlueWalletWatch Extension */ = { - isa = PBXNativeTarget; - buildConfigurationList = B40D4E4E225841ED00428FCC /* Build configuration list for PBXNativeTarget "BlueWalletWatch Extension" */; - buildPhases = ( - B40D4E38225841ED00428FCC /* Sources */, - B40D4E39225841ED00428FCC /* Frameworks */, - B40D4E3A225841ED00428FCC /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "BlueWalletWatch Extension"; - packageProductDependencies = ( - 6DFC806F24EA0B6C007B8700 /* EFQRCode */, - ); - productName = "BlueWalletWatch Extension"; - productReference = B40D4E3C225841ED00428FCC /* BlueWalletWatch Extension.appex */; - productType = "com.apple.product-type.watchkit2-extension"; + productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -761,8 +875,9 @@ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1250; - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1620; ORGANIZATIONNAME = BlueWallet; TargetAttributes = { 13B07F861A680F5B00A75B9A = { @@ -781,27 +896,15 @@ }; B40D4E2F225841EC00428FCC = { CreatedOnToolsVersion = 10.2; - DevelopmentTeam = A7W54YZ4WU; LastSwiftMigration = 1240; }; - B40D4E3B225841ED00428FCC = { - CreatedOnToolsVersion = 10.2; - DevelopmentTeam = A7W54YZ4WU; - LastSwiftMigration = 1130; - SystemCapabilities = { - com.apple.Keychain = { - enabled = 0; - }; - }; - }; }; }; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "BlueWallet" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + compatibilityVersion = "Xcode 15.3"; + developmentRegion = en_US; hasScannedForEncodings = 0; knownRegions = ( - English, en, Base, af, @@ -826,10 +929,14 @@ uk, tr, xh, + nb, + en_US, + "en-US", ); mainGroup = 83CBB9F61A601CBA00E9B192; packageReferences = ( 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */, + B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */, ); productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; projectDirPath = ""; @@ -837,7 +944,6 @@ targets = ( 13B07F861A680F5B00A75B9A /* BlueWallet */, B40D4E2F225841EC00428FCC /* BlueWalletWatch */, - B40D4E3B225841ED00428FCC /* BlueWalletWatch Extension */, 6D2A6460258BA92C0092292B /* Stickers */, 6DD4109B266CADF10087DE03 /* WidgetsExtension */, ); @@ -850,7 +956,11 @@ buildActionMask = 2147483647; files = ( 6DF25A9F249DB97E001D06F5 /* LaunchScreen.storyboard in Resources */, + B440340F2BCC40A400162242 /* fiatUnits.json in Resources */, 84E05A842721191B001A0D3A /* Settings.bundle in Resources */, + B4742E972CCDBE8300380EEE /* Localizable.xcstrings in Resources */, + B4549F362B82B10D002E3153 /* ci_post_clone.sh in Resources */, + B41C2E562BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -859,6 +969,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + B4742E9A2CCDBE8300380EEE /* Localizable.xcstrings in Resources */, 6D2A6464258BA92D0092292B /* Stickers.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -867,8 +978,10 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + B41C2E582BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */, + B44034112BCC40A400162242 /* fiatUnits.json in Resources */, + B4742E9B2CCDBE8300380EEE /* Localizable.xcstrings in Resources */, 6DD410B7266CAF5C0087DE03 /* Assets.xcassets in Resources */, - 6DD410AE266CAF1F0087DE03 /* fiatUnits.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -876,18 +989,13 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + B4742E982CCDBE8300380EEE /* Localizable.xcstrings in Resources */, + B44034102BCC40A400162242 /* fiatUnits.json in Resources */, + B4742E992CCDBE8300380EEE /* Localizable.xcstrings in Resources */, B40D4E36225841ED00428FCC /* Assets.xcassets in Resources */, - E5D4794C26781FC1007838C1 /* fiatUnits.json in Resources */, - B40D4E34225841EC00428FCC /* Interface.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B40D4E3A225841ED00428FCC /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( B4EE583C226703320003363C /* Assets.xcassets in Resources */, - E5D4794B26781FC0007838C1 /* fiatUnits.json in Resources */, + B41C2E572BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */, + B40D4E34225841EC00428FCC /* Interface.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -899,16 +1007,20 @@ buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( ); name = "Bundle React Native code and images"; + outputFileListPaths = ( + ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; + shellScript = "export EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=$(which node)\n../node_modules/react-native/scripts/react-native-xcode.sh\n\n"; }; - 4B36CFF6FE55027DCA5CB6E1 /* Upload Bugsnag dSYM */ = { + 3B467A525D105B531AB91B81 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -916,40 +1028,21 @@ inputFileListPaths = ( ); inputPaths = ( - "${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}", - "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Upload Bugsnag dSYM"; + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = "/usr/bin/env ruby"; - shellScript = "api_key = nil # Insert your key here to use it directly from this script\n\n# Attempt to get the API key from an environment variable\nunless api_key\n api_key = ENV[\"BUGSNAG_API_KEY\"]\n\n # If not present, attempt to lookup the value from the Info.plist\n unless api_key\n info_plist_path = \"#{ENV[\"BUILT_PRODUCTS_DIR\"]}/#{ENV[\"INFOPLIST_PATH\"]}\"\n plist_buddy_response = `/usr/libexec/PlistBuddy -c \"print :bugsnag:apiKey\" \"#{info_plist_path}\"`\n plist_buddy_response = `/usr/libexec/PlistBuddy -c \"print :BugsnagAPIKey\" \"#{info_plist_path}\"` if !$?.success?\n api_key = plist_buddy_response if $?.success?\n end\nend\n\nfail(\"No Bugsnag API key detected - add your key to your Info.plist, BUGSNAG_API_KEY environment variable or this Run Script phase\") unless api_key\n\nfork do\n Process.setsid\n STDIN.reopen(\"/dev/null\")\n STDOUT.reopen(\"/dev/null\", \"a\")\n STDERR.reopen(\"/dev/null\", \"a\")\n\n require 'shellwords'\n\n Dir[\"#{ENV[\"DWARF_DSYM_FOLDER_PATH\"]}/*/Contents/Resources/DWARF/*\"].each do |dsym|\n curl_command = \"curl --http1.1 -F dsym=@#{Shellwords.escape(dsym)} -F projectRoot=#{Shellwords.escape(ENV[\"PROJECT_DIR\"])} \"\n curl_command += \"-F apiKey=#{Shellwords.escape(api_key)} \"\n curl_command += \"https://upload.bugsnag.com/\"\n system(curl_command)\n end\nend\n"; - showEnvVarsInLog = 0; - }; - 68CD4C52AC5B27E333599B5C /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/rn-ldk/LDKFramework.framework/LDKFramework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LDKFramework.framework", + "$(DERIVED_FILE_DIR)/Pods-BlueWallet-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 6F7747C31A9EE6DDC5108476 /* [CP] Check Pods Manifest.lock */ = { + 4B36CFF6FE55027DCA5CB6E1 /* Upload Bugsnag dSYM */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -957,18 +1050,17 @@ inputFileListPaths = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", + "${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}", + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", ); - name = "[CP] Check Pods Manifest.lock"; + name = "Upload Bugsnag dSYM"; outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-BlueWallet-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellPath = "/usr/bin/env ruby"; + shellScript = "api_key = nil # Insert your key here to use it directly from this script\n\n# Attempt to get the API key from an environment variable\nunless api_key\n api_key = ENV[\"BUGSNAG_API_KEY\"]\n\n # If not present, attempt to lookup the value from the Info.plist\n unless api_key\n info_plist_path = \"#{ENV[\"BUILT_PRODUCTS_DIR\"]}/#{ENV[\"INFOPLIST_PATH\"]}\"\n plist_buddy_response = `/usr/libexec/PlistBuddy -c \"print :bugsnag:apiKey\" \"#{info_plist_path}\"`\n plist_buddy_response = `/usr/libexec/PlistBuddy -c \"print :BugsnagAPIKey\" \"#{info_plist_path}\"` if !$?.success?\n api_key = plist_buddy_response if $?.success?\n end\nend\n\nfail(\"No Bugsnag API key detected - add your key to your Info.plist, BUGSNAG_API_KEY environment variable or this Run Script phase\") unless api_key\n\nfork do\n Process.setsid\n STDIN.reopen(\"/dev/null\")\n STDOUT.reopen(\"/dev/null\", \"a\")\n STDERR.reopen(\"/dev/null\", \"a\")\n\n require 'shellwords'\n\n Dir[\"#{ENV[\"DWARF_DSYM_FOLDER_PATH\"]}/*/Contents/Resources/DWARF/*\"].each do |dsym|\n curl_command = \"curl --http1.1 -F dsym=@#{Shellwords.escape(dsym)} -F projectRoot=#{Shellwords.escape(ENV[\"PROJECT_DIR\"])} \"\n curl_command += \"-F apiKey=#{Shellwords.escape(api_key)} \"\n curl_command += \"https://upload.bugsnag.com/\"\n system(curl_command)\n end\nend\n\n"; showEnvVarsInLog = 0; }; A8D9893AE3CD454A9094B651 /* Upload source maps to Bugsnag */ = { @@ -976,63 +1068,34 @@ buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( ); name = "Upload source maps to Bugsnag"; + outputFileListPaths = ( + ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "SOURCE_MAP=\"$TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh"; + shellScript = "SOURCE_MAP=\"$TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh\n\n"; }; - C18D00A61007A84C9887DEDE /* [CP] Copy Pods Resources */ = { + BFE56A9A22A21E360BF7A1EC /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources.sh", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; CF0725821442A3000F20E874 /* Upload Bugsnag dSYM */ = { @@ -1053,7 +1116,24 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = "/usr/bin/env ruby"; - shellScript = "api_key = nil # Insert your key here to use it directly from this script\n\n# Attempt to get the API key from an environment variable\nunless api_key\n api_key = ENV[\"BUGSNAG_API_KEY\"]\n\n # If not present, attempt to lookup the value from the Info.plist\n unless api_key\n info_plist_path = \"#{ENV[\"BUILT_PRODUCTS_DIR\"]}/#{ENV[\"INFOPLIST_PATH\"]}\"\n plist_buddy_response = `/usr/libexec/PlistBuddy -c \"print :bugsnag:apiKey\" \"#{info_plist_path}\"`\n plist_buddy_response = `/usr/libexec/PlistBuddy -c \"print :BugsnagAPIKey\" \"#{info_plist_path}\"` if !$?.success?\n api_key = plist_buddy_response if $?.success?\n end\nend\n\nfail(\"No Bugsnag API key detected - add your key to your Info.plist, BUGSNAG_API_KEY environment variable or this Run Script phase\") unless api_key\n\nfork do\n Process.setsid\n STDIN.reopen(\"/dev/null\")\n STDOUT.reopen(\"/dev/null\", \"a\")\n STDERR.reopen(\"/dev/null\", \"a\")\n\n require 'shellwords'\n\n Dir[\"#{ENV[\"DWARF_DSYM_FOLDER_PATH\"]}/*/Contents/Resources/DWARF/*\"].each do |dsym|\n curl_command = \"curl --http1.1 -F dsym=@#{Shellwords.escape(dsym)} -F projectRoot=#{Shellwords.escape(ENV[\"PROJECT_DIR\"])} \"\n curl_command += \"-F apiKey=#{Shellwords.escape(api_key)} \"\n curl_command += \"https://upload.bugsnag.com/\"\n system(curl_command)\n end\nend\n"; + shellScript = "api_key = nil # Insert your key here to use it directly from this script\n\n# Attempt to get the API key from an environment variable\nunless api_key\n api_key = ENV[\"BUGSNAG_API_KEY\"]\n\n # If not present, attempt to lookup the value from the Info.plist\n unless api_key\n info_plist_path = \"#{ENV[\"BUILT_PRODUCTS_DIR\"]}/#{ENV[\"INFOPLIST_PATH\"]}\"\n plist_buddy_response = `/usr/libexec/PlistBuddy -c \"print :bugsnag:apiKey\" \"#{info_plist_path}\"`\n plist_buddy_response = `/usr/libexec/PlistBuddy -c \"print :BugsnagAPIKey\" \"#{info_plist_path}\"` if !$?.success?\n api_key = plist_buddy_response if $?.success?\n end\nend\n\nfail(\"No Bugsnag API key detected - add your key to your Info.plist, BUGSNAG_API_KEY environment variable or this Run Script phase\") unless api_key\n\nfork do\n Process.setsid\n STDIN.reopen(\"/dev/null\")\n STDOUT.reopen(\"/dev/null\", \"a\")\n STDERR.reopen(\"/dev/null\", \"a\")\n\n require 'shellwords'\n\n Dir[\"#{ENV[\"DWARF_DSYM_FOLDER_PATH\"]}/*/Contents/Resources/DWARF/*\"].each do |dsym|\n curl_command = \"curl --http1.1 -F dsym=@#{Shellwords.escape(dsym)} -F projectRoot=#{Shellwords.escape(ENV[\"PROJECT_DIR\"])} \"\n curl_command += \"-F apiKey=#{Shellwords.escape(api_key)} \"\n curl_command += \"https://upload.bugsnag.com/\"\n system(curl_command)\n end\nend\n\n"; + showEnvVarsInLog = 0; + }; + D0E81659D2FBFDD27024CF05 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -1063,10 +1143,46 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */, - 13B07FC11A68108700A75B9A /* main.m in Sources */, - B461B852299599F800E431AA /* AppDelegate.mm in Sources */, - 32B5A32A2334450100F8D608 /* Bridge.swift in Sources */, + B44033CA2BCC350A00162242 /* Currency.swift in Sources */, + B44033EE2BCC374500162242 /* Numeric+abbreviated.swift in Sources */, + B48630E82CCEE92400A8425C /* PriceWidget.swift in Sources */, + B44033DD2BCC36C300162242 /* LatestTransaction.swift in Sources */, + B44033FE2BCC37D700162242 /* MarketAPI.swift in Sources */, + B450109C2C0FCD8A00619044 /* Utilities.swift in Sources */, + B48630E52CCEE8B800A8425C /* PriceView.swift in Sources */, + B48630E72CCEE91900A8425C /* PriceWidgetProvider.swift in Sources */, + B4B3EC252D69FF8700327F3D /* EventEmitter.swift in Sources */, + B4F0A4A42FA1BC0000AAAA03 /* EventEmitter.mm in Sources */, + B48630ED2CCEEEB000A8425C /* WalletAppShortcuts.swift in Sources */, + B409AB062D71E07500BA06F8 /* MenuElementsEmitter.swift in Sources */, + B44033CE2BCC352900162242 /* UserDefaultsGroup.swift in Sources */, + B461B852299599F800E431AA /* AppDelegate.swift in Sources */, + B44033F42BCC377F00162242 /* WidgetData.swift in Sources */, + B49A28C52CD1A894006B08E4 /* MarketData.swift in Sources */, + B4AA75242DAA339E00CF5CBE /* MenuElementsEmitter.m in Sources */, + B49A28BF2CD18A9A006B08E4 /* FiatUnitEnum.swift in Sources */, + B44033C42BCC332400162242 /* Balance.swift in Sources */, + B48630EE2CCEEEE900A8425C /* PriceIntent.swift in Sources */, + B44034072BCC38A000162242 /* FiatUnit.swift in Sources */, + B44034002BCC37F800162242 /* Bundle+decode.swift in Sources */, + B44033E22BCC36CB00162242 /* Placeholders.swift in Sources */, + B4793DBB2CEDACBD00C92C2E /* Chain.swift in Sources */, + B4B3EC222D69FF6C00327F3D /* SegmentedControlView.swift in Sources */, + B4B3EF102D6AFF6C003270A0 /* SegmentedControlManager.swift in Sources */, + B4B3EF202D6CFF6C003270A0 /* SegmentedControlBridge.m in Sources */, + B4B1A4622BFA73110072E3BB /* WidgetHelper.swift in Sources */, + B4F0A4A22FA1BC0000AAAA01 /* WidgetHelper.mm in Sources */, + B48630E12CCEE7C800A8425C /* PriceWidgetEntryView.swift in Sources */, + B44033DA2BCC369A00162242 /* Colors.swift in Sources */, + B44033D32BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */, + B49A28BC2CD18999006B08E4 /* CompactPriceView.swift in Sources */, + B44033D82BCC369500162242 /* UserDefaultsExtension.swift in Sources */, + B44033E42BCC36FF00162242 /* WalletData.swift in Sources */, + B4793DC52CEDAD4400C92C2E /* KeychainHelper.swift in Sources */, + B44033BF2BCC32F800162242 /* BitcoinUnit.swift in Sources */, + B44034052BCC389200162242 /* XMLParserDelegate.swift in Sources */, + B48630DD2CCEE7AC00A8425C /* PriceWidgetEntry.swift in Sources */, + B44033F92BCC379200162242 /* WidgetDataStore.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1075,23 +1191,45 @@ buildActionMask = 2147483647; files = ( 6DD410BE266CAF5C0087DE03 /* SendReceiveButtons.swift in Sources */, - 6DD410B4266CAF5C0087DE03 /* WidgetAPI.swift in Sources */, + B48630E02CCEE7C800A8425C /* PriceWidgetEntryView.swift in Sources */, + 6DD410B4266CAF5C0087DE03 /* MarketAPI.swift in Sources */, + B4793DC32CEDAD4400C92C2E /* KeychainHelper.swift in Sources */, B40FC3FA29CCD1D00007EBAC /* SwiftTCPClient.swift in Sources */, + B48630EC2CCEEEA700A8425C /* WalletAppShortcuts.swift in Sources */, 6DD410A1266CADF10087DE03 /* Widgets.swift in Sources */, + B450109D2C0FCD9F00619044 /* Utilities.swift in Sources */, + B49A28BE2CD189B0006B08E4 /* FiatUnitEnum.swift in Sources */, 6DD410AC266CAE470087DE03 /* PriceWidget.swift in Sources */, + B4B1A4642BFA73110072E3BB /* WidgetHelper.swift in Sources */, + B44033D52BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */, 6DD410B2266CAF5C0087DE03 /* WalletInformationView.swift in Sources */, + B44034022BCC37F800162242 /* Bundle+decode.swift in Sources */, + B48630D62CCEE67100A8425C /* PriceWidgetProvider.swift in Sources */, + B44033CC2BCC350A00162242 /* Currency.swift in Sources */, 6DD410B6266CAF5C0087DE03 /* PriceView.swift in Sources */, + B48630DE2CCEE7AC00A8425C /* PriceWidgetEntry.swift in Sources */, + B49A28BB2CD18999006B08E4 /* CompactPriceView.swift in Sources */, 6DD410B3266CAF5C0087DE03 /* Colors.swift in Sources */, + B44033C12BCC32F800162242 /* BitcoinUnit.swift in Sources */, 6DD410BB266CAF5C0087DE03 /* MarketView.swift in Sources */, - 6DD410B5266CAF5C0087DE03 /* WidgetDataStore.swift in Sources */, + B44033F02BCC374500162242 /* Numeric+abbreviated.swift in Sources */, + B44033DF2BCC36C300162242 /* LatestTransaction.swift in Sources */, 6DD410C0266CB1460087DE03 /* MarketWidget.swift in Sources */, + B4793DBC2CEDACBD00C92C2E /* Chain.swift in Sources */, + B4AB225E2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */, + B44033F62BCC377F00162242 /* WidgetData.swift in Sources */, 6DD410BA266CAF5C0087DE03 /* FiatUnit.swift in Sources */, + B44033FB2BCC379200162242 /* WidgetDataStore.swift in Sources */, + B44033EB2BCC371A00162242 /* MarketData.swift in Sources */, 6DD410AF266CAF5C0087DE03 /* WalletInformationAndMarketWidget.swift in Sources */, - 6DD410BF266CB13D0087DE03 /* Models.swift in Sources */, + B44033C62BCC332400162242 /* Balance.swift in Sources */, + B44033E62BCC36FF00162242 /* WalletData.swift in Sources */, + 6DD410BF266CB13D0087DE03 /* Placeholders.swift in Sources */, 6DD410B0266CAF5C0087DE03 /* WalletInformationWidget.swift in Sources */, - 6DD410B1266CAF5C0087DE03 /* WidgetAPI+Electrum.swift in Sources */, + 6DD410B1266CAF5C0087DE03 /* MarketAPI+Electrum.swift in Sources */, 6DD410B9266CAF5C0087DE03 /* UserDefaultsGroup.swift in Sources */, 6DD410B8266CAF5C0087DE03 /* UserDefaultsExtension.swift in Sources */, + B48630EA2CCEED8400A8425C /* PriceIntent.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1104,23 +1242,45 @@ B43D037A225847C500FBAA95 /* Transaction.swift in Sources */, 32F0A29A2311DBB20095C559 /* ComplicationController.swift in Sources */, B40D4E602258425500428FCC /* SpecifyInterfaceController.swift in Sources */, + B4D0B2642C1DEA99006B6B1B /* ReceiveType.swift in Sources */, B43D0379225847C500FBAA95 /* WatchDataSource.swift in Sources */, + B450109F2C0FCDA500619044 /* Utilities.swift in Sources */, + B44033D42BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */, + B4D59C212D8BB42100B7025B /* File.swift in Sources */, + B44034012BCC37F800162242 /* Bundle+decode.swift in Sources */, + B4D0B2682C1DED67006B6B1B /* ReceiveMethod.swift in Sources */, 849047CA2702A32A008EE567 /* Handoff.swift in Sources */, - 6D4AF16325D21185009DD853 /* WidgetDataStore.swift in Sources */, + B44033EA2BCC371A00162242 /* MarketData.swift in Sources */, + B44033CB2BCC350A00162242 /* Currency.swift in Sources */, 6DFC807224EA2FA9007B8700 /* ViewQRCodefaceController.swift in Sources */, B40D4E46225841ED00428FCC /* NotificationController.swift in Sources */, B40D4E5D2258425500428FCC /* InterfaceController.swift in Sources */, + B4D59C272D8C5D6F00B7025B /* main.swift in Sources */, + B4793DBD2CEDACBD00C92C2E /* Chain.swift in Sources */, + B44033FA2BCC379200162242 /* WidgetDataStore.swift in Sources */, + B44033DE2BCC36C300162242 /* LatestTransaction.swift in Sources */, + B4D0B2662C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift in Sources */, B43D037B225847C500FBAA95 /* TransactionTableRow.swift in Sources */, B43D037D225847C500FBAA95 /* WalletInformation.swift in Sources */, - 6D4AF15925D21172009DD853 /* WidgetAPI.swift in Sources */, + 6D4AF15925D21172009DD853 /* MarketAPI.swift in Sources */, B40D4E642258425500428FCC /* WalletDetailsInterfaceController.swift in Sources */, - B40D4E44225841ED00428FCC /* ExtensionDelegate.swift in Sources */, - B40D4E682258426B00428FCC /* KeychainSwiftDistrib.swift in Sources */, - 6D4AF16D25D21192009DD853 /* Models.swift in Sources */, + 6D4AF16D25D21192009DD853 /* Placeholders.swift in Sources */, + B4793DC42CEDAD4400C92C2E /* KeychainHelper.swift in Sources */, B40D4E632258425500428FCC /* ReceiveInterfaceController.swift in Sources */, B43D0378225847C500FBAA95 /* WalletGradient.swift in Sources */, + B4EFF73B2C3F6C5E0095D655 /* MockData.swift in Sources */, + B44033C02BCC32F800162242 /* BitcoinUnit.swift in Sources */, + B44033E52BCC36FF00162242 /* WalletData.swift in Sources */, + B44033EF2BCC374500162242 /* Numeric+abbreviated.swift in Sources */, + B4793DC12CEDACE700C92C2E /* WalletType.swift in Sources */, + B4793DBF2CEDACDA00C92C2E /* TransactionType.swift in Sources */, + B4D0B2622C1DEA11006B6B1B /* ReceivePageInterfaceController.swift in Sources */, + B44033D02BCC352F00162242 /* UserDefaultsGroup.swift in Sources */, + B44033C52BCC332400162242 /* Balance.swift in Sources */, 6D4AF18425D215D1009DD853 /* UserDefaultsExtension.swift in Sources */, + B4AB225D2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */, B40D4E5E2258425500428FCC /* NumericKeypadInterfaceController.swift in Sources */, + B44033F52BCC377F00162242 /* WidgetData.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1129,7 +1289,6 @@ /* Begin PBXTargetDependency section */ 6D2A6467258BA92D0092292B /* PBXTargetDependency */ = { isa = PBXTargetDependency; - platformFilter = ios; target = 6D2A6460258BA92C0092292B /* Stickers */; targetProxy = 6D2A6466258BA92D0092292B /* PBXContainerItemProxy */; }; @@ -1142,11 +1301,6 @@ target = 6DD4109B266CADF10087DE03 /* WidgetsExtension */; targetProxy = 6DD410A5266CADF40087DE03 /* PBXContainerItemProxy */; }; - B40D4E3F225841ED00428FCC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = B40D4E3B225841ED00428FCC /* BlueWalletWatch Extension */; - targetProxy = B40D4E3E225841ED00428FCC /* PBXContainerItemProxy */; - }; B40D4E4C225841ED00428FCC /* PBXTargetDependency */ = { isa = PBXTargetDependency; platformFilter = ios; @@ -1182,6 +1336,8 @@ 6D294A9924D512690039E22B /* uk */, 6D294A9B24D512770039E22B /* tr */, 6D294A9D24D5127F0039E22B /* xh */, + B4B31A352C77BBA000663334 /* nb */, + B4742E9C2CCDC31300380EEE /* en_US */, ); name = Interface.storyboard; sourceTree = ""; @@ -1191,16 +1347,20 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9B3A324B70BC8C6D9314FD4F /* Pods-BlueWallet.debug.xcconfig */; + baseConfigurationReference = 1AE7FA8B4A18928E917F42D1 /* Pods-BlueWallet.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWallet.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 751; - DEAD_CODE_STRIPPING = NO; - DEVELOPMENT_TEAM = A7W54YZ4WU; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1703259999; + DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; + "DEVELOPMENT_TEAM[sdk=macosx*]" = A7W54YZ4WU; ENABLE_BITCODE = NO; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -1210,76 +1370,95 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = BlueWallet/Info.plist; + INFOPLIST_KEY_CLKComplicationPrincipalClass = "$(PRODUCT_BUNDLE_IDENTIFIER).ComplicationController"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = io.bluewallet.bluewallet; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = ( "$(SDKROOT)/usr/lib/swift", + "$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(inherited)", - "$(PROJECT_DIR)", ); - MARKETING_VERSION = 6.4.9; + MARKETING_VERSION = 8.0.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", "-lc++", ); + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet; PRODUCT_NAME = BlueWallet; PROVISIONING_PROFILE_SPECIFIER = ""; - SUPPORTS_MACCATALYST = "$(OVERRIDE_SUPPORTS_MACCATALYST:default=YES)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore io.bluewallet.bluewallet"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development io.bluewallet.bluewallet catalyst"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,6"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B459EE96941AE09BCB547DC0 /* Pods-BlueWallet.release.xcconfig */; + baseConfigurationReference = 93D74F9C8EE7B4443A49594C /* Pods-BlueWallet.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWalletRelease.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 751; - DEVELOPMENT_TEAM = A7W54YZ4WU; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1703259999; + DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; + "DEVELOPMENT_TEAM[sdk=macosx*]" = A7W54YZ4WU; ENABLE_BITCODE = NO; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = BlueWallet/Info.plist; + INFOPLIST_KEY_CLKComplicationPrincipalClass = "$(PRODUCT_BUNDLE_IDENTIFIER).ComplicationController"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = io.bluewallet.bluewallet; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = ( "$(SDKROOT)/usr/lib/swift", + "$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(inherited)", - "$(PROJECT_DIR)", ); - MARKETING_VERSION = 6.4.9; + MARKETING_VERSION = 8.0.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", "-lc++", ); + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet; PRODUCT_NAME = BlueWallet; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; - SUPPORTS_MACCATALYST = "$(OVERRIDE_SUPPORTS_MACCATALYST:default=YES)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore io.bluewallet.bluewallet"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match AppStore io.bluewallet.bluewallet catalyst"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h"; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,6"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -1293,23 +1472,35 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 751; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1703259999; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = A7W54YZ4WU; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Stickers/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MARKETING_VERSION = 6.4.9; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "$(SDKROOT)/System/iOSSupport/usr/lib/swift", + "$(inherited)", + ); + MARKETING_VERSION = 8.0.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development io.bluewallet.bluewallet.Stickers"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -1323,24 +1514,35 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 751; + CURRENT_PROJECT_VERSION = 1703259999; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = A7W54YZ4WU; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Stickers/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MARKETING_VERSION = 6.4.9; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "$(SDKROOT)/System/iOSSupport/usr/lib/swift", + "$(inherited)", + ); + MARKETING_VERSION = 8.0.0; MTL_FAST_MATH = YES; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore io.bluewallet.bluewallet.Stickers"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -1355,36 +1557,53 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = WidgetsExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 751; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1703259999; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = A7W54YZ4WU; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; + "DEVELOPMENT_TEAM[sdk=macosx*]" = A7W54YZ4WU; + "DEVELOPMENT_TEAM[sdk=watchos*]" = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.4.9; + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "$(SDKROOT)/System/iOSSupport/usr/lib/swift", + "$(inherited)", + ); + MARKETING_VERSION = 8.0.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development io.bluewallet.bluewallet.MarketWidget"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development io.bluewallet.bluewallet.MarketWidget catalyst"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=watchos*]" = "match Development io.bluewallet.bluewallet.MarketWidget"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator"; SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,6"; + TARGETED_DEVICE_FAMILY = "1,2,4,6"; + TVOS_DEPLOYMENT_TARGET = 15.6; + WATCHOS_DEPLOYMENT_TARGET = 9.6; }; name = Debug; }; @@ -1398,35 +1617,52 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = WidgetsExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 751; + CURRENT_PROJECT_VERSION = 1703259999; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = A7W54YZ4WU; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; + "DEVELOPMENT_TEAM[sdk=macosx*]" = A7W54YZ4WU; + "DEVELOPMENT_TEAM[sdk=watchos*]" = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.4.9; + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "$(SDKROOT)/System/iOSSupport/usr/lib/swift", + "$(inherited)", + ); + MARKETING_VERSION = 8.0.0; MTL_FAST_MATH = YES; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore io.bluewallet.bluewallet.MarketWidget"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match AppStore io.bluewallet.bluewallet.MarketWidget catalyst"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=watchos*]" = "match AppStore io.bluewallet.bluewallet.MarketWidget"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator"; SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,6"; + TARGETED_DEVICE_FAMILY = "1,2,4,6"; + TVOS_DEPLOYMENT_TARGET = 15.6; + WATCHOS_DEPLOYMENT_TARGET = 9.6; }; name = Release; }; @@ -1434,8 +1670,9 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CC = ""; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -1460,6 +1697,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + CXX = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; @@ -1470,6 +1708,7 @@ GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", + _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION, ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1478,13 +1717,29 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.2; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LD = ""; + LDPLUSPLUS = ""; LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + OTHER_LDFLAGS = "$(inherited)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; - SWIFT_VERSION = 4.2; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; + SWIFT_VERSION = 5.0; + USE_HERMES = true; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Debug; }; @@ -1492,8 +1747,9 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CC = ""; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -1518,103 +1774,45 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; + CXX = ""; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION, + ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.2; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LD = ""; + LDPLUSPLUS = ""; LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = NO; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_VERSION = 4.2; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - B40D4E4F225841ED00428FCC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = "BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 751; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = ""; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "BlueWalletWatch Extension/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( + OTHER_CFLAGS = ( "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", + "-DRCT_REMOVE_LEGACY_ARCH=1", ); - MARKETING_VERSION = 6.4.9; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension; - PRODUCT_NAME = "${TARGET_NAME}"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = watchos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 5.0; - }; - name = Debug; - }; - B40D4E50225841ED00428FCC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = "BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 751; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "BlueWalletWatch Extension/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( + OTHER_CPLUSPLUSFLAGS = ( "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", + "-DRCT_REMOVE_LEGACY_ARCH=1", ); - MARKETING_VERSION = 6.4.9; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension; - PRODUCT_NAME = "${TARGET_NAME}"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = watchos; - SKIP_INSTALL = YES; + OTHER_LDFLAGS = "$(inherited)"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 5.0; + USE_HERMES = true; + VALIDATE_PRODUCT = YES; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Release; }; @@ -1622,6 +1820,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -1631,27 +1830,46 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = BlueWalletWatch/BlueWalletWatch.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 751; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1703259999; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=watchos*]" = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; - IBSC_MODULE = BlueWalletWatch_Extension; INFOPLIST_FILE = BlueWalletWatch/Info.plist; - MARKETING_VERSION = 6.4.9; + INFOPLIST_KEY_CLKComplicationPrincipalClass = io.bluewallet.bluewallet.watch.extension.ComplicationController; + INFOPLIST_KEY_UIMainStoryboardFile = Interface; + INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = io.bluewallet.bluewallet; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "$(SDKROOT)/System/iOSSupport/usr/lib/swift", + "$(inherited)", + ); + MACOSX_DEPLOYMENT_TARGET = 12.4; + MARKETING_VERSION = 8.0.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=watchos*]" = "match Development io.bluewallet.bluewallet.watch"; SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OBJC_BRIDGING_HEADER = "WalletInformationWidget/Widgets/Shared/BlueWalletWatch-Bridging-Header.h"; + SWIFT_OBJC_BRIDGING_HEADER = "BlueWalletWatch/BlueWalletWatch-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 5.0; + WATCHOS_DEPLOYMENT_TARGET = 11.0; }; name = Debug; }; @@ -1659,6 +1877,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -1668,27 +1887,46 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = BlueWalletWatch/BlueWalletWatch.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 751; + CURRENT_PROJECT_VERSION = 1703259999; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=watchos*]" = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; - IBSC_MODULE = BlueWalletWatch_Extension; INFOPLIST_FILE = BlueWalletWatch/Info.plist; - MARKETING_VERSION = 6.4.9; + INFOPLIST_KEY_CLKComplicationPrincipalClass = io.bluewallet.bluewallet.watch.extension.ComplicationController; + INFOPLIST_KEY_UIMainStoryboardFile = Interface; + INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = io.bluewallet.bluewallet; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "$(SDKROOT)/System/iOSSupport/usr/lib/swift", + "$(inherited)", + ); + MACOSX_DEPLOYMENT_TARGET = 12.4; + MARKETING_VERSION = 8.0.0; MTL_FAST_MATH = YES; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=watchos*]" = "match AppStore io.bluewallet.bluewallet.watch"; SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OBJC_BRIDGING_HEADER = "WalletInformationWidget/Widgets/Shared/BlueWalletWatch-Bridging-Header.h"; + SWIFT_OBJC_BRIDGING_HEADER = "BlueWalletWatch/BlueWalletWatch-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 5.0; + WATCHOS_DEPLOYMENT_TARGET = 11.0; }; name = Release; }; @@ -1731,15 +1969,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - B40D4E4E225841ED00428FCC /* Build configuration list for PBXNativeTarget "BlueWalletWatch Extension" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B40D4E4F225841ED00428FCC /* Debug */, - B40D4E50225841ED00428FCC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; B40D4E52225841ED00428FCC /* Build configuration list for PBXNativeTarget "BlueWalletWatch" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1756,8 +1985,16 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/EFPrefix/EFQRCode.git"; requirement = { - kind = exactVersion; - version = 6.2.2; + kind = upToNextMajorVersion; + minimumVersion = 6.2.2; + }; + }; + B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/bugsnag/bugsnag-cocoa"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 6.28.1; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -1768,6 +2005,31 @@ package = 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */; productName = EFQRCode; }; + B41B76842B66B2FF002C48D5 /* Bugsnag */ = { + isa = XCSwiftPackageProductDependency; + package = B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */; + productName = Bugsnag; + }; + B41B76862B66B2FF002C48D5 /* BugsnagNetworkRequestPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */; + productName = BugsnagNetworkRequestPlugin; + }; + B4D59C192D8BAFE300B7025B /* EFQRCode */ = { + isa = XCSwiftPackageProductDependency; + package = 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */; + productName = EFQRCode; + }; + B4D59C1B2D8BAFE300B7025B /* Bugsnag */ = { + isa = XCSwiftPackageProductDependency; + package = B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */; + productName = Bugsnag; + }; + B4D59C1D2D8BAFE300B7025B /* BugsnagNetworkRequestPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */; + productName = BugsnagNetworkRequestPlugin; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; diff --git a/ios/BlueWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/BlueWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000000..23bedbaa34e --- /dev/null +++ b/ios/BlueWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,33 @@ +{ + "originHash" : "89509f555bc90a15b96ca0a326a69850770bdaac04a46f9cf482d81533702e3c", + "pins" : [ + { + "identity" : "bugsnag-cocoa", + "kind" : "remoteSourceControl", + "location" : "https://github.com/bugsnag/bugsnag-cocoa", + "state" : { + "revision" : "16b9145fc66e5296f16e733f6feb5d0e450574e8", + "version" : "6.28.1" + } + }, + { + "identity" : "efqrcode", + "kind" : "remoteSourceControl", + "location" : "https://github.com/EFPrefix/EFQRCode.git", + "state" : { + "revision" : "2991c2f318ad9529d93b2a73a382a3f9c72c64ce", + "version" : "6.2.2" + } + }, + { + "identity" : "swift_qrcodejs", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ApolloZhu/swift_qrcodejs.git", + "state" : { + "revision" : "374dc7f7b9e76c6aeb393f6a84590c6d387e1ecb", + "version" : "2.2.2" + } + } + ], + "version" : 3 +} diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet-tvOS.xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet-tvOS.xcscheme deleted file mode 100644 index 147e3b955e8..00000000000 --- a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet-tvOS.xcscheme +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet.xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet.xcscheme index ea2ea41c304..81cc58e2ed9 100644 --- a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet.xcscheme +++ b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet.xcscheme @@ -1,49 +1,25 @@ - - - - - - - - - - + skipped = "NO" + parallelizable = "YES"> @@ -69,13 +45,6 @@ ReferencedContainer = "container:BlueWallet.xcodeproj"> - - - - - + - + diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWalletWatch (Notification).xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWalletWatch (Notification).xcscheme deleted file mode 100644 index ce67c26c8a4..00000000000 --- a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWalletWatch (Notification).xcscheme +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWalletWatch.xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWalletWatch.xcscheme index 772e95fd06f..10423b80861 100644 --- a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWalletWatch.xcscheme +++ b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWalletWatch.xcscheme @@ -1,7 +1,7 @@ + LastUpgradeVersion = "1620" + version = "1.7"> @@ -40,17 +40,20 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> + + + + + notificationPayloadFile = "BlueWalletWatch/PushNotificationPayload.apns"> + debugDocumentVersioning = "YES" + notificationPayloadFile = "BlueWalletWatch/PushNotificationPayload.apns"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/PriceWidget.xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/PriceWidget.xcscheme new file mode 100644 index 00000000000..35e5cca60be --- /dev/null +++ b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/PriceWidget.xcscheme @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/Stickers.xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/Stickers.xcscheme index 7646ef3c3c9..4f3b7c4da90 100644 --- a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/Stickers.xcscheme +++ b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/Stickers.xcscheme @@ -1,7 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> + + + + diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WalletInformationAndMarketWidget.xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WalletInformationAndMarketWidget.xcscheme new file mode 100644 index 00000000000..e9daaeba9c2 --- /dev/null +++ b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WalletInformationAndMarketWidget.xcscheme @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WalletInformationWidget.xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WalletInformationWidget.xcscheme new file mode 100644 index 00000000000..7cd33822a23 --- /dev/null +++ b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WalletInformationWidget.xcscheme @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/BlueWallet.xcodeproj/xcuserdata/marcosrodriguez.xcuserdatad/xcschemes/xcschememanagement.plist b/ios/BlueWallet.xcodeproj/xcuserdata/marcosrodriguez.xcuserdatad/xcschemes/xcschememanagement.plist index f35b236dc92..bea36f0e869 100644 --- a/ios/BlueWallet.xcodeproj/xcuserdata/marcosrodriguez.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/ios/BlueWallet.xcodeproj/xcuserdata/marcosrodriguez.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,6 +4,11 @@ SchemeUserState + BlueWallet copy.xcscheme_^#shared#^_ + + orderHint + 113 + BlueWallet for Apple Watch (Notification).xcscheme_^#shared#^_ orderHint @@ -14,15 +19,15 @@ orderHint 71 - BlueWallet-tvOS.xcscheme_^#shared#^_ + BlueWallet.xcscheme_^#shared#^_ orderHint 0 - BlueWallet.xcscheme_^#shared#^_ + BlueWalletWatch (Complication).xcscheme orderHint - 1 + 9 BlueWalletWatch (Glance).xcscheme_^#shared#^_ @@ -32,22 +37,37 @@ BlueWalletWatch (Notification).xcscheme_^#shared#^_ orderHint - 3 + 7 BlueWalletWatch.xcscheme_^#shared#^_ orderHint - 2 + 6 - Stickers.xcscheme_^#shared#^_ + MarketWidget.xcscheme_^#shared#^_ orderHint 4 - WidgetsExtension.xcscheme_^#shared#^_ + PriceWidget.xcscheme_^#shared#^_ + + orderHint + 5 + + Stickers.xcscheme_^#shared#^_ + + orderHint + 1 + + WalletInformationAndMarketWidget.xcscheme_^#shared#^_ + + orderHint + 2 + + WalletInformationWidget.xcscheme_^#shared#^_ orderHint - 144 + 3 SuppressBuildableAutocreation @@ -72,6 +92,16 @@ primary + B47B21E82B2128B8001F6690 + + primary + + + B4A29A212B55C990002A67DF + + primary + + diff --git a/ios/BlueWallet.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/BlueWallet.xcworkspace/xcshareddata/swiftpm/Package.resolved index f95d1c169d3..23bedbaa34e 100644 --- a/ios/BlueWallet.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/BlueWallet.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,15 @@ { + "originHash" : "89509f555bc90a15b96ca0a326a69850770bdaac04a46f9cf482d81533702e3c", "pins" : [ + { + "identity" : "bugsnag-cocoa", + "kind" : "remoteSourceControl", + "location" : "https://github.com/bugsnag/bugsnag-cocoa", + "state" : { + "revision" : "16b9145fc66e5296f16e733f6feb5d0e450574e8", + "version" : "6.28.1" + } + }, { "identity" : "efqrcode", "kind" : "remoteSourceControl", @@ -19,5 +29,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/ios/BlueWallet/AppDelegate.h b/ios/BlueWallet/AppDelegate.h deleted file mode 100644 index 5d2808256ca..00000000000 --- a/ios/BlueWallet/AppDelegate.h +++ /dev/null @@ -1,6 +0,0 @@ -#import -#import - -@interface AppDelegate : RCTAppDelegate - -@end diff --git a/ios/BlueWallet/AppDelegate.mm b/ios/BlueWallet/AppDelegate.mm deleted file mode 100644 index e660034cf2a..00000000000 --- a/ios/BlueWallet/AppDelegate.mm +++ /dev/null @@ -1,178 +0,0 @@ -#import -#import "AppDelegate.h" - -#import -#import -#import -#import -#import "RNQuickActionManager.h" -#import -#import -#import "EventEmitter.h" -#import -#import - -@interface AppDelegate() - -@end - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - [Bugsnag start]; - [self copyDeviceUID]; - [[NSUserDefaults standardUserDefaults] addObserver:self - forKeyPath:@"deviceUID" - options:NSKeyValueObservingOptionNew - context:NULL]; - [[NSUserDefaults standardUserDefaults] addObserver:self - forKeyPath:@"deviceUIDCopy" - options:NSKeyValueObservingOptionNew - context:NULL]; - self.moduleName = @"BlueWallet"; - // You can add your custom initial props in the dictionary below. - // They will be passed down to the ViewController used by React Native. - self.initialProps = @{}; - - [[RCTI18nUtil sharedInstance] allowRTL:YES]; - - UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - center.delegate = self; - - return [super application:application didFinishLaunchingWithOptions:launchOptions]; -} - -- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge -{ -#if DEBUG - return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; -#else - return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; -#endif -} - -/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off. -/// -/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html -/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture). -/// @return: `true` if the `concurrentRoot` feature is enabled. Otherwise, it returns `false`. -- (BOOL)concurrentRootEnabled -{ - return true; -} - - -- (void)observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context -{ - if([keyPath isEqual:@"deviceUID"] || [keyPath isEqual:@"deviceUIDCopy"]) - { - [self copyDeviceUID]; - } -} - -- (void)copyDeviceUID { - NSString *deviceUID = [[NSUserDefaults standardUserDefaults] stringForKey:@"deviceUID"]; - if (deviceUID && deviceUID.length > 0) { - [NSUserDefaults.standardUserDefaults setValue:deviceUID forKey:@"deviceUIDCopy"]; - } -} - -- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity - restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler -{ - NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"]; - [defaults setValue:@{@"activityType": userActivity.activityType, @"userInfo": userActivity.userInfo} forKey:@"onUserActivityOpen"]; - if (userActivity.activityType == NSUserActivityTypeBrowsingWeb) { - return [RCTLinkingManager application:application - continueUserActivity:userActivity - restorationHandler:restorationHandler]; - } - else { - [EventEmitter.sharedInstance sendUserActivity:@{@"activityType": userActivity.activityType, @"userInfo": userActivity.userInfo}]; - return YES; - } -} - -- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { - return [RCTLinkingManager application:app openURL:url options:options]; -} - -- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier { - return NO; -} - -- (void)applicationWillTerminate:(UIApplication *)application { - [WCSession.defaultSession updateApplicationContext:@{@"isWalletsInitialized": @NO} error:nil]; - NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"]; - [defaults removeObjectForKey:@"onUserActivityOpen"]; -} - -- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded)) completionHandler { - [RNQuickActionManager onQuickActionPress:shortcutItem completionHandler:completionHandler]; -} - -//Called when a notification is delivered to a foreground app. --(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler -{ - NSDictionary *userInfo = notification.request.content.userInfo; - [EventEmitter.sharedInstance sendNotification:userInfo]; - completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge); -} - -- (void)openSettings { - [EventEmitter.sharedInstance openSettings]; -} - -- (void)buildMenuWithBuilder:(id)builder { - [super buildMenuWithBuilder:builder]; - [builder removeMenuForIdentifier:UIMenuServices]; - [builder removeMenuForIdentifier:UIMenuFormat]; - [builder removeMenuForIdentifier:UIMenuToolbar]; - [builder removeMenuForIdentifier:UIMenuFile]; - - UIKeyCommand *settingsCommand = [UIKeyCommand keyCommandWithInput:@"," modifierFlags:UIKeyModifierCommand action:@selector(openSettings)]; - [settingsCommand setTitle:@"Settings..."]; - UIMenu *settings = [UIMenu menuWithTitle:@"Settings..." image:nil identifier:@"openSettings" options:UIMenuOptionsDisplayInline children:@[settingsCommand]]; - - [builder insertSiblingMenu:settings afterMenuForIdentifier:UIMenuAbout]; -} - - --(void)showHelp:(id)sender { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://bluewallet.io/docs"] options:@{} completionHandler:nil]; -} - -- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { - if (action == @selector(showHelp:)) { - return true; - } else { - return [super canPerformAction:action withSender:sender]; - } -} - -// Required for the register event. -- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken -{ - [RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; -} -// Required for the notification event. You must call the completion handler after handling the remote notification. -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo -fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler -{ - [RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; -} -// Required for the registrationError event. -- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error -{ - [RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error]; -} -// Required for localNotification event -- (void)userNotificationCenter:(UNUserNotificationCenter *)center -didReceiveNotificationResponse:(UNNotificationResponse *)response - withCompletionHandler:(void (^)(void))completionHandler -{ - [RNCPushNotificationIOS didReceiveNotificationResponse:response]; -} - -@end diff --git a/ios/BlueWallet/AppDelegate.swift b/ios/BlueWallet/AppDelegate.swift new file mode 100644 index 00000000000..b0121ca9ed9 --- /dev/null +++ b/ios/BlueWallet/AppDelegate.swift @@ -0,0 +1,495 @@ +import UIKit +import React +import React_RCTAppDelegate +import ReactAppDependencyProvider +import UserNotifications +import Bugsnag + + +@main +class AppDelegate: RCTAppDelegate, UNUserNotificationCenterDelegate { + + private var userDefaultsGroup: UserDefaults? + + override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { + clearFilesIfNeeded() + + // Fix app group UserDefaults initialization + userDefaultsGroup = UserDefaults.standard + + // Set up device UID observers early + setupDeviceUIDObservers() + + let doNotTrackValue = userDefaultsGroup?.string(forKey: "donottrack") ?? "0" + NSLog("[AppDelegate] Initial Do Not Track value: '\(doNotTrackValue)'") + + if let isDoNotTrackEnabled = userDefaultsGroup?.string(forKey: "donottrack"), isDoNotTrackEnabled == "1" { + let isEnabled = userDefaultsGroup?.string(forKey: "donottrack") ?? "0" + NSLog("[AppDelegate] Do Not Track setting: \(isEnabled), expected to be '1'") + + userDefaultsGroup?.set("Disabled", forKey: "deviceUIDCopy") + userDefaultsGroup?.synchronize() + + NSLog("[AppDelegate] Do Not Track enabled: set deviceUIDCopy to 'Disabled'") + + } else { + #if targetEnvironment(macCatalyst) + let config = BugsnagConfiguration.loadConfig() + config.appType = "macOS" + Bugsnag.start(with: config) + copyDeviceUID() + #else + Bugsnag.start() + copyDeviceUID() + #endif + } + + self.moduleName = "BlueWallet" + self.dependencyProvider = RCTAppDependencyProvider() + self.initialProps = [:] + + RCTI18nUtil.sharedInstance().allowRTL(true) + + RNNotifications.startMonitorNotifications() + RNNotifications.addNativeDelegate(self) + + setupUserDefaultsListener() + registerNotificationCategories() + + // Access the singleton via the class method + _ = MenuElementsEmitter.sharedInstance() + NSLog("[MenuElements] AppDelegate: Initialized emitter singleton") + + let result = super.application(application, didFinishLaunchingWithOptions: launchOptions) + + return result + } + + override func sourceURL(for bridge: RCTBridge) -> URL? { + return bundleURL() + } + + override func bundleURL() -> URL? { + #if DEBUG + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") + #else + return Bundle.main.url(forResource: "main", withExtension: "jsbundle") + #endif + } + + private func registerNotificationCategories() { + let viewAddressTransactionsAction = UNNotificationAction( + identifier: "VIEW_ADDRESS_TRANSACTIONS", + title: NSLocalizedString("VIEW_ADDRESS_TRANSACTIONS_TITLE", comment: ""), + options: .foreground + ) + + let viewTransactionDetailsAction = UNNotificationAction( + identifier: "VIEW_TRANSACTION_DETAILS", + title: NSLocalizedString("VIEW_TRANSACTION_DETAILS_TITLE", comment: ""), + options: .foreground + ) + + let transactionCategory = UNNotificationCategory( + identifier: "TRANSACTION_CATEGORY", + actions: [viewAddressTransactionsAction, viewTransactionDetailsAction], + intentIdentifiers: [], + options: .customDismissAction + ) + + UNUserNotificationCenter.current().setNotificationCategories([transactionCategory]) + } + + private func setupUserDefaultsListener() { + guard let defaults = userDefaultsGroup else { + NSLog("[AppDelegate] Cannot setup UserDefaults listeners: group defaults not available") + return + } + + let keys = [ + "WidgetCommunicationAllWalletsSatoshiBalance", + "WidgetCommunicationAllWalletsLatestTransactionTime", + "WidgetCommunicationDisplayBalanceAllowed", + "WidgetCommunicationLatestTransactionIsUnconfirmed", + "preferredCurrency", + "preferredCurrencyLocale", + "electrum_host", + "electrum_tcp_port", + "electrum_ssl_port" + ] + + for key in keys { + defaults.addObserver(self, forKeyPath: key, options: .new, context: nil) + } + } + + private func copyDeviceUID() { + let isDoNotTrackEnabled = userDefaultsGroup?.string(forKey: "donottrack") == "1" + + let deviceUID = UserDefaults.standard.string(forKey: "deviceUID") ?? "" + let currentCopy = userDefaultsGroup?.string(forKey: "deviceUIDCopy") ?? "" + + if isDoNotTrackEnabled { + if currentCopy != "Disabled" { + userDefaultsGroup?.set("Disabled", forKey: "deviceUIDCopy") + userDefaultsGroup?.synchronize() + NSLog("[AppDelegate] Do Not Track enabled - set deviceUIDCopy to 'Disabled'") + } + return + } + + let hasCorrectFormat = deviceUID.count == 36 && deviceUID.components(separatedBy: "-").count == 5 + if deviceUID.isEmpty || !hasCorrectFormat { + let uuid = UUID().uuidString + UserDefaults.standard.setValue(uuid, forKey: "deviceUID") + copyDeviceUID() + return + } + if deviceUID != currentCopy { + userDefaultsGroup?.set(deviceUID, forKey: "deviceUIDCopy") + userDefaultsGroup?.synchronize() + + NSLog("[AppDelegate] Synced deviceUID to shared group: \(deviceUID)") + + let updatedCopy = userDefaultsGroup?.string(forKey: "deviceUIDCopy") ?? "" + NSLog("[AppDelegate] Verification - deviceUIDCopy is now: \(updatedCopy)") + } + } + + + private func setupDeviceUIDObservers() { + UserDefaults.standard.addObserver(self, forKeyPath: "deviceUID", options: .new, context: nil) + + if userDefaultsGroup != nil { + userDefaultsGroup?.addObserver(self, forKeyPath: "donottrack", options: .new, context: nil) + NSLog("[AppDelegate] Registered observer for donottrack changes") + } + + // Check if Do Not Track is enabled + let isDoNotTrackEnabled = userDefaultsGroup?.string(forKey: "donottrack") == "1" + NSLog("[AppDelegate] Do Not Track enabled: \(isDoNotTrackEnabled)") + + let currentDeviceUID = UserDefaults.standard.string(forKey: "deviceUID") + + if !isDoNotTrackEnabled { + var shouldSetUUID = false + + if currentDeviceUID == nil { + shouldSetUUID = true + NSLog("[AppDelegate] No deviceUID exists, will create a new one") + } else if let currentUID = currentDeviceUID { + let hasCorrectFormat = currentUID.count == 36 && currentUID.components(separatedBy: "-").count == 5 + if !hasCorrectFormat { + shouldSetUUID = true + NSLog("[AppDelegate] Current deviceUID doesn't match UUID format, will replace it") + } + } + + if shouldSetUUID { + let uuid = UUID().uuidString + UserDefaults.standard.setValue(uuid, forKey: "deviceUID") + NSLog("[AppDelegate] Set deviceUID to: \(uuid)") + } + } else { + NSLog("[AppDelegate] Do Not Track enabled - not setting UUID") + } + + if userDefaultsGroup != nil { + UserDefaults.standard.addSuite(named: UserDefaultsGroupKey.GroupName.rawValue) + NSLog("[AppDelegate] Registered app group UserDefaults with standard UserDefaults") + } + + copyDeviceUID() + } + + private func clearFilesIfNeeded() { + let defaults = UserDefaults.standard + if defaults.bool(forKey: "clearFilesOnLaunch") { + clearDirectory(.documentDirectory) + clearDirectory(.cachesDirectory) + clearTempDirectory() + + defaults.set(false, forKey: "clearFilesOnLaunch") + defaults.synchronize() + + DispatchQueue.main.async { + let alert = UIAlertController( + title: "Cache Cleared", + message: "The document, cache, and temp directories have been cleared.", + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.window.rootViewController?.present(alert, animated: true, completion: nil) + } + } + } + + private func clearDirectory(_ directory: FileManager.SearchPathDirectory) { + if let directoryURL = FileManager.default.urls(for: directory, in: .userDomainMask).last { + clearDirectory(at: directoryURL) + } + } + + private func clearTempDirectory() { + let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + clearDirectory(at: tempDirectory) + } + + private func clearDirectory(at url: URL) { + do { + let contents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: []) + for fileURL in contents { + try FileManager.default.removeItem(at: fileURL) + } + } catch { + print("Error clearing directory: \(error.localizedDescription)") + } + } + + // MARK: - Key-Value Observing + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { + guard let keyPath = keyPath else { return } + + // Handle deviceUID change + if keyPath == "deviceUID" { + NSLog("[AppDelegate] deviceUID changed, calling copyDeviceUID") + copyDeviceUID() + } + + // Handle donottrack changes + if keyPath == "donottrack" { + let newValue = userDefaultsGroup?.string(forKey: "donottrack") ?? "0" + NSLog("[AppDelegate] donottrack changed to: \(newValue)") + + if newValue != "1" { + let deviceUID = UserDefaults.standard.string(forKey: "deviceUID") ?? "" + let hasCorrectFormat = deviceUID.count == 36 && deviceUID.components(separatedBy: "-").count == 5 + + if deviceUID.isEmpty || !hasCorrectFormat { + let uuid = UUID().uuidString + UserDefaults.standard.setValue(uuid, forKey: "deviceUID") + NSLog("[AppDelegate] Do Not Track disabled - setting new deviceUID: \(uuid)") + } + } + + copyDeviceUID() + } + + let keys = [ + "WidgetCommunicationAllWalletsSatoshiBalance", + "WidgetCommunicationAllWalletsLatestTransactionTime", + "WidgetCommunicationDisplayBalanceAllowed", + "WidgetCommunicationLatestTransactionIsUnconfirmed", + "preferredCurrency", + "preferredCurrencyLocale", + "electrum_host", + "electrum_tcp_port", + "electrum_ssl_port" + ] + + if keys.contains(keyPath) { + WidgetHelper().reloadAllWidgets() + } + } + + override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + let activityType = userActivity.activityType + guard !activityType.isEmpty else { + print("[Handoff] Invalid or missing userActivity") + return false + } + + let userActivityData: [String: Any] = [ + "activityType": activityType, + "userInfo": userActivity.userInfo ?? [:] + ] + + userDefaultsGroup?.setValue(userActivityData, forKey: "onUserActivityOpen") + + if ["io.bluewallet.bluewallet.receiveonchain", "io.bluewallet.bluewallet.xpub", "io.bluewallet.bluewallet.blockexplorer"].contains(activityType) { + EventEmitter.shared().sendUserActivity(userActivityData) + return true + } + + if activityType == NSUserActivityTypeBrowsingWeb { + return RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler) + } + + print("[Handoff] Unhandled user activity type: \(activityType)") + return false + } + + override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + return RCTLinkingManager.application(app, open: url, options: options) + } + + override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + RNNotifications.didRegisterForRemoteNotifications(withDeviceToken: deviceToken) + } + + override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + RNNotifications.didFailToRegisterForRemoteNotificationsWithError(error) + } + + override func application( + _ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable: Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void + ) { + RNNotifications.didReceiveBackgroundNotification(userInfo, withCompletionHandler: completionHandler) + } + + override func applicationWillTerminate(_ application: UIApplication) { + userDefaultsGroup?.removeObject(forKey: "onUserActivityOpen") + + RNNotifications.removeNativeDelegate(self) + UserDefaults.standard.removeObserver(self, forKeyPath: "deviceUID") + } + + override func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { + RNQuickActionManager.onQuickActionPress(shortcutItem, completionHandler: completionHandler) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + completionHandler([.sound, .list, .banner, .badge]) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + let userInfo = response.notification.request.content.userInfo + let blockExplorer = userDefaultsGroup?.string(forKey: "blockExplorer") ?? "https://www.mempool.space" + + if let data = userInfo["data"] as? [String: Any] { + if response.actionIdentifier == "VIEW_ADDRESS_TRANSACTIONS", let address = data["address"] as? String { + if let url = URL(string: "\(blockExplorer)/address/\(address)") { + UIApplication.shared.open(url) + } + } else if response.actionIdentifier == "VIEW_TRANSACTION_DETAILS", let txid = data["txid"] as? String { + if let url = URL(string: "\(blockExplorer)/tx/\(txid)") { + UIApplication.shared.open(url) + } + } + } + + completionHandler() + } + + // MARK: - Menu Building (macOS Catalyst) + + override func buildMenu(with builder: UIMenuBuilder) { + super.buildMenu(with: builder) + + // Remove unnecessary menus + builder.remove(menu: .services) + builder.remove(menu: .format) + builder.remove(menu: .toolbar) + + // Remove the original Settings menu item + builder.remove(menu: .preferences) + + // File -> Add Wallet (Command + Shift + A) + let addWalletCommand = UIKeyCommand( + title: "Add Wallet", + action: #selector(addWalletAction), + input: "A", + modifierFlags: [.command, .shift] + ) + + // All menu items enabled by default + + // File -> Import Wallet (Command + I) + let importWalletCommand = UIKeyCommand( + title: "Import Wallet", + action: #selector(importWalletAction), + input: "I", + modifierFlags: .command + ) + + // Group Add Wallet and Import Wallet in a displayInline menu + let walletOperationsMenu = UIMenu( + title: "", + image: nil, + identifier: nil, + options: .displayInline, + children: [addWalletCommand, importWalletCommand] + ) + + // Modify the existing File menu to include Wallet Operations + if let fileMenu = builder.menu(for: .file) { + // Add "Reload Transactions" (Command + R) + let reloadTransactionsCommand = UIKeyCommand( + title: "Reload Transactions", + action: #selector(reloadTransactionsAction), + input: "R", + modifierFlags: .command + ) + + // Combine wallet operations and Reload Transactions into the new File menu + let newFileMenu = UIMenu( + title: fileMenu.title, + image: fileMenu.image, + identifier: fileMenu.identifier, + options: fileMenu.options, + children: [walletOperationsMenu, reloadTransactionsCommand] + ) + + builder.replace(menu: .file, with: newFileMenu) + } + + // BlueWallet -> Settings (Command + ,) + let settingsCommand = UIKeyCommand( + title: "Settings...", + action: #selector(openSettings), + input: ",", + modifierFlags: .command + ) + + let settingsMenu = UIMenu( + title: "", + image: nil, + identifier: nil, + options: .displayInline, + children: [settingsCommand] + ) + + // Insert the new Settings menu after the About menu + builder.insertSibling(settingsMenu, afterMenu: .about) + } + + @objc func openSettings(_ keyCommand: UIKeyCommand) { + DispatchQueue.main.async { + MenuElementsEmitter.sharedInstance().openSettings() + } + } + + @objc func addWalletAction(_ keyCommand: UIKeyCommand) { + DispatchQueue.main.async { + MenuElementsEmitter.sharedInstance().addWalletMenuAction() + } + } + + @objc func importWalletAction(_ keyCommand: UIKeyCommand) { + DispatchQueue.main.async { + MenuElementsEmitter.sharedInstance().importWalletMenuAction() + } + } + + @objc func reloadTransactionsAction(_ keyCommand: UIKeyCommand) { + DispatchQueue.main.async { + MenuElementsEmitter.sharedInstance().reloadTransactionsMenuAction() + } + } + + @objc func showHelp(_ sender: Any) { + if let url = URL(string: "https://bluewallet.io/docs") { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } + } + + override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + if action == #selector(showHelp(_:)) { + return true + } else { + return super.canPerformAction(action, withSender: sender) + } + } +} diff --git a/ios/BlueWallet/BlueWallet.entitlements b/ios/BlueWallet/BlueWallet.entitlements index 84242c28ec2..b2b8e48bb08 100644 --- a/ios/BlueWallet/BlueWallet.entitlements +++ b/ios/BlueWallet/BlueWallet.entitlements @@ -16,8 +16,6 @@ com.apple.security.network.client - com.apple.security.network.server - com.apple.security.personal-information.photos-library diff --git a/ios/BlueWallet/BlueWalletRelease.entitlements b/ios/BlueWallet/BlueWalletRelease.entitlements index 3d5de67c1b1..b2b8e48bb08 100644 --- a/ios/BlueWallet/BlueWalletRelease.entitlements +++ b/ios/BlueWallet/BlueWalletRelease.entitlements @@ -4,20 +4,6 @@ aps-environment development - com.apple.developer.icloud-container-identifiers - - iCloud.io.bluewallet.bluewallet - - com.apple.developer.icloud-services - - CloudDocuments - - com.apple.developer.ubiquity-container-identifiers - - iCloud.io.bluewallet.bluewallet - - com.apple.developer.ubiquity-kvstore-identifier - $(TeamIdentifierPrefix)$(CFBundleIdentifier) com.apple.security.app-sandbox com.apple.security.application-groups diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024 1.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024 1.png new file mode 100644 index 00000000000..594857ecda6 Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024 1.png differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024 2.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024 2.png new file mode 100644 index 00000000000..94ff21813d8 Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024 2.png differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024.png index 70d084bac84..9c2422d9c26 100644 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024.png and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/1024.png differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/128.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/128.png deleted file mode 100644 index ae965157c44..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/128.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/16.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/16.png deleted file mode 100644 index 9661e8a63c0..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/16.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/256-1.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/256-1.png deleted file mode 100644 index 1acf97697d3..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/256-1.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/256.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/256.png deleted file mode 100644 index 1acf97697d3..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/256.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/32-1.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/32-1.png deleted file mode 100644 index 4e67704c422..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/32-1.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/32.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/32.png deleted file mode 100644 index 4e67704c422..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/32.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/512-1.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/512-1.png deleted file mode 100644 index c9a0c65f0f7..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/512-1.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/512.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/512.png deleted file mode 100644 index c9a0c65f0f7..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/512.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/64.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/64.png deleted file mode 100644 index bb8fdcd79b6..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/64.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-20.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-20.png deleted file mode 100644 index 20c8694e6ef..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-20.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-20@2x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-20@2x.png deleted file mode 100644 index 538ecc1cef0..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-20@2x.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-20@3x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-20@3x.png deleted file mode 100644 index 4ebe02d9cb8..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-20@3x.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-29.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-29.png deleted file mode 100644 index 514920286d9..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-29.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-29@2x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-29@2x.png deleted file mode 100644 index 05dde42416a..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-29@2x.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-29@3x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-29@3x.png deleted file mode 100644 index 8da2de5496f..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-29@3x.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-40.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-40.png deleted file mode 100644 index 538ecc1cef0..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-40.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-40@2x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-40@2x.png deleted file mode 100644 index c2026acdcf7..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-40@2x.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-40@3x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-40@3x.png deleted file mode 100644 index 67eb590b45c..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-40@3x.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-60@2x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-60@2x.png deleted file mode 100644 index 67eb590b45c..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-60@2x.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-60@3x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-60@3x.png deleted file mode 100644 index 0d40abcd246..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-60@3x.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-76.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-76.png deleted file mode 100644 index bf200e75a0b..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-76.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-76@2x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-76@2x.png deleted file mode 100644 index 4e0e04b75c5..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-76@2x.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-83.5@2x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-83.5@2x.png deleted file mode 100644 index 40b59fb4893..00000000000 Binary files a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/BlueWallet-83.5@2x.png and /dev/null differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/Contents.json b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/Contents.json index ae17cf5c14f..052b626a2fa 100644 --- a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/Contents.json @@ -1,172 +1,100 @@ { "images" : [ { - "filename" : "BlueWallet-20@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "BlueWallet-20@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "BlueWallet-29@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "BlueWallet-29@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "BlueWallet-40@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "BlueWallet-40@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "BlueWallet-60@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "BlueWallet-60@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "BlueWallet-20.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "BlueWallet-20@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "BlueWallet-29.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "BlueWallet-29@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "BlueWallet-40.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "BlueWallet-40@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "BlueWallet-76.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "BlueWallet-76@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" + "filename" : "BlueWallet-1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" }, { - "filename" : "BlueWallet-83.5@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" }, { - "filename" : "BlueWallet-1024.png", - "idiom" : "ios-marketing", - "scale" : "1x", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "filename" : "1024 1.png", + "idiom" : "universal", + "platform" : "ios", "size" : "1024x1024" }, { - "filename" : "16.png", + "filename" : "icon_16x16.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { - "filename" : "32.png", + "filename" : "icon_16x16@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { - "filename" : "32-1.png", + "filename" : "icon_32x32.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { - "filename" : "64.png", + "filename" : "icon_32x32@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { - "filename" : "128.png", + "filename" : "icon_128x128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { - "filename" : "256.png", + "filename" : "icon_128x128@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { - "filename" : "256-1.png", + "filename" : "icon_256x256.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { - "filename" : "512.png", + "filename" : "icon_256x256@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { - "filename" : "512-1.png", + "filename" : "icon_512x512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { - "filename" : "1024.png", + "filename" : "icon_512x512@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" + }, + { + "filename" : "1024 2.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" } ], "info" : { diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_128x128.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_128x128.png new file mode 100644 index 00000000000..9222970acc0 Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_128x128.png differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png new file mode 100644 index 00000000000..a970347b62f Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_16x16.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_16x16.png new file mode 100644 index 00000000000..afb26ac6296 Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_16x16.png differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png new file mode 100644 index 00000000000..9fa78ea4bf5 Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_256x256.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_256x256.png new file mode 100644 index 00000000000..a970347b62f Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_256x256.png differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png new file mode 100644 index 00000000000..6e2c9019380 Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_32x32.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_32x32.png new file mode 100644 index 00000000000..9fa78ea4bf5 Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_32x32.png differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png new file mode 100644 index 00000000000..cc9ddb62411 Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_512x512.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_512x512.png new file mode 100644 index 00000000000..6e2c9019380 Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_512x512.png differ diff --git a/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png new file mode 100644 index 00000000000..cb6f9d6e1c3 Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png differ diff --git a/ios/BlueWallet/Images.xcassets/Contents.json b/ios/BlueWallet/Images.xcassets/Contents.json index da4a164c918..73c00596a7f 100644 --- a/ios/BlueWallet/Images.xcassets/Contents.json +++ b/ios/BlueWallet/Images.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/ios/BlueWallet/Images.xcassets/SplashIcon.imageset/Contents.json b/ios/BlueWallet/Images.xcassets/SplashIcon.imageset/Contents.json new file mode 100644 index 00000000000..add923b2afa --- /dev/null +++ b/ios/BlueWallet/Images.xcassets/SplashIcon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/BlueWallet/Images.xcassets/SplashIcon.imageset/icon.png b/ios/BlueWallet/Images.xcassets/SplashIcon.imageset/icon.png new file mode 100644 index 00000000000..4c4d57e85fb Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/SplashIcon.imageset/icon.png differ diff --git a/ios/BlueWallet/Images.xcassets/SplashIcon.imageset/icon@2x.png b/ios/BlueWallet/Images.xcassets/SplashIcon.imageset/icon@2x.png new file mode 100644 index 00000000000..23c1109bac5 Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/SplashIcon.imageset/icon@2x.png differ diff --git a/ios/BlueWallet/Images.xcassets/SplashIcon.imageset/icon@3x.png b/ios/BlueWallet/Images.xcassets/SplashIcon.imageset/icon@3x.png new file mode 100644 index 00000000000..84755259c91 Binary files /dev/null and b/ios/BlueWallet/Images.xcassets/SplashIcon.imageset/icon@3x.png differ diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index 3994481697f..40b1c7fa6ba 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -2,12 +2,12 @@ - CADisableMinimumFrameDurationOnPhone - BGTaskSchedulerPermittedIdentifiers io.bluewallet.bluewallet.fetchTxsForWallet + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion en CFBundleDisplayName @@ -28,6 +28,21 @@ io.bluewallet.psbt + + CFBundleTypeIconFiles + + CFBundleTypeName + Image + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + LSItemContentTypes + + public.jpeg + public.image + + CFBundleTypeIconFiles @@ -56,6 +71,20 @@ io.bluewallet.backup + + CFBundleTypeIconFiles + + CFBundleTypeName + BW COSIGNER + CFBundleTypeRole + Editor + LSHandlerRank + Owner + LSItemContentTypes + + io.bluewallet.bwcosigner + + CFBundleExecutable $(EXECUTABLE_NAME) @@ -65,11 +94,6 @@ 6.0 CFBundleName $(PRODUCT_NAME) - NSUserActivityTypes - - io.bluewallet.bluewallet.receiveonchain - io.bluewallet.bluewallet.xpub - CFBundlePackageType APPL CFBundleShortVersionString @@ -93,6 +117,10 @@ CFBundleVersion $(CURRENT_PROJECT_VERSION) + FIREBASE_ANALYTICS_COLLECTION_ENABLED + + FIREBASE_MESSAGING_AUTO_INIT_ENABLED + ITSAppUsesNonExemptEncryption LSApplicationCategoryType @@ -101,14 +129,28 @@ https http + itms-apps + LSMinimumSystemVersion + 14 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace + NSAppIntents + + + INIntentClassName + PriceView + IntentDescription + Quickly view the current Bitcoin market rate. + IntentName + Bitcoin Price + + NSAppTransportSecurity - NSAllowsArbitraryLoads + NSAllowsLocalNetworking NSExceptionDomains @@ -140,47 +182,31 @@ - NSAppleMusicUsageDescription - This alert should not show up as we do not require this data - NSBluetoothPeripheralUsageDescription - This alert should not show up as we do not require this data - NSCalendarsUsageDescription - This alert should not show up as we do not require this data NSCameraUsageDescription - In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code. + In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code. NSFaceIDUsageDescription - In order to use FaceID please confirm your permission. - NSLocationAlwaysUsageDescription - This alert should not show up as we do not require this data - NSLocationWhenInUseUsageDescription - This alert should not show up as we do not require this data - NSMicrophoneUsageDescription - This alert should not show up as we do not require this data - NSMotionUsageDescription - This alert should not show up as we do not require this data + In order to use FaceID for authentication when performing sensitive actions, we need your permission. NSPhotoLibraryAddUsageDescription Your authorization is required to save this image. NSPhotoLibraryUsageDescription In order to import an image for scanning, we need your permission to access your photo library. - NSSpeechRecognitionUsageDescription - This alert should not show up as we do not require this data + NSUserActivityTypes + + io.bluewallet.bluewallet.receiveonchain + io.bluewallet.bluewallet.xpub + + RCTNewArchEnabled + UIAppFonts - AntDesign.ttf Entypo.ttf - EvilIcons.ttf - Feather.ttf FontAwesome.ttf - FontAwesome5_Brands.ttf - FontAwesome5_Regular.ttf - FontAwesome5_Solid.ttf - Foundation.ttf + FontAwesome6_Brands.ttf + FontAwesome6_Regular.ttf + FontAwesome6_Solid.ttf Ionicons.ttf - MaterialCommunityIcons.ttf + MaterialDesignIcons.ttf MaterialIcons.ttf - Octicons.ttf - SimpleLineIcons.ttf - Zocial.ttf UIBackgroundModes @@ -188,26 +214,38 @@ processing remote-notification + UIFileSharingEnabled + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities - armv7 + arm64 UISupportedInterfaceOrientations + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown UISupportedInterfaceOrientations~ipad - UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~iphone-MaxScreenSizePhone + + UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance - + + UIDesignRequiresCompatibility + UTExportedTypeDeclarations @@ -217,8 +255,6 @@ UTTypeDescription Partially Signed Bitcoin Transaction - UTTypeIconFiles - UTTypeIdentifier io.bluewallet.psbt UTTypeTagSpecification @@ -230,43 +266,32 @@ - UTTypeConformsTo - - public.data - UTTypeDescription - Bitcoin Transaction - UTTypeIconFiles - + BW COSIGNER UTTypeIdentifier - io.bluewallet.psbt.txn + io.bluewallet.bwcosigner UTTypeTagSpecification public.filename-extension - txn + bwcosigner - - UTImportedTypeDeclarations - UTTypeConformsTo public.data UTTypeDescription - Partially Signed Bitcoin Transaction - UTTypeIconFiles - + Bitcoin Transaction UTTypeIdentifier - io.bluewallet.psbt + io.bluewallet.psbt.txn UTTypeTagSpecification public.filename-extension - psbt + txn @@ -276,39 +301,46 @@ public.data UTTypeDescription - Bitcoin Transaction - UTTypeIconFiles - + Electrum Backup UTTypeIdentifier - io.bluewallet.psbt.txn + io.bluewallet.backup UTTypeTagSpecification public.filename-extension - txn + backup + + UTImportedTypeDeclarations + + LSHandlerRank + Alternate UTTypeConformsTo - public.data + public.text UTTypeDescription - Electrum Backup - UTTypeIconFiles - + JSON File UTTypeIdentifier - io.bluewallet.backup + public.json UTTypeTagSpecification public.filename-extension - backup + json + + public.mime-type + + application/json + WKCompanionAppBundleIdentifier + io.bluewallet.bluewallet bugsnag apiKey diff --git a/ios/BlueWallet/main.m b/ios/BlueWallet/main.m deleted file mode 100644 index c316cf816e7..00000000000 --- a/ios/BlueWallet/main.m +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/ios/BlueWalletTests/BlueWalletTests.m b/ios/BlueWalletTests/BlueWalletTests.m deleted file mode 100644 index d7ee43a8d75..00000000000 --- a/ios/BlueWalletTests/BlueWalletTests.m +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import -#import - -#import -#import - -#define TIMEOUT_SECONDS 600 -#define TEXT_TO_LOOK_FOR @"Welcome to React Native!" - -@interface BlueWalletTests : XCTestCase - -@end - -@implementation BlueWalletTests - -- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test -{ - if (test(view)) { - return YES; - } - for (UIView *subview in [view subviews]) { - if ([self findSubviewInView:subview matching:test]) { - return YES; - } - } - return NO; -} - -- (void)testRendersWelcomeScreen -{ - UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; - BOOL foundElement = NO; - - __block NSString *redboxError = nil; - RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { - if (level >= RCTLogLevelError) { - redboxError = message; - } - }); - - while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - - foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { - if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { - return YES; - } - return NO; - }]; - } - - RCTSetLogFunction(RCTDefaultLogFunction); - - XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); - XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); -} - - -@end diff --git a/ios/BlueWalletTests/BlueWalletUITest.swift b/ios/BlueWalletTests/BlueWalletUITest.swift new file mode 100644 index 00000000000..449bc0538ea --- /dev/null +++ b/ios/BlueWalletTests/BlueWalletUITest.swift @@ -0,0 +1,37 @@ +// +// BlueWalletUITest.swift +// BlueWalletUITests +// +// Created by Marcos Rodriguez on 2/28/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import XCTest + +final class BlueWalletUITest: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testAppLaunchesAndShowsSettingsButton() throws { + let app = XCUIApplication() + app.launch() + + let settingsButton = app.buttons["SettingsButton"] + + // Wait for the settings button to appear to make sure the app has finished launching and is displaying its initial UI. + let exists = NSPredicate(format: "exists == true") + expectation(for: exists, evaluatedWith: settingsButton, handler: nil) + + // Wait for a maximum of 10 seconds for the settings button to appear + waitForExpectations(timeout: 10, handler: nil) + + // Assert that the settings button is not only present but also hittable (visible and interactable) + XCTAssertTrue(settingsButton.isHittable, "The settings button should be visible and interactable") + } +} diff --git a/ios/BlueWalletTests/MockData.swift b/ios/BlueWalletTests/MockData.swift new file mode 100644 index 00000000000..8460b865a37 --- /dev/null +++ b/ios/BlueWalletTests/MockData.swift @@ -0,0 +1,15 @@ +// +// MockData.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 7/10/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +struct MockData { + static let currentMarketData = MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2023-01-01T00:00:00+00:00") + static let previousMarketData = MarketData(nextBlock: "", sats: "", price: "$9,000", rate: 9000, dateString: "2022-12-31T00:00:00+00:00") + static let noChangeMarketData = MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2023-01-01T00:00:00+00:00") +} diff --git a/ios/BlueWalletUITests/BlueWalletUITests.swift b/ios/BlueWalletUITests/BlueWalletUITests.swift new file mode 100644 index 00000000000..cbf4c98fa89 --- /dev/null +++ b/ios/BlueWalletUITests/BlueWalletUITests.swift @@ -0,0 +1,41 @@ +// +// BlueWalletUITests.swift +// BlueWalletUITests +// +// Created by Marcos Rodriguez on 12/6/23. +// Copyright © 2023 BlueWallet. All rights reserved. +// + +import XCTest + +class BlueWalletUITests: XCTestCase { + + var app: XCUIApplication! + + override func setUp() { + super.setUp() + continueAfterFailure = false + + // Initialize the XCUIApplication instance + app = XCUIApplication() + + // Add a launch argument to differentiate between Mac Catalyst and iOS + #if targetEnvironment(macCatalyst) + app.launchArguments.append("--macCatalyst") + #else + app.launchArguments.append("--iOS") + #endif + + app.launch() + } + + func testAppLaunchesSuccessfully() { + XCTAssertEqual(app.state, .runningForeground, "App should be running in the foreground") + + #if targetEnvironment(macCatalyst) + XCTAssertTrue(app.windows.count > 0, "There should be at least one window in Mac Catalyst") + #else + XCTAssertTrue(app.buttons.count > 0, "There should be at least one button on iOS") + #endif + } +} diff --git a/ios/BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements b/ios/BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements deleted file mode 100644 index 0c67376ebac..00000000000 --- a/ios/BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/ios/BlueWalletWatch Extension/ExtensionDelegate.swift b/ios/BlueWalletWatch Extension/ExtensionDelegate.swift index b18a4a215f2..f14259e9734 100644 --- a/ios/BlueWalletWatch Extension/ExtensionDelegate.swift +++ b/ios/BlueWalletWatch Extension/ExtensionDelegate.swift @@ -3,37 +3,80 @@ // BlueWalletWatch Extension // // Created by Marcos Rodriguez on 3/6/19. -// Copyright © 2019 Facebook. All rights reserved. // import WatchKit import ClockKit +import Bugsnag +import WatchConnectivity +// WatchKit 2 uses WKExtensionDelegate, not WKApplicationDelegate class ExtensionDelegate: NSObject, WKExtensionDelegate { + let groupUserDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue) + func applicationDidFinishLaunching() { - // Perform any final initialization of your application. + // Initialize WatchDataSource in the application lifecycle + initializeWCSession() + scheduleNextReload() - ExtensionDelegate.preferredFiatCurrencyChanged() + updatePreferredFiatCurrency() + if let isDoNotTrackEnabled = groupUserDefaults?.bool(forKey: "donottrack"), !isDoNotTrackEnabled { + Bugsnag.start() + } } - static func preferredFiatCurrencyChanged() { - let fiatUnitUserDefaults: FiatUnit - if let preferredFiatCurrency = UserDefaults.standard.string(forKey: "preferredFiatCurrency"), let preferredFiatUnit = fiatUnit(currency: preferredFiatCurrency) { - fiatUnitUserDefaults = preferredFiatUnit + private func initializeWCSession() { + // Ensure WatchDataSource is initialized and session is started + WatchDataSource.shared.startSession() + + // Log session state for debugging + if WCSession.isSupported() { + let session = WCSession.default + print("WCSession initialized with state: \(session.activationState.rawValue)") + print("Is WCSession reachable: \(session.isReachable)") } else { - fiatUnitUserDefaults = fiatUnit(currency: "USD")! + print("WCSession is not supported on this device") } - WidgetAPI.fetchPrice(currency: fiatUnitUserDefaults.endPointKey) { (data, error) in - if let data = data, let encodedData = try? PropertyListEncoder().encode(data) { - UserDefaults.standard.set(encodedData, forKey: MarketData.string) - UserDefaults.standard.synchronize() - let server = CLKComplicationServer.sharedInstance() - - for complication in server.activeComplications ?? [] { - server.reloadTimeline(for: complication) - } - } + } + + func applicationDidBecomeActive() { + // Request data when app becomes active + WatchDataSource.shared.requestDataFromiOS() + } + + func applicationWillResignActive() { + // Perform any cleanup before app goes inactive + print("Watch app will resign active") + } + + func updatePreferredFiatCurrency() { + guard let fiatUnitUserDefaults = fetchPreferredFiatUnit() else { return } + updateMarketData(for: fiatUnitUserDefaults) + } + + private func fetchPreferredFiatUnit() -> FiatUnit? { + if let preferredFiatCurrency = groupUserDefaults?.string(forKey: "preferredCurrency"), let preferredFiatUnit = fiatUnit(currency: preferredFiatCurrency) { + return preferredFiatUnit + } else { + return fiatUnit(currency: "USD") + } + } + + private func updateMarketData(for fiatUnit: FiatUnit) { + MarketAPI.fetchPrice(currency: fiatUnit.endPointKey) { (data, error) in + guard let data = data, let encodedData = try? PropertyListEncoder().encode(data) else { return } + let groupUserDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue) + groupUserDefaults?.set(encodedData, forKey: MarketData.string) + groupUserDefaults?.synchronize() + ExtensionDelegate.reloadActiveComplications() + } + } + + private static func reloadActiveComplications() { + let server = CLKComplicationServer.sharedInstance() + for complication in server.activeComplications ?? [] { + server.reloadTimeline(for: complication) } } @@ -42,11 +85,10 @@ class ExtensionDelegate: NSObject, WKExtensionDelegate { return calendar.date(byAdding: .minute, value: 10, to: date)! } + // Update to use the correct API for scheduling background tasks in WatchKit 2 func scheduleNextReload() { let targetDate = nextReloadTime(after: Date()) - - NSLog("ExtensionDelegate: scheduling next update at %@", "\(targetDate)") - + // Use scheduleBackgroundRefresh instead of newer API WKExtension.shared().scheduleBackgroundRefresh( withPreferredDate: targetDate, userInfo: nil, @@ -54,52 +96,32 @@ class ExtensionDelegate: NSObject, WKExtensionDelegate { ) } - func reloadActiveComplications() { - let server = CLKComplicationServer.sharedInstance() - - for complication in server.activeComplications ?? [] { - server.reloadTimeline(for: complication) - } - } - - - func applicationDidBecomeActive() { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillResignActive() { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, etc. - } - func handle(_ backgroundTasks: Set) { for task in backgroundTasks { switch task { case let backgroundTask as WKApplicationRefreshBackgroundTask: - NSLog("ExtensionDelegate: handling WKApplicationRefreshBackgroundTask") - - scheduleNextReload() - let fiatUnitUserDefaults: FiatUnit - if let preferredFiatCurrency = UserDefaults.standard.string(forKey: "preferredFiatCurrency"), let preferredFiatUnit = fiatUnit(currency: preferredFiatCurrency) { - fiatUnitUserDefaults = preferredFiatUnit - } else { - fiatUnitUserDefaults = fiatUnit(currency: "USD")! - } - WidgetAPI.fetchPrice(currency: fiatUnitUserDefaults.endPointKey) { [weak self] (data, error) in - if let data = data, let encodedData = try? PropertyListEncoder().encode(data) { - UserDefaults.standard.set(encodedData, forKey: MarketData.string) - UserDefaults.standard.synchronize() - self?.reloadActiveComplications() - backgroundTask.setTaskCompletedWithSnapshot(false) - } - } - - default: - task.setTaskCompletedWithSnapshot(false) + handleApplicationRefreshBackgroundTask(backgroundTask) + case let snapshotTask as WKSnapshotRefreshBackgroundTask: + // Handle snapshot generation + snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil) + default: + task.setTaskCompletedWithSnapshot(false) } } } - + private func handleApplicationRefreshBackgroundTask(_ backgroundTask: WKApplicationRefreshBackgroundTask) { + scheduleNextReload() + guard let fiatUnitUserDefaults = fetchPreferredFiatUnit() else { + backgroundTask.setTaskCompletedWithSnapshot(false) + return + } + updateMarketData(for: fiatUnitUserDefaults) + + // Request updated wallet data during background refresh + WatchDataSource.shared.requestDataFromiOS() + + backgroundTask.setTaskCompletedWithSnapshot(false) + } } diff --git a/ios/BlueWalletWatch Extension/Info.plist b/ios/BlueWalletWatch Extension/Info.plist deleted file mode 100644 index 8953210aecb..00000000000 --- a/ios/BlueWalletWatch Extension/Info.plist +++ /dev/null @@ -1,55 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - BlueWalletWatch Extension - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - XPC! - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - CLKComplicationPrincipalClass - $(PRODUCT_MODULE_NAME).ComplicationController - CLKComplicationSupportedFamilies - - CLKComplicationFamilyCircularSmall - CLKComplicationFamilyGraphicBezel - CLKComplicationFamilyGraphicCircular - CLKComplicationFamilyGraphicRectangular - CLKComplicationFamilyGraphicCorner - CLKComplicationFamilyExtraLarge - CLKComplicationFamilyGraphicExtraLarge - CLKComplicationFamilyModularLarge - CLKComplicationFamilyModularSmall - CLKComplicationFamilyUtilitarianLarge - CLKComplicationFamilyUtilitarianSmall - CLKComplicationFamilyUtilitarianSmallFlat - - LSApplicationCategoryType - - NSExtension - - NSExtensionAttributes - - WKAppBundleIdentifier - io.bluewallet.bluewallet.watch - - NSExtensionPointIdentifier - com.apple.watchkit - - WKExtensionDelegateClassName - $(PRODUCT_MODULE_NAME).ExtensionDelegate - - diff --git a/ios/BlueWalletWatch Extension/InterfaceController.swift b/ios/BlueWalletWatch Extension/InterfaceController.swift index 31d6abb1a33..b086d3b627a 100644 --- a/ios/BlueWalletWatch Extension/InterfaceController.swift +++ b/ios/BlueWalletWatch Extension/InterfaceController.swift @@ -3,7 +3,6 @@ // BlueWalletWatch Extension // // Created by Marcos Rodriguez on 3/6/19. -// Copyright © 2019 Facebook. All rights reserved. // import WatchKit @@ -14,44 +13,57 @@ class InterfaceController: WKInterfaceController { @IBOutlet weak var walletsTable: WKInterfaceTable! @IBOutlet weak var noWalletsAvailableLabel: WKInterfaceLabel! - private let userActivity: NSUserActivity = NSUserActivity(activityType: HandoffIdentifier.ReceiveOnchain.rawValue) - + + override func awake(withContext context: Any?) { + super.awake(withContext: context) + print("InterfaceController: awake") + } + override func willActivate() { - // This method is called when watch view controller is about to be visible to user super.willActivate() - update(userActivity) + print("InterfaceController: willActivate (WatchKit 2)") - userActivity.userInfo = [HandOffUserInfoKey.ReceiveOnchain.rawValue: "bc1q2uvss3v0qh5smluggyqrzjgnqdg5xmun6afwpz"] - userActivity.isEligibleForHandoff = true; - userActivity.becomeCurrent() - if (WatchDataSource.shared.wallets.isEmpty) { - noWalletsAvailableLabel.setHidden(false) - } else { - processWalletsTable() - } - NotificationCenter.default.addObserver(self, selector: #selector(processWalletsTable), name: WatchDataSource.NotificationName.dataUpdated, object: nil) + // Request fresh data when controller becomes active + WatchDataSource.shared.requestDataFromiOS() + + // Update UI with any existing data + updateUI() + + // Register for notifications + NotificationCenter.default.addObserver( + self, + selector: #selector(updateUI), + name: Notifications.dataUpdated.name, + object: nil + ) } - @objc private func processWalletsTable() { - walletsTable.setNumberOfRows(WatchDataSource.shared.wallets.count, withRowType: WalletInformation.identifier) + override func didDeactivate() { + super.didDeactivate() + // Clean up observers when controller is no longer active + NotificationCenter.default.removeObserver(self) + } + + @objc private func updateUI() { + let wallets = WatchDataSource.shared.wallets + let isEmpty = wallets.isEmpty + noWalletsAvailableLabel.setHidden(!isEmpty) + walletsTable.setHidden(isEmpty) + + if isEmpty { return } - for index in 0.. Any? { - return rowIndex; + private func updateRow(at index: Int, with wallet: Wallet) { + guard let controller = walletsTable.rowController(at: index) as? WalletInformation else { return } + controller.configure(with: wallet) } + override func contextForSegue(withIdentifier segueIdentifier: String, in table: WKInterfaceTable, rowIndex: Int) -> Any? { + return rowIndex + } } diff --git a/ios/BlueWalletWatch Extension/Objects/Transaction.swift b/ios/BlueWalletWatch Extension/Objects/Transaction.swift deleted file mode 100644 index d7fca13af61..00000000000 --- a/ios/BlueWalletWatch Extension/Objects/Transaction.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// Wallet.swift -// BlueWalletWatch Extension -// -// Created by Marcos Rodriguez on 3/13/19. -// Copyright © 2019 Facebook. All rights reserved. -// - -import Foundation - -class Transaction: NSObject, NSCoding { - static let identifier: String = "Transaction" - - let time: String - let memo: String - let amount: String - let type: String - - init(time: String, memo: String, type: String, amount: String) { - self.time = time - self.memo = memo - self.type = type - self.amount = amount - } - - func encode(with aCoder: NSCoder) { - aCoder.encode(time, forKey: "time") - aCoder.encode(memo, forKey: "memo") - aCoder.encode(type, forKey: "type") - aCoder.encode(amount, forKey: "amount") - } - - required init?(coder aDecoder: NSCoder) { - time = aDecoder.decodeObject(forKey: "time") as! String - memo = aDecoder.decodeObject(forKey: "memo") as! String - amount = aDecoder.decodeObject(forKey: "amount") as! String - type = aDecoder.decodeObject(forKey: "type") as! String - } -} diff --git a/ios/BlueWalletWatch Extension/Objects/TransactionTableRow.swift b/ios/BlueWalletWatch Extension/Objects/TransactionTableRow.swift deleted file mode 100644 index ca798901688..00000000000 --- a/ios/BlueWalletWatch Extension/Objects/TransactionTableRow.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// TransactionTableRow.swift -// BlueWalletWatch Extension -// -// Created by Marcos Rodriguez on 3/10/19. -// Copyright © 2019 Facebook. All rights reserved. -// - -import WatchKit - -class TransactionTableRow: NSObject { - - @IBOutlet private weak var transactionAmountLabel: WKInterfaceLabel! - @IBOutlet private weak var transactionMemoLabel: WKInterfaceLabel! - @IBOutlet private weak var transactionTimeLabel: WKInterfaceLabel! - @IBOutlet private weak var transactionTypeImage: WKInterfaceImage! - - static let identifier: String = "TransactionTableRow" - - var amount: String = "" { - willSet { - transactionAmountLabel.setText(newValue) - } - } - - var memo: String = "" { - willSet { - transactionMemoLabel.setText(newValue) - } - } - - var time: String = "" { - willSet { - transactionTimeLabel.setText(newValue) - } - } - - var type: String = "" { - willSet { - if (newValue == "pendingConfirmation") { - transactionTypeImage.setImage(UIImage(named: "pendingConfirmation")) - } else if (newValue == "received") { - transactionTypeImage.setImage(UIImage(named: "receivedArrow")) - } else if (newValue == "sent") { - transactionTypeImage.setImage(UIImage(named: "sentArrow")) - } else { - transactionTypeImage.setImage(nil) - } - } - } - -} diff --git a/ios/BlueWalletWatch Extension/Objects/Wallet.swift b/ios/BlueWalletWatch Extension/Objects/Wallet.swift deleted file mode 100644 index 6061fc89dd3..00000000000 --- a/ios/BlueWalletWatch Extension/Objects/Wallet.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// Wallet.swift -// BlueWalletWatch Extension -// -// Created by Marcos Rodriguez on 3/13/19. -// Copyright © 2019 Facebook. All rights reserved. -// - -import Foundation - -enum InterfaceMode { - case Address, QRCode -} - -class Wallet: NSObject, NSCoding { - static let identifier: String = "Wallet" - - var identifier: Int? - let label: String - let balance: String - let type: String - let preferredBalanceUnit: String - let receiveAddress: String - let transactions: [Transaction] - let xpub: String? - let hideBalance: Bool - - init(label: String, balance: String, type: String, preferredBalanceUnit: String, receiveAddress: String, transactions: [Transaction], identifier: Int, xpub: String?, hideBalance: Bool = false) { - self.label = label - self.balance = balance - self.type = type - self.preferredBalanceUnit = preferredBalanceUnit - self.receiveAddress = receiveAddress - self.transactions = transactions - self.identifier = identifier - self.xpub = xpub - self.hideBalance = hideBalance - } - - func encode(with aCoder: NSCoder) { - aCoder.encode(label, forKey: "label") - aCoder.encode(balance, forKey: "balance") - aCoder.encode(type, forKey: "type") - aCoder.encode(receiveAddress, forKey: "receiveAddress") - aCoder.encode(preferredBalanceUnit, forKey: "preferredBalanceUnit") - aCoder.encode(transactions, forKey: "transactions") - aCoder.encode(identifier, forKey: "identifier") - aCoder.encode(xpub, forKey: "xpub") - aCoder.encode(hideBalance, forKey: "hideBalance") - } - - required init?(coder aDecoder: NSCoder) { - label = aDecoder.decodeObject(forKey: "label") as! String - balance = aDecoder.decodeObject(forKey: "balance") as! String - type = aDecoder.decodeObject(forKey: "type") as! String - preferredBalanceUnit = aDecoder.decodeObject(forKey: "preferredBalanceUnit") as! String - receiveAddress = aDecoder.decodeObject(forKey: "receiveAddress") as! String - transactions = aDecoder.decodeObject(forKey: "transactions") as? [Transaction] ?? [Transaction]() - xpub = aDecoder.decodeObject(forKey: "xpub") as? String - hideBalance = aDecoder.decodeObject(forKey: "hideBalance") as? Bool ?? false - - } - -} diff --git a/ios/BlueWalletWatch Extension/Objects/WalletGradient.swift b/ios/BlueWalletWatch Extension/Objects/WalletGradient.swift deleted file mode 100644 index 63b74ecc864..00000000000 --- a/ios/BlueWalletWatch Extension/Objects/WalletGradient.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// WalletGradient.swift -// BlueWalletWatch Extension -// -// Created by Marcos Rodriguez on 3/23/19. -// Copyright © 2019 Facebook. All rights reserved. -// - -import Foundation - -enum WalletGradient: String { - case SegwitHD = "HDsegwitP2SH" - case Segwit = "segwitP2SH" - case LightningCustodial = "lightningCustodianWallet" - case LightningLDK = "lightningLdk" - case SegwitNative = "HDsegwitBech32" - case WatchOnly = "watchOnly" - case MultiSig = "HDmultisig" - - var imageString: String{ - switch self { - case .Segwit: - return "wallet" - case .SegwitNative: - return "walletHDSegwitNative" - case .SegwitHD: - return "walletHD" - case .WatchOnly: - return "walletWatchOnly" - case .LightningCustodial, .LightningLDK: - return "walletLightningCustodial" - case .MultiSig: - return "watchMultisig" - } - } -} diff --git a/ios/BlueWalletWatch Extension/Objects/WalletInformation.swift b/ios/BlueWalletWatch Extension/Objects/WalletInformation.swift deleted file mode 100644 index 47c36953399..00000000000 --- a/ios/BlueWalletWatch Extension/Objects/WalletInformation.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// WalletInformation.swift -// BlueWalletWatch Extension -// -// Created by Marcos Rodriguez on 3/10/19. -// Copyright © 2019 Facebook. All rights reserved. -// - -import WatchKit - -class WalletInformation: NSObject { - - @IBOutlet weak var walletBalanceLabel: WKInterfaceLabel! - @IBOutlet private weak var walletNameLabel: WKInterfaceLabel! - @IBOutlet private weak var walletGroup: WKInterfaceGroup! - static let identifier: String = "WalletInformation" - - var name: String = "" { - willSet { - walletNameLabel.setText(newValue) - } - } - - var balance: String = "" { - willSet { - walletBalanceLabel.setText(newValue) - } - } - - var type: WalletGradient = .SegwitHD { - willSet { - walletGroup.setBackgroundImageNamed(newValue.imageString) - } - } - -} diff --git a/ios/BlueWalletWatch Extension/Objects/WatchDataSource.swift b/ios/BlueWalletWatch Extension/Objects/WatchDataSource.swift index 93bd98fd2c5..f2fb4b2aa25 100644 --- a/ios/BlueWalletWatch Extension/Objects/WatchDataSource.swift +++ b/ios/BlueWalletWatch Extension/Objects/WatchDataSource.swift @@ -1,140 +1,421 @@ -// -// WatchDataSource.swift -// BlueWalletWatch Extension -// -// Created by Marcos Rodriguez on 3/20/19. -// Copyright © 2019 Facebook. All rights reserved. -// - +// Data/WatchDataSource.swift import Foundation import WatchConnectivity +import Security +import ClockKit +struct NotificationName { + static let dataUpdated = Notification.Name(rawValue: "Notification.WalletDataSource.Updated") +} +struct Notifications { + static let dataUpdated = Notification(name: NotificationName.dataUpdated) +} + +/// Handles WatchConnectivity and data synchronization between iOS and Watch apps. class WatchDataSource: NSObject, WCSessionDelegate { - struct NotificationName { - static let dataUpdated = Notification.Name(rawValue: "Notification.WalletDataSource.Updated") - } - struct Notifications { - static let dataUpdated = Notification(name: NotificationName.dataUpdated) - } - - static let shared = WatchDataSource() - var wallets: [Wallet] = [Wallet]() - var companionWalletsInitialized = false - private let keychain = KeychainSwift() + // MARK: - Singleton Instance - override init() { - super.init() - if let existingData = keychain.getData(Wallet.identifier), let walletData = ((try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(existingData) as? [Wallet]) as [Wallet]??) { - guard let walletData = walletData, walletData != self.wallets else { return } - wallets = walletData - WatchDataSource.postDataUpdatedNotification() - } - if WCSession.isSupported() { - print("Activating watch session") - WCSession.default.delegate = self - WCSession.default.activate() - } - } - - func processWalletsData(walletsInfo: [String: Any]) { - if let walletsToProcess = walletsInfo["wallets"] as? [[String: Any]] { - wallets.removeAll(); - for (index, entry) in walletsToProcess.enumerated() { - guard let label = entry["label"] as? String, let balance = entry["balance"] as? String, let type = entry["type"] as? String, let preferredBalanceUnit = entry["preferredBalanceUnit"] as? String, let transactions = entry["transactions"] as? [[String: Any]] else { - continue + static func postDataUpdatedNotification() { + NotificationCenter.default.post(Notifications.dataUpdated) + } + + static let shared = WatchDataSource() + + // MARK: - Properties + + /// The list of wallets to be displayed on the Watch app. + var wallets: [Wallet] = [] { + didSet { + // When wallets are updated, save to keychain and refresh complications + saveWalletsToKeychain() + reloadComplications() + } + } + + var isDataLoaded: Bool = false + + // MARK: - Private Properties + + private let groupUserDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue) + private let keychain = KeychainHelper.shared + private let session: WCSession + + // MARK: - Initializer + + private override init() { + guard WCSession.isSupported() else { + print("WCSession is not supported on this device.") + self.session = WCSession.default + super.init() + return } + self.session = WCSession.default + super.init() - var transactionsProcessed = [Transaction]() - for transactionEntry in transactions { - guard let time = transactionEntry["time"] as? String, let memo = transactionEntry["memo"] as? String, let amount = transactionEntry["amount"] as? String, let type = transactionEntry["type"] as? String else { continue } - let transaction = Transaction(time: time, memo: memo, type: type, amount: amount) - transactionsProcessed.append(transaction) - } - let receiveAddress = entry["receiveAddress"] as? String ?? "" - let xpub = entry["xpub"] as? String ?? "" - let hideBalance = entry["hideBalance"] as? Bool ?? false - let wallet = Wallet(label: label, balance: balance, type: type, preferredBalanceUnit: preferredBalanceUnit, receiveAddress: receiveAddress, transactions: transactionsProcessed, identifier: index, xpub: xpub, hideBalance: hideBalance) - wallets.append(wallet) - } - - if let walletsArchived = try? NSKeyedArchiver.archivedData(withRootObject: wallets, requiringSecureCoding: false) { - keychain.set(walletsArchived, forKey: Wallet.identifier) - } - WatchDataSource.postDataUpdatedNotification() - } - } - - static func postDataUpdatedNotification() { - NotificationCenter.default.post(Notifications.dataUpdated) - } - - static func requestLightningInvoice(walletIdentifier: Int, amount: Double, description: String?, responseHandler: @escaping (_ invoice: String) -> Void) { - guard WatchDataSource.shared.wallets.count > walletIdentifier else { - responseHandler("") - return - } - WCSession.default.sendMessage(["request": "createInvoice", "walletIndex": walletIdentifier, "amount": amount, "description": description ?? ""], replyHandler: { (reply: [String : Any]) in - if let invoicePaymentRequest = reply["invoicePaymentRequest"] as? String, !invoicePaymentRequest.isEmpty { - responseHandler(invoicePaymentRequest) - } else { - responseHandler("") - } - }) { (error) in - print(error) - responseHandler("") - - } - } - - static func toggleWalletHideBalance(walletIdentifier: Int, hideBalance: Bool, responseHandler: @escaping (_ invoice: String) -> Void) { - guard WatchDataSource.shared.wallets.count > walletIdentifier else { - responseHandler("") - return - } - WCSession.default.sendMessage(["message": "hideBalance", "walletIndex": walletIdentifier, "hideBalance": hideBalance], replyHandler: { (reply: [String : Any]) in - responseHandler("") - }) { (error) in - print(error) - responseHandler("") - - } - } - - func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) { - processData(data: applicationContext) - } - - func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { - processData(data: applicationContext) - } - - func processData(data: [String: Any]) { - if let preferredFiatCurrency = data["preferredFiatCurrency"] as? String, let preferredFiatCurrencyUnit = fiatUnit(currency: preferredFiatCurrency) { - UserDefaults.standard.set(preferredFiatCurrencyUnit.endPointKey, forKey: "preferredFiatCurrency") - UserDefaults.standard.synchronize() - ExtensionDelegate.preferredFiatCurrencyChanged() - } else if let isWalletsInitialized = data["isWalletsInitialized"] as? Bool { - companionWalletsInitialized = isWalletsInitialized - NotificationCenter.default.post(Notifications.dataUpdated) - } else { - WatchDataSource.shared.processWalletsData(walletsInfo: data) - } - } - - func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) { - processData(data: userInfo) - } - - func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { - if activationState == .activated { - WCSession.default.sendMessage(["message" : "sendApplicationContext"], replyHandler: { (replyData) in - }) { (error) in - print(error) - } - } else { - WatchDataSource.shared.companionWalletsInitialized = false - } - } - + // Set delegate before trying to load data + self.session.delegate = self + + // Load cached data from keychain to show something while waiting for fresh data + loadKeychainData() + } + + // MARK: - Public Methods + + /// Starts the WatchConnectivity session. + func startSession() { + if session.activationState != .activated { + print("[WatchKit 2] Activating WCSession...") + session.activate() + } else { + print("[WatchKit 2] WCSession is already activated: \(session.activationState.rawValue)") + // Even if activated, attempt to request data + if session.isReachable { + requestDataFromiOS() + } + } + } + + /// Deactivates the WatchConnectivity session (if needed). + /// Note: WCSession does not provide a deactivate method, but you can handle any necessary cleanup here. + func deactivateSession() { + // Handle any necessary cleanup here. + } + + // MARK: - Keychain Operations + + /// Loads wallets data from the Keychain asynchronously. + private func loadKeychainData() { + DispatchQueue.global(qos: .background).async { [weak self] in + guard let self = self else { return } + guard let existingData = self.keychain.retrieve(service: UserDefaultsGroupKey.WatchAppBundleIdentifier.rawValue, account: UserDefaultsGroupKey.BundleIdentifier.rawValue), + let decodedWallets = try? JSONDecoder().decode([Wallet].self, from: existingData) else { + print("No existing wallets data found in Keychain.") + return + } + + // Filter wallets to include only on-chain wallets. + let onChainWallets = decodedWallets.filter { $0.chain == .onchain } + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + if onChainWallets != self.wallets { + self.wallets = onChainWallets + print("Loaded \(onChainWallets.count) on-chain wallets from Keychain.") + } + self.isDataLoaded = true + // Post notification about data update + WatchDataSource.postDataUpdatedNotification() + } + } + } + + /// Saves the current wallets data to the Keychain asynchronously. + private func saveWalletsToKeychain() { + DispatchQueue.global(qos: .background).async { [weak self] in + guard let self = self else { return } + // Save to keychain regardless of session state + guard let encodedData = try? JSONEncoder().encode(self.wallets) else { + print("Failed to encode wallets.") + return + } + let success = self.keychain.save(encodedData, service: UserDefaultsGroupKey.WatchAppBundleIdentifier.rawValue, account: UserDefaultsGroupKey.BundleIdentifier.rawValue) + if success { + print("Successfully saved wallets to Keychain.") + } else { + print("Failed to save wallets to Keychain.") + } + } + } + + // MARK: - WatchConnectivity Methods + + /// Handles the activation completion of the WCSession. + func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { + if let error = error { + print("[WatchKit 2] WCSession activation failed with error: \(error.localizedDescription)") + } else { + print("[WatchKit 2] WCSession activated with state: \(activationState.rawValue)") + + if activationState == .activated { + DispatchQueue.main.async { + self.requestDataFromiOS() + } + } + } + } + + /// Handles received messages from the iOS app. + func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { + processReceivedData(message) + } + + /// Handles received application context updates from the iOS app. + func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) { + print("[WatchKit 2] Received application context: \(applicationContext.keys)") + if applicationContext.isEmpty { return } + + DispatchQueue.main.async { + self.processReceivedData(applicationContext) + // Post notification that data was updated + WatchDataSource.postDataUpdatedNotification() + } + } + + /// Requests current data from the iOS app + func requestDataFromiOS() { + guard session.activationState == .activated else { + print("[WatchKit 2] Cannot request data: WCSession not activated (state: \(session.activationState.rawValue))") + startSession() // Try to activate the session + return + } + + let message = ["message": "sendApplicationContext"] + + // First check if we can use direct messaging + if session.isReachable { + print("[WatchKit 2] iOS app is reachable, sending direct message") + session.sendMessage(message, replyHandler: { [weak self] _ in + print("[WatchKit 2] Successfully requested application context from iOS app") + // Notify that we might have received data + DispatchQueue.main.async { + WatchDataSource.postDataUpdatedNotification() + } + }, errorHandler: { error in + print("[WatchKit 2] Error requesting application context: \(error.localizedDescription)") + + // Fallback to application context as a backup + self.sendApplicationContextRequest() + }) + } else { + print("[WatchKit 2] iOS app is not reachable, using application context") + sendApplicationContextRequest() + } + } + + private func sendApplicationContextRequest() { + do { + try session.updateApplicationContext(["message": "sendApplicationContext"]) + print("[WatchKit 2] Sent context update request to iOS app") + } catch { + print("[WatchKit 2] Failed to send context update request: \(error.localizedDescription)") + } + } + + // Enhance session reachability notification + func sessionReachabilityDidChange(_ session: WCSession) { + print("[WatchKit 2] Session reachability changed: \(session.isReachable)") + + if session.isReachable { + // If iOS app becomes reachable, request fresh data + requestDataFromiOS() + } + } + + // MARK: - Data Processing + + /// Processes received data from the iOS app. + /// - Parameter data: The data received either as a message or application context. + private func processReceivedData(_ data: [String: Any]) { + if let preferredFiatCurrency = data["preferredFiatCurrency"] as? String { + // Handle preferred fiat currency update. + groupUserDefaults?.set(preferredFiatCurrency, forKey: "preferredCurrency") + + // Fetch and update market data based on the new preferred currency. + updateMarketData(for: preferredFiatCurrency) + } else { + // Assume the data contains wallets information. + processWalletsData(walletsInfo: data) + } + } + + /// Processes wallets data received from the iOS app. + /// - Parameter walletsInfo: The wallets data received as a dictionary. + private func processWalletsData(walletsInfo: [String: Any]) { + guard let walletsToProcess = walletsInfo["wallets"] as? [[String: Any]] else { + print("No wallets data found in received context.") + return + } + + var processedWallets: [Wallet] = [] + + for entry in walletsToProcess { + guard let label = entry["label"] as? String, + let balance = entry["balance"] as? Double, + let typeString = entry["type"] as? String, + let preferredBalanceUnitString = entry["preferredBalanceUnit"] as? String, + let chainString = entry["chain"] as? String, + let transactions = entry["transactions"] as? [[String: Any]] else { + print("Incomplete wallet entry found. Skipping.") + continue + } + + var transactionsProcessed: [Transaction] = [] + for transactionEntry in transactions { + guard let timeString = transactionEntry["time"] as? String, + let memo = transactionEntry["memo"] as? String, + let amountDouble = transactionEntry["amount"] as? Double, + let type = transactionEntry["type"] as? String else { + print("Incomplete transaction entry found. Skipping.") + continue + } + + guard let time = ISO8601DateFormatter().date(from: timeString) else { + print("Invalid date format for transaction. Skipping.") + continue + } + + let amount = Decimal(amountDouble) + + let transactionType = TransactionType.fromRawString(type) + + let transaction = Transaction(time: time, memo: memo, type: transactionType, amount: amount) + transactionsProcessed.append(transaction) + } + + let receiveAddress = entry["receiveAddress"] as? String ?? "" + let xpub = entry["xpub"] as? String ?? "" + let hideBalance = entry["hideBalance"] as? Bool ?? false + let paymentCode = entry["paymentCode"] as? String + let chain = Chain(rawString: chainString) + + let wallet = Wallet( + label: label, + balance: "\(balance) BTC", + type: WalletType(rawString: typeString), + chain: chain, + preferredBalanceUnit: BitcoinUnit(rawString: preferredBalanceUnitString), + receiveAddress: receiveAddress, + transactions: transactionsProcessed, + xpub: xpub, + hideBalance: hideBalance, + paymentCode: paymentCode + ) + processedWallets.append(wallet) + } + + // Update the `wallets` property on the main thread. + DispatchQueue.main.async { [weak self] in + self?.wallets = processedWallets + print("Updated wallets from received context.") + WatchDataSource.postDataUpdatedNotification() + } + } + + /// Fetches market data based on the preferred fiat currency. + /// - Parameter fiatCurrency: The preferred fiat currency string. + private func updateMarketData(for fiatCurrency: String) { + guard !fiatCurrency.isEmpty else { + print("Invalid fiat currency provided") + return + } + + MarketAPI.fetchPrice(currency: fiatCurrency) { [weak self] (marketData, error) in + guard let self = self else { return } + if let error = error { + print("Failed to fetch market data: \(error.localizedDescription)") + // Consider implementing retry logic or fallback mechanism + return + } + + guard let marketData = marketData as? MarketData else { + print("Invalid market data format received") + return + } + + do { + let widgetData = WidgetDataStore(rate: "\(marketData.rate)", lastUpdate: marketData.dateString, rateDouble: marketData.rate) + if let encodedData = try? JSONEncoder().encode(widgetData) { + self.groupUserDefaults?.set(encodedData, forKey: MarketData.string) + print("Market data updated for currency: \(fiatCurrency)") + } else { + throw NSError(domain: "WatchDataSource", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to encode market data"]) + } + } catch { + print("Failed to process market data: \(error.localizedDescription)") + } + } + } + + // MARK: - Wallet Actions + + /// Requests a Lightning Invoice from the iOS app. + /// - Parameters: + /// - walletIdentifier: The index of the wallet in the `wallets` array. + /// - amount: The amount for the invoice. + /// - description: An optional description for the invoice. + /// - responseHandler: A closure to handle the invoice string received from the iOS app. + func requestLightningInvoice(walletIdentifier: Int, amount: Double, description: String?, responseHandler: @escaping (_ invoice: String) -> Void) { + let timeoutSeconds = 30.0 + let timeoutTimer = Timer.scheduledTimer(withTimeInterval: timeoutSeconds, repeats: false) { _ in + print("Lightning invoice request timed out") + responseHandler("") + } + + guard wallets.indices.contains(walletIdentifier) else { + timeoutTimer.invalidate() + responseHandler("") + return + } + let message: [String: Any] = [ + "request": "createInvoice", + "walletIndex": walletIdentifier, + "amount": amount, + "description": description ?? "" + ] + session.sendMessage(message, replyHandler: { reply in + timeoutTimer.invalidate() + if let invoicePaymentRequest = reply["invoicePaymentRequest"] as? String, !invoicePaymentRequest.isEmpty { + responseHandler(invoicePaymentRequest) + } else { + responseHandler("") + } + }, errorHandler: { error in + timeoutTimer.invalidate() + print("Error requesting Lightning Invoice: \(error.localizedDescription)") + responseHandler("") + }) + } + + /// Toggles the visibility of the wallet's balance. + /// - Parameters: + /// - walletIdentifier: The index of the wallet in the `wallets` array. + /// - hideBalance: A boolean indicating whether to hide the balance. + func toggleWalletHideBalance(walletIdentifier: UUID, hideBalance: Bool, responseHandler: @escaping (_ success: Bool) -> Void) { + guard wallets.indices.contains(walletIdentifier.hashValue) else { + responseHandler(false) + return + } + let message: [String: Any] = [ + "message": "hideBalance", + "walletIndex": walletIdentifier, + "hideBalance": hideBalance + ] + session.sendMessage(message, replyHandler: { reply in + responseHandler(true) + }, errorHandler: { error in + print("Error toggling hide balance: \(error.localizedDescription)") + responseHandler(false) + }) + } + + // MARK: - Complications Reload + + /// Reloads all active complications on the Watch face. + private func reloadComplications() { + let server = CLKComplicationServer.sharedInstance() + server.activeComplications?.forEach { complication in + server.reloadTimeline(for: complication) + print("[Complication] Reloaded timeline for \(complication.family.rawValue)") + } + } + +} + +extension WatchDataSource { + static var mock: WatchDataSource { + let mockDataSource = WatchDataSource() + mockDataSource.wallets = [Wallet.mock] + return mockDataSource + } } diff --git a/ios/BlueWalletWatch Extension/ReceiveInterfaceController.swift b/ios/BlueWalletWatch Extension/ReceiveInterfaceController.swift deleted file mode 100644 index d36a5916fdf..00000000000 --- a/ios/BlueWalletWatch Extension/ReceiveInterfaceController.swift +++ /dev/null @@ -1,192 +0,0 @@ -// -// ReceiveInterfaceController.swift -// BlueWalletWatch Extension -// -// Created by Marcos Rodriguez on 3/12/19. -// Copyright © 2019 Facebook. All rights reserved. -// - -import WatchKit -import WatchConnectivity -import Foundation -import EFQRCode - -class ReceiveInterfaceController: WKInterfaceController { - - static let identifier = "ReceiveInterfaceController" - private var wallet: Wallet? { - didSet { - if let address = wallet?.receiveAddress { - userActivity.userInfo = [HandOffUserInfoKey.ReceiveOnchain.rawValue: address] - userActivity.isEligibleForHandoff = true; - userActivity.becomeCurrent() - } - } - } - private var isRenderingQRCode: Bool? - private var receiveMethod: String = "receive" - private var interfaceMode: InterfaceMode = .Address - @IBOutlet weak var addressLabel: WKInterfaceLabel! - @IBOutlet weak var loadingIndicator: WKInterfaceGroup! - @IBOutlet weak var imageInterface: WKInterfaceImage! - private let userActivity: NSUserActivity = NSUserActivity(activityType: HandoffIdentifier.ReceiveOnchain.rawValue) - - override func willActivate() { - super.willActivate() - userActivity.title = HandOffTitle.ReceiveOnchain.rawValue - userActivity.requiredUserInfoKeys = [HandOffUserInfoKey.Xpub.rawValue] - userActivity.isEligibleForHandoff = true - update(userActivity) - } - - override func awake(withContext context: Any?) { - super.awake(withContext: context) - guard let passedContext = context as? (Int, String), WatchDataSource.shared.wallets.count >= passedContext.0 else { - pop() - return - } - let identifier = passedContext.0 - let wallet = WatchDataSource.shared.wallets[identifier] - self.wallet = wallet - receiveMethod = passedContext.1 - NotificationCenter.default.addObserver(forName: SpecifyInterfaceController.NotificationName.createQRCode, object: nil, queue: nil) { [weak self] (notification) in - self?.isRenderingQRCode = true - if let wallet = self?.wallet, wallet.type == WalletGradient.LightningCustodial.rawValue || wallet.type == WalletGradient.LightningLDK.rawValue, self?.receiveMethod == "createInvoice", let object = notification.object as? SpecifyInterfaceController.SpecificQRCodeContent, let amount = object.amount { - self?.imageInterface.setHidden(true) - self?.loadingIndicator.setHidden(false) - WatchDataSource.requestLightningInvoice(walletIdentifier: identifier, amount: amount, description: object.description, responseHandler: { (invoice) in - DispatchQueue.main.async { - if (!invoice.isEmpty) { - guard let cgImage = EFQRCode.generate( - content: "lightning:\(invoice)", inputCorrectionLevel: .h, pointShape: .circle) else { - return - } - let image = UIImage(cgImage: cgImage) - self?.loadingIndicator.setHidden(true) - self?.imageInterface.setHidden(false) - self?.imageInterface.setImage(nil) - self?.imageInterface.setImage(image) - self?.addressLabel.setText(invoice) - self?.interfaceMode = .QRCode - self?.toggleViewButtonPressed() - WCSession.default.sendMessage(["message": "fetchTransactions"], replyHandler: nil, errorHandler: nil) - } else { - self?.presentAlert(withTitle: "Error", message: "Unable to create invoice. Please open BlueWallet on your iPhone and unlock your wallets.", preferredStyle: .alert, actions: [WKAlertAction(title: "OK", style: .default, handler: { [weak self] in - self?.dismiss() - self?.pop() - })]) - } - } - }) - } else { - guard let notificationObject = notification.object as? SpecifyInterfaceController.SpecificQRCodeContent, let walletContext = self?.wallet, !walletContext.receiveAddress.isEmpty, let receiveAddress = self?.wallet?.receiveAddress else { return } - - var address = "bitcoin:\(receiveAddress)" - - var hasAmount = false - if let amount = notificationObject.amount { - address.append("?amount=\(amount)&") - hasAmount = true - } - if let description = notificationObject.description { - if (!hasAmount) { - address.append("?") - } - address.append("label=\(description)") - } - - DispatchQueue.main.async { - guard let cgImage = EFQRCode.generate( - content: address) else { - return - } - let image = UIImage(cgImage: cgImage) - self?.imageInterface.setImage(nil) - self?.imageInterface.setImage(image) - self?.imageInterface.setHidden(false) - self?.addressLabel.setText(receiveAddress) - self?.interfaceMode = .QRCode - self?.toggleViewButtonPressed() - self?.loadingIndicator.setHidden(true) - self?.isRenderingQRCode = false - } - } - } - - guard !wallet.receiveAddress.isEmpty, let cgImage = EFQRCode.generate( - content: wallet.receiveAddress), receiveMethod != "createInvoice" else { - return - } - - let image = UIImage(cgImage: cgImage) - imageInterface.setImage(image) - - if #available(watchOSApplicationExtension 6.0, *) { - if let image = UIImage(systemName: "textformat.subscript") { - addMenuItem(with: image, title: "Address", action:#selector(toggleViewButtonPressed)) - } else { - addMenuItem(with: .shuffle, title: "Address", action: #selector(toggleViewButtonPressed)) - } - } else { - addMenuItem(with: .shuffle, title: "Address", action: #selector(toggleViewButtonPressed)) - } - addressLabel.setText(wallet.receiveAddress) - } - - override func didAppear() { - super.didAppear() - if (wallet?.type == WalletGradient.LightningCustodial.rawValue || wallet?.type == WalletGradient.LightningLDK.rawValue) && receiveMethod == "createInvoice" { - if isRenderingQRCode == nil { - presentController(withName: SpecifyInterfaceController.identifier, context: wallet?.identifier) - isRenderingQRCode = false - } else if isRenderingQRCode == false { - pop() - } - } - } - - override func didDeactivate() { - super.didDeactivate() - NotificationCenter.default.removeObserver(self, name: SpecifyInterfaceController.NotificationName.createQRCode, object: nil) - userActivity.invalidate() - invalidateUserActivity() - - } - - @IBAction func specifyMenuItemTapped() { - presentController(withName: SpecifyInterfaceController.identifier, context: wallet?.identifier) - } - - - @IBAction @objc func toggleViewButtonPressed() { - clearAllMenuItems() - switch interfaceMode { - case .Address: - addressLabel.setHidden(false) - imageInterface.setHidden(true) - if #available(watchOSApplicationExtension 6.0, *) { - if let image = UIImage(systemName: "qrcode") { - addMenuItem(with: image, title: "QR Code", action:#selector(toggleViewButtonPressed)) - } else { - addMenuItem(with: .shuffle, title: "QR Code", action: #selector(toggleViewButtonPressed)) - } - } else { - addMenuItem(with: .shuffle, title: "QR Code", action: #selector(toggleViewButtonPressed)) - - } - case .QRCode: - addressLabel.setHidden(true) - imageInterface.setHidden(false) - if #available(watchOSApplicationExtension 6.0, *) { - if let image = UIImage(systemName: "textformat.subscript") { - addMenuItem(with: image, title: "Address", action:#selector(toggleViewButtonPressed)) - } else { - addMenuItem(with: .shuffle, title: "Address", action: #selector(toggleViewButtonPressed)) - } - } else { - addMenuItem(with: .shuffle, title: "Address", action: #selector(toggleViewButtonPressed)) - } - } - interfaceMode = interfaceMode == .QRCode ? .Address : .QRCode - } -} diff --git a/ios/BlueWalletWatch Extension/ViewQRCodefaceController.swift b/ios/BlueWalletWatch Extension/ViewQRCodefaceController.swift deleted file mode 100644 index b2b6c8b4869..00000000000 --- a/ios/BlueWalletWatch Extension/ViewQRCodefaceController.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// ReceiveInterfaceController.swift -// BlueWalletWatch Extension -// -// Created by Marcos Rodriguez on 3/12/19. -// Copyright © 2019 Facebook. All rights reserved. -// - -import WatchKit -import Foundation -import EFQRCode - -class ViewQRCodefaceController: WKInterfaceController { - - static let identifier = "ViewQRCodefaceController" - @IBOutlet weak var imageInterface: WKInterfaceImage! - @IBOutlet weak var addressLabel: WKInterfaceLabel! - var address: String? { - didSet { - if let address = address, !address.isEmpty{ - userActivity.userInfo = [HandOffUserInfoKey.Xpub.rawValue: address] - userActivity.becomeCurrent() - } - } - } - private var interfaceMode = InterfaceMode.Address - private let userActivity: NSUserActivity = NSUserActivity(activityType: HandoffIdentifier.Xpub.rawValue) - - override func awake(withContext context: Any?) { - super.awake(withContext: context) - userActivity.title = HandOffTitle.Xpub.rawValue - userActivity.requiredUserInfoKeys = [HandOffUserInfoKey.Xpub.rawValue] - userActivity.isEligibleForHandoff = true - guard let passedContext = context as? String else { - pop() - return - } - address = passedContext - addressLabel.setText(passedContext) - - DispatchQueue.main.async { - guard let cgImage = EFQRCode.generate( - content: passedContext) else { - return - } - let image = UIImage(cgImage: cgImage) - self.imageInterface.setImage(nil) - self.imageInterface.setImage(image) - } - if #available(watchOSApplicationExtension 6.0, *) { - if let image = UIImage(systemName: "textformat.subscript") { - addMenuItem(with: image, title: "Address", action:#selector(toggleViewButtonPressed)) - } else { - addMenuItem(with: .shuffle, title: "Address", action: #selector(toggleViewButtonPressed)) - } - } else { - addMenuItem(with: .shuffle, title: "Address", action: #selector(toggleViewButtonPressed)) - } - } - @IBAction @objc func toggleViewButtonPressed() { - clearAllMenuItems() - switch interfaceMode { - case .Address: - addressLabel.setHidden(false) - imageInterface.setHidden(true) - if #available(watchOSApplicationExtension 6.0, *) { - if let image = UIImage(systemName: "qrcode") { - addMenuItem(with: image, title: "QR Code", action:#selector(toggleViewButtonPressed)) - } else { - addMenuItem(with: .shuffle, title: "QR Code", action: #selector(toggleViewButtonPressed)) - } - } else { - addMenuItem(with: .shuffle, title: "QR Code", action: #selector(toggleViewButtonPressed)) - - } - case .QRCode: - addressLabel.setHidden(true) - imageInterface.setHidden(false) - if #available(watchOSApplicationExtension 6.0, *) { - if let image = UIImage(systemName: "textformat.subscript") { - addMenuItem(with: image, title: "Address", action:#selector(toggleViewButtonPressed)) - } else { - addMenuItem(with: .shuffle, title: "Address", action: #selector(toggleViewButtonPressed)) - } - } else { - addMenuItem(with: .shuffle, title: "Address", action: #selector(toggleViewButtonPressed)) - } - } - interfaceMode = interfaceMode == .QRCode ? .Address : .QRCode - } - - override func willActivate() { - super.willActivate() - update(userActivity) - } - - - override func didDeactivate() { - super.didDeactivate() - userActivity.invalidate() - invalidateUserActivity() - } - - -} diff --git a/ios/BlueWalletWatch Extension/WalletDetailsInterfaceController.swift b/ios/BlueWalletWatch Extension/WalletDetailsInterfaceController.swift deleted file mode 100644 index 711caeeb1dd..00000000000 --- a/ios/BlueWalletWatch Extension/WalletDetailsInterfaceController.swift +++ /dev/null @@ -1,130 +0,0 @@ -// -// WalletDetailsInterfaceController.swift -// BlueWalletWatch Extension -// -// Created by Marcos Rodriguez on 3/11/19. -// Copyright © 2019 Facebook. All rights reserved. -// - -import WatchKit -import Foundation -import WatchConnectivity - -class WalletDetailsInterfaceController: WKInterfaceController { - - var wallet: Wallet? - static let identifier = "WalletDetailsInterfaceController" - @IBOutlet weak var walletBasicsGroup: WKInterfaceGroup! - @IBOutlet weak var walletBalanceLabel: WKInterfaceLabel! - @IBOutlet weak var createInvoiceButton: WKInterfaceButton! - @IBOutlet weak var walletNameLabel: WKInterfaceLabel! - @IBOutlet weak var receiveButton: WKInterfaceButton! - @IBOutlet weak var viewXPubButton: WKInterfaceButton! - @IBOutlet weak var noTransactionsLabel: WKInterfaceLabel! - @IBOutlet weak var transactionsTable: WKInterfaceTable! - - - override func awake(withContext context: Any?) { - super.awake(withContext: context) - guard let identifier = context as? Int else { - pop() - return - } - processInterface(identifier: identifier) - } - - func processInterface(identifier: Int) { - let wallet = WatchDataSource.shared.wallets[identifier] - self.wallet = wallet - walletBalanceLabel.setHidden(wallet.hideBalance) - walletBalanceLabel.setText(wallet.hideBalance ? "" : wallet.balance) - walletNameLabel.setText(wallet.label) - walletBasicsGroup.setBackgroundImageNamed(WalletGradient(rawValue: wallet.type)?.imageString) - createInvoiceButton.setHidden(!(wallet.type == WalletGradient.LightningCustodial.rawValue || wallet.type == WalletGradient.LightningLDK.rawValue)) - receiveButton.setHidden(wallet.receiveAddress.isEmpty) - viewXPubButton.setHidden(!((wallet.type != WalletGradient.LightningCustodial.rawValue || wallet.type != WalletGradient.LightningLDK.rawValue) && !(wallet.xpub ?? "").isEmpty)) - processWalletsTable() - } - - - @IBAction func toggleBalanceVisibility(_ sender: Any) { - guard let wallet = wallet else { - return - } - - if wallet.hideBalance { - showBalanceMenuItemTapped() - } else{ - hideBalanceMenuItemTapped() - } - } - - - @objc func showBalanceMenuItemTapped() { - guard let identifier = wallet?.identifier else { return } - WatchDataSource.toggleWalletHideBalance(walletIdentifier: identifier, hideBalance: false) { [weak self] _ in - DispatchQueue.main.async { - WatchDataSource.postDataUpdatedNotification() - self?.processInterface(identifier: identifier) - } - } - } - - @objc func hideBalanceMenuItemTapped() { - guard let identifier = wallet?.identifier else { return } - WatchDataSource.toggleWalletHideBalance(walletIdentifier: identifier, hideBalance: true) { [weak self] _ in - DispatchQueue.main.async { - WatchDataSource.postDataUpdatedNotification() - self?.processInterface(identifier: identifier) - } - } - } - - @IBAction func viewXPubMenuItemTapped() { - guard let xpub = wallet?.xpub else { - return - } - presentController(withName: ViewQRCodefaceController.identifier, context: xpub) - } - - override func willActivate() { - super.willActivate() - transactionsTable.setHidden(wallet?.transactions.isEmpty ?? true) - noTransactionsLabel.setHidden(!(wallet?.transactions.isEmpty ?? false)) - } - - @IBAction func receiveMenuItemTapped() { - presentController(withName: ReceiveInterfaceController.identifier, context: (wallet, "receive")) - } - - - @objc private func processWalletsTable() { - transactionsTable.setNumberOfRows(wallet?.transactions.count ?? 0, withRowType: TransactionTableRow.identifier) - - for index in 0.. Any? { - return (wallet?.identifier, "receive") - } - -} diff --git a/ios/BlueWalletWatch Extension/main.swift b/ios/BlueWalletWatch Extension/main.swift new file mode 100644 index 00000000000..6ae96e068f8 --- /dev/null +++ b/ios/BlueWalletWatch Extension/main.swift @@ -0,0 +1,5 @@ +import WatchKit +import Foundation + +// For WatchKit 2, we use the NSExtensionMain function +NSExtensionMain() diff --git a/ios/BlueWalletWatch/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/BlueWalletWatch/Assets.xcassets/AppIcon.appiconset/Contents.json index 4bc91346428..b11e79d715e 100644 --- a/ios/BlueWalletWatch/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/BlueWalletWatch/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,92 +1,134 @@ { "images" : [ { - "size" : "24x24", - "idiom" : "watch", "filename" : "Icon-48.png", - "scale" : "2x", + "idiom" : "watch", "role" : "notificationCenter", + "scale" : "2x", + "size" : "24x24", "subtype" : "38mm" }, { - "size" : "27.5x27.5", - "idiom" : "watch", "filename" : "Icon-55.png", - "scale" : "2x", + "idiom" : "watch", "role" : "notificationCenter", + "scale" : "2x", + "size" : "27.5x27.5", "subtype" : "42mm" }, { - "size" : "29x29", - "idiom" : "watch", "filename" : "58.png", + "idiom" : "watch", "role" : "companionSettings", - "scale" : "2x" + "scale" : "2x", + "size" : "29x29" }, { - "size" : "29x29", - "idiom" : "watch", "filename" : "87.png", + "idiom" : "watch", "role" : "companionSettings", - "scale" : "3x" + "scale" : "3x", + "size" : "29x29" }, { - "size" : "40x40", "idiom" : "watch", - "filename" : "watch.png", + "role" : "notificationCenter", "scale" : "2x", + "size" : "33x33", + "subtype" : "45mm" + }, + { + "filename" : "watch.png", + "idiom" : "watch", "role" : "appLauncher", + "scale" : "2x", + "size" : "40x40", "subtype" : "38mm" }, { - "size" : "44x44", - "idiom" : "watch", "filename" : "Icon-88.png", - "scale" : "2x", + "idiom" : "watch", "role" : "appLauncher", + "scale" : "2x", + "size" : "44x44", "subtype" : "40mm" }, { - "size" : "50x50", "idiom" : "watch", - "filename" : "Icon-173.png", + "role" : "appLauncher", "scale" : "2x", + "size" : "46x46", + "subtype" : "41mm" + }, + { + "filename" : "Icon-173.png", + "idiom" : "watch", "role" : "appLauncher", + "scale" : "2x", + "size" : "50x50", "subtype" : "44mm" }, { - "size" : "86x86", "idiom" : "watch", - "filename" : "Icon-172.png", + "role" : "appLauncher", + "scale" : "2x", + "size" : "51x51", + "subtype" : "45mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", "scale" : "2x", + "size" : "54x54", + "subtype" : "49mm" + }, + { + "filename" : "Icon-172.png", + "idiom" : "watch", "role" : "quickLook", + "scale" : "2x", + "size" : "86x86", "subtype" : "38mm" }, { - "size" : "98x98", - "idiom" : "watch", "filename" : "Icon-196.png", - "scale" : "2x", + "idiom" : "watch", "role" : "quickLook", + "scale" : "2x", + "size" : "98x98", "subtype" : "42mm" }, { + "filename" : "group-copy-2@3x.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", "size" : "108x108", + "subtype" : "44mm" + }, + { "idiom" : "watch", - "filename" : "group-copy-2@3x.png", + "role" : "quickLook", "scale" : "2x", + "size" : "117x117", + "subtype" : "45mm" + }, + { + "idiom" : "watch", "role" : "quickLook", - "subtype" : "44mm" + "scale" : "2x", + "size" : "129x129", + "subtype" : "49mm" }, { - "size" : "1024x1024", - "idiom" : "watch-marketing", "filename" : "1024.png", - "scale" : "1x" + "idiom" : "watch-marketing", + "scale" : "1x", + "size" : "1024x1024" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/ios/BlueWalletWatch/Base.lproj/Interface.storyboard b/ios/BlueWalletWatch/Base.lproj/Interface.storyboard index 2fbb377a420..a7fc234d306 100644 --- a/ios/BlueWalletWatch/Base.lproj/Interface.storyboard +++ b/ios/BlueWalletWatch/Base.lproj/Interface.storyboard @@ -1,10 +1,9 @@ - + - - + @@ -227,6 +226,13 @@ + + + + + + + diff --git a/ios/Widgets/Shared/BlueWalletWatch-Bridging-Header.h b/ios/BlueWalletWatch/BlueWalletWatch-Bridging-Header.h similarity index 100% rename from ios/Widgets/Shared/BlueWalletWatch-Bridging-Header.h rename to ios/BlueWalletWatch/BlueWalletWatch-Bridging-Header.h diff --git a/ios/BlueWalletWatch/BlueWalletWatch.entitlements b/ios/BlueWalletWatch/BlueWalletWatch.entitlements index 0c67376ebac..86bfd6c51c6 100644 --- a/ios/BlueWalletWatch/BlueWalletWatch.entitlements +++ b/ios/BlueWalletWatch/BlueWalletWatch.entitlements @@ -1,5 +1,10 @@ - + + com.apple.security.application-groups + + group.io.bluewallet.bluewallet + + diff --git a/ios/BlueWalletWatch Extension/ComplicationController.swift b/ios/BlueWalletWatch/ComplicationController.swift similarity index 97% rename from ios/BlueWalletWatch Extension/ComplicationController.swift rename to ios/BlueWalletWatch/ComplicationController.swift index 8fe257b93ad..184f6ff54c5 100644 --- a/ios/BlueWalletWatch Extension/ComplicationController.swift +++ b/ios/BlueWalletWatch/ComplicationController.swift @@ -11,6 +11,7 @@ import ClockKit class ComplicationController: NSObject, CLKComplicationDataSource { + private let groupUserDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue) // MARK: - Timeline Configuration func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) { @@ -43,7 +44,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource { for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) { - let marketData: WidgetDataStore? = UserDefaults.standard.codable(forKey: MarketData.string) + let marketData: WidgetDataStore? = groupUserDefaults?.codable(forKey: MarketData.string) let entry: CLKComplicationTimelineEntry let date: Date let valueLabel: String @@ -55,7 +56,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource { valueLabel = price timeLabel = lastUpdated valueSmallLabel = priceAbbreviated - if let preferredFiatCurrency = UserDefaults.standard.string(forKey: "preferredFiatCurrency"), let preferredFiatUnit = fiatUnit(currency: preferredFiatCurrency) { + if let preferredFiatCurrency = groupUserDefaults?.string(forKey: "preferredCurrency"), let preferredFiatUnit = fiatUnit(currency: preferredFiatCurrency) { currencySymbol = preferredFiatUnit.symbol } else { currencySymbol = fiatUnit(currency: "USD")!.symbol diff --git a/ios/BlueWalletWatch/File.swift b/ios/BlueWalletWatch/File.swift new file mode 100644 index 00000000000..0ef0c0c74a7 --- /dev/null +++ b/ios/BlueWalletWatch/File.swift @@ -0,0 +1,8 @@ +// +// File.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 3/19/25. +// Copyright © 2025 BlueWallet. All rights reserved. +// + diff --git a/ios/BlueWalletWatch/Info.plist b/ios/BlueWalletWatch/Info.plist index 83742d4efc7..e5390497333 100644 --- a/ios/BlueWalletWatch/Info.plist +++ b/ios/BlueWalletWatch/Info.plist @@ -8,6 +8,8 @@ BlueWallet CFBundleExecutable $(EXECUTABLE_NAME) + CFBundleGetInfoString + CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -20,14 +22,40 @@ $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) + CLKComplicationPrincipalClass + $(PRODUCT_MODULE_NAME).ComplicationController + CLKComplicationSupportedFamilies + + CLKComplicationFamilyCircularSmall + CLKComplicationFamilyGraphicBezel + CLKComplicationFamilyGraphicCircular + CLKComplicationFamilyGraphicRectangular + CLKComplicationFamilyGraphicCorner + CLKComplicationFamilyExtraLarge + CLKComplicationFamilyGraphicExtraLarge + CLKComplicationFamilyModularLarge + CLKComplicationFamilyModularSmall + CLKComplicationFamilyUtilitarianLarge + CLKComplicationFamilyUtilitarianSmall + CLKComplicationFamilyUtilitarianSmallFlat + + LSApplicationCategoryType + public.app-category.finance + RCTNewArchEnabled + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown + WKApplication + WKCompanionAppBundleIdentifier io.bluewallet.bluewallet - WKWatchKitApp - + bugsnag + + apiKey + 17ba9059f676f1cc4f45d98182388b01 + diff --git a/ios/BlueWalletWatch/InterfaceController.swift b/ios/BlueWalletWatch/InterfaceController.swift new file mode 100644 index 00000000000..0c9b75ed582 --- /dev/null +++ b/ios/BlueWalletWatch/InterfaceController.swift @@ -0,0 +1,64 @@ +// +// InterfaceController.swift +// BlueWalletWatch Extension +// +// Created by Marcos Rodriguez on 3/6/19. +// + +import WatchKit +import WatchConnectivity +import Foundation + +class InterfaceController: WKInterfaceController { + + @IBOutlet weak var walletsTable: WKInterfaceTable! + @IBOutlet weak var noWalletsAvailableLabel: WKInterfaceLabel! + + override func awake(withContext context: Any?) { + super.awake(withContext: context) + // Ensure WatchDataSource is initialized early in the lifecycle + _ = WatchDataSource.shared + } + + override func willActivate() { + super.willActivate() + + // Request fresh data when controller becomes active + WatchDataSource.shared.requestDataFromiOS() + + // Update UI with any existing data + updateUI() + + // Register for notifications + NotificationCenter.default.addObserver(self, selector: #selector(updateUI), name: Notifications.dataUpdated.name, object: nil) + } + + override func didDeactivate() { + super.didDeactivate() + // Clean up observers when controller is no longer active + NotificationCenter.default.removeObserver(self) + } + + @objc private func updateUI() { + let wallets = WatchDataSource.shared.wallets + let isEmpty = wallets.isEmpty + noWalletsAvailableLabel.setHidden(!isEmpty) + walletsTable.setHidden(isEmpty) + + if isEmpty { return } + + walletsTable.setNumberOfRows(wallets.count, withRowType: WalletInformation.identifier) + for index in 0.. Any? { + return rowIndex + } +} diff --git a/ios/BlueWalletWatch Extension/NotificationController.swift b/ios/BlueWalletWatch/NotificationController.swift similarity index 94% rename from ios/BlueWalletWatch Extension/NotificationController.swift rename to ios/BlueWalletWatch/NotificationController.swift index c9b649e1ca4..7726169474b 100644 --- a/ios/BlueWalletWatch Extension/NotificationController.swift +++ b/ios/BlueWalletWatch/NotificationController.swift @@ -3,7 +3,7 @@ // BlueWalletWatch Extension // // Created by Marcos Rodriguez on 3/6/19. -// Copyright © 2019 Facebook. All rights reserved. + // import WatchKit diff --git a/ios/BlueWalletWatch Extension/NumericKeypadInterfaceController.swift b/ios/BlueWalletWatch/NumericKeypadInterfaceController.swift similarity index 98% rename from ios/BlueWalletWatch Extension/NumericKeypadInterfaceController.swift rename to ios/BlueWalletWatch/NumericKeypadInterfaceController.swift index 1a78fddf3f5..55d57a280b2 100644 --- a/ios/BlueWalletWatch Extension/NumericKeypadInterfaceController.swift +++ b/ios/BlueWalletWatch/NumericKeypadInterfaceController.swift @@ -3,7 +3,7 @@ // BlueWalletWatch Extension // // Created by Marcos Rodriguez on 3/23/19. -// Copyright © 2019 Facebook. All rights reserved. + // import WatchKit diff --git a/ios/BlueWalletWatch Extension/Objects/Handoff.swift b/ios/BlueWalletWatch/Objects/Handoff.swift similarity index 100% rename from ios/BlueWalletWatch Extension/Objects/Handoff.swift rename to ios/BlueWalletWatch/Objects/Handoff.swift diff --git a/ios/BlueWalletWatch/Objects/ReceiveInterfaceMode.swift b/ios/BlueWalletWatch/Objects/ReceiveInterfaceMode.swift new file mode 100644 index 00000000000..3daf911f76b --- /dev/null +++ b/ios/BlueWalletWatch/Objects/ReceiveInterfaceMode.swift @@ -0,0 +1,13 @@ +// +// ReceiveInterfaceMode.swift +// BlueWalletWatch Extension +// +// Created by Marcos Rodriguez on 6/15/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +enum ReceiveInterfaceMode { + case Address, QRCode +} diff --git a/ios/BlueWalletWatch/Objects/ReceiveMethod.swift b/ios/BlueWalletWatch/Objects/ReceiveMethod.swift new file mode 100644 index 00000000000..ea7c29ebe9c --- /dev/null +++ b/ios/BlueWalletWatch/Objects/ReceiveMethod.swift @@ -0,0 +1,13 @@ +// +// ReceiveMethod.swift +// BlueWalletWatch Extension +// +// Created by Marcos Rodriguez on 6/15/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +enum ReceiveMethod { + case Onchain, CreateInvoice +} diff --git a/ios/BlueWalletWatch/Objects/ReceiveType.swift b/ios/BlueWalletWatch/Objects/ReceiveType.swift new file mode 100644 index 00000000000..119bedc2d8c --- /dev/null +++ b/ios/BlueWalletWatch/Objects/ReceiveType.swift @@ -0,0 +1,13 @@ +// +// ReceiveType.swift +// BlueWalletWatch Extension +// +// Created by Marcos Rodriguez on 6/15/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +enum ReceiveType { + case Address, PaymentCode +} diff --git a/ios/BlueWalletWatch/Objects/Transaction.swift b/ios/BlueWalletWatch/Objects/Transaction.swift new file mode 100644 index 00000000000..fdadff525f1 --- /dev/null +++ b/ios/BlueWalletWatch/Objects/Transaction.swift @@ -0,0 +1,59 @@ +import Foundation + +/// Represents a transaction with various properties including its type. +/// Conforms to `Codable` and `Identifiable` for encoding/decoding and unique identification. +struct Transaction: Codable, Identifiable, Equatable { + let id: UUID + let time: Date + let memo: String + let type: TransactionType + let amount: Decimal + + /// Initializes a new Transaction instance. + /// - Parameters: + /// - id: Unique identifier for the transaction. Defaults to a new UUID. + /// - time: Timestamp of the transaction. + /// - memo: A memo or note associated with the transaction. + /// - type: The type of the transaction, defined by `TransactionType`. + /// - amount: The amount involved in the transaction as a string. + init(id: UUID = UUID(), time: Date, memo: String, type: TransactionType, amount: Decimal) { + self.id = id + self.time = time + self.memo = memo + self.type = type + self.amount = amount + } +} + +extension Transaction { + static var mock: Transaction { + Transaction( + time: Date(timeIntervalSince1970: 1714398896), // 2024-04-27T12:34:56Z + memo: "Mock Transaction", + type: .sent, + amount: Decimal(string: "-0.001")! + ) + } + + static var mockTransactions: [Transaction] { + [ + .mock, + Transaction( + time: Date(timeIntervalSince1970: 1714308153), // 2024-04-26T11:22:33Z + memo: "Another Mock Transaction", + type: .received, + amount: Decimal(string: "0.002")! + ), + Transaction( + time: Date(timeIntervalSince1970: 1714217482), // 2024-04-25T10:11:22Z + memo: "Third Mock Transaction", + type: .pending, + amount: Decimal.zero + ) + ] + } + +func formattedAmount(for unit: BitcoinUnit) -> String { + return amount.formatted(as: unit) + } +} diff --git a/ios/BlueWalletWatch/Objects/TransactionTableRow.swift b/ios/BlueWalletWatch/Objects/TransactionTableRow.swift new file mode 100644 index 00000000000..f738c10ab41 --- /dev/null +++ b/ios/BlueWalletWatch/Objects/TransactionTableRow.swift @@ -0,0 +1,72 @@ +// +// TransactionTableRow.swift +// BlueWalletWatch Extension +// +// Created by Marcos Rodriguez on 3/10/19. + +// + +import WatchKit + +class TransactionTableRow: NSObject { + + @IBOutlet private weak var transactionAmountLabel: WKInterfaceLabel! + @IBOutlet private weak var transactionMemoLabel: WKInterfaceLabel! + @IBOutlet private weak var transactionTimeLabel: WKInterfaceLabel! + @IBOutlet private weak var transactionTypeImage: WKInterfaceImage! + + static let identifier: String = "TransactionTableRow" + + var amount: String = "" { + willSet { + transactionAmountLabel.setText(newValue) + } + } + + var memo: String = "" { + willSet { + transactionMemoLabel.setText(newValue) + } + } + + var time: String = "" { + willSet { + if type == .pending { + transactionTimeLabel.setText("Pending...") + } else { + transactionTimeLabel.setText(newValue) + } + } + } + + var type: TransactionType = .pending { + willSet { + if newValue == .pending { + transactionTypeImage.setImage(UIImage(named: "pendingConfirmation")) + } else if newValue == .received { + transactionTypeImage.setImage(UIImage(named: "receivedArrow")) + } else if newValue == .sent { + transactionTypeImage.setImage(UIImage(named: "sentArrow")) + } else { + transactionTypeImage.setImage(nil) + } + } + } + +} + +// TransactionTableRow extension for configuration + extension TransactionTableRow { + func configure(with transaction: Transaction) { + amount = "\(transaction.amount)" + + type = transaction.type + + memo = transaction.memo + + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .short + time = formatter.string(from: transaction.time) + } + } diff --git a/ios/BlueWalletWatch/Objects/TransactionType.swift b/ios/BlueWalletWatch/Objects/TransactionType.swift new file mode 100644 index 00000000000..704d06c924a --- /dev/null +++ b/ios/BlueWalletWatch/Objects/TransactionType.swift @@ -0,0 +1,147 @@ +// +// TransactionType.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 11/20/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + + +// Models/TransactionType.swift + +import Foundation + +/// Represents the various types of transactions available in the application. +/// Conforms to `String`, `Codable`, `Equatable`, and `CustomStringConvertible` for easy encoding/decoding, comparisons, and descriptions. +enum TransactionType: Codable, Equatable { + // Transaction state + case pending + case expired + + // Transaction type + case onchain + case offchain + + // Fallback + case unknown(String) // For any unknown or future transaction types + + case sent + case received + + // MARK: - Coding Keys + enum CodingKeys: String, CodingKey { + case rawValue = "type" + } + + // MARK: - Decodable Conformance + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let typeString = try container.decode(String.self, forKey: .rawValue) + self = TransactionType.fromRawString(typeString) + } + + // MARK: - Encodable Conformance + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.rawString, forKey: .rawValue) + } + + static func fromRawString(_ typeString: String) -> TransactionType { + switch typeString.lowercased() { + case "sent": + return .sent + case "received": + return .received + case "pending": + return .pending + case "bitcoind_tx": + return .onchain + case "paid_invoice": + return .offchain + default: + return .unknown(typeString) + } + } + + // MARK: - Computed Property for Raw String + /// Returns the raw string associated with the `TransactionType`. + var rawString: String { + switch self { + case .sent: + return "sent" + case .received: + return "received" + case .pending: + return "pending" + case .onchain: + return "bitcoind_tx" + case .offchain: + return "paid_invoice" + case .unknown(let typeString): + return typeString + case .expired: + return "expired" + } + } +} + +// MARK: - CustomStringConvertible Conformance +extension TransactionType: CustomStringConvertible { + /// Provides a user-friendly description of the `TransactionType`. + var description: String { + switch self { + case .sent: + return "Sent" + case .received: + return "Received" + case .pending: + return "pending" + case .onchain: + return "Onchain" + case .offchain: + return "Offchain" + case .unknown(let typeString): + return typeString + case .expired: + return "Expired" + } + } +} + +// MARK: - Computed Properties for Categorizing Transaction Types +extension TransactionType { + var isIncoming: Bool { + switch self { + case .received: + return true + default: + return false + } + } + + var isOutgoing: Bool { + switch self { + case .sent: + return true + default: + return false + } + } + + var isPending: Bool { + switch self { + case .pending: + return true + default: + return false + } + } + + static var mockSent: TransactionType { + return .sent + } + + static var mockReceived: TransactionType { + return .received + } +} diff --git a/ios/BlueWalletWatch/Objects/Wallet.swift b/ios/BlueWalletWatch/Objects/Wallet.swift new file mode 100644 index 00000000000..bd2e6194d17 --- /dev/null +++ b/ios/BlueWalletWatch/Objects/Wallet.swift @@ -0,0 +1,66 @@ +import Foundation + +/// Represents a wallet with various properties including its type. +/// Conforms to `Codable` and `Identifiable` for encoding/decoding and unique identification. +struct Wallet: Codable, Identifiable, Equatable { + let id: UUID + let label: String + let balance: String + let type: WalletType + let chain: Chain + let preferredBalanceUnit: BitcoinUnit + let receiveAddress: String + let transactions: [Transaction] + let xpub: String + let hideBalance: Bool + let paymentCode: String? + + /// Initializes a new Wallet instance. + /// - Parameters: + /// - id: Unique identifier for the wallet. Defaults to a new UUID. + /// - label: Display label for the wallet. + /// - balance: Current balance of the wallet as a string. + /// - type: The type of the wallet, defined by `WalletType`. + /// - preferredBalanceUnit: The preferred unit for displaying balance (e.g., BTC). + /// - receiveAddress: The address to receive funds. + /// - transactions: An array of transactions associated with the wallet. + /// - xpub: Extended public key for HD wallets. + /// - hideBalance: Indicates whether the balance should be hidden. + /// - paymentCode: Optional payment code associated with the wallet. + init(id: UUID = UUID(), label: String, balance: String, type: WalletType, chain: Chain = .onchain, preferredBalanceUnit: BitcoinUnit = .sats, receiveAddress: String, transactions: [Transaction], xpub: String, hideBalance: Bool, paymentCode: String? = nil) { + self.id = id + self.label = label + self.balance = balance + self.type = type + self.chain = chain + self.preferredBalanceUnit = preferredBalanceUnit + self.receiveAddress = receiveAddress + self.transactions = transactions + self.xpub = xpub + self.hideBalance = hideBalance + self.paymentCode = paymentCode + } +} + +extension Wallet { + static var mock: Wallet { + Wallet( + label: "Mock Wallet", + balance: "1.2345 BTC", + type: .hdSegwitBech32Wallet, + preferredBalanceUnit: .sats, + receiveAddress: "bc1qmockaddressxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + transactions: Transaction.mockTransactions, // Includes multiple transactions + xpub: "xpub6CUGRUonZSQ4TWtTMmzXdrXDtypWKiKp...", + hideBalance: false, + paymentCode: "p2pkh_mock_payment_code" + ) + } +} + +extension Wallet { + var formattedBalance: String { + guard let balanceDecimal = Decimal(string: balance) else { return balance } + return balanceDecimal.formatted(as: preferredBalanceUnit) + } +} diff --git a/ios/BlueWalletWatch/Objects/WalletGradient.swift b/ios/BlueWalletWatch/Objects/WalletGradient.swift new file mode 100644 index 00000000000..a0993a1ac53 --- /dev/null +++ b/ios/BlueWalletWatch/Objects/WalletGradient.swift @@ -0,0 +1,161 @@ +import WatchKit + +// Extension to support hex color initialization for watchOS +extension UIColor { + convenience init(hex: String) { + var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines) + hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "") + + var rgb: UInt64 = 0 + Scanner(string: hexSanitized).scanHexInt64(&rgb) + + let red = CGFloat((rgb & 0xFF0000) >> 16) / 255.0 + let green = CGFloat((rgb & 0x00FF00) >> 8) / 255.0 + let blue = CGFloat(rgb & 0x0000FF) / 255.0 + + self.init(red: red, green: green, blue: blue, alpha: 1.0) + } +} + +struct WalletGradient { + + static let hdSegwitP2SHWallet: [UIColor] = [ + UIColor(hex: "#007AFF"), + UIColor(hex: "#0040FF") + ] + + static let hdSegwitBech32Wallet: [UIColor] = [ + UIColor(hex: "#6CD9FC"), + UIColor(hex: "#44BEE5") + ] + + static let segwitBech32Wallet: [UIColor] = [ + UIColor(hex: "#6CD9FC"), + UIColor(hex: "#44BEE5") + ] + + static let watchOnlyWallet: [UIColor] = [ + UIColor(hex: "#474646"), + UIColor(hex: "#282828") + ] + + static let legacyWallet: [UIColor] = [ + UIColor(hex: "#37E8C0"), + UIColor(hex: "#15BE98") + ] + + static let hdLegacyP2PKHWallet: [UIColor] = [ + UIColor(hex: "#FD7478"), + UIColor(hex: "#E73B40") + ] + + static let hdLegacyBreadWallet: [UIColor] = [ + UIColor(hex: "#FE6381"), + UIColor(hex: "#F99C42") + ] + + static let multisigHdWallet: [UIColor] = [ + UIColor(hex: "#1CE6EB"), + UIColor(hex: "#296FC5"), + UIColor(hex: "#3500A2") + ] + + static let defaultGradients: [UIColor] = [ + UIColor(hex: "#B770F6"), + UIColor(hex: "#9013FE") + ] + + static let lightningCustodianWallet: [UIColor] = [ + UIColor(hex: "#F1AA07"), + UIColor(hex: "#FD7E37") + ] + + static let aezeedWallet: [UIColor] = [ + UIColor(hex: "#8584FF"), + UIColor(hex: "#5351FB") + ] + + // MARK: - Gradient Layer Creation for WatchKit + + /// Creates gradient colors suitable for WatchKit interface + /// - Parameters: + /// - type: The wallet type + /// - Returns: An array of UIColors for the gradient + static func gradientColorsFor(type: WalletType) -> [UIColor] { + return gradientsFor(type: type) + } + + /// Gets the colors for a WKInterfaceGroup gradient + /// - Parameter type: The wallet type + /// - Returns: Colors array suitable for setting on WKInterfaceGroup + static func getWatchKitGroupColors(for type: WalletType) -> [Any] { + return gradientsFor(type: type).map { $0.cgColor as Any } + } + + // MARK: - Gradient Selection + + static func gradientsFor(type: WalletType) -> [UIColor] { + switch type { + case .watchOnlyWallet: + return WalletGradient.watchOnlyWallet + case .legacyWallet: + return WalletGradient.legacyWallet + case .hdLegacyP2PKHWallet: + return WalletGradient.hdLegacyP2PKHWallet + case .hdLegacyBreadWallet: + return WalletGradient.hdLegacyBreadWallet + case .hdSegwitP2SHWallet: + return WalletGradient.hdSegwitP2SHWallet + case .hdSegwitBech32Wallet: + return WalletGradient.hdSegwitBech32Wallet + case .segwitBech32Wallet: + return WalletGradient.segwitBech32Wallet + case .multisigHdWallet: + return WalletGradient.multisigHdWallet + case .aezeedWallet: + return WalletGradient.aezeedWallet + case .lightningCustodianWallet: + return WalletGradient.lightningCustodianWallet + default: + return WalletGradient.defaultGradients + } + } + + // MARK: - Header Color Selection + + /// Returns the primary color for headers based on the wallet type. + /// Typically, the first color of the gradient is used for headers. + /// - Parameter type: The type of the wallet. + /// - Returns: A `UIColor` representing the header color. + static func headerColorFor(type: WalletType) -> UIColor { + let gradient = gradientsFor(type: type) + return gradient.first ?? UIColor.black // Defaults to black if gradient is empty + } + + static func imageStringFor(type: WalletType) -> String { + switch type { + case .hdSegwitP2SHWallet: + return "wallet" + case .segwitBech32Wallet: + return "walletHDSegwitNative" + case .hdSegwitBech32Wallet: + return "walletHD" + case .watchOnlyWallet: + return "walletWatchOnly" + case .lightningCustodianWallet: + return "walletLightningCustodial" + case .multisigHdWallet: + return "watchMultisig" + case .legacyWallet: + return "walletLegacy" + case .hdLegacyP2PKHWallet: + return "walletHDLegacyP2PKH" + case .hdLegacyBreadWallet: + return "walletHDLegacyBread" + case .aezeedWallet: + return "walletAezeed" + case .defaultGradients: + return "walletLegacy" + } + } +} diff --git a/ios/BlueWalletWatch/Objects/WalletInformation.swift b/ios/BlueWalletWatch/Objects/WalletInformation.swift new file mode 100644 index 00000000000..5b709d0c213 --- /dev/null +++ b/ios/BlueWalletWatch/Objects/WalletInformation.swift @@ -0,0 +1,42 @@ +// +// WalletInformation.swift +// BlueWalletWatch Extension +// +// Created by Marcos Rodriguez on 3/10/19. + +// + +import WatchKit + +class WalletInformation: NSObject { + + @IBOutlet weak var walletBalanceLabel: WKInterfaceLabel! + @IBOutlet private weak var walletNameLabel: WKInterfaceLabel! + @IBOutlet private weak var walletGroup: WKInterfaceGroup! + static let identifier: String = "WalletInformation" + let type: Wallet? = nil + + var name: String = "" { + willSet { + walletNameLabel.setText(newValue) + } + } + + var balance: String = "" { + willSet { + walletBalanceLabel.setText(newValue) + } + } + + + +} + +// WalletInformation extension for configuration +extension WalletInformation { + func configure(with wallet: Wallet) { + walletBalanceLabel.setHidden(wallet.hideBalance) + name = wallet.label + balance = wallet.hideBalance ? "" : wallet.balance + } +} diff --git a/ios/BlueWalletWatch/Objects/WalletType.swift b/ios/BlueWalletWatch/Objects/WalletType.swift new file mode 100644 index 00000000000..291dd8dbfa4 --- /dev/null +++ b/ios/BlueWalletWatch/Objects/WalletType.swift @@ -0,0 +1,194 @@ +// +// WalletType.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 11/20/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + + +import Foundation + +/// Represents the various types of wallets available in the application. +/// Conforms to `Codable` and `Equatable`, handling encoding and decoding for known and unknown types. +enum WalletType: Codable, Equatable { + case hdSegwitP2SHWallet + case hdSegwitBech32Wallet + case segwitBech32Wallet + case watchOnlyWallet + case legacyWallet + case hdLegacyP2PKHWallet + case hdLegacyBreadWallet + case multisigHdWallet + case lightningCustodianWallet + case aezeedWallet + case defaultGradients + + // MARK: - Coding Keys + enum CodingKeys: String, CodingKey { + case rawValue = "type" + } + + // MARK: - Decodable Conformance + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let typeString = try container.decode(String.self, forKey: .rawValue) + + switch typeString { + case "HDsegwitP2SH": + self = .hdSegwitP2SHWallet + case "HDsegwitBech32": + self = .hdSegwitBech32Wallet + case "segwitBech32": + self = .segwitBech32Wallet + case "watchOnly": + self = .watchOnlyWallet + case "legacy": + self = .legacyWallet + case "HDLegacyP2PKH": + self = .hdLegacyP2PKHWallet + case "HDLegacyBreadwallet": + self = .hdLegacyBreadWallet + case "HDmultisig": + self = .multisigHdWallet + case "LightningCustodianWallet": + self = .lightningCustodianWallet + case "HDAezeedWallet": + self = .aezeedWallet + default: + self = .defaultGradients + } + } + + // MARK: - Encodable Conformance + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .hdSegwitP2SHWallet: + try container.encode("HDsegwitP2SH", forKey: .rawValue) + case .hdSegwitBech32Wallet: + try container.encode("HDsegwitBech32", forKey: .rawValue) + case .segwitBech32Wallet: + try container.encode("segwitBech32", forKey: .rawValue) + case .watchOnlyWallet: + try container.encode("watchOnly", forKey: .rawValue) + case .legacyWallet: + try container.encode("legacy", forKey: .rawValue) + case .hdLegacyP2PKHWallet: + try container.encode("HDLegacyP2PKH", forKey: .rawValue) + case .hdLegacyBreadWallet: + try container.encode("HDLegacyBreadwallet", forKey: .rawValue) + case .multisigHdWallet: + try container.encode("HDmultisig", forKey: .rawValue) + case .lightningCustodianWallet: + try container.encode("LightningCustodianWallet", forKey: .rawValue) + case .aezeedWallet: + try container.encode("HDAezeedWallet", forKey: .rawValue) + case .defaultGradients: + try container.encode("DefaultGradients", forKey: .rawValue) + } + } + + // MARK: - Custom Initializer from Raw String + /// Initializes a `WalletType` from a raw string. + /// - Parameter rawString: The raw string representing the wallet type. + init(rawString: String) { + self = WalletType.fromRawString(rawString) + } + + static func fromRawString(_ typeString: String) -> WalletType { + switch typeString { + case "HDsegwitP2SH": + return .hdSegwitP2SHWallet + case "HDsegwitBech32": + return .hdSegwitBech32Wallet + case "segwitBech32": + return .segwitBech32Wallet + case "watchOnly": + return .watchOnlyWallet + case "legacy": + return .legacyWallet + case "HDLegacyP2PKH": + return .hdLegacyP2PKHWallet + case "HDLegacyBreadwallet": + return .hdLegacyBreadWallet + case "HDmultisig": + return .multisigHdWallet + case "LightningCustodianWallet": + return .lightningCustodianWallet + case "HDAezeedWallet": + return .aezeedWallet + case "DefaultGradients": + return .defaultGradients + default: + return .defaultGradients + } + } + + // MARK: - Computed Property for Raw String + /// Returns the raw string associated with the `WalletType`. + var rawString: String { + switch self { + case .hdSegwitP2SHWallet: + return "HDsegwitP2SH" + case .hdSegwitBech32Wallet: + return "HDsegwitBech32" + case .segwitBech32Wallet: + return "segwitBech32" + case .watchOnlyWallet: + return "watchOnly" + case .legacyWallet: + return "legacy" + case .hdLegacyP2PKHWallet: + return "HDLegacyP2PKH" + case .hdLegacyBreadWallet: + return "HDLegacyBreadwallet" + case .multisigHdWallet: + return "HDmultisig" + case .lightningCustodianWallet: + return "LightningCustodianWallet" + case .aezeedWallet: + return "HDAezeedWallet" + case .defaultGradients: + return "DefaultGradients" + } + } +} + +// MARK: - CustomStringConvertible Conformance +extension WalletType: CustomStringConvertible { + /// Provides a user-friendly description of the `WalletType`. + var description: String { + switch self { + case .hdSegwitP2SHWallet: + return "HD Segwit P2SH Wallet" + case .hdSegwitBech32Wallet: + return "HD Segwit Bech32 Wallet" + case .segwitBech32Wallet: + return "Segwit Bech32 Wallet" + case .watchOnlyWallet: + return "Watch Only Wallet" + case .legacyWallet: + return "Legacy Wallet" + case .hdLegacyP2PKHWallet: + return "HD Legacy P2PKH Wallet" + case .hdLegacyBreadWallet: + return "HD Legacy Bread Wallet" + case .multisigHdWallet: + return "Multisig HD Wallet" + case .lightningCustodianWallet: + return "Lightning Custodian Wallet" + case .aezeedWallet: + return "Aezeed Wallet" + case .defaultGradients: + return "Default Gradients" + } + } +} + +extension WalletType { + static var mockType: WalletType { + return .hdSegwitBech32Wallet + } +} \ No newline at end of file diff --git a/ios/BlueWalletWatch/Objects/WatchDataSource.swift b/ios/BlueWalletWatch/Objects/WatchDataSource.swift new file mode 100644 index 00000000000..f77c0cc2645 --- /dev/null +++ b/ios/BlueWalletWatch/Objects/WatchDataSource.swift @@ -0,0 +1,432 @@ +// Data/WatchDataSource.swift + +import Foundation +import WatchConnectivity +import Security +import Combine +import ClockKit + +struct NotificationName { + static let dataUpdated = Notification.Name(rawValue: "Notification.WalletDataSource.Updated") +} +struct Notifications { + static let dataUpdated = Notification(name: NotificationName.dataUpdated) +} + +/// Represents the group user defaults keys. +/// Ensure these match the keys used in your iOS app for sharing data. + +/// Handles WatchConnectivity and data synchronization between iOS and Watch apps. +class WatchDataSource: NSObject, ObservableObject, WCSessionDelegate { + // MARK: - Singleton Instance + + static func postDataUpdatedNotification() { + NotificationCenter.default.post(Notifications.dataUpdated) + } + + + static let shared = WatchDataSource() + + // MARK: - Published Properties + + /// The list of wallets to be displayed on the Watch app. + @Published var wallets: [Wallet] = [] + + @Published var isDataLoaded: Bool = false + + // MARK: - Private Properties + + private let groupUserDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue) + private let keychain = KeychainHelper.shared + private let session: WCSession + private var cancellables = Set() + + // MARK: - Initializer + + private override init() { + guard WCSession.isSupported() else { + print("WCSession is not supported on this device.") + self.session = WCSession.default + super.init() + return + } + self.session = WCSession.default + super.init() + + // Set delegate and setup bindings before trying to load data + self.session.delegate = self + setupBindings() + + // Load cached data from keychain to show something while waiting for fresh data + loadKeychainData() + } + + // MARK: - Public Methods + + /// Starts the WatchConnectivity session. + func startSession() { + if session.activationState != .activated { + print("[WatchKit 2] Activating WCSession...") + session.activate() + } else { + print("[WatchKit 2] WCSession is already activated: \(session.activationState.rawValue)") + // Even if activated, attempt to request data + if session.isReachable { + requestDataFromiOS() + } + } + } + + /// Deactivates the WatchConnectivity session (if needed). + /// Note: WCSession does not provide a deactivate method, but you can handle any necessary cleanup here. + func deactivateSession() { + // Handle any necessary cleanup here. + } + + // MARK: - Data Binding + + /// Sets up bindings to observe changes to `wallets` and perform actions accordingly. + private func setupBindings() { + // Observe changes to wallets and perform actions if needed. + $wallets + .sink { [weak self] updatedWallets in + self?.saveWalletsToKeychain() + self?.reloadComplications() + } + .store(in: &cancellables) + } + + // MARK: - Keychain Operations + + /// Loads wallets data from the Keychain asynchronously. + private func loadKeychainData() { + DispatchQueue.global(qos: .background).async { [weak self] in + guard let self = self else { return } + guard let existingData = self.keychain.retrieve(service: UserDefaultsGroupKey.WatchAppBundleIdentifier.rawValue, account: UserDefaultsGroupKey.BundleIdentifier.rawValue), + let decodedWallets = try? JSONDecoder().decode([Wallet].self, from: existingData) else { + print("No existing wallets data found in Keychain.") + return + } + + // Filter wallets to include only on-chain wallets. + let onChainWallets = decodedWallets.filter { $0.chain == .onchain } + + DispatchQueue.main.async { + if onChainWallets != self.wallets { + self.wallets = onChainWallets + print("Loaded \(onChainWallets.count) on-chain wallets from Keychain.") + } + self.isDataLoaded = true + } + } + } + + /// Saves the current wallets data to the Keychain asynchronously. + private func saveWalletsToKeychain() { + DispatchQueue.global(qos: .background).async { [weak self] in + guard let self = self else { return } + // Save to keychain regardless of session state + guard let encodedData = try? JSONEncoder().encode(self.wallets) else { + print("Failed to encode wallets.") + return + } + let success = self.keychain.save(encodedData, service: UserDefaultsGroupKey.WatchAppBundleIdentifier.rawValue, account: UserDefaultsGroupKey.BundleIdentifier.rawValue) + if success { + print("Successfully saved wallets to Keychain.") + } else { + print("Failed to save wallets to Keychain.") + } + } + } + + // MARK: - WatchConnectivity Methods + + /// Handles the activation completion of the WCSession. + func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { + if let error = error { + print("[WatchKit 2] WCSession activation failed with error: \(error.localizedDescription)") + } else { + print("[WatchKit 2] WCSession activated with state: \(activationState.rawValue)") + + if activationState == .activated { + DispatchQueue.main.async { + self.requestDataFromiOS() + } + } + } + } + + /// Handles received messages from the iOS app. + func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { + processReceivedData(message) + } + + /// Handles received application context updates from the iOS app. + func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) { + print("[WatchKit 2] Received application context: \(applicationContext.keys)") + if applicationContext.isEmpty { return } + + DispatchQueue.main.async { + self.processReceivedData(applicationContext) + // Post notification that data was updated + WatchDataSource.postDataUpdatedNotification() + } + } + + /// Requests current data from the iOS app + func requestDataFromiOS() { + guard session.activationState == .activated else { + print("[WatchKit 2] Cannot request data: WCSession not activated (state: \(session.activationState.rawValue))") + startSession() // Try to activate the session + return + } + + let message = ["message": "sendApplicationContext"] + + // First check if we can use direct messaging + if session.isReachable { + print("[WatchKit 2] iOS app is reachable, sending direct message") + session.sendMessage(message, replyHandler: { [weak self] _ in + print("[WatchKit 2] Successfully requested application context from iOS app") + // Notify that we might have received data + DispatchQueue.main.async { + WatchDataSource.postDataUpdatedNotification() + } + }, errorHandler: { error in + print("[WatchKit 2] Error requesting application context: \(error.localizedDescription)") + + // Fallback to application context as a backup + self.sendApplicationContextRequest() + }) + } else { + print("[WatchKit 2] iOS app is not reachable, using application context") + sendApplicationContextRequest() + } + } + + private func sendApplicationContextRequest() { + do { + try session.updateApplicationContext(["message": "sendApplicationContext"]) + print("[WatchKit 2] Sent context update request to iOS app") + } catch { + print("[WatchKit 2] Failed to send context update request: \(error.localizedDescription)") + } + } + + // Enhance session reachability notification + func sessionReachabilityDidChange(_ session: WCSession) { + print("[WatchKit 2] Session reachability changed: \(session.isReachable)") + + if session.isReachable { + // If iOS app becomes reachable, request fresh data + requestDataFromiOS() + } + } + + // MARK: - Data Processing + + /// Processes received data from the iOS app. + /// - Parameter data: The data received either as a message or application context. + private func processReceivedData(_ data: [String: Any]) { + if let preferredFiatCurrency = data["preferredFiatCurrency"] as? String { + // Handle preferred fiat currency update. + groupUserDefaults?.set(preferredFiatCurrency, forKey: "preferredCurrency") + + // Fetch and update market data based on the new preferred currency. + updateMarketData(for: preferredFiatCurrency) + } else { + // Assume the data contains wallets information. + processWalletsData(walletsInfo: data) + } + } + + /// Processes wallets data received from the iOS app. + /// - Parameter walletsInfo: The wallets data received as a dictionary. + private func processWalletsData(walletsInfo: [String: Any]) { + guard let walletsToProcess = walletsInfo["wallets"] as? [[String: Any]] else { + print("No wallets data found in received context.") + return + } + + var processedWallets: [Wallet] = [] + + for entry in walletsToProcess { + guard let label = entry["label"] as? String, + let balance = entry["balance"] as? Double, + let typeString = entry["type"] as? String, + let preferredBalanceUnitString = entry["preferredBalanceUnit"] as? String, + let chainString = entry["chain"] as? String, + let transactions = entry["transactions"] as? [[String: Any]] else { + print("Incomplete wallet entry found. Skipping.") + continue + } + + var transactionsProcessed: [Transaction] = [] + for transactionEntry in transactions { + guard let timeString = transactionEntry["time"] as? String, + let memo = transactionEntry["memo"] as? String, + let amountDouble = transactionEntry["amount"] as? Double, + let type = transactionEntry["type"] as? String else { + print("Incomplete transaction entry found. Skipping.") + continue + } + + guard let time = ISO8601DateFormatter().date(from: timeString) else { + print("Invalid date format for transaction. Skipping.") + continue + } + + let amount = Decimal(amountDouble) + + let transactionType = TransactionType.fromRawString(type) + + let transaction = Transaction(time: time, memo: memo, type: transactionType, amount: amount) + transactionsProcessed.append(transaction) + } + + let receiveAddress = entry["receiveAddress"] as? String ?? "" + let xpub = entry["xpub"] as? String ?? "" + let hideBalance = entry["hideBalance"] as? Bool ?? false + let paymentCode = entry["paymentCode"] as? String + let chain = Chain(rawString: chainString) + + let wallet = Wallet( + label: label, + balance: "\(balance) BTC", + type: WalletType(rawString: typeString), + chain: chain, + preferredBalanceUnit: BitcoinUnit(rawString: preferredBalanceUnitString), + receiveAddress: receiveAddress, + transactions: transactionsProcessed, + xpub: xpub, + hideBalance: hideBalance, + paymentCode: paymentCode + ) + processedWallets.append(wallet) + } + + // Update the published `wallets` property on the main thread. + DispatchQueue.main.async { [weak self] in + self?.wallets = processedWallets + print("Updated wallets from received context.") + WatchDataSource.postDataUpdatedNotification() + } + } + + /// Fetches market data based on the preferred fiat currency. + /// - Parameter fiatCurrency: The preferred fiat currency string. + private func updateMarketData(for fiatCurrency: String) { + guard !fiatCurrency.isEmpty else { + print("Invalid fiat currency provided") + return + } + + MarketAPI.fetchPrice(currency: fiatCurrency) { [weak self] (marketData, error) in + guard let self = self else { return } + if let error = error { + print("Failed to fetch market data: \(error.localizedDescription)") + // Consider implementing retry logic or fallback mechanism + return + } + + guard let marketData = marketData as? MarketData else { + print("Invalid market data format received") + return + } + + do { + let widgetData = WidgetDataStore(rate: "\(marketData.rate)", lastUpdate: marketData.dateString, rateDouble: marketData.rate) + if let encodedData = try? JSONEncoder().encode(widgetData) { + self.groupUserDefaults?.set(encodedData, forKey: MarketData.string) + print("Market data updated for currency: \(fiatCurrency)") + } else { + throw NSError(domain: "WatchDataSource", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to encode market data"]) + } + } catch { + print("Failed to process market data: \(error.localizedDescription)") + } + } + } + + // MARK: - Wallet Actions + + /// Requests a Lightning Invoice from the iOS app. + /// - Parameters: + /// - walletIdentifier: The index of the wallet in the `wallets` array. + /// - amount: The amount for the invoice. + /// - description: An optional description for the invoice. + /// - responseHandler: A closure to handle the invoice string received from the iOS app. + func requestLightningInvoice(walletIdentifier: Int, amount: Double, description: String?, responseHandler: @escaping (_ invoice: String) -> Void) { + let timeoutSeconds = 30.0 + let timeoutTimer = Timer.scheduledTimer(withTimeInterval: timeoutSeconds, repeats: false) { _ in + print("Lightning invoice request timed out") + responseHandler("") + } + + guard wallets.indices.contains(walletIdentifier) else { + timeoutTimer.invalidate() + responseHandler("") + return + } + let message: [String: Any] = [ + "request": "createInvoice", + "walletIndex": walletIdentifier, + "amount": amount, + "description": description ?? "" + ] + session.sendMessage(message, replyHandler: { reply in + timeoutTimer.invalidate() + if let invoicePaymentRequest = reply["invoicePaymentRequest"] as? String, !invoicePaymentRequest.isEmpty { + responseHandler(invoicePaymentRequest) + } else { + responseHandler("") + } + }, errorHandler: { error in + timeoutTimer.invalidate() + print("Error requesting Lightning Invoice: \(error.localizedDescription)") + responseHandler("") + }) + } + + /// Toggles the visibility of the wallet's balance. + /// - Parameters: + /// - walletIdentifier: The index of the wallet in the `wallets` array. + /// - hideBalance: A boolean indicating whether to hide the balance. + func toggleWalletHideBalance(walletIdentifier: UUID, hideBalance: Bool, responseHandler: @escaping (_ success: Bool) -> Void) { + guard wallets.indices.contains(walletIdentifier.hashValue) else { + responseHandler(false) + return + } + let message: [String: Any] = [ + "message": "hideBalance", + "walletIndex": walletIdentifier, + "hideBalance": hideBalance + ] + session.sendMessage(message, replyHandler: { reply in + responseHandler(true) + }, errorHandler: { error in + print("Error toggling hide balance: \(error.localizedDescription)") + responseHandler(false) + }) + } + + // MARK: - Complications Reload + + /// Reloads all active complications on the Watch face. + private func reloadComplications() { + let server = CLKComplicationServer.sharedInstance() + server.activeComplications?.forEach { complication in + server.reloadTimeline(for: complication) + print("[Complication] Reloaded timeline for \(complication.family.rawValue)") + } + } + +} + +extension WatchDataSource { + static var mock: WatchDataSource { + let mockDataSource = WatchDataSource() + mockDataSource.wallets = [Wallet.mock] + return mockDataSource + } +} diff --git a/ios/BlueWalletWatch Extension/PushNotificationPayload.apns b/ios/BlueWalletWatch/PushNotificationPayload.apns similarity index 100% rename from ios/BlueWalletWatch Extension/PushNotificationPayload.apns rename to ios/BlueWalletWatch/PushNotificationPayload.apns diff --git a/ios/BlueWalletWatch/ReceiveInterfaceController.swift b/ios/BlueWalletWatch/ReceiveInterfaceController.swift new file mode 100644 index 00000000000..5852a790ab6 --- /dev/null +++ b/ios/BlueWalletWatch/ReceiveInterfaceController.swift @@ -0,0 +1,104 @@ +// +// ReceiveInterfaceController.swift +// BlueWalletWatch Extension +// +// Created by Marcos Rodriguez on 3/12/19. + +import WatchKit +import WatchConnectivity +import Foundation +import EFQRCode + +class ReceiveInterfaceController: WKInterfaceController { + + static let identifier = "ReceiveInterfaceController" + private var wallet: Wallet? + private var receiveMethod: ReceiveMethod = .Onchain + private var interfaceMode: ReceiveInterfaceMode = .Address + var receiveType: ReceiveType = .Address + @IBOutlet weak var addressLabel: WKInterfaceLabel! + @IBOutlet weak var loadingIndicator: WKInterfaceGroup! + @IBOutlet weak var imageInterface: WKInterfaceImage! + private let userActivity: NSUserActivity = NSUserActivity(activityType: HandoffIdentifier.ReceiveOnchain.rawValue) + + override func willActivate() { + super.willActivate() + userActivity.title = HandOffTitle.ReceiveOnchain.rawValue + userActivity.requiredUserInfoKeys = [HandOffUserInfoKey.ReceiveOnchain.rawValue] + userActivity.isEligibleForHandoff = true + update(userActivity) + } + + override func awake(withContext context: Any?) { + super.awake(withContext: context) + guard let passedContext = context as? (Int, ReceiveMethod, ReceiveType) else { + pop() + return + } + let wallet = WatchDataSource.shared.wallets[passedContext.0] + self.wallet = wallet + receiveMethod = passedContext.1 + receiveType = passedContext.2 + setupView() + } + + private func setupView() { + if receiveMethod == .CreateInvoice && (wallet?.type == .lightningCustodianWallet) { + presentController(withName: SpecifyInterfaceController.identifier, context: wallet?.id) + } else { + setupQRCode() + setupMenuItems() + } + } + + private func setupQRCode() { + guard let address = receiveType == .Address ? wallet?.receiveAddress : wallet?.paymentCode else { return } + addressLabel.setText(address) + generateQRCode(from: address) + } + + private func generateQRCode(from content: String) { + DispatchQueue.global(qos: .userInteractive).async { + guard let cgImage = EFQRCode.generate(for: content) else { return } + DispatchQueue.main.async { + let image = UIImage(cgImage: cgImage) + self.imageInterface.setImage(image) + self.loadingIndicator.setHidden(true) + self.imageInterface.setHidden(false) + } + } + } + + private func setupMenuItems() { + clearAllMenuItems() + addMenuItem(with: .shuffle, title: "Toggle View", action: #selector(toggleViewButtonPressed)) + } + + @IBAction @objc func toggleViewButtonPressed() { + interfaceMode = interfaceMode == .QRCode ? .Address : .QRCode + updateView() + } + + private func updateView() { + addressLabel.setHidden(interfaceMode != .Address) + imageInterface.setHidden(interfaceMode != .QRCode) + } + + override func didAppear() { + super.didAppear() + if isCreatingInvoice() { + presentController(withName: SpecifyInterfaceController.identifier, context: wallet?.id) + } + } + + private func isCreatingInvoice() -> Bool { + return receiveMethod == .CreateInvoice && (wallet?.type == .lightningCustodianWallet) + } + + override func didDeactivate() { + super.didDeactivate() + NotificationCenter.default.removeObserver(self) + userActivity.invalidate() + } +} + diff --git a/ios/BlueWalletWatch/ReceivePageInterfaceController.swift b/ios/BlueWalletWatch/ReceivePageInterfaceController.swift new file mode 100644 index 00000000000..fe55a41437e --- /dev/null +++ b/ios/BlueWalletWatch/ReceivePageInterfaceController.swift @@ -0,0 +1,29 @@ +// +// ReceivePageViewController.swift +// BlueWalletWatch Extension +// +// Created by Marcos Rodriguez on 6/15/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation +import WatchKit + +class ReceivePageInterfaceController: WKInterfaceController { + static let identifier = "ReceivePageInterfaceController" + var pageNames = ["Address", "Payment Code"] + var pageControllers = ["ReceiveInterfaceController", "ReceiveInterfaceController"] + + override func awake(withContext context: Any?) { + super.awake(withContext: context) + + let wallet = context as? Wallet + + WKInterfaceController.reloadRootPageControllers( + withNames: pageControllers, + contexts: [(wallet,ReceiveMethod.Onchain , ReceiveType.Address), (wallet, ReceiveMethod.Onchain, ReceiveType.PaymentCode)], + orientation: .horizontal, + pageIndex: 0 + ) + } +} diff --git a/ios/BlueWalletWatch Extension/SpecifyInterfaceController.swift b/ios/BlueWalletWatch/SpecifyInterfaceController.swift similarity index 80% rename from ios/BlueWalletWatch Extension/SpecifyInterfaceController.swift rename to ios/BlueWalletWatch/SpecifyInterfaceController.swift index cf13fbea970..9d13c64c02b 100644 --- a/ios/BlueWalletWatch Extension/SpecifyInterfaceController.swift +++ b/ios/BlueWalletWatch/SpecifyInterfaceController.swift @@ -3,7 +3,7 @@ // BlueWalletWatch Extension // // Created by Marcos Rodriguez on 3/23/19. -// Copyright © 2019 Facebook. All rights reserved. + // import WatchKit @@ -40,7 +40,7 @@ class SpecifyInterfaceController: WKInterfaceController { let wallet = WatchDataSource.shared.wallets[identifier] self.wallet = wallet self.createButton.setAlpha(0.5) - self.specifiedQRContent.bitcoinUnit = (wallet.type == WalletGradient.LightningCustodial.rawValue || wallet.type == WalletGradient.LightningLDK.rawValue) ? .SATS : .BTC + self.specifiedQRContent.bitcoinUnit = (wallet.type == .lightningCustodianWallet) ? .SATS : .BTC NotificationCenter.default.addObserver(forName: NumericKeypadInterfaceController.NotificationName.keypadDataChanged, object: nil, queue: nil) { [weak self] (notification) in guard let amountObject = notification.object as? [String], !amountObject.isEmpty else { return } if amountObject.count == 1 && (amountObject.first == "." || amountObject.first == "0") { @@ -60,7 +60,7 @@ class SpecifyInterfaceController: WKInterfaceController { var isShouldCreateButtonBeEnabled = amountDouble > 0 && !title.isEmpty - if (wallet.type == WalletGradient.LightningCustodial.rawValue || wallet.type == WalletGradient.LightningLDK.rawValue) && !WCSession.default.isReachable { + if (wallet.type == .lightningCustodianWallet) && !WCSession.default.isReachable { isShouldCreateButtonBeEnabled = false } @@ -89,14 +89,8 @@ class SpecifyInterfaceController: WKInterfaceController { } @IBAction func createButtonTapped() { - if WatchDataSource.shared.companionWalletsInitialized { - NotificationCenter.default.post(name: NotificationName.createQRCode, object: specifiedQRContent) - dismiss() - } else { - presentAlert(withTitle: "Error", message: "Unable to create invoice. Please open BlueWallet on your iPhone and unlock your wallets.", preferredStyle: .alert, actions: [WKAlertAction(title: "OK", style: .default, handler: { [weak self] in - self?.dismiss() - })]) - } + NotificationCenter.default.post(name: NotificationName.createQRCode, object: specifiedQRContent) + dismiss() } override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? { diff --git a/ios/BlueWalletWatch/ViewQRCodefaceController.swift b/ios/BlueWalletWatch/ViewQRCodefaceController.swift new file mode 100644 index 00000000000..be326619d42 --- /dev/null +++ b/ios/BlueWalletWatch/ViewQRCodefaceController.swift @@ -0,0 +1,95 @@ +// BlueWalletWatch Extension +// +// Created by Marcos Rodriguez on 3/11/19. + +import WatchKit +import Foundation +import EFQRCode + +class ViewQRCodefaceController: WKInterfaceController { + + static let identifier = "ViewQRCodefaceController" + @IBOutlet weak var imageInterface: WKInterfaceImage! + @IBOutlet weak var addressLabel: WKInterfaceLabel! + + var address: String? { + didSet { + updateQRCode() + updateUserActivity() + } + } + + private var interfaceMode = ReceiveInterfaceMode.Address + private let userActivity: NSUserActivity = NSUserActivity(activityType: HandoffIdentifier.Xpub.rawValue) + + override func awake(withContext context: Any?) { + super.awake(withContext: context) + configureUserActivity() + guard let passedContext = context as? String else { + pop() + return + } + address = passedContext + addressLabel.setText(passedContext) + toggleViewButtonPressed() + } + + private func configureUserActivity() { + userActivity.title = HandOffTitle.Xpub.rawValue + userActivity.requiredUserInfoKeys = [HandOffUserInfoKey.Xpub.rawValue] + userActivity.isEligibleForHandoff = true + } + + private func updateUserActivity() { + if let address = address, !address.isEmpty { + userActivity.userInfo = [HandOffUserInfoKey.Xpub.rawValue: address] + userActivity.becomeCurrent() + } else { + userActivity.invalidate() + } + } + + private func updateQRCode() { + guard let address = address, !address.isEmpty else { + imageInterface.setImage(nil) + return + } + DispatchQueue.global(qos: .userInteractive).async { + guard let cgImage = EFQRCode.generate(for: address) else { + return + } + DispatchQueue.main.async { + let image = UIImage(cgImage: cgImage) + self.imageInterface.setImage(image) + } + } + } + + @IBAction @objc func toggleViewButtonPressed() { + clearAllMenuItems() + interfaceMode = interfaceMode == .QRCode ? .Address : .QRCode + + let menuItemTitle = interfaceMode == .QRCode ? "QR Code" : "Address" + let systemImageName = interfaceMode == .QRCode ? "textformat.subscript" : "qrcode" + let defaultMenuItemIcon = interfaceMode == .QRCode ? WKMenuItemIcon.shuffle : WKMenuItemIcon.shuffle + + addressLabel.setHidden(interfaceMode != .Address) + imageInterface.setHidden(interfaceMode != .QRCode) + + if #available(watchOSApplicationExtension 6.0, *), let image = UIImage(systemName: systemImageName) { + addMenuItem(with: image, title: menuItemTitle, action: #selector(toggleViewButtonPressed)) + } else { + addMenuItem(with: defaultMenuItemIcon, title: menuItemTitle, action: #selector(toggleViewButtonPressed)) + } + } + + override func willActivate() { + super.willActivate() + updateUserActivity() + } + + override func didDeactivate() { + super.didDeactivate() + userActivity.invalidate() + } +} diff --git a/ios/BlueWalletWatch/WalletDetailsInterfaceController.swift b/ios/BlueWalletWatch/WalletDetailsInterfaceController.swift new file mode 100644 index 00000000000..5abdd9e6d6b --- /dev/null +++ b/ios/BlueWalletWatch/WalletDetailsInterfaceController.swift @@ -0,0 +1,128 @@ +// BlueWalletWatch Extension +// +// Created by Marcos Rodriguez on 3/11/19. + +import WatchKit +import Foundation +import WatchConnectivity + +class WalletDetailsInterfaceController: WKInterfaceController { + + var wallet: Wallet? + static let identifier = "WalletDetailsInterfaceController" + @IBOutlet weak var walletBasicsGroup: WKInterfaceGroup! + @IBOutlet weak var walletBalanceLabel: WKInterfaceLabel! + @IBOutlet weak var createInvoiceButton: WKInterfaceButton! + @IBOutlet weak var walletNameLabel: WKInterfaceLabel! + @IBOutlet weak var receiveButton: WKInterfaceButton! + @IBOutlet weak var viewXPubButton: WKInterfaceButton! + @IBOutlet weak var noTransactionsLabel: WKInterfaceLabel! + @IBOutlet weak var transactionsTable: WKInterfaceTable! + + override func awake(withContext context: Any?) { + super.awake(withContext: context) + guard let identifier = context as? UUID else { + pop() + return + } + loadWalletDetails(identifier: identifier) + } + + private func loadWalletDetails(identifier: UUID) { + let index = WatchDataSource.shared.wallets.firstIndex(where: { $0.id == identifier }) ?? 0 + let wallet = WatchDataSource.shared.wallets[index] + self.wallet = wallet + updateWalletUI(wallet: wallet) + updateTransactionsTable(forWallet: wallet) + } + + private func updateWalletUI(wallet: Wallet) { + walletBalanceLabel.setHidden(wallet.hideBalance) + walletBalanceLabel.setText(wallet.hideBalance ? "" : wallet.balance) + walletNameLabel.setText(wallet.label) +// walletBasicsGroup.setBackgroundImageNamed(WalletGradient(rawValue: wallet.type)?) + + let isLightningWallet = wallet.type == .lightningCustodianWallet + createInvoiceButton.setHidden(!isLightningWallet) + receiveButton.setHidden(wallet.receiveAddress.isEmpty) + viewXPubButton.setHidden(!isXPubAvailable(wallet: wallet)) + } + + private func isXPubAvailable(wallet: Wallet) -> Bool { + return (wallet.type != .lightningCustodianWallet) && !(wallet.xpub).isEmpty + } + + private func updateTransactionsTable(forWallet wallet: Wallet) { + let transactions = wallet.transactions + transactionsTable.setNumberOfRows(transactions.count, withRowType: TransactionTableRow.identifier) + + for index in 0.. Any? { + guard let wallet = wallet else { return nil } + return (wallet.id, ReceiveMethod.Onchain) + } + +} diff --git a/ios/BlueWalletWatch/en_US.lproj/Interface.strings b/ios/BlueWalletWatch/en_US.lproj/Interface.strings new file mode 100644 index 00000000000..b766702be55 --- /dev/null +++ b/ios/BlueWalletWatch/en_US.lproj/Interface.strings @@ -0,0 +1,105 @@ + +/* Class = "WKInterfaceButton"; title = "Amount"; ObjectID = "0Hm-hv-Yi3"; */ +"0Hm-hv-Yi3.title" = "Amount"; + +/* Class = "WKInterfaceButton"; title = "8"; ObjectID = "3FQ-tZ-9kd"; */ +"3FQ-tZ-9kd.title" = "8"; + +/* Class = "WKInterfaceButton"; title = "Create"; ObjectID = "6eh-lx-UEe"; */ +"6eh-lx-UEe.title" = "Create"; + +/* Class = "WKInterfaceButton"; title = "Create Invoice"; ObjectID = "7bc-tt-Pab"; */ +"7bc-tt-Pab.title" = "Create Invoice"; + +/* Class = "WKInterfaceButton"; title = "5"; ObjectID = "AA6-Gq-qRe"; */ +"AA6-Gq-qRe.title" = "5"; + +/* Class = "WKInterfaceLabel"; text = "memo"; ObjectID = "AJ8-p9-ID7"; */ +"AJ8-p9-ID7.text" = "memo"; + +/* Class = "WKInterfaceController"; title = "BlueWallet"; ObjectID = "AgC-eL-Hgc"; */ +"AgC-eL-Hgc.title" = "BlueWallet"; + +/* Class = "WKInterfaceLabel"; text = "Time"; ObjectID = "GqE-KB-TRD"; */ +"GqE-KB-TRD.text" = "Time"; + +/* Class = "WKInterfaceLabel"; text = "No wallets available. Please, add one by opening BlueWallet on your iPhone."; ObjectID = "I2I-8t-hp3"; */ +"I2I-8t-hp3.text" = "No wallets available. Please, add one by opening BlueWallet on your iPhone."; + +/* Class = "WKInterfaceLabel"; text = "Alert Label"; ObjectID = "IdU-wH-bcW"; */ +"IdU-wH-bcW.text" = "Alert Label"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "JMO-XZ-1si"; */ +"JMO-XZ-1si.text" = "Label"; + +/* Class = "WKInterfaceButton"; title = "9"; ObjectID = "NJM-uR-nyO"; */ +"NJM-uR-nyO.title" = "9"; + +/* Class = "WKInterfaceButton"; title = "6"; ObjectID = "Nt9-we-M9f"; */ +"Nt9-we-M9f.title" = "6"; + +/* Class = "WKInterfaceLabel"; text = "Wallet"; ObjectID = "PQi-JV-aYW"; */ +"PQi-JV-aYW.text" = "Wallet"; + +/* Class = "WKInterfaceLabel"; text = "Balance"; ObjectID = "QYx-3e-6zf"; */ +"QYx-3e-6zf.text" = "Balance"; + +/* Class = "WKInterfaceMenuItem"; title = "Customize"; ObjectID = "RHB-IJ-Utd"; */ +"RHB-IJ-Utd.title" = "Customize"; + +/* Class = "WKInterfaceButton"; title = "0"; ObjectID = "S1H-Id-l6g"; */ +"S1H-Id-l6g.title" = "0"; + +/* Class = "WKInterfaceButton"; title = "3"; ObjectID = "TKO-lc-aYf"; */ +"TKO-lc-aYf.title" = "3"; + +/* Class = "WKInterfaceLabel"; text = "Balance"; ObjectID = "WTr-jJ-w7L"; */ +"WTr-jJ-w7L.text" = "Balance"; + +/* Class = "WKInterfaceController"; title = "Transactions"; ObjectID = "XWa-4i-Abg"; */ +"XWa-4i-Abg.title" = "Transactions"; + +/* Class = "WKInterfaceButton"; title = "2"; ObjectID = "aUI-EE-NVw"; */ +"aUI-EE-NVw.title" = "2"; + +/* Class = "WKInterfaceButton"; title = "Receive"; ObjectID = "bPO-h8-ccD"; */ +"bPO-h8-ccD.title" = "Receive"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "c3W-8T-srG"; */ +"c3W-8T-srG.text" = "Label"; + +/* Class = "WKInterfaceController"; title = "Receive"; ObjectID = "egq-Yw-qK5"; */ +"egq-Yw-qK5.title" = "Receive"; + +/* Class = "WKInterfaceButton"; title = "Description"; ObjectID = "fcI-6Z-moQ"; */ +"fcI-6Z-moQ.title" = "Description"; + +/* Class = "WKInterfaceButton"; title = "."; ObjectID = "g6Z-9t-ahQ"; */ +"g6Z-9t-ahQ.title" = "."; + +/* Class = "WKInterfaceButton"; title = "1"; ObjectID = "ghD-Jq-ubw"; */ +"ghD-Jq-ubw.title" = "1"; + +/* Class = "WKInterfaceButton"; title = "View XPUB"; ObjectID = "j0O-fq-mwp"; */ +"j0O-fq-mwp.title" = "View XPUB"; + +/* Class = "WKInterfaceButton"; title = "4"; ObjectID = "kH2-N1-Hbe"; */ +"kH2-N1-Hbe.title" = "4"; + +/* Class = "WKInterfaceLabel"; text = "Creating Invoice..."; ObjectID = "n5f-iL-ib7"; */ +"n5f-iL-ib7.text" = "Creating Invoice..."; + +/* Class = "WKInterfaceButton"; title = "7"; ObjectID = "ohU-B0-mvg"; */ +"ohU-B0-mvg.title" = "7"; + +/* Class = "WKInterfaceLabel"; text = "No Transactions"; ObjectID = "pi4-Bk-Jiq"; */ +"pi4-Bk-Jiq.text" = "No Transactions"; + +/* Class = "WKInterfaceButton"; title = "<"; ObjectID = "q8Q-tK-nzd"; */ +"q8Q-tK-nzd.title" = "<"; + +/* Class = "WKInterfaceLabel"; text = "Wallet"; ObjectID = "qpj-I1-cWt"; */ +"qpj-I1-cWt.text" = "Wallet"; + +/* Class = "WKInterfaceLabel"; text = "Amount"; ObjectID = "sAS-LI-RY7"; */ +"sAS-LI-RY7.text" = "Amount"; diff --git a/ios/BlueWalletWatch/main.swift b/ios/BlueWalletWatch/main.swift new file mode 100644 index 00000000000..f6019f03272 --- /dev/null +++ b/ios/BlueWalletWatch/main.swift @@ -0,0 +1,8 @@ +// +// main.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 3/20/25. +// Copyright © 2025 BlueWallet. All rights reserved. +// + diff --git a/ios/BlueWalletWatch/nb.lproj/Interface.strings b/ios/BlueWalletWatch/nb.lproj/Interface.strings new file mode 100644 index 00000000000..b766702be55 --- /dev/null +++ b/ios/BlueWalletWatch/nb.lproj/Interface.strings @@ -0,0 +1,105 @@ + +/* Class = "WKInterfaceButton"; title = "Amount"; ObjectID = "0Hm-hv-Yi3"; */ +"0Hm-hv-Yi3.title" = "Amount"; + +/* Class = "WKInterfaceButton"; title = "8"; ObjectID = "3FQ-tZ-9kd"; */ +"3FQ-tZ-9kd.title" = "8"; + +/* Class = "WKInterfaceButton"; title = "Create"; ObjectID = "6eh-lx-UEe"; */ +"6eh-lx-UEe.title" = "Create"; + +/* Class = "WKInterfaceButton"; title = "Create Invoice"; ObjectID = "7bc-tt-Pab"; */ +"7bc-tt-Pab.title" = "Create Invoice"; + +/* Class = "WKInterfaceButton"; title = "5"; ObjectID = "AA6-Gq-qRe"; */ +"AA6-Gq-qRe.title" = "5"; + +/* Class = "WKInterfaceLabel"; text = "memo"; ObjectID = "AJ8-p9-ID7"; */ +"AJ8-p9-ID7.text" = "memo"; + +/* Class = "WKInterfaceController"; title = "BlueWallet"; ObjectID = "AgC-eL-Hgc"; */ +"AgC-eL-Hgc.title" = "BlueWallet"; + +/* Class = "WKInterfaceLabel"; text = "Time"; ObjectID = "GqE-KB-TRD"; */ +"GqE-KB-TRD.text" = "Time"; + +/* Class = "WKInterfaceLabel"; text = "No wallets available. Please, add one by opening BlueWallet on your iPhone."; ObjectID = "I2I-8t-hp3"; */ +"I2I-8t-hp3.text" = "No wallets available. Please, add one by opening BlueWallet on your iPhone."; + +/* Class = "WKInterfaceLabel"; text = "Alert Label"; ObjectID = "IdU-wH-bcW"; */ +"IdU-wH-bcW.text" = "Alert Label"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "JMO-XZ-1si"; */ +"JMO-XZ-1si.text" = "Label"; + +/* Class = "WKInterfaceButton"; title = "9"; ObjectID = "NJM-uR-nyO"; */ +"NJM-uR-nyO.title" = "9"; + +/* Class = "WKInterfaceButton"; title = "6"; ObjectID = "Nt9-we-M9f"; */ +"Nt9-we-M9f.title" = "6"; + +/* Class = "WKInterfaceLabel"; text = "Wallet"; ObjectID = "PQi-JV-aYW"; */ +"PQi-JV-aYW.text" = "Wallet"; + +/* Class = "WKInterfaceLabel"; text = "Balance"; ObjectID = "QYx-3e-6zf"; */ +"QYx-3e-6zf.text" = "Balance"; + +/* Class = "WKInterfaceMenuItem"; title = "Customize"; ObjectID = "RHB-IJ-Utd"; */ +"RHB-IJ-Utd.title" = "Customize"; + +/* Class = "WKInterfaceButton"; title = "0"; ObjectID = "S1H-Id-l6g"; */ +"S1H-Id-l6g.title" = "0"; + +/* Class = "WKInterfaceButton"; title = "3"; ObjectID = "TKO-lc-aYf"; */ +"TKO-lc-aYf.title" = "3"; + +/* Class = "WKInterfaceLabel"; text = "Balance"; ObjectID = "WTr-jJ-w7L"; */ +"WTr-jJ-w7L.text" = "Balance"; + +/* Class = "WKInterfaceController"; title = "Transactions"; ObjectID = "XWa-4i-Abg"; */ +"XWa-4i-Abg.title" = "Transactions"; + +/* Class = "WKInterfaceButton"; title = "2"; ObjectID = "aUI-EE-NVw"; */ +"aUI-EE-NVw.title" = "2"; + +/* Class = "WKInterfaceButton"; title = "Receive"; ObjectID = "bPO-h8-ccD"; */ +"bPO-h8-ccD.title" = "Receive"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "c3W-8T-srG"; */ +"c3W-8T-srG.text" = "Label"; + +/* Class = "WKInterfaceController"; title = "Receive"; ObjectID = "egq-Yw-qK5"; */ +"egq-Yw-qK5.title" = "Receive"; + +/* Class = "WKInterfaceButton"; title = "Description"; ObjectID = "fcI-6Z-moQ"; */ +"fcI-6Z-moQ.title" = "Description"; + +/* Class = "WKInterfaceButton"; title = "."; ObjectID = "g6Z-9t-ahQ"; */ +"g6Z-9t-ahQ.title" = "."; + +/* Class = "WKInterfaceButton"; title = "1"; ObjectID = "ghD-Jq-ubw"; */ +"ghD-Jq-ubw.title" = "1"; + +/* Class = "WKInterfaceButton"; title = "View XPUB"; ObjectID = "j0O-fq-mwp"; */ +"j0O-fq-mwp.title" = "View XPUB"; + +/* Class = "WKInterfaceButton"; title = "4"; ObjectID = "kH2-N1-Hbe"; */ +"kH2-N1-Hbe.title" = "4"; + +/* Class = "WKInterfaceLabel"; text = "Creating Invoice..."; ObjectID = "n5f-iL-ib7"; */ +"n5f-iL-ib7.text" = "Creating Invoice..."; + +/* Class = "WKInterfaceButton"; title = "7"; ObjectID = "ohU-B0-mvg"; */ +"ohU-B0-mvg.title" = "7"; + +/* Class = "WKInterfaceLabel"; text = "No Transactions"; ObjectID = "pi4-Bk-Jiq"; */ +"pi4-Bk-Jiq.text" = "No Transactions"; + +/* Class = "WKInterfaceButton"; title = "<"; ObjectID = "q8Q-tK-nzd"; */ +"q8Q-tK-nzd.title" = "<"; + +/* Class = "WKInterfaceLabel"; text = "Wallet"; ObjectID = "qpj-I1-cWt"; */ +"qpj-I1-cWt.text" = "Wallet"; + +/* Class = "WKInterfaceLabel"; text = "Amount"; ObjectID = "sAS-LI-RY7"; */ +"sAS-LI-RY7.text" = "Amount"; diff --git a/ios/Bridge.swift b/ios/Bridge.swift deleted file mode 100644 index 87c7573dc59..00000000000 --- a/ios/Bridge.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// Bridge.swift -// BlueWallet -// -// Created by Marcos Rodriguez on 9/19/19. -// Copyright © 2019 Facebook. All rights reserved. -// - -import Foundation diff --git a/ios/Components/EventEmitter.mm b/ios/Components/EventEmitter.mm new file mode 100644 index 00000000000..f6923df0ddd --- /dev/null +++ b/ios/Components/EventEmitter.mm @@ -0,0 +1,9 @@ +#import +#import "NativeEventEmitterSpec.h" + +@interface RCT_EXTERN_REMAP_MODULE(EventEmitter, EventEmitter, RCTEventEmitter) +RCT_EXTERN_METHOD(addListener:(NSString *)eventName) +RCT_EXTERN_METHOD(removeListeners:(double)count) +RCT_EXTERN_METHOD(getMostRecentUserActivity:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +@end diff --git a/ios/Components/EventEmitter.swift b/ios/Components/EventEmitter.swift new file mode 100644 index 00000000000..ad0f9e91332 --- /dev/null +++ b/ios/Components/EventEmitter.swift @@ -0,0 +1,36 @@ +import Foundation +import React + +@objc(EventEmitter) +class EventEmitter: RCTEventEmitter, NativeEventEmitterSpec { + static let sharedInstance = EventEmitter() + + @objc static func shared() -> EventEmitter { + return sharedInstance + } + + override func supportedEvents() -> [String]! { + return ["onUserActivityOpen"] + } + + override func addListener(_ eventName: String!) { + // Required for TurboModule event emitters; no-op handled by JS side + } + + override func removeListeners(_ count: Double) { + // Required for TurboModule event emitters; no-op handled by JS side + } + + @objc func sendUserActivity(_ userInfo: [String: Any]) { + sendEvent(withName: "onUserActivityOpen", body: userInfo) + } + + @objc func getMostRecentUserActivity(_ resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + if let defaults = UserDefaults(suiteName: "group.io.bluewallet.bluewallet") { + resolve(defaults.value(forKey: "onUserActivityOpen")) + } else { + resolve(nil) + } + } +} diff --git a/ios/Components/MenuElementsEmitter.m b/ios/Components/MenuElementsEmitter.m new file mode 100644 index 00000000000..9b3f422993e --- /dev/null +++ b/ios/Components/MenuElementsEmitter.m @@ -0,0 +1,13 @@ +#import +#import +#import "NativeMenuElementsEmitterSpec.h" + +@interface RCT_EXTERN_REMAP_MODULE(MenuElementsEmitter, MenuElementsEmitter, RCTEventEmitter) +RCT_EXTERN_METHOD(addListener:(NSString *)eventName) +RCT_EXTERN_METHOD(removeListeners:(double)count) +RCT_EXTERN_METHOD(openSettings) +RCT_EXTERN_METHOD(addWalletMenuAction) +RCT_EXTERN_METHOD(importWalletMenuAction) +RCT_EXTERN_METHOD(reloadTransactionsMenuAction) +RCT_EXTERN_METHOD(sharedInstance) +@end diff --git a/ios/Components/WidgetHelper.mm b/ios/Components/WidgetHelper.mm new file mode 100644 index 00000000000..77a57d7164c --- /dev/null +++ b/ios/Components/WidgetHelper.mm @@ -0,0 +1,6 @@ +#import +#import "NativeWidgetHelperSpec.h" + +@interface RCT_EXTERN_REMAP_MODULE(WidgetHelper, WidgetHelperModule, NSObject) +RCT_EXTERN_METHOD(reloadAllWidgets) +@end diff --git a/ios/Components/WidgetHelper.swift b/ios/Components/WidgetHelper.swift new file mode 100644 index 00000000000..0ec38bf1d68 --- /dev/null +++ b/ios/Components/WidgetHelper.swift @@ -0,0 +1,40 @@ +import Foundation +import WidgetKit +#if canImport(React_Codegen) +import React +#endif + +// Lightweight helper used by the app target to refresh widget timelines from native code. +class WidgetHelper { + func reloadAllWidgets() { + if #available(iOS 14.0, *) { + WidgetCenter.shared.reloadAllTimelines() + } + } +} + +#if canImport(React_Codegen) +@objc(WidgetHelperModule) +class WidgetHelperModule: NSObject, NativeWidgetHelperSpec { + static func moduleName() -> String! { "WidgetHelper" } + static func requiresMainQueueSetup() -> Bool { false } + + @objc + func reloadAllWidgets() { + if #available(iOS 14.0, *) { + WidgetCenter.shared.reloadAllTimelines() + } + } +} +#else +// Fallback for targets (e.g., widget extension) that do not pull in React codegen modules. +@objc(WidgetHelperModule) +class WidgetHelperModule: NSObject { + func reloadAllWidgets() { + // WidgetsExtension does not link the app's WidgetHelper; invoke WidgetKit directly. + if #available(iOS 14.0, *) { + WidgetCenter.shared.reloadAllTimelines() + } + } +} +#endif diff --git a/ios/EventEmitter.h b/ios/EventEmitter.h deleted file mode 100644 index f0b56a6bcc9..00000000000 --- a/ios/EventEmitter.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// EventEmitter.h -// BlueWallet -// -// Created by Marcos Rodriguez on 12/25/20. -// Copyright © 2020 BlueWallet. All rights reserved. -// - -#import -#import - -@interface EventEmitter : RCTEventEmitter - -+ (EventEmitter *)sharedInstance; -- (void)sendNotification:(NSDictionary *)userInfo; -- (void)openSettings; -- (void)sendUserActivity:(NSDictionary *)userInfo; - -@end diff --git a/ios/EventEmitter.m b/ios/EventEmitter.m deleted file mode 100644 index cf1e8e8c2d4..00000000000 --- a/ios/EventEmitter.m +++ /dev/null @@ -1,62 +0,0 @@ -// -// EventEmitter.m -// BlueWallet -// -// Created by Marcos Rodriguez on 12/25/20. -// Copyright © 2020 BlueWallet. All rights reserved. -// - -#import "EventEmitter.h" - -static EventEmitter *sharedInstance; - -@implementation EventEmitter - -RCT_EXPORT_MODULE(); - -+ (BOOL)requiresMainQueueSetup { - return NO; -} - -+ (EventEmitter *)sharedInstance { - return sharedInstance; -} - -- (void)removeListeners:(double)count { - -} - -- (instancetype)init { - sharedInstance = [super init]; - return sharedInstance; -} - -- (NSArray *)supportedEvents { - return @[@"onNotificationReceived",@"openSettings",@"onUserActivityOpen"]; -} - -- (void)sendNotification:(NSDictionary *)userInfo -{ - [sharedInstance sendEventWithName:@"onNotificationReceived" body:userInfo]; -} - -- (void)sendUserActivity:(NSDictionary *)userInfo -{ - [sharedInstance sendEventWithName:@"onUserActivityOpen" body:userInfo]; -} - -RCT_REMAP_METHOD(getMostRecentUserActivity, resolve: (RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) -{ - NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"]; - resolve([defaults valueForKey:@"onUserActivityOpen"]); -} - - -- (void)openSettings -{ - [sharedInstance sendEventWithName:@"openSettings" body:nil]; -} - - -@end diff --git a/ios/KeychainSwiftDistrib.swift b/ios/KeychainSwiftDistrib.swift deleted file mode 100644 index 6287bcf8280..00000000000 --- a/ios/KeychainSwiftDistrib.swift +++ /dev/null @@ -1,454 +0,0 @@ -// -// Keychain helper for iOS/Swift. -// -// https://github.com/evgenyneu/keychain-swift -// -// This file was automatically generated by combining multiple Swift source files. -// - - -// ---------------------------- -// -// KeychainSwift.swift -// -// ---------------------------- - -import Security -import Foundation - -/** - -A collection of helper functions for saving text and data in the keychain. - -*/ -open class KeychainSwift { - - var lastQueryParameters: [String: Any]? // Used by the unit tests - - /// Contains result code from the last operation. Value is noErr (0) for a successful result. - open var lastResultCode: OSStatus = noErr - - var keyPrefix = "" // Can be useful in test. - - /** - - Specify an access group that will be used to access keychain items. Access groups can be used to share keychain items between applications. When access group value is nil all application access groups are being accessed. Access group name is used by all functions: set, get, delete and clear. - - */ - open var accessGroup: String? - - - /** - - Specifies whether the items can be synchronized with other devices through iCloud. Setting this property to true will - add the item to other devices with the `set` method and obtain synchronizable items with the `get` command. Deleting synchronizable items will remove them from all devices. In order for keychain synchronization to work the user must enable "Keychain" in iCloud settings. - - Does not work on macOS. - - */ - open var synchronizable: Bool = false - - private let readLock = NSLock() - - /// Instantiate a KeychainSwift object - public init() { } - - /** - - - parameter keyPrefix: a prefix that is added before the key in get/set methods. Note that `clear` method still clears everything from the Keychain. - - */ - public init(keyPrefix: String) { - self.keyPrefix = keyPrefix - } - - /** - - Stores the text value in the keychain item under the given key. - - - parameter key: Key under which the text value is stored in the keychain. - - parameter value: Text string to be written to the keychain. - - parameter withAccess: Value that indicates when your app needs access to the text in the keychain item. By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed only while the device is unlocked by the user. - - - returns: True if the text was successfully written to the keychain. - - */ - @discardableResult - open func set(_ value: String, forKey key: String, - withAccess access: KeychainSwiftAccessOptions? = nil) -> Bool { - - if let value = value.data(using: String.Encoding.utf8) { - return set(value, forKey: key, withAccess: access) - } - - return false - } - - /** - - Stores the data in the keychain item under the given key. - - - parameter key: Key under which the data is stored in the keychain. - - parameter value: Data to be written to the keychain. - - parameter withAccess: Value that indicates when your app needs access to the text in the keychain item. By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed only while the device is unlocked by the user. - - - returns: True if the text was successfully written to the keychain. - - */ - @discardableResult - open func set(_ value: Data, forKey key: String, - withAccess access: KeychainSwiftAccessOptions? = nil) -> Bool { - - delete(key) // Delete any existing key before saving it - - let accessible = access?.value ?? KeychainSwiftAccessOptions.defaultOption.value - - let prefixedKey = keyWithPrefix(key) - - var query: [String : Any] = [ - KeychainSwiftConstants.klass : kSecClassGenericPassword, - KeychainSwiftConstants.attrAccount : prefixedKey, - KeychainSwiftConstants.valueData : value, - KeychainSwiftConstants.accessible : accessible - ] - - query = addAccessGroupWhenPresent(query) - query = addSynchronizableIfRequired(query, addingItems: true) - lastQueryParameters = query - - lastResultCode = SecItemAdd(query as CFDictionary, nil) - - return lastResultCode == noErr - } - - /** - - Stores the boolean value in the keychain item under the given key. - - - parameter key: Key under which the value is stored in the keychain. - - parameter value: Boolean to be written to the keychain. - - parameter withAccess: Value that indicates when your app needs access to the value in the keychain item. By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed only while the device is unlocked by the user. - - - returns: True if the value was successfully written to the keychain. - - */ - @discardableResult - open func set(_ value: Bool, forKey key: String, - withAccess access: KeychainSwiftAccessOptions? = nil) -> Bool { - - let bytes: [UInt8] = value ? [1] : [0] - let data = Data(bytes) - - return set(data, forKey: key, withAccess: access) - } - - /** - - Retrieves the text value from the keychain that corresponds to the given key. - - - parameter key: The key that is used to read the keychain item. - - returns: The text value from the keychain. Returns nil if unable to read the item. - - */ - open func get(_ key: String) -> String? { - if let data = getData(key) { - - if let currentString = String(data: data, encoding: .utf8) { - return currentString - } - - lastResultCode = -67853 // errSecInvalidEncoding - } - - return nil - } - - /** - - Retrieves the data from the keychain that corresponds to the given key. - - - parameter key: The key that is used to read the keychain item. - - returns: The text value from the keychain. Returns nil if unable to read the item. - - */ - open func getData(_ key: String) -> Data? { - // The lock prevents the code to be run simlultaneously - // from multiple threads which may result in crashing - readLock.lock() - defer { readLock.unlock() } - - let prefixedKey = keyWithPrefix(key) - - var query: [String: Any] = [ - KeychainSwiftConstants.klass : kSecClassGenericPassword, - KeychainSwiftConstants.attrAccount : prefixedKey, - KeychainSwiftConstants.returnData : kCFBooleanTrue!, - KeychainSwiftConstants.matchLimit : kSecMatchLimitOne - ] - - query = addAccessGroupWhenPresent(query) - query = addSynchronizableIfRequired(query, addingItems: false) - lastQueryParameters = query - - var result: AnyObject? - - lastResultCode = withUnsafeMutablePointer(to: &result) { - SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) - } - - if lastResultCode == noErr { return result as? Data } - - return nil - } - - /** - - Retrieves the boolean value from the keychain that corresponds to the given key. - - - parameter key: The key that is used to read the keychain item. - - returns: The boolean value from the keychain. Returns nil if unable to read the item. - - */ - open func getBool(_ key: String) -> Bool? { - guard let data = getData(key) else { return nil } - guard let firstBit = data.first else { return nil } - return firstBit == 1 - } - - /** - - Deletes the single keychain item specified by the key. - - - parameter key: The key that is used to delete the keychain item. - - returns: True if the item was successfully deleted. - - */ - @discardableResult - open func delete(_ key: String) -> Bool { - let prefixedKey = keyWithPrefix(key) - - var query: [String: Any] = [ - KeychainSwiftConstants.klass : kSecClassGenericPassword, - KeychainSwiftConstants.attrAccount : prefixedKey - ] - - query = addAccessGroupWhenPresent(query) - query = addSynchronizableIfRequired(query, addingItems: false) - lastQueryParameters = query - - lastResultCode = SecItemDelete(query as CFDictionary) - - return lastResultCode == noErr - } - - /** - - Deletes all Keychain items used by the app. Note that this method deletes all items regardless of the prefix settings used for initializing the class. - - - returns: True if the keychain items were successfully deleted. - - */ - @discardableResult - open func clear() -> Bool { - var query: [String: Any] = [ kSecClass as String : kSecClassGenericPassword ] - query = addAccessGroupWhenPresent(query) - query = addSynchronizableIfRequired(query, addingItems: false) - lastQueryParameters = query - - lastResultCode = SecItemDelete(query as CFDictionary) - - return lastResultCode == noErr - } - - /// Returns the key with currently set prefix. - func keyWithPrefix(_ key: String) -> String { - return "\(keyPrefix)\(key)" - } - - func addAccessGroupWhenPresent(_ items: [String: Any]) -> [String: Any] { - guard let accessGroup = accessGroup else { return items } - - var result: [String: Any] = items - result[KeychainSwiftConstants.accessGroup] = accessGroup - return result - } - - /** - - Adds kSecAttrSynchronizable: kSecAttrSynchronizableAny` item to the dictionary when the `synchronizable` property is true. - - - parameter items: The dictionary where the kSecAttrSynchronizable items will be added when requested. - - parameter addingItems: Use `true` when the dictionary will be used with `SecItemAdd` method (adding a keychain item). For getting and deleting items, use `false`. - - - returns: the dictionary with kSecAttrSynchronizable item added if it was requested. Otherwise, it returns the original dictionary. - - */ - func addSynchronizableIfRequired(_ items: [String: Any], addingItems: Bool) -> [String: Any] { - if !synchronizable { return items } - var result: [String: Any] = items - result[KeychainSwiftConstants.attrSynchronizable] = addingItems == true ? true : kSecAttrSynchronizableAny - return result - } -} - - -// ---------------------------- -// -// TegKeychainConstants.swift -// -// ---------------------------- - -import Foundation -import Security - -/// Constants used by the library -public struct KeychainSwiftConstants { - /// Specifies a Keychain access group. Used for sharing Keychain items between apps. - public static var accessGroup: String { return toString(kSecAttrAccessGroup) } - - /** - - A value that indicates when your app needs access to the data in a keychain item. The default value is AccessibleWhenUnlocked. For a list of possible values, see KeychainSwiftAccessOptions. - - */ - public static var accessible: String { return toString(kSecAttrAccessible) } - - /// Used for specifying a String key when setting/getting a Keychain value. - public static var attrAccount: String { return toString(kSecAttrAccount) } - - /// Used for specifying synchronization of keychain items between devices. - public static var attrSynchronizable: String { return toString(kSecAttrSynchronizable) } - - /// An item class key used to construct a Keychain search dictionary. - public static var klass: String { return toString(kSecClass) } - - /// Specifies the number of values returned from the keychain. The library only supports single values. - public static var matchLimit: String { return toString(kSecMatchLimit) } - - /// A return data type used to get the data from the Keychain. - public static var returnData: String { return toString(kSecReturnData) } - - /// Used for specifying a value when setting a Keychain value. - public static var valueData: String { return toString(kSecValueData) } - - static func toString(_ value: CFString) -> String { - return value as String - } -} - - -// ---------------------------- -// -// KeychainSwiftAccessOptions.swift -// -// ---------------------------- - -import Security - -/** - -These options are used to determine when a keychain item should be readable. The default value is AccessibleWhenUnlocked. - -*/ -public enum KeychainSwiftAccessOptions { - - /** - - The data in the keychain item can be accessed only while the device is unlocked by the user. - - This is recommended for items that need to be accessible only while the application is in the foreground. Items with this attribute migrate to a new device when using encrypted backups. - - This is the default value for keychain items added without explicitly setting an accessibility constant. - - */ - case accessibleWhenUnlocked - - /** - - The data in the keychain item can be accessed only while the device is unlocked by the user. - - This is recommended for items that need to be accessible only while the application is in the foreground. Items with this attribute do not migrate to a new device. Thus, after restoring from a backup of a different device, these items will not be present. - - */ - case accessibleWhenUnlockedThisDeviceOnly - - /** - - The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user. - - After the first unlock, the data remains accessible until the next restart. This is recommended for items that need to be accessed by background applications. Items with this attribute migrate to a new device when using encrypted backups. - - */ - case accessibleAfterFirstUnlock - - /** - - The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user. - - After the first unlock, the data remains accessible until the next restart. This is recommended for items that need to be accessed by background applications. Items with this attribute do not migrate to a new device. Thus, after restoring from a backup of a different device, these items will not be present. - - */ - case accessibleAfterFirstUnlockThisDeviceOnly - - /** - - The data in the keychain item can always be accessed regardless of whether the device is locked. - - This is not recommended for application use. Items with this attribute migrate to a new device when using encrypted backups. - - */ - case accessibleAlways - - /** - - The data in the keychain can only be accessed when the device is unlocked. Only available if a passcode is set on the device. - - This is recommended for items that only need to be accessible while the application is in the foreground. Items with this attribute never migrate to a new device. After a backup is restored to a new device, these items are missing. No items can be stored in this class on devices without a passcode. Disabling the device passcode causes all items in this class to be deleted. - - */ - case accessibleWhenPasscodeSetThisDeviceOnly - - /** - - The data in the keychain item can always be accessed regardless of whether the device is locked. - - This is not recommended for application use. Items with this attribute do not migrate to a new device. Thus, after restoring from a backup of a different device, these items will not be present. - - */ - case accessibleAlwaysThisDeviceOnly - - static var defaultOption: KeychainSwiftAccessOptions { - return .accessibleWhenUnlocked - } - - var value: String { - switch self { - case .accessibleWhenUnlocked: - return toString(kSecAttrAccessibleWhenUnlocked) - - case .accessibleWhenUnlockedThisDeviceOnly: - return toString(kSecAttrAccessibleWhenUnlockedThisDeviceOnly) - - case .accessibleAfterFirstUnlock: - return toString(kSecAttrAccessibleAfterFirstUnlock) - - case .accessibleAfterFirstUnlockThisDeviceOnly: - return toString(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) - - case .accessibleAlways: - return toString(kSecAttrAccessibleAlways) - - case .accessibleWhenPasscodeSetThisDeviceOnly: - return toString(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly) - - case .accessibleAlwaysThisDeviceOnly: - return toString(kSecAttrAccessibleAlwaysThisDeviceOnly) - } - } - - func toString(_ value: CFString) -> String { - return KeychainSwiftConstants.toString(value) - } -} - - diff --git a/ios/LaunchScreen.storyboard b/ios/LaunchScreen.storyboard index 11c51cc325c..be4a253a818 100644 --- a/ios/LaunchScreen.storyboard +++ b/ios/LaunchScreen.storyboard @@ -1,10 +1,11 @@ - + - + + @@ -15,8 +16,21 @@ - + + + + + + + + + + + + + + @@ -24,4 +38,10 @@ + + + + + + diff --git a/ios/Localizable.xcstrings b/ios/Localizable.xcstrings new file mode 100644 index 00000000000..be54eb39f2d --- /dev/null +++ b/ios/Localizable.xcstrings @@ -0,0 +1,388 @@ +{ + "sourceLanguage" : "en_US", + "strings" : { + "%@" : { + + }, + "Argentina (Argentine Peso)" : { + + }, + "Aruba (Aruban Florin)" : { + + }, + "at %@" : { + + }, + "Australia (Australian Dollar)" : { + + }, + "Bahrain (Bahraini Dinar)" : { + + }, + "Balance" : { + + }, + "Bitcoin (%@)" : { + + }, + "Bitcoin price: %@" : { + + }, + "Brazil (Brazilian Real)" : { + + }, + "BTC" : { + + }, + "Canada (Canadian Dollar)" : { + + }, + "Central African Republic (Central African Franc)" : { + + }, + "Checked at %@" : { + + }, + "Chile (Chilean Peso)" : { + + }, + "China (Chinese Yuan)" : { + + }, + "Choose your preferred currency." : { + + }, + "Choose your preferred fiat currency." : { + + }, + "Colombia (Colombian Peso)" : { + + }, + "Configure Market Widget with ${electrumHost}" : { + + }, + "Configure Market Widget with ${electrumHost} and show error messages: ${showErrorMessages}" : { + + }, + "Configure the Market Widget to show the Electrum host connected to/being attempted and display error messages if data retrieval fails." : { + + }, + "Croatia (Croatian Kuna)" : { + + }, + "Currency" : { + + }, + "Currency: %@" : { + + }, + "Current Bitcoin Market Rate" : { + + }, + "Current Bitcoin Price: %@" : { + + }, + "Czech Republic (Czech Koruna)" : { + + }, + "Denmark (Danish Krone)" : { + + }, + "display_in_BROWSER_TITLE" : { + "localizations" : { + "en_US" : { + "stringUnit" : { + "state" : "translated", + "value" : "View in Browser" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ver en el navegador" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voir dans le navigateur" + } + } + } + }, + "Egypt (Egyptian Pound)" : { + "comment" : "Description of the \"Egyptian Pound\" fiat currency.", + "isCommentAutoGenerated" : true + }, + "Electrum Host" : { + + }, + "Error" : { + + }, + "European Union (Euro)" : { + + }, + "Failed to retrieve the Bitcoin market rate." : { + + }, + "Fiat Currency" : { + + }, + "from" : { + + }, + "From %@" : { + + }, + "Ghana (Ghanaian Cedi)" : { + + }, + "Hungary (Hungarian Forint)" : { + + }, + "Iceland (Icelandic Króna)" : { + + }, + "India (Indian Rupee)" : { + + }, + "Indonesia (Indonesian Rupiah)" : { + + }, + "Iran (Iranian Rial)" : { + + }, + "Iran (Iranian Toman)" : { + + }, + "Israel (Israeli New Shekel)" : { + + }, + "Japan (Japanese Yen)" : { + + }, + "Kenya (Kenyan Shilling)" : { + + }, + "Kuwait (Kuwaiti Dinar)" : { + + }, + "Last Updated" : { + + }, + "Last updated %@ from %@" : { + "localizations" : { + "en_US" : { + "stringUnit" : { + "state" : "new", + "value" : "Last updated %1$@ from %2$@" + } + } + } + }, + "Latest transaction" : { + + }, + "Lebanon (Lebanese Pound)" : { + + }, + "Malaysia (Malaysian Ringgit)" : { + + }, + "Market" : { + + }, + "Market Rate" : { + + }, + "Market Widget" : { + + }, + "Market Widget Configuration" : { + + }, + "Market Widget is connected to ${electrumHost}" : { + + }, + "Mexico (Mexican Peso)" : { + + }, + "Mozambique (Mozambican Metical)" : { + + }, + "New Zealand (New Zealand Dollar)" : { + + }, + "Next Block" : { + + }, + "Nigeria (Nigerian Naira)" : { + + }, + "Norway (Norwegian Krone)" : { + + }, + "Oman (Omani Rial)" : { + + }, + "Philippines (Philippine Peso)" : { + + }, + "Poland (Polish Zloty)" : { + + }, + "Price" : { + + }, + "Qatar (Qatari Riyal)" : { + + }, + "receive" : { + + }, + "Romania (Romanian Leu)" : { + + }, + "Russia (Russian Ruble)" : { + + }, + "Sats/%@" : { + + }, + "Saudi Arabia (Saudi Riyal)" : { + + }, + "send" : { + + }, + "Show Error Messages" : { + + }, + "Singapore (Singapore Dollar)" : { + + }, + "Source: %@" : { + + }, + "South Africa (South African Rand)" : { + + }, + "South Korea (South Korean Won)" : { + + }, + "Sri Lanka (Sri Lankan Rupee)" : { + + }, + "Sweden (Swedish Krona)" : { + + }, + "Switzerland (Swiss Franc)" : { + + }, + "Taiwan (New Taiwan Dollar)" : { + + }, + "Tanzania (Tanzanian Shilling)" : { + + }, + "Thailand (Thai Baht)" : { + + }, + "Turkey (Turkish Lira)" : { + + }, + "Uganda (Ugandan Shilling)" : { + + }, + "Ukraine (Ukrainian Hryvnia)" : { + + }, + "United Arab Emirates (UAE Dirham)" : { + + }, + "United Kingdom (British Pound)" : { + + }, + "United States of America (US Dollar)" : { + + }, + "Updated: %@" : { + + }, + "Uruguay (Uruguayan Peso)" : { + + }, + "Venezuela (Venezuelan Bolívar Fuerte)" : { + + }, + "Venezuela (Venezuelan Bolívar Soberano)" : { + + }, + "View the current Bitcoin market rate in your preferred currency." : { + + }, + "View the current Bitcoin market rate in your preferred fiat currency." : { + + }, + "View the current Bitcoin market rate." : { + + }, + "View the current market information." : { + + }, + "View the current price of Bitcoin" : { + + }, + "View the current price of Bitcoin." : { + + }, + "View the Electrum host connected to/being attempted" : { + + }, + "View your accumulated balance." : { + + }, + "View your total wallet balance and network prices." : { + + }, + "VIEW_ADDRESS_TRANSACTIONS_TITLE" : { + "extractionState" : "manual", + "localizations" : { + "en_US" : { + "stringUnit" : { + "state" : "translated", + "value" : "View Address in Browser" + } + }, + "es" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Ver dirección en el navegador" + } + } + } + }, + "VIEW_TRANSACTION_DETAILS_TITLE" : { + "extractionState" : "manual", + "localizations" : { + "en_US" : { + "stringUnit" : { + "state" : "translated", + "value" : "View Transaction in Browser" + } + }, + "es" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Ver transacción en el navegador" + } + } + } + }, + "Wallet and Market" : { + + } + }, + "version" : "1.1" +} \ No newline at end of file diff --git a/ios/MenuElementsEmitter/MenuElementsEmitter.m b/ios/MenuElementsEmitter/MenuElementsEmitter.m new file mode 100644 index 00000000000..42b50b6bb06 --- /dev/null +++ b/ios/MenuElementsEmitter/MenuElementsEmitter.m @@ -0,0 +1 @@ +// Intentionally left empty after TurboModule migration. diff --git a/ios/MenuElementsEmitter/MenuElementsEmitter.swift b/ios/MenuElementsEmitter/MenuElementsEmitter.swift new file mode 100644 index 00000000000..bb1586e9c34 --- /dev/null +++ b/ios/MenuElementsEmitter/MenuElementsEmitter.swift @@ -0,0 +1,75 @@ +import Foundation +import React + +@objc(MenuElementsEmitter) +class MenuElementsEmitter: RCTEventEmitter, NativeMenuElementsEmitterSpec { + private static var instance: MenuElementsEmitter? + private var hasListeners = false + + override init() { + super.init() + MenuElementsEmitter.instance = self + } + + @objc + class func sharedInstance() -> MenuElementsEmitter { + if instance == nil { + instance = MenuElementsEmitter() + } + return instance! + } + + // NativeMenuElementsEmitterSpec expects an instance method; bridge it to the singleton above. + @objc + func sharedInstance() { + _ = MenuElementsEmitter.sharedInstance() + } + + override func supportedEvents() -> [String]! { + return ["openSettings", "addWalletMenuAction", "importWalletMenuAction", "reloadTransactionsMenuAction"] + } + + override func addListener(_ eventName: String!) { + // Required for TurboModule event emitters; JS handles bookkeeping + } + + override func removeListeners(_ count: Double) { + // Required for TurboModule event emitters; JS handles bookkeeping + } + + override func startObserving() { + hasListeners = true + } + + override func stopObserving() { + hasListeners = false + } + + @objc + func openSettings() { + if hasListeners { + sendEvent(withName: "openSettings", body: nil) + } + } + + @objc + func addWalletMenuAction() { + if hasListeners { + sendEvent(withName: "addWalletMenuAction", body: nil) + } + } + + @objc + func importWalletMenuAction() { + if hasListeners { + sendEvent(withName: "importWalletMenuAction", body: nil) + } + } + + @objc + func reloadTransactionsMenuAction() { + if hasListeners { + sendEvent(withName: "reloadTransactionsMenuAction", body: nil) + } + } +} diff --git a/ios/NativeEventEmitterSpec.h b/ios/NativeEventEmitterSpec.h new file mode 100644 index 00000000000..553408d6dfd --- /dev/null +++ b/ios/NativeEventEmitterSpec.h @@ -0,0 +1,9 @@ +#import + +// Keep this spec light for Swift bridging; avoid TurboModule includes to prevent C++ STL lookup issues during Swift dependency scanning. +@protocol NativeEventEmitterSpec +- (void)addListener:(NSString *)eventName; +- (void)removeListeners:(double)count; +- (void)getMostRecentUserActivity:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject; +@end diff --git a/ios/NativeMenuElementsEmitterSpec.h b/ios/NativeMenuElementsEmitterSpec.h new file mode 100644 index 00000000000..f3c30e65251 --- /dev/null +++ b/ios/NativeMenuElementsEmitterSpec.h @@ -0,0 +1,11 @@ +#import + +@protocol NativeMenuElementsEmitterSpec +- (void)addListener:(NSString *)eventName; +- (void)removeListeners:(double)count; +- (void)openSettings; +- (void)addWalletMenuAction; +- (void)importWalletMenuAction; +- (void)reloadTransactionsMenuAction; +- (void)sharedInstance; +@end diff --git a/ios/NativeWidgetHelperSpec.h b/ios/NativeWidgetHelperSpec.h new file mode 100644 index 00000000000..8973da81906 --- /dev/null +++ b/ios/NativeWidgetHelperSpec.h @@ -0,0 +1,5 @@ +#import + +@protocol NativeWidgetHelperSpec +- (void)reloadAllWidgets; +@end diff --git a/ios/Podfile b/ios/Podfile index 35373ac7083..ab4c401d2a7 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,72 +1,64 @@ -require_relative '../node_modules/react-native/scripts/react_native_pods' -require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' +def node_require(script) + # Resolve script with node to allow for hoisting + require Pod::Executable.execute_command('node', ['-p', + "require.resolve( + '#{script}', + {paths: [process.argv[1]]}, + )", __dir__]).strip +end +node_require('react-native/scripts/react_native_pods.rb') +node_require('react-native-permissions/scripts/setup.rb') workspace 'BlueWallet' -platform :ios, '13.0' +platform :ios, min_ios_version_supported prepare_react_native_project! - -# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. -# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded -# -# To fix this you can also exclude `react-native-flipper` using a `react-native.config.js` -# ```js -# module.exports = { -# dependencies: { -# ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}), -# ``` -# flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled - -flipper_config = FlipperConfiguration.disabled +setup_permissions(['Camera', 'Notifications']) linkage = ENV['USE_FRAMEWORKS'] if linkage != nil Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green use_frameworks! :linkage => linkage.to_sym end - -target 'BlueWallet' do +# Define a common function to configure shared settings for targets +def configure_target() config = use_native_modules! - # Flags change depending on the env values. - flags = get_default_flags() - use_react_native!( + # Specify the path directly if use_native_modules! does not provide it :path => config[:reactNativePath], - # Hermes is now enabled by default. Disable by setting this flag to false. - # Upcoming versions of React Native may rely on get_default_flags(), but - # we make it explicit here to aid in the React Native upgrade process. - :hermes_enabled => true, - :fabric_enabled => flags[:fabric_enabled], - # Enables Flipper. - # - # Note that if you have use_frameworks! enabled, Flipper will not work and - # you should disable the next line. - #:flipper_configuration => flipper_config, - # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/.." + ) + pod 'react-native-bw-file-access', :path => '../blue_modules/react-native-bw-file-access' +end - post_install do |installer| - react_native_post_install( - installer, - # Set `mac_catalyst_enabled` to `true` in order to apply patches - # necessary for Mac Catalyst builds - :mac_catalyst_enabled => true - ) - pod 'Bugsnag' - plugin 'cocoapods-bugsnag' - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' - if ['React-Core-AccessibilityResources'].include? target.name - config.build_settings['CODE_SIGN_STYLE'] = "Manual" - config.build_settings['CODE_SIGN_IDENTITY'] = "Apple Distribution: Bluewallet Services, S. R. L. (A7W54YZ4WU)" - config.build_settings['DEVELOPMENT_TEAM'] = "A7W54YZ4WU" - end - end - __apply_Xcode_12_5_M1_post_install_workaround(installer) - end - end - +target 'BlueWallet' do + configure_target() +end + +post_install do |installer| + react_native_post_install( + installer, + :mac_catalyst_enabled => true, + :ccache_enabled => ENV['USE_CCACHE'] == '1', + ) + # Fix RCTDeprecation module map error with Xcode 16+/26+ (required for react-native-notifications). + # The bridging header imports RCTBridgeModule.h which does + # #import . With -fmodules, Clang treats this + # as a module import and builds a PCM, but PCH verification then fails with + # "module 'RCTDeprecation' is not defined in any loaded module map file". + # Fix: remove RCTDeprecation -fmodule-map-file from the consumer xcconfigs + # so the import resolves as a plain textual include via HEADER_SEARCH_PATHS. + # No code uses @import RCTDeprecation, so this is safe. + installer.generated_aggregate_targets.each do |aggregate_target| + aggregate_target.xcconfigs.each do |config_name, config| + xcconfig_path = aggregate_target.xcconfig_path(config_name) + content = File.read(xcconfig_path) + # Order matters: match -Xcc variant first to avoid leaving a dangling -Xcc + content.gsub!('-Xcc -fmodule-map-file="${PODS_ROOT}/Headers/Public/RCTDeprecation/RCTDeprecation.modulemap" ', '') + content.gsub!('-fmodule-map-file="${PODS_ROOT}/Headers/Public/RCTDeprecation/RCTDeprecation.modulemap" ', '') + File.write(xcconfig_path, content) + end + end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 57323374e13..7013b253a30 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,531 +1,2550 @@ PODS: - - boost (1.76.0) - - BugsnagReactNative (7.20.2): + - BugsnagReactNative (8.8.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - BVLinearGradient (2.8.3): + - React-Core + - CocoaAsyncSocket (7.6.5) + - FBLazyVector (0.85.3) + - hermes-engine (250829098.0.10): + - hermes-engine/Pre-built (= 250829098.0.10) + - hermes-engine/Pre-built (250829098.0.10) + - lottie-ios (4.6.0) + - lottie-react-native (7.3.7): + - hermes-engine + - lottie-ios (= 4.6.0) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RCTDeprecation (0.85.3) + - RCTRequired (0.85.3) + - RCTSwiftUI (0.85.3) + - RCTSwiftUIWrapper (0.85.3): + - RCTSwiftUI + - RCTTypeSafety (0.85.3): + - FBLazyVector (= 0.85.3) + - RCTRequired (= 0.85.3) + - React-Core (= 0.85.3) + - React (0.85.3): + - React-Core (= 0.85.3) + - React-Core/DevSupport (= 0.85.3) + - React-Core/RCTWebSocket (= 0.85.3) + - React-RCTActionSheet (= 0.85.3) + - React-RCTAnimation (= 0.85.3) + - React-RCTBlob (= 0.85.3) + - React-RCTImage (= 0.85.3) + - React-RCTLinking (= 0.85.3) + - React-RCTNetwork (= 0.85.3) + - React-RCTSettings (= 0.85.3) + - React-RCTText (= 0.85.3) + - React-RCTVibration (= 0.85.3) + - React-callinvoker (0.85.3) + - React-Core (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default (= 0.85.3) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core-prebuilt (0.85.3): + - ReactNativeDependencies + - React-Core/CoreModulesHeaders (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/Default (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/DevSupport (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default (= 0.85.3) + - React-Core/RCTWebSocket (= 0.85.3) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTActionSheetHeaders (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTAnimationHeaders (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTBlobHeaders (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTImageHeaders (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTLinkingHeaders (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTNetworkHeaders (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTSettingsHeaders (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTTextHeaders (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTVibrationHeaders (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTWebSocket (0.85.3): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default (= 0.85.3) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-CoreModules (0.85.3): + - RCTTypeSafety (= 0.85.3) + - React-Core-prebuilt + - React-Core/CoreModulesHeaders (= 0.85.3) + - React-debug + - React-featureflags + - React-jsi (= 0.85.3) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-NativeModulesApple + - React-RCTBlob + - React-RCTFBReactNativeSpec + - React-RCTImage (= 0.85.3) + - React-runtimeexecutor + - React-utils + - ReactCommon + - ReactNativeDependencies + - React-cxxreact (0.85.3): + - hermes-engine + - React-callinvoker (= 0.85.3) + - React-Core-prebuilt + - React-debug (= 0.85.3) + - React-jsi (= 0.85.3) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-logger (= 0.85.3) + - React-perflogger (= 0.85.3) + - React-runtimeexecutor + - React-timing (= 0.85.3) + - React-utils + - ReactNativeDependencies + - React-debug (0.85.3): + - React-debug/redbox (= 0.85.3) + - React-debug/redbox (0.85.3) + - React-defaultsnativemodule (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-domnativemodule + - React-Fabric/animated + - React-featureflags + - React-featureflagsnativemodule + - React-idlecallbacksnativemodule + - React-intersectionobservernativemodule + - React-jsi + - React-jsiexecutor + - React-microtasksnativemodule + - React-mutationobservernativemodule + - React-RCTFBReactNativeSpec + - React-webperformancenativemodule + - ReactNativeDependencies + - Yoga + - React-domnativemodule (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-Fabric + - React-Fabric/bridging + - React-FabricComponents + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-Fabric (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/animated (= 0.85.3) + - React-Fabric/animationbackend (= 0.85.3) + - React-Fabric/animations (= 0.85.3) + - React-Fabric/attributedstring (= 0.85.3) + - React-Fabric/bridging (= 0.85.3) + - React-Fabric/componentregistry (= 0.85.3) + - React-Fabric/componentregistrynative (= 0.85.3) + - React-Fabric/components (= 0.85.3) + - React-Fabric/consistency (= 0.85.3) + - React-Fabric/core (= 0.85.3) + - React-Fabric/dom (= 0.85.3) + - React-Fabric/imagemanager (= 0.85.3) + - React-Fabric/leakchecker (= 0.85.3) + - React-Fabric/mounting (= 0.85.3) + - React-Fabric/observers (= 0.85.3) + - React-Fabric/scheduler (= 0.85.3) + - React-Fabric/telemetry (= 0.85.3) + - React-Fabric/uimanager (= 0.85.3) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animated (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/animationbackend + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animationbackend (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animations (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/attributedstring (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/bridging (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/componentregistry (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/componentregistrynative (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/components/legacyviewmanagerinterop (= 0.85.3) + - React-Fabric/components/root (= 0.85.3) + - React-Fabric/components/scrollview (= 0.85.3) + - React-Fabric/components/view (= 0.85.3) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/legacyviewmanagerinterop (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/root (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/scrollview (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/view (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-renderercss + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-Fabric/consistency (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/core (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/dom (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/imagemanager (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/leakchecker (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/mounting (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/observers/events (= 0.85.3) + - React-Fabric/observers/intersection (= 0.85.3) + - React-Fabric/observers/mutation (= 0.85.3) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers/events (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers/intersection (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers/mutation (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/scheduler (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/animationbackend + - React-Fabric/observers/events + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-performancecdpmetrics + - React-performancetimeline + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/telemetry (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/uimanager (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/uimanager/consistency (= 0.85.3) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/uimanager/consistency (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-FabricComponents (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components (= 0.85.3) + - React-FabricComponents/textlayoutmanager (= 0.85.3) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components/inputaccessory (= 0.85.3) + - React-FabricComponents/components/iostextinput (= 0.85.3) + - React-FabricComponents/components/modal (= 0.85.3) + - React-FabricComponents/components/rncore (= 0.85.3) + - React-FabricComponents/components/safeareaview (= 0.85.3) + - React-FabricComponents/components/scrollview (= 0.85.3) + - React-FabricComponents/components/switch (= 0.85.3) + - React-FabricComponents/components/text (= 0.85.3) + - React-FabricComponents/components/textinput (= 0.85.3) + - React-FabricComponents/components/unimplementedview (= 0.85.3) + - React-FabricComponents/components/virtualview (= 0.85.3) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/inputaccessory (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/iostextinput (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/modal (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/rncore (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/safeareaview (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/scrollview (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/switch (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/text (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/textinput (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/unimplementedview (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/virtualview (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/textlayoutmanager (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricImage (0.85.3): + - hermes-engine + - RCTRequired (= 0.85.3) + - RCTTypeSafety (= 0.85.3) + - React-Core-prebuilt + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsiexecutor (= 0.85.3) + - React-logger + - React-rendererdebug + - React-utils + - ReactCommon + - ReactNativeDependencies + - Yoga + - React-featureflags (0.85.3): + - React-Core-prebuilt + - ReactNativeDependencies + - React-featureflagsnativemodule (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-graphics (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsiexecutor + - React-utils + - ReactNativeDependencies + - React-hermes (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact (= 0.85.3) + - React-jsi + - React-jsiexecutor (= 0.85.3) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-oscompat + - React-perflogger (= 0.85.3) + - React-runtimeexecutor + - ReactNativeDependencies + - React-idlecallbacksnativemodule (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - React-runtimescheduler + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-ImageManager (0.85.3): + - React-Core-prebuilt + - React-Core/Default + - React-debug + - React-Fabric + - React-graphics + - React-rendererdebug + - React-utils + - ReactNativeDependencies + - React-intersectionobservernativemodule (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-Fabric + - React-Fabric/bridging + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - React-runtimescheduler + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-jserrorhandler (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - ReactCommon/turbomodule/bridging + - ReactNativeDependencies + - React-jsi (0.85.3): + - hermes-engine + - React-Core-prebuilt + - ReactNativeDependencies + - React-jsiexecutor (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-jserrorhandler + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsinspector (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-jsinspectortracing + - React-oscompat + - React-perflogger (= 0.85.3) + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsinspectorcdp (0.85.3): + - React-Core-prebuilt + - ReactNativeDependencies + - React-jsinspectornetwork (0.85.3): + - React-Core-prebuilt + - React-jsinspectorcdp + - ReactNativeDependencies + - React-jsinspectortracing (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsinspectornetwork + - React-oscompat + - React-timing + - React-utils + - ReactNativeDependencies + - React-jsitooling (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact (= 0.85.3) + - React-debug + - React-jsi (= 0.85.3) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsitracing (0.85.3): + - React-jsi + - React-logger (0.85.3): + - React-Core-prebuilt + - ReactNativeDependencies + - React-Mapbuffer (0.85.3): + - React-Core-prebuilt + - React-debug + - ReactNativeDependencies + - React-microtasksnativemodule (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-mutationobservernativemodule (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-Fabric + - React-Fabric/bridging + - React-Fabric/observers/mutation + - React-featureflags + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-biometrics (3.0.1): + - React-Core + - react-native-blue-crypto (1.0.0): + - React + - react-native-bw-file-access (1.0.0): + - React-Core + - react-native-capture-protection (2.4.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-context-menu-view (1.21.0): + - React + - react-native-document-picker (12.0.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-get-random-values (1.11.0): + - React-Core + - react-native-image-picker (8.2.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-notifications (5.2.2): + - React-Core + - react-native-safe-area-context (5.7.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - 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-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-safe-area-context/common (5.7.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-safe-area-context/fabric (5.7.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - react-native-safe-area-context/common + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-secure-key-store (2.0.10): + - React-Core + - react-native-tcp-socket (6.4.1): + - CocoaAsyncSocket + - React-Core + - react-native-vector-icons-entypo (13.1.1) + - react-native-vector-icons-fontawesome (13.1.1) + - react-native-vector-icons-fontawesome6 (13.1.1) + - react-native-vector-icons-ionicons (13.1.1) + - react-native-vector-icons-material-design-icons (13.1.1) + - react-native-vector-icons-material-icons (13.1.1) + - React-NativeModulesApple (0.85.3): + - hermes-engine + - React-callinvoker + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-runtimeexecutor + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-networking (0.85.3): + - React-Core-prebuilt + - React-jsinspectornetwork + - React-jsinspectortracing + - React-performancetimeline + - React-timing + - ReactNativeDependencies + - React-oscompat (0.85.3) + - React-perflogger (0.85.3): + - React-Core-prebuilt + - ReactNativeDependencies + - React-performancecdpmetrics (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-performancetimeline + - React-runtimeexecutor + - React-timing + - ReactNativeDependencies + - React-performancetimeline (0.85.3): + - React-Core-prebuilt + - React-featureflags + - React-jsinspector + - React-jsinspectortracing + - React-perflogger + - React-timing + - ReactNativeDependencies + - React-RCTActionSheet (0.85.3): + - React-Core/RCTActionSheetHeaders (= 0.85.3) + - React-RCTAnimation (0.85.3): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTAnimationHeaders + - React-debug + - React-featureflags + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTAppDelegate (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety - React-Core - - BVLinearGradient (2.8.0): + - React-Core-prebuilt + - React-CoreModules + - React-debug + - React-defaultsnativemodule + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsitooling + - React-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RCTImage + - React-RCTNetwork + - React-RCTRuntime + - React-rendererdebug + - React-RuntimeApple + - React-RuntimeCore + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon + - ReactNativeDependencies + - React-RCTBlob (0.85.3): + - hermes-engine + - React-Core-prebuilt + - React-Core/RCTBlobHeaders + - React-Core/RCTWebSocket + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - ReactNativeDependencies + - React-RCTFabric (0.85.3): + - hermes-engine + - RCTSwiftUIWrapper + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-FabricComponents + - React-FabricImage + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-networking + - React-performancecdpmetrics + - React-performancetimeline + - React-RCTAnimation + - React-RCTFBReactNativeSpec + - React-RCTImage + - React-RCTText + - React-rendererconsistency + - React-renderercss + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-RCTFBReactNativeSpec (0.85.3): + - hermes-engine + - RCTRequired + - RCTTypeSafety - React-Core - - CocoaAsyncSocket (7.6.5) - - DoubleConversion (1.1.6) - - FBLazyVector (0.71.11) - - FBReactNativeSpec (0.71.11): - - RCT-Folly (= 2021.07.22.00) - - RCTRequired (= 0.71.11) - - RCTTypeSafety (= 0.71.11) - - React-Core (= 0.71.11) - - React-jsi (= 0.71.11) - - ReactCommon/turbomodule/core (= 0.71.11) - - fmt (6.2.1) - - glog (0.3.5) - - hermes-engine (0.71.11): - - hermes-engine/Pre-built (= 0.71.11) - - hermes-engine/Pre-built (0.71.11) - - libevent (2.1.12) - - lottie-ios (3.4.4) - - lottie-react-native (5.1.6): - - lottie-ios (~> 3.4.0) - - React-Core - - PasscodeAuth (1.0.0): - - React - - RCT-Folly (2021.07.22.00): - - boost - - DoubleConversion - - fmt (~> 6.2.1) - - glog - - RCT-Folly/Default (= 2021.07.22.00) - - RCT-Folly/Default (2021.07.22.00): - - boost - - DoubleConversion - - fmt (~> 6.2.1) - - glog - - RCT-Folly/Futures (2021.07.22.00): - - boost - - DoubleConversion - - fmt (~> 6.2.1) - - glog - - libevent - - RCTRequired (0.71.11) - - RCTTypeSafety (0.71.11): - - FBLazyVector (= 0.71.11) - - RCTRequired (= 0.71.11) - - React-Core (= 0.71.11) - - React (0.71.11): - - React-Core (= 0.71.11) - - React-Core/DevSupport (= 0.71.11) - - React-Core/RCTWebSocket (= 0.71.11) - - React-RCTActionSheet (= 0.71.11) - - React-RCTAnimation (= 0.71.11) - - React-RCTBlob (= 0.71.11) - - React-RCTImage (= 0.71.11) - - React-RCTLinking (= 0.71.11) - - React-RCTNetwork (= 0.71.11) - - React-RCTSettings (= 0.71.11) - - React-RCTText (= 0.71.11) - - React-RCTVibration (= 0.71.11) - - React-callinvoker (0.71.11) - - React-Codegen (0.71.11): - - FBReactNativeSpec - - hermes-engine - - RCT-Folly + - React-Core-prebuilt + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec/components (= 0.85.3) + - ReactCommon + - ReactNativeDependencies + - React-RCTFBReactNativeSpec/components (0.85.3): + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics - React-jsi - - React-jsiexecutor - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - React-Core (0.71.11): - - glog + - React-NativeModulesApple + - React-rendererdebug + - React-utils + - ReactCommon + - ReactNativeDependencies + - Yoga + - React-RCTImage (0.85.3): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTImageHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - ReactNativeDependencies + - React-RCTLinking (0.85.3): + - React-Core/RCTLinkingHeaders (= 0.85.3) + - React-jsi (= 0.85.3) + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactCommon/turbomodule/core (= 0.85.3) + - React-RCTNetwork (0.85.3): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTNetworkHeaders + - React-debug + - React-featureflags + - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-NativeModulesApple + - React-networking + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTRuntime (0.85.3): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default (= 0.71.11) - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) + - React-Core + - React-Core-prebuilt + - React-debug + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-RuntimeApple + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-utils + - ReactNativeDependencies + - React-RCTSettings (0.85.3): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTSettingsHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTText (0.85.3): + - React-Core/RCTTextHeaders (= 0.85.3) - Yoga - - React-Core/CoreModulesHeaders (0.71.11): - - glog + - React-RCTVibration (0.85.3): + - React-Core-prebuilt + - React-Core/RCTVibrationHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-rendererconsistency (0.85.3) + - React-renderercss (0.85.3): + - React-debug + - React-utils + - React-rendererdebug (0.85.3): + - React-Core-prebuilt + - React-debug + - ReactNativeDependencies + - React-RuntimeApple (0.85.3): - hermes-engine - - RCT-Folly (= 2021.07.22.00) + - React-callinvoker + - React-Core-prebuilt - React-Core/Default - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) - - Yoga - - React-Core/Default (0.71.11): - - glog + - React-CoreModules + - React-cxxreact + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsitooling + - React-Mapbuffer + - React-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - React-RuntimeCore (0.85.3): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) - - Yoga - - React-Core/DevSupport (0.71.11): - - glog + - React-Core-prebuilt + - React-cxxreact + - React-Fabric + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsitooling + - React-performancetimeline + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - React-runtimeexecutor (0.85.3): + - React-Core-prebuilt + - React-debug + - React-featureflags + - React-jsi (= 0.85.3) + - React-utils + - ReactNativeDependencies + - React-RuntimeHermes (0.85.3): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default (= 0.71.11) - - React-Core/RCTWebSocket (= 0.71.11) - - React-cxxreact (= 0.71.11) + - React-Core-prebuilt + - React-featureflags - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-jsinspector (= 0.71.11) - - React-perflogger (= 0.71.11) - - Yoga - - React-Core/RCTActionSheetHeaders (0.71.11): - - glog + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-jsitracing + - React-RuntimeCore + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-runtimescheduler (0.85.3): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) - - Yoga - - React-Core/RCTAnimationHeaders (0.71.11): - - glog + - React-callinvoker + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - React-jsinspectortracing + - React-performancetimeline + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-timing + - React-utils + - ReactNativeDependencies + - React-timing (0.85.3): + - React-debug + - React-utils (0.85.3): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) - - Yoga - - React-Core/RCTBlobHeaders (0.71.11): - - glog + - React-Core-prebuilt + - React-debug + - React-jsi (= 0.85.3) + - ReactNativeDependencies + - React-webperformancenativemodule (0.85.3): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) - - Yoga - - React-Core/RCTImageHeaders (0.71.11): - - glog + - React-Core-prebuilt + - React-cxxreact + - React-jsi + - React-jsiexecutor + - React-performancetimeline + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - ReactAppDependencyProvider (0.85.3): + - ReactCodegen + - ReactCodegen (0.85.3): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) - - Yoga - - React-Core/RCTLinkingHeaders (0.71.11): - - glog + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-FabricImage + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-NativeModulesApple + - React-RCTAppDelegate + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - ReactCommon (0.85.3): + - React-Core-prebuilt + - ReactCommon/turbomodule (= 0.85.3) + - ReactNativeDependencies + - ReactCommon/turbomodule (0.85.3): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) - - Yoga - - React-Core/RCTNetworkHeaders (0.71.11): - - glog + - React-callinvoker (= 0.85.3) + - React-Core-prebuilt + - React-cxxreact (= 0.85.3) + - React-jsi (= 0.85.3) + - React-logger (= 0.85.3) + - React-perflogger (= 0.85.3) + - ReactCommon/turbomodule/bridging (= 0.85.3) + - ReactCommon/turbomodule/core (= 0.85.3) + - ReactNativeDependencies + - ReactCommon/turbomodule/bridging (0.85.3): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) - - Yoga - - React-Core/RCTSettingsHeaders (0.71.11): - - glog + - React-callinvoker (= 0.85.3) + - React-Core-prebuilt + - React-cxxreact (= 0.85.3) + - React-jsi (= 0.85.3) + - React-logger (= 0.85.3) + - React-perflogger (= 0.85.3) + - ReactNativeDependencies + - ReactCommon/turbomodule/core (0.85.3): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) - - Yoga - - React-Core/RCTTextHeaders (0.71.11): - - glog + - React-callinvoker (= 0.85.3) + - React-Core-prebuilt + - React-cxxreact (= 0.85.3) + - React-debug (= 0.85.3) + - React-featureflags (= 0.85.3) + - React-jsi (= 0.85.3) + - React-logger (= 0.85.3) + - React-perflogger (= 0.85.3) + - React-utils (= 0.85.3) + - ReactNativeDependencies + - ReactNativeCameraKit (17.0.4): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-Core/RCTVibrationHeaders (0.71.11): - - glog + - ReactNativeDependencies (0.85.3) + - RealmJS (20.2.0): + - React + - RNCAsyncStorage (2.2.0): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-Core/RCTWebSocket (0.71.11): - - glog + - RNCClipboard (1.16.3): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default (= 0.71.11) - - React-cxxreact (= 0.71.11) - - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) - - Yoga - - React-CoreModules (0.71.11): - - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.71.11) - - React-Codegen (= 0.71.11) - - React-Core/CoreModulesHeaders (= 0.71.11) - - React-jsi (= 0.71.11) - - React-RCTBlob - - React-RCTImage (= 0.71.11) - - ReactCommon/turbomodule/core (= 0.71.11) - - React-cxxreact (0.71.11): - - boost (= 1.76.0) - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-callinvoker (= 0.71.11) - - React-jsi (= 0.71.11) - - React-jsinspector (= 0.71.11) - - React-logger (= 0.71.11) - - React-perflogger (= 0.71.11) - - React-runtimeexecutor (= 0.71.11) - - React-hermes (0.71.11): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - RCT-Folly/Futures (= 2021.07.22.00) - - React-cxxreact (= 0.71.11) - - React-jsi - - React-jsiexecutor (= 0.71.11) - - React-jsinspector (= 0.71.11) - - React-perflogger (= 0.71.11) - - React-jsi (0.71.11): - - boost (= 1.76.0) - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-jsiexecutor (0.71.11): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-cxxreact (= 0.71.11) - - React-jsi (= 0.71.11) - - React-perflogger (= 0.71.11) - - React-jsinspector (0.71.11) - - React-logger (0.71.11): - - glog - - react-native-blue-crypto (1.0.0): - - React - - react-native-document-picker (8.2.0): - - React-Core - - react-native-fingerprint-scanner (6.0.0): - - React - - react-native-idle-timer (2.1.6): - - React-Core - - react-native-image-picker (4.8.5): - - React-Core - - react-native-ios-context-menu (1.15.3): - - React-Core - - react-native-qrcode-local-image (1.0.4): - - React - - react-native-randombytes (3.6.1): - - React-Core - - react-native-safe-area-context (3.4.1): - - React-Core - - react-native-secure-key-store (2.0.10): - - React-Core - - react-native-tcp-socket (5.6.2): - - CocoaAsyncSocket - - React-Core - - react-native-tor (0.1.8): - - React - - react-native-webview (12.4.0): - - React-Core - - react-native-widget-center (0.0.9): - - React - - React-perflogger (0.71.11) - - React-RCTActionSheet (0.71.11): - - React-Core/RCTActionSheetHeaders (= 0.71.11) - - React-RCTAnimation (0.71.11): - - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.71.11) - - React-Codegen (= 0.71.11) - - React-Core/RCTAnimationHeaders (= 0.71.11) - - React-jsi (= 0.71.11) - - ReactCommon/turbomodule/core (= 0.71.11) - - React-RCTAppDelegate (0.71.11): - - RCT-Folly - - RCTRequired - - RCTTypeSafety - - React-Core - - ReactCommon/turbomodule/core - - React-RCTBlob (0.71.11): - - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Codegen (= 0.71.11) - - React-Core/RCTBlobHeaders (= 0.71.11) - - React-Core/RCTWebSocket (= 0.71.11) - - React-jsi (= 0.71.11) - - React-RCTNetwork (= 0.71.11) - - ReactCommon/turbomodule/core (= 0.71.11) - - React-RCTImage (0.71.11): - - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.71.11) - - React-Codegen (= 0.71.11) - - React-Core/RCTImageHeaders (= 0.71.11) - - React-jsi (= 0.71.11) - - React-RCTNetwork (= 0.71.11) - - ReactCommon/turbomodule/core (= 0.71.11) - - React-RCTLinking (0.71.11): - - React-Codegen (= 0.71.11) - - React-Core/RCTLinkingHeaders (= 0.71.11) - - React-jsi (= 0.71.11) - - ReactCommon/turbomodule/core (= 0.71.11) - - React-RCTNetwork (0.71.11): - - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.71.11) - - React-Codegen (= 0.71.11) - - React-Core/RCTNetworkHeaders (= 0.71.11) - - React-jsi (= 0.71.11) - - ReactCommon/turbomodule/core (= 0.71.11) - - React-RCTSettings (0.71.11): - - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.71.11) - - React-Codegen (= 0.71.11) - - React-Core/RCTSettingsHeaders (= 0.71.11) - - React-jsi (= 0.71.11) - - ReactCommon/turbomodule/core (= 0.71.11) - - React-RCTText (0.71.11): - - React-Core/RCTTextHeaders (= 0.71.11) - - React-RCTVibration (0.71.11): - - RCT-Folly (= 2021.07.22.00) - - React-Codegen (= 0.71.11) - - React-Core/RCTVibrationHeaders (= 0.71.11) - - React-jsi (= 0.71.11) - - ReactCommon/turbomodule/core (= 0.71.11) - - React-runtimeexecutor (0.71.11): - - React-jsi (= 0.71.11) - - ReactCommon/turbomodule/bridging (0.71.11): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-callinvoker (= 0.71.11) - - React-Core (= 0.71.11) - - React-cxxreact (= 0.71.11) - - React-jsi (= 0.71.11) - - React-logger (= 0.71.11) - - React-perflogger (= 0.71.11) - - ReactCommon/turbomodule/core (0.71.11): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-callinvoker (= 0.71.11) - - React-Core (= 0.71.11) - - React-cxxreact (= 0.71.11) - - React-jsi (= 0.71.11) - - React-logger (= 0.71.11) - - React-perflogger (= 0.71.11) - - ReactNativeCameraKit (13.0.0): - - React-Core - - RealmJS (11.10.1): - - React - - rn-ldk (0.8.4): - - React-Core - - RNCAsyncStorage (1.19.1): - - React-Core - - RNCClipboard (1.11.2): - - React-Core - - RNCPushNotificationIOS (1.11.0): + - RCTRequired + - RCTTypeSafety - React-Core - - RNDefaultPreference (1.4.4): + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNDefaultPreference (1.5.1): - React-Core - - RNDeviceInfo (8.7.1): + - RNDeviceInfo (14.1.1): - React-Core - RNFS (2.20.0): - React-Core - - RNGestureHandler (2.9.0): + - RNGestureHandler (2.31.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga - RNHandoff (0.0.3): - React - - RNKeychain (8.1.1): + - RNKeychain (10.0.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety - React-Core - - RNLocalize (3.0.2): + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNLocalize (3.7.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety - React-Core - - RNPrivacySnapshot (1.0.0): - - React + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNPermissions (5.5.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga - RNQuickAction (0.3.13): - React - - RNRate (1.2.12): + - RNReactNativeHapticFeedback (2.3.4): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNReanimated (4.3.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety - React-Core - - RNReactNativeHapticFeedback (2.0.3): + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNReanimated/apple (= 4.3.0) + - RNReanimated/common (= 4.3.0) + - RNWorklets + - Yoga + - RNReanimated/apple (4.3.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety - React-Core - - RNReanimated (2.17.0): - - DoubleConversion - - FBLazyVector - - FBReactNativeSpec - - glog - - RCT-Folly + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets + - Yoga + - RNReanimated/common (4.3.0): + - hermes-engine - RCTRequired - RCTTypeSafety - - React-callinvoker - React-Core - - React-Core/DevSupport - - React-Core/RCTWebSocket - - React-CoreModules - - React-cxxreact + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-RCTActionSheet - - React-RCTAnimation - - React-RCTBlob + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets + - Yoga + - RNScreens (4.24.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric - React-RCTImage - - React-RCTLinking - - React-RCTNetwork - - React-RCTSettings - - React-RCTText + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNScreens/common (= 4.24.0) - Yoga - - RNScreens (3.20.0): + - RNScreens/common (4.24.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric - React-RCTImage - - RNShare (8.2.2): + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNShare (12.2.6): + - hermes-engine + - RCTRequired + - RCTTypeSafety - React-Core - - RNSVG (13.9.0): + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNSVG (15.15.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety - React-Core - - RNVectorIcons (9.2.0): + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNSVG/common (= 15.15.5) + - Yoga + - RNSVG/common (15.15.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga - RNWatch (1.1.0): - React - - Yoga (1.14.0) + - RNWorklets (0.8.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets/apple (= 0.8.1) + - RNWorklets/common (= 0.8.1) + - Yoga + - RNWorklets/apple (0.8.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNWorklets/common (0.8.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - Yoga (0.0.0) DEPENDENCIES: - - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - - Bugsnag - "BugsnagReactNative (from `../node_modules/@bugsnag/react-native`)" - BVLinearGradient (from `../node_modules/react-native-linear-gradient`) - - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) - - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - - libevent (~> 2.1.12) - - lottie-ios (from `../node_modules/lottie-ios`) - lottie-react-native (from `../node_modules/lottie-react-native`) - - PasscodeAuth (from `../node_modules/react-native-passcode-auth`) - - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) + - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../node_modules/react-native/Libraries/Required`) + - RCTSwiftUI (from `../node_modules/react-native/ReactApple/RCTSwiftUI`) + - RCTSwiftUIWrapper (from `../node_modules/react-native/ReactApple/RCTSwiftUIWrapper`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - React (from `../node_modules/react-native/`) - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) - - React-Codegen (from `build/generated/ios`) - React-Core (from `../node_modules/react-native/`) + - React-Core-prebuilt (from `../node_modules/react-native/React-Core-prebuilt.podspec`) - React-Core/RCTWebSocket (from `../node_modules/react-native/`) - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) + - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) + - React-defaultsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) + - React-domnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/dom`) + - React-Fabric (from `../node_modules/react-native/ReactCommon`) + - React-FabricComponents (from `../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) + - React-featureflagsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) + - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) + - React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) + - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-intersectionobservernativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver`) + - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) + - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsinspectorcdp (from `../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) + - React-jsinspectornetwork (from `../node_modules/react-native/ReactCommon/jsinspector-modern/network`) + - React-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) + - React-jsitooling (from `../node_modules/react-native/ReactCommon/jsitooling`) + - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) + - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - React-mutationobservernativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/mutationobserver`) + - react-native-biometrics (from `../node_modules/react-native-biometrics`) - react-native-blue-crypto (from `../node_modules/react-native-blue-crypto`) - - react-native-document-picker (from `../node_modules/react-native-document-picker`) - - react-native-fingerprint-scanner (from `../node_modules/react-native-fingerprint-scanner`) - - react-native-idle-timer (from `../node_modules/react-native-idle-timer`) + - react-native-bw-file-access (from `../blue_modules/react-native-bw-file-access`) + - react-native-capture-protection (from `../node_modules/react-native-capture-protection`) + - react-native-context-menu-view (from `../node_modules/react-native-context-menu-view`) + - "react-native-document-picker (from `../node_modules/@react-native-documents/picker`)" + - react-native-get-random-values (from `../node_modules/react-native-get-random-values`) - react-native-image-picker (from `../node_modules/react-native-image-picker`) - - react-native-ios-context-menu (from `../node_modules/react-native-ios-context-menu`) - - "react-native-qrcode-local-image (from `../node_modules/@remobile/react-native-qrcode-local-image`)" - - react-native-randombytes (from `../node_modules/react-native-randombytes`) + - react-native-notifications (from `../node_modules/react-native-notifications`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-secure-key-store (from `../node_modules/react-native-secure-key-store`) - react-native-tcp-socket (from `../node_modules/react-native-tcp-socket`) - - react-native-tor (from `../node_modules/react-native-tor`) - - react-native-webview (from `../node_modules/react-native-webview`) - - react-native-widget-center (from `../node_modules/react-native-widget-center`) + - "react-native-vector-icons-entypo (from `../node_modules/@react-native-vector-icons/entypo`)" + - "react-native-vector-icons-fontawesome (from `../node_modules/@react-native-vector-icons/fontawesome`)" + - "react-native-vector-icons-fontawesome6 (from `../node_modules/@react-native-vector-icons/fontawesome6`)" + - "react-native-vector-icons-ionicons (from `../node_modules/@react-native-vector-icons/ionicons`)" + - "react-native-vector-icons-material-design-icons (from `../node_modules/@react-native-vector-icons/material-design-icons`)" + - "react-native-vector-icons-material-icons (from `../node_modules/@react-native-vector-icons/material-icons`)" + - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-networking (from `../node_modules/react-native/ReactCommon/react/networking`) + - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) + - React-performancecdpmetrics (from `../node_modules/react-native/ReactCommon/react/performance/cdpmetrics`) + - React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`) - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) + - React-RCTFabric (from `../node_modules/react-native/React`) + - React-RCTFBReactNativeSpec (from `../node_modules/react-native/React`) - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) + - React-RCTRuntime (from `../node_modules/react-native/React/Runtime`) - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) + - React-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`) + - React-renderercss (from `../node_modules/react-native/ReactCommon/react/renderer/css`) + - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - React-timing (from `../node_modules/react-native/ReactCommon/react/timing`) + - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) + - React-webperformancenativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/webperformance`) + - ReactAppDependencyProvider (from `build/generated/ios/ReactAppDependencyProvider`) + - ReactCodegen (from `build/generated/ios/ReactCodegen`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - - ReactNativeCameraKit (from `../node_modules/react-native-camera-kit`) + - 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`) - - rn-ldk (from `../node_modules/rn-ldk`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)" - - "RNCPushNotificationIOS (from `../node_modules/@react-native-community/push-notification-ios`)" - RNDefaultPreference (from `../node_modules/react-native-default-preference`) - RNDeviceInfo (from `../node_modules/react-native-device-info`) - RNFS (from `../node_modules/react-native-fs`) @@ -533,105 +2552,156 @@ DEPENDENCIES: - RNHandoff (from `../node_modules/react-native-handoff`) - RNKeychain (from `../node_modules/react-native-keychain`) - RNLocalize (from `../node_modules/react-native-localize`) - - RNPrivacySnapshot (from `../node_modules/react-native-privacy-snapshot`) + - RNPermissions (from `../node_modules/react-native-permissions`) - RNQuickAction (from `../node_modules/react-native-quick-actions`) - - RNRate (from `../node_modules/react-native-rate`) - RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`) - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) - RNShare (from `../node_modules/react-native-share`) - RNSVG (from `../node_modules/react-native-svg`) - - RNVectorIcons (from `../node_modules/react-native-vector-icons`) - RNWatch (from `../node_modules/react-native-watch-connectivity`) + - RNWorklets (from `../node_modules/react-native-worklets`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: trunk: - CocoaAsyncSocket - - fmt - - libevent + - lottie-ios EXTERNAL SOURCES: - boost: - :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" BugsnagReactNative: :path: "../node_modules/@bugsnag/react-native" BVLinearGradient: :path: "../node_modules/react-native-linear-gradient" - DoubleConversion: - :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" FBLazyVector: :path: "../node_modules/react-native/Libraries/FBLazyVector" - FBReactNativeSpec: - :path: "../node_modules/react-native/React/FBReactNativeSpec" - glog: - :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" - lottie-ios: - :path: "../node_modules/lottie-ios" + :tag: hermes-v250829098.0.10 lottie-react-native: :path: "../node_modules/lottie-react-native" - PasscodeAuth: - :path: "../node_modules/react-native-passcode-auth" - RCT-Folly: - :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" + RCTDeprecation: + :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" RCTRequired: - :path: "../node_modules/react-native/Libraries/RCTRequired" + :path: "../node_modules/react-native/Libraries/Required" + RCTSwiftUI: + :path: "../node_modules/react-native/ReactApple/RCTSwiftUI" + RCTSwiftUIWrapper: + :path: "../node_modules/react-native/ReactApple/RCTSwiftUIWrapper" RCTTypeSafety: :path: "../node_modules/react-native/Libraries/TypeSafety" React: :path: "../node_modules/react-native/" React-callinvoker: :path: "../node_modules/react-native/ReactCommon/callinvoker" - React-Codegen: - :path: build/generated/ios React-Core: :path: "../node_modules/react-native/" + React-Core-prebuilt: + :podspec: "../node_modules/react-native/React-Core-prebuilt.podspec" React-CoreModules: :path: "../node_modules/react-native/React/CoreModules" React-cxxreact: :path: "../node_modules/react-native/ReactCommon/cxxreact" + React-debug: + :path: "../node_modules/react-native/ReactCommon/react/debug" + React-defaultsnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/defaults" + React-domnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/dom" + React-Fabric: + :path: "../node_modules/react-native/ReactCommon" + React-FabricComponents: + :path: "../node_modules/react-native/ReactCommon" + React-FabricImage: + :path: "../node_modules/react-native/ReactCommon" + React-featureflags: + :path: "../node_modules/react-native/ReactCommon/react/featureflags" + React-featureflagsnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" + React-graphics: + :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" React-hermes: :path: "../node_modules/react-native/ReactCommon/hermes" + React-idlecallbacksnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" + React-ImageManager: + :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + React-intersectionobservernativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver" + React-jserrorhandler: + :path: "../node_modules/react-native/ReactCommon/jserrorhandler" React-jsi: :path: "../node_modules/react-native/ReactCommon/jsi" React-jsiexecutor: :path: "../node_modules/react-native/ReactCommon/jsiexecutor" React-jsinspector: - :path: "../node_modules/react-native/ReactCommon/jsinspector" + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" + React-jsinspectorcdp: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" + React-jsinspectornetwork: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/network" + React-jsinspectortracing: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" + React-jsitooling: + :path: "../node_modules/react-native/ReactCommon/jsitooling" + React-jsitracing: + :path: "../node_modules/react-native/ReactCommon/hermes/executor/" React-logger: :path: "../node_modules/react-native/ReactCommon/logger" + React-Mapbuffer: + :path: "../node_modules/react-native/ReactCommon" + React-microtasksnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + React-mutationobservernativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/mutationobserver" + react-native-biometrics: + :path: "../node_modules/react-native-biometrics" react-native-blue-crypto: :path: "../node_modules/react-native-blue-crypto" + react-native-bw-file-access: + :path: "../blue_modules/react-native-bw-file-access" + react-native-capture-protection: + :path: "../node_modules/react-native-capture-protection" + react-native-context-menu-view: + :path: "../node_modules/react-native-context-menu-view" react-native-document-picker: - :path: "../node_modules/react-native-document-picker" - react-native-fingerprint-scanner: - :path: "../node_modules/react-native-fingerprint-scanner" - react-native-idle-timer: - :path: "../node_modules/react-native-idle-timer" + :path: "../node_modules/@react-native-documents/picker" + react-native-get-random-values: + :path: "../node_modules/react-native-get-random-values" react-native-image-picker: :path: "../node_modules/react-native-image-picker" - react-native-ios-context-menu: - :path: "../node_modules/react-native-ios-context-menu" - react-native-qrcode-local-image: - :path: "../node_modules/@remobile/react-native-qrcode-local-image" - react-native-randombytes: - :path: "../node_modules/react-native-randombytes" + react-native-notifications: + :path: "../node_modules/react-native-notifications" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" react-native-secure-key-store: :path: "../node_modules/react-native-secure-key-store" react-native-tcp-socket: :path: "../node_modules/react-native-tcp-socket" - react-native-tor: - :path: "../node_modules/react-native-tor" - react-native-webview: - :path: "../node_modules/react-native-webview" - react-native-widget-center: - :path: "../node_modules/react-native-widget-center" + react-native-vector-icons-entypo: + :path: "../node_modules/@react-native-vector-icons/entypo" + react-native-vector-icons-fontawesome: + :path: "../node_modules/@react-native-vector-icons/fontawesome" + react-native-vector-icons-fontawesome6: + :path: "../node_modules/@react-native-vector-icons/fontawesome6" + react-native-vector-icons-ionicons: + :path: "../node_modules/@react-native-vector-icons/ionicons" + react-native-vector-icons-material-design-icons: + :path: "../node_modules/@react-native-vector-icons/material-design-icons" + react-native-vector-icons-material-icons: + :path: "../node_modules/@react-native-vector-icons/material-icons" + React-NativeModulesApple: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + React-networking: + :path: "../node_modules/react-native/ReactCommon/react/networking" + React-oscompat: + :path: "../node_modules/react-native/ReactCommon/oscompat" React-perflogger: :path: "../node_modules/react-native/ReactCommon/reactperflogger" + React-performancecdpmetrics: + :path: "../node_modules/react-native/ReactCommon/react/performance/cdpmetrics" + React-performancetimeline: + :path: "../node_modules/react-native/ReactCommon/react/performance/timeline" React-RCTActionSheet: :path: "../node_modules/react-native/Libraries/ActionSheetIOS" React-RCTAnimation: @@ -640,34 +2710,62 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/Libraries/AppDelegate" React-RCTBlob: :path: "../node_modules/react-native/Libraries/Blob" + React-RCTFabric: + :path: "../node_modules/react-native/React" + React-RCTFBReactNativeSpec: + :path: "../node_modules/react-native/React" React-RCTImage: :path: "../node_modules/react-native/Libraries/Image" React-RCTLinking: :path: "../node_modules/react-native/Libraries/LinkingIOS" React-RCTNetwork: :path: "../node_modules/react-native/Libraries/Network" + React-RCTRuntime: + :path: "../node_modules/react-native/React/Runtime" React-RCTSettings: :path: "../node_modules/react-native/Libraries/Settings" React-RCTText: :path: "../node_modules/react-native/Libraries/Text" React-RCTVibration: :path: "../node_modules/react-native/Libraries/Vibration" + React-rendererconsistency: + :path: "../node_modules/react-native/ReactCommon/react/renderer/consistency" + React-renderercss: + :path: "../node_modules/react-native/ReactCommon/react/renderer/css" + React-rendererdebug: + :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" + React-RuntimeApple: + :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + React-RuntimeCore: + :path: "../node_modules/react-native/ReactCommon/react/runtime" React-runtimeexecutor: :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" + React-RuntimeHermes: + :path: "../node_modules/react-native/ReactCommon/react/runtime" + React-runtimescheduler: + :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + React-timing: + :path: "../node_modules/react-native/ReactCommon/react/timing" + React-utils: + :path: "../node_modules/react-native/ReactCommon/react/utils" + React-webperformancenativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/webperformance" + ReactAppDependencyProvider: + :path: build/generated/ios/ReactAppDependencyProvider + ReactCodegen: + :path: build/generated/ios/ReactCodegen ReactCommon: :path: "../node_modules/react-native/ReactCommon" ReactNativeCameraKit: - :path: "../node_modules/react-native-camera-kit" + :path: "../node_modules/react-native-camera-kit-no-google" + ReactNativeDependencies: + :podspec: "../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec" RealmJS: :path: "../node_modules/realm" - rn-ldk: - :path: "../node_modules/rn-ldk" RNCAsyncStorage: :path: "../node_modules/@react-native-async-storage/async-storage" RNCClipboard: :path: "../node_modules/@react-native-clipboard/clipboard" - RNCPushNotificationIOS: - :path: "../node_modules/@react-native-community/push-notification-ios" RNDefaultPreference: :path: "../node_modules/react-native-default-preference" RNDeviceInfo: @@ -682,12 +2780,10 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-keychain" RNLocalize: :path: "../node_modules/react-native-localize" - RNPrivacySnapshot: - :path: "../node_modules/react-native-privacy-snapshot" + RNPermissions: + :path: "../node_modules/react-native-permissions" RNQuickAction: :path: "../node_modules/react-native-quick-actions" - RNRate: - :path: "../node_modules/react-native-rate" RNReactNativeHapticFeedback: :path: "../node_modules/react-native-haptic-feedback" RNReanimated: @@ -698,94 +2794,132 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-share" RNSVG: :path: "../node_modules/react-native-svg" - RNVectorIcons: - :path: "../node_modules/react-native-vector-icons" RNWatch: :path: "../node_modules/react-native-watch-connectivity" + RNWorklets: + :path: "../node_modules/react-native-worklets" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: 57d2868c099736d80fcd648bf211b4431e51a558 - BugsnagReactNative: bf6f4ebababa8536726b3014c7d3e4af8c53d488 - BVLinearGradient: 612a04ff38e8480291f3379ee5b5a2c571f03fe0 + BugsnagReactNative: bee770e3f497a8571feb1579bdc083a070bee1f3 + BVLinearGradient: cb006ba232a1f3e4f341bb62c42d1098c284da70 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 - DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 - FBLazyVector: c511d4cd0210f416cb5c289bd5ae6b36d909b048 - FBReactNativeSpec: a911fb22def57aef1d74215e8b6b8761d25c1c54 - fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - hermes-engine: 34c863b446d0135b85a6536fa5fd89f48196f848 - libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - lottie-ios: 8f97d3271e155c2d688875c29cd3c74908aef5f8 - lottie-react-native: 8f9d4be452e23f6e5ca0fdc11669dc99ab52be81 - PasscodeAuth: 3e88093ff46c31a952d8b36c488240de980517be - RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 - RCTRequired: f6187ec763637e6a57f5728dd9a3bdabc6d6b4e0 - RCTTypeSafety: a01aca2dd3b27fa422d5239252ad38e54e958750 - React: 741b4f5187e7a2137b69c88e65f940ba40600b4b - React-callinvoker: 72ba74b2d5d690c497631191ae6eeca0c043d9cf - React-Codegen: 8a7cda1633e4940de8a710f6bf5cae5dd673546e - React-Core: 72bb19702c465b6451a40501a2879532bec9acee - React-CoreModules: ffd19b082fc36b9b463fedf30955138b5426c053 - React-cxxreact: 8b3dd87e3b8ea96dd4ad5c7bac8f31f1cc3da97f - React-hermes: be95942c3f47fc032da1387360413f00dae0ea68 - React-jsi: 9978e2a64c2a4371b40e109f4ef30a33deaa9bcb - React-jsiexecutor: 18b5b33c5f2687a784a61bc8176611b73524ae77 - React-jsinspector: b6ed4cb3ffa27a041cd440300503dc512b761450 - React-logger: 186dd536128ae5924bc38ed70932c00aa740cd5b - react-native-blue-crypto: 23f1558ad3d38d7a2edb7e2f6ed1bc520ed93e56 - react-native-document-picker: 495c444c0c773c6e83a5d91165890ecb1c0a399a - react-native-fingerprint-scanner: ac6656f18c8e45a7459302b84da41a44ad96dbbe - react-native-idle-timer: f7f651542b39dce9b9473e4578cb64a255075f17 - react-native-image-picker: cd420f97f6ed6ff74fc4686d27dbcfdbd051db91 - react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5 - react-native-qrcode-local-image: 35ccb306e4265bc5545f813e54cc830b5d75bcfc - react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846 - react-native-safe-area-context: 9e40fb181dac02619414ba1294d6c2a807056ab9 - react-native-secure-key-store: 910e6df6bc33cb790aba6ee24bc7818df1fe5898 - react-native-tcp-socket: c1b7297619616b4c9caae6889bcb0aba78086989 - react-native-tor: 3b14e9160b2eb7fa3f310921b2dee71a5171e5b7 - react-native-webview: 65f1143983cfeaedf02fd25b2621d3f4a37075de - react-native-widget-center: 12dfba20a4fa995850b52cf0afecf734397f4b9c - React-perflogger: e706562ab7eb8eb590aa83a224d26fa13963d7f2 - React-RCTActionSheet: 57d4bd98122f557479a3359ad5dad8e109e20c5a - React-RCTAnimation: ccf3ef00101ea74bda73a045d79a658b36728a60 - React-RCTAppDelegate: d0c28a35c65e9a0aef287ac0dafe1b71b1ac180c - React-RCTBlob: 1700b92ece4357af0a49719c9638185ad2902e95 - React-RCTImage: f2e4904566ccccaa4b704170fcc5ae144ca347bf - React-RCTLinking: 52a3740e3651e30aa11dff5a6debed7395dd8169 - React-RCTNetwork: ea0976f2b3ffc7877cd7784e351dc460adf87b12 - React-RCTSettings: ed5ac992b23e25c65c3cc31f11b5c940ae5e3e60 - React-RCTText: c9dfc6722621d56332b4f3a19ac38105e7504145 - React-RCTVibration: f09f08de63e4122deb32506e20ca4cae6e4e14c1 - React-runtimeexecutor: 4817d63dbc9d658f8dc0ec56bd9b83ce531129f0 - ReactCommon: 08723d2ed328c5cbcb0de168f231bc7bae7f8aa1 - ReactNativeCameraKit: 9d46a5d7dd544ca64aa9c03c150d2348faf437eb - RealmJS: 8deebf00c84f499c654bc585583c54f55b586751 - rn-ldk: 0d8749d98cc5ce67302a32831818c116b67f7643 - RNCAsyncStorage: f47fe18526970a69c34b548883e1aeceb115e3e1 - RNCClipboard: 3f0451a8100393908bea5c5c5b16f96d45f30bfc - RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8 - RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31 - RNDeviceInfo: aad3c663b25752a52bf8fce93f2354001dd185aa - RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 - RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39 - RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa - RNKeychain: ff836453cba46938e0e9e4c22e43d43fa2c90333 - RNLocalize: dbea38dcb344bf80ff18a1757b1becf11f70cae4 - RNPrivacySnapshot: 71919dde3c6a29dd332115409c2aec564afee8f4 - RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93 - RNRate: ef3bcff84f39bb1d1e41c5593d3eea4aab2bd73a - RNReactNativeHapticFeedback: afa5bf2794aecbb2dba2525329253da0d66656df - RNReanimated: f186e85d9f28c9383d05ca39e11dd194f59093ec - RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f - RNShare: d82e10f6b7677f4b0048c23709bd04098d5aee6c - RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315 - RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8 - RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236 - Yoga: f7decafdc5e8c125e6fa0da38a687e35238420fa + FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d + hermes-engine: 86cdbf283775c54dc008895c3eacd24a1f2a40b4 + lottie-ios: 8f959969761e9c45d70353667d00af0e5b9cadb3 + lottie-react-native: 26b365c3d5615e87f4db048dcb151de3eb9a8e76 + RCTDeprecation: a4c521821fab57cbb125b36effe84d897d0dfa12 + RCTRequired: 9f3a7e5645d4bc3f551593de7550bb66ab6e42bc + RCTSwiftUI: 239ed2eb9e73de5a6f518810630f0c95e01c8702 + RCTSwiftUIWrapper: 966ca7f5f22ac0b2b2255fb09cffc381f5440b03 + RCTTypeSafety: 2a6403ba3492c04510e7c15bd635461646c43bb2 + React: e2dc35338068bbd299c66f043ae0d7f25de8499e + React-callinvoker: 28b25d21b124c26cebaea713ba7d801b9351dc48 + React-Core: 02ed7d2ffb70437bdf2aba074a13078a7b0b9ff0 + React-Core-prebuilt: 9e875134f667c471ab68bf9edf1661fa11b86540 + React-CoreModules: b3a5a42dadcde3b5d47b325bd912eb2ced89e146 + React-cxxreact: fe8f88dda044e5905e99a00f41b7a874c3908716 + React-debug: 92944dc4d89f56d640e75498266cbde557a48189 + React-defaultsnativemodule: cd64bc09d7ca24112bbaf1b91edbbcf3d81ea7dc + React-domnativemodule: dacf5bc055ae041039574f38b73a20b91e368774 + React-Fabric: bb0baa33d91839631d315800eb23e9aaa4338a44 + React-FabricComponents: c504d0b0e2f3054b2ba19af839f175cb361153c0 + React-FabricImage: 91eaea1cc58d25ae2596a9277bcfe028f92374c3 + React-featureflags: 5ac0455da0af12ca79b40402e2f42c5c7556b638 + React-featureflagsnativemodule: df7da181b064f10f5959a7cd529b5aab3686ecb2 + React-graphics: d25b1195baf24c7918543f4aff9be89cc080906f + React-hermes: 663286153a8ad6bf752b742654c766ff5e8991e7 + React-idlecallbacksnativemodule: 0bd5392cb67f1ab25df736814b7c05213b1d3c68 + React-ImageManager: a03eed3e3d4222130dc0ad503a1a5f3aa89de746 + React-intersectionobservernativemodule: 5d0f1c3c7b30031b0f6f730ee52dd9d607e2c966 + React-jserrorhandler: d5d6e7e20c5a2d6e8607e18d31d8712d7de676f6 + React-jsi: eb7cc4cffcf24796cc302d5b2bca0e92544139a9 + React-jsiexecutor: 65006f60e64c72c6b82f62ef6bd17c84846e73f8 + React-jsinspector: 01e32e2247b2486117fbb143db7f7717ef462c6d + React-jsinspectorcdp: f1cbb34ca41d188ba22efd9b663cf258a911f6cd + React-jsinspectornetwork: f61acc94c881c41451f508abfe6efa748b956c21 + React-jsinspectortracing: 9395894d9bd4d17931b9afcf230c0e7f4cb3d674 + React-jsitooling: 03ead841daa12a93b18479f5e400ceab3732d36d + React-jsitracing: 4ae61c79e14360d1c6ab566031c62c490da78439 + React-logger: bf149dea4343a9037b74bade36cced8b63f03f46 + React-Mapbuffer: fec3e025f0ffba6b32cd2a1d7bbdee3e269aae90 + React-microtasksnativemodule: ab33a818d339f5a1da308893c11b487be66121a8 + React-mutationobservernativemodule: a42d1626651ccd7d0dc02a56e69d4ec77c248893 + react-native-biometrics: 43ed5b828646a7862dbc7945556446be00798e7d + react-native-blue-crypto: de5babd59b17fbf3fc31d2e1e5d59ec859093fbc + react-native-bw-file-access: fe925b77dbf48500df0b294c6851f8c84607a203 + react-native-capture-protection: a8a5c641e8ee1136a2e4f94912868e2bd4c9e300 + react-native-context-menu-view: da39a4612c1294d651907162529cfba8421e9b89 + react-native-document-picker: 15cca2d1a6bfb6d0d3ff0283bd9b903e57cb028b + react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba + react-native-image-picker: 23540feacc79c63c60857f318fdfa8477c26e70a + react-native-notifications: e2d3c022d6077de7e420ba5c01b4bd9464f3941d + react-native-safe-area-context: 6b4966397ada0f7dd481e4486a2ef936834861a1 + react-native-secure-key-store: eb45b44bdec3f48e9be5cdfca0f49ddf64892ea6 + react-native-tcp-socket: 7c7e53a07f122ecf00fb3626684bc0ca82c4f044 + react-native-vector-icons-entypo: f9de1c24005da510dde0de27caf0d2f5471bd433 + react-native-vector-icons-fontawesome: 7fb9da054077e3f34a33a096210ef2b2d55ac4b6 + react-native-vector-icons-fontawesome6: 9e06c3add7acc69821381f36140969a41c7f501c + react-native-vector-icons-ionicons: c416c2cdf94f4cd9957ec77fde502e3410e40d14 + react-native-vector-icons-material-design-icons: 99c750d15cbc5df6f240bf1e7a45a0c07d42a015 + react-native-vector-icons-material-icons: 1c5ff7b5da7188a9885b2f0b8f12ed8afa4a0ea2 + React-NativeModulesApple: deba264b03bd79c6bd61014fa30e40321b5e443a + React-networking: 35e6070b084f435429f85c5db40b4d5b38652fe9 + React-oscompat: 64a0c7ef5441855dc6e2a6afe8ba8f92aa05075e + React-perflogger: ca04287f205086a1edb5c95882be7b6068458889 + React-performancecdpmetrics: cf1d0a3178ccd59353cedcacbda421f40100a889 + React-performancetimeline: c9771212e7a43032d6f8d5edfb58280d46a7ce1f + React-RCTActionSheet: ab545c1e7b5f1ce4f8b40b6fa06afe2869095884 + React-RCTAnimation: 343147a9cd68c93d0ca280799fedfc7102d76ce4 + React-RCTAppDelegate: 5054754e92aaa9f8bfabe0f1022b84e46f3dcb57 + React-RCTBlob: 8c7ae3422ca4e72bc64b7a0142fd730efc5d4dfb + React-RCTFabric: be458db054b206c4d8e4f20f666e75d5f2c2d420 + React-RCTFBReactNativeSpec: 06db2e8d0f352d9fa23321aed1dd2cde25a3e83c + React-RCTImage: 481457bca63e039eb997f7d16c7560472f49657e + React-RCTLinking: 76cbb871240cec2dc5e7aa26c60f59e0ebbcf5a2 + React-RCTNetwork: e23a778225b7672e38545d3c5c24e1f4aad6d15f + React-RCTRuntime: 93c830b3ab3f7b494bbe7ae7289784f8b07b3947 + React-RCTSettings: 96196b535bef147381f96cc60ce9bda85d8be848 + React-RCTText: 749ebbd1a999fd84d80f37002ea3bf597fcea6df + React-RCTVibration: 5b41a7f274757c2928845981d970916ef9e4ca14 + React-rendererconsistency: 6708acd4bc39c1c5b00164370d0010d93b324c1f + React-renderercss: 80eb778756fed511d6128fc005188ac9008b0baf + React-rendererdebug: b46f338fb9d3f0bea6cf0621016c6c5a7a18e72e + React-RuntimeApple: c494b2089fad4a0c553cf63c2bb265f7eca2285d + React-RuntimeCore: b0bb151c3e2b26c6309d45d05c54aa65a6e0c094 + React-runtimeexecutor: 00b18635b6216a1708f6eb35dbadfd993ec91c7b + React-RuntimeHermes: bb44c4c574ce1b9507cad2e6be015344d18b94a9 + React-runtimescheduler: e1631e57209cb94b3efc29002b6a049cac3f6599 + React-timing: 356b88317ca60d373b0d94b6e7a71b0a572899f5 + React-utils: ccc01da318979af773259c4f6cdb1876f6f86f1a + React-webperformancenativemodule: f8b97c2cb6cfa94e92a503c09ad6d491c50a1390 + ReactAppDependencyProvider: 25c9c516839be2c5e3d3344f95dc7da5f7e63fc2 + ReactCodegen: 1bd7f2174582b0e142f8671735b5c906c08b72ea + ReactCommon: 7dfc3250793bf36cf221096ff59e1179e13eef7f + ReactNativeCameraKit: 5974256fc608631c1c812710cd98abe95dae0f88 + ReactNativeDependencies: 0a5c93845772e4b1c5ad065c59a859518b13a6b7 + RealmJS: 1c37c6bdfe060f4caa0f9175aa0eedb962622ee1 + RNCAsyncStorage: 2ad919e88b8bc2cd80e8697ce66d04d006743283 + RNCClipboard: 715fa7c6c8366f17d00f05a439ee7488f390fa5f + RNDefaultPreference: 8a089ee8ce829a66c5453e3c5434f0785499d1c3 + RNDeviceInfo: bcce8752b5043a623fe3c26789679b473f705d3c + RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8 + RNGestureHandler: 2ff61eac036eaf89f6818bf4ed9c39771a17d134 + RNHandoff: bc8af5a86853ff13b033e7ba1114c3c5b38e6385 + RNKeychain: 6778b35b5bd067c322f8479526ac09b1d61f31d0 + RNLocalize: f370284ea42c48f29f0d8dd3a7bcc28a04f82155 + RNPermissions: dfbe915a8ee532bc55018cf5e387407847713b02 + RNQuickAction: c2c8f379e614428be0babe4d53a575739667744d + RNReactNativeHapticFeedback: 576e23c1ad2d800ded4502be3f66b767308b63a1 + RNReanimated: c4e6659e58b793885ae6da476cb514fc913e7b85 + RNScreens: 01b065ded2dfe7987bcce770ff3a196be417ff41 + RNShare: 2afdc1739d80ac140b2870ae81e8b2098f4599d9 + RNSVG: 0e52210d4d43165e7e2cf9c890a9848b27e513ac + RNWatch: 28fe1f5e0c6410d45fd20925f4796fce05522e3f + RNWorklets: dd3b2cb0750090d78d85cd3b3ec0fdbeab5ce118 + Yoga: 77dfa8673de2874e1855002ae59c68b8be9b007b -PODFILE CHECKSUM: 8e9e7955e2b1beadc75774b0b7f8eb52f7299757 +PODFILE CHECKSUM: a6ebefd60fd3fd993430ecd1d3feb222ff502eb0 -COCOAPODS: 1.11.3 +COCOAPODS: 1.16.2 diff --git a/ios/PrivacyInfo.xcprivacy b/ios/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..4126cc03cc1 --- /dev/null +++ b/ios/PrivacyInfo.xcprivacy @@ -0,0 +1,47 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + 3B52.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + 85F4.1 + E174.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/ios/Settings.bundle/Root.plist b/ios/Settings.bundle/Root.plist index ff555eeadd7..0de1f5da9d4 100644 --- a/ios/Settings.bundle/Root.plist +++ b/ios/Settings.bundle/Root.plist @@ -12,16 +12,32 @@ FooterText Provide this Unique ID when reporting an issue Title - + Report Issue Type PSTextFieldSpecifier Title Unique ID - Key + Key deviceUIDCopy + + Type + PSGroupSpecifier + Title + Cache + + + Type + PSToggleSwitchSpecifier + Title + Clear Cache on Next Launch + Key + clearFilesOnLaunch + DefaultValue + + diff --git a/ios/Shared/Balance.swift b/ios/Shared/Balance.swift new file mode 100644 index 00000000000..4c52d7ffabd --- /dev/null +++ b/ios/Shared/Balance.swift @@ -0,0 +1,58 @@ +import Foundation + +class Balance { + static func formatBalance(_ balance: Decimal, toUnit: BitcoinUnit, withFormatting: Bool = false, completion: @escaping (String) -> Void) { + switch toUnit { + case .sats: + if withFormatting { + completion(NumberFormatter.localizedString(from: balance as NSNumber, number: .decimal) + " SATS") + } else { + completion("\(balance) SATS") + } + case .localCurrency: + fetchLocalCurrencyEquivalent(satoshi: balance, completion: completion) + + default: + let value = balance / Decimal(100_000_000) + completion("\(value) BTC") // Localize unit names as needed. + } + } + + private static func fetchLocalCurrencyEquivalent(satoshi: Decimal, completion: @escaping (String) -> Void) { + + let currency = Currency.getUserPreferredCurrency() // Ensure this method retrieves the correct currency code. + MarketAPI.fetchPrice(currency: currency) { dataStore, error in + DispatchQueue.main.async { + guard let dataStore = dataStore, error == nil else { + completion("Error: \(error?.localizedDescription ?? "Unknown error")") + return + } + let rate = Decimal(string: dataStore.rate) ?? Decimal(0) + let convertedAmount = (satoshi / Decimal(100_000_000)) * rate + completion("\(convertedAmount) \(currency)") + } + } + } +} + +extension Decimal { + func formatted(as unit: BitcoinUnit, withFormatting: Bool = false) -> String { + switch unit { + case .sats: + return withFormatting ? NumberFormatter.localizedString(from: self as NSNumber, number: .decimal) + " SATS" : "\(self) SATS" + case .localCurrency: + let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue) + if let widgetData = userDefaults?.object(forKey: MarketData.string) as? Data, + let marketData = try? JSONDecoder().decode(MarketData.self, from: widgetData) { + let rate = Decimal(marketData.rate) + let convertedAmount = (self / Decimal(100_000_000)) * rate + return "\(convertedAmount) \(Currency.getUserPreferredCurrency())" + } else { + return "N/A" + } + default: + let value = self / Decimal(100_000_000) + return "\(value) BTC" + } + } +} diff --git a/ios/Shared/BitcoinUnit.swift b/ios/Shared/BitcoinUnit.swift new file mode 100644 index 00000000000..0939bfb15bc --- /dev/null +++ b/ios/Shared/BitcoinUnit.swift @@ -0,0 +1,56 @@ +// +// BitcoinUnit.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 4/14/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// +import Foundation + +/// Represents the various balance units used in the application. +/// Conforms to `String`, `Codable`, `Equatable`, and `CustomStringConvertible` for easy encoding/decoding, comparisons, and descriptions. +enum BitcoinUnit: String, Codable, Equatable, CustomStringConvertible { + case btc = "BTC" + case sats = "sats" + case localCurrency = "local_currency" + case max = "MAX" + + /// Provides a user-friendly description of the `BitcoinUnit`. + var description: String { + switch self { + case .btc: + return "BTC" + case .sats: + return "sats" + case .localCurrency: + return "Local Currency" + case .max: + return "MAX" + } + } + + /// Initializes a `BitcoinUnit` from a raw string. + /// - Parameter rawString: The raw string representing the balance unit. + init(rawString: String) { + switch rawString.lowercased() { + case "btc": + self = .sats + case "sats": + self = .sats + case "local_currency": + self = .localCurrency + case "max": + self = .max + default: + // Handle unknown balance units if necessary + // For now, defaulting to .max + self = .max + } + } +} + +extension BitcoinUnit { + static var mockUnit: BitcoinUnit { + return .sats + } +} diff --git a/ios/Shared/Bundle+decode.swift b/ios/Shared/Bundle+decode.swift new file mode 100644 index 00000000000..0eec91eccff --- /dev/null +++ b/ios/Shared/Bundle+decode.swift @@ -0,0 +1,40 @@ +// +// Bundle+decode.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 4/14/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +extension Bundle { + func decode(_ type: T.Type, from file: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T { + guard let url = self.url(forResource: file, withExtension: nil) else { + fatalError("Failed to locate \(file) in bundle.") + } + + guard let data = try? Data(contentsOf: url) else { + fatalError("Failed to load \(file) from bundle.") + } + + let decoder = JSONDecoder() + + decoder.dateDecodingStrategy = dateDecodingStrategy + decoder.keyDecodingStrategy = keyDecodingStrategy + + do { + return try decoder.decode(T.self, from: data) + } catch DecodingError.keyNotFound(let key, let context) { + fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' not found – \(context.debugDescription)") + } catch DecodingError.typeMismatch(_, let context) { + fatalError("Failed to decode \(file) from bundle due to type mismatch – \(context.debugDescription)") + } catch DecodingError.valueNotFound(let type, let context) { + fatalError("Failed to decode \(file) from bundle due to missing \(type) value – \(context.debugDescription)") + } catch DecodingError.dataCorrupted(_) { + fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON") + } catch { + fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)") + } + } +} diff --git a/ios/Shared/Chain.swift b/ios/Shared/Chain.swift new file mode 100644 index 00000000000..041fe5c7129 --- /dev/null +++ b/ios/Shared/Chain.swift @@ -0,0 +1,47 @@ +// +// Chain.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 11/16/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +/// Represents the chain type for a wallet. +/// Conforms to `String`, `Codable`, `Equatable`, and `CustomStringConvertible` for easy encoding/decoding, comparisons, and descriptions. +enum Chain: String, Codable, Equatable, CustomStringConvertible { + case onchain = "ONCHAIN" + case offchain = "OFFCHAIN" + + /// Provides a user-friendly description of the `Chain`. + var description: String { + switch self { + case .onchain: + return "On-chain" + case .offchain: + return "Off-chain" + } + } + + /// Initializes a `Chain` from a raw string. + /// - Parameter rawString: The raw string representing the chain type. + init(rawString: String) { + switch rawString.uppercased() { + case "ONCHAIN": + self = .onchain + case "OFFCHAIN": + self = .offchain + default: + // Handle unknown chain types if necessary + // For now, defaulting to .onchain + self = .onchain + } + } +} + +extension Chain { + static var mockChain: Chain { + return .onchain + } +} \ No newline at end of file diff --git a/ios/Widgets/Shared/Colors.swift b/ios/Shared/Colors.swift similarity index 99% rename from ios/Widgets/Shared/Colors.swift rename to ios/Shared/Colors.swift index 54cfabcf432..afeaf2131f9 100644 --- a/ios/Widgets/Shared/Colors.swift +++ b/ios/Shared/Colors.swift @@ -8,6 +8,7 @@ import SwiftUI + extension Color { static let textColor = Color("TextColor") static let textColorLightGray = Color(red: 0.6, green: 0.63, blue: 0.67) diff --git a/ios/Shared/Currency.swift b/ios/Shared/Currency.swift new file mode 100644 index 00000000000..5748da87d05 --- /dev/null +++ b/ios/Shared/Currency.swift @@ -0,0 +1,57 @@ +// +// Currency.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 4/14/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +struct CurrencyError: LocalizedError { + var errorDescription: String = "Failed to parse response" +} + +class Currency { + + static func getUserPreferredCurrency() -> String { + + guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue), + let preferredCurrency = userDefaults.string(forKey: "preferredCurrency") + else { + return "USD" + } + + if preferredCurrency != Currency.getLastSelectedCurrency() { + UserDefaults.standard.removeObject(forKey: WidgetData.WidgetCachedDataStoreKey) + UserDefaults.standard.removeObject(forKey: WidgetData.WidgetDataStoreKey) + UserDefaults.standard.synchronize() + } + + return preferredCurrency + } + + static func getUserPreferredCurrencyLocale() -> String { + guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue), + let preferredCurrency = userDefaults.string(forKey: "preferredCurrencyLocale") + else { + return "en_US" + } + return preferredCurrency + } + + static func getLastSelectedCurrency() -> String { + guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue), let dataStore = userDefaults.string(forKey: "currency") else { + return "USD" + } + + return dataStore + } + + static func saveNewSelectedCurrency() { + UserDefaults.standard.setValue(Currency.getUserPreferredCurrency(), forKey: "currency") + } + + +} + diff --git a/ios/Shared/Fiat/FiatUnit.swift b/ios/Shared/Fiat/FiatUnit.swift new file mode 100644 index 00000000000..4cf12098538 --- /dev/null +++ b/ios/Shared/Fiat/FiatUnit.swift @@ -0,0 +1,20 @@ +// +// FiatUnit.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 11/20/20. +// Copyright © 2020 BlueWallet. All rights reserved. +// +import Foundation + +struct FiatUnit: Codable { + let endPointKey: String + let symbol: String + let locale: String + let source: String + +} + +func fiatUnit(currency: String) -> FiatUnit? { + return Bundle.main.decode([String: FiatUnit].self, from: "fiatUnits.json").first(where: {$1.endPointKey == currency})?.value +} diff --git a/ios/Shared/Fiat/XMLParserDelegate.swift b/ios/Shared/Fiat/XMLParserDelegate.swift new file mode 100644 index 00000000000..d2c09e45da8 --- /dev/null +++ b/ios/Shared/Fiat/XMLParserDelegate.swift @@ -0,0 +1,29 @@ +// +// XMLParserDelegate.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 11/13/23. +// Copyright © 2023 BlueWallet. All rights reserved. +// + +import Foundation + +class BNRXMLParserDelegate: NSObject, XMLParserDelegate { + var usdRate: Double? + var currentElement = "" + var foundRate = false + + func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) { + currentElement = elementName + if elementName == "Rate" && attributeDict["currency"] == "USD" { + foundRate = true + } + } + + func parser(_ parser: XMLParser, foundCharacters string: String) { + if foundRate { + usdRate = Double(string) + foundRate = false + } + } +} diff --git a/ios/Shared/LatestTransaction.swift b/ios/Shared/LatestTransaction.swift new file mode 100644 index 00000000000..5a100bc9623 --- /dev/null +++ b/ios/Shared/LatestTransaction.swift @@ -0,0 +1,14 @@ +// +// LatestTransaction.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 4/14/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +struct LatestTransaction { + let isUnconfirmed: Bool? + let epochValue: Int? +} diff --git a/ios/Shared/MarketAPI+Electrum.swift b/ios/Shared/MarketAPI+Electrum.swift new file mode 100644 index 00000000000..b24f5c8ce46 --- /dev/null +++ b/ios/Shared/MarketAPI+Electrum.swift @@ -0,0 +1,100 @@ +// +// MarketAPI+Electrum.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 11/8/20. +// Copyright © 2020 BlueWallet. All rights reserved. +// + +import Foundation + +struct APIError: LocalizedError { + var errorDescription: String = "Failed to fetch Electrum data..." +} + +extension MarketAPI { + + static func fetchNextBlockFee() async throws -> MarketData { + let client = SwiftTCPClient(hosts: hardcodedPeers) + defer { + client.close() + print("Closed SwiftTCPClient connection.") + } + + guard await client.connectToNextAvailable(validateCertificates: false) else { + print("Failed to connect to any Electrum peer.") + throw APIError() + } + + let message = "{\"id\": 1, \"method\": \"mempool.get_fee_histogram\", \"params\": []}\n" + guard let data = message.data(using: .utf8) else { + print("Failed to encode message to data.") + throw APIError() + } + + print("Sending fee histogram request: \(message)") + + guard await client.send(data: data) else { + print("Failed to send fee histogram request.") + throw APIError() + } + + do { + let receivedData = try await client.receive() + print("Received data: \(receivedData)") + + guard let json = try JSONSerialization.jsonObject(with: receivedData, options: .allowFragments) as? [String: AnyObject], + let feeHistogram = json["result"] as? [[Double]] else { + print("Invalid JSON structure in response.") + throw APIError() + } + + let fastestFee = calcEstimateFeeFromFeeHistogram(numberOfBlocks: 1, feeHistogram: feeHistogram) + print("Calculated fastest fee: \(fastestFee)") + return MarketData(nextBlock: String(format: "%.0f", fastestFee), sats: "0", price: "0", rate: 0, dateString: "") + } catch { + print("Error during fetchNextBlockFee: \(error.localizedDescription)") + throw APIError() + } + } + + static func fetchMarketData(currency: String) async throws -> MarketData { + var marketDataEntry = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0) + + do { + if let priceResult = try await fetchPrice(currency: currency) { + marketDataEntry.rate = priceResult.rateDouble + marketDataEntry.price = priceResult.formattedRate ?? "!" + print("Fetched price data: rateDouble=\(priceResult.rateDouble), formattedRate=\(priceResult.formattedRate ?? "nil")") + } + } catch { + print("Error fetching price: \(error.localizedDescription)") + } + + do { + let nextBlockData = try await fetchNextBlockFee() + marketDataEntry.nextBlock = nextBlockData.nextBlock + print("Fetched next block fee data: nextBlock=\(nextBlockData.nextBlock)") + } catch { + print("Error fetching next block fee: \(error.localizedDescription)") + marketDataEntry.nextBlock = "!" + } + + marketDataEntry.sats = numberFormatter.string(from: NSNumber(value: Double(10 / marketDataEntry.rate) * 10000000)) ?? "!" + print("Calculated sats: \(marketDataEntry.sats)") + + return marketDataEntry + } + + static func fetchMarketData(currency: String, completion: @escaping (Result) -> ()) { + Task { + do { + let marketData = try await fetchMarketData(currency: currency) + completion(.success(marketData)) + } catch { + completion(.failure(error)) + } + } + } +} + diff --git a/ios/Shared/MarketAPI.swift b/ios/Shared/MarketAPI.swift new file mode 100644 index 00000000000..d0f667017f8 --- /dev/null +++ b/ios/Shared/MarketAPI.swift @@ -0,0 +1,212 @@ +// +// MarketAPI.swift +// +// Created by Marcos Rodriguez on 11/2/19. +// + +// + +import Foundation + +class MarketAPI { + + private static func buildURLString(source: String, endPointKey: String) -> String { + switch source { + case "Yadio": + return "https://api.yadio.io/json/\(endPointKey)" + case "YadioConvert": + return "https://api.yadio.io/convert/1/BTC/\(endPointKey)" + case "Exir": + return "https://api.exir.io/v1/ticker?symbol=btc-irt" + case "coinpaprika": + return "https://api.coinpaprika.com/v1/tickers/btc-bitcoin?quotes=INR" + case "Bitstamp": + return "https://www.bitstamp.net/api/v2/ticker/btc\(endPointKey.lowercased())" + case "Coinbase": + return "https://api.coinbase.com/v2/prices/BTC-\(endPointKey.uppercased())/buy" + case "CoinGecko": + return "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=\(endPointKey.lowercased())" + case "BNR": + return "https://www.bnr.ro/nbrfxrates.xml" + case "Kraken": + return "https://api.kraken.com/0/public/Ticker?pair=XXBTZ\(endPointKey.uppercased())" + default: // CoinDesk + return "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=\(endPointKey)" + } + } + + private static func handleDefaultData(data: Data, source: String, endPointKey: String) throws -> WidgetDataStore? { + guard let json = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else { + throw CurrencyError(errorDescription: "JSON parsing error.") + } + + return try parseJSONBasedOnSource(json: json, source: source, endPointKey: endPointKey) + } + + private static func parseJSONBasedOnSource(json: [String: Any], source: String, endPointKey: String) throws -> WidgetDataStore? { + var latestRateDataStore: WidgetDataStore? + + switch source { + case "Yadio": + if let rateDict = json[endPointKey] as? [String: Any], + let rateDouble = rateDict["price"] as? Double, + let lastUpdated = rateDict["timestamp"] as? Int { + let unix = Double(lastUpdated / 1_000) + let lastUpdatedString = ISO8601DateFormatter().string(from: Date(timeIntervalSince1970: unix)) + latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble) + return latestRateDataStore + } else { + throw CurrencyError(errorDescription: "Data formatting error for source: \(source)") + } + case "YadioConvert": + guard let rateDouble = json["rate"] as? Double, + let lastUpdated = json["timestamp"] as? Int else { + throw CurrencyError(errorDescription: "Data formatting error for source: \(source)") + } + let unix = Double(lastUpdated / 1_000) + let lastUpdatedString = ISO8601DateFormatter().string(from: Date(timeIntervalSince1970: unix)) + latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble) + return latestRateDataStore + case "CoinGecko": + if let bitcoinDict = json["bitcoin"] as? [String: Any], + let rateDouble = bitcoinDict[endPointKey.lowercased()] as? Double { + let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) + latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble) + return latestRateDataStore + } else { + throw CurrencyError(errorDescription: "Data formatting error for source: \(source)") + } + case "Exir": + if let rateDouble = json["last"] as? Double { + let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) + latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble) + return latestRateDataStore + } else { + throw CurrencyError(errorDescription: "Data formatting error for source: \(source)") + } + case "Bitstamp": + if let rateString = json["last"] as? String, let rateDouble = Double(rateString) { + let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) + latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble) + return latestRateDataStore + } else { + throw CurrencyError(errorDescription: "Data formatting error for source: \(source)") + } + case "coinpaprika": + if let quotesDict = json["quotes"] as? [String: Any], + let currencyDict = quotesDict[endPointKey.uppercased()] as? [String: Any], + let rateDouble = currencyDict["price"] as? Double { + let rateString = String(rateDouble) + let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) + latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble) + return latestRateDataStore + } else { + throw CurrencyError(errorDescription: "Data formatting error for source: \(source)") + } + case "Coinbase": + if let data = json["data"] as? [String: Any], + let rateString = data["amount"] as? String, + let rateDouble = Double(rateString) { + let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) + latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble) + return latestRateDataStore + } else { + throw CurrencyError(errorDescription: "Data formatting error for source: \(source)") + } + case "BNR": + throw CurrencyError(errorDescription: "BNR data source is not yet implemented") + case "Kraken": + if let result = json["result"] as? [String: Any], + let tickerData = result["XXBTZ\(endPointKey.uppercased())"] as? [String: Any], + let c = tickerData["c"] as? [String], + let rateString = c.first, + let rateDouble = Double(rateString) { + let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) + latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble) + return latestRateDataStore + } else { + if let errorMessage = json["error"] as? [String] { + throw CurrencyError(errorDescription: "Kraken API error: \(errorMessage.joined(separator: ", "))") + } else { + throw CurrencyError(errorDescription: "Data formatting error for source: \(source)") + } + } + default: // CoinDesk + if let rateDouble = json[endPointKey] as? Double { + let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) + latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble) + return latestRateDataStore + } else { + throw CurrencyError(errorDescription: "Data formatting error for source: \(source)") + } + } + } + + private static func handleBNRData(data: Data) async throws -> WidgetDataStore? { + let parser = XMLParser(data: data) + let delegate = BNRXMLParserDelegate() + parser.delegate = delegate + if parser.parse(), let usdToRonRate = delegate.usdRate { + let coinGeckoUrl = URL(string: "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd")! + let (data, _) = try await URLSession.shared.data(from: coinGeckoUrl) + if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], + let bitcoinDict = json["bitcoin"] as? [String: Double], + let btcToUsdRate = bitcoinDict["usd"] { + let btcToRonRate = btcToUsdRate * usdToRonRate + let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) + let latestRateDataStore = WidgetDataStore(rate: String(btcToRonRate), lastUpdate: lastUpdatedString, rateDouble: btcToRonRate) + return latestRateDataStore + } else { + throw CurrencyError() + } + } else { + throw CurrencyError(errorDescription: "XML parsing error.") + } + } + + + static func fetchPrice(currency: String) async throws -> WidgetDataStore? { + let currencyToFiatUnit = fiatUnit(currency: currency) + guard let source = currencyToFiatUnit?.source, let endPointKey = currencyToFiatUnit?.endPointKey else { + throw CurrencyError(errorDescription: "Invalid currency unit or endpoint.") + } + + let urlString = buildURLString(source: source, endPointKey: endPointKey) + guard let url = URL(string: urlString) else { + throw CurrencyError(errorDescription: "Invalid URL.") + } + + return try await fetchData(url: url, source: source, endPointKey: endPointKey) + } + + private static func fetchData(url: URL, source: String, endPointKey: String, retries: Int = 3) async throws -> WidgetDataStore? { + do { + let (data, _) = try await URLSession.shared.data(from: url) + if source == "BNR" { + return try await handleBNRData(data: data) + } else { + return try handleDefaultData(data: data, source: source, endPointKey: endPointKey) + } + } catch { + if retries > 0 { + return try await fetchData(url: url, source: source, endPointKey: endPointKey, retries: retries - 1) + } else { + throw error + } + } + } + + static func fetchPrice(currency: String, completion: @escaping ((WidgetDataStore?, Error?) -> Void)) { + Task { + do { + if let dataStore = try await fetchPrice(currency: currency) { + completion(dataStore, nil) + } else { + completion(nil, CurrencyError(errorDescription: "No data received.")) + } + } catch { + completion(nil, error) + } + } + } +} diff --git a/ios/Shared/MarketData.swift b/ios/Shared/MarketData.swift new file mode 100644 index 00000000000..7d6219e772e --- /dev/null +++ b/ios/Shared/MarketData.swift @@ -0,0 +1,49 @@ +// +// MarketData.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 4/14/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +public struct MarketData:Codable { + public var nextBlock: String + public var sats: String + public var price: String + public var rate: Double + + var formattedNextBlock: String { + if nextBlock == "..." { + return "..." + } else { + if let nextBlockInt = Int(nextBlock) { + let numberFormatter = NumberFormatter() + numberFormatter.numberStyle = .decimal + if let formattedNumber = numberFormatter.string(from: NSNumber(value: nextBlockInt)) { + return "\(formattedNumber) sat/vb" + } + } + return "\(nextBlock) sat/vb" // Fallback in case the nextBlock cannot be converted to an Int + } + } + var dateString: String = "" + var formattedDate: String? { + let isoDateFormatter = ISO8601DateFormatter() + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale.current + dateFormatter.timeStyle = .short + + if let date = isoDateFormatter.date(from: dateString) { + return dateFormatter.string(from: date) + } + return nil + } + static let string = "MarketData" +} + +enum MarketDataTimeline: String { + case Previous = "previous" + case Current = "current" +} diff --git a/ios/Shared/Numeric+abbreviated.swift b/ios/Shared/Numeric+abbreviated.swift new file mode 100644 index 00000000000..7f0a83934e0 --- /dev/null +++ b/ios/Shared/Numeric+abbreviated.swift @@ -0,0 +1,27 @@ +// +// Numeric+abbreviated.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 4/14/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +extension Numeric { + + var abbreviated: String { + let bytecountFormatter = ByteCountFormatter() + bytecountFormatter.zeroPadsFractionDigits = true + bytecountFormatter.countStyle = .decimal + bytecountFormatter.isAdaptive = false + let bytesString = bytecountFormatter.string(fromByteCount: (self as! NSNumber).int64Value) + + let numericString = bytesString + .replacingOccurrences(of: "bytes", with: "") + .replacingOccurrences(of: "B", with: "") // removes B (bytes) in 'KB'/'MB'/'GB' + .replacingOccurrences(of: "G", with: "B") // replace G (Giga) to just B (billions) + return numericString.replacingOccurrences(of: " ", with: "") + } + +} diff --git a/ios/Shared/Placeholders.swift b/ios/Shared/Placeholders.swift new file mode 100644 index 00000000000..c4789ee81b1 --- /dev/null +++ b/ios/Shared/Placeholders.swift @@ -0,0 +1,16 @@ +// +// Models.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 11/1/20. +// Copyright © 2020 BlueWallet. All rights reserved. +// + +import Foundation + + + +let emptyMarketData = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0) +let emptyWalletData = WalletData(balance: 0, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: Int(Date().timeIntervalSince1970))) + + diff --git a/ios/Shared/SizeClassEmitter.m b/ios/Shared/SizeClassEmitter.m new file mode 100644 index 00000000000..11eb4e95e71 --- /dev/null +++ b/ios/Shared/SizeClassEmitter.m @@ -0,0 +1,10 @@ +#import +#import + +@interface RCT_EXTERN_MODULE(SizeClassEmitter, RCTEventEmitter) + +RCT_EXTERN_METHOD(getCurrentSizeClass:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(emitSizeClassChange:(UIWindow *)window) +RCT_EXTERN_METHOD(sharedInstance) + +@end diff --git a/ios/Shared/SizeClassEmitter.swift b/ios/Shared/SizeClassEmitter.swift new file mode 100644 index 00000000000..c524c39e70b --- /dev/null +++ b/ios/Shared/SizeClassEmitter.swift @@ -0,0 +1,161 @@ +import Foundation +import UIKit +import React + +@objc(SizeClassEmitter) +class SizeClassEmitter: RCTEventEmitter { + private enum SizeClassValue: Int { + case compact = 0 + case regular = 1 + case large = 2 + } + + private static var sharedEmitter = SizeClassEmitter() + private var hasListeners = false + + override init() { + super.init() + SizeClassEmitter.sharedEmitter = self + } + + override class func requiresMainQueueSetup() -> Bool { + true + } + + override func supportedEvents() -> [String]! { + ["sizeClassDidChange"] + } + + // MARK: - Singleton access used by ObjC bridge + + @objc func sharedInstance() -> SizeClassEmitter { + SizeClassEmitter.sharedEmitter + } + + // MARK: - Public API exposed to JS + + @objc func getCurrentSizeClass(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + DispatchQueue.main.async { + guard let payload = self.buildPayload(window: nil) else { + reject("size_class_unavailable", "Unable to read current size class", nil) + return + } + resolve(payload) + } + } + + @objc func emitSizeClassChange(_ window: UIWindow?) { + DispatchQueue.main.async { + self.sendUpdate(window: window, reason: "manual") + } + } + + // MARK: - Listener lifecycle + + override func startObserving() { + hasListeners = true + + NotificationCenter.default.addObserver( + self, + selector: #selector(handlePotentialTraitChange), + name: UIDevice.orientationDidChangeNotification, + object: nil + ) + + NotificationCenter.default.addObserver( + self, + selector: #selector(handlePotentialTraitChange), + name: UIApplication.didBecomeActiveNotification, + object: nil + ) + + NotificationCenter.default.addObserver( + self, + selector: #selector(handlePotentialTraitChange), + name: UIWindow.didBecomeKeyNotification, + object: nil + ) + + NotificationCenter.default.addObserver( + self, + selector: #selector(handlePotentialTraitChange), + name: UIWindow.didResignKeyNotification, + object: nil + ) + } + + override func stopObserving() { + hasListeners = false + NotificationCenter.default.removeObserver(self) + } + + // MARK: - Notification handling + + @objc private func handlePotentialTraitChange() { + sendUpdate(window: nil, reason: "notification") + } + + private func sendUpdate(window: UIWindow?, reason: String) { + guard hasListeners else { return } + guard let payload = buildPayload(window: window) else { return } + + #if DEBUG + NSLog("[SizeClassEmitter] Emitting update (%@): %@", reason, payload.description) + #endif + + sendEvent(withName: "sizeClassDidChange", body: payload) + } + + // MARK: - Payload construction + + private func buildPayload(window: UIWindow?) -> [String: Any]? { + guard let activeWindow = resolveWindow(window) else { + return nil + } + + let traits = activeWindow.traitCollection + let bounds = activeWindow.bounds + + let horizontalClass = map(sizeClass: traits.horizontalSizeClass) + let verticalClass = map(sizeClass: traits.verticalSizeClass) + + // Preserve previous JS behavior: any non-Compact width is considered Large overall. + let overallClass: SizeClassValue = horizontalClass == .compact ? .compact : .large + + let orientation: String = bounds.width > bounds.height ? "landscape" : "portrait" + let isLargeScreen = horizontalClass != .compact + + return [ + "horizontal": horizontalClass.rawValue, + "vertical": verticalClass.rawValue, + "sizeClass": overallClass.rawValue, + "orientation": orientation, + "isLargeScreen": isLargeScreen, + "width": Double(bounds.width), + "height": Double(bounds.height), + ] + } + + private func map(sizeClass: UIUserInterfaceSizeClass) -> SizeClassValue { + switch sizeClass { + case .compact: + return .compact + case .regular: + return .regular + default: + return .regular + } + } + + private func resolveWindow(_ providedWindow: UIWindow?) -> UIWindow? { + if let providedWindow { + return providedWindow + } + + if let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) { + return keyWindow + } + + return UIApplication.shared.windows.first + } +} diff --git a/ios/Shared/UserDefaultsExtension.swift b/ios/Shared/UserDefaultsExtension.swift new file mode 100644 index 00000000000..b9200c9a154 --- /dev/null +++ b/ios/Shared/UserDefaultsExtension.swift @@ -0,0 +1,18 @@ +// +// UserDefaultsExtension.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 2/8/21. +// Copyright © 2021 BlueWallet. All rights reserved. +// + +import Foundation + +extension UserDefaults { + +func codable(forKey key: String) -> Element? { + guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue), let data = userDefaults.data(forKey: key) else { return nil } + let element = try? PropertyListDecoder().decode(Element.self, from: data) + return element + } +} diff --git a/ios/Shared/UserDefaultsGroup.swift b/ios/Shared/UserDefaultsGroup.swift new file mode 100644 index 00000000000..7eb399b743d --- /dev/null +++ b/ios/Shared/UserDefaultsGroup.swift @@ -0,0 +1,71 @@ +// +// UserDefaultsGroup.swift +// MarketWidgetExtension +// +// Created by Marcos Rodriguez on 10/31/20. +// Copyright © 2020 BlueWallet. All rights reserved. +// + +import Foundation + +struct UserDefaultsElectrumSettings { + var host: String? + var port: UInt16? + var sslPort: UInt16? +} + +let hardcodedPeers = DefaultElectrumPeers.map { settings in + ( + host: settings.host ?? "", + port: settings.sslPort ?? settings.port ?? 50001, + useSSL: settings.sslPort != nil + ) +} + +let DefaultElectrumPeers = [ + UserDefaultsElectrumSettings(host: "mainnet.foundationdevices.com", port: 50001, sslPort: 50002), + // UserDefaultsElectrumSettings(host: "electrum.jochen-hoenicke.de", port: 50001, sslPort: 50006), + UserDefaultsElectrumSettings(host: "electrum1.bluewallet.io", port: 50001, sslPort: 443), + UserDefaultsElectrumSettings(host: "electrum.acinq.co", port: 50001, sslPort: 50002), + UserDefaultsElectrumSettings(host: "electrum.bitaroo.net", port: 50001, sslPort: 50002), +] + +class UserDefaultsGroup { + static private let suite = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue) + + static func getElectrumSettings() -> UserDefaultsElectrumSettings { + guard let electrumSettingsHost = suite?.string(forKey: UserDefaultsGroupKey.ElectrumSettingsHost.rawValue) else { + return DefaultElectrumPeers.randomElement() ?? UserDefaultsElectrumSettings() + } + + let electrumSettingsTCPPort = suite?.integer(forKey: UserDefaultsGroupKey.ElectrumSettingsTCPPort.rawValue) ?? 50001 + let electrumSettingsSSLPort = suite?.integer(forKey: UserDefaultsGroupKey.ElectrumSettingsSSLPort.rawValue) ?? 443 + + let host = electrumSettingsHost + let sslPort = UInt16(electrumSettingsSSLPort) + let port = UInt16(electrumSettingsTCPPort) + + return UserDefaultsElectrumSettings(host: host, port: port, sslPort: sslPort) + } + + static func getAllWalletsBalance() -> Double { + guard let allWalletsBalance = suite?.string(forKey: UserDefaultsGroupKey.AllWalletsBalance.rawValue) else { + return 0 + } + + return Double(allWalletsBalance) ?? 0 + } + + // Int: EPOCH value, Bool: Latest transaction is unconfirmed + static func getAllWalletsLatestTransactionTime() -> LatestTransaction { + guard let allWalletsTransactionTime = suite?.string(forKey: UserDefaultsGroupKey.AllWalletsLatestTransactionTime.rawValue) else { + return LatestTransaction(isUnconfirmed: false, epochValue: 0) + } + + if allWalletsTransactionTime == UserDefaultsGroupKey.LatestTransactionIsUnconfirmed.rawValue { + return LatestTransaction(isUnconfirmed: true, epochValue: 0) + } else { + return LatestTransaction(isUnconfirmed: false, epochValue: Int(allWalletsTransactionTime) ?? 0) + } + } +} diff --git a/ios/Shared/UserDefaultsGroupKey.swift b/ios/Shared/UserDefaultsGroupKey.swift new file mode 100644 index 00000000000..c4a4554eefb --- /dev/null +++ b/ios/Shared/UserDefaultsGroupKey.swift @@ -0,0 +1,22 @@ +// +// UserDefaultsGroupKeys.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 4/14/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +enum UserDefaultsGroupKey: String { + case GroupName = "group.io.bluewallet.bluewallet" + case PreferredCurrency = "preferredCurrency" + case WatchAppBundleIdentifier = "io.bluewallet.bluewallet.watch" + case BundleIdentifier = "io.bluewallet.bluewallet" + case ElectrumSettingsHost = "electrum_host" + case ElectrumSettingsTCPPort = "electrum_tcp_port" + case ElectrumSettingsSSLPort = "electrum_ssl_port" + case AllWalletsBalance = "WidgetCommunicationAllWalletsSatoshiBalance" + case AllWalletsLatestTransactionTime = "WidgetCommunicationAllWalletsLatestTransactionTime" + case LatestTransactionIsUnconfirmed = "\"WidgetCommunicationLatestTransactionIsUnconfirmed\"" +} diff --git a/ios/Shared/Utilities/KeychainHelper.swift b/ios/Shared/Utilities/KeychainHelper.swift new file mode 100644 index 00000000000..ff76c15bdad --- /dev/null +++ b/ios/Shared/Utilities/KeychainHelper.swift @@ -0,0 +1,70 @@ +// +// KeychainHelper.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 11/20/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + + +import Foundation +import Security + +class KeychainHelper { + + static let shared = KeychainHelper() + + private init() {} + + /// Save data to Keychain + func save(_ data: Data, service: String, account: String) -> Bool { + // Create query + let query: [String: Any] = [ + kSecClass as String : kSecClassGenericPassword, + kSecAttrService as String : service, + kSecAttrAccount as String : account, + kSecValueData as String : data + ] + + // Delete any existing item + SecItemDelete(query as CFDictionary) + + // Add new item + let status = SecItemAdd(query as CFDictionary, nil) + return status == errSecSuccess + } + + /// Retrieve data from Keychain + func retrieve(service: String, account: String) -> Data? { + // Create query + let query: [String: Any] = [ + kSecClass as String : kSecClassGenericPassword, + kSecAttrService as String : service, + kSecAttrAccount as String : account, + kSecReturnData as String : true, + kSecMatchLimit as String : kSecMatchLimitOne + ] + + var dataTypeRef: AnyObject? + let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) + + if status == errSecSuccess { + return dataTypeRef as? Data + } else { + return nil + } + } + + /// Delete data from Keychain + func delete(service: String, account: String) -> Bool { + // Create query + let query: [String: Any] = [ + kSecClass as String : kSecClassGenericPassword, + kSecAttrService as String : service, + kSecAttrAccount as String : account + ] + + let status = SecItemDelete(query as CFDictionary) + return status == errSecSuccess + } +} diff --git a/ios/Shared/Utilities/Utilities.swift b/ios/Shared/Utilities/Utilities.swift new file mode 100644 index 00000000000..9f7d1a5385b --- /dev/null +++ b/ios/Shared/Utilities/Utilities.swift @@ -0,0 +1,88 @@ +// +// Utilities.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 6/4/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +func percentile(_ arr: [Double], p: Double) -> Double { + guard !arr.isEmpty else { return 0 } + guard p >= 0, p <= 1 else { fatalError("Percentile must be between 0 and 1") } + + if p == 0 { return arr.first! } + if p == 1 { return arr.last! } + + let index = Double(arr.count - 1) * p + let lower = Int(floor(index)) + let upper = lower + 1 + let weight = index - Double(lower) + + if upper >= arr.count { return arr[lower] } + return arr[lower] * (1 - weight) + arr[upper] * weight +} + +func calcEstimateFeeFromFeeHistogram(numberOfBlocks: Int, feeHistogram: [[Double]]) -> Double { + var totalVsize = 0.0 + var histogramToUse: [(fee: Double, vsize: Double)] = [] + + for h in feeHistogram { + var (fee, vsize) = (h[0], h[1]) + var timeToStop = false + + if totalVsize + vsize >= 1000000.0 * Double(numberOfBlocks) { + vsize = 1000000.0 * Double(numberOfBlocks) - totalVsize + timeToStop = true + } + + histogramToUse.append((fee, vsize)) + totalVsize += vsize + if timeToStop { break } + } + + var histogramFlat: [Double] = [] + for hh in histogramToUse { + histogramFlat += Array(repeating: hh.fee, count: Int(hh.vsize / 25000)) + } + + histogramFlat.sort() + + return max(2, percentile(histogramFlat, p: 0.5)) +} + + +var numberFormatter: NumberFormatter { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 0 + formatter.locale = Locale.current + return formatter +} + +extension Double { + func formattedPriceString() -> String { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 0 + return formatter.string(from: NSNumber(value: self)) ?? "--" + } + + func formattedCurrencyString() -> String { + let formatter = NumberFormatter() + formatter.numberStyle = .currency + formatter.maximumFractionDigits = 0 + formatter.currencySymbol = fiatUnit(currency: Currency.getUserPreferredCurrency())?.symbol + return formatter.string(from: NSNumber(value: self)) ?? "--" + } +} + +extension Date { + var formattedDate: String { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .short + return formatter.string(from: self) + } +} diff --git a/ios/Shared/WalletData.swift b/ios/Shared/WalletData.swift new file mode 100644 index 00000000000..3d713216c8c --- /dev/null +++ b/ios/Shared/WalletData.swift @@ -0,0 +1,27 @@ +// +// WalletData.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 4/14/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +struct WalletData { + var balance: Double + var latestTransactionTime: LatestTransaction = LatestTransaction(isUnconfirmed: false, epochValue: 0) + var formattedBalanceBTC: String { + let formatter = NumberFormatter() + formatter.numberStyle = .none + formatter.usesSignificantDigits = true + formatter.maximumSignificantDigits = 9 + formatter.roundingMode = .up + let value = NSNumber(value: balance / 100000000); + if let valueString = formatter.string(from: value) { + return "\(String(describing: valueString)) \(BitcoinUnit.btc.rawValue)" + } else { + return "0 \(BitcoinUnit.btc.rawValue)" + } + } +} diff --git a/ios/Shared/WidgetData.swift b/ios/Shared/WidgetData.swift new file mode 100644 index 00000000000..d740adccf3f --- /dev/null +++ b/ios/Shared/WidgetData.swift @@ -0,0 +1,22 @@ +// +// WidgetData.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 4/14/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +class WidgetData { + + static let WidgetDataStoreKey = "WidgetDataStoreKey" + static let WidgetCachedDataStoreKey = "WidgetCachedDataStoreKey" + + static func savePriceRateAndLastUpdate(rate: String, lastUpdate: String) { + guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue) else { return } + userDefaults.setValue(["rate": rate, "lastUpdate": lastUpdate], forKey: WidgetDataStoreKey) + userDefaults.synchronize() + } + +} diff --git a/ios/Shared/WidgetDataStore.swift b/ios/Shared/WidgetDataStore.swift new file mode 100644 index 00000000000..300972c4ab0 --- /dev/null +++ b/ios/Shared/WidgetDataStore.swift @@ -0,0 +1,62 @@ +// +// WidgetDataStore.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 4/14/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +struct WidgetDataStore: Codable { + let rate: String + let lastUpdate: String + let rateDouble: Double + var formattedRate: String? { + let numberFormatter = NumberFormatter() + numberFormatter.locale = Locale(identifier: Currency.getUserPreferredCurrencyLocale()) + numberFormatter.numberStyle = .currency + numberFormatter.maximumFractionDigits = 0 + numberFormatter.minimumFractionDigits = 0 + if let rateString = numberFormatter.string(from: NSNumber(value: rateDouble)) { + return rateString + } + return rate + } + var formattedRateForSmallComplication: String? { + return rateDouble.abbreviated + } + + var formattedRateForComplication: String? { + let numberFormatter = NumberFormatter() + numberFormatter.locale = Locale(identifier: Currency.getUserPreferredCurrencyLocale()) + numberFormatter.numberStyle = .currency + numberFormatter.currencySymbol = "" + if let rateString = numberFormatter.string(from: NSNumber(value: rateDouble)) { + return rateString + } + return rate + } + + var date: Date? { + let isoDateFormatter = ISO8601DateFormatter() + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale.current + dateFormatter.timeStyle = .short + + return isoDateFormatter.date(from: lastUpdate) + } + var formattedDate: String? { + let isoDateFormatter = ISO8601DateFormatter() + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale.current + dateFormatter.timeStyle = .short + + if let date = isoDateFormatter.date(from: lastUpdate) { + return dateFormatter.string(from: date) + } + return nil + } +} + + diff --git a/ios/Stickers/Info.plist b/ios/Stickers/Info.plist index 239f73ed71d..2c47fae6efe 100644 --- a/ios/Stickers/Info.plist +++ b/ios/Stickers/Info.plist @@ -27,5 +27,7 @@ NSExtensionPrincipalClass StickerBrowserViewController + RCTNewArchEnabled + diff --git a/ios/WalletInformationWidget/WalletInformationWidget.swift b/ios/WalletInformationWidget/WalletInformationWidget.swift index e21078754ff..34a27790990 100644 --- a/ios/WalletInformationWidget/WalletInformationWidget.swift +++ b/ios/WalletInformationWidget/WalletInformationWidget.swift @@ -10,85 +10,102 @@ import WidgetKit import SwiftUI struct WalletInformationWidgetProvider: TimelineProvider { - typealias Entry = WalletInformationWidgetEntry - func placeholder(in context: Context) -> WalletInformationWidgetEntry { - return WalletInformationWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000))) - } - - func getSnapshot(in context: Context, completion: @escaping (WalletInformationWidgetEntry) -> ()) { - let entry: WalletInformationWidgetEntry - if (context.isPreview) { - entry = WalletInformationWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000))) - } else { - entry = WalletInformationWidgetEntry(date: Date(), marketData: emptyMarketData) + typealias Entry = WalletInformationWidgetEntry + static var lastSuccessfulEntries: [WalletInformationWidgetEntry] = [] + + func placeholder(in context: Context) -> WalletInformationWidgetEntry { + return WalletInformationWidgetEntry.placeholder + } + + func getSnapshot(in context: Context, completion: @escaping (WalletInformationWidgetEntry) -> ()) { + let entry: WalletInformationWidgetEntry + if (context.isPreview) { + entry = WalletInformationWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000))) + } else { + entry = WalletInformationWidgetEntry(date: Date(), marketData: emptyMarketData) + } + completion(entry) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { + var entries: [WalletInformationWidgetEntry] = [] + let userPreferredCurrency = Currency.getUserPreferredCurrency() + let allwalletsBalance = WalletData(balance: UserDefaultsGroup.getAllWalletsBalance(), latestTransactionTime: UserDefaultsGroup.getAllWalletsLatestTransactionTime()) + + MarketAPI.fetchPrice(currency: userPreferredCurrency) { (result, error) in + let entry: WalletInformationWidgetEntry + + if let result = result { + entry = WalletInformationWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "", sats: "", price: result.formattedRate ?? "!", rate: result.rateDouble), allWalletsBalance: allwalletsBalance) + WalletInformationWidgetProvider.lastSuccessfulEntries.append(entry) + if WalletInformationWidgetProvider.lastSuccessfulEntries.count > 5 { + WalletInformationWidgetProvider.lastSuccessfulEntries.removeFirst() + } + } else { + if let lastEntry = WalletInformationWidgetProvider.lastSuccessfulEntries.last { + entry = lastEntry + } else { + entry = WalletInformationWidgetEntry.placeholder + } + } + entries.append(entry) + let timeline = Timeline(entries: entries, policy: .atEnd) + completion(timeline) + } } - completion(entry) - } - - func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { - var entries: [WalletInformationWidgetEntry] = [] - let userPreferredCurrency = WidgetAPI.getUserPreferredCurrency(); - - let marketDataEntry = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0) - let allwalletsBalance = WalletData(balance: UserDefaultsGroup.getAllWalletsBalance(), latestTransactionTime: UserDefaultsGroup.getAllWalletsLatestTransactionTime()) - WidgetAPI.fetchPrice(currency: userPreferredCurrency, completion: { (result, error) in - let entry: WalletInformationWidgetEntry - if let result = result { - entry = WalletInformationWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "", sats: "", price: result.formattedRate ?? "!", rate: result.rateDouble), allWalletsBalance: allwalletsBalance) - - } else { - entry = WalletInformationWidgetEntry(date: Date(), marketData: marketDataEntry, allWalletsBalance: allwalletsBalance) - } - entries.append(entry) - let timeline = Timeline(entries: entries, policy: .atEnd) - completion(timeline) - }) - } } struct WalletInformationWidgetEntry: TimelineEntry { - let date: Date - let marketData: MarketData - var allWalletsBalance: WalletData = WalletData(balance: 0) + let date: Date + let marketData: MarketData + var allWalletsBalance: WalletData = WalletData(balance: 0) +} + +extension WalletInformationWidgetEntry { + static var placeholder: WalletInformationWidgetEntry { + WalletInformationWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000))) + } } -struct WalletInformationWidgetEntryView : View { - let entry: WalletInformationWidgetEntry - - var WalletBalance: some View { - WalletInformationView(allWalletsBalance: entry.allWalletsBalance, marketData: entry.marketData) - } - - var body: some View { - VStack(content: { - WalletBalance - }).padding().background(Color.widgetBackground) - } +struct WalletInformationWidgetEntryView: View { + let entry: WalletInformationWidgetEntry + + var WalletBalance: some View { + WalletInformationView(allWalletsBalance: entry.allWalletsBalance, marketData: entry.marketData) + } + + var body: some View { + VStack(content: { + WalletBalance + }).padding().background(Color.widgetBackground) + } } struct WalletInformationWidget: Widget { - let kind: String = "WalletInformationWidget" - - var body: some WidgetConfiguration { - if #available(iOSApplicationExtension 16.0, *) { - return StaticConfiguration(kind: kind, provider: WalletInformationWidgetProvider()) { entry in - WalletInformationWidgetEntryView(entry: entry) - } - .configurationDisplayName("Balance") - .description("View your accumulated balance.").supportedFamilies([.systemSmall]) - } else { - return StaticConfiguration(kind: kind, provider: WalletInformationWidgetProvider()) { entry in - WalletInformationWidgetEntryView(entry: entry) - } - .configurationDisplayName("Balance") - .description("View your accumulated balance.").supportedFamilies([.systemSmall]) + let kind: String = "WalletInformationWidget" + + var body: some WidgetConfiguration { + if #available(iOSApplicationExtension 16.0, *) { + return StaticConfiguration(kind: kind, provider: WalletInformationWidgetProvider()) { entry in + WalletInformationWidgetEntryView(entry: entry) + } + .configurationDisplayName("Balance") + .description("View your accumulated balance.").supportedFamilies([.systemSmall]) + .contentMarginsDisabledIfAvailable() + } else { + return StaticConfiguration(kind: kind, provider: WalletInformationWidgetProvider()) { entry in + WalletInformationWidgetEntryView(entry: entry) + } + .configurationDisplayName("Balance") + .description("View your accumulated balance.").supportedFamilies([.systemSmall]) + .contentMarginsDisabledIfAvailable() + } } - } } struct WalletInformationWidget_Previews: PreviewProvider { - static var previews: some View { - WalletInformationWidgetEntryView(entry: WalletInformationWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: Double(0)), allWalletsBalance: WalletData(balance: 10000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000)))) - .previewContext(WidgetPreviewContext(family: .systemSmall)) - } + static var previews: some View { + WalletInformationWidgetEntryView(entry: WalletInformationWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: Double(0)), allWalletsBalance: WalletData(balance: 0, latestTransactionTime: LatestTransaction(isUnconfirmed: nil, epochValue: nil)))) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + } } diff --git a/ios/WidgetHelper.swift b/ios/WidgetHelper.swift new file mode 100644 index 00000000000..7b3fa226791 --- /dev/null +++ b/ios/WidgetHelper.swift @@ -0,0 +1,21 @@ +import Foundation +import WidgetKit + +class WidgetHelper { + static func reloadAllWidgets() { + #if arch(arm64) || arch(i386) || arch(x86_64) + if #available(iOS 14.0, *) { + WidgetCenter.shared.reloadAllTimelines() + } + #endif + } + + static func getSharedUserDefaults() -> UserDefaults? { + let suiteName = "group.io.bluewallet.bluewallet" + let defaults = UserDefaults(suiteName: suiteName) + if defaults == nil { + NSLog("[WidgetHelper] Warning: Could not access shared UserDefaults") + } + return defaults + } +} diff --git a/ios/Widgets/Info.plist b/ios/Widgets/Info.plist index a71f97fb336..72c00dced04 100644 --- a/ios/Widgets/Info.plist +++ b/ios/Widgets/Info.plist @@ -20,11 +20,47 @@ $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + onion + + NSExceptionAllowsInsecureHTTPLoads + + NSIncludesSubdomains + + + tailscale.net + + NSExceptionAllowsInsecureHTTPLoads + + NSIncludesSubdomains + + + ts.net + + NSExceptionAllowsInsecureHTTPLoads + + NSIncludesSubdomains + + + + NSExtension NSExtensionPointIdentifier com.apple.widgetkit-extension + RCTNewArchEnabled + bugsnag apiKey diff --git a/ios/Widgets/MarketWidget/MarketWidget.swift b/ios/Widgets/MarketWidget/MarketWidget.swift index 01b2edac014..eeaab5d30c9 100644 --- a/ios/Widgets/MarketWidget/MarketWidget.swift +++ b/ios/Widgets/MarketWidget/MarketWidget.swift @@ -10,88 +10,103 @@ import WidgetKit import SwiftUI struct MarketWidgetProvider: TimelineProvider { - func placeholder(in context: Context) -> MarketWidgetEntry { - return MarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10 000", rate: 10000)) - } - - func getSnapshot(in context: Context, completion: @escaping (MarketWidgetEntry) -> ()) { - let entry: MarketWidgetEntry - if (context.isPreview) { - entry = MarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10 000", rate: 10000)) - } else { - entry = MarketWidgetEntry(date: Date(), marketData: emptyMarketData) + static var lastSuccessfulEntry: MarketWidgetEntry? + + func placeholder(in context: Context) -> MarketWidgetEntry { + return MarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10 000", rate: 10000)) } - completion(entry) - } - - func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { - var entries: [MarketWidgetEntry] = [] - if context.isPreview { - let entry = MarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10 000", rate: 10000)) - entries.append(entry) - let timeline = Timeline(entries: entries, policy: .atEnd) - completion(timeline) - }else { - let userPreferredCurrency = WidgetAPI.getUserPreferredCurrency(); - let marketDataEntry = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0) - WidgetAPI.fetchMarketData(currency: userPreferredCurrency, completion: { (result, error) in + + func getSnapshot(in context: Context, completion: @escaping (MarketWidgetEntry) -> ()) { let entry: MarketWidgetEntry - if let result = result { - entry = MarketWidgetEntry(date: Date(), marketData: result) - + if context.isPreview { + entry = MarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10 000", rate: 10000)) } else { - entry = MarketWidgetEntry(date: Date(), marketData: marketDataEntry) + entry = MarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0)) } - entries.append(entry) - let timeline = Timeline(entries: entries, policy: .atEnd) - completion(timeline) - }) + completion(entry) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { + let currentDate = Date() + var entries: [MarketWidgetEntry] = [] + + let marketDataEntry = MarketWidgetEntry(date: currentDate, marketData: MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0)) + entries.append(marketDataEntry) // Initial placeholder entry + + let userPreferredCurrency = Currency.getUserPreferredCurrency() + fetchMarketDataWithRetry(currency: userPreferredCurrency, retries: 3) { marketData in + let entry = MarketWidgetEntry(date: Date(), marketData: marketData) + entries.append(entry) + let timeline = Timeline(entries: entries, policy: .atEnd) + completion(timeline) + } + } + + private func fetchMarketDataWithRetry(currency: String, retries: Int, completion: @escaping (MarketData) -> ()) { + var attempt = 0 + + func attemptFetch() { + attempt += 1 + print("Attempt \(attempt) to fetch market data.") + + MarketAPI.fetchMarketData(currency: currency) { result in + switch result { + case .success(let marketData): + print("Successfully fetched market data on attempt \(attempt).") + completion(marketData) + case .failure(let error): + print("Fetch market data failed (attempt \(attempt)): \(error.localizedDescription)") + if attempt < retries { + DispatchQueue.global().asyncAfter(deadline: .now() + 2) { + attemptFetch() + } + } else { + print("Failed to fetch market data after \(retries) attempts.") + let fallbackData = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0) + completion(fallbackData) + } + } + } + } + + attemptFetch() } - } } struct MarketWidgetEntry: TimelineEntry { - let date: Date - let marketData: MarketData + let date: Date + var marketData: MarketData } -struct MarketWidgetEntryView : View { - var entry: MarketWidgetProvider.Entry - - var MarketStack: some View { - MarketView(marketData: entry.marketData).padding(EdgeInsets(top: 18, leading: 11, bottom: 18, trailing: 11)) +struct MarketWidgetEntryView: View { + var entry: MarketWidgetEntry + + var MarketStack: some View { + MarketView(marketData: entry.marketData) } var body: some View { VStack(content: { - MarketStack.background(Color.widgetBackground) + MarketStack.containerBackground(Color.widgetBackground, for: .widget) }) } } struct MarketWidget: Widget { - let kind: String = "MarketWidget" - - var body: some WidgetConfiguration { - if #available(iOSApplicationExtension 16.0, *) { - return StaticConfiguration(kind: kind, provider: MarketWidgetProvider()) { entry in - MarketWidgetEntryView(entry: entry) - } - .configurationDisplayName("Market") - .description("View the current market information.").supportedFamilies([.systemSmall]) - } else { - return StaticConfiguration(kind: kind, provider: MarketWidgetProvider()) { entry in - MarketWidgetEntryView(entry: entry) - } - .configurationDisplayName("Market") - .description("View the current market information.").supportedFamilies([.systemSmall]) + let kind: String = "MarketWidget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: MarketWidgetProvider()) { entry in + MarketWidgetEntryView(entry: entry) + } + .configurationDisplayName("Market") + .description("View the current market information.").supportedFamilies([.systemSmall]) } - } } struct MarketWidget_Previews: PreviewProvider { - static var previews: some View { - MarketWidgetEntryView(entry: MarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 0))) - .previewContext(WidgetPreviewContext(family: .systemSmall)) - } + static var previews: some View { + MarketWidgetEntryView(entry: MarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9,134", price: "$10,000", rate: 0))) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + } } diff --git a/ios/Widgets/PriceWidget/CompactPriceView.swift b/ios/Widgets/PriceWidget/CompactPriceView.swift new file mode 100644 index 00000000000..e9057d6d953 --- /dev/null +++ b/ios/Widgets/PriceWidget/CompactPriceView.swift @@ -0,0 +1,67 @@ +import SwiftUI + +@available(iOS 15.0, *) +struct CompactPriceView: View { + @Environment(\.colorScheme) var colorScheme + + let price: String + let lastUpdated: String + let code: String + let dataSource: String + + var body: some View { + VStack(alignment: .center, spacing: 16) { + Text(price) + .font(.title) + .bold() + .multilineTextAlignment(.center) + .dynamicTypeSize(.large ... .accessibility5) + .foregroundColor(textColor) + .accessibilityLabel("Bitcoin price: \(price)") + + VStack(alignment: .center, spacing: 8) { + Text(code) + .shadow(color: shadowColor, radius: 1, x: 0, y: 1) + Text(lastUpdated) + .shadow(color: shadowColor, radius: 1, x: 0, y: 1) + Text(dataSource) + .shadow(color: shadowColor, radius: 1, x: 0, y: 1) + } + .font(.subheadline) + .foregroundColor(textColor) + .multilineTextAlignment(.center) + .accessibilityElement(children: .combine) + } + .padding() + .frame(maxWidth: .infinity) + } + + private var textColor: Color { + colorScheme == .dark ? .white : .black + } + + private var shadowColor: Color { + textColor.opacity(0.2) + } +} + +@available(iOS 15.0, *) +struct CompactPriceView_Previews: PreviewProvider { + static var previews: some View { + ZStack { + LinearGradient( + gradient: Gradient(colors: [.blue, .purple]), + startPoint: .top, + endPoint: .bottom + ) + .ignoresSafeArea() + + CompactPriceView( + price: "$50,000", + lastUpdated: "Last updated: Oct 10, 2023", + code: "BTC", + dataSource: "Data source: CoinDesk" + ) + } + } +} diff --git a/ios/Widgets/PriceWidget/PriceIntent.swift b/ios/Widgets/PriceWidget/PriceIntent.swift new file mode 100644 index 00000000000..0d229e2df4b --- /dev/null +++ b/ios/Widgets/PriceWidget/PriceIntent.swift @@ -0,0 +1,173 @@ +// +// PriceIntent.swift +// BlueWallet +// + +import AppIntents +import SwiftUI +import Foundation + +// MARK: - Error Types + +enum PriceIntentError: LocalizedError { + case fetchFailed + case invalidData + case networkUnavailable + + var errorDescription: String? { + switch self { + case .fetchFailed: + return "Failed to fetch Bitcoin price data" + case .invalidData: + return "Received invalid price data" + case .networkUnavailable: + return "Network is unavailable" + } + } +} + +// MARK: - Price Data Model + +struct PriceData { + let rate: Double + let lastUpdate: String + let formattedPrice: String + let currencyCode: String + let dataSource: String +} + +@available(iOS 16.0, *) +struct PriceIntent: AppIntent { + // MARK: - Intent Metadata + + static var title: LocalizedStringResource = "Market Rate" + static var description = IntentDescription("View the current Bitcoin market rate in your preferred currency.") + static var openAppWhenRun: Bool { false } + + // MARK: - Parameters + + @Parameter( + title: "Currency", + description: "Choose your preferred currency." + ) + var fiatCurrency: FiatUnitEnum? + + @MainActor + func perform() async throws -> some IntentResult & ReturnsValue & ProvidesDialog & ShowsSnippetView { + let selectedCurrency = resolveCurrency() + + do { + let priceData = try await fetchPriceData(for: selectedCurrency) + let successView = CompactPriceView( + price: priceData.formattedPrice, + lastUpdated: priceData.lastUpdate, + code: priceData.currencyCode, + dataSource: priceData.dataSource + ) + + return .result( + value: priceData.rate, + dialog: "Current Bitcoin Market Rate", + view: successView + ) + } catch { + let errorView = CompactPriceView( + price: "N/A", + lastUpdated: "--", + code: selectedCurrency.rawValue, + dataSource: "Error fetching data" + ) + + return .result( + value: 0.0, + dialog: "Failed to retrieve the Bitcoin market rate.", + view: errorView + ) + } + } + + // MARK: - Currency Resolution + + private func resolveCurrency() -> FiatUnitEnum { + // Priority order: parameter -> shared defaults -> device locale -> USD fallback + if let providedCurrency = fiatCurrency { + return providedCurrency + } + + if let sharedCurrency = getSharedCurrency() { + return sharedCurrency + } + + if let deviceCurrency = getDeviceCurrency() { + return deviceCurrency + } + + return .USD + } + + private func getSharedCurrency() -> FiatUnitEnum? { + guard let sharedDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue), + let currencyCode = sharedDefaults.string(forKey: UserDefaultsGroupKey.PreferredCurrency.rawValue), + let currency = FiatUnitEnum(rawValue: currencyCode.uppercased()) else { + return nil + } + return currency + } + + private func getDeviceCurrency() -> FiatUnitEnum? { + guard let deviceCurrencyCode = Locale.current.currency?.identifier, + let currency = FiatUnitEnum(rawValue: deviceCurrencyCode.uppercased()) else { + return nil + } + return currency + } + + // MARK: - Data Fetching + + private func fetchPriceData(for currency: FiatUnitEnum) async throws -> PriceData { + guard let fetchedData = try await MarketAPI.fetchPrice(currency: currency.rawValue) else { + throw PriceIntentError.fetchFailed + } + + let formattedPrice = formatPrice(fetchedData.rateDouble, currencyCode: currency.rawValue) + let formattedDate = formatDate(from: fetchedData.lastUpdate) + + return PriceData( + rate: fetchedData.rateDouble, + lastUpdate: formattedDate, + formattedPrice: formattedPrice, + currencyCode: currency.rawValue, + dataSource: currency.source + ) } + + // MARK: - Formatting Methods + + private func formatDate(from isoString: String?) -> String { + guard let isoString = isoString, + let date = ISO8601DateFormatter().date(from: isoString) else { + return "--" + } + + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .short + return formatter.string(from: date) + } + + private func formatPrice(_ price: Double, currencyCode: String) -> String { + let formatter = NumberFormatter() + formatter.numberStyle = .currency + formatter.locale = Locale.current + formatter.currencyCode = currencyCode + + if price >= 1000 { + formatter.maximumFractionDigits = 0 + formatter.minimumFractionDigits = 0 + } else { + formatter.maximumFractionDigits = 2 + formatter.minimumFractionDigits = 2 + } + + return formatter.string(from: NSNumber(value: price)) ?? "\(price)" + } +} diff --git a/ios/Widgets/PriceWidget/PriceWidget.swift b/ios/Widgets/PriceWidget/PriceWidget.swift index 992cc39b073..c93edfeb2ca 100644 --- a/ios/Widgets/PriceWidget/PriceWidget.swift +++ b/ios/Widgets/PriceWidget/PriceWidget.swift @@ -9,103 +9,73 @@ import WidgetKit import SwiftUI -var marketData: [MarketDataTimeline: MarketData?] = [ .Current: nil, .Previous: nil] -struct PriceWidgetProvider: TimelineProvider { - typealias Entry = PriceWidgetEntry - - func placeholder(in context: Context) -> PriceWidgetEntry { - return PriceWidgetEntry(date: Date(), currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00")) - } - - func getSnapshot(in context: Context, completion: @escaping (PriceWidgetEntry) -> ()) { - let entry: PriceWidgetEntry - if (context.isPreview) { - entry = PriceWidgetEntry(date: Date(), currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00")) - } else { - entry = PriceWidgetEntry(date: Date(), currentMarketData: emptyMarketData) +@available(iOS 16.0, *) +struct PriceWidget: Widget { + let kind: String = "PriceWidget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: PriceWidgetProvider()) { entry in + PriceWidgetEntryView(entry: entry) + } + .configurationDisplayName("Price") + .description("View the current price of Bitcoin.") + .supportedFamilies(supportedFamilies) + .contentMarginsDisabledIfAvailable() } - completion(entry) - } - - func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { - var entries: [PriceWidgetEntry] = [] - if (context.isPreview) { - let entry = PriceWidgetEntry(date: Date(), currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00")) - entries.append(entry) - let timeline = Timeline(entries: entries, policy: .atEnd) - completion(timeline) - } else { - if WidgetAPI.getUserPreferredCurrency() != WidgetAPI.getLastSelectedCurrency() { - marketData[.Current] = nil - marketData[.Previous] = nil - WidgetAPI.saveNewSelectedCurrency() - } - - var entryMarketData = marketData[.Current] ?? emptyMarketData - WidgetAPI.fetchPrice(currency: WidgetAPI.getUserPreferredCurrency()) { (data, error) in - if let data = data, let formattedRate = data.formattedRate { - let currentMarketData = MarketData(nextBlock: "", sats: "", price: formattedRate, rate: data.rateDouble, dateString: data.lastUpdate) - if let cachedMarketData = marketData[.Current], currentMarketData.dateString != cachedMarketData?.dateString { - marketData[.Previous] = marketData[.Current] - marketData[.Current] = currentMarketData - entryMarketData = currentMarketData - entries.append(PriceWidgetEntry(date:Date(), currentMarketData: entryMarketData)) - } else { - entries.append(PriceWidgetEntry(date:Date(), currentMarketData: currentMarketData)) - } + + @available(iOS 16.0, *) + private var supportedFamilies: [WidgetFamily] { + if #available(iOSApplicationExtension 16.0, *) { + return [.systemSmall, .accessoryCircular, .accessoryInline, .accessoryRectangular] + } else { + return [.systemSmall] } - - let timeline = Timeline(entries: entries, policy: .atEnd) - completion(timeline) - } } - } } -struct PriceWidgetEntry: TimelineEntry { - let date: Date - let currentMarketData: MarketData? - var previousMarketData: MarketData? { - return marketData[.Previous] as? MarketData - } +@available(iOS 16.0, *) +struct PriceWidget_Previews: PreviewProvider { + static var previews: some View { + Group { + PriceWidgetEntryView(entry: PreviewData.entry) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + if #available(iOSApplicationExtension 16.0, *) { + PriceWidgetEntryView(entry: PreviewData.entry) + .previewContext(WidgetPreviewContext(family: .accessoryCircular)) + PriceWidgetEntryView(entry: PreviewData.entry) + .previewContext(WidgetPreviewContext(family: .accessoryInline)) + PriceWidgetEntryView(entry: PreviewData.entry) + .previewContext(WidgetPreviewContext(family: .accessoryRectangular)) + } + } + } } -struct PriceWidgetEntryView : View { - let entry: PriceWidgetEntry - var priceView: some View { - PriceView(currentMarketData: entry.currentMarketData, previousMarketData: marketData[.Previous] ?? emptyMarketData).padding() - } - - var body: some View { - VStack(content: { - priceView - }).background(Color.widgetBackground) - } -} +let previewMarketData = MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00") -struct PriceWidget: Widget { - let kind: String = "PriceWidget" - - var body: some WidgetConfiguration { - if #available(iOSApplicationExtension 16.0, *) { - return StaticConfiguration(kind: kind, provider: PriceWidgetProvider()) { entry in - PriceWidgetEntryView(entry: entry) - } - .configurationDisplayName("Price") - .description("View the current price of Bitcoin.").supportedFamilies([.systemSmall]) - } else { - return StaticConfiguration(kind: kind, provider: PriceWidgetProvider()) { entry in - PriceWidgetEntryView(entry: entry) - } - .configurationDisplayName("Price") - .description("View the current price of Bitcoin.").supportedFamilies([.systemSmall]) - } - } +@available(iOS 14.0, *) +struct PreviewData { + static let entry = PriceWidgetEntry( + date: Date(), + family: .systemSmall, + currentMarketData: previewMarketData, + previousMarketData: emptyMarketData + ) } -struct PriceWidget_Previews: PreviewProvider { - static var previews: some View { - PriceWidgetEntryView(entry: PriceWidgetEntry(date: Date(), currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00"))) - .previewContext(WidgetPreviewContext(family: .systemSmall)) - } +@available(iOS 14.0, *) +extension WidgetConfiguration +{ + @available(iOS 15.0, *) + func contentMarginsDisabledIfAvailable() -> some WidgetConfiguration + { + if #available(iOSApplicationExtension 17.0, *) + { + return self.contentMarginsDisabled() + } + else + { + return self + } + } } diff --git a/ios/Widgets/PriceWidget/PriceWidgetEntry.swift b/ios/Widgets/PriceWidget/PriceWidgetEntry.swift new file mode 100644 index 00000000000..02f396a01ee --- /dev/null +++ b/ios/Widgets/PriceWidget/PriceWidgetEntry.swift @@ -0,0 +1,25 @@ +// +// PriceWidgetEntry.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 10/27/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import AppIntents +import WidgetKit + +@available(iOS 14.0, *) +public struct PriceWidgetEntry: TimelineEntry { + public let date: Date + public let family: WidgetFamily + public let currentMarketData: MarketData? + public let previousMarketData: MarketData? + + public init(date: Date, family: WidgetFamily, currentMarketData: MarketData?, previousMarketData: MarketData?) { + self.date = date + self.family = family + self.currentMarketData = currentMarketData + self.previousMarketData = previousMarketData + } +} diff --git a/ios/Widgets/PriceWidget/PriceWidgetEntryView.swift b/ios/Widgets/PriceWidget/PriceWidgetEntryView.swift new file mode 100644 index 00000000000..dce51c7e44c --- /dev/null +++ b/ios/Widgets/PriceWidget/PriceWidgetEntryView.swift @@ -0,0 +1,19 @@ +// +// PriceWidgetEntryView.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 10/27/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import SwiftUI + + +@available(iOS 16.0, *) +struct PriceWidgetEntryView: View { + let entry: PriceWidgetEntry + + var body: some View { + PriceView(entry: entry) + } +} diff --git a/ios/Widgets/PriceWidget/PriceWidgetProvider.swift b/ios/Widgets/PriceWidget/PriceWidgetProvider.swift new file mode 100644 index 00000000000..b1958b33df8 --- /dev/null +++ b/ios/Widgets/PriceWidget/PriceWidgetProvider.swift @@ -0,0 +1,79 @@ +// +// PriceWidgetProvider.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 10/27/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + + +import WidgetKit +import SwiftUI + +@available(iOS 16.0, *) +struct PriceWidgetProvider: TimelineProvider { + typealias Entry = PriceWidgetEntry + static var lastSuccessfulEntry: PriceWidgetEntry? + + func placeholder(in context: Context) -> PriceWidgetEntry { + createEntry(date: Date(), family: context.family, currentMarketData: previewMarketData) + } + + func getSnapshot(in context: Context, completion: @escaping (PriceWidgetEntry) -> Void) { + let entry: PriceWidgetEntry + if context.isPreview { + entry = createEntry(date: Date(), family: context.family, currentMarketData: previewMarketData) + } else { + entry = createEntry(date: Date(), family: context.family, currentMarketData: emptyMarketData) + } + completion(entry) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { + var entries: [PriceWidgetEntry] = [] + + let userPreferredCurrency = Currency.getUserPreferredCurrency() + if userPreferredCurrency != Currency.getLastSelectedCurrency() { + Currency.saveNewSelectedCurrency() + } + + Task { + do { + if let data = try await MarketAPI.fetchPrice(currency: userPreferredCurrency), let formattedRate = data.formattedRate { + let currentMarketData = MarketData(nextBlock: "", sats: "", price: formattedRate, rate: data.rateDouble, dateString: data.lastUpdate) + let previousMarketData = PriceWidgetProvider.lastSuccessfulEntry?.currentMarketData + + let entry = createEntry( + date: Date(), + family: context.family, + currentMarketData: currentMarketData, + previousMarketData: previousMarketData ?? emptyMarketData + ) + PriceWidgetProvider.lastSuccessfulEntry = entry + entries.append(entry) + } else { + if let lastEntry = PriceWidgetProvider.lastSuccessfulEntry { + entries.append(lastEntry) + } else { + let entry = createEntry(date: Date(), family: context.family, currentMarketData: emptyMarketData) + entries.append(entry) + } + } + } catch { + if let lastEntry = PriceWidgetProvider.lastSuccessfulEntry { + entries.append(lastEntry) + } else { + let entry = createEntry(date: Date(), family: context.family, currentMarketData: emptyMarketData) + entries.append(entry) + } + } + + let timeline = Timeline(entries: entries, policy: .atEnd) + completion(timeline) + } + } + + private func createEntry(date: Date, family: WidgetFamily, currentMarketData: MarketData, previousMarketData: MarketData = emptyMarketData) -> PriceWidgetEntry { + PriceWidgetEntry(date: date, family: family, currentMarketData: currentMarketData, previousMarketData: previousMarketData) + } +} diff --git a/ios/Widgets/Shared/Fiat/FiatUnit.swift b/ios/Widgets/Shared/Fiat/FiatUnit.swift deleted file mode 100644 index eaf2c73e84e..00000000000 --- a/ios/Widgets/Shared/Fiat/FiatUnit.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// FiatUnit.swift -// BlueWallet -// -// Created by Marcos Rodriguez on 11/20/20. -// Copyright © 2020 BlueWallet. All rights reserved. -// -import Foundation - -struct FiatUnit: Codable { - let endPointKey: String - let symbol: String - let locale: String - let source: String - -} - -func fiatUnit(currency: String) -> FiatUnit? { - return Bundle.main.decode([String: FiatUnit].self, from: "fiatUnits.json").first(where: {$1.endPointKey == currency})?.value -} - -extension Bundle { - func decode(_ type: T.Type, from file: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T { - guard let url = self.url(forResource: file, withExtension: nil) else { - fatalError("Failed to locate \(file) in bundle.") - } - - guard let data = try? Data(contentsOf: url) else { - fatalError("Failed to load \(file) from bundle.") - } - - let decoder = JSONDecoder() - - decoder.dateDecodingStrategy = dateDecodingStrategy - decoder.keyDecodingStrategy = keyDecodingStrategy - - do { - return try decoder.decode(T.self, from: data) - } catch DecodingError.keyNotFound(let key, let context) { - fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' not found – \(context.debugDescription)") - } catch DecodingError.typeMismatch(_, let context) { - fatalError("Failed to decode \(file) from bundle due to type mismatch – \(context.debugDescription)") - } catch DecodingError.valueNotFound(let type, let context) { - fatalError("Failed to decode \(file) from bundle due to missing \(type) value – \(context.debugDescription)") - } catch DecodingError.dataCorrupted(_) { - fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON") - } catch { - fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)") - } - } -} diff --git a/ios/Widgets/Shared/FiatUnitEnum.swift b/ios/Widgets/Shared/FiatUnitEnum.swift new file mode 100644 index 00000000000..e1aeb0d3ade --- /dev/null +++ b/ios/Widgets/Shared/FiatUnitEnum.swift @@ -0,0 +1,262 @@ + +// Hardcoding values for simplicity; AppIntents are unnecessarily complex + +import AppIntents + +@available(iOS 16.0, *) +enum FiatUnitEnum: String, AppEnum, CaseIterable, Identifiable, Codable { + + var id: String { self.rawValue } + + case AED + case ARS + case AUD + case AWG + case BHD + case BRL + case CAD + case CHF + case CLP + case CNY + case COP + case CZK + case DKK + case EGP + case EUR + case GBP + case HRK + case HUF + case IDR + case ILS + case INR + case IRR + case IRT + case ISK + case JPY + case KES + case KRW + case KWD + case LBP + case LKR + case MXN + case MYR + case MZN + case NGN + case NOK + case NZD + case OMR + case PHP + case PLN + case QAR + case RON + case RUB + case SAR + case SEK + case SGD + case THB + case TRY + case TWD + case TZS + case UAH + case UGX + case USD + case UYU + case VEF + case VES + case XAF + case ZAR + case GHS + + var code: String { + return self.rawValue + } + + var source: String { + switch self { + case .AED: + return "CoinGecko" + case .ARS: + return "Yadio" + case .AUD: + return "CoinGecko" + case .AWG: + return "CoinDesk" + case .BHD: + return "CoinGecko" + case .BRL: + return "CoinGecko" + case .CAD: + return "CoinGecko" + case .CHF: + return "CoinGecko" + case .CLP: + return "Yadio" + case .CNY: + return "Coinbase" + case .COP: + return "CoinDesk" + case .CZK: + return "CoinGecko" + case .DKK: + return "CoinGecko" + case .EGP: + return "YadioConvert" + case .EUR: + return "Kraken" + case .GBP: + return "Kraken" + case .HRK: + return "CoinDesk" + case .HUF: + return "CoinGecko" + case .IDR: + return "CoinGecko" + case .ILS: + return "CoinGecko" + case .INR: + return "coinpaprika" + case .IRR: + return "Exir" + case .IRT: + return "Exir" + case .ISK: + return "CoinDesk" + case .JPY: + return "CoinGecko" + case .KES: + return "CoinDesk" + case .KRW: + return "CoinGecko" + case .KWD: + return "CoinGecko" + case .LBP: + return "YadioConvert" + case .LKR: + return "CoinGecko" + case .MXN: + return "CoinGecko" + case .MYR: + return "CoinGecko" + case .MZN: + return "CoinDesk" + case .NGN: + return "CoinGecko" + case .NOK: + return "CoinGecko" + case .NZD: + return "CoinGecko" + case .OMR: + return "CoinDesk" + case .PHP: + return "CoinGecko" + case .PLN: + return "CoinGecko" + case .QAR: + return "CoinDesk" + case .RON: + return "BNR" + case .RUB: + return "CoinGecko" + case .SAR: + return "CoinGecko" + case .SEK: + return "CoinGecko" + case .SGD: + return "CoinGecko" + case .THB: + return "CoinGecko" + case .TRY: + return "CoinGecko" + case .TWD: + return "CoinGecko" + case .TZS: + return "CoinDesk" + case .UAH: + return "CoinGecko" + case .UGX: + return "CoinDesk" + case .USD: + return "Kraken" + case .UYU: + return "CoinDesk" + case .VEF: + return "CoinGecko" + case .VES: + return "Yadio" + case .XAF: + return "CoinDesk" + case .ZAR: + return "CoinGecko" + case .GHS: + return "CoinDesk" + } + } + + static var typeDisplayRepresentation: TypeDisplayRepresentation { + TypeDisplayRepresentation(stringLiteral: "Currency") + } + + static var caseDisplayRepresentations: [FiatUnitEnum: DisplayRepresentation] { + return [ + .AED: DisplayRepresentation(stringLiteral: "United Arab Emirates (UAE Dirham)"), + .ARS: DisplayRepresentation(stringLiteral: "Argentina (Argentine Peso)"), + .AUD: DisplayRepresentation(stringLiteral: "Australia (Australian Dollar)"), + .AWG: DisplayRepresentation(stringLiteral: "Aruba (Aruban Florin)"), + .BHD: DisplayRepresentation(stringLiteral: "Bahrain (Bahraini Dinar)"), + .BRL: DisplayRepresentation(stringLiteral: "Brazil (Brazilian Real)"), + .CAD: DisplayRepresentation(stringLiteral: "Canada (Canadian Dollar)"), + .CHF: DisplayRepresentation(stringLiteral: "Switzerland (Swiss Franc)"), + .CLP: DisplayRepresentation(stringLiteral: "Chile (Chilean Peso)"), + .CNY: DisplayRepresentation(stringLiteral: "China (Chinese Yuan)"), + .COP: DisplayRepresentation(stringLiteral: "Colombia (Colombian Peso)"), + .CZK: DisplayRepresentation(stringLiteral: "Czech Republic (Czech Koruna)"), + .DKK: DisplayRepresentation(stringLiteral: "Denmark (Danish Krone)"), + .EGP: DisplayRepresentation(stringLiteral: "Egypt (Egyptian Pound)"), + .EUR: DisplayRepresentation(stringLiteral: "European Union (Euro)"), + .GBP: DisplayRepresentation(stringLiteral: "United Kingdom (British Pound)"), + .HRK: DisplayRepresentation(stringLiteral: "Croatia (Croatian Kuna)"), + .HUF: DisplayRepresentation(stringLiteral: "Hungary (Hungarian Forint)"), + .IDR: DisplayRepresentation(stringLiteral: "Indonesia (Indonesian Rupiah)"), + .ILS: DisplayRepresentation(stringLiteral: "Israel (Israeli New Shekel)"), + .INR: DisplayRepresentation(stringLiteral: "India (Indian Rupee)"), + .IRR: DisplayRepresentation(stringLiteral: "Iran (Iranian Rial)"), + .IRT: DisplayRepresentation(stringLiteral: "Iran (Iranian Toman)"), + .ISK: DisplayRepresentation(stringLiteral: "Iceland (Icelandic Króna)"), + .JPY: DisplayRepresentation(stringLiteral: "Japan (Japanese Yen)"), + .KES: DisplayRepresentation(stringLiteral: "Kenya (Kenyan Shilling)"), + .KRW: DisplayRepresentation(stringLiteral: "South Korea (South Korean Won)"), + .KWD: DisplayRepresentation(stringLiteral: "Kuwait (Kuwaiti Dinar)"), + .LBP: DisplayRepresentation(stringLiteral: "Lebanon (Lebanese Pound)"), + .LKR: DisplayRepresentation(stringLiteral: "Sri Lanka (Sri Lankan Rupee)"), + .MXN: DisplayRepresentation(stringLiteral: "Mexico (Mexican Peso)"), + .MYR: DisplayRepresentation(stringLiteral: "Malaysia (Malaysian Ringgit)"), + .MZN: DisplayRepresentation(stringLiteral: "Mozambique (Mozambican Metical)"), + .NGN: DisplayRepresentation(stringLiteral: "Nigeria (Nigerian Naira)"), + .NOK: DisplayRepresentation(stringLiteral: "Norway (Norwegian Krone)"), + .NZD: DisplayRepresentation(stringLiteral: "New Zealand (New Zealand Dollar)"), + .OMR: DisplayRepresentation(stringLiteral: "Oman (Omani Rial)"), + .PHP: DisplayRepresentation(stringLiteral: "Philippines (Philippine Peso)"), + .PLN: DisplayRepresentation(stringLiteral: "Poland (Polish Zloty)"), + .QAR: DisplayRepresentation(stringLiteral: "Qatar (Qatari Riyal)"), + .RON: DisplayRepresentation(stringLiteral: "Romania (Romanian Leu)"), + .RUB: DisplayRepresentation(stringLiteral: "Russia (Russian Ruble)"), + .SAR: DisplayRepresentation(stringLiteral: "Saudi Arabia (Saudi Riyal)"), + .SEK: DisplayRepresentation(stringLiteral: "Sweden (Swedish Krona)"), + .SGD: DisplayRepresentation(stringLiteral: "Singapore (Singapore Dollar)"), + .THB: DisplayRepresentation(stringLiteral: "Thailand (Thai Baht)"), + .TRY: DisplayRepresentation(stringLiteral: "Turkey (Turkish Lira)"), + .TWD: DisplayRepresentation(stringLiteral: "Taiwan (New Taiwan Dollar)"), + .TZS: DisplayRepresentation(stringLiteral: "Tanzania (Tanzanian Shilling)"), + .UAH: DisplayRepresentation(stringLiteral: "Ukraine (Ukrainian Hryvnia)"), + .UGX: DisplayRepresentation(stringLiteral: "Uganda (Ugandan Shilling)"), + .USD: DisplayRepresentation(stringLiteral: "United States of America (US Dollar)"), + .UYU: DisplayRepresentation(stringLiteral: "Uruguay (Uruguayan Peso)"), + .VEF: DisplayRepresentation(stringLiteral: "Venezuela (Venezuelan Bolívar Fuerte)"), + .VES: DisplayRepresentation(stringLiteral: "Venezuela (Venezuelan Bolívar Soberano)"), + .XAF: DisplayRepresentation(stringLiteral: "Central African Republic (Central African Franc)"), + .ZAR: DisplayRepresentation(stringLiteral: "South Africa (South African Rand)"), + .GHS: DisplayRepresentation(stringLiteral: "Ghana (Ghanaian Cedi)"), + ] + } +} + diff --git a/ios/Widgets/Shared/HostManager.swift b/ios/Widgets/Shared/HostManager.swift new file mode 100644 index 00000000000..dd5c22c6480 --- /dev/null +++ b/ios/Widgets/Shared/HostManager.swift @@ -0,0 +1,56 @@ +import Foundation + +actor HostManager { + var availableHosts: [(host: String, port: UInt16, useSSL: Bool)] + var hostFailureCounts: [String: Int] = [:] + let maxRetriesPerHost: Int + + init(hosts: [(host: String, port: UInt16, useSSL: Bool)], maxRetriesPerHost: Int) { + self.availableHosts = hosts + self.maxRetriesPerHost = maxRetriesPerHost + print("Initialized HostManager with \(hosts.count) hosts.") + } + + func getNextHost() -> (host: String, port: UInt16, useSSL: Bool)? { + guard !availableHosts.isEmpty else { + print("No available hosts to retrieve.") + return nil + } + + var attempts = availableHosts.count + while attempts > 0 { + let currentHost = availableHosts.removeFirst() + if !shouldSkipHost(currentHost.host) { + availableHosts.append(currentHost) + print("Selected host: \(currentHost.host):\(currentHost.port) (SSL: \(currentHost.useSSL))") + return currentHost + } else { + availableHosts.append(currentHost) + attempts -= 1 + print("Host \(currentHost.host) is skipped due to max retries.") + } + } + + print("All hosts have been exhausted after max retries.") + return nil + } + + func shouldSkipHost(_ host: String) -> Bool { + if let failureCount = hostFailureCounts[host], failureCount >= maxRetriesPerHost { + print("Host \(host) has reached max retries (\(failureCount)). It will be skipped.") + return true + } + return false + } + + func resetFailureCount(for host: String) { + hostFailureCounts[host] = 0 + print("Reset failure count for host \(host).") + } + + func incrementFailureCount(for host: String) { + hostFailureCounts[host, default: 0] += 1 + let newCount = hostFailureCounts[host]! + print("Incremented failure count for host \(host). New count: \(newCount)") + } +} \ No newline at end of file diff --git a/ios/Widgets/Shared/Models.swift b/ios/Widgets/Shared/Models.swift deleted file mode 100644 index 25a600d5192..00000000000 --- a/ios/Widgets/Shared/Models.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// Models.swift -// BlueWallet -// -// Created by Marcos Rodriguez on 11/1/20. -// Copyright © 2020 BlueWallet. All rights reserved. -// - -import Foundation - -struct MarketData:Codable { - var nextBlock: String - var sats: String - var price: String - var rate: Double - var formattedNextBlock: String { - return nextBlock == "..." ? "..." : #"\#(nextBlock) sat/b"# - } - var dateString: String = "" - var formattedDate: String? { - let isoDateFormatter = ISO8601DateFormatter() - let dateFormatter = DateFormatter() - dateFormatter.locale = Locale.current - dateFormatter.timeStyle = .short - - if let date = isoDateFormatter.date(from: dateString) { - return dateFormatter.string(from: date) - } - return nil - } - static let string = "MarketData" -} - -struct WalletData { - var balance: Double - var latestTransactionTime: LatestTransaction = LatestTransaction(isUnconfirmed: false, epochValue: 0) - var formattedBalanceBTC: String { - let formatter = NumberFormatter() - formatter.numberStyle = .none - formatter.usesSignificantDigits = true - formatter.maximumSignificantDigits = 9 - formatter.roundingMode = .up - let value = NSNumber(value: balance / 100000000); - if let valueString = formatter.string(from: value) { - return "\(String(describing: valueString)) BTC" - } else { - return "0 BTC" - } - } - -} - -struct LatestTransaction { - let isUnconfirmed: Bool? - let epochValue: Int? -} -let emptyMarketData = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0) -let emptyWalletData = WalletData(balance: 0, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: Int(Date().timeIntervalSince1970))) - -enum MarketDataTimeline: String { - case Previous = "previous" - case Current = "current" -} - -enum UserDefaultsGroupKey: String { - case GroupName = "group.io.bluewallet.bluewallet" - case PreferredCurrency = "preferredCurrency" - case ElectrumSettingsHost = "electrum_host" - case ElectrumSettingsTCPPort = "electrum_tcp_port" - case ElectrumSettingsSSLPort = "electrum_ssl_port" - case AllWalletsBalance = "WidgetCommunicationAllWalletsSatoshiBalance" - case AllWalletsLatestTransactionTime = "WidgetCommunicationAllWalletsLatestTransactionTime" - case LatestTransactionIsUnconfirmed = "\"WidgetCommunicationLatestTransactionIsUnconfirmed\"" -} diff --git a/ios/Widgets/Shared/SwiftTCPClient.swift b/ios/Widgets/Shared/SwiftTCPClient.swift index 3aa75a313e4..44c7f4e0e6b 100644 --- a/ios/Widgets/Shared/SwiftTCPClient.swift +++ b/ios/Widgets/Shared/SwiftTCPClient.swift @@ -1,101 +1,527 @@ -// -// File.swift -// BlueWallet -// -// Created by Marcos Rodriguez on 3/23/23. -// Copyright © 2023 BlueWallet. All rights reserved. -// - import Foundation +import Network +import Dispatch -class SwiftTCPClient: NSObject { - private var inputStream: InputStream? - private var outputStream: OutputStream? - private let bufferSize = 1024 - - // Define the completion block type - typealias ReceiveCompletion = (Result) -> Void - - // Add a completion block property - var receiveCompletion: ReceiveCompletion? - - func connect(to host: String, port: UInt32) -> Bool { - var readStream: Unmanaged? - var writeStream: Unmanaged? - - CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host as CFString, port, &readStream, &writeStream) - - guard let read = readStream?.takeRetainedValue(), let write = writeStream?.takeRetainedValue() else { - return false +enum SwiftTCPClientError: Error, LocalizedError { + case connectionNil + case connectionCancelled + case readTimedOut + case noDataReceived + case unknown(Error) + + var errorDescription: String? { + switch self { + case .connectionNil: + return "Connection is nil." + case .connectionCancelled: + return "Connection was cancelled." + case .readTimedOut: + return "Read timed out." + case .noDataReceived: + return "No data received." + case .unknown(let error): + return error.localizedDescription } + } +} + +struct TimeoutError: Error {} + +actor HostManager { + var availableHosts: [(host: String, port: UInt16, useSSL: Bool)] + var hostFailureCounts: [String: Int] = [:] + let maxRetriesPerHost: Int - inputStream = read as InputStream - outputStream = write as OutputStream + init(hosts: [(host: String, port: UInt16, useSSL: Bool)], maxRetriesPerHost: Int) { + self.availableHosts = hosts + self.maxRetriesPerHost = maxRetriesPerHost + } - inputStream?.delegate = self - outputStream?.delegate = self + func getNextHost() -> (host: String, port: UInt16, useSSL: Bool)? { + guard !availableHosts.isEmpty else { + return nil + } + // Rotate the first host to the end + let currentHost = availableHosts.removeFirst() + availableHosts.append(currentHost) + return currentHost + } - inputStream?.schedule(in: .current, forMode: .default) - outputStream?.schedule(in: .current, forMode: .default) + func shouldSkipHost(_ host: String) -> Bool { + if let failureCount = hostFailureCounts[host], failureCount >= maxRetriesPerHost { + return true + } + return false + } - inputStream?.open() - outputStream?.open() + func resetFailureCount(for host: String) { + hostFailureCounts[host] = 0 + } - return true + func incrementFailureCount(for host: String) { + hostFailureCounts[host, default: 0] += 1 + } +} + +class SwiftTCPClient { + private var connection: NWConnection? + private let queue = DispatchQueue(label: "SwiftTCPClientQueue", qos: .userInitiated) + private let readTimeout: TimeInterval = 5.0 + let maxRetries = 3 + private let hostManager: HostManager + + private enum ConnectionState: CustomStringConvertible { + case disconnected + case connecting + case connected(NWConnection.State) + case failed(Error) + case cancelled + + var description: String { + switch self { + case .disconnected: + return "Disconnected" + case .connecting: + return "Connecting" + case .connected(let state): + return "Connected (\(state))" + case .failed(let error): + return "Failed: \(error.localizedDescription)" + case .cancelled: + return "Cancelled" + } + } + } + + private var connectionState: ConnectionState = .disconnected + + // Add a path monitor to detect network changes + private var pathMonitor: NWPathMonitor? + private var currentPath: NWPath? + + init(hosts: [(host: String, port: UInt16, useSSL: Bool)] = [], maxRetriesPerHost: Int = 3) { + self.hostManager = HostManager(hosts: hosts, maxRetriesPerHost: maxRetriesPerHost) + setupPathMonitor() + } + + deinit { + stopPathMonitor() + close() + } + + private func setupPathMonitor() { + pathMonitor = NWPathMonitor() + pathMonitor?.pathUpdateHandler = { [weak self] path in + guard let self = self else { return } + let previousPath = self.currentPath + self.currentPath = path + + if let previousPath = previousPath, previousPath.status != path.status { + print("Network path changed: \(path.status), available interfaces: \(path.availableInterfaces.map { $0.name })") + + // If we're connected and the network changed to unsatisfied, we might need to reconnect + if path.status == .unsatisfied, case .connected = self.connectionState { + print("Network became unavailable, connection may be affected") + } + } + } + pathMonitor?.start(queue: queue) + } + + private func stopPathMonitor() { + pathMonitor?.cancel() + pathMonitor = nil } - func send(data: Data) -> Bool { - guard let outputStream = outputStream else { + func connect(to host: String, port: UInt16, useSSL: Bool = false, validateCertificates: Bool = true, retries: Int = 0) async -> Bool { + // Skip host if it has failed too many times + if await hostManager.shouldSkipHost(host) { + print("Skipping host \(host) after \(hostManager.maxRetriesPerHost) retries.") return false } - let bytesWritten = data.withUnsafeBytes { bufferPointer -> Int in - guard let baseAddress = bufferPointer.baseAddress else { - return 0 - } - return outputStream.write(baseAddress.assumingMemoryBound(to: UInt8.self), maxLength: data.count) + // Reset connection state and close any existing connection + connectionState = .disconnected + close() + + // Check network availability before attempting to connect + if let currentPath = currentPath, currentPath.status == .unsatisfied { + print("Network is currently unavailable, can't connect to \(host):\(port)") + await hostManager.incrementFailureCount(for: host) + return false + } + + let parameters: NWParameters + if useSSL { + parameters = NWParameters(tls: createTLSOptions(validateCertificates: validateCertificates), tcp: .init()) + } else { + parameters = NWParameters.tcp } - return bytesWritten == data.count + parameters.prohibitExpensivePaths = false + parameters.expiredDNSBehavior = .allow + parameters.multipathServiceType = .handover + + let tcpOptions = parameters.defaultProtocolStack.internetProtocol as? NWProtocolTCP.Options + tcpOptions?.enableFastOpen = false + tcpOptions?.noDelay = true + + tcpOptions?.enableKeepalive = true + tcpOptions?.keepaliveCount = 5 + tcpOptions?.keepaliveIdle = 60 + tcpOptions?.keepaliveInterval = 5 + + tcpOptions?.connectionTimeout = 10 + + guard let nwPort = NWEndpoint.Port(rawValue: port) else { + print("Invalid port number: \(port)") + return false + } + + let isLocalhost = host == "localhost" || host == "127.0.0.1" || host == "::1" + if isLocalhost { + print("Connecting to localhost, checking if service is available on port \(port)...") + } + + connectionState = .connecting + let endpoint = NWEndpoint.Host(host) + connection = NWConnection(host: endpoint, port: nwPort, using: parameters) + connection?.start(queue: queue) + + print("Attempting to connect to \(host):\(port) (SSL: \(useSSL))") + + guard let connection = connection else { + print("Connection object creation failed") + connectionState = .failed(SwiftTCPClientError.connectionNil) + return false + } + + do { + try await withCheckedThrowingContinuation { [self] (continuation: CheckedContinuation) in + let syncQueue = DispatchQueue(label: "com.bluewallet.continuationSync") + var isContinuationResolved = false + + // Safe completion function to avoid multiple resolutions + let completeOnce: (Result) -> Void = { result in + syncQueue.sync { + if !isContinuationResolved { + isContinuationResolved = true + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + connection.stateUpdateHandler = { state in + switch state { + case .ready: + print("Successfully connected to \(host):\(port)") + + if let localEndpointDesc = connection.currentPath?.localEndpoint?.debugDescription { + print("Local endpoint: \(localEndpointDesc)") + } + if let remoteEndpointDesc = connection.currentPath?.remoteEndpoint?.debugDescription { + print("Remote endpoint: \(remoteEndpointDesc)") + } + + self.connectionState = .connected(state) + completeOnce(.success(())) + + case .failed(let error): + let nsError = error as NSError + print("Connection to \(host):\(port) failed with error: \(error.localizedDescription) (Code: \(nsError.code))") + + if self.isTLSError(error) { + print("SSL Error while connecting to \(host):\(port) - \(error.localizedDescription)") + } else if nsError.code == 61 { + print("Connection refused by \(host):\(port) - Server may not be listening on this port") + if isLocalhost { + print("For localhost connections, ensure the server is running and listening on port \(port)") + } + } else if nsError.code == 60 { + print("Operation timed out connecting to \(host):\(port) - Server might be unreachable") + } else if nsError.code == 65 { + print("No route to host \(host):\(port) - Network route unavailable") + } + + self.connectionState = .failed(error) + completeOnce(.failure(SwiftTCPClientError.unknown(error))) + + case .cancelled: + print("Connection to \(host):\(port) was cancelled.") + self.connectionState = .cancelled + completeOnce(.failure(SwiftTCPClientError.connectionCancelled)) + + case .preparing: + print("Preparing connection to \(host):\(port)...") + + case .waiting(let error): + print("Waiting to connect to \(host):\(port) - \(error.localizedDescription)") + + case .setup: + print("Setting up connection to \(host):\(port)...") + + @unknown default: + print("Unknown connection state for \(host):\(port)") + } + } + + let timeoutWorkItem = DispatchWorkItem { [weak self] in + guard let self = self else { return } + + syncQueue.sync { + if !isContinuationResolved { + self.connectionState = .failed(SwiftTCPClientError.readTimedOut) + print("Connection to \(host):\(port) timed out after \(self.readTimeout) seconds") + completeOnce(.failure(SwiftTCPClientError.readTimedOut)) + } + } + } + + self.queue.asyncAfter(deadline: .now() + self.readTimeout, execute: timeoutWorkItem) + } + + await hostManager.resetFailureCount(for: host) + return true + + } catch { + print("Connection to \(host) failed with error: \(error.localizedDescription)") + await hostManager.incrementFailureCount(for: host) + + if let nsError = error as NSError?, nsError.code == 61 { + print("Connection refused by \(host):\(port) - skipping retries as server is not listening") + return false + } + + if retries < maxRetries - 1 { + print("Retrying connection to \(host) (\(retries + 1)/\(maxRetries))...") + try? await Task.sleep(nanoseconds: 500_000_000) // 500ms delay + return await connect(to: host, port: port, useSSL: useSSL, validateCertificates: validateCertificates, retries: retries + 1) + } else { + print("Host \(host) failed after \(maxRetries) retries. Skipping.") + return false + } + } } - func receive() -> Data? { - let data = NSMutableData() - return data as Data + func connectToNextAvailable(validateCertificates: Bool = true) async -> Bool { + while true { + guard let currentHost = await hostManager.getNextHost() else { + print("No available hosts to connect.") + return false + } + + if await hostManager.shouldSkipHost(currentHost.host) { + print("Skipping host \(currentHost.host) after \(hostManager.maxRetriesPerHost) retries.") + continue + } + + print("Attempting to connect to next available host: \(currentHost.host):\(currentHost.port) (SSL: \(currentHost.useSSL))") + + if await connect(to: currentHost.host, port: currentHost.port, useSSL: currentHost.useSSL, validateCertificates: validateCertificates) { + print("Connected to host \(currentHost.host):\(currentHost.port)") + await hostManager.resetFailureCount(for: currentHost.host) + return true + } else { + print("Failed to connect to host \(currentHost.host):\(currentHost.port)") + await hostManager.incrementFailureCount(for: currentHost.host) + } + } } - func close() { - inputStream?.close() - outputStream?.close() - inputStream?.remove(from: .current, forMode: .default) - outputStream?.remove(from: .current, forMode: .default) - inputStream = nil - outputStream = nil + func isConnectionReady() -> Bool { + guard connection != nil else { + return false + } + + if case .connected(let state) = connectionState, state == .ready { + return true + } + return false } -} -extension SwiftTCPClient: StreamDelegate { - func stream(_ aStream: Stream, handle eventCode: Stream.Event) { - switch eventCode { - case .hasBytesAvailable: - if let inputStream = aStream as? InputStream { - let buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) - defer { - buffer.deallocate() + func send(data: Data) async -> Bool { + if !isConnectionReady() { + print("Send failed: Connection is not ready. Current state: \(connectionState)") + return false + } + + guard let connection = connection else { + print("Send failed: No active connection.") + return false + } + + guard let _ = connection.currentPath?.remoteEndpoint else { + print("Send failed: No remote endpoint available") + return false + } + + do { + print("Sending data (\(data.count) bytes)") + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + let connectionCopy = connection + + let workItem = DispatchWorkItem { + connectionCopy.send(content: data, completion: .contentProcessed { error in + if let error = error { + print("Send error: \(error.localizedDescription)") + continuation.resume(throwing: error) + } else { + print("Data sent successfully") + continuation.resume() + } + }) } + + self.queue.async(execute: workItem) + } + return true + } catch { + print("Send failed with error: \(error.localizedDescription)") + + // Update connection state if we detect it's failed + if let nsError = error as NSError?, nsError.code == 57 || nsError.code == 54 { + connectionState = .failed(error) + print("Connection appears to be closed or reset") + } + + return false + } + } - while inputStream.hasBytesAvailable { - let bytesRead = inputStream.read(buffer, maxLength: bufferSize) - if bytesRead > 0 { - let data = Data(bytes: buffer, count: bytesRead) - receiveCompletion?(.success(data)) + func receive() async throws -> Data { + guard case .connected = connectionState else { + print("Receive failed: Connection is not in connected state. Current state: \(connectionState)") + throw SwiftTCPClientError.connectionNil + } + + guard let connection = connection else { + throw SwiftTCPClientError.connectionNil + } + + print("Attempting to receive data...") + + // Extract the timeout value to avoid capturing self in the task closures + let timeout = self.readTimeout + let closeConnection = { [weak self] in self?.close() } + + return try await withThrowingTaskGroup(of: Data.self) { group in + // Create a task for receiving data + group.addTask { @Sendable in + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + // We'll use a dedicated flag with a lock for thread safety + let syncQueue = DispatchQueue(label: "com.bluewallet.receiveSync") + var isCompleted = false + + connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, _, isComplete, error in + syncQueue.sync { + guard !isCompleted else { return } + isCompleted = true + + if let error = error { + let nsError = error as NSError + print("Receive error: \(error.localizedDescription) (Code: \(nsError.code))") + continuation.resume(throwing: SwiftTCPClientError.unknown(error)) + return + } + + if let data = data, !data.isEmpty { + print("Received data: \(data)") + continuation.resume(returning: data) + } else if isComplete { + print("Connection closed by peer.") + closeConnection() + continuation.resume(throwing: SwiftTCPClientError.noDataReceived) + } else { + print("Read timed out.") + continuation.resume(throwing: SwiftTCPClientError.readTimedOut) + } + } } } } - case .hasSpaceAvailable, .openCompleted, .endEncountered, .errorOccurred: - break - default: - break + + group.addTask { @Sendable in + try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000)) + print("Receive operation timed out after \(timeout) seconds.") + throw SwiftTCPClientError.readTimedOut + } + + if let firstResult = try await group.next() { + group.cancelAll() + print("Receive operation completed successfully.") + return firstResult + } else { + print("Receive operation timed out.") + throw SwiftTCPClientError.readTimedOut + } + } + } + + func close() { + print("Closing connection") + connection?.stateUpdateHandler = nil + connectionState = .disconnected + connection?.cancel() + connection = nil + } + + private func createTLSOptions(validateCertificates: Bool = true) -> NWProtocolTLS.Options { + let tlsOptions = NWProtocolTLS.Options() + if (!validateCertificates) { + sec_protocol_options_set_verify_block(tlsOptions.securityProtocolOptions, { _, _, completion in + completion(true) + }, DispatchQueue.global()) + print("SSL certificate validation is disabled.") + } + return tlsOptions + } + + private func isTLSError(_ error: NWError) -> Bool { + let nsError = error as NSError + let code = nsError.code + if #available(iOS 16.4, *) { + switch code { + case 20, 21, 22: + return true + case 1, 2, 3, 4: + return false + default: + return false + } + } else { + switch code { + case 20, 21, 22: + return true + default: + return false + } + } + } + + private func withTimeout(seconds: TimeInterval, operation: @escaping @Sendable () async throws -> T) async throws -> T { + // Extract the value to avoid capturing self + let timeoutSeconds = seconds + + return try await withThrowingTaskGroup(of: T.self) { group in + // Add the main operation task + group.addTask { @Sendable in + return try await operation() + } + + // Add the timeout task + group.addTask { @Sendable in + try await Task.sleep(nanoseconds: UInt64(timeoutSeconds * 1_000_000_000)) + throw TimeoutError() + } + + let result = try await group.next()! + group.cancelAll() + return result } } } diff --git a/ios/Widgets/Shared/UserDefaultsExtension.swift b/ios/Widgets/Shared/UserDefaultsExtension.swift deleted file mode 100644 index d08423adc59..00000000000 --- a/ios/Widgets/Shared/UserDefaultsExtension.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// UserDefaultsExtension.swift -// BlueWallet -// -// Created by Marcos Rodriguez on 2/8/21. -// Copyright © 2021 BlueWallet. All rights reserved. -// - -import Foundation - -extension UserDefaults { - -func codable(forKey key: String) -> Element? { - guard let data = UserDefaults.standard.data(forKey: key) else { return nil } - let element = try? PropertyListDecoder().decode(Element.self, from: data) - return element - } -} diff --git a/ios/Widgets/Shared/UserDefaultsGroup.swift b/ios/Widgets/Shared/UserDefaultsGroup.swift deleted file mode 100644 index 79736f48154..00000000000 --- a/ios/Widgets/Shared/UserDefaultsGroup.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// UserDefaultsGroup.swift -// MarketWidgetExtension -// -// Created by Marcos Rodriguez on 10/31/20. -// Copyright © 2020 BlueWallet. All rights reserved. -// - -import Foundation - -struct UserDefaultsElectrumSettings { - let host: String? - let port: Int32? - let sslPort: Int32? -} - -let DefaultElectrumPeers = [UserDefaultsElectrumSettings(host: "electrum1.bluewallet.io", port: 50001, sslPort: 443), - UserDefaultsElectrumSettings(host: "electrum2.bluewallet.io", port: 50001, sslPort: 443), - UserDefaultsElectrumSettings(host: "electrum3.bluewallet.io", port: 50001, sslPort: 443)] - -class UserDefaultsGroup { - static private let suite = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue) - - static func getElectrumSettings() -> UserDefaultsElectrumSettings { - guard let electrumSettingsHost = suite?.string(forKey: UserDefaultsGroupKey.ElectrumSettingsHost.rawValue) else { - return UserDefaultsElectrumSettings(host: "electrum1.bluewallet.io", port: 50001, sslPort: 443) - } - - let electrumSettingsTCPPort = suite?.string(forKey: UserDefaultsGroupKey.ElectrumSettingsTCPPort.rawValue) ?? "50001" - let electrumSettingsSSLPort = suite?.string(forKey: UserDefaultsGroupKey.ElectrumSettingsSSLPort.rawValue) ?? "443" - - let host = electrumSettingsHost - let sslPort = Int32(electrumSettingsSSLPort) - let port = Int32(electrumSettingsTCPPort) - - return UserDefaultsElectrumSettings(host: host, port: port, sslPort: sslPort) - } - - static func getAllWalletsBalance() -> Double { - guard let allWalletsBalance = suite?.string(forKey: UserDefaultsGroupKey.AllWalletsBalance.rawValue) else { - return 0 - } - - return Double(allWalletsBalance) ?? 0 - } - - // Int: EPOCH value, Bool: Latest transaction is unconfirmed - static func getAllWalletsLatestTransactionTime() -> LatestTransaction { - guard let allWalletsTransactionTime = suite?.string(forKey: UserDefaultsGroupKey.AllWalletsLatestTransactionTime.rawValue) else { - return LatestTransaction(isUnconfirmed: false, epochValue: 0) - } - - if allWalletsTransactionTime == UserDefaultsGroupKey.LatestTransactionIsUnconfirmed.rawValue { - return LatestTransaction(isUnconfirmed: true, epochValue: 0) - } else { - return LatestTransaction(isUnconfirmed: false, epochValue: Int(allWalletsTransactionTime)) - } - } - -} diff --git a/ios/Widgets/Shared/Views/MarketView.swift b/ios/Widgets/Shared/Views/MarketView.swift index ca7f72dbcb8..892a1c5cd7f 100644 --- a/ios/Widgets/Shared/Views/MarketView.swift +++ b/ios/Widgets/Shared/Views/MarketView.swift @@ -28,9 +28,9 @@ struct MarketView: View { Spacer() HStack(alignment: .center, spacing: 0, content: { - Text("Sats/\(WidgetAPI.getUserPreferredCurrency())").bold().lineLimit(1).font(Font.system(size:11, weight: .medium, design: .default)).foregroundColor(.textColor) + Text("Sats/\(Currency.getUserPreferredCurrency())").bold().lineLimit(1).font(Font.system(size:11, weight: .medium, design: .default)).foregroundColor(.textColor) Spacer() - Text(marketData.sats == "..." ? "..." : marketData.sats).padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4)).lineLimit(1).minimumScaleFactor(0.1).foregroundColor(.widgetBackground).font(Font.system(size:11, weight: .semibold, design: .default)).background(Color(red: 0.97, green: 0.21, blue: 0.38)).overlay( + Text( marketData.sats).padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4)).lineLimit(1).minimumScaleFactor(0.1).foregroundColor(.widgetBackground).font(Font.system(size:11, weight: .semibold, design: .default)).background(Color(red: 0.97, green: 0.21, blue: 0.38)).overlay( RoundedRectangle(cornerRadius: 4.0) .stroke(Color.containerRed, lineWidth: 4.0)) }) @@ -38,7 +38,7 @@ struct MarketView: View { HStack(alignment: .center, spacing: 0, content: { Text("Price").bold().lineLimit(1).font(Font.system(size:11, weight: . medium, design: .default)).foregroundColor(.textColor) Spacer() - Text(marketData.price == "..." ? "..." : marketData.price).padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4)).lineLimit(1).minimumScaleFactor(0.1).foregroundColor(.widgetBackground).font(Font.system(size:11, weight: .semibold, design: .default)).background(Color(red: 0.29, green: 0.86, blue: 0.73)).overlay( + Text( marketData.price).padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4)).lineLimit(1).minimumScaleFactor(0.1).foregroundColor(.widgetBackground).font(Font.system(size:11, weight: .semibold, design: .default)).background(Color(red: 0.29, green: 0.86, blue: 0.73)).overlay( RoundedRectangle(cornerRadius:4.0) .stroke(Color.containerGreen, lineWidth: 4.0)) }) diff --git a/ios/Widgets/Shared/Views/PriceView.swift b/ios/Widgets/Shared/Views/PriceView.swift index 4fc39c18922..6ea556f3a50 100644 --- a/ios/Widgets/Shared/Views/PriceView.swift +++ b/ios/Widgets/Shared/Views/PriceView.swift @@ -9,40 +9,182 @@ import SwiftUI import WidgetKit +@available(iOS 16.0, *) struct PriceView: View { - - var currentMarketData: MarketData? = emptyMarketData - var previousMarketData: MarketData? = emptyMarketData + var entry: PriceWidgetEntry var body: some View { - VStack(alignment: .trailing, spacing: /*@START_MENU_TOKEN@*/nil/*@END_MENU_TOKEN@*/, content: { - Text("Last Updated").font(Font.system(size: 11, weight: .regular, design: .default)).foregroundColor(.textColorLightGray) - HStack(alignment: .lastTextBaseline, spacing: /*@START_MENU_TOKEN@*/nil/*@END_MENU_TOKEN@*/, content: { - Text(currentMarketData?.formattedDate ?? "").lineLimit(1).foregroundColor(.textColor).font(Font.system(size:13, weight: .regular, design: .default)).minimumScaleFactor(0.01).transition(.opacity) + switch entry.family { + case .accessoryInline, .accessoryCircular, .accessoryRectangular: + if #available(iOSApplicationExtension 16.0, *) { + wrappedView(for: getView(for: entry.family), family: entry.family) + } else { + getView(for: entry.family) + } + default: + defaultView.background(Color(UIColor.systemBackground)) + } + } + + private func getView(for family: WidgetFamily) -> some View { + switch family { + case .accessoryCircular: + return AnyView(accessoryCircularView) + case .accessoryInline: + return AnyView(accessoryInlineView) + case .accessoryRectangular: + return AnyView(accessoryRectangularView) + default: + return AnyView(defaultView) + } + } + + @ViewBuilder + private func wrappedView(for content: Content, family: WidgetFamily) -> some View { + if #available(iOSApplicationExtension 16.0, *) { + ZStack { + if family == .accessoryRectangular { + AccessoryWidgetBackground() + .background(Color(UIColor.systemBackground)) + .clipShape(RoundedRectangle(cornerRadius: 10)) + } else { + AccessoryWidgetBackground() + } + content + } + } else { + content + } + } + + private var accessoryCircularView: some View { + let priceString = formattedPriceString(from: entry.currentMarketData?.rate) + let priceChangePercentage = formattedPriceChangePercentage(currentRate: entry.currentMarketData?.rate, previousRate: entry.previousMarketData?.rate) + + return VStack(alignment: .center, spacing: 4) { + Text("BTC") + .font(.caption) + .minimumScaleFactor(0.1) + Text(priceString) + .font(.body) + .minimumScaleFactor(0.1) + .lineLimit(1) + if let priceChangePercentage = priceChangePercentage { + Text(priceChangePercentage) + .font(.caption2) + .foregroundColor(priceChangePercentage.contains("-") ? .red : .green) + } + } + .widgetURL(URL(string: "bluewallet://marketprice")) + } + + private var accessoryInlineView: some View { + let priceString = formattedCurrencyString(from: entry.currentMarketData?.rate) + let priceChangePercentage = formattedPriceChangePercentage(currentRate: entry.currentMarketData?.rate, previousRate: entry.previousMarketData?.rate) + + return HStack { + Text(priceString) + .font(.body) + .minimumScaleFactor(0.1) + if let priceChangePercentage = priceChangePercentage { + Image(systemName: priceChangePercentage.contains("-") ? "arrow.down" : "arrow.up") + .foregroundColor(priceChangePercentage.contains("-") ? .red : .green) + } + } + } + + private var accessoryRectangularView: some View { + let currentPrice = formattedCurrencyString(from: entry.currentMarketData?.rate) + + return VStack(alignment: .leading, spacing: 4) { + Text("Bitcoin (\(Currency.getUserPreferredCurrency()))") + .font(.caption) + .foregroundColor(.secondary) + HStack { + Text(currentPrice) + .font(.caption) + .fontWeight(.bold) + if let currentMarketDataRate = entry.currentMarketData?.rate, + let previousMarketDataRate = entry.previousMarketData?.rate, + currentMarketDataRate != previousMarketDataRate { + Image(systemName: currentMarketDataRate > previousMarketDataRate ? "arrow.up" : "arrow.down") + } + } + + if let previousMarketDataPrice = entry.previousMarketData?.price, Int(entry.currentMarketData?.rate ?? 0) != Int(entry.previousMarketData?.rate ?? 0) { + Text("From \(previousMarketDataPrice)") + .font(.caption) + .foregroundColor(.secondary) + } + + Text("at \(entry.currentMarketData?.formattedDate ?? "--")") + .font(.caption2) + .foregroundColor(.secondary) + } + .padding(.all, 8) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color(UIColor.systemBackground)) + .clipShape(RoundedRectangle(cornerRadius: 10)) + } + + private var defaultView: some View { + VStack(alignment: .trailing, spacing: nil, content: { + Text("Last Updated").font(Font.system(size: 11, weight: .regular)).foregroundColor(Color(UIColor.lightGray)) + HStack(alignment: .lastTextBaseline, spacing: nil, content: { + Text(entry.currentMarketData?.formattedDate ?? "").lineLimit(1).foregroundColor(.primary).font(Font.system(size: 13, weight: .regular)).minimumScaleFactor(0.01).transition(.opacity) }) Spacer() VStack(alignment: .trailing, spacing: 16, content: { - HStack(alignment: .lastTextBaseline, spacing: /*@START_MENU_TOKEN@*/nil/*@END_MENU_TOKEN@*/, content: { - Text(currentMarketData?.price ?? "").lineLimit(1).foregroundColor(.textColor).font(Font.system(size:28, weight: .bold, design: .default)).minimumScaleFactor(0.01).transition(.opacity) + HStack(alignment: .lastTextBaseline, spacing: nil, content: { + Text(entry.currentMarketData?.price ?? "").lineLimit(1).foregroundColor(.primary).font(Font.system(size: 28, weight: .bold)).minimumScaleFactor(0.01).transition(.opacity) }) - if let previousMarketDataPrice = previousMarketData?.price, let currentMarketDataRate = currentMarketData?.rate, let previousMarketDataRate = previousMarketData?.rate, previousMarketDataRate > 0, currentMarketDataRate != previousMarketDataRate { - HStack(alignment: .lastTextBaseline, spacing: /*@START_MENU_TOKEN@*/nil/*@END_MENU_TOKEN@*/, content: { - Image(systemName: currentMarketDataRate > previousMarketDataRate ? "arrow.up" : "arrow.down") - Text("from").lineLimit(1).foregroundColor(.textColor).font(Font.system(size:13, weight: .regular, design: .default)).minimumScaleFactor(0.01) - Text(previousMarketDataPrice).lineLimit(1).foregroundColor(.textColor).font(Font.system(size:13, weight: .regular, design: .default)).minimumScaleFactor(0.01) + if let previousMarketDataPrice = entry.previousMarketData?.price, let currentMarketDataRate = entry.currentMarketData?.rate, let previousMarketDataRate = entry.previousMarketData?.rate, previousMarketDataRate > 0, currentMarketDataRate != previousMarketDataRate { + HStack(alignment: .lastTextBaseline, spacing: nil, content: { + Image(systemName: currentMarketDataRate > previousMarketDataRate ? "arrow.up" : "arrow.down") + Text("from").lineLimit(1).foregroundColor(.primary).font(Font.system(size: 13, weight: .regular)).minimumScaleFactor(0.01) + Text(previousMarketDataPrice).lineLimit(1).foregroundColor(.primary).font(Font.system(size: 13, weight: .regular)).minimumScaleFactor(0.01) }).transition(.slide) } }) - }).frame(minWidth: 0, - maxWidth: .infinity, - minHeight: 0, - maxHeight: .infinity, - alignment: .trailing) + }).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .trailing).padding() + } + + private func formattedPriceString(from rate: Double?) -> String { + let numberFormatter = NumberFormatter() + numberFormatter.numberStyle = .decimal + numberFormatter.maximumFractionDigits = 0 + return numberFormatter.string(from: NSNumber(value: rate ?? 0)) ?? "--" + } + + private func formattedCurrencyString(from rate: Double?) -> String { + let numberFormatter = NumberFormatter() + numberFormatter.maximumFractionDigits = 0 + numberFormatter.numberStyle = .currency + numberFormatter.currencySymbol = fiatUnit(currency: Currency.getUserPreferredCurrency())?.symbol + return numberFormatter.string(from: NSNumber(value: rate ?? 0)) ?? "--" + } + + private func formattedPriceChangePercentage(currentRate: Double?, previousRate: Double?) -> String? { + guard let currentRate = currentRate, let previousRate = previousRate, previousRate > 0 else { return nil } + let change = ((currentRate - previousRate) / previousRate) * 100 + return change == 0 ? nil : String(format: "%+.1f%%", change) } } +@available(iOS 16.0, *) struct PriceView_Previews: PreviewProvider { static var previews: some View { - PriceView().previewContext(WidgetPreviewContext(family: .systemSmall)) + Group { + PriceView(entry: PriceWidgetEntry(date: Date(), family: .systemSmall, currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00"), previousMarketData: emptyMarketData)) + .previewContext(WidgetPreviewContext(family: .systemSmall)).padding() + if #available(iOSApplicationExtension 16.0, *) { + PriceView(entry: PriceWidgetEntry(date: Date(), family: .accessoryCircular, currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00"), previousMarketData: emptyMarketData)) + .previewContext(WidgetPreviewContext(family: .accessoryCircular)) + PriceView(entry: PriceWidgetEntry(date: Date(), family: .accessoryInline, currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00"), previousMarketData: emptyMarketData)) + .previewContext(WidgetPreviewContext(family: .accessoryInline)) + PriceView(entry: PriceWidgetEntry(date: Date(), family: .accessoryRectangular, currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00"), previousMarketData: emptyMarketData)) + .previewContext(WidgetPreviewContext(family: .accessoryRectangular)) + } + } } } diff --git a/ios/Widgets/Shared/Views/WalletInformationView.swift b/ios/Widgets/Shared/Views/WalletInformationView.swift index 24c16f2b984..cb00ed0aa96 100644 --- a/ios/Widgets/Shared/Views/WalletInformationView.swift +++ b/ios/Widgets/Shared/Views/WalletInformationView.swift @@ -16,7 +16,7 @@ struct WalletInformationView: View { var formattedBalance: String { let numberFormatter = NumberFormatter() - numberFormatter.locale = Locale(identifier: WidgetAPI.getUserPreferredCurrencyLocale()) + numberFormatter.locale = Locale(identifier: Currency.getUserPreferredCurrencyLocale()) numberFormatter.numberStyle = .currency let amount = numberFormatter.string(from: NSNumber(value: ((allWalletsBalance.balance / 100000000) * marketData.rate))) ?? "" return amount diff --git a/ios/Widgets/Shared/WidgetAPI+Electrum.swift b/ios/Widgets/Shared/WidgetAPI+Electrum.swift deleted file mode 100644 index 88a9091921f..00000000000 --- a/ios/Widgets/Shared/WidgetAPI+Electrum.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// WidgetAPI+Electrum.swift -// BlueWallet -// -// Created by Marcos Rodriguez on 11/8/20. -// Copyright © 2020 BlueWallet. All rights reserved. -// - -import Foundation - -struct APIError: LocalizedError { - var errorDescription: String = "Failed to fetch Electrum data..." -} - -extension WidgetAPI { - - static func fetchNextBlockFee(completion: @escaping ((MarketData?, Error?) -> Void), userElectrumSettings: UserDefaultsElectrumSettings = UserDefaultsGroup.getElectrumSettings()) { - guard let host = userElectrumSettings.host, let _ = userElectrumSettings.sslPort, let port = userElectrumSettings.port else { - print("No valid UserDefaultsElectrumSettings found"); - return - } - DispatchQueue.global(qos: .background).async { - let client = SwiftTCPClient() - client.receiveCompletion = { result in - switch result { - case .success(let data): - print("Received: \(data)") - guard let response = String(bytes: data, encoding: .utf8)?.data(using: .utf8) else { - client.close() - completion(nil, APIError()) - return - } - do { - if let json = try JSONSerialization.jsonObject(with: response, options: .allowFragments) as? [String: AnyObject], let nextBlockResponseDouble = json["result"] as? Double { - print("Successfully obtained response from Electrum sever") - print(userElectrumSettings) - client.close() - let marketData = MarketData(nextBlock: String(format: "%.0f", (nextBlockResponseDouble / 1024) * 100000000), sats: "0", price: "0", rate: 0) - completion(marketData, nil) - } - } catch { - client.close() - completion(nil, APIError()) - } - case .failure(let error): - print("Error: \(error.localizedDescription)") - client.close() - completion(nil, APIError()) - } - } - - if client.connect(to: host, port: UInt32(exactly: port)!) { - let message = "{\"id\": 1, \"method\": \"blockchain.estimatefee\", \"params\": [1]}\n" - if let data = message.data(using: .utf8), client.send(data: data) { - print("Message sent!") - RunLoop.current.run(until: Date(timeIntervalSinceNow: 5)) - } - client.close() - } else { - print("Connection failed") - client.close() - if userElectrumSettings.host == DefaultElectrumPeers.last?.host { - completion(nil, APIError()) - } else if let currentIndex = DefaultElectrumPeers.firstIndex(where: {$0.host == userElectrumSettings.host}) { - fetchNextBlockFee(completion: completion, userElectrumSettings: DefaultElectrumPeers[DefaultElectrumPeers.index(after: currentIndex)]) - } else { - if let first = DefaultElectrumPeers.first { - fetchNextBlockFee(completion: completion, userElectrumSettings: first) - } - } - } - } - } - - static func fetchMarketData(currency: String, completion: @escaping ((MarketData?, Error?) -> Void)) { - var marketDataEntry = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0) - WidgetAPI.fetchPrice(currency: currency, completion: { (result, error) in - if let result = result { - marketDataEntry.rate = result.rateDouble - marketDataEntry.price = result.formattedRate ?? "!" - } - WidgetAPI.fetchNextBlockFee { (marketData, error) in - if let nextBlock = marketData?.nextBlock { - marketDataEntry.nextBlock = nextBlock - } else { - marketDataEntry.nextBlock = "!" - } - if let rateDouble = result?.rateDouble { - marketDataEntry.sats = numberFormatter.string(from: NSNumber(value: Double(10 / rateDouble) * 10000000)) ?? "!" - } - completion(marketDataEntry, nil) - } - }) - } - -} diff --git a/ios/Widgets/Shared/WidgetAPI.swift b/ios/Widgets/Shared/WidgetAPI.swift deleted file mode 100644 index e31cd4576b4..00000000000 --- a/ios/Widgets/Shared/WidgetAPI.swift +++ /dev/null @@ -1,156 +0,0 @@ -// -// WidgetAPI.swift -// TodayExtension -// -// Created by Marcos Rodriguez on 11/2/19. -// Copyright © 2019 Facebook. All rights reserved. -// - -import Foundation - -struct CurrencyError: LocalizedError { - var errorDescription: String = "Failed to parse response" -} - -var numberFormatter: NumberFormatter { - let formatter = NumberFormatter() - formatter.numberStyle = .decimal - formatter.maximumFractionDigits = 0 - formatter.locale = Locale.current - return formatter -} - -class WidgetAPI { - static func fetchPrice(currency: String, completion: @escaping ((WidgetDataStore?, Error?) -> Void)) { - let currencyToFiatUnit = fiatUnit(currency: currency) - guard let source = currencyToFiatUnit?.source, let endPointKey = currencyToFiatUnit?.endPointKey else { return } - - var urlString: String - switch source { - case "Yadio": - urlString = "https://api.yadio.io/json/\(endPointKey)" - case "YadioConvert": - urlString = "https://api.yadio.io/convert/1/BTC/\(endPointKey)" - case "Exir": - urlString = "https://api.exir.io/v1/ticker?symbol=btc-irt" - case "wazirx": - urlString = "https://api.wazirx.com/api/v2/tickers/btcinr" - case "Bitstamp": - urlString = "https://www.bitstamp.net/api/v2/ticker/btc\(endPointKey.lowercased())" - case "CoinGecko": - urlString = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=\(endPointKey.lowercased())" - default: - urlString = "https://api.coindesk.com/v1/bpi/currentprice/\(endPointKey).json" - } - - guard let url = URL(string:urlString) else { return } - - URLSession.shared.dataTask(with: url) { (data, response, error) in - guard let dataResponse = data, - let json = (try? JSONSerialization.jsonObject(with: dataResponse, options: .mutableContainers) as? Dictionary), - error == nil - else { - print(error?.localizedDescription ?? "Response Error") - completion(nil, error) - return - } - - var latestRateDataStore: WidgetDataStore? - switch source { - case "Yadio": - guard let rateDict = json[endPointKey] as? [String: Any], - let rateDouble = rateDict["price"] as? Double, - let lastUpdated = json["timestamp"] as? Int - else { break } - let unix = Double(lastUpdated / 1_000) - let lastUpdatedString = ISO8601DateFormatter().string(from: Date(timeIntervalSince1970: unix)) - latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble) - case "CoinGecko": - guard let rateDict = json["bitcoin"] as? [String: Any], - let rateDouble = rateDict[endPointKey.lowercased()] as? Double - else { break } - let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) - latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble) - case "YadioConvert": - guard let rateDict = json as? [String: Any], - let rateDouble = rateDict["rate"] as? Double, - let lastUpdated = json["timestamp"] as? Int - else { break } - let unix = Double(lastUpdated / 1_000) - let lastUpdatedString = ISO8601DateFormatter().string(from: Date(timeIntervalSince1970: unix)) - latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble) - case "Exir": - guard let rateDouble = json["last"] as? Double else { break } - let rateString = String(rateDouble) - let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) - latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble) - case "Bitstamp": - guard let rateString = json["last"] as? String, let rateDouble = Double(rateString) else { break } - let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) - latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble) - case "wazirx": - guard let tickerDict = json["ticker"] as? [String: Any], - let rateString = tickerDict["buy"] as? String, - let rateDouble = Double(rateString) - else { break } - let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) - latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble) - default: - guard let bpi = json["bpi"] as? Dictionary, - let preferredCurrency = bpi[endPointKey] as? Dictionary, - let rateString = preferredCurrency["rate"] as? String, - let rateDouble = preferredCurrency["rate_float"] as? Double, - let time = json["time"] as? Dictionary, - let lastUpdatedString = time["updatedISO"] as? String - else { break } - latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble) - } - - if (latestRateDataStore == nil) { - completion(nil, CurrencyError()) - return - } - - completion(latestRateDataStore, nil) - }.resume() - } - - static func getUserPreferredCurrency() -> String { - - guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue), - let preferredCurrency = userDefaults.string(forKey: "preferredCurrency") - else { - return "USD" - } - - if preferredCurrency != WidgetAPI.getLastSelectedCurrency() { - UserDefaults.standard.removeObject(forKey: WidgetData.WidgetCachedDataStoreKey) - UserDefaults.standard.removeObject(forKey: WidgetData.WidgetDataStoreKey) - UserDefaults.standard.synchronize() - } - - return preferredCurrency - } - - static func getUserPreferredCurrencyLocale() -> String { - guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue), - let preferredCurrency = userDefaults.string(forKey: "preferredCurrencyLocale") - else { - return "en_US" - } - return preferredCurrency - } - - static func getLastSelectedCurrency() -> String { - guard let dataStore = UserDefaults.standard.string(forKey: "currency") else { - return "USD" - } - - return dataStore - } - - static func saveNewSelectedCurrency() { - UserDefaults.standard.setValue(WidgetAPI.getUserPreferredCurrency(), forKey: "currency") - } - -} diff --git a/ios/Widgets/Shared/WidgetDataStore.swift b/ios/Widgets/Shared/WidgetDataStore.swift deleted file mode 100644 index d5228ab1090..00000000000 --- a/ios/Widgets/Shared/WidgetDataStore.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// Created by Marcos Rodriguez on 11/3/19. -// - -import Foundation - -extension Numeric { - - var abbreviated: String { - let bytecountFormatter = ByteCountFormatter() - bytecountFormatter.zeroPadsFractionDigits = true - bytecountFormatter.countStyle = .decimal - bytecountFormatter.isAdaptive = false - let bytesString = bytecountFormatter.string(fromByteCount: (self as! NSNumber).int64Value) - - let numericString = bytesString - .replacingOccurrences(of: "bytes", with: "") - .replacingOccurrences(of: "B", with: "") // removes B (bytes) in 'KB'/'MB'/'GB' - .replacingOccurrences(of: "G", with: "B") // replace G (Giga) to just B (billions) - return numericString.replacingOccurrences(of: " ", with: "") - } -} - -struct WidgetDataStore: Codable { - let rate: String - let lastUpdate: String - let rateDouble: Double - var formattedRate: String? { - let numberFormatter = NumberFormatter() - numberFormatter.locale = Locale(identifier: WidgetAPI.getUserPreferredCurrencyLocale()) - numberFormatter.numberStyle = .currency - numberFormatter.maximumFractionDigits = 0 - numberFormatter.minimumFractionDigits = 0 - if let rateString = numberFormatter.string(from: NSNumber(value: rateDouble)) { - return rateString - } - return rate - } - var formattedRateForSmallComplication: String? { - return rateDouble.abbreviated - } - - var formattedRateForComplication: String? { - let numberFormatter = NumberFormatter() - numberFormatter.locale = Locale(identifier: WidgetAPI.getUserPreferredCurrencyLocale()) - numberFormatter.numberStyle = .currency - numberFormatter.currencySymbol = "" - if let rateString = numberFormatter.string(from: NSNumber(value: rateDouble)) { - return rateString - } - return rate - } - - var date: Date? { - let isoDateFormatter = ISO8601DateFormatter() - let dateFormatter = DateFormatter() - dateFormatter.locale = Locale.current - dateFormatter.timeStyle = .short - - return isoDateFormatter.date(from: lastUpdate) - } - var formattedDate: String? { - let isoDateFormatter = ISO8601DateFormatter() - let dateFormatter = DateFormatter() - dateFormatter.locale = Locale.current - dateFormatter.timeStyle = .short - - if let date = isoDateFormatter.date(from: lastUpdate) { - return dateFormatter.string(from: date) - } - return nil - } -} - -class WidgetData { - - static let WidgetDataStoreKey = "WidgetDataStoreKey" - static let WidgetCachedDataStoreKey = "WidgetCachedDataStoreKey" - - static func savePriceRateAndLastUpdate(rate: String, lastUpdate: String) { - UserDefaults.standard.setValue(["rate": rate, "lastUpdate": lastUpdate], forKey: WidgetDataStoreKey) - UserDefaults.standard.synchronize() - } - -} diff --git a/ios/Widgets/WalletAppShortcuts.swift b/ios/Widgets/WalletAppShortcuts.swift new file mode 100644 index 00000000000..0480df05928 --- /dev/null +++ b/ios/Widgets/WalletAppShortcuts.swift @@ -0,0 +1,27 @@ +// +// WalletAppShortcuts.swift +// BlueWallet + + +import AppIntents + +@available(iOS 16.4, *) +struct WalletAppShortcuts: AppShortcutsProvider { + + @AppShortcutsBuilder + static var appShortcuts: [AppShortcut] { + AppShortcut( + intent: PriceIntent(), + phrases: [ + AppShortcutPhrase("Market rate for Bitcoin in \(\.$fiatCurrency) using ${applicationName}"), + AppShortcutPhrase("Get the current Bitcoin market rate in \(\.$fiatCurrency) with ${applicationName}"), + AppShortcutPhrase("What's the current Bitcoin rate in \(\.$fiatCurrency) using ${applicationName}?"), + AppShortcutPhrase("Show me the current Bitcoin price in \(\.$fiatCurrency) via ${applicationName}"), + AppShortcutPhrase("Retrieve Bitcoin rate in \(\.$fiatCurrency) from ${applicationName}") + ], + shortTitle: "Market Rate", + systemImageName: "bitcoinsign.circle" + ) + + } +} diff --git a/ios/Widgets/WalletInformationAndMarketWidget/WalletInformationAndMarketWidget.swift b/ios/Widgets/WalletInformationAndMarketWidget/WalletInformationAndMarketWidget.swift index 1baf51106f2..19142506aff 100644 --- a/ios/Widgets/WalletInformationAndMarketWidget/WalletInformationAndMarketWidget.swift +++ b/ios/Widgets/WalletInformationAndMarketWidget/WalletInformationAndMarketWidget.swift @@ -10,113 +10,160 @@ import WidgetKit import SwiftUI struct WalletInformationAndMarketWidgetProvider: TimelineProvider { - typealias Entry = WalletInformationAndMarketWidgetEntry - func placeholder(in context: Context) -> WalletInformationAndMarketWidgetEntry { - return WalletInformationAndMarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000))) - } - - func getSnapshot(in context: Context, completion: @escaping (WalletInformationAndMarketWidgetEntry) -> ()) { - let entry: WalletInformationAndMarketWidgetEntry - if (context.isPreview) { - entry = WalletInformationAndMarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000))) - } else { - entry = WalletInformationAndMarketWidgetEntry(date: Date(), marketData: emptyMarketData) + typealias Entry = WalletInformationAndMarketWidgetEntry + + actor LastSuccessfulEntryStore { + private var lastSuccessfulEntry: WalletInformationAndMarketWidgetEntry? + + func getLastSuccessfulEntry() -> WalletInformationAndMarketWidgetEntry? { + return lastSuccessfulEntry + } + + func setLastSuccessfulEntry(_ entry: WalletInformationAndMarketWidgetEntry) { + lastSuccessfulEntry = entry + } + } + + let entryStore = LastSuccessfulEntryStore() + + func placeholder(in context: Context) -> WalletInformationAndMarketWidgetEntry { + return WalletInformationAndMarketWidgetEntry.placeholder } - completion(entry) - } - - func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { - var entries: [WalletInformationAndMarketWidgetEntry] = [] - if (context.isPreview) { - let entry = WalletInformationAndMarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000))) - entries.append(entry) - let timeline = Timeline(entries: entries, policy: .atEnd) - completion(timeline) - } else { - let userPreferredCurrency = WidgetAPI.getUserPreferredCurrency(); - let allwalletsBalance = WalletData(balance: UserDefaultsGroup.getAllWalletsBalance(), latestTransactionTime: UserDefaultsGroup.getAllWalletsLatestTransactionTime()) - let marketDataEntry = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0) - WidgetAPI.fetchMarketData(currency: userPreferredCurrency, completion: { (result, error) in + + func getSnapshot(in context: Context, completion: @escaping (WalletInformationAndMarketWidgetEntry) -> ()) { let entry: WalletInformationAndMarketWidgetEntry - if let result = result { - entry = WalletInformationAndMarketWidgetEntry(date: Date(), marketData: result, allWalletsBalance: allwalletsBalance) - + if (context.isPreview) { + entry = WalletInformationAndMarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000))) } else { - entry = WalletInformationAndMarketWidgetEntry(date: Date(), marketData: marketDataEntry, allWalletsBalance: allwalletsBalance) + entry = WalletInformationAndMarketWidgetEntry(date: Date(), marketData: emptyMarketData) } - entries.append(entry) - let timeline = Timeline(entries: entries, policy: .atEnd) - completion(timeline) - }) + completion(entry) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { + var entries: [WalletInformationAndMarketWidgetEntry] = [] + if (context.isPreview) { + let entry = WalletInformationAndMarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000))) + entries.append(entry) + let timeline = Timeline(entries: entries, policy: .atEnd) + completion(timeline) + } else { + let userPreferredCurrency = Currency.getUserPreferredCurrency() + let allWalletsBalance = WalletData(balance: UserDefaultsGroup.getAllWalletsBalance(), latestTransactionTime: UserDefaultsGroup.getAllWalletsLatestTransactionTime()) + + fetchMarketDataWithRetry(currency: userPreferredCurrency, retries: 3) { marketData in + let entry = WalletInformationAndMarketWidgetEntry(date: Date(), marketData: marketData, allWalletsBalance: allWalletsBalance) + Task { + await entryStore.setLastSuccessfulEntry(entry) + entries.append(entry) + let timeline = Timeline(entries: entries, policy: .atEnd) + completion(timeline) + } + } + } + } + + private func fetchMarketDataWithRetry(currency: String, retries: Int, completion: @escaping (MarketData) -> ()) { + var attempt = 0 + + func attemptFetch() { + attempt += 1 + print("Attempt \(attempt) to fetch market data.") + + MarketAPI.fetchMarketData(currency: currency) { result in + switch result { + case .success(let marketData): + print("Successfully fetched market data on attempt \(attempt).") + completion(marketData) + case .failure(let error): + print("Error fetching market data: \(error.localizedDescription). Retry \(attempt)/\(retries)") + if attempt < retries { + DispatchQueue.global().asyncAfter(deadline: .now() + 2) { + attemptFetch() + } + } else { + print("Max retries reached.") + Task { + if let lastEntry = await entryStore.getLastSuccessfulEntry() { + completion(lastEntry.marketData) + } else { + completion(WalletInformationAndMarketWidgetEntry.placeholder.marketData) + } + } + } + } + } + } + + attemptFetch() } - } } struct WalletInformationAndMarketWidgetEntry: TimelineEntry { - let date: Date - let marketData: MarketData - var allWalletsBalance: WalletData = WalletData(balance: 0) + let date: Date + let marketData: MarketData + var allWalletsBalance: WalletData = WalletData(balance: 0) + static var placeholder = WalletInformationAndMarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0), allWalletsBalance: WalletData(balance: 0, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000))) } -struct WalletInformationAndMarketWidgetEntryView : View { - @Environment(\.widgetFamily) var family - let entry: WalletInformationAndMarketWidgetEntry - - - var WalletBalance: some View { - WalletInformationView(allWalletsBalance: entry.allWalletsBalance, marketData: entry.marketData).background(Color.widgetBackground) - } - - var MarketStack: some View { - MarketView(marketData: entry.marketData) - } - - var SendReceiveButtonsView: some View { - SendReceiveButtons().padding(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/, /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/) - } - - var body: some View { - if family == .systemLarge { - HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: /*@START_MENU_TOKEN@*/nil/*@END_MENU_TOKEN@*/, content: { - VStack(alignment: .leading, spacing: nil, content: { - HStack(content: { - WalletBalance.padding() - }).background(Color.widgetBackground) +struct WalletInformationAndMarketWidgetEntryView: View { + @Environment(\.widgetFamily) var family + let entry: WalletInformationAndMarketWidgetEntry + + var WalletBalance: some View { + WalletInformationView(allWalletsBalance: entry.allWalletsBalance, marketData: entry.marketData).background(Color.widgetBackground) + } + + var MarketStack: some View { + MarketView(marketData: entry.marketData) + } + + var SendReceiveButtonsView: some View { + SendReceiveButtons().padding(.all, 10) + } + + var body: some View { + if family == .systemLarge { + HStack(alignment: .center, spacing: nil, content: { + VStack(alignment: .leading, spacing: nil, content: { + HStack(content: { + WalletBalance.padding() + }).background(Color.widgetBackground) + HStack(content: { + MarketStack + }).padding() + SendReceiveButtonsView + }).background(Color(.lightGray).opacity(0.77)) + }) + } else { + HStack(content: { + WalletBalance.padding() HStack(content: { - MarketStack }).padding() - SendReceiveButtonsView }).background(Color(.lightGray).opacity(0.77)) - - }) - - } else { - HStack(content: { - WalletBalance.padding() - HStack(content: { - MarketStack.padding() - }).background(Color(.lightGray).opacity(0.77)) - }).background(Color.widgetBackground) - + MarketStack.padding() + }).background(Color(.lightGray).opacity(0.77)) + }).background(Color.widgetBackground) + } } - } } struct WalletInformationAndMarketWidget: Widget { - let kind: String = "WalletInformationAndMarketWidget" - - var body: some WidgetConfiguration { - StaticConfiguration(kind: kind, provider: WalletInformationAndMarketWidgetProvider()) { entry in - WalletInformationAndMarketWidgetEntryView(entry: entry) + let kind: String = "WalletInformationAndMarketWidget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: WalletInformationAndMarketWidgetProvider()) { entry in + WalletInformationAndMarketWidgetEntryView(entry: entry) + } + .configurationDisplayName("Wallet and Market") + .description("View your total wallet balance and network prices.").supportedFamilies([.systemMedium, .systemLarge]) + .contentMarginsDisabledIfAvailable() } - .configurationDisplayName("Wallet and Market") - .description("View your total wallet balance and network prices.").supportedFamilies([.systemMedium, .systemLarge]) - } } struct WalletInformationAndMarketWidget_Previews: PreviewProvider { - static var previews: some View { - WalletInformationAndMarketWidgetEntryView(entry: WalletInformationAndMarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 0), allWalletsBalance: WalletData(balance: 10000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000)))) - .previewContext(WidgetPreviewContext(family: .systemMedium)) - WalletInformationAndMarketWidgetEntryView(entry: WalletInformationAndMarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 0), allWalletsBalance: WalletData(balance: 10000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000)))) - .previewContext(WidgetPreviewContext(family: .systemLarge)) - } + static var previews: some View { + WalletInformationAndMarketWidgetEntryView(entry: WalletInformationAndMarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 0), allWalletsBalance: WalletData(balance: 10000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000)))) + .previewContext(WidgetPreviewContext(family: .systemMedium)) + WalletInformationAndMarketWidgetEntryView(entry: WalletInformationAndMarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 0), allWalletsBalance: WalletData(balance: 10000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000)))) + .previewContext(WidgetPreviewContext(family: .systemLarge)) + } } diff --git a/ios/ci_scripts/ci_post_clone.sh b/ios/ci_scripts/ci_post_clone.sh new file mode 100755 index 00000000000..3903c152494 --- /dev/null +++ b/ios/ci_scripts/ci_post_clone.sh @@ -0,0 +1,36 @@ +#!/bin/zsh + +echo "===== Installing CocoaPods =====" +export HOMEBREW_NO_INSTALL_CLEANUP=TRUE +brew install cocoapods +echo "CocoaPods installation complete." + +echo "===== Installing Node.js =====" +brew install node@20 +echo "Node.js installation complete." + +# Configure environment to use node@20 +echo "Configuring environment to use node@20..." +echo 'export PATH="/usr/local/opt/node@20/bin:$PATH"' >> ~/.zshrc +export PATH="/usr/local/opt/node@20/bin:$PATH" + +echo 'export LDFLAGS="-L/usr/local/opt/node@20/lib"' >> ~/.zshrc +export LDFLAGS="-L/usr/local/opt/node@20/lib" + +echo 'export CPPFLAGS="-I/usr/local/opt/node@20/include"' >> ~/.zshrc +export CPPFLAGS="-I/usr/local/opt/node@20/include" +echo "Configuration complete." + +# Install dependencies using npm +echo "===== Running npm ci =====" +npm ci | tee npm-ci-log.txt +npm prune --production | tee npm-prune-log.txt +echo "npm ci complete. Full log output in npm-ci-log.txt and npm-prune-log.txt" + +echo "===== Running pod install =====" +cd ios +pod install | tee pod-install-log.txt +echo "pod install complete. Full log output in pod-install-log.txt" +cd .. + +echo "===== Installation and Setup Complete =====" diff --git a/ios/export_options.plist b/ios/export_options.plist new file mode 100644 index 00000000000..99d4b96c67f --- /dev/null +++ b/ios/export_options.plist @@ -0,0 +1,33 @@ + + + + + method + app-store + signingStyle + manual + teamID + A7W54YZ4WU + uploadSymbols + + compileBitcode + + thinning + none + destination + export + provisioningProfiles + + io.bluewallet.bluewallet + match AppStore io.bluewallet.bluewallet + io.bluewallet.bluewallet.watch + match AppStore io.bluewallet.bluewallet.watch + io.bluewallet.bluewallet.watch.extension + match AppStore io.bluewallet.bluewallet.watch.extension + io.bluewallet.bluewallet.Stickers + match AppStore io.bluewallet.bluewallet.Stickers + io.bluewallet.bluewallet.MarketWidget + match AppStore io.bluewallet.bluewallet.MarketWidget + + + \ No newline at end of file diff --git a/ios/fastlane/Appfile b/ios/fastlane/Appfile deleted file mode 100644 index 18030630931..00000000000 --- a/ios/fastlane/Appfile +++ /dev/null @@ -1,6 +0,0 @@ -# app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app -# apple_id("[[APPLE_ID]]") # Your Apple email address - - -# For more information about the Appfile, see: -# https://docs.fastlane.tools/advanced/#appfile diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile deleted file mode 100644 index 0f39ea637ed..00000000000 --- a/ios/fastlane/Fastfile +++ /dev/null @@ -1,23 +0,0 @@ -# This file contains the fastlane.tools configuration -# You can find the documentation at https://docs.fastlane.tools -# -# For a list of all available actions, check out -# -# https://docs.fastlane.tools/actions -# -# For a list of all available plugins, check out -# -# https://docs.fastlane.tools/plugins/available-plugins -# - -# Uncomment the line if you want fastlane to automatically update itself -# update_fastlane - -default_platform(:ios) - -platform :ios do - desc "Description of what the lane does" - lane :custom_lane do - # add actions here: https://docs.fastlane.tools/actions - end -end diff --git a/ios/fastlane/metadata/copyright.txt b/ios/fastlane/metadata/copyright.txt deleted file mode 100644 index 8e98a266a63..00000000000 --- a/ios/fastlane/metadata/copyright.txt +++ /dev/null @@ -1 +0,0 @@ -2023 BlueWallet Services S.R.L. diff --git a/ios/fastlane/metadata/de-DE/name.txt b/ios/fastlane/metadata/de-DE/name.txt deleted file mode 100644 index f6df796effe..00000000000 --- a/ios/fastlane/metadata/de-DE/name.txt +++ /dev/null @@ -1 +0,0 @@ -BlueWallet - Bitcoin Wallet \ No newline at end of file diff --git a/ios/fastlane/metadata/ja/description.txt b/ios/fastlane/metadata/ja/description.txt deleted file mode 100644 index fc0f24480c4..00000000000 --- a/ios/fastlane/metadata/ja/description.txt +++ /dev/null @@ -1,46 +0,0 @@ -ビットコインを送る・受け取る・保存することができる、セキュリティとシンプルさを重視したウォレットです。 - -BlueWalletは、秘密鍵をあなたが所有するビットコインウォレット。ビットコインユーザーが作った、コミュニティのためのビットコインウォレット。 - -世界中の誰とでもすぐに取引ができ、あなたのポケットからファイナンスの仕組みを変革します。 - -無料で数の制限なくビットコインウォレットを作ることも、お持ちのウォレットをインポートすることもできます。シンプルでスピーディです。 - -_____ - -特長: - - -1 - デザインによるセキュリティ - -オープンソース -MITライセンスにより、あなた自身でビルド・実行可能! ReactNative製。 - -もっともらしい否認 -アクセスの開示を強要された時でも、フェイクのビットコインウォレットを復号できるパスワード - -完全な暗号化 -iOSのマルチレイヤー暗号化に加え、追加のパスワードですべてを暗号化 - -フルノード -Electrumを通じてビットコインフルノードに接続 - -コールドストレージ -ハードウェアウォレットに接続し、コインをコールドストレージに保存 - -2 - ユーザーエクスペリエンス重視 - -思いのままに -秘密鍵はずっとあなたのデバイスの中に。あなたが秘密鍵をコントロールします - -柔軟な手数料 -1 Satoshiから。ユーザーのあなた自身が決定します - -Replace-By-Fee -(RBF) 手数料を増やして取引をスピードアップ (BIP125) - -閲覧専用ウォレット -閲覧専用ウォレットにより、ハードウェアに触れることなくコールドストレージを監視できます。 - -ライトニングネットワーク -設定の要らないライトニングウォレット。有り得ないほど安く、速い取引で最高のビットコイン体験を。 \ No newline at end of file diff --git a/ios/fastlane/metadata/ja/keywords.txt b/ios/fastlane/metadata/ja/keywords.txt deleted file mode 100644 index 45c637c91a2..00000000000 --- a/ios/fastlane/metadata/ja/keywords.txt +++ /dev/null @@ -1 +0,0 @@ -ビットコイン,ウォレット,ビットコインウォレット,ブロックチェーン,btc,仮想通貨,暗号通貨,electrum,イーサリアム diff --git a/ios/fastlane/metadata/ja/promotional_text.txt b/ios/fastlane/metadata/ja/promotional_text.txt deleted file mode 100644 index 5f21469d958..00000000000 --- a/ios/fastlane/metadata/ja/promotional_text.txt +++ /dev/null @@ -1,10 +0,0 @@ -特長 - -* オープンソース -* 完全な暗号化 -* もっともらしい否認 -* 柔軟な手数料 -* Replace-By-Fee (RBF) -* SegWit -* 閲覧専用(Sentinel)ウォレット -* ライトニングネットワーク \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000000..c8690ab7ec2 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,17 @@ +module.exports = { + testEnvironment: '/tests/custom-environment.js', + reporters: ['default', ['/tests/custom-reporter.js', {}]], + preset: '@react-native/jest-preset', + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + }, + moduleFileExtensions: ['js', 'json', 'ts', 'tsx'], + transformIgnorePatterns: ['node_modules/(?!((jest-)?react-native(-.*)?|@react-native(-community)?)|silent-payments|@arkade-os)/'], + moduleNameMapper: { + '^expo/fetch$': '/util/expo-fetch-nodejs.js', + '^@react-native-vector-icons/(.*)$': '/tests/mocks/vector-icons.js', + '^react-native-svg$': '/tests/mocks/react-native-svg.js', + }, + setupFiles: ['./tests/setup.js'], + watchPathIgnorePatterns: ['/node_modules'], +}; diff --git a/loc/ar.json b/loc/ar.json index 78781af1f3f..1a9799d8918 100644 --- a/loc/ar.json +++ b/loc/ar.json @@ -4,32 +4,32 @@ "cancel": "إلغاء", "continue": "المتابعة", "clipboard": "الحافظة", + "discard_changes": "تجاهل التغييرات؟", "enter_password": "إدخال كلمة المرور", "never": "أبدًا", - "disabled": "معطّل", "of": "{number} من {total}", "ok": "موافق", "storage_is_encrypted": "وحدة التخزين مشفرة. أنت بحاجة إلى كلمة المرور لفك تشفيرها", "yes": "نعم", "no": "لا", - "save": "حفظ", "seed": "عبارة الاسترداد", "success": "نجاح", "wallet_key": "مفتاح المحفظة", - "invalid_animated_qr_code_fragment": "جزء من رمز الاستجابة السريع (QR) غير صحيح، حاول مرة اخرى.", - "file_saved": "تم حفظ الملف {filePath} في {destination}.", - "downloads_folder": "مجلد التنزيلات", - "close": "اغلاق", - "change_input_currency": "تغيير عملة الادخال", + "close": "إغلاق", + "change_input_currency": "تغيير عملة الإدخال", "refresh": "تحديث", - "more": "المزيد", - "pick_image": "اختر صورة من الصور", "pick_file": "اختر ملف", "enter_amount": "أدخل القيمة", - "qr_custom_input_button": "أنقر ١٠ مرات لإدخال قيمة مخصصة" - }, - "alert": { - "default": "تنبيه" + "qr_custom_input_button": "أنقر ١٠ مرات لإدخال قيمة مخصصة", + "copied": "تم النسخ!", + "discard_changes_explain": "لديك تغييرات غير محفوظة. هل أنت متأكد أنك تريد تجاهلها ومغادرة الشاشة؟", + "enter_url": "إدخال الرابط", + "save": "حفظ...", + "pick_image": "اختيار من المكتبة", + "unlock": "فتح القفل", + "port": "المنفذ", + "ssl_port": "منفذ SSL", + "suggested": "مقترح" }, "azteco": { "codeIs": "رمز القسيمة الخاص بك هو", @@ -38,12 +38,14 @@ "redeem": "الاسترداد إلى المحفظة", "redeemButton": "الاسترداد", "success": "نجح الاسترداد", - "title": "استرداد قسيمة Azte.co" + "title": "استرداد قسيمة Azte.co", + "successMessage": "تم استرداد القسيمة بنجاح! من المفترض أن تصل أموالك إلى محفظة Bitcoin الخاصة بك قريبًا." }, "entropy": { "save": "حفظ", "title": "الإنتروبيا (العشوائية)", - "undo": "تراجع" + "undo": "تراجع", + "amountOfEntropy": "{bits} من {limit} بت" }, "errors": { "broadcast": "فشل البث", @@ -51,80 +53,63 @@ "network": "خطأ في الشبكة" }, "lnd": { - "active": "نشط", - "inactive": "غير نشط", - "channels": "القنوات", - "no_channels": "لا يوجد قنوات", - "claim_balance": "المطالبة برصيد {balance}", - "close_channel": "اغلق القناة", - "new_channel": "قناة جديدة", - "errorInvoiceExpired": "انتهت صلاحية الفاتورة", - "force_close_channel": "هل تود فرض اغلاق القناة؟", "expired": "منتهية الصلاحية", - "node_alias": "اسم النود", "expiresIn": "تنتهي بعد {time} دقيقة", "payButton": "دفع", "placeholder": "عنوان أو برقية", - "open_channel": "فتح قناة", - "funding_amount_placeholder": "مبلغ التمويل، على سبيل المثال 0.001", - "opening_channnel_for_from": "جارِ فتح قناة للمحفظة {forWalletLabel} بتمويل من {fromWalletLabel}", - "are_you_sure_open_channel": "هل أنت متأكد أنك تريد فتح هذه القناة؟", "potentialFee": "الرسوم المحتملة: {fee}", - "remote_host": "المضيف البعيد", "refill": "إعادة التعبئة", - "reconnect_peer": "إعادة الاتصال بالأقران", "refill_create": "للمتابعة، يُرجى إنشاء محفظة بتكوين لإعادة التعبئة باستخدامها.", "refill_external": "إعادة التعبئة باستخدام محفظة خارجية", "refill_lnd_balance": "إعادة تعبئة رصيد محفظة البرق (Lightning)", - "sameWalletAsInvoiceError": "لايمكنك دفع البرقية من نفس المحفظة", + "sameWalletAsInvoiceError": "لا يمكنك دفع فاتورة بنفس المحفظة المستخدمة لإنشائها.", "title": "إدارة الأموال", - "can_send": "يمكن أن ترسل", - "can_receive": "يمكن ان تستقبل", - "view_logs": "عرض السجلات" + "errorInvoiceExpired": "انتهت صلاحية الفاتورة.", + "payment": "دفعة" }, "lndViewInvoice": { "additional_info": "معلومة إضافية", "for": "إلى:", "lightning_invoice": "برقية", - "open_direct_channel": "فتح قناة مباشرة مع هذه النود:", "please_pay_between_and": "يرجى دفع ما بين {min} و{max}", "please_pay": "يُرجى الدفع", - "preimage": "الصورة الأصلية", "sats": "بالساتوشي", - "wasnt_paid_and_expired": "لم يتم دفع هذه البرقية وانتهت صلاحيتها" + "wasnt_paid_and_expired": "لم يتم دفع هذه البرقية وانتهت صلاحيتها", + "preimage": "صورة أولية", + "date_time": "التاريخ والوقت" }, "plausibledeniability": { "create_fake_storage": "إنشاء وحدة تخزين مشفرة", - "create_password": "إنشاء كلمة مرور", "create_password_explanation": "يجب ألا تتطابق كلمة المرور لوحدة التخزين المزيفة مع كلمة المرور لوحدة التخزين الرئيسية", "help": "قد تُضطر في ظل ظروف معينة إلى الكشف عن كلمة مرور محفظتك. وللحفاظ على امان عملاتك، يمكن لمحفظة BlueWallet إنشاء وحدة تخزين مشفرة أخرى بكلمة مرور مختلفة. ويمكنك الكشف عن كلمة المرور هذه للطرف الخارجي في حال التعرض لأي ضغوط. إذا تم ادخال كلمة المرور هذه في BlueWallet، فسيتم فتح وحدة تخزين \"مزيفة\" جديدة. ستبدو تلك المحفظة كمحفظة حقيقية للطرف الخارجي، وفي نفس الوقت سيحفظ ذلك على سريّة أمان محفظتك الرئيسية التي تحتفظ فيها بعملاتك الحقيقية.", "help2": "ستعمل وحدة التخزين الجديدة بكامل طاقتها، ويمكنك تخزين بعض المبالغ البسيطة هناك بحيث تبدو أكثر مصداقية ومنطقية.", "password_should_not_match": "كلمة المرور قيد الاستخدام حاليًا. يُرجى تجربة كلمة مرور مختلفة.", - "passwords_do_not_match": "كلمات المرور غير متطابقة، حاول مرة أخرى.", - "retype_password": "أعد إدخال كلمة المرور", - "success": "نجحت العملية", "title": "الإنكار المقبول" }, "pleasebackup": { "ask": "هل حفظت العبارة الاحتياطية لمحفظتك؟ ستحتاج إلى هذه العبارة الاحتياطية للوصول إلى أموالك في حالة فقدانك لهذا الجهاز. ودون العبارة الاحتياطية، ستفقد أموالك للأبد.", - "ask_no": "لا، لم أفعل", - "ask_yes": "نعم، لقد فعلت", "ok": "حسنًا، لقد دوَّنتها!", "ok_lnd": "حسنًا، لقد حفظتها.", - "text": "يُرجى أخذ لحظة من وقتك لكتابة هذه العبارة التذكيرية على ورقة. إنها وسيلتك الاحتياطية لاستعادة المحفظة على جهاز آخر.", - "text_lnd": "يُرجى حفظ هذه النسخة الاحتياطية للمحفظة. لكي تتتمكن من استعادة المحفظة في حالة فقدها.", - "title": "تم إنشاء محفظتك" + "text": "يُرجى أخذ لحظة من وقتك لكتابة هذه العبارة التذكيرية على ورقة.\nإنها وسيلتك الاحتياطية لاستعادة المحفظة على جهاز آخر.", + "text_lnd": "يُرجى حفظ هذه النسخة الاحتياطية للمحفظة. لكي تتمكن من استعادة المحفظة في حالة فقدها.", + "title": "تم إنشاء محفظتك...", + "ask_no": "لا، لم أقم بذلك.", + "ask_yes": "نعم، لقد قمت بذلك." }, "receive": { "details_create": "إنشاء", "details_label": "الوصف", "details_setAmount": "استلام مبلغ محدد", - "details_share": "مشاركة", "header": "استلام", - "maxSats": "الحد الأقصى للمبلغ هو {min} ساتوشي", - "maxSatsFull": "الحد الأقصى للمبلغ هو {min} ساتوشي أو {currency}", + "maxSats": "الحد الأقصى للمبلغ هو {max} ساتوشي", + "maxSatsFull": "الحد الأقصى للمبلغ هو {max} ساتوشي أو {currency}", "minSats": "الحد الأدنى للمبلغ هو {min} ساتوشي", - "minSatsFull": "الحد الأدنى للمبلغ هو {min} ساتوشي أو {currency}" + "minSatsFull": "الحد الأدنى للمبلغ هو {min} ساتوشي أو {currency}", + "details_share": "مشاركة...", + "address_not_found": "تعذر إنشاء عنوان استلام.", + "reset": "إعادة تعيين", + "qrcode_for_the_address": "رمز QR للعنوان", + "bip47_explanation": "رموز الدفع هي عنوان عالمي يتجنب الكشف عن عناوين محفظتك. لن تدعمها جميع الخدمات." }, "send": { "provided_address_is_invoice": "يبدو أن هذا عنوان برقية. من فضلك انتقل إلى محفظة البرق الخاصة بك لدفع هذه الفاتورة.", @@ -142,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", @@ -153,22 +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_frozen": "{amount} بتكوين تم تجميدها", "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": "جارٍ التهيئة...", @@ -193,8 +177,6 @@ "permission_camera_message": "نحتاج إلى إذنك لاستخدام الكاميرا الخاصة بك", "psbt_sign": "وقّع معاملة", "open_settings": "فتح الإعدادات", - "permission_storage_later": "اسألني لاحقًا", - "permission_storage_message": "تحتاج BlueWallet إلى إذنك للوصول إلى وحدة التخزين الخاصة بك لحفظ هذه المعاملة.", "permission_storage_denied_message": "BlueWallet غير قادر على حفظ هذا الملف. يرجى فتح إعدادات جهازك وتمكين إذن التخزين.", "permission_storage_title": "إذن وصول BlueWallet إلى وحدة التخزين", "psbt_clipboard": "النسخ إلى الحافظة", @@ -204,12 +186,27 @@ "outdated_rate": "تم تحديث السعر آخر مرة في {date}", "psbt_tx_open": "فتح معاملة موقَّعة", "psbt_tx_scan": "المسح الضوئي لمعاملة موقَّعة", - "qr_error_no_qrcode": "لم نتمكن من قراءة رمز الاستجابة السريع (QR) في الصورة المحددة. تأكد أن الصورة تحتوي فقط على رمز (QR) دون أي محتوى إضافي آخر مثل النص أو الأزرار.", "reset_amount": "إعادة تعيين المبلغ", "reset_amount_confirm": "هل تريد إعادة تعيين المبلغ؟", "success_done": "تم", - "txSaved": "تم حفظ ملف المعاملة ({filePath}) في مجلد \"التنزيلات\" الخاص بك.", - "problem_with_psbt": "مشكلة مع PSBT" + "problem_with_psbt": "مشكلة مع PSBT", + "details_insert_contact": "إدراج جهة اتصال", + "details_add_recc_rem_all_alert_description": "هل أنت متأكد أنك تريد إزالة جميع المستلمين؟", + "details_add_rec_rem_all": "إزالة جميع المستلمين", + "details_recipients_title": "المستلمون", + "details_recipient_title": "المستلم #{number} من #{total}", + "please_complete_recipient_title": "مستلم غير مكتمل", + "please_complete_recipient_details": "يُرجى إكمال تفاصيل المستلم #{number} قبل إضافة مستلم جديد.", + "details_frozen": "{amount} BTC مجمدة.", + "details_scan_error": "خطأ في المسح الضوئي", + "insert_custom_fee": "إدخال الرسوم", + "invalid_psbt": "PSBT غير صالحة.", + "qr_error_no_qrcode": "تعذر العثور على رمز QR صالح في الصورة المحددة. تأكد من أن الصورة تحتوي على رمز QR فقط دون أي محتوى إضافي مثل النصوص أو الأزرار.", + "txSaved": "تم حفظ ملف المعاملة ({filePath}).", + "file_saved_at_path": "تم حفظ الملف ({filePath}).", + "cant_send_to_silentpayment_adress": "لا يمكن لهذه المحفظة الإرسال إلى عناوين Silent Payments", + "cant_send_to_bip47": "لا يمكن لهذه المحفظة الإرسال إلى رموز الدفع BIP47", + "cant_find_bip47_notification": "أضف رمز الدفع هذا إلى جهات الاتصال أولاً" }, "settings": { "about": "نبذة", @@ -222,28 +219,19 @@ "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_discord": "سيرفر Discord", "about_sm_telegram": "قناة تيليجرام", - "about_sm_twitter": "تابعنا على تويتر", - "advanced_options": "الخيارات المتقدمة", "biometrics": "القياسات الحيوية", "biom_10times": "لقد حاولت إدخال كلمة المرور الخاصة بك 10 مرات. هل ترغب في إعادة تعيين التخزين الخاص بك؟ سيؤدي هذا إلى إزالة جميع المحافظ وفك تشفير التخزين الخاص بك.", "biom_conf_identity": "الرجاء تأكيد هويتك.", - "biom_no_passcode": "جهازك ليس لديه رمز مرور. للمتابعة ، يرجى اضافة رمز مرور من إعدادات الجهاز.", - "biom_remove_decrypt": "ستتم إزالة جميع محافظك وفك تشفير التخزين الخاص بك. هل انت متأكد انك تريد المتابعة؟", + "biom_remove_decrypt": "ستتم إزالة جميع محافظك وفك تشفير التخزين الخاص بك. هل أنت متأكد أنك تريد المتابعة؟", "currency": "العملة", - "currency_source": "تم الاستعلام عن السعر عن طريق", "currency_fetch_error": "حدث خطأ أثناء الحصول على سعر العملة المحددة.", - "default_desc": "عند تعطيل هذا الإعداد، ستفتح BlueWallet المحفظة المحدَّدة فور التشغيل.", - "default_info": "المعلومات الافتراضية", "default_title": "عند التشغيل", - "default_wallets": "عرض جميع المحافظ", "electrum_connected": "متصل", "electrum_connected_not": "غير متصل", - "electrum_error_connect": "يتعذَّر الاتصال بخادم Electrum المقدَّم", "lndhub_uri": "على سبيل المثال، {example}", "electrum_host": "على سبيل المثال، {example}", "electrum_offline_mode": "وضع عدم الاتصال بالشبكة", @@ -252,85 +240,99 @@ "use_ssl": "استخدم SSL", "electrum_saved": "تم حفظ تغييراتك بنجاح. قد تحتاج إلى إعادة التشغيل لتصبح التغييرات سارية المفعول.", "set_electrum_server_as_default": "هل تريد تعيين {server} كخادم Electrum الافتراضي؟", - "set_lndhub_as_default": "هل تريد تعيين {url} كخادم LNDHub الافتراضي؟", "electrum_settings_server": "خادم Electrum", - "electrum_settings_explain": "اتركه فارغا لاستخدام الاعدادات الافتراضية.", "electrum_status": "الحالة", - "electrum_clear_alert_title": "محو السجل؟", - "electrum_clear_alert_message": "هل تريد مسح سجل خوادم Electrum؟", - "electrum_clear_alert_cancel": "الغاء", - "electrum_clear_alert_ok": "موافق", - "electrum_select": "اختيار", - "electrum_reset": "إعادة تعيين إلى الافتراضي", "electrum_unable_to_connect": "تعذر الاتصال بـ {server}.", - "electrum_history": "تاريخ الخادم", - "electrum_reset_to_default": "هل أنت متأكد من رغبتك في إعادة تعيين إعدادات Electrum إلى الإعدادات الافتراضية؟", - "electrum_clear": "حذف", - "tor_supported": "اتصال Tor مدعوم", - "tor_unsupported": "اتصالات Tor غير مدعومة.", + "electrum_reset": "إعادة تعيين إلى الافتراضي", "encrypt_decrypt": "فك تشفير وحدة التخزين", "encrypt_decrypt_q": "هل أنت متأكد أنك تريد فك تشفير وحدة التخزين الخاصة بك؟ سيسمح إجراء ذلك بالوصول إلى محافظك دون كلمة مرور.", - "encrypt_enc_and_pass": "مشفرة ومحمية بكلمة مرور", "encrypt_title": "الأمان", "encrypt_tstorage": "وحدة التخزين", "encrypt_use": "استخدام {type}", - "encrypt_use_expl": "سيتم استخدام {type} لتأكيد هويتك قبل إجراء معاملة أو فتح محفظة أو تصديرها أو حذفها. ولن يتم استخدام {type} لفتح وحدة تخزين مشفرة.", "general": "عام", - "general_adv_mode": "الوضع المتقدم", - "general_adv_mode_e": "عند تمكين هذا الإعداد، سترى خيارات متقدمة أثناء إنشاء المحفظة، مثل أنواع محافظ مختلفة، القدرة على الاتصال ب LNDHub محدد، وإنتروبيا (عشوائية) مخصصة.", "general_continuity": "الاتساق", "general_continuity_e": "عند تمكين هذا الإعداد، ستتمكن من عرض المحافظ والعمليات المحدَّدة باستخدام أجهزتك الأخرى المتصلة بنفس حساب Apple iCloud.", "groundcontrol_explanation": "GroundControl هو خادم إشعارات فورية مجاني مفتوح المصدر لمحافظ البتكوين. يمكنك تثبيت خادم GroundControl الخاص بك ووضع عنوان (URL) له هنا لعدم الاعتماد على البنية التحتية لمحفظة BlueWallet. اترك الحقل فارغًا لاستخدام الإعدادات الافتراضية", "header": "الإعدادات", "language": "اللغة", "last_updated": "آخر تحديث", - "language_isRTL": "يجب اعادة تشغيل BlueWallet حتى تظهر تعديلات تغيير اللغة.", - "lightning_error_lndhub_uri": "معرِّف URI لبرنامج تضمين LndHub غير صالح", + "language_isRTL": "يجب إعادة تشغيل BlueWallet حتى تظهر تعديلات تغيير اللغة.", "lightning_saved": "تم حفظ تغييراتك بنجاح", "lightning_settings": "إعدادات البرق", - "tor_settings": "اعدادات tor", - "lightning_settings_explain": "للاتصال بنود LND الخاص بك، يُرجى تثبيت LNDHub ثم وضع رابطه هنا في الإعدادات. تذكر: فقط المحافظ التي يتم إنشاؤها بعد حفظ التغييرات ستتصل بنود LNDHub التي قمت بإضافتها.", "network": "الشبكة", "network_broadcast": "بث العملية", "network_electrum": "خادم Electrum", "not_a_valid_uri": "معرِّف URI غير صالح", "notifications": "الإشعارات", "open_link_in_explorer": "فتح الرابط في المتصفح", - "password": "كلمه المرور", - "password_explain": "أنشئ كلمة المرور التي ستستخدمها لفك تشفير وحدة التخزين", - "passwords_do_not_match": "كلمتا المرور لا تتطابقان", + "password": "كلمة المرور", "plausible_deniability": "الإنكار المقبول", "privacy": "الخصوصية", "privacy_read_clipboard": "قراءة الحافظة", - "privacy_system_settings": "اعدادات الجهاز", + "privacy_system_settings": "إعدادات الجهاز", "privacy_quickactions": "اختصارات المحفظة", - "privacy_quickactions_explanation": "المس مع الاستمرار ايقونة تطبيق BlueWallet على شاشتك الرئيسية للمشاهدة عرض سريع لرصيد محفظتك.", "privacy_clipboard_explanation": "عرض اختصار إذا تم العثور على عنوان أو فاتورة في الحافظة الخاصة بك.", "privacy_do_not_track": "تعطيل التحليلات", "privacy_do_not_track_explanation": "لن يتم إرسال التحليلات المتعلقة بأداء وقابلية تشغيل المحفظة إلينا.", - "push_notifications": "الإشعارات الفورية", "rate": "سعر الصرف", - "retype_password": "إعادة إدخال كلمة المرور", "selfTest": "اختبار ذاتي", "save": "حفظ", "saved": "تم الحفظ", - "success_transaction_broadcasted": "تمت بنجاح! لقد تم بث العملية!", - "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 وإعادة فتحه.", + "biometrics_no_longer_available": "تغيرت إعدادات جهازك ولم تعد متطابقة مع إعدادات الأمان المحددة في التطبيق. يُرجى إعادة تفعيل القياسات الحيوية أو رمز المرور، ثم إعادة تشغيل التطبيق لتطبيق هذه التغييرات.", + "biom_no_passcode": "لا يحتوي جهازك على رمز المرور أو القياسات الحيوية. للمتابعة، يُرجى تكوين رمز المرور أو القياسات الحيوية في تطبيق الإعدادات.", + "currency_source": "يتم الحصول على السعر من", + "donate": "تبرع", + "donate_description": "ساعدنا في إبقاء Blue مجانيًا!", + "electrum_error_connect": "تعذر الاتصال بخادم Electrum المقدم", + "electrum_error_connect_tor": "تعذر الاتصال بخادم Electrum المقدم. يُرجى التأكد من اتصال تطبيق Orbot والمحاولة مرة أخرى.", + "set_lndhub_as_default": "تعيين {url} كخادم LNDhub الافتراضي؟", + "electrum_preferred_server": "الخادم المفضل", + "electrum_preferred_server_description": "أدخل الخادم الذي تريد أن تستخدمه محفظتك لجميع أنشطة Bitcoin. بمجرد التعيين، ستستخدم محفظتك هذا الخادم حصريًا للتحقق من الأرصدة وإرسال المعاملات وجلب بيانات الشبكة. تأكد من ثقتك بهذا الخادم قبل تعيينه.", + "electrum_history": "السجل", + "electrum_reset_to_default": "سيسمح هذا لـ BlueWallet باختيار خادم عشوائيًا من قائمة الخوادم.", + "electrum_reset_to_default_and_clear_history": "إعادة التعيين إلى الافتراضي ومسح السجل", + "encrypt_enc_and_pass": "محمي بكلمة مرور", + "encrypt_storage_explanation_headline": "تمكين تشفير التخزين", + "encrypt_storage_explanation_description_line1": "يضيف تمكين تشفير التخزين طبقة حماية إضافية لتطبيقك من خلال تأمين طريقة تخزين بياناتك على جهازك. هذا يجعل من الصعب على أي شخص الوصول إلى معلوماتك دون إذن.", + "encrypt_storage_explanation_description_line2": "ومع ذلك، من المهم أن تعلم أن هذا التشفير يحمي فقط الوصول إلى محافظك المخزنة في حلقة مفاتيح الجهاز. لا يضع كلمة مرور أو أي حماية إضافية على المحافظ نفسها.", + "i_understand": "أنا أفهم", + "block_explorer": "مستكشف الكتل", + "block_explorer_preferred": "استخدام مستكشف الكتل المفضل", + "block_explorer_error_saving_custom": "حدث خطأ في حفظ مستكشف الكتل المفضل", + "set_as_preferred": "تعيين كمفضل", + "set_as_preferred_electrum": "تعيين {host}:{port} كخادم مفضل سيعطل الاتصال بخادم مقترح عشوائيًا.", + "encrypted_feature_disabled": "لا يمكن استخدام هذه الميزة عند تمكين التخزين المشفر.", + "encrypt_use_expl": "سيتم استخدام {type} لتأكيد هويتك قبل إجراء معاملة أو فتح القفل أو التصدير أو حذف محفظة.", + "biometrics_fail": "إذا لم يتم تمكين {type}، أو فشل في فتح القفل، يمكنك استخدام رمز مرور جهازك كبديل.", + "license": "الترخيص", + "lightning_error_lndhub_uri": "URI خاص بـ LNDhub غير صالح", + "lightning_error_lndhub_uri_tor": "URI خاص بـ LNDhub غير صالح. يُرجى التأكد من اتصال تطبيق Orbot والمحاولة مرة أخرى.", + "lightning_settings_explain": "للاتصال بنود LND الخاص بك، يُرجى تثبيت LNDhub ووضع عنوانه هنا في الإعدادات. يُرجى ملاحظة أن المحافظ التي يتم إنشاؤها بعد حفظ التغييرات فقط ستتصل بـ LNDhub المحدد.", + "lndhub_github": "مستودع GitHub", + "electrum_suggested_description": "عندما لا يتم تعيين خادم مفضل، سيتم اختيار خادم مقترح للاستخدام عشوائيًا.", + "password_explain": "أدخل كلمة المرور التي ستستخدمها لفتح تخزينك.", + "privacy_quickactions_explanation": "اضغط مع الاستمرار على أيقونة تطبيق BlueWallet لعرض رصيد محفظتك بسرعة.", + "push_notifications_explanation": "من خلال تمكين الإشعارات، سيتم إرسال رمز الجهاز الخاص بك إلى الخادم، إلى جانب عناوين المحفظة ومعرفات المعاملات لجميع المحافظ والمعاملات التي تتم بعد تمكين الإشعارات. يُستخدم رمز الجهاز لإرسال الإشعارات، وتسمح لنا معلومات المحفظة بإعلامك بشأن Bitcoin الوارد أو تأكيدات المعاملات.\n\nيتم نقل المعلومات فقط من بعد تمكين الإشعارات—لا يتم جمع أي شيء من قبل ذلك.\n\nسيؤدي تعطيل الإشعارات إلى إزالة كل هذه المعلومات من الخادم. بالإضافة إلى ذلك، فإن حذف محفظة من التطبيق سيؤدي أيضًا إلى إزالة المعلومات المرتبطة بها من الخادم.", + "success_transaction_broadcasted": "تم بث معاملتك بنجاح!" }, "notifications": { "would_you_like_to_receive_notifications": "هل ترغب في تلقي إشعارات عند استلام مدفوعات؟", - "no_and_dont_ask": "لا، ولا تسألني مرة أخرى", - "ask_me_later": "اسالني لاحقا" + "notifications_subtitle": "المدفوعات الواردة وتأكيدات المعاملات", + "no_and_dont_ask": "لا، ولا تسألني مرة أخرى.", + "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؛ أي التحكم بالعملية الرئيسية بمعاملة فرعية.", @@ -338,19 +340,14 @@ "cpfp_title": "تسريع المعاملة (CPFP)", "details_balance_hide": "إخفاء الرصيد", "details_balance_show": "عرض الرصيد", - "details_block": "ارتفاع الكتلة", "details_copy": "نسخ", - "details_copy_amount": "نسخ المبلغ", "details_copy_block_explorer_link": "نسخ رابط متصفح الكتل", "details_copy_note": "نسخ الملاحظة", "details_copy_txid": "نسخ معرّف المعاملة", - "details_from": "من", "details_inputs": "المدخلات", "details_outputs": "المخرجات", "date": "التاريخ", "details_received": "التاريخ", - "transaction_note_saved": "تم حفظ مذكرة المعاملة بنجاح.", - "details_show_in_block_explorer": "العرض في مستكشف الكتل", "details_title": "العملية", "details_to": "إلى", "enable_offline_signing": "هذه المحفظة لا يتم استعمالها مع التوقيع دون اتصال. هل ترغب في تمكينه الآن؟", @@ -361,8 +358,9 @@ "eta_10m": "الوقت المقدر للتأكيد: في حوالي 10 دقائق", "eta_3h": "الوقت المقدر للتأكيد: في حوالي 3 ساعات", "eta_1d": "الوقت المقدر للتأكيد: في حوالي يوم واحد", - "view_wallet": "عرض {walletLabel}", "list_title": "العمليات", + "list_title_received": "التاريخ", + "transaction": "العملية", "open_url_error": "تعذر فتح الرابط باستخدام المتصفح الافتراضي. يرجى تغيير المتصفح الافتراضي الخاص بك وحاول مرة أخرى.", "rbf_explain": "سنستبدل هذه المعاملة بمعاملة جديدة تدفع رسوم اعلى؛ حتى يجري تعدينها بشكلٍ أسرع. وهذا ما يُسمَّى RBF؛ أي الاستبدال بالرسوم.", "rbf_title": "تسريع العملية (RBF)", @@ -370,50 +368,75 @@ "status_cancel": "إلغاء العملية", "transactions_count": "عدد العمليات", "txid": "معرّف العملية", - "updating": "جارٍ التحديث ..." + "updating": "جارٍ التحديث ...", + "watchOnlyWarningDescription": "تنبيه احتيال: انتبه إلى أن المحتالين عادةً ما يستخدمون هذا النوع من المحفظة \"للمشاهدة فقط\" لمحاولة السرقة من المستخدمين. هذه المحفظة التي لا يمكنك التحكم بها أو الإرسال منها، إلا بتصريح جهاز آخر، المحفظة تسمح فقط بمراقبة الرصيد.", + "transaction_loading_error": "حدثت مشكلة في تحميل المعاملة. يُرجى المحاولة لاحقًا.", + "transaction_not_available": "المعاملة غير متوفرة", + "details_view_in_browser": "العرض في المتصفح", + "incoming_transaction": "معاملة واردة", + "outgoing_transaction": "معاملة صادرة", + "expired_transaction": "معاملة منتهية الصلاحية", + "pending_transaction": "معاملة قيد الانتظار", + "offchain": "خارج السلسلة", + "onchain": "على السلسلة", + "list_title_sent": "مرسلة", + "watchOnlyWarningTitle": "تحذير أمني", + "custom_fee_warning_title": "تحذير", + "custom_fee_warning_description": "الرسوم الأقل من 1 ساتوشي/بايت افتراضي صالحة، ولكن قد لا يتم نقلها بسبب سياسات النود.", + "details_eta_analyzing": "جارٍ التحليل...", + "details_sent": "مرسلة", + "details_section": "التفاصيل", + "details_explorer": "مستكشف", + "details_network_fee": "رسوم الشبكة", + "details_to_address": "إلى", + "details_id": "المعرّف", + "details_note": "ملاحظة", + "details_add_note": "إضافة", + "details_advanced": "متقدم", + "details_fee_rate": "معدل الرسوم", + "details_size": "الحجم", + "details_virtual_size": "الحجم الافتراضي", + "details_tx_hex": "Hex المعاملة", + "details_inputs_count": "المدخلات ({count})", + "details_outputs_count": "المخرجات ({count})" }, "wallets": { "add_bitcoin": "بتكوين", "add_bitcoin_explain": "محفظة بتكوين بسيطة وقوية", "add_create": "إنشاء", + "total_balance": "الرصيد الإجمالي", + "add_entropy": "الإنتروبيا (العشوائية)", "add_entropy_generated": "{gen} بايت من الإنتروبيا (العشوائية) المحققة", "add_entropy_provide": "توفير الإنتروبيا (العشوائية) باستخدام النرد", "add_entropy_remain": "{gen} بايت من الإنتروبيا (العشوائية) المحققة. سيتم الحصول على {rem} بايت المتبقية من مولِّد الأرقام العشوائية للنظام.", "add_import_wallet": "استيراد محفظة", "add_lightning": "البرق", "add_lightning_explain": "لإرسال المعاملات بشكل فوري عبر شبكة البرق", - "add_lndhub": "اتصل ب LNDHub الخاص بك", - "add_lndhub_error": "عنوان النود المقدَّم غير صالح.", "add_lndhub_placeholder": "عنوان النود الخاص بك", "add_placeholder": "محفظتي الأولى", "add_title": "إضافة محفظة", "add_wallet_name": "الاسم", "add_wallet_type": "النوع", - "balance": "الرصيد", "clipboard_bitcoin": "يوجد عنوان بتكوين في سجل النسخ الخاص بك. هل ترغب في استخدامه لمعاملة؟", "clipboard_lightning": "لديك عنوان برقية في سجل النسخ. هل ترغب في استخدام العنوان لإرسال البرقية؟", "details_address": "العنوان", "details_advanced": "الخيارات المتقدمة", "details_are_you_sure": "هل أنت متأكد؟", "details_connected_to": "متصلة بـ", - "details_del_wb_err": "لا يتطابق مبلغ الرصيد المقدَّم مع رصيد هذه المحفظة. يُرجى إعادة المحاولة", - "details_del_wb_q": "هذه المحفظة يوجد بها رصيد. قبل الاستمرار يرجى العلم أنك لن تتمكن من استرداد الأموال بدون عبارة الاسترداد لهذه المحفظة. لتجنب الحذف الغير متعمد، يرجى إدخال رصيد المحفظة البالغ {balance} ساتوشي.", + "details_del_wb_q": "هذه المحفظة يوجد بها رصيد. قبل الاستمرار يرجى العلم أنك لن تتمكن من استرداد الأموال بدون عبارة الاسترداد لهذه المحفظة. لتجنب الحذف غير المتعمد، يرجى إدخال رصيد المحفظة البالغ {balance} ساتوشي.", "details_delete": "الحذف", "details_delete_wallet": "حذف المحفظة", "details_derivation_path": "مسار الاشتقاق (derivation path)", - "details_display": "العرض في قائمة المحافظ", "details_export_backup": "التصدير/النسخ الاحتياطي", - "details_export_history": "تصدير السجل ل ملف CSV", + "details_export_history": "تصدير السجل إلى ملف CSV", "details_master_fingerprint": "البصمة الرئيسية", "details_multisig_type": "متعدد التواقيع", - "details_no_cancel": "لا، إلغاء", - "details_save": "حفظ", "details_show_xpub": "إظهار عنوان XPUB للمحفظة", "details_show_addresses": "عرض العناوين", "details_title": "المحفظة", + "wallets": "المحافظ", "details_type": "النوع", "details_use_with_hardware_wallet": "الاستخدام مع محفظة جهاز", - "details_wallet_updated": "تم تحديث المحفظة", "details_yes_delete": "نعم، احذف", "enter_bip38_password": "أدخل كلمة المرور لفك التشفير", "export_title": "تصدير المحفظة", @@ -433,61 +456,95 @@ "import_discovery_subtitle": "اختر المحفظة المعثور عليها", "import_discovery_derivation": "استخدم مسار اشتقاق مخصص (derivation path)", "import_discovery_no_wallets": "لم يتم العثور على محفظة", - "import_derivation_found": "موجود", - "import_derivation_found_not": "غير موجود", - "import_derivation_loading": "جار التحميل", - "import_derivation_subtitle": "ادخل مسار اشتقاق مخصص (derivation path) وسنحاول العثور على محفظتك", "import_derivation_title": "مسار الاشتقاق (derivation path)", - "import_derivation_unknown": "غير معروف", - "import_wrong_path": "مسار اشتقاق غير صحيح", "list_create_a_button": "إضافة الآن", "list_create_a_wallet": "إضافة محفظة", - "list_create_a_wallet_text": "مجاناً ويمكنك انشاء اي عدد ترغب به من المحافظ.", "list_empty_txs1": "ستظهر معاملاتك هنا", "list_empty_txs1_lightning": "يجب استخدام محفظة البرق (Lightning) في معاملاتك اليومية. الرسوم رخيصة جدًا والسرعة كبيرة حقًا.", "list_empty_txs2": "ابدأ بمحفظتك", "list_empty_txs2_lightning": "\nللبدء في استخدامها، اضغط على \"إدارة الأموال\" واشحن رصيدك.", "list_latest_transaction": "آخر عملية", - "list_ln_browser": "متصفح LApp", "list_long_choose": "اختيار صورة", - "list_long_clipboard": "النسخ من الحافظة", + "paste_from_clipboard": "اللصق", + "import_file": "استيراد ملف", "list_long_scan": "مسح رمز الاستجابة (QR) ضوئيًا", "list_title": "المحافظ", "list_tryagain": "إعادة المحاولة", "no_ln_wallet_error": "قبل دفع البرقية، يجب عليك أولاً إضافة محفظة برق.", - "looks_like_bip38": "يبدوا ان هذا مفتاح خاص محمي بكلمة مرور (BIP38).", - "reorder_title": "إعادة ترتيب المحافظ", - "reorder_instructions": "اضغط باستمرار على اي محفظة لتحريكها عبر القائمة", + "looks_like_bip38": "يبدو أن هذا مفتاح خاص محمي بكلمة مرور (BIP38).", "please_continue_scanning": "الرجاء متابعة الفحص.", "select_no_bitcoin": "لا توجد محافظ بتكوين متاحة حاليًا.", "select_no_bitcoin_exp": "تحتاج إلى محفظة بتكوين لإعادة تعبئة محافظ البرق. يُرجى إنشاء محفظة أو استيراد واحدة.", "select_wallet": "اختيار محفظة", - "xpub_copiedToClipboard": "تم النسخ إلى الحافظة.", "pull_to_refresh": "اسحب للتحديث", - "warning_do_not_disclose": "تحذير! لا تنشر هذا.", "add_ln_wallet_first": "يجب عليك أولاً إضافة محفظة برق.", "identity_pubkey": "هوية Pubkey", - "xpub_title": "عنوان XPUB للمحفظة" + "xpub_title": "عنوان XPUB للمحفظة", + "add_entropy_reset_title": "إعادة تعيين الإنتروبيا", + "add_entropy_reset_message": "سيؤدي تغيير نوع المحفظة إلى إعادة تعيين الإنتروبيا الحالية. هل تريد المتابعة؟", + "add_entropy_bytes": "{bytes} بايت من الإنتروبيا", + "add_lndhub": "الاتصال بـ LNDhub الخاص بك", + "add_lndhub_error": "عنوان النود المقدم هو نود LNDhub غير صالح.", + "add_wallet_seed_length": "طول عبارة الاسترداد", + "add_wallet_seed_length_12": "12 كلمة", + "add_wallet_seed_length_24": "24 كلمة", + "clear_clipboard_on_import": "مسح الحافظة عند الاستيراد", + "details_del_wb_err": "مبلغ الرصيد المقدم لا يطابق رصيد هذه المحفظة. يُرجى المحاولة مرة أخرى.", + "details_display": "العرض في الشاشة الرئيسية", + "swipe_balance_hide": "إخفاء", + "swipe_balance_show": "إظهار", + "drag_to_reorder": "اسحب لإعادة الترتيب", + "clear_search": "مسح البحث", + "learn_more": "معرفة المزيد", + "import_discovery_offline": "BlueWallet حاليًا في وضع عدم الاتصال. في هذا الوضع، لا يمكنه التحقق من وجود المحفظة، لذلك ستحتاج إلى تحديد المحفظة الصحيحة يدويًا", + "import_derivation_found": "تم العثور", + "import_derivation_found_not": "لم يتم العثور", + "import_derivation_loading": "جارٍ التحميل...", + "import_derivation_subtitle": "أدخل مسار اشتقاق مخصص، وسنحاول اكتشاف محفظتك.", + "import_derivation_unknown": "غير معروف", + "import_wrong_path": "مسار اشتقاق خاطئ", + "list_create_a_wallet_text": "إنها مجانية، ويمكنك إنشاء \nالعديد منها كما تشاء.", + "manage_title": "إدارة المحافظ", + "no_results_found": "لم يتم العثور على نتائج.", + "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": "انسخ هذه المعلومات واحفظها بأمان لاستعادة محفظتك لاحقًا.", + "manage_wallets_search_placeholder": "البحث في المحافظ والعناوين والمعاملات والمذكرات", + "more_info": "المزيد من المعلومات", + "details_delete_wallet_error_message": "حدثت مشكلة في تأكيد ما إذا كانت هذه المحفظة قد أُزيلت من الإشعارات—قد يكون ذلك بسبب مشكلة في الشبكة أو ضعف الاتصال. إذا تابعت، فقد تستمر في تلقي إشعارات للمعاملات المتعلقة بهذه المحفظة، حتى بعد حذفها.", + "details_delete_anyway": "احذف على أي حال" + }, + "total_balance_view": { + "title": "الرصيد الإجمالي", + "display_in_bitcoin": "العرض بـ Bitcoin", + "hide": "إخفاء", + "display_in_sats": "العرض بالساتوشي", + "display_in_fiat": "العرض بـ {currency}", + "explanation": "عرض الرصيد الإجمالي لجميع محافظك في شاشة النظرة العامة." }, "multisig": { - "multisig_vault": "خزنة", + "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": "إرسال", - "share": "المشاركة", "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": "إنشاء", @@ -498,47 +555,55 @@ "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": "من", "wallet_type": "نوع المحفظة", - "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 الخاص بشريك التوقيع—وهو جاهز للاستيراد إلى محفظة أخرى. من الآمن مشاركته.", - "wallet_key_created": "تم انشاء خزنتك. يُرجى أخذ لحظة من وقتك لتدوين عبارة الاسترداد في مكان آمن.", + "wallet_key_created": "تم إنشاء خزنتك. يُرجى أخذ لحظة من وقتك لتدوين عبارة الاسترداد في مكان آمن.", "are_you_sure_seed_will_be_lost": "هل أنت متأكد؟ ستفقد عبارة الاسترداد إذا لم تحتفظ بها في مكان آخر آمن.", - "forget_this_seed": "انسى هذه العبارة التذكيرية واستخدام XPUB بدلا من ذلك.", - "view_edit_cosigners": "عرض/تحرير شركاء التوقيع", - "this_cosigner_is_already_imported": "تم استيراد شريك التوقيع هذا سابقا.", + "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": "مسح أو استيراد معاملة موقعة", + "provide_signature_next_steps_details": "بمجرد أن توقع محفظتك المعاملة بنجاح، امسح رمز QR المقدم أو استورد الملف المرافق، ثم راجع جميع تفاصيل المعاملة قبل بثها.", + "share": "مشاركة...", + "shared_key_detected": "شريك توقيع تمت مشاركته", + "shared_key_detected_question": "تمت مشاركة شريك توقيع معك، هل تريد استيراده؟", + "invalid_mnemonics": "لا تبدو عبارة الاسترداد هذه صالحة.", + "invalid_cosigner": "بيانات شريك التوقيع غير صالحة", + "invalid_cosigner_format": "شريك توقيع غير صحيح: هذا ليس شريك توقيع لتنسيق {format}.", + "this_is_cosigners_xpub": "هذا هو XPUB الخاص بشريك التوقيع—جاهز للاستيراد إلى محفظة أخرى. من الآمن مشاركته.", + "this_is_cosigners_xpub_airdrop": "إذا قمت بالمشاركة عبر AirDrop، يجب أن يكون المستلمون في شاشة التنسيق.", + "view_edit_cosigners": "عرض/تحرير شركاء التوقيع", + "this_cosigner_is_already_imported": "تم استيراد شريك التوقيع هذا بالفعل." }, "is_it_my_address": { "title": "هل هذا عنواني؟", @@ -546,20 +611,33 @@ "enter_address": "أدخل العنوان", "check_address": "تحقق من العنوان", "no_wallet_owns_address": "لا تمتلك أي من المحافظ المتاحة هذا العنوان.", - "view_qrcode": "عرض رمز الاستجابة السريع (QR)" + "view_qrcode": "عرض رمز QR" + }, + "autofill_word": { + "title": "الكلمة الأخيرة من عبارة الاسترداد", + "enter": "أدخل عبارة الاسترداد الجزئية", + "generate_word": "إنشاء الكلمة الأخيرة", + "error": "المدخل ليس عبارة استرداد جزئية من 11 أو 23 كلمة. يُرجى المحاولة مرة أخرى." }, "cc": { "change": "تغيير", "coins_selected": "العملات المحددة ({number})", "selected_summ": "{value} مختاره", - "empty": "لا تحتوي هذه المحفظة على أي عملات في الوقت الحالي.", "freeze": "تجميد", "freezeLabel": "تجميد", "freezeLabel_un": "فك التجميد", "header": "التحكم في العملات", "use_coin": "استخدم العملة", "use_coins": "استخدام العملات", - "tip": "تتيح لك هذه الميزة رؤية العملات أو تسميتها أو تجميدها أو تحديدها لتحسين إدارة المحفظة. يمكنك تحديد عدة عملات من خلال النقر على الدوائر الملونة." + "tip": "تتيح لك هذه الميزة رؤية العملات أو تسميتها أو تجميدها أو تحديدها لتحسين إدارة المحفظة. يمكنك تحديد عدة عملات من خلال النقر على الدوائر الملونة.", + "sort_status": "الحالة", + "empty": "لا تحتوي هذه المحفظة على أي عملات في الوقت الحالي.", + "sort_asc": "تصاعدي", + "sort_desc": "تنازلي", + "sort_height": "الارتفاع", + "sort_value": "القيمة", + "sort_label": "العلامة", + "sort_by": "الترتيب حسب" }, "units": { "BTC": "BTC", @@ -581,7 +659,9 @@ "type_change": "الباقي", "type_receive": "استلام", "type_used": "مستخدم", - "transactions": "الحوالات" + "transactions": "الحوالات", + "copy_private_key": "نسخ المفتاح الخاص", + "sensitive_private_key": "تحذير: المفاتيح الخاصة حساسة للغاية. متابعة؟" }, "lnurl_auth": { "register_question_part_1": "هل ترغب بفتح حساب في", @@ -601,9 +681,24 @@ }, "bip47": { "payment_code": "كود الدفع", - "payment_codes_list": "قائمة أكواد الدفع", - "who_can_pay_me": "من يستطيع الدفع لي:", - "purpose": "أكواد المشاركة التي يمكن أعادة استخدامها (BIP47)", - "not_found": "لم يتم العثور على كود الدفع" + "purpose": "أكواد المشاركة التي يمكن إعادة استخدامها (BIP47)", + "not_found": "لم يتم العثور على كود الدفع", + "contacts": "جهات الاتصال", + "bip47_explain": "رمز قابل لإعادة الاستخدام والمشاركة", + "bip47_explain_subtitle": "BIP47", + "pay_this_contact": "ادفع لجهة الاتصال هذه", + "rename_contact": "إعادة تسمية جهة الاتصال", + "copy_payment_code": "نسخ رمز الدفع", + "hide_contact": "إخفاء جهة الاتصال", + "rename": "إعادة التسمية", + "provide_name": "قدم اسمًا جديدًا لجهة الاتصال هذه", + "add_contact": "إضافة جهة اتصال", + "provide_payment_code": "قدم رمز الدفع", + "invalid_pc": "رمز دفع غير صالح", + "notification_tx_unconfirmed": "معاملة الإشعار غير مؤكدة بعد، يُرجى الانتظار", + "failed_create_notif_tx": "فشل إنشاء المعاملة على السلسلة", + "onchain_tx_needed": "معاملة على السلسلة مطلوبة", + "notif_tx_sent": "تم إرسال معاملة الإشعار. يُرجى الانتظار حتى يتم تأكيدها", + "notif_tx": "معاملة الإشعار" } } diff --git a/loc/be@tarask.json b/loc/be@tarask.json index 069dcfd0d72..a1862204166 100644 --- a/loc/be@tarask.json +++ b/loc/be@tarask.json @@ -4,37 +4,48 @@ "cancel": "Адмяніць", "continue": "Працягнуць", "clipboard": "Буфер абмену", + "copied": "Скапіявана!", + "discard_changes": "Адмяніць зьмены?", + "discard_changes_explain": "У вас ёсьць незахаваныя зьмены. Вы ўпэўненыя, што хочаце адхіліць іх і пакінуць экран?", "enter_password": "Увесьці пароль", "never": "Ніколі", - "disabled": "Адключаны", "of": "{number} ад {total}", "ok": "Акей", + "enter_url": "Увесьці URL", "storage_is_encrypted": "Ваша сховішча зашыфравана. Для расшыфроўкі патрабуецца пароль.", "yes": "Так", "no": "Не", - "save": "Захаваць", - "seed": "Семя", + "save": "Захаваць...", + "seed": "Сід-фраза", "success": "Посьпех", "wallet_key": "Ключ ад кашалька", - "invalid_animated_qr_code_fragment": "Несапраўдны фрагмент аніміраванага QR-коду. Калі ласка, паспрабуйце яшчэ раз.", - "file_saved": "Файл {filePath} быў захаваны ў вашым месцы {destination}.", - "downloads_folder": "Папка Запамповак" - }, - "alert": { - "default": "Папярэджанне" + "close": "Закрыць", + "change_input_currency": "Зьмяніць валюту ўводу", + "refresh": "Абнавіць", + "pick_image": "Выбраць з бібліятэкі", + "pick_file": "Выбраць файл", + "enter_amount": "Увесьці суму", + "qr_custom_input_button": "Націсьніце 10 разоў, каб увесьці адвольны ўвод", + "unlock": "Разблякаваць", + "port": "Порт", + "ssl_port": "SSL-порт", + "suggested": "Прапанаваны" }, "azteco": { "codeIs": "Ваш код ваўчара", "errorBeforeRefeem": "Перад выплатай вы павінны спачатку дадаць кашалёк Bitcoin.", - "errorSomething": "Нешта пайшло не так. Сапраўдны ці гэты ваўчар па-ранейшаму?", + "errorSomething": "Нешта пайшло не так. Ці гэты ваўчар яшчэ сапраўдны?", "redeem": "Перавесьці на кашалёк", "redeemButton": "Перавесьці", - "success": "Посьпех" + "success": "Посьпех", + "successMessage": "Ваўчар пасьпяхова перавядзены! Вашы сродкі павінны неўзабаве паступіць на ваш кашалёк Bitcoin.", + "title": "Перавесьці ваўчар Azte.co" }, "entropy": { "save": "Захаваць", "title": "Энтрапія", - "undo": "Адмяніць" + "undo": "Адмяніць", + "amountOfEntropy": "{bits} з {limit} бітаў" }, "errors": { "broadcast": "Збой трансляцыі.", @@ -42,28 +53,652 @@ "network": "Памылка Сеткі" }, "lnd": { - "inactive": "Неактыўны", - "channels": "Каналы", - "no_channels": "Няма каналаў", - "close_channel": "Зачыніць канал", - "new_channel": "Новы канал", - "errorInvoiceExpired": "Тэрмін дзеяння рахункі скончыўся", + "errorInvoiceExpired": "Тэрмін дзеяньня інвойсу скончыўся.", "expired": "Скончыўся", - "open_channel": "Адкрыць Канал" + "expiresIn": "Скончыцца праз {time} хвілінаў", + "payButton": "Заплаціць", + "payment": "Плацёж", + "placeholder": "Інвойс альбо адрас", + "potentialFee": "Магчымая камісія: {fee}", + "refill": "Папоўніць", + "refill_create": "Каб працягнуць, калі ласка, стварыце кашалёк Bitcoin для папаўненьня.", + "refill_external": "Папоўніць зь вонкавага кашалька", + "refill_lnd_balance": "Папоўніць баланс кашалька Lightning", + "sameWalletAsInvoiceError": "Вы ня можаце аплаціць інвойс тым жа кашальком, які быў выкарыстаны для яго стварэньня.", + "title": "Кіраваньне сродкамі" + }, + "lndViewInvoice": { + "additional_info": "Дадатковая інфармацыя", + "for": "Для:", + "lightning_invoice": "Інвойс Lightning", + "please_pay_between_and": "Калі ласка, заплаціце ад {min} да {max}", + "please_pay": "Калі ласка, заплаціце", + "preimage": "Прообраз", + "sats": "sats.", + "date_time": "Дата і час", + "wasnt_paid_and_expired": "Гэты інвойс ня быў аплачаны, і тэрмін яго дзеяньня скончыўся." }, "plausibledeniability": { - "success": "Посьпех" + "create_fake_storage": "Стварыць зашыфраванае сховішча", + "create_password_explanation": "Пароль для падманнага сховішча не павінен супадаць з паролем для вашага асноўнага сховішча.", + "help": "Пры пэўных абставінах вас могуць прымусіць раскрыць пароль. Каб захаваць вашы манэты ў бясьпецы, BlueWallet можа стварыць яшчэ адно зашыфраванае сховішча зь іншым паролем. Пад ціскам вы можаце раскрыць гэты пароль трэцяй асобе. Калі ён будзе ўведзены ў BlueWallet, ён разблакуе новае „падманнае” сховішча. Гэта будзе выглядаць пераканаўча для трэцяй асобы, але сакрэтна захавае ваша асноўнае сховішча з манэтамі ў бясьпецы.", + "help2": "Новае сховішча будзе цалкам функцыянальным, і вы можаце захоўваць там некаторыя мінімальныя сумы, каб яно выглядала больш праўдападобна.", + "password_should_not_match": "Пароль ужо выкарыстоўваецца. Калі ласка, паспрабуйце іншы пароль.", + "title": "Праўдападобнае адмаўленьне" + }, + "pleasebackup": { + "ask": "Ці захавалі вы рэзервовую фразу вашага кашалька? Гэтая рэзервовая фраза патрэбна для доступу да вашых сродкаў, калі вы страціце гэтую прыладу. Без рэзервовай фразы вашы сродкі будуць назаўжды страчаны.", + "ask_no": "Не, не захаваў.", + "ask_yes": "Так, захаваў.", + "ok": "Добра, я запісаў гэта.", + "ok_lnd": "Добра, я гэта захаваў.", + "text": "Калі ласка, надайце хвілінку, каб запісаць гэтую мнэмонічную фразу на лісьце паперы.\nГэта ваша рэзервовая копія, і вы можаце выкарыстаць яе для аднаўленьня кашалька.", + "text_lnd": "Калі ласка, захавайце гэтую рэзервовую копію кашалька. Яна дазваляе вам аднавіць кашалёк у выпадку страты.", + "title": "Ваш кашалёк створаны." + }, + "receive": { + "details_create": "Стварыць", + "details_label": "Апісаньне", + "details_setAmount": "Атрымаць з сумай", + "details_share": "Падзяліцца...", + "address_not_found": "Немагчыма згенэраваць адрас для атрыманьня.", + "bip47_explanation": "Плацежныя коды — гэта ўнівэрсальны адрас, які пазьбягае раскрыцьця адрасоў вашага кашалька. Не ўсе сэрвісы будуць іх падтрымліваць.", + "header": "Атрымаць", + "reset": "Скінуць", + "maxSats": "Максымальная сума — {max} sats", + "maxSatsFull": "Максымальная сума — {max} sats альбо {currency}", + "minSats": "Мінімальная сума — {min} sats", + "minSatsFull": "Мінімальная сума — {min} sats альбо {currency}", + "qrcode_for_the_address": "QR-код для адрасу" }, "send": { + "provided_address_is_invoice": "Гэты адрас, здаецца, належыць да інвойсу Lightning. Калі ласка, перайдзіце ў ваш кашалёк Lightning, каб аплаціць гэты інвойс.", + "broadcastButton": "Транслюваць", "broadcastError": "Памылка", + "broadcastNone": "Уставіць hex трансакцыі", + "broadcastPending": "У чаканьні", "broadcastSuccess": "Посьпех", - "create_to": "Да" + "confirm_header": "Пацьвердзіць", + "confirm_sendNow": "Даслаць зараз", + "create_amount": "Сума", + "create_broadcast": "Транслюваць", + "create_copy": "Скапіяваць і транслюваць пазьней", + "create_fee": "Камісія", + "create_memo": "Нататка", + "create_satoshi_per_vbyte": "Satoshi за vByte", + "create_details": "Дэталі", + "create_to": "Да", + "create_this_is_hex": "Гэта hex вашай трансакцыі — падпісаны і гатовы да трансьляцыі ў сетку.", + "create_tx_size": "Памер трансакцыі", + "create_verify": "Праверыць на coinb.in", + "details_insert_contact": "Уставіць кантакт", + "details_add_rec_add": "Дадаць атрымальніка", + "details_add_rec_rem": "Выдаліць атрымальніка", + "details_add_recc_rem_all_alert_description": "Вы ўпэўненыя, што хочаце выдаліць усіх атрымальнікаў?", + "details_add_rec_rem_all": "Выдаліць усіх атрымальнікаў", + "details_recipients_title": "Атрымальнікі", + "details_recipient_title": "Атрымальнік #{number} з #{total}", + "please_complete_recipient_title": "Няпоўны атрымальнік", + "please_complete_recipient_details": "Калі ласка, запоўніце дэталі атрымальніка #{number} перад дадаваньнем новага атрымальніка.", + "details_address": "Адрас", + "details_address_field_is_not_valid": "Адрас несапраўдны.", + "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 sats.", + "details_create": "Стварыць інвойс", + "details_error_decode": "Немагчыма дэкадаваць адрас Bitcoin", + "details_fee_field_is_not_valid": "Камісія несапраўдная.", + "details_frozen": "{amount} BTC замарожана.", + "details_next": "Далей", + "details_no_signed_tx": "Выбраны файл ня ўтрымлівае трансакцыі, якую можна імпартаваць.", + "details_note_placeholder": "Нататка сабе", + "details_scan": "Сканаваць", + "details_scan_hint": "Двойчы націсьніце, каб сканаваць альбо імпартаваць пункт прызначэньня", + "details_scan_error": "Памылка сканаваньня", + "details_total_exceeds_balance": "Сума адпраўкі перавышае даступны баланс.", + "details_total_exceeds_balance_frozen": "Сума адпраўкі перавышае даступны баланс. Калі ласка, заўважце, што замарожаныя манэты выключаны.", + "details_unrecognized_file_format": "Нераспазнаны фармат файлу", + "details_wallet_before_tx": "Перш чым ствараць трансакцыю, вы павінны спачатку дадаць кашалёк Bitcoin.", + "dynamic_init": "Ініцыялізацыя", + "dynamic_next": "Далей", + "dynamic_prev": "Папярэдні", + "dynamic_start": "Пачаць", + "dynamic_stop": "Спыніць", + "fee_10m": "10хв", + "fee_1d": "1д", + "fee_3h": "3г", + "fee_custom": "Адвольная", + "insert_custom_fee": "Уставіць камісію", + "fee_fast": "Хуткая", + "fee_medium": "Сярэдняя", + "fee_replace_minvb": "Агульная стаўка камісіі (satoshi за vByte), якую вы хочаце заплаціць, павінна быць вышэйшая за {min} sat/vByte.", + "fee_satvbyte": "у sat/vByte", + "fee_slow": "Павольная", + "header": "Даслаць", + "input_clear": "Ачысьціць", + "input_done": "Гатова", + "input_paste": "Уставіць", + "input_total": "Усяго:", + "permission_camera_message": "Нам патрэбны ваш дазвол на выкарыстаньне камэры.", + "psbt_sign": "Падпісаць трансакцыю", + "invalid_psbt": "Прадастаўлены няправільны PSBT.", + "open_settings": "Адкрыць налады", + "permission_storage_denied_message": "BlueWallet ня можа захаваць гэты файл. Калі ласка, адкрыйце налады вашай прылады і ўключыце дазвол на доступ да сховішча.", + "permission_storage_title": "Дазвол на доступ да сховішча", + "psbt_clipboard": "Скапіяваць у буфэр абмену", + "psbt_this_is_psbt": "Гэта часткова падпісаная трансакцыя Bitcoin (PSBT). Калі ласка, скончыце яе падпісаньне з дапамогай вашага апаратнага кашалька.", + "psbt_tx_export": "Экспартаваць у файл", + "no_tx_signing_in_progress": "Няма падпісаньня трансакцыі ў працэсе.", + "outdated_rate": "Курс быў апошні раз абноўлены: {date}", + "psbt_tx_open": "Адкрыць падпісаную трансакцыю", + "psbt_tx_scan": "Сканаваць падпісаную трансакцыю", + "qr_error_no_qrcode": "Мы не змаглі знайсьці сапраўдны QR-код на выбранай выяве. Пераканайцеся, што выява ўтрымлівае толькі QR-код і ніякага дадатковага зьместу, такога як тэкст альбо кнопкі.", + "reset_amount": "Скінуць суму", + "reset_amount_confirm": "Жадаеце скінуць суму?", + "success_done": "Гатова", + "txSaved": "Файл трансакцыі ({filePath}) быў захаваны.", + "file_saved_at_path": "Файл ({filePath}) быў захаваны.", + "cant_send_to_silentpayment_adress": "Гэты кашалёк ня можа адсылаць на адрасы Silent Payments", + "cant_send_to_bip47": "Гэты кашалёк ня можа адсылаць на плацежныя коды BIP47", + "cant_find_bip47_notification": "Спачатку дадайце гэты плацежны код у кантакты", + "problem_with_psbt": "Праблема з PSBT" }, "settings": { - "electrum_clear_alert_cancel": "Адмяніць", - "save": "Захаваць" + "about": "Пра праграму", + "about_awesome": "Зроблена з выдатнымі", + "about_backup": "Заўсёды рабіце рэзервовую копію вашых ключоў!", + "about_free": "BlueWallet — гэта бясплатны праект з адкрытым зыходным кодам. Створаны карыстальнікамі Bitcoin.", + "about_license": "Ліцэнзія MIT", + "about_release_notes": "Заўвагі да выпуску", + "about_review": "Пакіньце нам водгук", + "performance_score": "Балы прадукцыйнасьці: {num}", + "run_performance_test": "Праверыць прадукцыйнасьць", + "about_selftest": "Запусьціць самаправерку", + "about_selftest_electrum_disabled": "Самаправерка недаступная ў афлайнавым рэжыме Electrum. Калі ласка, адключыце афлайнавы рэжым і паспрабуйце зноў.", + "about_selftest_ok": "Усе ўнутраныя тэсты пасьпяхова пройдзены. Кашалёк працуе добра.", + "about_sm_github": "GitHub", + "about_sm_telegram": "Канал у Telegram", + "privacy_temporary_screenshots": "Дазволіць захоп экрану", + "privacy_temporary_screenshots_instructions": "Абарона ад захопу экрану будзе часова адключана, дазваляючы рабіць здымкі экрану і запіс экрану. Абарона аўтаматычна актывуецца зноў, калі вы закрыеце і адкрыеце BlueWallet.", + "biometrics": "Біямэтрыя", + "biometrics_no_longer_available": "Налады вашай прылады зьмяніліся і больш не адпавядаюць выбраным наладам бясьпекі ў праграме. Калі ласка, паўторна ўключыце біямэтрыю альбо код доступу, а затым перазапусьціце праграму, каб прымяніць гэтыя зьмены.", + "biom_10times": "Вы 10 разоў спрабавалі ўвесьці пароль. Жадаеце скінуць ваша сховішча? Гэта выдаліць усе кашалькі і расшыфруе ваша сховішча.", + "biom_conf_identity": "Калі ласка, пацьвердзіце вашу асобу.", + "biom_remove_decrypt": "Усе вашы кашалькі будуць выдалены, і ваша сховішча будзе расшыфравана. Вы ўпэўненыя, што хочаце працягнуць?", + "currency": "Валюта", + "currency_source": "Курс атрыманы з", + "currency_fetch_error": "Узьнікла памылка пры атрыманьні курсу для выбранай валюты.", + "default_title": "Пры запуску", + "donate": "Ахвяраваць", + "donate_description": "Дапамажыце нам захаваць Blue бясплатнай!", + "electrum_connected": "Падключана", + "electrum_connected_not": "Не падключана", + "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_status": "Статус", + "electrum_preferred_server": "Пераважны сэрвэр", + "electrum_preferred_server_description": "Увядзіце сэрвэр, які вы хочаце, каб ваш кашалёк выкарыстоўваў для ўсіх дзеяньняў Bitcoin. Пасьля ўстаноўкі ваш кашалёк будзе выключна выкарыстоўваць гэты сэрвэр для праверкі балансаў, адпраўкі трансакцыяў і атрыманьня сеткавых дадзеных. Пераканайцеся, што вы давяраеце гэтаму сэрвэру, перш чым яго ўстанаўліваць.", + "electrum_history": "Гісторыя", + "electrum_reset_to_default": "Гэта дазволіць BlueWallet выпадковым чынам выбраць сэрвэр са сьпісу сэрвэраў.", + "electrum_reset": "Скінуць да змаўчаньня", + "electrum_reset_to_default_and_clear_history": "Скінуць да змаўчаньня і ачысьціць гісторыю", + "electrum_saved": "Вашы зьмены пасьпяхова захаваны. Перазапуск BlueWallet можа спатрэбіцца для прымяненьня зьменаў.", + "electrum_unable_to_connect": "Немагчыма падключыцца да {server}.", + "encrypt_decrypt": "Расшыфраваць сховішча", + "encrypt_decrypt_q": "Вы ўпэўненыя, што хочаце расшыфраваць ваша сховішча? Гэта дазволіць атрымаць доступ да вашых кашалькоў без пароля.", + "encrypt_enc_and_pass": "Абаронена паролем", + "encrypt_storage_explanation_headline": "Уключыць шыфраваньне сховішча", + "encrypt_storage_explanation_description_line1": "Уключэньне шыфраваньня сховішча дадае дадатковы ўзровень абароны вашай праграмы, забясьпечваючы спосаб захоўваньня вашых дадзеных на вашай прыладзе. Гэта робіць больш цяжкім доступ да вашай інфармацыі бяз дазволу.", + "encrypt_storage_explanation_description_line2": "Аднак важна ведаць, што гэтае шыфраваньне абараняе толькі доступ да вашых кашалькоў, захаваных у keychain прылады. Яно не ўсталёўвае пароль альбо дадатковую абарону для саміх кашалькоў.", + "encrypted_feature_disabled": "Гэтую функцыю немагчыма выкарыстоўваць з уключаным шыфраваным сховішчам.", + "i_understand": "Я разумею", + "block_explorer": "Аглядальнік блёкаў", + "block_explorer_preferred": "Выкарыстоўваць пераважны аглядальнік блёкаў", + "block_explorer_invalid_custom_url": "Прадастаўлены URL няправільны. Калі ласка, увядзіце сапраўдны URL, які пачынаецца з http:// альбо https://.", + "block_explorer_error_saving_custom": "Памылка пры захаваньні пераважнага аглядальніка блёкаў", + "encrypt_title": "Бясьпека", + "encrypt_tstorage": "Сховішча", + "encrypt_use": "Выкарыстоўваць {type}", + "encrypt_use_expl": "{type} будзе выкарыстоўвацца для пацьверджаньня вашай асобы перад правядзеньнем трансакцыі, разблакаваньнем, экспартам альбо выдаленьнем кашалька.", + "set_as_preferred": "Зрабіць пераважным", + "set_as_preferred_electrum": "Усталяваньне {host}:{port} як пераважнага сэрвэра адключыць выпадковае падключэньне да прапанаванага сэрвэра.", + "general": "Агульныя", + "general_continuity": "Continuity", + "general_continuity_e": "Калі ўключана, вы зможаце праглядаць выбраныя кашалькі і трансакцыі, выкарыстоўваючы вашы іншыя прылады Apple, падключаныя да iCloud.", + "groundcontrol_explanation": "GroundControl — гэта бясплатны сэрвэр пуш-апавяшчэньняў з адкрытым зыходным кодам для кашалькоў Bitcoin. Вы можаце ўсталяваць ваш уласны сэрвэр GroundControl і ўвесьці яго URL сюды, каб не залежаць ад інфраструктуры BlueWallet. Пакіньце пустым, каб выкарыстаць стандартны сэрвэр GroundControl.", + "header": "Налады", + "language": "Мова", + "language_isRTL": "Перазапуск BlueWallet патрабуецца, каб арыентацыя мовы ўступіла ў сілу.", + "last_updated": "Апошняе абнаўленьне", + "license": "Ліцэнзія", + "lightning_error_lndhub_uri": "Няправільны URI LNDhub", + "lightning_error_lndhub_uri_tor": "Няправільны URI LNDhub. Калі ласка, пераканайцеся, што праграма Orbot падключана, і паспрабуйце зноў.", + "lightning_saved": "Вашы зьмены пасьпяхова захаваны.", + "lightning_settings": "Налады Lightning", + "lightning_settings_explain": "Каб падключыцца да вашага ўласнага вузла LND, калі ласка, устанавіце LNDhub і ўвядзіце яго URL тут у наладах. Калі ласка, заўважце, што толькі кашалькі, створаныя пасьля захаваньня зьменаў, будуць падключацца да ўказанага LNDhub.", + "lndhub_github": "Рэпазытар на GitHub", + "network": "Сетка", + "network_broadcast": "Транслюваць трансакцыю", + "network_electrum": "Сэрвэр Electrum", + "electrum_suggested_description": "Калі пераважны сэрвэр ня ўстаноўлены, прапанаваны сэрвэр будзе выпадковым чынам выбіраны для выкарыстаньня.", + "not_a_valid_uri": "Няправільны URI", + "notifications": "Апавяшчэньні", + "open_link_in_explorer": "Адкрыць спасылку ў аглядальніку", + "password": "Пароль", + "password_explain": "Увядзіце пароль, які вы будзеце выкарыстоўваць для разблакаваньня вашага сховішча.", + "plausible_deniability": "Праўдападобнае адмаўленьне", + "privacy": "Прыватнасьць", + "privacy_read_clipboard": "Чытаць буфэр абмену", + "privacy_system_settings": "Сыстэмныя налады", + "privacy_quickactions": "Цэтлікі кашалька", + "privacy_quickactions_explanation": "Націсьніце і ўтрымлівайце значок BlueWallet, каб хутка прагледзець баланс вашага кашалька.", + "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Адключэньне апавяшчэньняў выдаліць усю гэтую інфармацыю з сэрвэра. Акрамя таго, выдаленьне кашалька з праграмы таксама выдаліць зьвязаную зь ім інфармацыю з сэрвэра.", + "selfTest": "Самаправерка", + "save": "Захаваць", + "saved": "Захавана", + "set_electrum_server_as_default": "Усталяваць {server} як стандартны сэрвэр Electrum?", + "set_lndhub_as_default": "Усталяваць {url} як стандартны сэрвэр LNDhub?", + "success_transaction_broadcasted": "Ваша трансакцыя пасьпяхова транслявана!", + "total_balance": "Агульны баланс", + "total_balance_explanation": "Адлюстраваць агульны баланс усіх вашых кашалькоў на віджэтах хатняга экрану.", + "widgets": "Віджэты", + "tools": "Інструмэнты" + }, + "notifications": { + "would_you_like_to_receive_notifications": "Ці жадаеце вы атрымліваць апавяшчэньні аб уваходных плацяжах?", + "notifications_subtitle": "Уваходныя плацяжы і пацьверджаньні трансакцыяў", + "no_and_dont_ask": "Не, і больш не пытайцеся.", + "permission_denied_message": "Вы адмовіліся ад дазволу на дасыланьне вам апавяшчэньняў. Калі вы хочаце атрымліваць апавяшчэньні, калі ласка, уключыце іх у наладах вашай прылады." + }, + "transactions": { + "cancel_explain": "Мы заменім гэтую трансакцыю на тую, што плаціць вам і мае больш высокія камісіі. Гэта эфэктыўна адмяняе бягучую трансакцыю. Гэта называецца RBF — Replace by Fee.", + "cancel_no": "Гэтая трансакцыя не падлягае замене.", + "cancel_title": "Адмяніць гэтую трансакцыю (RBF)", + "transaction_loading_error": "Узьнікла праблема пры загрузцы трансакцыі. Калі ласка, паспрабуйце пазьней.", + "transaction_not_available": "Трансакцыя недаступная", + "confirmations_lowercase": "{confirmations} пацьверджаньняў", + "expand_note": "Разгарнуць нататку", + "cpfp_create": "Стварыць", + "cpfp_exp": "Мы створым яшчэ адну трансакцыю, якая будзе расходаваць вашу непацьверджаную трансакцыю. Агульная камісія будзе вышэйшая за камісію зыходнай трансакцыі, таму яна павінна быць намайнена хутчэй. Гэта называецца CPFP — Child Pays for Parent.", + "cpfp_no_bump": "Камісію гэтай трансакцыі нельга павысіць.", + "cpfp_title": "Павысіць камісію (CPFP)", + "details_balance_hide": "Схаваць баланс", + "details_balance_show": "Паказаць баланс", + "details_copy": "Скапіяваць", + "details_copy_block_explorer_link": "Скапіяваць спасылку аглядальніка блёкаў", + "details_copy_note": "Скапіяваць нататку", + "details_copy_txid": "Скапіяваць ID трансакцыі", + "details_inputs": "Уваходы", + "details_outputs": "Выхады", + "date": "Дата", + "details_received": "Атрымана", + "details_view_in_browser": "Адкрыць у браўзэры", + "details_title": "Трансакцыя", + "incoming_transaction": "Уваходная трансакцыя", + "outgoing_transaction": "Выходная трансакцыя", + "expired_transaction": "Пратэрмінаваная трансакцыя", + "pending_transaction": "Трансакцыя ў чаканьні", + "offchain": "Оф-чэйн", + "onchain": "Он-чэйн", + "details_to": "Выхад", + "list_conf": "Пацьв.: {number}", + "pending": "У чаканьні", + "eta_10m": "Чакана: прыкл. праз 10 хвілінаў", + "eta_3h": "Чакана: прыкл. праз 3 гадзіны", + "eta_1d": "Чакана: прыкл. праз 1 дзень", + "list_title": "Трансакцыі", + "list_title_sent": "Дасланыя", + "list_title_received": "Атрыманыя", + "transaction": "Трансакцыя", + "pending_with_amount": "У чаканьні {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "rbf_title": "Паскорыць (RBF)", + "status_bump": "Паскорыць", + "status_cancel": "Адмяніць", + "transactions_count": "Колькасьць трансакцыяў", + "txid": "ID трансакцыі", + "updating": "Абнаўленьне...", + "watchOnlyWarningTitle": "Папярэджаньне аб бясьпецы", + "watchOnlyWarningDescription": "Сьцеражыцеся ашуканцаў, якія часта выкарыстоўваюць кашалькі „толькі для прагляду”, каб падмануць карыстальнікаў. Гэтыя кашалькі не дазваляюць вам кіраваць ці адсылаць сродкі; яны дазваляюць толькі бачыць баланс.", + "custom_fee_warning_title": "Папярэджаньне", + "custom_fee_warning_description": "Камісіі ніжэйшыя за 1 sat/vB сапраўдныя, але могуць не транслявацца з-за палітыкі вузлоў.", + "rbf_explain": "Мы заменім гэтую трансакцыю на тую, што мае больш высокую камісію, так што яна будзе хутчэй намайнена. Гэта называецца RBF — Replace by Fee.", + "open_url_error": "Немагчыма адкрыць спасылку з браўзэрам па змаўчаньні. Калі ласка, зьмяніце ваш браўзэр па змаўчаньні і паспрабуйце зноў.", + "enable_offline_signing": "Гэты кашалёк не выкарыстоўваецца ў сувязі з афлайнавым падпісаньнем. Жадаеце ўключыць яго зараз?", + "details_eta_analyzing": "Аналіз...", + "details_sent": "Дасланыя", + "details_section": "Дэталі", + "details_explorer": "аглядальнік", + "details_network_fee": "Сеткавая камісія", + "details_to_address": "Да", + "details_id": "ID", + "details_note": "Нататка", + "details_add_note": "дадаць", + "details_advanced": "Дадаткова", + "details_fee_rate": "Стаўка камісіі", + "details_size": "Памер", + "details_virtual_size": "Віртуальны памер", + "details_tx_hex": "Hex трансакцыі", + "details_inputs_count": "Уваходы ({count})", + "details_outputs_count": "Выхады ({count})" }, "wallets": { - "details_save": "Захаваць" + "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Просты і магутны кашалёк Bitcoin", + "add_create": "Стварыць", + "total_balance": "Агульны баланс", + "add_entropy_reset_title": "Скінуць энтрапію", + "add_entropy_reset_message": "Зьмена тыпу кашалька скіне бягучую энтрапію. Жадаеце працягнуць?", + "add_entropy": "Энтрапія", + "add_entropy_bytes": "{bytes} байтаў энтрапіі", + "add_entropy_generated": "{gen} байтаў згенэраванай энтрапіі", + "add_entropy_provide": "Прадаставіць энтрапію праз кідкі касьцяў", + "add_entropy_remain": "{gen} байтаў згенэраванай энтрапіі. Астатнія {rem} байтаў будуць атрыманы ад сыстэмнага генэратара выпадковых лікаў.", + "add_import_wallet": "Імпартаваць кашалёк", + "add_lightning": "Lightning", + "add_lightning_explain": "Для расходаваньня зь імгненнымі трансакцыямі", + "add_lndhub": "Падключыцца да вашага LNDhub", + "add_lndhub_error": "Прадастаўлены адрас вузла зьяўляецца няправільным вузлом LNDhub.", + "add_lndhub_placeholder": "Адрас вашага вузла", + "add_placeholder": "мой першы кашалёк", + "add_title": "Дадаць кашалёк", + "add_wallet_name": "Назва", + "add_wallet_type": "Тып", + "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": "Ачысьціць буфэр абмену пры імпарце", + "details_address": "Адрас", + "details_advanced": "Дадаткова", + "details_are_you_sure": "Вы ўпэўненыя?", + "details_connected_to": "Падключана да", + "details_del_wb_err": "Прадастаўленая сума балансу не супадае з балансам гэтага кашалька. Калі ласка, паспрабуйце зноў.", + "details_del_wb_q": "У гэтага кашалька ёсьць баланс. Перад тым, як працягнуць, зьвярніце ўвагу, што вы ня зможаце аднавіць сродкі без сід-фразы гэтага кашалька. Каб пазьбегнуць выпадковага выдаленьня, увядзіце баланс кашалька — {balance} сатошы.", + "details_delete": "Выдаліць", + "details_delete_wallet": "Выдаліць кашалёк", + "details_delete_wallet_error_message": "Узьнікла праблема з пацьверджаньнем таго, што гэты кашалёк быў выдалены з апавяшчэньняў — гэта можа быць зьвязана з праблемай сеткі альбо слабым злучэньнем. Калі вы працягнеце, вы можаце працягваць атрымліваць апавяшчэньні аб трансакцыях, зьвязаных з гэтым кашальком, нават пасьля яго выдаленьня.", + "details_derivation_path": "шлях дэрывацыі", + "details_display": "Паказаць на хатнім экране", + "details_export_backup": "Экспарт/Рэзервовая копія", + "details_export_history": "Экспартаваць гісторыю ў CSV", + "details_master_fingerprint": "Майстар-адбітак", + "details_multisig_type": "мультыподпіс", + "details_show_xpub": "Паказаць XPUB кашалька", + "details_show_addresses": "Паказаць адрасы", + "details_title": "Кашалёк", + "wallets": "Кашалькі", + "swipe_balance_hide": "Схаваць", + "swipe_balance_show": "Паказаць", + "drag_to_reorder": "Перацягніце для пераўпарадкаваньня", + "clear_search": "Ачысьціць пошук", + "details_type": "Тып", + "details_use_with_hardware_wallet": "Выкарыстоўваць з апаратным кашальком", + "details_yes_delete": "Так, выдаліць", + "enter_bip38_password": "Увесьці пароль для расшыфроўкі", + "export_title": "Экспарт кашалька", + "import_do_import": "Імпартаваць", + "import_passphrase": "Кодавая фраза", + "import_passphrase_title": "Кодавая фраза", + "import_passphrase_message": "Увядзіце кодавую фразу, калі вы яе выкарыстоўвалі", + "import_error": "Не атрымалася імпартаваць. Калі ласка, праверце, што прадастаўленыя дадзеныя сапраўдныя.", + "import_explanation": "Калі ласка, увядзіце словы сід-фразы, публічны ключ, WIF або што заўгодна. BlueWallet паспрабуе адгадаць правільны фармат і імпартаваць ваш кашалёк.", + "import_imported": "Імпартавана", + "import_scan_qr": "Сканаваць альбо імпартаваць файл", + "import_success": "Ваш кашалёк пасьпяхова імпартаваны.", + "import_success_watchonly": "Ваш кашалёк пасьпяхова імпартаваны. ПАПЯРЭДЖАНЬНЕ: гэта кашалёк толькі для прагляду, вы НЕ можаце расходаваць зь яго.", + "import_search_accounts": "Шукаць рахункі", + "import_title": "Імпарт", + "learn_more": "Даведацца больш", + "import_discovery_title": "Адкрыцьцё", + "import_discovery_subtitle": "Выберыце знойдзены кашалёк", + "import_discovery_derivation": "Выкарыстаць адвольны шлях дэрывацыі", + "import_discovery_no_wallets": "Кашалькі не знойдзены.", + "import_discovery_offline": "BlueWallet зараз знаходзіцца ў афлайнавым рэжыме. У гэтым рэжыме ён ня можа праверыць існаваньне кашалька, таму вам спатрэбіцца выбраць правільны ўручную", + "import_derivation_subtitle": "Увядзіце адвольны шлях дэрывацыі, і мы паспрабуем знайсьці ваш кашалёк.", + "import_derivation_found": "Знойдзена", + "import_derivation_found_not": "Не знойдзена", + "import_derivation_loading": "Загрузка...", + "import_derivation_title": "Шлях дэрывацыі", + "import_derivation_unknown": "Невядома", + "import_wrong_path": "Няправільны шлях дэрывацыі", + "list_create_a_button": "Дадаць цяпер", + "list_create_a_wallet": "Дадаць кашалёк", + "list_empty_txs1": "Вашы трансакцыі будуць адлюстроўвацца тут.", + "list_empty_txs1_lightning": "Кашалёк Lightning варта выкарыстоўваць для штодзённых трансакцыяў. Камісіі вельмі танныя, а хуткасьць маланкавая.", + "list_empty_txs2": "Пачніце са свайго кашалька.", + "list_empty_txs2_lightning": "\nКаб пачаць яго выкарыстоўваць, націсьніце Кіраваньне сродкамі і папоўніце ваш баланс.", + "list_latest_transaction": "Апошняя трансакцыя", + "list_long_choose": "Выбраць фота", + "list_create_a_wallet_text": "Гэта бясплатна, і вы можаце ствараць \nіх столькі, колькі захочаце.", + "paste_from_clipboard": "Уставіць", + "import_file": "Імпартаваць файл", + "list_long_scan": "Сканаваць QR-код", + "list_title": "Кашалькі", + "list_tryagain": "Паспрабаваць зноў", + "looks_like_bip38": "Гэта выглядае як прыватны ключ, абаронены паролем (BIP38).", + "manage_title": "Кіраваньне кашалькамі", + "no_results_found": "Нічога не знойдзена.", + "please_continue_scanning": "Калі ласка, працягвайце сканаваньне.", + "select_wallet": "Выбраць кашалёк", + "pull_to_refresh": "Пацягніце, каб абнавіць", + "warning_do_not_disclose": "Ніколі не дзяліцеся прыведзенай ніжэй інфармацыяй", + "identity_pubkey": "Публічны ключ ідэнтычнасьці", + "xpub_title": "XPUB кашалька", + "manage_wallets_search_placeholder": "Шукаць кашалькі, адрасы, трансакцыі і нататкі", + "more_info": "Больш інфармацыі", + "no_ln_wallet_error": "Перш чым аплачваць інвойс Lightning, вы павінны спачатку дадаць кашалёк Lightning.", + "add_ln_wallet_first": "Вы павінны спачатку дадаць кашалёк Lightning.", + "select_no_bitcoin": "Зараз няма даступных кашалькоў Bitcoin.", + "select_no_bitcoin_exp": "Каб папоўніць кашалькі Lightning, патрэбны кашалёк Bitcoin. Калі ласка, стварыце альбо імпартуйце адзін.", + "wallet_type_this": "Тып гэтага кашалька — {type}.", + "share_number": "Падзяліцца {number}", + "scan_import": "Адсканіруйце гэты QR-код, каб імпартаваць ваш кашалёк у іншую праграму.", + "write_down_header": "Стварыце ручную рэзервовую копію", + "write_down": "Запішыце і бясьпечна захавайце гэтыя словы. Выкарыстоўвайце іх, каб аднавіць кашалёк пазьней.", + "copy_ln_url": "Скапіюйце і бясьпечна захавайце гэты URL, каб аднавіць ваш кашалёк пазьней.", + "copy_ln_public": "Скапіюйце і бясьпечна захавайце гэтую інфармацыю, каб аднавіць ваш кашалёк пазьней.", + "details_delete_anyway": "Усё роўна выдаліць" + }, + "total_balance_view": { + "display_in_bitcoin": "Паказаць у Bitcoin", + "hide": "Схаваць", + "display_in_sats": "Паказаць у sats", + "display_in_fiat": "Паказаць у {currency}", + "title": "Агульны баланс", + "explanation": "Прагляд агульнага балансу ўсіх вашых кашалькоў на экране агляду." + }, + "multisig": { + "multisig_vault": "Мультыподпісны сэйф", + "default_label": "Мультыподпісны сэйф", + "multisig_vault_explain": "Найлепшая бясьпека для вялікіх сумаў", + "provide_signature": "Падпісаць", + "provide_signature_details": "Выкарыстайце вашу прыладу і кашалёк, дзе знаходзіцца ключ, каб падпісаць гэтую трансакцыю", + "provide_signature_details_bluewallet": "У BlueWallet перайдзіце ў мэню экрану Даслаць і выберыце ", + "provide_signature_next_steps": "Сканаваць альбо імпартаваць падпісаную трансакцыю", + "provide_signature_next_steps_details": "Як толькі ваш кашалёк пасьпяхова падпіша трансакцыю, адсканіруйце прадастаўлены QR-код альбо імпартуйце суправаджальны файл, а затым праверце ўсе дэталі трансакцыі перад яе трансьляцыяй.", + "vault_key": "Ключ сэйфу {number}", + "required_keys_out_of_total": "Патрабуемых ключоў з агульнай колькасьці", + "fee": "Камісія: {number}", + "fee_btc": "{number} BTC", + "confirm": "Пацьвердзіць", + "header": "Даслаць", + "share": "Падзяліцца...", + "view": "Прагляд", + "shared_key_detected": "Агульны сападпісант", + "shared_key_detected_question": "З вамі падзяліліся сападпісантам, ці жадаеце вы яго імпартаваць?", + "manage_keys": "Кіраваньне ключамі", + "signatures_required_to_spend": "Патрабуецца подпісаў: {number}", + "signatures_we_can_make": "можам зрабіць {number}", + "scan_or_import_file": "Сканаваць альбо імпартаваць файл", + "export_coordination_setup": "Экспартаваць наладку каардынацыі", + "cosign_this_transaction": "Падпісаць гэтую трансакцыю?", + "lets_start": "Пачнем", + "create": "Стварыць", + "native_segwit_title": "Найлепшая практыка", + "wrapped_segwit_title": "Найлепшая сумяшчальнасьць", + "legacy_title": "Састарэлы", + "co_sign_transaction": "Падпісаць трансакцыю", + "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": "для расходаваньня.", + "vault_advanced_customize": "Налады сэйфу", + "quorum": "{m} з {n} (кворум)", + "quorum_header": "Кворум", + "of": "з", + "wallet_type": "Тып кашалька", + "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_path": "Уставіць шлях дэрывацыі", + "ms_help": "Дапамога", + "ms_help_title": "Як працуюць мультыподпісныя сэйфы: парады і хітрыкі", + "ms_help_text": "Кашалёк зь некалькімі ключамі, для павышанай бясьпекі альбо сумеснага захоўваньня", + "ms_help_title1": "Рэкамэндуюцца некалькі прылад.", + "ms_help_title2": "Рэдагаваньне ключоў", + "ms_help_title3": "Рэзервовыя копіі сэйфу", + "ms_help_title4": "Імпарт сэйфаў", + "ms_help_title5": "Пашыраны рэжым", + "ms_help_1": "Сэйф будзе працаваць зь іншымі праграмамі BlueWallet і кашалькамі, сумяшчальнымі з PSBT, такімі як Electrum, Specter, Coldcard, Cobo Vault і г.д.", + "ms_help_2": "Вы можаце стварыць усе ключы сэйфу на гэтай прыладзе і выдаліць альбо адрэдагаваць іх пазьней. Маючы ўсе ключы на адной прыладзе, бясьпека эквівалентна звычайнаму кашальку Bitcoin.", + "ms_help_3": "У наладах кашалька вы знойдзеце рэзэрвовую копію сэйфа і watch-only копію. Гэтая рэзэрвовая копія — як мапа да вашага кашалька. Яна вельмі важная для аднаўленьня кашалька, калі вы згубіце адну з сід-фразаў.", + "ms_help_4": "Каб імпартаваць multisig, выкарыстайце файл рэзэрвовай копіі і функцыю «Імпарт». Калі ў вас ёсьць толькі сід-фразы і XPUB, можаце выкарыстаць асобную кнопку імпарту пры стварэньні ключоў сэйфа.", + "ms_help_5": "Па змаўчаньні BlueWallet створыць сэйф 2-з-3. Каб стварыць іншы кворум альбо зьмяніць тып адрасу, актывуйце Пашыраны рэжым у наладах.", + "input_fp_explain": "Прапусьціце, каб выкарыстаць стандартны (00000000)", + "input_path_explain": "Прапусьціце, каб выкарыстаць стандартны ({default})", + "how_many_signatures_can_bluewallet_make": "колькі подпісаў можа зрабіць BlueWallet" + }, + "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-словнай частковай мнэмонічнай фразай. Калі ласка, паспрабуйце зноў." + }, + "cc": { + "change": "Рэшта", + "coins_selected": "Выбрана манэт ({number})", + "selected_summ": "{value} выбрана", + "empty": "У гэтым кашальку зараз няма манэт.", + "tip": "Гэтая функцыя дазваляе вам бачыць, пазначаць, замарозьваць альбо выбіраць манэты для лепшага кіраваньня кашальком. Вы можаце выбіраць некалькі манэт, націскаючы на каляровыя кругі.", + "freeze": "Замарозіць", + "freezeLabel": "Замарозіць", + "freezeLabel_un": "Размарозіць", + "header": "Кіраваньне UTXO", + "use_coin": "Выкарыстаць манэту", + "use_coins": "Выкарыстаць манэты", + "sort_asc": "Па ўзрастаньні", + "sort_desc": "Па зьніжэньні", + "sort_height": "Вышыня", + "sort_value": "Значэньне", + "sort_label": "Метка", + "sort_status": "Статус", + "sort_by": "Сартаваць па" + }, + "units": { + "BTC": "BTC", + "MAX": "Макс", + "sat_vbyte": "sat/vByte", + "sats": "sats" + }, + "addresses": { + "copy_private_key": "Скапіяваць прыватны ключ", + "sensitive_private_key": "Папярэджаньне: прыватныя ключы вельмі канфідэнцыйныя. Працягнуць?", + "sign_title": "Падпісаць/праверыць паведамленьне", + "sign_help": "Тут вы можаце стварыць альбо праверыць крыптаграфічны подпіс на аснове адрасу Bitcoin.", + "sign_sign": "Падпісаць", + "sign_verify": "Праверыць", + "sign_signature_correct": "Праверка пасьпяховая!", + "sign_signature_incorrect": "Праверка ня ўдалася!", + "sign_placeholder_address": "Адрас", + "sign_placeholder_message": "Паведамленьне", + "sign_placeholder_signature": "Подпіс", + "addresses_title": "Адрасы", + "type_change": "Рэшта", + "type_receive": "Атрымаць", + "type_used": "Выкарыстана", + "transactions": "Трансакцыі" + }, + "lnurl_auth": { + "register_question_part_1": "Жадаеце зарэгістраваць рахунак на", + "register_question_part_2": "выкарыстоўваючы ваш кашалёк Lightning?", + "register_answer": "Вы пасьпяхова зарэгістравалі рахунак на {hostname}!", + "login_question_part_1": "Жадаеце ўвайсьці на", + "login_question_part_2": "выкарыстоўваючы ваш кашалёк Lightning?", + "login_answer": "Вы пасьпяхова ўвайшлі на {hostname}!", + "link_question_part_1": "Жадаеце прывязаць ваш рахунак на", + "link_question_part_2": "да вашага кашалька Lightning?", + "link_answer": "Ваш кашалёк Lightning пасьпяхова прывязаны да вашага рахунку на {hostname}!", + "auth_question_part_1": "Жадаеце аўтэнтыфікавацца на", + "auth_question_part_2": "выкарыстоўваючы ваш кашалёк Lightning?", + "auth_answer": "Вы пасьпяхова аўтэнтыфікаваны на {hostname}!", + "could_not_auth": "Мы не змаглі аўтэнтыфікаваць вас на {hostname}.", + "authenticate": "Аўтэнтыфікавацца" + }, + "bip47": { + "payment_code": "Плацежны код", + "contacts": "Кантакты", + "bip47_explain": "Шматразовы код, якім можна дзяліцца", + "bip47_explain_subtitle": "BIP47", + "purpose": "Шматразовы код, якім можна дзяліцца (BIP47)", + "pay_this_contact": "Заплаціць гэтаму кантакту", + "rename_contact": "Перайменаваць кантакт", + "copy_payment_code": "Скапіяваць плацежны код", + "hide_contact": "Схаваць кантакт", + "rename": "Перайменаваць", + "provide_name": "Увядзіце новую назву для гэтага кантакту", + "add_contact": "Дадаць кантакт", + "provide_payment_code": "Прадаставіць плацежны код", + "invalid_pc": "Няправільны плацежны код", + "notification_tx_unconfirmed": "Трансакцыя-паведамленьне яшчэ не пацьверджана, калі ласка, пачакайце", + "onchain_tx_needed": "Патрабуецца он-чэйн трансакцыя", + "notif_tx_sent": "Трансакцыя-паведамленьне дасланая. Калі ласка, дачакайцеся яе пацьверджаньня", + "notif_tx": "Трансакцыя-паведамленьне", + "failed_create_notif_tx": "Не атрымалася стварыць он-чэйн трансакцыю", + "not_found": "Плацежны код не знойдзены" } } diff --git a/loc/bg_bg.json b/loc/bg_bg.json index 424891a7323..96bb1a9cb06 100644 --- a/loc/bg_bg.json +++ b/loc/bg_bg.json @@ -3,34 +3,49 @@ "bad_password": "Грешна парола, опитайте отново.", "cancel": "Отказ", "continue": "Продължи", + "clipboard": "Клипборд", + "copied": "Копирано!", + "discard_changes": "Отказваш промените?", + "discard_changes_explain": "Имате незапазени промени. Сигурни ли сте, че искате да ги отхвърлите и да напуснете екрана?", "enter_password": "Въведете парола", "never": "Никога", "of": "{number} от {total}", "ok": "OK", + "enter_url": "Въведете URL", "storage_is_encrypted": "Вашият портфейл е криптиран. Необходима е парола за декриптиране", "yes": "Да", "no": "Не", - "save": "Запази", - "seed": "Сиид", + "save": "Запази...", + "seed": "Сийд", "success": "Успех", "wallet_key": "Парола на портфейла", - "invalid_animated_qr_code_fragment": "Невалиден анимиран QRCode фрагмент. Моля, опитай отново.", - "file_saved": "Файлът {filePath} беше запазен в {destination}.", - "downloads_folder": "Папка с изтегляния" + "close": "Затвори", + "change_input_currency": "Промени валутата за въвеждане", + "refresh": "Обнови", + "pick_image": "Избери от библиотеката", + "pick_file": "Избери файл", + "enter_amount": "Въведете сума", + "qr_custom_input_button": "Докоснете 10 пъти за персонализирано въвеждане", + "unlock": "Отключи", + "port": "Порт", + "ssl_port": "SSL порт", + "suggested": "Препоръчан" }, "azteco": { - "codeIs": "Цода на вашият ваучър е", - "errorBeforeRefeem": "Преди осребряване, трябва да добавиш Бикойн портфейл.", - "errorSomething": "Възникна грешка. Уверете се, че ваучъра е валиден?", - "redeem": "Депозирай в уолета", + "codeIs": "Кодът на вашия ваучър е", + "errorBeforeRefeem": "Преди осребряване, трябва да добавиш Биткойн портфейл.", + "errorSomething": "Възникна грешка. Уверете се, че ваучърът е валиден?", + "redeem": "Депозирай в портфейла", "redeemButton": "Осребри", "success": "Успех", + "successMessage": "Ваучерът е осребрен успешно! Вашите средства трябва да пристигнат в Bitcoin портфейла ви скоро.", "title": "Осребри Azte.co ваучър" }, "entropy": { "save": "Запази", "title": "Ентропия", - "undo": "Отмени" + "undo": "Отмени", + "amountOfEntropy": "{bits} от {limit} бита" }, "errors": { "broadcast": "Неуспешно изпращане", @@ -38,57 +53,70 @@ "network": "Грешка с мрежата" }, "lnd": { - "errorInvoiceExpired": "Изтекла фактура", + "errorInvoiceExpired": "Фактурата е изтекла.", "expired": "Изтекла", + "expiresIn": "Изтича след {time} минути", "payButton": "Плати", - "placeholder": "Фактура", + "payment": "Плащане", + "placeholder": "Фактура или адрес", "potentialFee": "Възможна такса: {fee}", "refill": "Зареди", "refill_create": "За да продължите, моля създайте Биткойн портфейл", "refill_external": "Зареди с Външен Портфейл", "refill_lnd_balance": "Зареди Лайтнинг Баланс", - "sameWalletAsInvoiceError": "Не можете да платите фактура със същия портфейл в който е създадена", + "sameWalletAsInvoiceError": "Не можете да платите фактура със същия портфейл, който е използван за нейното създаване.", "title": "Управление на средства" }, "lndViewInvoice": { "additional_info": "Допълнителна информация", "for": "За:", "lightning_invoice": "Лайтнинг фактура", - "open_direct_channel": "Директно свързване с нода:", + "please_pay_between_and": "Моля, платете между {min} и {max}", "please_pay": "Моля, плати", + "preimage": "Прообраз", "sats": "сатоши", + "date_time": "Дата и час", "wasnt_paid_and_expired": "Фактурата не е платена и е изтекла." }, "plausibledeniability": { "create_fake_storage": "Създайте Криптирано Хранилище", - "create_password": "Изберете парола", "create_password_explanation": "Паролата за 'Фалшивото' хранилище трябва да е различна от паролата за главното хранилище", - "help": "При определени обстоятелства, може да се наложи да предадете Вашата парола. За да предпазите средствата си, Блу Уолет може да създаде допълнително хранилище с различна парола. Ако сте в ситуация където сте принудени да предадете Вашата парола, дайте паролата за фалшивото хранилище. Когато я въведете, Блу Уолет ще отключи 'Фалшивото' хранилище. Портфеиля изглежда легитимен, като в същото време средствата ви ще са в безопасност.", - "help2": "Новото хранилище ще бъде напълно функционално и може да държите минимални средства в него. Така ще изглежда като легитимен портфеил.", + "help": "При определени обстоятелства, може да се наложи да предадете Вашата парола. За да предпазите средствата си, Блу Уолет може да създаде допълнително хранилище с различна парола. Ако сте в ситуация където сте принудени да предадете Вашата парола, дайте паролата за фалшивото хранилище. Когато я въведете, Блу Уолет ще отключи 'Фалшивото' хранилище. Портфейлът изглежда легитимен, като в същото време средствата ви ще са в безопасност.", + "help2": "Новото хранилище ще бъде напълно функционално и може да държите минимални средства в него. Така ще изглежда като легитимен портфейл.", "password_should_not_match": "Моля, изберете различна парола.", - "passwords_do_not_match": "Паролите не съвпадат. Моля, опитайте отново.", - "retype_password": "Повторете паролата", - "success": "Успех", - "title": "Plausible Deniability" + "title": "Правдоподобно отричане" }, "pleasebackup": { - "ask": "Запазихте ли паролата - 12/24 думи за портфейла? В случай, че изгубите достъп до устройството, паролата е необходима за да въстановите средствата. В случай, че загубите паролата - 12/24 думи, перманентно ще изгубите достъп до средствата.", - "ask_no": "Не, не съм", - "ask_yes": "Да", - "text_lnd": "Моля, запазете паролата/ думите. Те ви позволяват да възтановите портфейла и средствата си на друго устройство." + "ask": "Запазихте ли сийд фразата - 12/24 думи за портфейла? В случай, че изгубите достъп до устройството, сийд фразата е необходима за да възстановите средствата. В случай, че загубите сийд фразата - 12/24 думи, перманентно ще изгубите достъп до средствата.", + "ask_no": "Не, не съм.", + "ask_yes": "Да, запазих ги.", + "ok": "OK, записах ги.", + "ok_lnd": "Да, запазих сийда.", + "text": "Моля, отделете малко време, за да запишете тази мнемонична фраза на лист хартия.\nТова е вашето резервно копие и можете да го използвате, за да възстановите портфейла.", + "text_lnd": "Моля, запазете сийд фразата. Тя ви позволява да възстановите портфейла и средствата си на друго устройство.", + "title": "Портфейла е създаден." }, "receive": { "details_create": "Създай", "details_label": "Описание", "details_setAmount": "Получаване на определена сума", - "details_share": "Сподели", - "header": "Получаване" + "details_share": "Сподели...", + "address_not_found": "Не може да се генерира адрес за получаване.", + "header": "Получаване", + "reset": "Нулирай", + "maxSats": "Максималната сума е {max} сатоши", + "maxSatsFull": "Максималната сума е {max} сатоши или {currency}", + "minSats": "Минималната сума е {min} сатоши", + "minSatsFull": "Минималната сума е {min} сатоши или {currency}", + "qrcode_for_the_address": "QR код за адреса", + "bip47_explanation": "Платежните кодове са универсален адрес, който избягва разкриването на адресите на портфейла ви. Не всички услуги ги поддържат." }, "send": { + "provided_address_is_invoice": "Този адрес изглежда е за Lightning фактура. Моля, отидете в Lightning портфейла си, за да извършите плащане по тази фактура.", "broadcastButton": "Изпрати", "broadcastError": "Грешка", "broadcastNone": "Въведи Transaction Hex", - "broadcastPending": "Не потвърдена транзакция", + "broadcastPending": "Чакаща", "broadcastSuccess": "Успех", "confirm_header": "Потвърди", "confirm_sendNow": "Изпрати сега", @@ -98,28 +126,42 @@ "create_details": "Детайли", "create_fee": "Такса", "create_memo": "Бележка", - "create_this_is_hex": "Това е вашата трансакция/и hex-подписана и гова/и за изпращане", + "create_satoshi_per_vbyte": "Сатоши на vByte", + "create_this_is_hex": "Това е вашата транзакция/и hex-подписана и готова/и за изпращане", "create_to": "До", "create_tx_size": "Размер на Транзакцията", "create_verify": "Потвърди в coinb.in", + "details_insert_contact": "Въведи контакт", "details_add_rec_add": "Добави Контакт", "details_add_rec_rem": "Премахни Контакт", + "details_add_recc_rem_all_alert_description": "Сигурни ли сте, че искате да премахнете всички получатели?", + "details_add_rec_rem_all": "Премахни всички получатели", + "details_recipients_title": "Получатели", + "details_recipient_title": "Получател #{number} от #{total}", + "please_complete_recipient_title": "Непълен получател", + "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": "Избраният файл не съдържа транзакция която може да бъде въведена.", + "details_no_signed_tx": "Избраният файл не съдържа транзакция, която може да бъде импортирана.", "details_note_placeholder": "Бележка за мен", "details_scan": "Сканирай", - "details_total_exceeds_balance": "Сумата надвишава наличният баланс.", + "details_scan_hint": "Докоснете два пъти, за да сканирате или импортирате дестинация", + "details_scan_error": "Грешка при сканиране", + "details_total_exceeds_balance": "Сумата надвишава наличния баланс.", + "details_total_exceeds_balance_frozen": "Сумата за изпращане надвишава наличния баланс. Моля, имайте предвид, че замразените монети са изключени.", "details_unrecognized_file_format": "Непознат файл формат", "details_wallet_before_tx": "Преди създаване на транзакция, трябва да създадете Биткойн портфейл.", "dynamic_init": "Начало", @@ -131,106 +173,532 @@ "fee_1d": "1 ден", "fee_3h": "3ч", "fee_custom": "Персонализиран", + "insert_custom_fee": "Въведете такса", "fee_fast": "Бързо", "fee_medium": "Средно", + "fee_replace_minvb": "Общата ставка на таксата (сатоши на vByte), която искате да платите, трябва да е по-висока от {min} sat/vByte.", + "fee_satvbyte": "в sat/vByte", "fee_slow": "Бавно", "header": "Изпрати", "input_clear": "Изчисти", "input_done": "Готово", "input_paste": "Постави", - "input_total": "Тотално:", + "input_total": "Общо:", "permission_camera_message": "Необходимо е вашето разрешение за достъп до камерата.", - "permission_camera_title": "Разрешение за достъп до камерата", "psbt_sign": "Подпиши транзакция", + "invalid_psbt": "Предоставен е невалиден PSBT.", "open_settings": "Отвори настройки", - "permission_storage_later": "Питай ме по-късно", - "permission_storage_message": "Блу Уолет се нуждае от достъп до папките с файлове за да запази този файл.", + "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}", "psbt_tx_open": "Отвори подписана транзакция", "psbt_tx_scan": "Подпиши транзакция", + "qr_error_no_qrcode": "Не успяхме да намерим валиден QR код в избраното изображение. Уверете се, че изображението съдържа само QR код и няма допълнително съдържание като текст или бутони.", + "reset_amount": "Нулирай сумата", + "reset_amount_confirm": "Искате ли да нулирате сумата?", "success_done": "Готово", - "txSaved": "Файл с трансакцията ({filePath}) беше запазен в папката Свалени.", + "txSaved": "Файлът с транзакцията ({filePath}) беше запазен.", + "file_saved_at_path": "Файлът ({filePath}) беше запазен.", + "cant_send_to_silentpayment_adress": "Този портфейл не може да изпраща към Silent Payments адреси", + "cant_send_to_bip47": "Този портфейл не може да изпраща към BIP47 платежни кодове", + "cant_find_bip47_notification": "Първо добавете този платежен код към контактите", "problem_with_psbt": "Проблем с ЧПБТ / PSBT" }, "settings": { "about": "Повече", "about_awesome": "Създаден с любов", - "about_backup": "Не забравяйте да направите копие от паролата/думите!", + "about_backup": "Винаги архивирайте ключовете си!", "about_free": "Блу Уолет е безплатен и отворен код проект. Създаден от Биткойн фенове", "about_license": "MIT Лиценз", "about_release_notes": "Промени и Подобрения", "about_review": "Напишете мнение", + "performance_score": "Резултат за производителност: {num}", + "run_performance_test": "Тествай производителността", "about_selftest": "Направете селф-тест", - "about_selftest_ok": "Теста прмина успешно. Портфейла работи отлично.", + "block_explorer_invalid_custom_url": "Предоставеният URL е невалиден. Моля, въведете валиден URL, започващ с http:// или https://.", + "about_selftest_electrum_disabled": "Самотестът не е достъпен в офлайн режим на Electrum. Моля, изключете офлайн режима и опитайте отново.", + "about_selftest_ok": "Тестът премина успешно. Портфейлът работи отлично.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord", "about_sm_telegram": "Telegram", - "about_sm_twitter": "Последвайте ни в Twitter", - "advanced_options": "Допълнителни Опции", + "privacy_temporary_screenshots": "Разреши заснемане на екрана", + "privacy_temporary_screenshots_instructions": "Защитата срещу заснемане на екрана ще бъде временно изключена, което позволява екранни снимки и запис на екрана. Защитата ще се активира автоматично, когато затворите и отворите отново BlueWallet.", "biometrics": "Биометрични данни", + "biometrics_no_longer_available": "Настройките на устройството ви са се променили и вече не съответстват на избраните настройки за сигурност в приложението. Моля, активирайте отново биометрията или кода за достъп и след това рестартирайте приложението, за да приложите тези промени.", "biom_10times": "Опитахте се да въведете вашата парола 10 пъти. Желаете ли да направите ресет на апликацията? Това ще изтрие всички портфейли и ще декриптира хранилището.", "biom_conf_identity": "Моля, потвърдете вашата самоличност.", - "biom_no_passcode": "Вашето устройство няма създадена парола. За да продължите, конфигурирайте парола в 'Настройки'на устройството.", + "biom_no_passcode": "Вашето устройство няма зададен код за достъп или активирана биометрия. За да продължите, моля, конфигурирайте код за достъп или биометрия в приложението Настройки.", "biom_remove_decrypt": "Всички портфейли ще бъдат изтрити и хранилището ще бъде декриптирано. Сигурни ли сте, че искате да продължите?", "currency": "Валута", - "default_info": "Информация", + "currency_source": "Курсът се получава от", + "currency_fetch_error": "Възникна грешка при получаването на курса за избраната валута.", "default_title": "При Стартиране", - "default_wallets": "Виж всички портфейли", + "donate": "Дарение", + "donate_description": "Помогнете ни да запазим Blue безплатен!", "electrum_connected": "Свързан", "electrum_connected_not": "Няма връзка", - "electrum_error_connect": "Невъзможно свързване със посочения Електрум сървър", + "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_saved": "Промените бяха запазени успешно. Моля, рестартирайте Блу Уолет за да видите промените.", - "set_electrum_server_as_default": "Задайте {server} като Електрум сървър по подразбиране? ", - "set_lndhub_as_default": "Задайте {url} като LNDHub сървър по подразбиране?", + "set_electrum_server_as_default": "Задайте {server} като Електрум сървър по подразбиране?", + "set_lndhub_as_default": "Задайте {url} като LNDhub сървър по подразбиране?", + "electrum_settings_server": "Electrum сървър", "electrum_status": "Статус", - "electrum_clear_alert_title": "Изчисти историята?", - "electrum_clear_alert_message": "Искате ли да изтриете електрум сървър историята?", - "electrum_clear_alert_cancel": "Отказ", - "electrum_clear_alert_ok": "Ок", - "electrum_select": "Избери", + "electrum_preferred_server": "Предпочитан сървър", + "electrum_preferred_server_description": "Въведете сървъра, който искате портфейлът ви да използва за всички Bitcoin дейности. След като бъде зададен, портфейлът ви ще използва изключително този сървър за проверка на баланси, изпращане на транзакции и получаване на мрежови данни. Уверете се, че имате доверие на този сървър, преди да го зададете.", + "electrum_unable_to_connect": "Невъзможно свързване със сървър {server}.", + "electrum_history": "История", + "electrum_reset_to_default": "Това ще позволи на BlueWallet да избере произволно сървър от списъка със сървъри.", "electrum_reset": "Начални настройки", - "electrum_unable_to_connect": "Не възможно свързване със сървър {server}.", - "electrum_history": "Сървър история", - "electrum_reset_to_default": "Сигурни ли сте, че искате да върнете Електрум към първоначалните настройки?", - "electrum_clear": "Изчисти", + "electrum_reset_to_default_and_clear_history": "Възстановяване по подразбиране и изчистване на историята", "encrypt_decrypt": "Декриптирай хранилището", "encrypt_decrypt_q": "Сигурни ли сте, че искате да декриптирате хранилището? Това ще направи портфейлите ви достъпни без парола.", - "encrypt_enc_and_pass": "Криптиран и защитен с парола", + "encrypt_enc_and_pass": "Защитен с парола", + "encrypt_storage_explanation_headline": "Активиране на криптираното хранилище", + "encrypt_storage_explanation_description_line1": "Активирането на криптираното хранилище добавя допълнителен слой защита към приложението ви, като защитава начина, по който данните ви се съхраняват на устройството. Това затруднява достъпа на трети страни до информацията ви без разрешение.", + "encrypt_storage_explanation_description_line2": "Важно е обаче да знаете, че това криптиране защитава само достъпа до портфейлите ви, съхранявани в ключодържателя на устройството. То не поставя парола или допълнителна защита върху самите портфейли.", + "i_understand": "Разбирам", + "block_explorer": "Блок експлорър", + "block_explorer_preferred": "Използвай предпочитан блок експлорър", + "block_explorer_error_saving_custom": "Грешка при запазване на предпочитания блок експлорър", "encrypt_title": "Сигурност", "encrypt_tstorage": "Хранилище", "encrypt_use": "Използвай {type}", + "set_as_preferred": "Задай като предпочитан", + "set_as_preferred_electrum": "Задаването на {host}:{port} като предпочитан сървър ще деактивира свързването с произволен предложен сървър.", + "encrypted_feature_disabled": "Тази функция не може да се използва, когато е активирано криптираното хранилище.", + "encrypt_use_expl": "{type} ще се използва за потвърждаване на самоличността ви преди извършване на транзакция, отключване, експортиране или изтриване на портфейл.", + "biometrics_fail": "Ако {type} не е активирана или не успее да отключи, можете да използвате кода за достъп на устройството като алтернатива.", "general": "Основно", - "general_adv_mode": "Развирени Настройки", - "plausible_deniability": "Plausible Deniability", - "retype_password": "Повторете паролата", - "save": "Запази" - }, - "notifications": { - "ask_me_later": "Питай ме по-късно" + "general_continuity": "Непрекъснатост", + "general_continuity_e": "Когато е активирано, ще можете да преглеждате избрани портфейли и транзакции с помощта на другите си устройства, свързани с Apple iCloud.", + "groundcontrol_explanation": "GroundControl е безплатен сървър за push известия с отворен код за Bitcoin портфейли. Можете да инсталирате собствен GroundControl сървър и да поставите неговия URL тук, за да не разчитате на инфраструктурата на BlueWallet. Оставете празно, за да използвате сървъра по подразбиране на GroundControl.", + "header": "Настройки", + "language": "Език", + "last_updated": "Последно обновено", + "language_isRTL": "Рестартирането на BlueWallet е необходимо, за да влезе в сила ориентацията на езика.", + "license": "Лиценз", + "lightning_error_lndhub_uri": "Невалиден LNDhub URI", + "lightning_error_lndhub_uri_tor": "Невалиден LNDhub URI. Моля, уверете се, че Orbot е свързан и опитайте отново.", + "lightning_saved": "Промените бяха запазени успешно.", + "lightning_settings": "Lightning настройки", + "lightning_settings_explain": "За да се свържете със собствения си LND възел, моля, инсталирайте LNDhub и поставете неговия URL тук в настройките. Моля, имайте предвид, че само портфейли, създадени след запазване на промените, ще се свържат с посочения LNDhub.", + "lndhub_github": "GitHub хранилище", + "network": "Мрежа", + "network_broadcast": "Излъчи транзакция", + "network_electrum": "Electrum сървър", + "electrum_suggested_description": "Когато не е зададен предпочитан сървър, ще бъде избран произволен предложен сървър.", + "not_a_valid_uri": "Невалиден URI", + "notifications": "Известия", + "open_link_in_explorer": "Отвори връзката в експлорър", + "password": "Парола", + "password_explain": "Въведете паролата, която ще използвате за отключване на хранилището си.", + "plausible_deniability": "Правдоподобно отричане", + "privacy": "Поверителност", + "privacy_read_clipboard": "Чети клипборда", + "privacy_system_settings": "Системни настройки", + "privacy_quickactions": "Преки пътища за портфейла", + "privacy_quickactions_explanation": "Докоснете и задръжте иконата на приложението BlueWallet, за да видите бързо баланса на портфейла си.", + "privacy_clipboard_explanation": "Предоставя преки пътища, ако в клипборда ви се намери адрес или фактура.", + "privacy_do_not_track": "Деактивирай анализите", + "privacy_do_not_track_explanation": "Информацията за производителността и надеждността няма да бъде изпращана за анализ.", + "rate": "Курс", + "push_notifications_explanation": "Чрез активиране на известията, токенът на устройството ви ще бъде изпратен на сървъра, заедно с адресите на портфейлите и идентификаторите на транзакциите за всички портфейли и транзакции, направени след активиране на известията. Токенът на устройството се използва за изпращане на известия, а информацията за портфейла ни позволява да ви известяваме за входящи Bitcoin или потвърждения на транзакции.\n\nПредава се само информация след като активирате известията — нищо от преди не се събира.\n\nДеактивирането на известията ще премахне цялата тази информация от сървъра. Освен това, изтриването на портфейл от приложението също ще премахне свързаната с него информация от сървъра.", + "selfTest": "Самотест", + "save": "Запази", + "saved": "Запазено", + "success_transaction_broadcasted": "Вашата транзакция беше успешно излъчена!", + "total_balance": "Общ баланс", + "total_balance_explanation": "Показва общия баланс на всички ваши портфейли в джаджите на началния екран.", + "widgets": "Джаджи", + "tools": "Инструменти" }, "transactions": { + "cancel_explain": "Ще заменим тази транзакция с такава, която плаща на вас и има по-висока такса. Това на практика отменя текущата транзакция. Това се нарича RBF — Replace by Fee.", + "cancel_no": "Тази транзакция не може да бъде заменена.", + "cancel_title": "Отмени тази транзакция (RBF)", + "transaction_loading_error": "Имаше проблем при зареждане на транзакцията. Моля, опитайте отново по-късно.", + "transaction_not_available": "Транзакцията не е налична", + "confirmations_lowercase": "{confirmations} потвърждения", + "expand_note": "Разшири бележката", "cpfp_create": "Създай", - "pending": "Не потвърдена транзакция" + "cpfp_exp": "Ще създадем друга транзакция, която изразходва вашата непотвърдена транзакция. Общата такса ще бъде по-висока от таксата на оригиналната транзакция, така че би трябвало да бъде минирана по-бързо. Това се нарича CPFP — Child Pays for Parent.", + "cpfp_no_bump": "Тази транзакция не може да бъде ускорена.", + "cpfp_title": "Увеличи такса (CPFP)", + "details_balance_hide": "Скрий баланса", + "details_balance_show": "Покажи баланса", + "details_copy": "Копирай", + "details_copy_block_explorer_link": "Копирай линка към блок експлоръра", + "details_copy_note": "Копирай бележката", + "details_copy_txid": "Копирай ID на транзакцията", + "details_inputs": "Входове", + "details_outputs": "Изходи", + "date": "Дата", + "details_received": "Получено", + "details_view_in_browser": "Виж в браузъра", + "details_title": "Транзакция", + "incoming_transaction": "Входяща транзакция", + "outgoing_transaction": "Изходяща транзакция", + "expired_transaction": "Изтекла транзакция", + "pending_transaction": "Транзакция в очакване", + "offchain": "Оф-чейн", + "onchain": "Он-чейн", + "details_to": "Изход", + "enable_offline_signing": "Този портфейл не се използва във връзка с офлайн подписване. Желаете ли да го активирате сега?", + "list_conf": "Потв.: {number}", + "pending": "Чакаща", + "pending_with_amount": "В очакване {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: за ~10 минути", + "eta_3h": "ETA: за ~3 часа", + "eta_1d": "ETA: за ~1 ден", + "list_title": "Транзакции", + "list_title_sent": "Изпратени", + "list_title_received": "Получени", + "transaction": "Транзакция", + "open_url_error": "Не може да се отвори връзката с браузъра по подразбиране. Моля, променете браузъра си по подразбиране и опитайте отново.", + "rbf_explain": "Ще заменим тази транзакция с такава с по-висока такса, за да бъде минирана по-бързо. Това се нарича RBF — Replace by Fee.", + "rbf_title": "Ускори (RBF)", + "status_bump": "Ускори", + "status_cancel": "Отмени", + "transactions_count": "Брой транзакции", + "txid": "ID на транзакцията", + "updating": "Обновяване...", + "watchOnlyWarningTitle": "Предупреждение за сигурност", + "watchOnlyWarningDescription": "Внимавайте за измамници, които често използват портфейли „само за наблюдение“, за да заблудят потребителите. Тези портфейли не ви позволяват да контролирате или изпращате средства; те ви позволяват само да виждате баланса.", + "custom_fee_warning_title": "Внимание", + "custom_fee_warning_description": "Такси под 1 sat/vB са валидни, но може да не бъдат препредадени поради политиките на възела.", + "details_eta_analyzing": "Анализиране...", + "details_sent": "Изпратени", + "details_section": "Детайли", + "details_explorer": "експлорър", + "details_network_fee": "Мрежова такса", + "details_to_address": "До", + "details_id": "ID", + "details_note": "Бележка", + "details_add_note": "добави", + "details_advanced": "Разширени", + "details_fee_rate": "Ставка на таксата", + "details_size": "Размер", + "details_virtual_size": "Виртуален размер", + "details_tx_hex": "Hex на транзакцията", + "details_inputs_count": "Входове ({count})", + "details_outputs_count": "Изходи ({count})" }, "wallets": { + "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Прост и мощен Bitcoin портфейл", "add_create": "Създай", + "total_balance": "Общ баланс", + "add_entropy_reset_title": "Нулирай ентропията", + "add_entropy_reset_message": "Промяната на типа на портфейла ще нулира текущата ентропия. Искате ли да продължите?", + "add_entropy": "Ентропия", + "add_entropy_bytes": "{bytes} байта ентропия", + "add_entropy_generated": "{gen} байта генерирана ентропия", + "add_entropy_provide": "Предостави ентропия чрез хвърляне на зар", + "add_entropy_remain": "{gen} байта генерирана ентропия. Останалите {rem} байта ще бъдат получени от системния генератор на случайни числа.", + "add_import_wallet": "Импортирай портфейл", + "add_lightning": "Lightning", + "add_lightning_explain": "За харчене с мигновени транзакции", + "add_lndhub": "Свържи се с твоя LNDhub", + "add_lndhub_error": "Предоставеният адрес на възел е невалиден LNDhub възел.", + "add_lndhub_placeholder": "Адрес на твоя възел", + "add_placeholder": "моят първи портфейл", + "add_title": "Добави портфейл", + "add_wallet_name": "Име", + "add_wallet_type": "Тип", + "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": "Изчисти клипборда при импорт", "details_address": "Адрес", - "details_save": "Запази" + "details_advanced": "Разширени", + "details_are_you_sure": "Сигурни ли сте?", + "details_connected_to": "Свързан с", + "details_del_wb_err": "Предоставената сума на баланса не съответства на баланса на този портфейл. Моля, опитайте отново.", + "details_del_wb_q": "Този портфейл има баланс. Преди да продължите, моля, имайте предвид, че няма да можете да възстановите средствата без сийд фразата на този портфейл. За да избегнете случайно премахване, моля, въведете баланса на портфейла си от {balance} сатоши.", + "details_delete": "Изтрий", + "details_delete_wallet": "Изтрий портфейла", + "details_derivation_path": "път на деривация", + "details_display": "Покажи на началния екран", + "details_export_backup": "Експортирай/Резервно копие", + "details_export_history": "Експортирай историята в CSV", + "details_master_fingerprint": "Отпечатък на главния ключ", + "details_multisig_type": "мултисиг", + "details_show_xpub": "Покажи xpub на портфейла", + "details_show_addresses": "Покажи адресите", + "details_title": "Портфейл", + "wallets": "Портфейли", + "swipe_balance_hide": "Скрий", + "swipe_balance_show": "Покажи", + "drag_to_reorder": "Влачете, за да пренаредите", + "clear_search": "Изчисти търсенето", + "details_type": "Тип", + "details_use_with_hardware_wallet": "Използвай с хардуерен портфейл", + "details_yes_delete": "Да, изтрий", + "enter_bip38_password": "Въведете парола за декриптиране", + "export_title": "Експорт на портфейла", + "import_do_import": "Импортирай", + "import_passphrase": "Тайна фраза", + "import_passphrase_title": "Тайна фраза", + "import_passphrase_message": "Въведете тайна фраза, ако сте използвали такава", + "import_error": "Импортирането е неуспешно. Моля, уверете се, че предоставените данни са валидни.", + "import_explanation": "Моля, въведете думите на сийда, публичния ключ, WIF или каквото и да имате. BlueWallet ще се опита да отгатне правилния формат и да импортира портфейла ви.", + "import_imported": "Импортирано", + "import_scan_qr": "Сканирай или импортирай файл", + "import_success": "Вашият портфейл беше успешно импортиран.", + "import_success_watchonly": "Вашият портфейл беше успешно импортиран. ВНИМАНИЕ: Това е портфейл само за наблюдение, не можете да харчите от него.", + "import_search_accounts": "Търси акаунти", + "import_title": "Импортирай", + "learn_more": "Научи повече", + "import_discovery_title": "Откриване", + "import_discovery_subtitle": "Изберете открит портфейл", + "import_discovery_derivation": "Използвай персонализиран път на деривация", + "import_discovery_no_wallets": "Не са намерени портфейли.", + "import_discovery_offline": "BlueWallet в момента е в офлайн режим. В този режим не може да провери съществуването на портфейла, така че ще трябва да изберете правилния ръчно", + "import_derivation_found": "Намерен", + "import_derivation_found_not": "Не е намерен", + "import_derivation_loading": "Зареждане...", + "import_derivation_subtitle": "Въведете персонализиран път на деривация и ние ще се опитаме да открием вашия портфейл.", + "import_derivation_title": "Път на деривация", + "import_derivation_unknown": "Неизвестен", + "import_wrong_path": "Грешен път на деривация", + "list_create_a_button": "Добави сега", + "list_create_a_wallet": "Добави портфейл", + "list_create_a_wallet_text": "Безплатно е и можете да създадете \nколкото искате.", + "list_empty_txs1": "Вашите транзакции ще се появяват тук.", + "list_empty_txs1_lightning": "Lightning портфейлът трябва да се използва за ежедневните ви транзакции. Таксите са нечестно евтини, а скоростта е светкавична.", + "list_empty_txs2": "Започнете с вашия портфейл.", + "list_empty_txs2_lightning": "\nЗа да започнете да го използвате, докоснете „Управление на средства“ и заредете баланса си.", + "list_latest_transaction": "Последна транзакция", + "list_long_choose": "Избери снимка", + "paste_from_clipboard": "Постави", + "import_file": "Импортирай файл", + "list_long_scan": "Сканирай QR код", + "list_title": "Портфейли", + "list_tryagain": "Опитай отново", + "no_ln_wallet_error": "Преди да платите Lightning фактура, трябва първо да добавите Lightning портфейл.", + "looks_like_bip38": "Това изглежда като защитен с парола частен ключ (BIP38).", + "manage_title": "Управление на портфейлите", + "no_results_found": "Не са намерени резултати.", + "please_continue_scanning": "Моля, продължете сканирането.", + "select_no_bitcoin": "В момента няма налични Bitcoin портфейли.", + "select_no_bitcoin_exp": "Необходим е Bitcoin портфейл, за да зареждате Lightning портфейли. Моля, създайте или импортирайте такъв.", + "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": "Копирайте и съхранявайте сигурно този URL, за да възстановите портфейла си по-късно.", + "copy_ln_public": "Копирайте и съхранявайте сигурно тази информация, за да възстановите портфейла си по-късно.", + "add_ln_wallet_first": "Първо трябва да добавите Lightning портфейл.", + "identity_pubkey": "Публичен ключ за идентичност", + "xpub_title": "xpub на портфейла", + "manage_wallets_search_placeholder": "Търси портфейли, адреси, транзакции и бележки", + "more_info": "Повече информация", + "details_delete_wallet_error_message": "Имаше проблем при потвърждаването дали този портфейл е премахнат от известията — това може да се дължи на мрежов проблем или лоша връзка. Ако продължите, може все още да получавате известия за транзакции, свързани с този портфейл, дори след като той бъде изтрит.", + "details_delete_anyway": "Изтрий въпреки това" }, "multisig": { + "multisig_vault": "Мултисиг сейф", + "default_label": "Мултисиг сейф", + "multisig_vault_explain": "Най-добра сигурност за големи суми", + "provide_signature": "Предостави подпис", + "provide_signature_details": "Използвайте устройството и портфейла си, където се намира ключът, за да подпишете тази транзакция", + "provide_signature_details_bluewallet": "В BlueWallet отидете в менюто на екрана Изпрати и изберете ", + "provide_signature_next_steps": "Сканирай или импортирай подписана транзакция", + "provide_signature_next_steps_details": "След като портфейлът ви успешно подпише транзакцията, сканирайте предоставения QR код или импортирайте придружаващия файл, и след това прегледайте всички детайли на транзакцията преди да я излъчите.", + "vault_key": "Ключ на сейфа {number}", + "required_keys_out_of_total": "Необходими ключове от общия брой", + "fee": "Такса: {number}", + "fee_btc": "{number} BTC", "confirm": "Потвърди", "header": "Изпрати", - "share": "Сподели", + "share": "Сподели...", + "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": "Да започваме", "create": "Създай", + "native_segwit_title": "Най-добра практика", + "wrapped_segwit_title": "Най-добра съвместимост", + "legacy_title": "Legacy", "co_sign_transaction": "Подпиши транзакция", - "ms_help_title5": "Развирени Настройки" + "what_is_vault": "Сейфът е", + "what_is_vault_numberOfWallets": " {m}-от-{n} мултисиг ", + "what_is_vault_wallet": "портфейл.", + "vault_advanced_customize": "Настройки на сейфа", + "needs": "Има нужда от", + "what_is_vault_description_number_of_vault_keys": " {m} ключа на сейфа ", + "what_is_vault_description_to_spend": "за харчене и трети, който \nможете да използвате като резервно копие.", + "what_is_vault_description_to_spend_other": "за харчене.", + "quorum": "{m} от {n} кворум", + "quorum_header": "Кворум", + "of": "от", + "wallet_type": "Тип на портфейла", + "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": "Можете да създадете всички ключове на сейфа на това устройство и да ги премахнете или редактирате по-късно. Наличието на всички ключове на едно и също устройство има еквивалентна сигурност на обикновен Bitcoin портфейл.", + "ms_help_title3": "Резервни копия на сейфа", + "ms_help_3": "В опциите на портфейла ще намерите резервното копие на сейфа и резервното копие само за наблюдение. Това резервно копие е като карта към вашия портфейл. То е от съществено значение за възстановяване на портфейла в случай, че загубите един от сийдовете си.", + "ms_help_title4": "Импортиране на сейфове", + "ms_help_4": "За да импортирате мултисиг, използвайте файла с резервно копие и функцията Импортирай. Ако имате само сийдове и xpub-ове, можете да използвате индивидуалния бутон Импортирай при създаване на ключове на сейфа.", + "ms_help_title5": "Разширен режим", + "ms_help_5": "По подразбиране BlueWallet ще генерира сейф 2-от-3. За да създадете различен кворум или да промените типа на адреса, активирайте Разширен режим в Настройките." + }, + "cc": { + "change": "Ресто", + "coins_selected": "Избрани монети ({number})", + "selected_summ": "{value} избрани", + "empty": "Този портфейл в момента няма монети.", + "freeze": "Замрази", + "freezeLabel": "Замразена", + "freezeLabel_un": "Размразена", + "header": "Управление на UTXO", + "use_coin": "Използвай монета", + "use_coins": "Използвай монети", + "tip": "Тази функция ви позволява да виждате, етикетирате, замразявате или избирате монети за по-добро управление на портфейла. Можете да изберете няколко монети, като докоснете цветните кръгчета.", + "sort_asc": "Възходящо", + "sort_desc": "Низходящо", + "sort_height": "Височина", + "sort_value": "Стойност", + "sort_label": "Етикет", + "sort_status": "Статус", + "sort_by": "Сортирай по" }, "addresses": { + "copy_private_key": "Копирай частния ключ", + "sensitive_private_key": "Внимание: частните ключове са изключително чувствителни. Продължаване?", + "sign_title": "Подпиши/Провери съобщение", + "sign_help": "Тук можете да създадете или проверите криптографски подпис въз основа на Bitcoin адрес.", + "sign_sign": "Подпиши", + "sign_verify": "Провери", + "sign_signature_correct": "Проверката е успешна!", + "sign_signature_incorrect": "Проверката е неуспешна!", "sign_placeholder_address": "Адрес", - "type_receive": "Получаване" + "sign_placeholder_message": "Съобщение", + "sign_placeholder_signature": "Подпис", + "addresses_title": "Адреси", + "type_change": "Ресто", + "type_receive": "Получаване", + "type_used": "Използван", + "transactions": "Транзакции" + }, + "notifications": { + "would_you_like_to_receive_notifications": "Искате ли да получавате известия, когато получите входящи плащания?", + "notifications_subtitle": "Входящи плащания и потвърждения на транзакции", + "no_and_dont_ask": "Не, и не ме питай отново.", + "permission_denied_message": "Отказахте разрешение да ви изпращаме известия. Ако искате да получавате известия, моля, активирайте ги в настройките на устройството си." + }, + "total_balance_view": { + "display_in_bitcoin": "Покажи в Bitcoin", + "hide": "Скрий", + "display_in_sats": "Покажи в сатоши", + "display_in_fiat": "Покажи в {currency}", + "title": "Общ баланс", + "explanation": "Виж общия баланс на всички твои портфейли в екрана за преглед." + }, + "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 думи. Моля, опитайте отново." + }, + "units": { + "BTC": "BTC", + "MAX": "Макс", + "sat_vbyte": "sat/vByte", + "sats": "сатоши" + }, + "lnurl_auth": { + "register_question_part_1": "Искате ли да регистрирате акаунт в", + "register_question_part_2": "използвайки вашия Lightning портфейл?", + "register_answer": "Успешно регистрирахте акаунт в {hostname}!", + "login_question_part_1": "Искате ли да влезете в", + "login_question_part_2": "използвайки вашия Lightning портфейл?", + "login_answer": "Успешно влязохте в {hostname}!", + "link_question_part_1": "Искате ли да свържете акаунта си в", + "link_question_part_2": "с вашия Lightning портфейл?", + "link_answer": "Вашият Lightning портфейл беше успешно свързан с акаунта ви в {hostname}!", + "auth_question_part_1": "Искате ли да се удостоверите в", + "auth_question_part_2": "използвайки вашия Lightning портфейл?", + "auth_answer": "Успешно се удостоверихте в {hostname}!", + "could_not_auth": "Не можахме да ви удостоверим в {hostname}.", + "authenticate": "Удостовери" + }, + "bip47": { + "payment_code": "Платежен код", + "contacts": "Контакти", + "bip47_explain": "Многократно използваем и споделим код", + "bip47_explain_subtitle": "BIP47", + "purpose": "Многократно използваем и споделим код (BIP47)", + "pay_this_contact": "Плати на този контакт", + "rename_contact": "Преименувай контакта", + "copy_payment_code": "Копирай платежния код", + "hide_contact": "Скрий контакта", + "rename": "Преименувай", + "provide_name": "Въведете ново име за този контакт", + "add_contact": "Добави контакт", + "provide_payment_code": "Въведете платежен код", + "invalid_pc": "Невалиден платежен код", + "notification_tx_unconfirmed": "Транзакцията за уведомление все още не е потвърдена, моля изчакайте", + "failed_create_notif_tx": "Неуспешно създаване на он-чейн транзакция", + "onchain_tx_needed": "Необходима е он-чейн транзакция", + "notif_tx_sent": "Транзакцията за уведомление е изпратена. Моля, изчакайте да бъде потвърдена", + "notif_tx": "Транзакция за уведомление", + "not_found": "Платежният код не е намерен" } } diff --git a/loc/bqi.json b/loc/bqi.json new file mode 100644 index 00000000000..6e37e303b78 --- /dev/null +++ b/loc/bqi.json @@ -0,0 +1,704 @@ +{ + "_": { + "bad_password": "رزم زبال نؽ. ز نۊ تفره کو.", + "cancel": "لقو", + "continue": "رئڌن وا پؽش", + "clipboard": "ویرگه", + "discard_changes": "نیڌه گرئڌن آلشتکاریا؟", + "enter_password": "رزمته بزن", + "never": "هرگشت", + "of": "{number} ز {total}", + "ok": "هری", + "enter_url": "نشۊوی اینترنتی ن بزݩ", + "storage_is_encrypted": "جاگه زفت کردنی ایسا ریس رزم هڌ. سی گۊشیڌنس وا رزمسه داشته بۊی.", + "yes": "هری", + "no": "ن", + "save": "زفت...", + "seed": "سید", + "success": "سر ٱنجوم گرهڌ", + "wallet_key": "کیلیت کیف پیل", + "close": "بستن", + "change_input_currency": "آلشتکاری ٱرز وۊرۊڌی", + "refresh": "وانۊ کردن", + "pick_image": "ز کتاو هووه پسند کۊنین", + "pick_file": "پسند فایل", + "enter_amount": "مقدار ن بزن", + "qr_custom_input_button": "سی زیڌن وۊرۊڌی سفارشی، 10 کرت ریس بزن", + "unlock": "گۊشیڌن چفت", + "port": "پورت", + "ssl_port": "پورت SSL", + "suggested": "پؽشنهاڌی", + "copied": "لف گیری وابی!", + "discard_changes_explain": "آلشتکاریا زفت نوابیڌه دارین. اخۊی هونا ن نیڌه گری ۉ ز ای بلگه و در بئری؟" + }, + "azteco": { + "codeIs": "کوڌ تخفیف ایسا", + "errorBeforeRefeem": "پؽش ز فعال کردن ٱول وا ی کیف پیل بیت کوین ازاف کۊنین.", + "errorSomething": "موشکلؽ پؽش ٱووڌ. ای کوڌ تخفیف هنی زبال هڌ؟", + "redeem": "ازاف کردن و کیف پیل", + "redeemButton": "فعال کردن", + "success": "سر ٱنجوم گرهڌ", + "title": "فعال کردن کوڌ تخفیف Azte.co", + "successMessage": "ووچر وا مووفقیت فعال وابی! دارایی ایسا و زۊڌی و کیف پیل بیت کوین ایسا اوݩ." + }, + "entropy": { + "save": "زفت کردن", + "title": "آنتروپی", + "undo": "وورگشتن و هالت پؽشی", + "amountOfEntropy": "{bits} ز {limit} بیتا" + }, + "errors": { + "broadcast": "انتشار ٱنجوم نوابی.", + "error": "ختا", + "network": "ختا شبکه" + }, + "lnd": { + "errorInvoiceExpired": "سۊرت هساو مونقزی وابیڌه.", + "expired": "مونقزی وابیڌه", + "expiresIn": "تا {time} دیقه دی مونقزی ابۊ", + "payButton": "پرداخت", + "payment": "پرداخت", + "placeholder": "سۊرت هساو یا آدرس", + "potentialFee": "کارمزد ائتمالی: {fee}", + "refill": "پور کردن", + "refill_create": "سی ادامه، یه کیف پیل بیت کوین سی پور کردن وورکل کۊنین.", + "refill_external": "پور کردن وا کیف پیل خارجی", + "refill_lnd_balance": "پور کردن مۉجۊڌی کیف پیل لایتنینگ", + "sameWalletAsInvoiceError": "ایسا نترین سۊرت هساوی ن وا همو کیف پیلی ک وورکل کردین، پرداخت کۊنین.", + "title": "دؽوۉداری دارایی" + }, + "lndViewInvoice": { + "additional_info": "دووسمندیا قلوه", + "for": "سی:", + "lightning_invoice": "سۊرت هساو لایتنینگ", + "please_pay_between_and": "منجا {min} ۉ {max} پرداخت کۊنین", + "please_pay": "پرداخت کۊنین", + "preimage": "پؽش شؽوات", + "sats": "ساتۊشی پرداخت کوݩ.", + "date_time": "ویرگار وو زمووݩ", + "wasnt_paid_and_expired": "ای سۊرت هساو پرداخت نوابیڌه ۉ مونقزی وابیڌه." + }, + "plausibledeniability": { + "create_fake_storage": "وورکل جاگه رزم ناڌه وابیڌه سی زفت کردن", + "create_password_explanation": "رزمی ک سی جاگه زفت کردنی جعلی هڌ، نوا وا رزم جاگه زفت کردنی ٱلسی ی جۊر بۊ.", + "help2": "جاگه زفت کردنی نۊ قلوه و کار ایا وو ایسا ترین یتی ز دارایی خوتۉݩ ن اۊچنا واڌارنین تا ب تؽ بیا ک هونی زس استفاڌه اکۊنین.", + "password_should_not_match": "رزم هونی ب کار اروه. ی رزم دیری ن ب کار بگر.", + "title": "انکار مووجه", + "help": "تو هالاتی، شاید ایسا ن مجبۊر کۊنن رزم خوته فاش کۊنی. سی ایکه کوینا خوته امن داری، BlueWallet اتره ی جاگه زفت کردنی دؽ ریس رزم وا رزم دیر وورکل کونه. د هالت فشار، تری ای رزم ن وا ی نفر ی غیر فاش کۊنی. ٱر منه BlueWallet زیڌه بۊ، جاگه زفت کردنی نۊ «جعلی» گۊشیڌه ابۊ. یۊ سی ی نفر ی غیر، واقعی ب نظر اونه، اما د دل، جاگه زفت کردنی ٱلسی ایسا وا کوینا امن میمونه." + }, + "pleasebackup": { + "ask_no": "ن، مو نڌاروم.", + "ask_yes": "هری، مو داروم.", + "ok": "خا، هو ن هؽل کردوم.", + "ok_lnd": "خا، هو ن زفت کردوم.", + "title": "کیف پیل ایسا وورکل وابی.", + "ask": "عبارت بازیابی کیف پیلته زفت کردیه؟ ای عبارت بازیابی سی دسرسی و دارایی خوت لازمه، ٱر ای دسگاهن گوم کۊنی. بؽ ای عبارت بازیابی، دارایی خوت سی همیشه گوم اونه.", + "text": "ی دیقه ویرگار بنا تا ای عبارت بازیابی ن ری ی بلگه کاغذ بنویسی.\nیۊ نوسخه لادرار خوته هڌ ۉ تری وا یۊ کیف پیلته بازیابی کۊنی.", + "text_lnd": "تی کۊن ای نوسخه لادرار کیف پیل ن زفت کۊنی. یۊ ایلیه ٱر گوم وابی، کیف پیل ته بازیابی کۊنی." + }, + "receive": { + "details_create": "وورکل", + "details_label": "گوڌنا دیاری", + "details_setAmount": "گرؽڌن وا مقدار", + "details_share": "یک رسۊوی...", + "address_not_found": "نتره آدرس گیرنده ن وورکل کونه.", + "header": "گرؽڌن", + "reset": "وورنشۊوی سامووا", + "maxSats": "بیشترین مقدار {max} ساتۊشی هڌ.", + "maxSatsFull": "بیشترین مقدار {max} ساتۊشی یا {currency} هڌ.", + "minSats": "کمترین مقدار {min} ساتۊشی هڌ.", + "minSatsFull": "کمترین مقدار {min} ساتۊشی یا {currency} هڌ.", + "qrcode_for_the_address": "QR کود سی ای نشۊوی", + "bip47_explanation": "کود پرداخت ی آدرس عمومی هڌ ک بؽ فاش کردن آدرسا کیف پیل ایسا کار اکونه. هومه خدماتا تی پشتؽوانی نکونن." + }, + "send": { + "provided_address_is_invoice": "منی ای آدرس ی سۊرت هساو لایتنینگ هڌ. سی پرداخت ای سۊرت هساو، و کیف پیل لایتنینگ خوتۉݩ ریوین.", + "broadcastButton": "انتشار", + "broadcastError": "ختا", + "broadcastNone": "هگزادسیمال تراکونش ن بزن", + "broadcastPending": "مندیر سی زفت", + "broadcastSuccess": "سر ٱنجوم گرهڌ", + "confirm_header": "تاییڌ", + "confirm_sendNow": "هیم سکو بفشن", + "create_amount": "مقدار", + "create_broadcast": "انتشار", + "create_copy": "لف گیری وو دیندا مونتشر کو", + "create_details": "جۊزیات", + "create_fee": "کارمزد", + "create_memo": "ویرداشت", + "create_satoshi_per_vbyte": "ساتۊشی سی هر بایت مجازی", + "create_this_is_hex": "یو هگزادسیمال تراکونش ایسا هڌ—امزا وابیڌه وو ٱماڌه سی تیجنیڌن من شبکه.", + "create_to": "وه", + "create_tx_size": "هندا تراکونش", + "create_verify": "تاییڌ من coinb.in", + "details_insert_contact": "ٱووردن هومدنگ", + "details_add_rec_add": "ٱووردن گیرنده", + "details_add_rec_rem": "پاک کردن گیرنده", + "details_add_rec_rem_all": "پاک کردن پوی گیرنده یل", + "details_recipients_title": "گیرنده یل", + "details_recipient_title": "گیرنده #{number} ز #{total}", + "please_complete_recipient_title": "گیرنده ناقس", + "details_address": "آدرس", + "details_address_field_is_not_valid": "آدرس زبال نؽ.", + "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_create": "وورکل سۊرت هساو", + "details_error_decode": "نا مووفق منه گۊشیڌن رزم آدرس بیت کوین", + "details_fee_field_is_not_valid": "کارمزد زبال نؽ.", + "details_frozen": "{amount} بیت کوین مسدۊد وابیڌه.", + "details_next": "نیایی", + "details_no_signed_tx": "فایل پسند بیڌه، تراکونشی منس نؽڌ ک ترسته بویم ب من یاریمس.", + "details_note_placeholder": "ویرداشت و خوت", + "details_scan": "اسکن", + "details_scan_hint": "سی اسکن یا و من ٱووردن مقسد، دو کرت بزن ریس", + "details_scan_error": "ختا اسکن", + "details_total_exceeds_balance": "مقداری ک خۊی فیشنی، بیشتر ز مۉجۊڌیت هڌ.", + "details_unrecognized_file_format": "قالو فایل نشناخته", + "details_wallet_before_tx": "پؽش ز وورکل کردن تراکونش، ٱول وا ی کیف پیل بیت کوین ازاف کۊنین.", + "dynamic_init": "هونی رئس اونه", + "dynamic_next": "نیایی", + "dynamic_prev": "دیندایی", + "dynamic_start": "ناڌن پا", + "dynamic_stop": "واڌاشتن", + "fee_10m": "10 دؽقه", + "fee_1d": "1 رۊز", + "fee_3h": "3 ساعت", + "fee_custom": "سفارشی", + "insert_custom_fee": "ٱووردن هزینه", + "fee_fast": "زل", + "fee_medium": "منجا", + "fee_satvbyte": "ب ساتۊشی/بایت مجازی", + "fee_slow": "کوند", + "header": "فشناڌن", + "input_clear": "روفتن", + "input_done": "ٱنجوم وابی", + "input_paste": "جا وندن", + "input_total": "کول:", + "permission_camera_message": "سی ب کار گرهڌن شؽوات گر، لنگ اجازه ایسانیم.", + "psbt_sign": "امزا کردن تراکونش", + "invalid_psbt": "PSBT ک زبال نؽ داڌه وابیڌه.", + "open_settings": "گۊشیڌن سامووا", + "permission_storage_title": "موجوز دسرسی و جاگه زفت کردنی", + "psbt_clipboard": "لف گیری منه ویرگه", + "psbt_tx_export": "گرؽڌن جۊر فایل", + "no_tx_signing_in_progress": "هیچ امزا تراکونشی ک هونی ٱنجوم ابۊ نؽ.", + "outdated_rate": "ورۊ رسۊوی دیندایی نرخ: {date}", + "psbt_tx_open": "گۊشیڌن تراکونش امزا وابیڌه", + "psbt_tx_scan": "اسکن تراکونش امزا وابیڌه", + "reset_amount": "وورنشۊوی مقدار", + "reset_amount_confirm": "اخۊی مقدار ن وورنشۊوی؟", + "success_done": "ٱنجوم وابی", + "txSaved": "فایل تراکونش ({filePath}) زفت وابیڌه.", + "file_saved_at_path": "فایل ({filePath}) زفت وابیڌه.", + "problem_with_psbt": "موشکل وا تراکونش ناقس امزا بیڌه PSBT", + "details_add_recc_rem_all_alert_description": "اخۊی پوی گیرنده یل ن پاک کۊنی؟", + "please_complete_recipient_details": "تی کۊن جۊزیات گیرنده #{number} ن پؽش ز ٱووردن گیرنده نۊ کامل کۊنی.", + "details_total_exceeds_balance_frozen": "مقداری ک خۊی فیشنی، بیشتر ز مۉجۊڌیت هڌ. ویرت بۊ ک کوینا مسدۊد، شومارش نوابن.", + "fee_replace_minvb": "نرخ کارمزد کول (ساتۊشی سی هر بایت مجازی) ک اخۊی پرداخت کۊنی، وا بیشتر ز {min} ساتۊشی سی هر بایت مجازی بۊ.", + "permission_storage_denied_message": "BlueWallet نتره ای فایل ن زفت کونه. تی کۊن سامووا دسگا ته ن گۊشی ۉ موجوز دسرسی و جاگه زفت کردنی ن فعال کۊنی.", + "psbt_this_is_psbt": "یۊ ی تراکونش بیت کوین ناقس امزا بیڌه (PSBT) هڌ. تی کۊن وا کیف پیل سخت ٱفزاری خوت امزاسه کامل کۊنی.", + "qr_error_no_qrcode": "ایما نترسیم ی QR کود زبال منه کتاو هووه پسند بیڌه پؽڌا کۊنیم. موتمعن بۊ ک کتاو هووه فقط ی QR کود داره ۉ هیچ دؽوار، چی نوشتار یا دگمه نڌاره.", + "cant_send_to_silentpayment_adress": "ای کیف پیل نتره و آدرسا Silent Payments فیشنه", + "cant_send_to_bip47": "ای کیف پیل نتره و کود پرداخت BIP47 فیشنه", + "cant_find_bip47_notification": "ٱول ای کود پرداخت ن و هومدنگا اضاف کۊنی" + }, + "settings": { + "about": "زبار", + "about_awesome": "ورکل وابیڌه وا بؽڌرینا", + "about_backup": "همیشه ز کیلیتا خوتۉݩ نوسخه لادرار بگیرین!", + "about_license": "پروانه ام آی تی", + "about_release_notes": "ویرداشتا انتشار", + "about_review": "سی ایما یه واجۊری بنین", + "performance_score": "امتیاز کارکرد: {num}", + "run_performance_test": "واجۊری کارکرد", + "about_selftest": "ره وندن خوس آزمایی", + "about_selftest_electrum_disabled": "خوس آزمایی منه هالت آفلاین منه دسرس نؽ. هالت آفلاین ن قیر فعال کو ۉ ز نۊ تفره کو.", + "about_selftest_ok": "پوی واجۊریا منی ب خۊوی ٱنجوم وابین. کیف پیل ب خۊوی کار اکونه.", + "about_sm_github": "گیت هاب", + "about_sm_telegram": "تورگه تلگرام", + "privacy_temporary_screenshots": "هشتن زفت کردن بلگه نمایش", + "biometrics": "بیومتریک", + "biom_conf_identity": "هۊویته خوته تاییڌ کو.", + "currency": "واهڌ پیل", + "currency_source": "نرخ ب دست ٱووڌه ز", + "currency_fetch_error": "منه گرؽڌن نرخ سی واهڌ پیل پسند بیڌه ی ختا پؽش اووڌ.", + "default_title": "موقه ره وندن", + "electrum_connected": "منپیز", + "electrum_connected_not": "بؽ منپیز", + "electrum_error_connect": "نتره و سرور الکترام داڌه وابیڌه منپیز بۊوه", + "lndhub_uri": "سی نمووه، {example}", + "electrum_host": "سی نمووه، {example}", + "electrum_offline_mode": "هالت آفلاین", + "electrum_offline_description": "ٱر فعال بۊ، کیف پیلا بیت کوین ایسا نترن مۉجۊدی گرن یا تراکونشی داشته بۊن.", + "electrum_port": "پورت و توور مئمۊل {example}", + "use_ssl": "SSL ن ب کار بگر", + "set_electrum_server_as_default": "{server} سی سرور پؽش فرز الکترام ساموو بۊوه؟", + "electrum_settings_server": "سرور الکترام", + "electrum_status": "وزیت", + "electrum_preferred_server": "سرور ترجیهی", + "electrum_unable_to_connect": "نا مووفق منه منپیز ب {server}", + "electrum_history": "ویرگار", + "electrum_reset": "ورگندن و پؽش فرز", + "encrypt_decrypt": "رزم گوشایی جاگه زفت کردنی", + "encrypt_storage_explanation_headline": "فعال کردن رزم ناهاڌن ری جاگه زفت کردنی", + "i_understand": "افئموم", + "block_explorer": "گشت گر بلاک", + "block_explorer_preferred": "گشت گر بلاک ترجیهی ن و کار بگیرین", + "block_explorer_error_saving_custom": "ختا من زفت کردن گشت گر بلاک ترجیهی", + "encrypt_title": "امنیت", + "encrypt_tstorage": "جاگه زفت کردنی", + "encrypt_use": "{type} ن ب کار بگر", + "set_as_preferred": "سامووݩ و عونوان ترجیهی", + "general": "پوی وولاتی", + "header": "سامووا", + "language": "زووݩ", + "last_updated": "ورۊ رسۊوی دیندایی", + "language_isRTL": "ره وندن دۊوارته BlueWallet سی انجوم آلشت کاریا ری زووݩ الن وا انجوم بۊ.", + "license": "موجوز", + "lightning_error_lndhub_uri": "یۊ آر آی LNDhub زبال نؽ", + "lightning_saved": "آلشت کاریا ایسا و خۊوی زفت وابین.", + "lightning_settings": "سامووا لایتنینگ", + "network": "شبکه", + "network_broadcast": "تیجنیڌن تراکونش", + "network_electrum": "سرور الکترام", + "not_a_valid_uri": "یۊ آر آی زبال نؽ", + "notifications": "وارسۊویا", + "open_link_in_explorer": "گۊشیڌن لینگ من گشت گر", + "password": "رزم", + "plausible_deniability": "انکار موجه", + "privacy": "سی خومی", + "privacy_read_clipboard": "خوندن ویرگه", + "privacy_system_settings": "سامووا دسگا", + "privacy_quickactions": "ر نهنگا کیف پیل", + "privacy_do_not_track": "قیر فعال کردن تئلیل", + "rate": "نرخ", + "selfTest": "خوس ازمایی", + "save": "زفت کردن", + "saved": "زفت وابی", + "total_balance": "پوی مۉجۊدی", + "widgets": "اوزارکا", + "tools": "اوزارا", + "about_free": "BlueWallet ی پروژه ٱزاد ۉ بازیه ٱفرینش کۊن. ساته وابیڌه وا کاربرا بیت کوین.", + "block_explorer_invalid_custom_url": "نشۊوی داڌه وابیڌه زبال نؽ. تی کۊن ی نشۊوی زبال ک وا http:// یا https:// شۊرۊ بۊ بزن.", + "privacy_temporary_screenshots_instructions": "موسامینیڌن بلگه نمایش موقتاً قیر فعال اونه، تا بلگه نمایش زفت کردن ۉ بلگه نمایش هۉنبلت کردن مومکن بۊ. ٱر BlueWallet ن بستی ۉ ز نۊ گۊشی، موسامینیڌن ب توور خوس کار وا فعال اونه.", + "biometrics_no_longer_available": "سامووا دسگاه ایسا آلشت وابیڌه ۉ ز ای ب دیندا وا سامووا امنیتی پسند بیڌه من برنامه هومخۊوݩ نؽ. تی کۊن بیومتریک یا کد دسترسی ن ز نۊ فعال کۊنی، ۉ بعد برنامه ن ز نۊ شۊرۊ کۊنی تا آلشتا اعمال بۊن.", + "biom_10times": "ایسا 10 کرت کۊشش کردیه رزم ته بزنی. اخۊی جاگه زفت کردنی ته وورنشۊوی کۊنی؟ ای کار، پوی کیف پیلا ن پاک اکونه ۉ جاگه زفت کردنی ته رزم گوشایی اکونه.", + "biom_no_passcode": "دسگا ایسا کد دسترسی یا بیومتریک فعال نڌاره. سی ادامه، تی کۊن کد دسترسی یا بیومتریک ن منه برنامه سامووا ساموو کۊنی.", + "biom_remove_decrypt": "پوی کیف پیلا ایسا پاک اونن ۉ جاگه زفت کردنی ایسا رزم گوشایی اونه. اخۊی ادامه دی؟", + "donate": "اهدا", + "donate_description": "هؽاری کۊن تا Blue ٱزاد بمونه!", + "electrum_error_connect_tor": "نتره و سرور الکترام داڌه وابیڌه منپیز بۊوه. تی کۊن موتمعن بۊ ک برنامه Orbot منپیز هڌ ۉ ز نۊ تفره کو.", + "electrum_saved": "آلشتکاریا ایسا وا مووفقیت زفت وابین. شاید لازم بۊ BlueWallet ن ز نۊ شۊرۊ کۊنی تا آلشتکاریا اعمال بۊن.", + "set_lndhub_as_default": "{url} سی سرور پؽش فرز LNDhub ساموو بۊوه؟", + "electrum_preferred_server_description": "سروری ک اخۊی کیف پیلت سی پوی کارا بیت کوین استفاڌه کونه ن بزن. ٱر ساموو وابی، کیف پیلت فقط ز ای سرور سی واجۊری مۉجۊڌی، فشناڌن تراکونش ۉ گرؽڌن داڌه شبکه استفاڌه اکونه. پؽش ز سامووݩ، موتمعن بۊ ک و ای سرور اعتماد داری.", + "electrum_reset_to_default": "یۊ ایلیه ک BlueWallet ی سرور ن ز فهرست سرورا تسادفی پسند کونه.", + "electrum_reset_to_default_and_clear_history": "ورگندن و پؽش فرز ۉ پاک کردن ویرگار", + "encrypt_decrypt_q": "اخۊی جاگه زفت کردنی ته رزم گوشایی کۊنی؟ ای کار ایلیه کیف پیلا ته بؽ رزم دسرس بۊن.", + "encrypt_enc_and_pass": "موسامینیڌه وا رزم", + "encrypt_storage_explanation_description_line1": "فعال کردن رزم ناهاڌن ری جاگه زفت کردنی، ی لایه موسامینیڌن دؽ و برنامه ایسا اضاف اکونه ۉ شیوه ناهاڌن داڌه ها ری دسگا ته امن اکونه. یۊ کار اکونه ک سختر بۊ تا هرکی بؽ موجوز و داڌه ها ایسا دسرس داشته بۊ.", + "encrypt_storage_explanation_description_line2": "اما، لازمه بدۊنی ک ای رزم ناهاڌن، فقط دسرسی و کیف پیلا ناهاڌه وابیڌه ری کیچین دسگا ته موسامینه. کیف پیلا ٱلسی ن وا رزم یا موسامینیڌن دؽ، موسامینه نکونه.", + "set_as_preferred_electrum": "سامووݩ {host}:{port} و عونوان سرور ترجیهی، منپیز و سرور پؽشنهاڌی تسادفی ن قیر فعال اکونه.", + "encrypted_feature_disabled": "ای ویژگی وا جاگه زفت کردنی موسامینیڌه فعال نتره ب کار رئڌه بۊوه.", + "encrypt_use_expl": "{type} سی تاییڌ هۊویت ایسا پؽش ز انجوم تراکونش، گۊشیڌن چفت، و در کشیڌن، یا پاک کردن کیف پیل ب کار رئڌه ابۊ.", + "biometrics_fail": "ٱر {type} فعال نؽ، یا منه گۊشیڌن چفت ناکام بۊ، تری ز کد دسترسی دسگا ته و عونوان جانشین استفاڌه کۊنی.", + "general_continuity": "تڌاوم", + "general_continuity_e": "ٱر فعال بۊ، تری کیف پیلا ۉ تراکونشا پسند بیڌه ن ز دۊیار دسگاها Apple iCloud منپیز خوت ووینی.", + "groundcontrol_explanation": "GroundControl ی سرور وارسۊوی ٱزاد ۉ بازیه ٱفرینش کۊن سی کیف پیلا بیت کوین هڌ. تری سرور GroundControl خوته نسو کۊنی ۉ نشۊوی ٱنا ای جا بنی تا و زیر ساخت BlueWallet وابسته نووی. سی استفاڌه ز سرور پؽش فرز GroundControl، ای جا ن خالی هؽل کو.", + "lightning_error_lndhub_uri_tor": "یۊ آر آی LNDhub زبال نؽ. تی کۊن موتمعن بۊ ک برنامه Orbot منپیز هڌ ۉ ز نۊ تفره کو.", + "lightning_settings_explain": "سی منپیز و گره LND خوت، تی کۊن LNDhub ن نسو کۊنی ۉ نشۊوی ٱنا ای جا منه سامووا بنی. ویرت بۊ ک فقط کیف پیلایی ک پس ز زفت آلشتکاریا وورکل اونن، و LNDhub داڌه وابیڌه منپیز اونن.", + "lndhub_github": "گنجگه گیت هاب", + "electrum_suggested_description": "ٱر سرور ترجیهی ساموو نوابی، ی سرور پؽشنهاڌی ب توور تسادفی سی استفاڌه پسند اونه.", + "password_explain": "رزمی ک سی گۊشیڌن چفت جاگه زفت کردنی ته ب کار اوری ن بزن.", + "privacy_quickactions_explanation": "ری نشۊن BlueWallet نگاه دار ۉ بفشار تا مۉجۊڌی کیف پیل خوته ب زۊڌی ووینی.", + "privacy_clipboard_explanation": "ٱر آدرس یا سۊرت هساوی منه ویرگه ایسا پؽڌا وابی، ر نهنگا نشۉݩ بڌه.", + "privacy_do_not_track_explanation": "دؽوسمندیا کارکرد ۉ اعتماد ٱفرینی سی تئلیل فیشناڌه نوابن.", + "push_notifications_explanation": "وا فعال کردن وارسۊویا، توکن دسگا ایسا و گرا وا آدرسا کیف پیل ۉ شناسه تراکونشا سی پوی کیف پیلا ۉ تراکونشایی ک پس ز فعال کردن وارسۊویا ٱنجوم اونن، فیشناڌه ابۊ. توکن دسگا سی فشناڌن وارسۊویا ب کار رئڌه ابۊ، ۉ دؽوسمندیا کیف پیل ایلیه ایما ایسا ن ز بیت کوین وۊرۊڌی یا تاییڌ تراکونش ٱگاه کۊنیم.\n\nفقط دؽوسمندی پس ز فعال کردن وارسۊویا ، فیشناڌه اونه—هیچ چی ز پؽش، گرها وابیڌه نؽ.\n\nقیر فعال کردن وارسۊویا، پوی ای دؽوسمندی ن ز سرور پاک اکونه. ای جور، پاک کردن ی کیف پیل ز برنامه، دؽوسمندی پۊشمت وا هون ن نی ز سرور پاک اکونه.", + "success_transaction_broadcasted": "تراکونش ایسا وا مووفقیت تیجنیڌه وابی!", + "total_balance_explanation": "پوی مۉجۊڌی کیف پیلا خوته من اوزارکا بلگه ٱلسی نشۉݩ بڌه." + }, + "transactions": { + "cancel_title": "ای تراکونشن لقو کوݩ (RBF)", + "confirmations_lowercase": "{confirmations} تاییڌ", + "expand_note": "نشۉݩ داڌن کامل ویرداشت", + "cpfp_create": "وورکل", + "cpfp_no_bump": "ای تراکونش نتره کارمزدس بیشتر بۊ.", + "cpfp_title": "بیشتر کردن کارمزد (CPFP)", + "details_balance_hide": "بؽڌار کردن مۉجۊدی", + "details_balance_show": "نشۉݩ داڌن مۉجۊدی", + "details_copy": "لف گیری", + "details_copy_block_explorer_link": "لف گیری لینگ گشت گر بلاک", + "details_copy_note": "لف گیری ویرداشت", + "date": "ویرگار", + "details_received": "گرؽڌه وابیڌه", + "details_title": "تراکونش", + "details_inputs": "وۊرۊڌیا", + "details_outputs": "خروجیا", + "details_to": "خروجی", + "details_to_address": "وه", + "details_network_fee": "کارمزد شبکه", + "details_section": "جۊزیات", + "details_explorer": "گشت گر", + "details_size": "هندا", + "details_virtual_size": "هندا مجازی", + "details_tx_hex": "هگزادسیمال تراکونش", + "details_inputs_count": "وۊرۊڌیا ({count})", + "details_outputs_count": "خروجیا ({count})", + "expired_transaction": "تراکونش مونقزی وابیڌه", + "pending_transaction": "تراکونش مندیر سی زفت", + "pending_with_amount": "مندیر سی زفت {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "list_title": "تراکونشا", + "rbf_title": "تسریع (RBF)", + "status_bump": "تسریع", + "status_cancel": "لقو", + "offchain": "آفچین", + "onchain": "آنچین", + "pending": "مندیر سی زفت", + "list_title_received": "گرؽڌه وابیڌه", + "transaction": "تراکونش", + "open_url_error": "نا مووفق منه گۊشیڌن لینگ وا گشت گر پؽش فرز. گشت گر پؽش فرز خوته آلشت کوݩ ۉ ز نۊ تفره کو.", + "cancel_explain": "ای تراکونش ن وا تراکونش دیر ک و خوت پرداخت اکونه ۉ کارمزد بیشتر داره، جانشین اکۊنیم. یۊ تراکونش الن ن لقو اکونه. ای کار ن RBF—جانشینی وا کارمزد اگۊن.", + "cancel_no": "ای تراکونش جانشین پزیر نؽ.", + "transaction_loading_error": "موشکلؽ منه بار ونی تراکونش پؽش ٱووڌ. تی کۊن دیرتر ز نۊ تفره کۊنی.", + "transaction_not_available": "تراکونش منه دسرس نؽ", + "cpfp_exp": "ایما تراکونش دؽر وورکل اکۊنیم ک تراکونش تاییڌ نوابیڌه ته خرج اکونه. کارمزد کول ز کارمزد تراکونش ٱلسی بیشتر اونه، سی همیمی زۊڌتر استخراج اونه. ای کار ن CPFP—بچه سی ٱقا پرداخت اکونه اگۊن.", + "details_copy_txid": "لف گیری شناسه تراکونش", + "details_view_in_browser": "ووینسن من گشت گر", + "incoming_transaction": "تراکونش وۊرۊڌی", + "outgoing_transaction": "تراکونش خروجی", + "enable_offline_signing": "ای کیف پیل وا امزای آفلاین ب کار نری ابۊ. اخۊی ایسا هونه فعال کۊنی؟", + "list_conf": "تاییڌ: {number}", + "eta_10m": "زمووݩ تخمینی: حدود 10 دؽقه دی", + "eta_3h": "زمووݩ تخمینی: حدود 3 ساعت دی", + "eta_1d": "زمووݩ تخمینی: حدود 1 رۊز دی", + "list_title_sent": "فیشناڌه وابیڌه", + "rbf_explain": "ای تراکونش ن وا تراکونش دیر ک کارمزدس بیشتره، جانشین اکۊنیم تا زۊڌتر استخراج بۊ. ای کار ن RBF—جانشینی وا کارمزد اگۊن.", + "transactions_count": "شومار تراکونشا", + "txid": "شناسه تراکونش", + "updating": "ورۊ رسۊوی...", + "watchOnlyWarningTitle": "هشڌار امنیتی", + "watchOnlyWarningDescription": "ز کلاش بازا ک گاهاً ز کیف پیلا «فقط‌خواندنی» سی فریو کاربرا استفاڌه اکونن، باپرها بۊ. ای کیف پیلا ایلیه نڌن دارایی ن وار آور یا فیشنی؛ فقط ایلیه مۉجۊڌی ن ووینی.", + "custom_fee_warning_title": "هشڌار", + "custom_fee_warning_description": "کارمزدا کمتر ز 1 ساتۊشی سی هر بایت مجازی زبالن، اما شاید ب ٱر سامووا گره ها، بازنشر نوابن.", + "details_eta_analyzing": "تئلیل...", + "details_sent": "فیشناڌه وابی", + "details_id": "شناسه", + "details_note": "ویرداشت", + "details_add_note": "ٱووردن", + "details_advanced": "پؽشرفته", + "details_fee_rate": "نرخ کارمزد" + }, + "wallets": { + "add_bitcoin": "بیت کوین", + "add_create": "وورکل", + "total_balance": "پوی مۉجۊدی", + "add_entropy": "آنتروپی", + "add_wallet_type": "نوع", + "details_address": "آدرس", + "details_delete_wallet": "پاک کردن کیف پیل", + "details_derivation_path": "تور موشتق وابیڌن", + "details_display": "نشووݩ داڌن من بلگه ٱلسی", + "details_export_backup": "و در کردن نوسخه لادرار", + "details_export_history": "گرؽڌن وو و در کشیڌن ویرگار و فورمت CSV", + "details_master_fingerprint": "جا کلک ٱلسی", + "details_multisig_type": "چند امزایی", + "details_show_xpub": "نشووݩ داڌن XPUB کیف پیل", + "details_show_addresses": "نشووݩ داڌن آدرسا", + "details_title": "کیف پیل", + "wallets": "کیف پیلا", + "details_type": "نوع", + "details_use_with_hardware_wallet": "و کار گرؽڌن وا کیف پیل سخت ٱفزاری", + "details_yes_delete": "هری پاک کوݩ", + "enter_bip38_password": "رزمن سی رزم گوشایی بزنین", + "export_title": "و در کشیڌن کیف پیل", + "import_do_import": "و من ٱووردن", + "import_passphrase": "پس فریز (Passphrase)", + "import_passphrase_title": "پس فریز (Passphrase)", + "import_passphrase_message": "ٱر پس فریز (Passphrase) ن و کار گرؽڌینه، هونه بزنین", + "import_error": "و من ٱووردن نا مووفق بی. ز زبال بیڌن داده داڌه وابیڌه موتمعن بۊین.", + "import_imported": "و من ٱووڌ", + "import_scan_qr": "اسکن یا و من ٱووردن فایل", + "import_success": "کیف پیل ایسا وا مووفقیت و من ٱووڌ.", + "import_search_accounts": "پیتینیڌن هساوا", + "import_title": "و من ٱووردن", + "learn_more": "قلوه دووسته بۊین", + "import_discovery_title": "جوستن", + "import_discovery_subtitle": "کیف پیل جوسته وابیڌه ن پسند کۊنین", + "import_discovery_derivation": "و کار گرؽڌن تور موشتق دلخا", + "import_discovery_no_wallets": "کیف پیلی نجۊرست", + "import_derivation_found": "جۊرست", + "import_derivation_found_not": "نجۊرست", + "import_derivation_loading": "هونی بار ونی ابۊ...", + "import_derivation_title": "تور موشتق وابیڌن", + "import_derivation_unknown": "ن دیاری", + "list_create_a_button": "هیم سکو ازاف کوݩ", + "list_create_a_wallet": "ٱووردن کیف پیل", + "list_empty_txs2": "وا کیف پیل خوت شۊرۊ کوݩ", + "list_latest_transaction": "تراکونش دیندایی", + "list_long_choose": "پسند شؽوات", + "paste_from_clipboard": "جا وندن", + "import_file": "و من ٱووردن فایل", + "list_long_scan": "اسکن کود QR", + "list_title": "کیف پیلا", + "list_tryagain": "ز نۊ تفره کوݩ", + "more_info": "دووسمندیا قلوه", + "details_delete_anyway": "و هر هال پاک بۊ", + "add_lightning": "لایتنینگ", + "swipe_balance_hide": "بؽڌار", + "details_delete": "پاک کردن", + "add_bitcoin_explain": "کیف پیل بیت کوین ساڌه ۉ پۊر هؽز", + "add_entropy_reset_title": "وورنشۊوی آنتروپی", + "add_entropy_reset_message": "آلشت نوع کیف پیل، آنتروپی الن ن وورنشۊوی اکونه. اخۊی ادامه دی؟", + "add_entropy_bytes": "{bytes} بایت آنتروپی", + "add_entropy_generated": "{gen} بایت آنتروپی وورکل وابیڌه", + "add_entropy_provide": "آنتروپی ن ز ریشت کوݩ تاسا فره کۊن", + "add_entropy_remain": "{gen} بایت آنتروپی وورکل وابیڌه. {rem} بایت ماݩ ز وورکل کوݩ شومارا تسادفی سیستم گرها اونن.", + "add_import_wallet": "و من ٱووردن کیف پیل", + "add_lightning_explain": "سی خرج کردن وا تراکونشا فۊری", + "add_lndhub": "منپیز و LNDhub خوت", + "add_lndhub_error": "نشۊوی گره داڌه وابیڌه، ی گره LNDhub زبال نؽ.", + "add_lndhub_placeholder": "نشۊوی گره ایسا", + "add_placeholder": "کیف پیل ٱوولی مو", + "add_title": "ٱووردن کیف پیل", + "add_wallet_name": "نوم", + "add_wallet_seed_length": "هندا سید", + "add_wallet_seed_length_12": "12 وشه", + "add_wallet_seed_length_24": "24 وشه", + "clipboard_bitcoin": "ی آدرس بیت کوین منه ویرگه ایسا هڌ. اخۊی هونه سی ی تراکونش ب کار بگری؟", + "clipboard_lightning": "ی سۊرت هساو لایتنینگ منه ویرگه ایسا هڌ. اخۊی هونه سی ی تراکونش ب کار بگری؟", + "clear_clipboard_on_import": "پاک کردن ویرگه موقع و من ٱووردن", + "details_advanced": "پؽشرفته", + "details_are_you_sure": "اخۊی موتمعنی؟", + "details_connected_to": "منپیز و", + "details_del_wb_err": "مقدار مۉجۊڌی داڌه وابیڌه وا مۉجۊڌی ای کیف پیل هومخۊوݩ نؽ. تی کۊن ز نۊ تفره کۊنی.", + "details_del_wb_q": "ای کیف پیل مۉجۊڌی داره. پؽش ز ادامه، ویرت بۊ ک بؽ عبارت بازیابی ای کیف پیل، نتری دارایی ن بازیابی کۊنی. سی پؽش گری ز پاک کردن نا خۊسته، تی کۊن مۉجۊڌی کیف پیلت {balance} ساتۊشی ن بزن.", + "swipe_balance_show": "نشۉݩ داڌن", + "drag_to_reorder": "سی ترتیو دی، بکش", + "clear_search": "پاک کردن پیتینیڌن", + "import_explanation": "تی کۊن وشه ها سید، کلید عمومی، WIF، یا هر چی ک داری ن بزن. BlueWallet پوی تلاش خوسه اکونه تا فورمت زبال ن خومه بزنه ۉ کیف پیل ته و من بئره.", + "import_success_watchonly": "کیف پیل ایسا وا مووفقیت و من ٱووڌ. هشڌار: یۊ ی کیف پیل فقط‌خواندنی هڌ، نتری زس فیشنی.", + "import_discovery_offline": "BlueWallet هونی منه هالت آفلاینه. د ای هالت، نتره وجۊد کیف پیل ن تاییڌ کونه، سی همیمی وا دس کیف پیل زبال ن پسند کۊنی", + "import_derivation_subtitle": "تور موشتق وابیڌن دلخای خوته بزن، ایما تلاش اکۊنیم کیف پیل ته پؽڌا کۊنیم.", + "import_wrong_path": "تور موشتق وابیڌن زبال نؽ", + "list_create_a_wallet_text": "ٱزاده، تری هر گد ک اخۊی\nوورکل کۊنی.", + "list_empty_txs1": "تراکونشا ایسا ای جا اوینسن.", + "list_empty_txs1_lightning": "کیف پیل لایتنینگ وا تراکونشا رۊزانه ایسا ب کار رئڌه ابۊ. کارمزدا ب توور بؽ انسافی ارزۊن ۉ پؽڌنی زل هڌ.", + "list_empty_txs2_lightning": "\nسی شۊرۊ ب کار گرهڌن، ری دؽوۉداری دارایی بزن ۉ مۉجۊڌی ته پور کۊن.", + "no_ln_wallet_error": "پؽش ز پرداخت ی سۊرت هساو لایتنینگ، ٱول وا ی کیف پیل لایتنینگ ازاف کۊنی.", + "looks_like_bip38": "یۊ ی کلید خصوصی موسامینیڌه وا رزم (BIP38) ب نظر اونه.", + "manage_title": "مدیریت کیف پیلا", + "no_results_found": "هیچ ناتجه ای پؽڌا نوابی.", + "please_continue_scanning": "تی کۊن ب اسکن کردن ادامه دی.", + "select_no_bitcoin": "هونی هیچ کیف پیل بیت کوین منه دسرس نؽ.", + "select_no_bitcoin_exp": "سی پور کردن کیف پیلا لایتنینگ، ی کیف پیل بیت کوین لازمه. تی کۊن یتی ن وورکل یا و من بئر.", + "select_wallet": "پسند کیف پیل", + "pull_to_refresh": "بکش سی وانۊ کردن", + "warning_do_not_disclose": "دؽوسمندی پاوین ن هیچ گد ب اشتراک نگرا", + "scan_import": "ای QR کود ن اسکن کۊنی تا کیف پیل ته و ی برنامه دؽ و من بئری.", + "write_down_header": "ی نوسخه لادرار دسی وورکل کۊنی", + "write_down": "ای وشه ها ن بنویس ۉ امن نگه دار. هونا ن سی بازیابی کیف پیلت دیرتر ب کار بگر.", + "wallet_type_this": "نوع ای کیف پیل {type} هڌ.", + "share_number": "یک رسۊوی {number}", + "copy_ln_url": "ای نشۊوی ن لف گیری ۉ امن نگه دار تا دیرتر کیف پیل ته بازیابی کۊنی.", + "copy_ln_public": "ای دؽوسمندی ن لف گیری ۉ امن نگه دار تا دیرتر کیف پیل ته بازیابی کۊنی.", + "add_ln_wallet_first": "ٱول وا ی کیف پیل لایتنینگ ازاف کۊنی.", + "identity_pubkey": "کلید عمومی هۊویت", + "xpub_title": "xpub کیف پیل", + "manage_wallets_search_placeholder": "پیتینیڌن کیف پیلا، آدرسا، تراکونشا ۉ ویرداشتا", + "details_delete_wallet_error_message": "موشکلؽ منه تاییڌ پاک وابیڌن ای کیف پیل ز وارسۊویا پؽش ٱووڌ — یۊ شاید ز موشکل شبکه یا منپیز زبال نکوݩ بۊ. ٱر ادامه دی، شاید هنی سی تراکونشا پۊشمت وا ای کیف پیل وارسۊوی گری، حتا پس ز پاک وابیڌنس." + }, + "total_balance_view": { + "display_in_bitcoin": "نشووݩ داڌن من بیت کوین", + "hide": "بؽڌار", + "display_in_sats": "نشووݩ داڌن و ری ساتۊشی", + "display_in_fiat": "نشووݩ داڌن من {currency}", + "title": "پوی مۉجۊدی", + "explanation": "پوی مۉجۊڌی کیف پیلا خوته من بلگه نمای کلی ووین." + }, + "multisig": { + "confirm": "تاییڌ", + "header": "فشناڌن", + "share": "یک رسۊوی...", + "create": "وورکل", + "co_sign_transaction": "امزا کردن تراکونش", + "ms_help_title5": "هالت پؽش رئڌه", + "multisig_vault": "گاوصندوق چند امزایی", + "default_label": "گاوصندوق چند امزایی", + "provide_signature": "دین امزا", + "vault_key": "کیلیت گاوصندوق {number}", + "fee": "کارمزد: {number}", + "fee_btc": "{number} BTC", + "quorum": "{m} ز {n} حد نصاب", + "quorum_header": "حد نصاب", + "of": "ز", + "wallet_type": "نوع کیف پیل", + "vault_advanced_customize": "سامووا گاوصندوق", + "multisig_vault_explain": "بؽڌرین امنیت سی مقدارا گت", + "provide_signature_details": "ز دسگا ۉ کیف پیل خوت ک کلید ٱنا هڌ سی امزا ای تراکونش استفاڌه کۊنی", + "provide_signature_details_bluewallet": "د BlueWallet، و بلگه فشناڌن ر ۉ ز فهرست، یۊ ن پسند کۊن: ", + "provide_signature_next_steps": "اسکن یا و من ٱووردن تراکونش امزا وابیڌه", + "provide_signature_next_steps_details": "ٱر کیف پیلت تراکونش ن وا مووفقیت امزا کرد، QR کود داڌه وابیڌه ن اسکن کۊن یا فایل هومرا ن و من بئر، ۉ بعد پوی جۊزیات تراکونش ن پؽش ز انتشار وارسی کۊن.", + "required_keys_out_of_total": "کلیدا لازم ز کول", + "view": "ووینسن", + "shared_key_detected": "امزا کوݩ هومبهر یک رسۊوی وابیڌه", + "shared_key_detected_question": "ی امزا کوݩ هومبهر وا ایسا یک رسۊوی وابیڌه، اخۊی هونه و من بئری؟", + "manage_keys": "مدیریت کلیدا", + "how_many_signatures_can_bluewallet_make": "BlueWallet چن گد امزا تره بکونه", + "signatures_required_to_spend": "امزا یل لازم {number}", + "signatures_we_can_make": "تره {number} ن بکونه", + "scan_or_import_file": "اسکن یا و من ٱووردن فایل", + "export_coordination_setup": "و در کردن سامووا هومٱئنگی", + "cosign_this_transaction": "ای تراکونش ن هومرا امزا کۊنی؟", + "lets_start": "بیا شۊرۊ کۊنیم", + "native_segwit_title": "بؽڌرین کاره", + "wrapped_segwit_title": "بؽڌرین هومخۊوݩ کاری", + "legacy_title": "قدیمی", + "what_is_vault": "گاوصندوق ی", + "what_is_vault_numberOfWallets": " چن امزایی {m} ز {n} ", + "what_is_vault_wallet": "کیف پیل هڌ.", + "needs": "هون اخا", + "what_is_vault_description_number_of_vault_keys": " {m} کلید گاوصندوق ", + "what_is_vault_description_to_spend": "سی خرج کردن ۉ یۊ سؽومی ک \nتری سی نوسخه لادرار ب کار بگری.", + "what_is_vault_description_to_spend_other": "سی خرج کردن.", + "invalid_mnemonics": "ای عبارت بازیابی زبال ب نظر نونه.", + "invalid_cosigner": "داڌه امزا کوݩ هومبهر زبال نؽ", + "not_a_multisignature_xpub": "یۊ xpub ز ی کیف پیل چند امزایی نؽ!", + "invalid_cosigner_format": "امزا کوݩ هومبهر زبال نؽ: یۊ امزا کوݩ هومبهر سی فورمت {format} نؽ.", + "create_new_key": "وورکل نۊ", + "scan_or_open_file": "اسکن یا گۊشیڌن فایل", + "i_have_mnemonics": "مو سید سی ای کلید داروم.", + "type_your_mnemonics": "ی سید بزن سی و من ٱووردن کلید گاوصندوق الن.", + "this_is_cosigners_xpub": "یۊ xpub امزا کوݩ هومبهر هڌ—ٱماڌه سی و من ٱووردن من ی کیف پیل دؽ. یک رسۊویس امنه.", + "this_is_cosigners_xpub_airdrop": "ٱر ز AirDrop یک رسۊوی کۊنی، گرنده ها وا منه بلگه هومٱئنگی بۊن.", + "wallet_key_created": "کلید گاوصندوق ایسا وورکل وابی. ی دیقه ویرگار بنا تا سید عبارت بازیابی ته امن نوسخه لادرار بگری.", + "are_you_sure_seed_will_be_lost": "اخۊی موتمعنی؟ ٱر نوسخه لادرار نڌاری، سید عبارت بازیابی ته گوم اونه.", + "forget_this_seed": "ای سید ن ز ویر بئر ۉ ز xpub و جاس استفاڌه کو.", + "view_edit_cosigners": "ووینسن/ویرٱست امزا کوݩ هومبهر", + "this_cosigner_is_already_imported": "ای امزا کوݩ هومبهر ز پؽش و من ٱووڌه.", + "export_signed_psbt": "و در کردن PSBT امزا وابیڌه", + "input_fp": "جا کلک ن بزن", + "input_fp_explain": "سی استفاڌه ز پؽش فرز (00000000) ز ای کار بئر", + "input_path": "ٱووردن تور موشتق وابیڌن", + "input_path_explain": "سی استفاڌه ز پؽش فرز ({default}) ز ای کار بئر", + "ms_help": "هؽاری", + "ms_help_title": "گاوصندوقا چن امزایی چی جور کار اکونن: نکات ۉ ترفندا", + "ms_help_text": "ی کیف پیل وا چن کلید، سی امنیت بیشتر یا دؽوۉداری اشتراکی", + "ms_help_title1": "استفاڌه ز چن دسگا پؽشنهاڌ اونه.", + "ms_help_1": "گاوصندوق وا برنامه ها دؽر BlueWallet ۉ کیف پیلا هومخۊوݩ وا PSBT چی Electrum، Specter، Coldcard، Cobo Vault ۉ غیره کار اکونه.", + "ms_help_title2": "ویرٱست کلیدا", + "ms_help_2": "تری پوی کلیدا گاوصندوق ن ری ای دسگا وورکل کۊنی ۉ دیرتر هونا ن پاک یا ویرٱست کۊنی. ناهاڌن پوی کلیدا ری ی دسگا، هومراز ی کیف پیل بیت کوین مئمۊلی امنه.", + "ms_help_title3": "نوسخه لادرارا گاوصندوق", + "ms_help_3": "منه گیزینه ها کیف پیل، نوسخه لادرار گاوصندوق ۉ نوسخه لادرار فقط‌خواندنی ته پؽڌا اکۊنی. ای نوسخه لادرار چی ی نخشه ز کیف پیلته. هون سی بازیابی کیف پیل ٱر یتی ز سیدا ته گوم کۊنی ضروریه.", + "ms_help_title4": "و من ٱووردن گاوصندوقا", + "ms_help_4": "سی و من ٱووردن چن امزایی، ز فایل نوسخه لادرار خوت ۉ ویژگی و من ٱووردن استفاڌه کۊن. ٱر فقط سیدا ۉ xpub داری، تری ز دگمه و من ٱووردن جودا موقع وورکل کلیدا گاوصندوق استفاڌه کۊنی.", + "ms_help_5": "ب توور پؽش فرز، BlueWallet ی گاوصندوق 2 ز 3 وورکل اکونه. سی وورکل ی حد نصاب دیر یا آلشت نوع آدرس، هالت پؽشرفته ن منه سامووا فعال کۊن." + }, + "cc": { + "sort_status": "وزیت", + "change": "آلشتکاری", + "header": "مدیریت UTXO", + "selected_summ": "{value} پسند بیڌه", + "sort_label": "برچسب", + "coins_selected": "کوینا پسند بیڌه ({number})", + "empty": "ای کیف پیل هونی هیچ کوینی نڌاره.", + "freeze": "مسدۊد کردن", + "freezeLabel": "مسدۊد کردن", + "freezeLabel_un": "گۊشیڌن مسدۊدی", + "use_coin": "استفاڌه ز کوین", + "use_coins": "استفاڌه ز کوینا", + "tip": "ای ویژگی ایلیه کوینا ته سی مدیریت بؽڌر کیف پیلت ووینی، برچسب بنی، مسدۊد کۊنی یا پسند کۊنی. تری وا زیڌن ری دایره ها رنگی، چن کوین ن پسند کۊنی.", + "sort_asc": "ز کم و بیشتر", + "sort_desc": "ز بیشتر و کم", + "sort_height": "بلندی", + "sort_value": "ارزش", + "sort_by": "ترتیو ری ٱر" + }, + "addresses": { + "sign_placeholder_address": "آدرس", + "type_receive": "گرؽڌن", + "type_change": "آلشتکاری", + "addresses_title": "آدرسا", + "transactions": "تراکونشا", + "copy_private_key": "لف گیری کلید خصوصی", + "sensitive_private_key": "هشڌار: کلیدا خصوصی قلوه هساسن. ادامه دی؟", + "sign_title": "امزا/وارسی پیوم", + "sign_help": "ای جا تری ی امزای رزمی ن ری مووا ی آدرس بیت کوین وورکل یا وارسی کۊنی.", + "sign_sign": "امزا", + "sign_verify": "وارسی", + "sign_signature_correct": "وارسی وا مووفقیت ٱنجوم وابی!", + "sign_signature_incorrect": "وارسی ناکام بی!", + "sign_placeholder_message": "پیوم", + "sign_placeholder_signature": "امزا", + "type_used": "ب کار رئڌه وابی" + }, + "units": { + "BTC": "BTC", + "sat_vbyte": "ساتۊشی سی هر بایت مجازی", + "sats": "ساتۊشی", + "MAX": "بیشترین" + }, + "bip47": { + "payment_code": "کود پرداخت", + "contacts": "هومدنگا", + "bip47_explain_subtitle": "BIP47", + "copy_payment_code": "لف گیری کود پرداخت", + "add_contact": "ٱووردن هومدنگ", + "invalid_pc": "کود پرداخت زبال نؽ", + "not_found": "کود پرداخت نجۊرست", + "bip47_explain": "کود قابل استفاڌه دۊوار ۉ یک رسۊوی", + "purpose": "کود قابل استفاڌه دۊوار ۉ یک رسۊوی (BIP47)", + "pay_this_contact": "پرداخت و ای هومدنگ", + "rename_contact": "آلشت نوم هومدنگ", + "hide_contact": "بؽڌار کردن هومدنگ", + "rename": "آلشت نوم", + "provide_name": "نوم نۊ سی ای هومدنگ بزن", + "provide_payment_code": "کود پرداخت ن بزن", + "notification_tx_unconfirmed": "تراکونش وارسۊوی هنی تاییڌ نوابیڌه، تی کۊن مندیر بۊ", + "failed_create_notif_tx": "وورکل کردن تراکونش آنچین ناکام بی", + "onchain_tx_needed": "تراکونش آنچین لازمه", + "notif_tx_sent": "تراکونش وارسۊوی فیشناڌه وابی. تی کۊن مندیر بۊ تا تاییڌ بۊ", + "notif_tx": "تراکونش وارسۊوی" + }, + "notifications": { + "would_you_like_to_receive_notifications": "اخۊی موقع گرؽڌن پرداختا وۊرۊڌی، وارسۊوی گری؟", + "notifications_subtitle": "پرداختا وۊرۊڌی ۉ تاییڌ تراکونشا", + "no_and_dont_ask": "ن، ز نۊ نی پرس.", + "permission_denied_message": "ایسا موجوز فشناڌن وارسۊویا ن قبۊل نکردیه. ٱر اخۊی وارسۊویا گری، تی کۊن هونا ن منه سامووا دسگا ته فعال کۊنی." + }, + "is_it_my_address": { + "title": "آدرس مونه؟", + "owns": "{label} {address} ن داره", + "enter_address": "آدرس ن بزن", + "check_address": "وارسی آدرس", + "no_wallet_owns_address": "هیچ یتی ز کیف پیلا منه دسرس، آدرس داڌه وابیڌه ن نڌاره.", + "view_qrcode": "ووینسن QR کود" + }, + "autofill_word": { + "title": "وشه ٱخر سید", + "enter": "عبارت بازیابی ناقس ته بزن", + "generate_word": "وورکل وشه ٱخر", + "error": "وۊرۊڌی ی عبارت بازیابی ناقس 11 یا 23 وشه ای نؽ. تی کۊن ز نۊ تفره کۊنی." + }, + "lnurl_auth": { + "register_question_part_1": "اخۊی ی هساو ای جا نوم نویسی کۊنی", + "register_question_part_2": "وا استفاڌه ز کیف پیل لایتنینگ خوت؟", + "register_answer": "ایسا وا مووفقیت ی هساو من {hostname} نوم نویسی کردیه!", + "login_question_part_1": "اخۊی ای جا وۊرۊڌ بۊوی", + "login_question_part_2": "وا استفاڌه ز کیف پیل لایتنینگ خوت؟", + "login_answer": "ایسا وا مووفقیت من {hostname} وۊرۊڌ وابیه!", + "link_question_part_1": "اخۊی هساو خوته ای جا پیوست کۊنی", + "link_question_part_2": "و کیف پیل لایتنینگ خوت؟", + "link_answer": "کیف پیل لایتنینگ ایسا وا مووفقیت و هساو ایسا من {hostname} پیوست وابی!", + "auth_question_part_1": "اخۊی ای جا هۊویت ته تاییڌ کۊنی", + "auth_question_part_2": "وا استفاڌه ز کیف پیل لایتنینگ خوت؟", + "auth_answer": "ایسا وا مووفقیت من {hostname} هۊویت ته تاییڌ کردیه!", + "could_not_auth": "ایما نترسیم هۊویت ایسا ن من {hostname} تاییڌ کۊنیم.", + "authenticate": "تاییڌ هۊویت" + } +} diff --git a/loc/ca.json b/loc/ca.json index 110d260bb15..8e1f697c78c 100644 --- a/loc/ca.json +++ b/loc/ca.json @@ -4,105 +4,115 @@ "cancel": "Cancel·lar", "continue": "Continuar", "clipboard": "Porta-retalls", + "copied": "Copiat!", + "discard_changes": "Descartar els canvis?", + "discard_changes_explain": "Teniu canvis sense desar. Esteu segur que voleu descartar-los i sortir de la pantalla?", "enter_password": "Introduïu la contrasenya", "never": "mai", - "disabled": "Desactivat", "of": "{number} de {total}", "ok": "OK", - "storage_is_encrypted": "L'informació està xifrada. Es requereix la contrasenya per a desxifrar-la.", - "yes": "Si", + "enter_url": "Entrar URL", + "storage_is_encrypted": "La informació està xifrada. Es requereix la contrasenya per a desxifrar-la.", + "yes": "Sí", "no": "No", - "save": "Desar", + "save": "Desar...", "seed": "Llavor", "success": "Èxit", "wallet_key": "Clau del moneder", - "invalid_animated_qr_code_fragment": "Fragment QRCode animat no vàlid. Siusplau torna-ho a provar.", - "downloads_folder": "Carpeta de descàrregues" - }, - "alert": { - "default": "Alerta" + "close": "Tancar", + "change_input_currency": "Canviar moneda d'entrada", + "refresh": "Refresca", + "pick_image": "Triar de la biblioteca", + "pick_file": "Triar arxiu", + "enter_amount": "Introdueix la quantitat", + "qr_custom_input_button": "Toqueu 10 vegades per introduir una entrada personalitzada", + "unlock": "Desbloquejar", + "port": "Port", + "ssl_port": "Port SSL", + "suggested": "Suggerit" }, "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", - "title": "Canviar cupó de Azte.co" + "successMessage": "Val bescanviat correctament! Els vostres fons haurien d'arribar al vostre moneder de Bitcoin en breu.", + "title": "Canviar cupó d'Azte.co" }, "entropy": { "save": "Desar", "title": "Entropia", - "undo": "Desfer" + "undo": "Desfer", + "amountOfEntropy": "{bits} de {limit} bits" }, "errors": { "broadcast": "La transmissió ha fallat", "error": "Error", - "network": "Error de red" + "network": "Error de xarxa" }, "lnd": { - "active": "Actiu", - "inactive": "Inactiu", - "channels": "Canals", - "no_channels": "No hi ha canals", - "claim_balance": "Reclamar el saldo {balance}", - "close_channel": "Tancar el canal", - "new_channel": "Nou canal", - "errorInvoiceExpired": "La factura ha caducat", - "force_close_channel": "Forçar tancar el canal?", + "errorInvoiceExpired": "Factura caducada.", "expired": "Caducat", - "node_alias": "Àlies del node", "expiresIn": "Caduca en {time} minuts", "payButton": "Pagar", - "placeholder": "Factura", - "open_channel": "Obra el canal", + "payment": "Pagament", + "placeholder": "Factura o adreça", "potentialFee": "Comissió potencial: {fee}", "refill": "Recarregar", + "refill_create": "Per a continuar, creeu un moneder de Bitcoin amb el qual recarregar.", "refill_external": "Recarregar amb un moneder extern", "refill_lnd_balance": "Recarregar el balanç del moneder Lightning", "sameWalletAsInvoiceError": "No pots pagar una factura amb el mateix moneder que l'ha creat.", - "title": "gestionar fons", - "can_send": "Pots enviar", - "can_receive": "Pots rebre" + "title": "gestionar fons" }, "lndViewInvoice": { "additional_info": "Informació addicional", "for": "Per:", "lightning_invoice": "Factura Lightning", - "open_direct_channel": "Obrir un canal directe amb aquest node:", + "please_pay_between_and": "Si us plau, pagui entre {min} i {max}", "please_pay": "Si us plau, pagui", + "preimage": "Preimatge", "sats": "sats", + "date_time": "Data i hora", "wasnt_paid_and_expired": "Aquesta factura no ha estat pagada i ha caducat" }, "plausibledeniability": { "create_fake_storage": "Crear informació xifrada falsa", - "create_password": "Crear una contrasenya", "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.", - "passwords_do_not_match": "Les contrasenyes no coincideixin. Torni-ho a intentar", - "retype_password": "Tornar a escriure la contrasenya", - "success": "Exit", "title": "Negació plausible" }, "pleasebackup": { "ask": "Heu desat la frase de recuperació del vostre moneder? Aquesta frase de recuperació és necessària per accedir als vostres fons en cas que perdeu aquest dispositiu. Sense la frase de recuperació, els vostres fons es perdran permanentment.", - "ask_no": "No, no en tinc", - "ask_yes": "Si, en tinc", + "ask_no": "No, encara no.", + "ask_yes": "Sí, ja ho he fet.", + "ok": "D'acord, ho he apuntat.", "ok_lnd": "D'acord, l'he guardat", + "text": "Si us plau, dediqueu un moment a apuntar aquesta frase mnemònica en un paper.\nÉs la vostra còpia de seguretat i la podeu utilitzar per recuperar el moneder.", "text_lnd": "Si us plau, deseu aquesta còpia de seguretat del moneder. Permet restaurar el moneder en cas de pèrdua.", - "title": "El teu moneder ha estat creat" + "title": "El teu moneder ha estat creat..." }, "receive": { "details_create": "Crear", "details_label": "Descripció", "details_setAmount": "Rebre quantitat", - "details_share": "Compartir", - "header": "Rebre" + "details_share": "Compartir...", + "address_not_found": "No s'ha pogut generar l'adreça de recepció.", + "header": "Rebre", + "reset": "Restablir", + "maxSats": "La quantitat màxima és {max} sats", + "maxSatsFull": "La quantitat màxima és {max} sats o {currency}", + "minSats": "La quantitat mínima és {min} sats", + "minSatsFull": "La quantitat mínima és {min} sats o {currency}", + "qrcode_for_the_address": "Codi QR per a l'adreça", + "bip47_explanation": "Els codis de pagament són una adreça universal que evita revelar les adreces del vostre moneder. No tots els serveis els admeten." }, "send": { + "provided_address_is_invoice": "Aquesta adreça sembla ser per a una factura Lightning. Si us plau, aneu al vostre moneder Lightning per realitzar un pagament d'aquesta factura.", "broadcastButton": "Enviar", "broadcastError": "Error", "broadcastNone": "Inserir la transacció Hex", @@ -117,28 +127,41 @@ "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", + "details_insert_contact": "Inserir contacte", "details_add_rec_add": "Afegir receptor", "details_add_rec_rem": "Eliminar receptor", + "details_add_recc_rem_all_alert_description": "Esteu segur que voleu eliminar tots els destinataris?", + "details_add_rec_rem_all": "Eliminar tots els destinataris", + "details_recipients_title": "Destinataris", + "details_recipient_title": "Destinatari núm. {number} de núm. {total}", + "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_fee_field_is_not_valid": "Comissió invalida", - "details_frozen": "{amount} BTC està bloquejat", + "details_error_decode": "No s'ha pogut descodificar l'adreça de Bitcoin", + "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.", "details_note_placeholder": "comentari (útil per tu)", "details_scan": "Escanejar", + "details_scan_hint": "Toqueu dues vegades per escanejar o importar una destinació", + "details_scan_error": "Error d'escaneig", "details_total_exceeds_balance": "La quantitat excedeix el balanç disponible.", + "details_total_exceeds_balance_frozen": "La quantitat a enviar excedeix el saldo disponible. Tingueu en compte que les monedes congelades queden excloses.", "details_unrecognized_file_format": "Format de fitxer no reconegut", "details_wallet_before_tx": "Abans de crear una transacció, primer heu d’afegir un moneder Bitcoin.", "dynamic_init": "Inicialitzant", @@ -150,8 +173,10 @@ "fee_1d": "1d", "fee_3h": "3h", "fee_custom": "Personalitzada", + "insert_custom_fee": "Inserir comissió", "fee_fast": "Ràpid", "fee_medium": "Mitjà", + "fee_replace_minvb": "La taxa total de comissió (satoshi per vByte) que voleu pagar hauria de ser superior a {min} sat/vByte.", "fee_satvbyte": "en sat/vByte", "fee_slow": "Lent", "header": "enviar", @@ -161,173 +186,519 @@ "input_total": "Total:", "permission_camera_message": "Necessitem el vostre permís per usar la vostra càmera.", "psbt_sign": "Signar una transacció", + "invalid_psbt": "PSBT no vàlid.", "open_settings": "Obre configuració", - "permission_storage_later": "Pregunta'm després", - "permission_storage_message": "BlueWallet necessita el vostre permís per accedir al vostre emmagatzematge per desar aquest fitxer.", + "permission_storage_denied_message": "BlueWallet no pot desar aquest fitxer. Si us plau, obriu la configuració del dispositiu i activeu el permís d'emmagatzematge.", "permission_storage_title": "Permís d'accés a emmagatzematge", "psbt_clipboard": "Copiar al portapapers", + "psbt_this_is_psbt": "Aquesta és una transacció de Bitcoin parcialment signada (PSBT). Si us plau, acabeu de signar-la amb el vostre moneder de maquinari.", "psbt_tx_export": "Exportar a arxiu", + "no_tx_signing_in_progress": "No hi ha cap signatura de transacció en curs.", + "outdated_rate": "La cotització es va actualitzar per última vegada: {date}", + "psbt_tx_open": "Obrir transacció signada", + "psbt_tx_scan": "Escanejar transacció signada", + "qr_error_no_qrcode": "No hem pogut trobar un codi QR vàlid a la imatge seleccionada. Assegureu-vos que la imatge només conté un codi QR i no conté contingut addicional com text o botons.", + "reset_amount": "Restablir quantitat", + "reset_amount_confirm": "Voleu restablir la quantitat?", "success_done": "Fet", + "txSaved": "El fitxer de la transacció ({filePath}) s'ha desat.", + "file_saved_at_path": "El fitxer ({filePath}) s'ha desat.", + "cant_send_to_silentpayment_adress": "Aquest moneder no pot enviar a adreces SilentPayment", + "cant_send_to_bip47": "Aquest moneder no pot enviar a codis de pagament BIP47", + "cant_find_bip47_notification": "Afegiu primer aquest codi de pagament als contactes", "problem_with_psbt": "Problema amb PSBT" }, "settings": { "about": "Sobre nosaltres", + "about_awesome": "Construït amb l'increïble", "about_backup": "Feu sempre còpies de seguretat de les vostres claus!", + "about_free": "BlueWallet és un projecte gratuït i de codi obert. Creat per usuaris de Bitcoin.", "about_license": "Llicència MIT", + "about_release_notes": "Notes de la versió", "about_review": "Deixa'ns una ressenya", + "performance_score": "Puntuació de rendiment: {num}", + "run_performance_test": "Provar rendiment", "about_selftest": "Executa l'autotest", + "block_explorer_invalid_custom_url": "L'URL proporcionat no és vàlid. Si us plau, introduïu un URL vàlid que comenci per http:// o https://.", + "about_selftest_electrum_disabled": "L'autotest no està disponible amb el mode fora de línia d'Electrum. Si us plau, desactiveu el mode fora de línia i torneu-ho a provar.", + "about_selftest_ok": "Totes les proves internes s'han superat correctament. El moneder funciona bé.", "about_sm_github": "GitHub", - "about_sm_discord": "Servidor Discord", "about_sm_telegram": "Canal de Telegram", - "about_sm_twitter": "Segueix-nos a Twitter", - "advanced_options": "Configuracions avançades", + "privacy_temporary_screenshots": "Permetre captures de pantalla", + "privacy_temporary_screenshots_instructions": "La protecció contra captures de pantalla es desactivarà temporalment, permetent captures i enregistraments de pantalla. La protecció es reactivarà automàticament quan tanqueu i torneu a obrir BlueWallet.", "biometrics": "Biometria", + "biometrics_no_longer_available": "La configuració del dispositiu ha canviat i ja no coincideix amb la configuració de seguretat seleccionada a l'aplicació. Si us plau, torneu a activar la biometria o el codi d'accés, i després reinicieu l'aplicació per aplicar aquests canvis.", + "biom_10times": "Heu intentat introduir la contrasenya 10 vegades. Voleu restablir l'emmagatzematge? Això eliminarà tots els moneders i desxifrarà l'emmagatzematge.", "biom_conf_identity": "Si us plau, confirmeu la vostra identitat.", + "biom_no_passcode": "El vostre dispositiu no té cap codi d'accés ni biometria activats. Per a continuar, configureu un codi d'accés o biometria a l'aplicació Configuració.", + "biom_remove_decrypt": "Tots els vostres moneders s'eliminaran i el vostre emmagatzematge es desxifrarà. Esteu segur que voleu continuar?", "currency": "Moneda", - "default_info": "Informació predeterminada", - "default_wallets": "Veure tots els moneders", - "electrum_connected": "Conectat", - "electrum_connected_not": "No conectat", + "currency_source": "La cotització s'obté de", + "currency_fetch_error": "S'ha produït un error en obtenir la cotització per a la moneda seleccionada.", + "default_title": "En obrir", + "donate": "Donar", + "donate_description": "Ajudeu-nos a mantenir Blue gratuït!", + "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}", + "electrum_host": "P. ex., {example}", + "electrum_offline_mode": "Mode fora de línia", + "electrum_offline_description": "Quan està activat, els vostres moneders de Bitcoin no intentaran obtenir saldos ni transaccions.", + "electrum_port": "Port, normalment {example}", "use_ssl": "Utilitza SSL", + "electrum_saved": "Els vostres canvis s'han desat correctament. Pot ser necessari reiniciar BlueWallet perquè els canvis tinguin efecte.", + "set_electrum_server_as_default": "Establir {server} com a servidor Electrum predeterminat?", + "set_lndhub_as_default": "Establir {url} com a servidor LNDhub predeterminat?", "electrum_settings_server": "Servidor Electrum", - "electrum_settings_explain": "Deixa-ho buit per usar el valor per defecte", "electrum_status": "estat", - "electrum_clear_alert_title": "Netejar l'historial?", - "electrum_clear_alert_cancel": "Cancel·lar", - "electrum_clear_alert_ok": "D'acord", - "electrum_select": "selecciona", + "electrum_preferred_server": "Servidor preferit", + "electrum_preferred_server_description": "Introduïu el servidor que voleu que el vostre moneder utilitzi per a totes les activitats de Bitcoin. Un cop establert, el vostre moneder utilitzarà exclusivament aquest servidor per consultar saldos, enviar transaccions i obtenir dades de la xarxa. Assegureu-vos que confieu en aquest servidor abans de configurar-lo.", + "electrum_unable_to_connect": "No es pot connectar a {server}.", + "electrum_history": "Historial", + "electrum_reset_to_default": "Això permetrà que BlueWallet triï aleatòriament un servidor de la llista de servidors.", "electrum_reset": "Restableix la configuració predeterminada", - "electrum_clear": "Neteja", - "tor_supported": "Tor suportat", - "tor_unsupported": "Les conexions Tor no estan suportades", + "electrum_reset_to_default_and_clear_history": "Restablir als valors predeterminats i esborrar l'historial", + "encrypt_decrypt": "Desxifrar emmagatzematge", + "encrypt_decrypt_q": "Esteu segur que voleu desxifrar el vostre emmagatzematge? Això permetrà accedir als vostres moneders sense contrasenya.", + "encrypt_enc_and_pass": "Protegit amb contrasenya", + "encrypt_storage_explanation_headline": "Activar el xifratge de l'emmagatzematge", + "encrypt_storage_explanation_description_line1": "Activar el xifratge de l'emmagatzematge afegeix una capa extra de protecció a la vostra aplicació, assegurant la manera com les vostres dades es guarden al dispositiu. Això fa més difícil que ningú accedeixi a la vostra informació sense permís.", + "encrypt_storage_explanation_description_line2": "Tanmateix, és important saber que aquest xifratge només protegeix l'accés als moneders emmagatzemats al clauer del dispositiu. No posa cap contrasenya ni protecció addicional als moneders mateixos.", + "i_understand": "Ho entenc", + "block_explorer": "Explorador de blocs", + "block_explorer_preferred": "Utilitzar l'explorador de blocs preferit", + "block_explorer_error_saving_custom": "Error en desar l'explorador de blocs preferit", "encrypt_title": "Seguretat", + "encrypt_tstorage": "Emmagatzematge", "encrypt_use": "Utilitza {type}", + "set_as_preferred": "Establir com a preferit", + "set_as_preferred_electrum": "Establir {host}:{port} com a servidor preferit desactivarà la connexió aleatòria a un servidor suggerit.", + "encrypted_feature_disabled": "Aquesta funcionalitat no es pot utilitzar amb el xifratge de l'emmagatzematge activat.", + "encrypt_use_expl": "{type} s'utilitzarà per confirmar la vostra identitat abans de fer una transacció, desbloquejar, exportar o eliminar un moneder.", + "biometrics_fail": "Si {type} no està activat o falla en desbloquejar, podeu utilitzar el codi d'accés del dispositiu com a alternativa.", "general": "General", - "general_adv_mode": "Habilitar el mode avançat", + "general_continuity": "Continuïtat", + "general_continuity_e": "Quan està activat, podreu visualitzar moneders i transaccions seleccionats mitjançant els vostres altres dispositius Apple connectats amb iCloud.", + "groundcontrol_explanation": "GroundControl és un servidor de notificacions push gratuït i de codi obert per a moneders de Bitcoin. Podeu instal·lar el vostre propi servidor GroundControl i posar-ne l'URL aquí per no dependre de la infraestructura de BlueWallet. Deixeu-ho en blanc per utilitzar el servidor predeterminat de GroundControl.", "header": "Configuració", "language": "Idioma", "last_updated": "Última actualització", - "lightning_error_lndhub_uri": "LNDHub URI no vàlid", + "language_isRTL": "Cal reiniciar BlueWallet perquè l'orientació de l'idioma tingui efecte.", + "license": "Llicència", + "lightning_error_lndhub_uri": "URI de LNDhub no vàlid", + "lightning_error_lndhub_uri_tor": "URI de LNDhub no vàlid. Si us plau, assegureu-vos que l'aplicació Orbot està connectada i torneu-ho a provar.", + "lightning_saved": "Els vostres canvis s'han desat correctament.", "lightning_settings": "Configuració Lightning", - "tor_settings": "Configuració de Tor", + "lightning_settings_explain": "Per connectar-vos al vostre propi node LND, si us plau instal·leu LNDhub i poseu-ne l'URL aquí a la configuració. Tingueu en compte que només els moneders creats després de desar els canvis es connectaran al LNDhub especificat.", + "lndhub_github": "Repositori de GitHub", "network": "Xarxa", + "network_broadcast": "Transmetre transacció", "network_electrum": "Servidor Electrum", + "electrum_suggested_description": "Quan no s'estableix cap servidor preferit, se seleccionarà aleatòriament un servidor suggerit per utilitzar.", "not_a_valid_uri": "URI no vàlid", "notifications": "Notificacions", "open_link_in_explorer": "Obre l'enllaç a l'explorador", "password": "Contrasenya", - "password_explain": "Crear la contrasenya que usaràs per desxifrar l'informació dels moneders", - "passwords_do_not_match": "La contrasenya no coincideix", + "password_explain": "Introduïu la contrasenya que utilitzareu per desbloquejar el vostre emmagatzematge.", "plausible_deniability": "Negació plausible...", "privacy": "Privacitat", "privacy_read_clipboard": "Llegir el portapapers", "privacy_system_settings": "Configuració del Sistema", - "retype_password": "Introdueix de nou la contrasenya contrasenya", + "privacy_quickactions": "Dreceres del moneder", + "privacy_quickactions_explanation": "Mantingueu premuda la icona de l'aplicació BlueWallet per consultar ràpidament el saldo del vostre moneder.", + "privacy_clipboard_explanation": "Proporcionar dreceres si es troba una adreça o factura al vostre porta-retalls.", + "privacy_do_not_track": "Desactivar analítiques", + "privacy_do_not_track_explanation": "La informació de rendiment i fiabilitat no s'enviarà per a la seva anàlisi.", + "rate": "Cotització", + "push_notifications_explanation": "En activar les notificacions, el token del vostre dispositiu s'enviarà al servidor, juntament amb les adreces del moneder i els IDs de les transaccions per a tots els moneders i transaccions fets després d'activar les notificacions. El token del dispositiu s'utilitza per enviar notificacions, i la informació del moneder ens permet notificar-vos sobre Bitcoin entrants o confirmacions de transaccions.\n\nNomés es transmet la informació posterior a l'activació de les notificacions—no es recopila res anterior.\n\nDesactivar les notificacions eliminarà tota aquesta informació del servidor. A més, eliminar un moneder de l'aplicació també eliminarà la seva informació associada del servidor.", + "selfTest": "Autotest", "save": "guardar", "saved": "Desat", + "success_transaction_broadcasted": "La vostra transacció s'ha transmès correctament!", "total_balance": "Saldo total", + "total_balance_explanation": "Mostrar el saldo total de tots els vostres moneders als ginys de la pantalla d'inici.", + "widgets": "Ginys", "tools": "Eines" }, "notifications": { - "no_and_dont_ask": "No, i no m'ho tornis a preguntar", - "ask_me_later": "Pregunta'm després" + "would_you_like_to_receive_notifications": "Voleu rebre notificacions quan rebeu pagaments entrants?", + "notifications_subtitle": "Pagaments entrants i confirmacions de transaccions", + "no_and_dont_ask": "No, i no em tornis a preguntar.", + "permission_denied_message": "Heu denegat el permís per enviar-vos notificacions. Si voleu rebre notificacions, activeu-les a la configuració del vostre dispositiu." }, "transactions": { + "cancel_explain": "Reemplaçarem aquesta transacció per una que us paga a vós mateix i té comissions més altes. Això cancel·la efectivament la transacció actual. Aquesta tècnica s'anomena RBF—Replace by Fee.", + "cancel_no": "Aquesta transacció no es pot reemplaçar.", "cancel_title": "Cancel·lar aquesta transacció (RBF)", + "transaction_loading_error": "S'ha produït un problema en carregar la transacció. Si us plau, torneu-ho a provar més tard.", + "transaction_not_available": "Transacció no disponible", "confirmations_lowercase": "{confirmations} confirmacions", - "copy_link": "Còpia l'enllaç", + "expand_note": "Ampliar nota", "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ó.", + "cpfp_title": "Ampliar comissió (CPFP)", "details_balance_hide": "Amaga el saldo", "details_balance_show": "Mostra el saldo", - "details_block": "Altura del bloc", "details_copy": "Copiar", - "details_copy_amount": "Còpia la quantitat", - "details_from": "De", + "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_inputs": "Entrades", "details_outputs": "Sortides", "date": "Data", "details_received": "Rebut", - "transaction_note_saved": "La nota de transacció s'ha desat correctament.", - "details_show_in_block_explorer": "Mostrar en l'explorador de blocs", + "details_view_in_browser": "Veure al navegador", "details_title": "Transacció", + "incoming_transaction": "Transacció entrant", + "outgoing_transaction": "Transacció sortint", + "expired_transaction": "Transacció caducada", + "pending_transaction": "Transacció pendent", + "offchain": "Fora de cadena", + "onchain": "En cadena", "details_to": "A", + "enable_offline_signing": "Aquest moneder no s'està utilitzant juntament amb una signatura fora de línia. Voleu activar-la ara?", "list_conf": "Conf: {number}", "pending": "Pendent", - "view_wallet": "Veure {walletLabel}", + "pending_with_amount": "Pendent {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: En ~10 minuts", + "eta_3h": "ETA: En ~3 hores", + "eta_1d": "ETA: En ~1 dia", "list_title": "transaccions", + "list_title_sent": "Enviat", + "list_title_received": "Rebut", + "transaction": "Transacció", + "open_url_error": "No es pot obrir l'enllaç amb el navegador per defecte. Si us plau, canvieu el navegador per defecte i torneu-ho a provar.", + "rbf_explain": "Reemplaçarem aquesta transacció per una amb una comissió més alta perquè es mini més ràpid. Aquesta tècnica s'anomena RBF—Replace by Fee.", + "rbf_title": "Accelerar (RBF)", + "status_bump": "Accelerar", "status_cancel": "Cancel·lar la Transacció", + "transactions_count": "Recompte de transaccions", "txid": "ID de la transacció", - "updating": "Actualitzant..." + "updating": "Actualitzant...", + "watchOnlyWarningTitle": "Advertència de seguretat", + "watchOnlyWarningDescription": "Aneu amb compte amb els estafadors que sovint utilitzen moneders \"només lectura\" per enganyar els usuaris. Aquests moneders no us permeten controlar ni enviar fons; només us permeten veure el saldo.", + "custom_fee_warning_title": "Advertència", + "custom_fee_warning_description": "Les comissions inferiors a 1 sat/vB són vàlides, però podrien no ser propagades a causa de les polítiques dels nodes.", + "details_eta_analyzing": "Analitzant...", + "details_sent": "Enviat", + "details_section": "Detalls", + "details_explorer": "explorador", + "details_network_fee": "Comissió de xarxa", + "details_to_address": "A", + "details_id": "ID", + "details_note": "Nota", + "details_add_note": "afegir", + "details_advanced": "Avançat", + "details_fee_rate": "Taxa de comissió", + "details_size": "Mida", + "details_virtual_size": "Mida virtual", + "details_tx_hex": "Hex de la TX", + "details_inputs_count": "Entrades ({count})", + "details_outputs_count": "Sortides ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Moneder de Bitcoin senzill i potent", "add_create": "Crear", + "total_balance": "Saldo total", + "add_entropy_reset_title": "Restablir entropia", + "add_entropy_reset_message": "Canviar el tipus de moneder restablirà l'entropia actual. Voleu continuar?", + "add_entropy": "Entropia", + "add_entropy_bytes": "{bytes} bytes d'entropia", + "add_entropy_generated": "{gen} bytes d'entropia generada", + "add_entropy_provide": "Proporcionar entropia mitjançant llançaments de daus", + "add_entropy_remain": "{gen} bytes d'entropia generada. Els {rem} bytes restants s'obtindran del generador de nombres aleatoris del sistema.", "add_import_wallet": "Importar moneder", "add_lightning": "Lightning", + "add_lightning_explain": "Per gastar amb transaccions instantànies", + "add_lndhub": "Connectar al vostre LNDhub", + "add_lndhub_error": "L'adreça del node proporcionada no és un node LNDhub vàlid.", + "add_lndhub_placeholder": "L'adreça del vostre node", + "add_placeholder": "el meu primer moneder", "add_title": "Afegir moneder", "add_wallet_name": "nom del moneder", "add_wallet_type": "tipus de moneder", - "details_address": "Direcció", + "add_wallet_seed_length": "Longitud de la llavor", + "add_wallet_seed_length_12": "12 paraules", + "add_wallet_seed_length_24": "24 paraules", + "clipboard_bitcoin": "Teniu una adreça de Bitcoin al porta-retalls. Voleu utilitzar-la per a una transacció?", + "clipboard_lightning": "Teniu una factura Lightning al porta-retalls. Voleu utilitzar-la per a una transacció?", + "clear_clipboard_on_import": "Esborrar el porta-retalls en importar", + "details_address": "Adreça", "details_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.", "details_delete": "Eliminar", "details_delete_wallet": "Eliminar Moneder", + "details_derivation_path": "camí de derivació", + "details_display": "Mostrar a la pantalla d'inici", "details_export_backup": "Exportar / Guardar", - "details_master_fingerprint": "Petjada digital mestre", - "details_no_cancel": "No, cancel·lar", - "details_save": "Guardar", - "details_show_xpub": "Mostrar wallet XPUB", + "details_export_history": "Exportar historial a CSV", + "details_master_fingerprint": "Empremta digital mestra", + "details_multisig_type": "multisig", + "details_show_xpub": "Mostrar XPUB del moneder", + "details_show_addresses": "Mostrar adreces", "details_title": "Detalls del moneder", + "wallets": "moneders", + "swipe_balance_hide": "Amagar", + "swipe_balance_show": "Mostrar", + "drag_to_reorder": "Arrossegueu per reordenar", + "clear_search": "Esborrar cerca", "details_type": "Tipus", - "details_use_with_hardware_wallet": "Usar amb un moneder hardware", - "details_wallet_updated": "Moneder actualitzat", - "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_error": "No s'ha pogut importar. ¿És vàlid?", + "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_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?", "import_success": "Èxit", + "import_success_watchonly": "El vostre moneder s'ha importat correctament. AVÍS: Aquest és un moneder només lectura, NO podeu gastar des d'ell.", + "import_search_accounts": "Cercar comptes", "import_title": "importar", - "import_derivation_found": "trobat", + "learn_more": "Aprendre'n més", + "import_discovery_title": "Descobriment", + "import_discovery_subtitle": "Trieu un moneder descobert", + "import_discovery_derivation": "Utilitzar camí de derivació personalitzat", + "import_discovery_no_wallets": "No s'ha trobat cap moneder.", + "import_discovery_offline": "BlueWallet està actualment en mode fora de línia. En aquest mode, no es pot verificar l'existència del moneder, així que haureu de seleccionar-ne el correcte manualment", + "import_derivation_found": "Trobat", "import_derivation_found_not": "No trobat", + "import_derivation_loading": "Carregant...", + "import_derivation_subtitle": "Introduïu un camí de derivació personalitzat i intentarem descobrir el vostre moneder.", + "import_derivation_title": "Camí de derivació", + "import_derivation_unknown": "Desconegut", + "import_wrong_path": "Camí de derivació incorrecte", "list_create_a_button": "Afegir ara", "list_create_a_wallet": "Afegeix un moneder", + "list_create_a_wallet_text": "És gratuït i en podeu crear \ntants com vulgueu.", "list_empty_txs1": "Les seves transaccions apareixeran aquí,", "list_empty_txs1_lightning": "Els moneders Lightning poden ser usats per les seves transaccions diàries. Les comissions són baixes i els pagaments ràpids.", + "list_empty_txs2": "Comenceu amb el vostre moneder.", + "list_empty_txs2_lightning": "\nPer començar a utilitzar-lo, toqueu a Gestionar fons i recarregueu el vostre saldo.", "list_latest_transaction": "última transacció", "list_long_choose": "Tria la foto", + "paste_from_clipboard": "Enganxar", + "import_file": "Importar arxiu", "list_long_scan": "Escaneja el codi QR", "list_title": "moneders", "list_tryagain": "Torna-ho a provar", - "reorder_title": "Reorganitzar moneder", + "no_ln_wallet_error": "Abans de pagar una factura Lightning, primer heu d'afegir un moneder Lightning.", + "looks_like_bip38": "Això sembla una clau privada protegida amb contrasenya (BIP38).", + "manage_title": "Gestionar moneders", + "no_results_found": "No s'han trobat resultats.", + "please_continue_scanning": "Si us plau, continueu escanejant.", + "select_no_bitcoin": "Actualment no hi ha cap moneder de Bitcoin disponible.", + "select_no_bitcoin_exp": "Es requereix un moneder de Bitcoin per recarregar moneders Lightning. Si us plau, creeu-ne o importeu-ne un.", "select_wallet": "Seleccioni moneder", - "xpub_copiedToClipboard": "Copiat al porta-retalls." + "pull_to_refresh": "Estireu per actualitzar", + "warning_do_not_disclose": "No compartiu mai la informació següent", + "scan_import": "Escanegeu aquest codi QR per importar el vostre moneder en una altra aplicació.", + "write_down_header": "Crear una còpia de seguretat manual", + "write_down": "Apunteu i guardeu de forma segura aquestes paraules. Utilitzeu-les per restaurar el vostre moneder més endavant.", + "wallet_type_this": "Aquest tipus de moneder és {type}.", + "share_number": "Compartir {number}", + "copy_ln_url": "Copieu i guardeu de forma segura aquest URL per restaurar el vostre moneder més endavant.", + "copy_ln_public": "Copieu i guardeu de forma segura aquesta informació per restaurar el vostre moneder més endavant.", + "add_ln_wallet_first": "Primer heu d'afegir un moneder Lightning.", + "identity_pubkey": "Clau pública d'identitat", + "xpub_title": "XPUB del moneder", + "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" + }, + "total_balance_view": { + "display_in_bitcoin": "Mostrar en Bitcoin", + "hide": "Amagar", + "display_in_sats": "Mostrar en sats", + "display_in_fiat": "Mostrar en {currency}", + "title": "Saldo total", + "explanation": "Visualitzeu el saldo total de tots els vostres moneders a la pantalla de visió general." }, "multisig": { - "multisig_vault": "Bóveda", + "multisig_vault": "Caixa forta multisig", + "default_label": "Caixa forta multisig", + "multisig_vault_explain": "La millor seguretat per a grans quantitats", + "provide_signature": "Aportar signatura", + "provide_signature_details": "Utilitzeu el vostre dispositiu i moneder on resideix la clau per signar aquesta transacció", + "provide_signature_details_bluewallet": "A BlueWallet, aneu al menú de la pantalla Enviar i seleccioneu ", + "provide_signature_next_steps": "Escanejar o importar transacció signada", + "provide_signature_next_steps_details": "Un cop el vostre moneder hagi signat correctament la transacció, escanegeu el codi QR proporcionat o importeu el fitxer adjunt, i després reviseu tots els detalls de la transacció abans de transmetre-la.", + "vault_key": "Clau de caixa forta {number}", + "required_keys_out_of_total": "Claus requerides del total", + "fee": "Comissió: {number}", "fee_btc": "{number} BTC", "confirm": "Confirmar", "header": "Enviar", - "share": "Compartir", + "share": "Compartir...", "view": "Vista", + "shared_key_detected": "Cosignant compartit", + "shared_key_detected_question": "Us han compartit un cosignant, el voleu importar?", + "manage_keys": "Gestionar claus", + "how_many_signatures_can_bluewallet_make": "quantes signatures pot fer BlueWallet", + "signatures_required_to_spend": "Signatures requerides {number}", + "signatures_we_can_make": "pot fer-ne {number}", + "scan_or_import_file": "Escanejar o importar arxiu", + "export_coordination_setup": "Exportar configuració de coordinació", + "cosign_this_transaction": "Cosignar aquesta transacció?", + "lets_start": "Comencem", "create": "Crear", + "native_segwit_title": "Millor pràctica", + "wrapped_segwit_title": "Millor compatibilitat", + "legacy_title": "Legat", "co_sign_transaction": "Signar una transacció", + "what_is_vault": "Una caixa forta és un", + "what_is_vault_numberOfWallets": " multisig {m}-de-{n} ", "what_is_vault_wallet": "moneder", + "vault_advanced_customize": "Configuració de la caixa forta", + "needs": "Necessita", + "what_is_vault_description_number_of_vault_keys": " {m} claus de caixa forta ", + "what_is_vault_description_to_spend": "per gastar i una tercera que \npodeu utilitzar com a còpia de seguretat.", + "what_is_vault_description_to_spend_other": "per gastar.", + "quorum": "quòrum {m} de {n}", + "quorum_header": "Quòrum", "of": "de", + "wallet_type": "Tipus de moneder", + "invalid_mnemonics": "Aquesta frase mnemònica no sembla ser vàlida.", + "invalid_cosigner": "Dades de cosignant no vàlides", + "not_a_multisignature_xpub": "Aquest no és un XPUB d'un moneder multisignatura!", + "invalid_cosigner_format": "Cosignant incorrecte: aquest no és un cosignant per al format {format}.", "create_new_key": "Crear nou", "scan_or_open_file": "Escanejar o obrir arxiu", + "i_have_mnemonics": "Tinc una llavor per a aquesta clau.", + "type_your_mnemonics": "Inseriu una llavor per importar la vostra clau de caixa forta existent.", + "this_is_cosigners_xpub": "Aquest és l'XPUB del cosignant—llest per ser importat a un altre moneder. És segur compartir-lo.", + "this_is_cosigners_xpub_airdrop": "Si compartiu via AirDrop, els receptors han d'estar a la pantalla de coordinació.", + "wallet_key_created": "S'ha creat la vostra clau de caixa forta. Dediqueu un moment a fer una còpia de seguretat segura de la vostra llavor mnemònica.", + "are_you_sure_seed_will_be_lost": "Esteu segur? La vostra llavor mnemònica es perdrà si no en teniu una còpia de seguretat.", + "forget_this_seed": "Oblidar aquesta llavor i utilitzar l'XPUB en el seu lloc.", + "view_edit_cosigners": "Veure/editar cosignants", + "this_cosigner_is_already_imported": "Aquest cosignant ja està importat.", + "export_signed_psbt": "Exportar PSBT signat", + "input_fp": "Introduïu la petjada digital", + "input_fp_explain": "Ometeu per utilitzar la predeterminada (00000000)", + "input_path": "Inserir camí de derivació", + "input_path_explain": "Ometeu per utilitzar el predeterminat ({default})", "ms_help": "Ajuda", - "ms_help_title5": "Habilitar el mode avançat" + "ms_help_title": "Com funcionen les caixes fortes multisig: consells i trucs", + "ms_help_text": "Un moneder amb múltiples claus, per a major seguretat o custòdia compartida", + "ms_help_title1": "Es recomanen múltiples dispositius.", + "ms_help_1": "La caixa forta funcionarà amb altres aplicacions BlueWallet i moneders compatibles amb PSBT, com ara Electrum, Specter, Coldcard, Cobo Vault, etc.", + "ms_help_title2": "Edició de claus", + "ms_help_2": "Podeu crear totes les claus de la caixa forta en aquest dispositiu i eliminar-les o editar-les més endavant. Tenir totes les claus al mateix dispositiu té la seguretat equivalent a un moneder de Bitcoin normal.", + "ms_help_title3": "Còpies de seguretat de la caixa forta", + "ms_help_3": "A les opcions del moneder, hi trobareu la còpia de seguretat de la vostra caixa forta i la còpia de seguretat només lectura. Aquesta còpia de seguretat és com un mapa del vostre moneder. És essencial per recuperar el moneder en cas que perdeu una de les vostres llavors.", + "ms_help_title4": "Importació de caixes fortes", + "ms_help_4": "Per importar una multisig, utilitzeu el fitxer de còpia de seguretat i la funció Importar. Si només teniu llavors i XPUBs, podeu utilitzar el botó Importar individual en crear les claus de la caixa forta.", + "ms_help_title5": "Habilitar el mode avançat", + "ms_help_5": "Per defecte, BlueWallet generarà una caixa forta de 2 de 3. Per crear un quòrum diferent o canviar el tipus d'adreça, activeu el mode avançat a la Configuració." + }, + "is_it_my_address": { + "title": "És la meva adreça?", + "owns": "{label} posseeix {address}", + "enter_address": "Introduïu l'adreça", + "check_address": "Comprovar adreça", + "no_wallet_owns_address": "Cap dels moneders disponibles posseeix l'adreça proporcionada.", + "view_qrcode": "Veure codi QR" + }, + "autofill_word": { + "title": "Paraula final de la llavor", + "enter": "Introduïu la vostra frase mnemònica parcial", + "generate_word": "Generar la paraula final", + "error": "L'entrada no és una mnemònica parcial d'11 o 23 paraules. Si us plau, torneu-ho a provar." + }, + "cc": { + "change": "Canvi", + "coins_selected": "Monedes seleccionades ({number})", + "selected_summ": "{value} seleccionat", + "empty": "Aquest moneder no té cap moneda en aquest moment.", + "freeze": "Congelar", + "freezeLabel": "Congelar", + "freezeLabel_un": "Descongelar", + "header": "Control de monedes", + "use_coin": "Utilitzar moneda", + "use_coins": "Utilitzar monedes", + "tip": "Aquesta funcionalitat us permet veure, etiquetar, congelar o seleccionar monedes per a una millor gestió del moneder. Podeu seleccionar diverses monedes tocant els cercles de colors.", + "sort_asc": "Ascendent", + "sort_desc": "Descendent", + "sort_height": "Alçada", + "sort_value": "Valor", + "sort_label": "Etiqueta", + "sort_status": "estat", + "sort_by": "Ordenar per" }, "units": { + "BTC": "BTC", + "MAX": "Màx", + "sat_vbyte": "sat/vByte", "sats": "sats" }, "addresses": { + "copy_private_key": "Copiar clau privada", + "sensitive_private_key": "Advertència: les claus privades són extremadament sensibles. Continuar?", + "sign_title": "Signar/Verificar missatge", + "sign_help": "Aquí podeu crear o verificar una signatura criptogràfica basada en una adreça de Bitcoin.", + "sign_sign": "Signar", + "sign_verify": "Verificar", + "sign_signature_correct": "Verificació correcta!", + "sign_signature_incorrect": "La verificació ha fallat!", "sign_placeholder_address": "Adreça", + "sign_placeholder_message": "Missatge", + "sign_placeholder_signature": "Signatura", + "addresses_title": "Adreces", + "type_change": "Canvi", "type_receive": "Rebre", + "type_used": "Utilitzada", "transactions": "transaccions" + }, + "lnurl_auth": { + "register_question_part_1": "Voleu registrar un compte a", + "register_question_part_2": "utilitzant el vostre moneder Lightning?", + "register_answer": "Heu registrat correctament un compte a {hostname}!", + "login_question_part_1": "Voleu iniciar sessió a", + "login_question_part_2": "utilitzant el vostre moneder Lightning?", + "login_answer": "Heu iniciat sessió correctament a {hostname}!", + "link_question_part_1": "Voleu enllaçar el vostre compte a", + "link_question_part_2": "amb el vostre moneder Lightning?", + "link_answer": "El vostre moneder Lightning s'ha enllaçat correctament al vostre compte a {hostname}!", + "auth_question_part_1": "Voleu autenticar-vos a", + "auth_question_part_2": "utilitzant el vostre moneder Lightning?", + "auth_answer": "Us heu autenticat correctament a {hostname}!", + "could_not_auth": "No hem pogut autenticar-vos a {hostname}.", + "authenticate": "Autenticar" + }, + "bip47": { + "payment_code": "Codi de pagament", + "contacts": "Contactes", + "bip47_explain": "Codi reutilitzable i compartible", + "bip47_explain_subtitle": "BIP47", + "purpose": "Codi reutilitzable i compartible (BIP47)", + "pay_this_contact": "Pagar a aquest contacte", + "rename_contact": "Reanomenar contacte", + "copy_payment_code": "Copiar codi de pagament", + "hide_contact": "Amagar contacte", + "rename": "Reanomenar", + "provide_name": "Proporcioneu un nou nom per a aquest contacte", + "add_contact": "Afegir contacte", + "provide_payment_code": "Proporcioneu el codi de pagament", + "invalid_pc": "Codi de pagament no vàlid", + "notification_tx_unconfirmed": "La transacció de notificació encara no està confirmada, si us plau espereu", + "failed_create_notif_tx": "No s'ha pogut crear la transacció en cadena", + "onchain_tx_needed": "Cal una transacció en cadena", + "notif_tx_sent": "Transacció de notificació enviada. Si us plau, espereu que es confirmi", + "notif_tx": "Transacció de notificació", + "not_found": "No s'ha trobat el codi de pagament" } } diff --git a/loc/cs_cz.json b/loc/cs_cz.json index e6d5b542140..f3d725a09a9 100644 --- a/loc/cs_cz.json +++ b/loc/cs_cz.json @@ -1,320 +1,322 @@ { "_": { - "bad_password": "Nesprávné heslo, zkuste to znovu.", + "bad_password": "Nesprávné heslo. Zkuste to prosím znovu.", "cancel": "Zrušit", "continue": "Pokračovat", "clipboard": "Schránka", + "copied": "Zkopírováno!", + "discard_changes": "Zahodit změny?", + "discard_changes_explain": "Máte neuložené změny. Opravdu je chcete zahodit a opustit obrazovku?", "enter_password": "Zadejte heslo", "never": "Nikdy", - "disabled": "Vypnuto", - "of": "{number} z {total}", + "of": "{number} z(e) {total}", "ok": "OK", - "storage_is_encrypted": "Vaše úložiště je zašifrované. Zadejte heslo k odemčení", + "enter_url": "Zadejte URL", + "storage_is_encrypted": "Vaše úložiště je zašifrované. Zadejte heslo k odemčení.", "yes": "Ano", "no": "Ne", - "save": "Uložit", + "save": "Uložit…", "seed": "Seed", "success": "Úspěch", "wallet_key": "Klíč peněženky", - "invalid_animated_qr_code_fragment": "Neplatný animovaný fragment QRCode, zkuste to prosím znovu", - "file_saved": "Soubor {filePath} byl uložen ve vašem {destination}.", - "downloads_folder": "Složka Stažené soubory", "close": "Zavřít", "change_input_currency": "Změnit vstupní měnu", "refresh": "Obnovit", - "more": "Více", - "pick_image": "Vybrat Obrázek z galerie", - "pick_file": "Vybrat soubor", - "enter_amount": "Vložte částku", - "qr_custom_input_button": "Klikněte 10x k zadání vlastního vstupu" - }, - "alert": { - "default": "Upozornění" + "pick_image": "Vyberte z knihovny", + "pick_file": "Vyberte soubor", + "enter_amount": "Zadejte částku", + "qr_custom_input_button": "Klepněte 10× k zadání vlastního vstupu", + "unlock": "Odemknout", + "ssl_port": "SSL port", + "suggested": "Doporučené", + "port": "Port" }, "azteco": { "codeIs": "Váš kód voucheru je", - "errorBeforeRefeem": "Před uplatněním musíte nejprve přidat bitcoinovou peněženku.", + "errorBeforeRefeem": "Před uplatněním musíte nejdříve přidat bitcoinovou peněženku.", "errorSomething": "Něco se pokazilo. Je tento voucher stále platný?", "redeem": "Uplatnit do peněženky", "redeemButton": "Uplatnit", "success": "Úspěch", + "successMessage": "Poukaz byl úspěšně uplatněn! Vaše prostředky by měly brzy dorazit na vaši bitcoinovou peněženku.", "title": "Uplatnit Azte.co voucher" }, "entropy": { "save": "Uložit", "title": "Entropie", - "undo": "Zpět" + "undo": "Vrátit zpět", + "amountOfEntropy": "{bits} z(e) {limit} bitů" }, "errors": { - "broadcast": "Vysílání se nezdařilo", + "broadcast": "Odesílání se nezdařilo.", "error": "Chyba", "network": "Chyba sítě" }, "lnd": { - "active": "Aktivní", - "inactive": "Neaktivní", - "channels": "Kanály", - "no_channels": "Žádné kanály", - "claim_balance": "Požadovat zůstatek {balance}", - "close_channel": "Zavřít kanál", - "new_channel": "Nový kanál", - "errorInvoiceExpired": "Faktura vypršela", - "force_close_channel": "Vynutit uzavření kanálu?", + "errorInvoiceExpired": "Platnost faktury vypršela.", "expired": "Expirováno", - "node_alias": "Alias uzlu", "expiresIn": "Vyprší za {time} minut", "payButton": "Zaplatit", + "payment": "Platba", "placeholder": "Faktura nebo adresa", - "open_channel": "Otevřít kanál", - "funding_amount_placeholder": "Výše financování, např. 0.001", - "opening_channnel_for_from": "Otevření kanálu pro peněženku {forWalletLabel}, financováním z {fromWalletLabel}", - "are_you_sure_open_channel": "Jste si jisti, že chcete tento kanál otevřít?", "potentialFee": "Potenciální poplatek: {fee}", - "remote_host": "Vzdálený hostitel", "refill": "Doplnit", - "reconnect_peer": "Znovu připojit peera", "refill_create": "Chcete-li pokračovat, vytvořte prosím bitcoinovou peněženku, kterou můžete doplnit.", "refill_external": "Doplnit pomocí externí peněženky", "refill_lnd_balance": "Doplnit zůstatek na Lightning peněžence", - "sameWalletAsInvoiceError": "Fakturu nelze zaplatit stejnou peněženkou, která byla použita k jejímu vytvoření.", - "title": "Správa zůstatků", - "can_send": "Může odeslat", - "can_receive": "Může přijmout", - "view_logs": "Zobrazit logy" + "sameWalletAsInvoiceError": "Fakturu nemůžete zaplatit pomocí stejné peněženky, kterou jste použili k jejímu vytvoření.", + "title": "Správa prostředků" }, "lndViewInvoice": { "additional_info": "Dodatečné informace", "for": "Pro:", "lightning_invoice": "Lightning faktura", - "open_direct_channel": "Otevřít přímý kanál s tímto uzlem:", - "please_pay_between_and": "Zaplaťte prosím mezi {min} a {max}.", - "please_pay": "Prosím zaplaťte", + "please_pay_between_and": "Zaplaťte prosím mezi {min} a {max}", + "please_pay": "Zaplaťte prosím", "preimage": "Předobraz", - "sats": "sats.", - "wasnt_paid_and_expired": "Tato faktura nebyla zaplacena a její platnost vypršela." + "date_time": "Datum a čas", + "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ě", - "create_password": "Vytvořit heslo", - "create_password_explanation": "Heslo k falešnému úložišti nesmí být stejné jako heslo k hlavnímu úložišti", - "help": "Za určitých okolností můžete být donuceni k prozrazení vašeho hesla. K zajištění bezpečností vašich prostředků, BlueWallet může vytvořit další zašifrované úložiště s rozdílným heslem. V případě potřeby můžete toto heslo dát třetí straně. Pokud bude zadáno do BlueWallet, odemkne nové \"falešné\" úložiště. To bude vypadat legitimně, ale udrží vaše pravé hlavní úložiště v bezpečí.", + "create_password_explanation": "Heslo k falešnému úložišti nesmí být stejné jako heslo k hlavnímu úložišti.", + "help": "Za určitých okolností můžete být donuceni k prozrazení vašeho hesla. K zajištění bezpečnosti vašich prostředků může BlueWallet vytvořit další zašifrované úložiště s jiným heslem. V případě potřeby můžete toto heslo dát třetí straně. Pokud bude zadáno do BlueWallet, odemkne nové „falešné“ úložiště. To bude vypadat legitimně, ale udrží vaše pravé hlavní úložiště v bezpečí.", "help2": "Nové úložiště bude plně funkční, můžete na něj uložit minimální částku, aby vypadalo více uvěřitelně.", - "password_should_not_match": "Heslo k falešnému úložišti nesmí být stejné jako heslo k hlavnímu úložišti", - "passwords_do_not_match": "Hesla se neshodují, zkuste to prosím znovu.", - "retype_password": "Zadejte heslo znovu", - "success": "Úspěch", + "password_should_not_match": "Heslo k falešnému úložišti nesmí být stejné jako heslo k hlavnímu úložišti.", "title": "Věrohodná popiratelnost" }, "pleasebackup": { "ask": "Uložili jste záložní větu vaší peněženky? Tato záložní věta je vyžadována pro přístup k vašim finančním prostředkům pro případ ztráty tohoto zařízení. Bez záložní věty budou vaše prostředky trvale ztraceny.", - "ask_no": "Ne, neuložil", - "ask_yes": "Ano, uložil", - "ok": "Dobře, napsal jsem si to", - "ok_lnd": "Dobře, uložil jsem si to", - "text": "Věnujte prosím chvíli zapsaní si této mnemotechnické fráze na kousek papíru.\nJe to vaše záloha a můžete ji použít k obnovení peněženky.", - "text_lnd": "Uložte si tuto zálohu peněženky. Umožňí vám obnovit peněženku v případě ztráty.", - "title": "Vaše peněženka je vytvořena" + "ask_no": "Ne, neuložil.", + "ask_yes": "Ano, uložil.", + "ok": "Dobře, zapsal jsem si ji.", + "ok_lnd": "Dobře, uložil jsem to.", + "text": "Věnujte prosím chvíli zapsání si této mnemotechnické fráze na kousek papíru.\nJe to vaše záloha a můžete ji použít k obnovení peněženky.", + "text_lnd": "Uložte si tuto zálohu peněženky. Umožní vám obnovit peněženku v případě ztráty.", + "title": "Vaše peněženka byla vytvořena." }, "receive": { "details_create": "Vytvořit", "details_label": "Popis", "details_setAmount": "Přijmout částku...", - "details_share": "Sdílet", + "details_share": "Sdílet…", + "address_not_found": "Nepodařilo se vygenerovat přijímací adresu", "header": "Přijmout", - "maxSats": "Maximální množství sats je {max} ", - "maxSatsFull": "Maximální množství sats je {max} nebo {currency} ", - "minSats": "Minimální množství sats je {max} ", - "minSatsFull": "Minimální množství sats je {min} nebo {currency} " + "reset": "Obnovit", + "maxSats": "Maximální množství je {max} sats", + "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}", + "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í." }, "send": { "provided_address_is_invoice": "Zdá se, že tato adresa je určena pro Lightning fakturu. Přejděte prosím do své Lightning peněženky, abyste mohli provést platbu této faktury.", - "broadcastButton": "ODESLAT DO SÍTĚ", + "broadcastButton": "Odeslat do sítě", "broadcastError": "Chyba", - "broadcastNone": "Hash vstupní transakce", + "broadcastNone": "Vložte hex transakce", "broadcastPending": "Čekající", "broadcastSuccess": "Úspěch", "confirm_header": "Potvrdit", - "confirm_sendNow": "Poslat hned", + "confirm_sendNow": "Odeslat nyní", "create_amount": "Částka", "create_broadcast": "Odeslat do sítě", - "create_copy": "Kopírovat a vysílat později", - "create_details": "Detaily", + "create_copy": "Zkopírovat a odeslat později", + "create_details": "Podrobnosti", "create_fee": "Poplatek", - "create_memo": "Popisek", + "create_memo": "Poznámka", "create_satoshi_per_vbyte": "Satoshi na vByte", "create_this_is_hex": "Toto je vaše transakce, podepsána a připravena k odeslání do sítě.", "create_to": "Komu", "create_tx_size": "Velikost transakce", "create_verify": "Ověřit na coinb.in", + "details_insert_contact": "Vložte kontakt", "details_add_rec_add": "Přidat příjemce", "details_add_rec_rem": "Odebrat příjemce", + "details_add_recc_rem_all_alert_description": "Jste si jisti, že chcete odebrat všechny příjemce?", + "details_add_rec_rem_all": "Odebrat všechny příjemce", + "details_recipients_title": "Příjemci", + "details_recipient_title": "Příjemce č. {number} z(e) {total}", + "please_complete_recipient_title": "Nekompletní příjemce", + "please_complete_recipient_details": "Vyplňte prosím všechny detaily příjemce č. {number} před přidáním nového příjemce.", "details_address": "Adresa", - "details_address_field_is_not_valid": "Adresa není správně vyplněna", + "details_address_field_is_not_valid": "Adresa není správně vyplněna.", "details_adv_fee_bump": "Povolit navýšení poplatku", "details_adv_full": "Použít celý zůstatek", "details_adv_full_sure": "Opravdu chcete pro tuto transakci použít celý zůstatek vaší peněženky?", - "details_adv_full_sure_frozen": "Jste si jisti, že chcete pro tuto transakci použít celý zůstatek peněženky? Upozorňujeme, že zmrazené mince jsou vyloučeny.", + "details_adv_full_sure_frozen": "Jste si jisti, že chcete pro tuto transakci použít celý zůstatek peněženky? Upozorňujeme, že zmrazené mince nejsou zahrnuty.", "details_adv_import": "Importovat transakci", - "details_adv_import_qr": "Import transakce (QR)", - "details_amount_field_is_not_valid": "Čáskta není správně vyplněna", - "details_amount_field_is_less_than_minimum_amount_sat": "Zadaná částka je příliš malá. Zadejte částku větší než 500 sat.", + "details_adv_import_qr": "Importovat transakci (QR)", + "details_amount_field_is_not_valid": "Částka není správně vyplněna.", + "details_amount_field_is_less_than_minimum_amount_sat": "Zadaná částka je příliš malá. Zadejte částku vyšší než 500 sat.", "details_create": "Vytvořit fakturu", "details_error_decode": "Bitcoinovou adresu nelze dekódovat", - "details_fee_field_is_not_valid": "Poplatek není správně vyplněn", - "details_frozen": "{amount} BTC je zmraženo ", + "details_fee_field_is_not_valid": "Poplatek není správně vyplněn.", + "details_frozen": "{amount} BTC je zmrazeno.", "details_next": "Další", "details_no_signed_tx": "Vybraný soubor neobsahuje transakci, kterou lze importovat.", "details_note_placeholder": "Poznámka pro sebe", "details_scan": "Skenovat", "details_scan_hint": "Dvojitým klepnutím naskenujete nebo importujete cíl", - "details_total_exceeds_balance": "Částka, kterou se snažíte poslat, přesahuje dostupný zůstatek.", - "details_total_exceeds_balance_frozen": "Odesílaná částka překračuje dostupný zůstatek. Mějte prosím na paměti, že zmrazené mince nejsou zahrnuty.", + "details_scan_error": "Chyba skenování", + "details_total_exceeds_balance": "Částka, kterou se snažíte odeslat, přesahuje dostupný zůstatek.", + "details_total_exceeds_balance_frozen": "Odesílaná částka překračuje dostupný zůstatek. Upozorňujeme, že zmrazené mince nejsou zahrnuty.", "details_unrecognized_file_format": "Neznámý formát souboru", - "details_wallet_before_tx": "Před vytvořením transakce musíte nejprve přidat bitcoinovou peněženku.", - "dynamic_init": "Inicializace", + "details_wallet_before_tx": "Před vytvořením transakce musíte nejdříve přidat bitcoinovou peněženku.", + "dynamic_init": "Zahajování", "dynamic_next": "Další", "dynamic_prev": "Předchozí", - "dynamic_start": "Začít", + "dynamic_start": "Zahájit", "dynamic_stop": "Zastavit", - "fee_10m": "10m", - "fee_1d": "1d", - "fee_3h": "3h", + "fee_10m": "10 min.", + "fee_1d": "1 d", + "fee_3h": "3 h", "fee_custom": "Vlastní", + "insert_custom_fee": "Zadejte poplatek", "fee_fast": "Rychle", - "fee_medium": "Středně", - "fee_replace_minvb": "Celková hodnota poplatku (satoshi na vByte), kterou chcete zaplatit, by měla být vyšší než {min} sat/vByte.", - "fee_satvbyte": "v sat/vByte", + "fee_medium": "Středně rychle", + "fee_replace_minvb": "Celková sazba poplatku (satoshi na vByte), kterou chcete zaplatit, by měla být vyšší než {min} sat/vByte.", + "fee_satvbyte": "v jednotkách sat/vByte", "fee_slow": "Pomalu", - "header": "Poslat", + "header": "Odeslat", "input_clear": "Vymazat", "input_done": "Hotovo", "input_paste": "Vložit", "input_total": "Celkem:", "permission_camera_message": "K použití fotoaparátu potřebujeme vaše povolení", "psbt_sign": "Podepsat transakci", + "invalid_psbt": "Byla poskytnuta neplatná PSBT.", "open_settings": "Otevřít nastavení", - "permission_storage_later": "Zeptejte se mě později", - "permission_storage_message": "BlueWallet potřebuje vaše oprávnění k přístupu k vašemu úložišti, aby mohl tento soubor uložit.", "permission_storage_denied_message": "BlueWallet nemůže tento soubor uložit. Otevřete prosím nastavení zařízení a povolte funkci Oprávnění k ukládání.", "permission_storage_title": "Povolení k přístupu do úložiště", "psbt_clipboard": "Zkopírovat do schránky", - "psbt_this_is_psbt": "Toto je částečně podepsaná bitcoinová transakce (PSBT). Dokončete podepisování pomocí hardwarové peněženky.", - "psbt_tx_export": "Export do souboru", - "no_tx_signing_in_progress": "Neprobíhá žádné podepisování transakcí", + "psbt_this_is_psbt": "Toto je částečně podepsaná bitcoinová transakce (PSBT). Dokončete její podepsání pomocí hardwarové peněženky.", + "psbt_tx_export": "Exportovat do souboru", + "no_tx_signing_in_progress": "Neprobíhá žádné podepisování transakcí.", "outdated_rate": "Kurz byl naposledy aktualizován: {date}", "psbt_tx_open": "Otevřít podepsanou transakci", "psbt_tx_scan": "Skenovat podepsanou transakci", - "qr_error_no_qrcode": "Ve vybraném obrázku se nám nepodařilo najít kód QR. Ujistěte se, že obrázek obsahuje pouze QR kód a žádný další obsah, například text nebo tlačítka.", - "reset_amount": "Resetovat částku", + "qr_error_no_qrcode": "Ve vybraném obrázku nebyl nalezen platný QR kód. Zajistěte, aby obrázek obsahoval pouze QR kód a žádný další obsah jako třeba text nebo tlačítka.", + "reset_amount": "Vynulovat částku", "reset_amount_confirm": "Chcete částku vynulovat?", "success_done": "Hotovo", - "txSaved": "Transakční soubor ({filePath}) byl uložen do složky Soubory ke stažení.", - "problem_with_psbt": "Problém s PSBT" + "txSaved": "Soubor s transakcemi ({filePath}) byl uložen.", + "file_saved_at_path": "Soubor ({filePath}) byl uložen.", + "cant_send_to_silentpayment_adress": "Tato peněženka nedokáže odesílat prostředky na SilentPayment adresy", + "cant_send_to_bip47": "Tato peněženka nedokáže odesílat prostředky na platební kódy BIP47", + "cant_find_bip47_notification": "Nejdříve tento platební kód přidejte mezi kontakty", + "problem_with_psbt": "Problém s PSBT (částečně podepsanou bitcoinovou transakcí)" }, "settings": { "about": "O BlueWallet", - "about_awesome": "Postaveno s úžasem", - "about_backup": "Vždy zálohujte klíče!", + "about_awesome": "Sestaveno s úžasnými", + "about_backup": "Vždy zálohujte své klíče!", "about_free": "BlueWallet je bezplatný a open source projekt. Vytvořeno bitcoinery.", - "about_license": "MIT Licence", + "about_license": "Licence MIT", "about_release_notes": "Poznámky k vydání", "about_review": "Napište nám recenzi", - "performance_score": "Výkonnostní skóre: {num}", - "run_performance_test": "Test výkonu", + "performance_score": "Skóre výkonu: {num}", + "run_performance_test": "Otestovat výkon", "about_selftest": "Spustit autotest", - "about_selftest_electrum_disabled": "Samotné testování není v režimu Electrum Offline k dispozici. Vypněte prosím offline režim a zkuste to znovu.", + "block_explorer_invalid_custom_url": "Zadaná URL je neplatná. Zadejte prosím platnou URL začínající http:// nebo https://.", + "about_selftest_electrum_disabled": "Autotest není v offline režimu serveru Electrum k dispozici. Vypněte prosím offline režim a zkuste to znovu.", "about_selftest_ok": "Všechny interní testy úspěšně prošly. Peněženka funguje správně.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord Server", "about_sm_telegram": "Telegram kanál", - "about_sm_twitter": "Sleduj nás na Twitteru", - "advanced_options": "Pokročilé možnosti", + "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", - "biom_10times": "Pokusili jste se zadat heslo desetkrát. Chcete obnovit své ú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 totožnost.", - "biom_no_passcode": "Vaše zařízení nemá přístupový kód. Chcete-li pokračovat, nakonfigurujte přístupový kód v Nastavení aplikace.", + "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í.", "biom_remove_decrypt": "Všechny vaše peněženky budou odstraněny a vaše úložiště bude dešifrováno. Opravdu chcete pokračovat?", "currency": "Měna", - "currency_source": "Cena se získává z", + "currency_source": "Kurz je získáván z", "currency_fetch_error": "Při získávání kurzu vybrané měny došlo k chybě.", - "default_desc": "Pokud je zakázána, BlueWallet při spuštění ihned otevře vybranou peněženku.", - "default_info": "Výchozí informace", "default_title": "Při spuštění", - "default_wallets": "Zobrazit všechny peněženky", + "donate": "Darovat", + "donate_description": "Pomozte nám zachovat Blue zdarma!", "electrum_connected": "Připojeno", "electrum_connected_not": "Nepřipojeno", - "electrum_error_connect": "Nelze se připojit k poskytovanému serveru Electrum", + "electrum_error_connect": "K poskytnutému Electrum serveru se nelze připojit", + "electrum_error_connect_tor": "K zadanému Electrum serveru se nelze připojit. Ujistěte se prosím, že aplikace Orbot je připojena a zkuste to znovu.", "lndhub_uri": "Např. {example}", "electrum_host": "Např. {example}", "electrum_offline_mode": "Offline režim", - "electrum_offline_description": "Pokud je tato funkce povolena, vaše Bitcoin peněženky se nebudou pokoušet načítat zůstatky nebo transakce.", + "electrum_offline_description": "Pokud je tato funkce povolena, vaše bitcoinové peněženky se nebudou pokoušet načítat zůstatky nebo transakce.", "electrum_port": "Port, obvykle {example}", "use_ssl": "Použít protokol SSL", - "electrum_saved": "Vaše změny byly úspěšně uloženy. Změny se projeví až po restartu.", - "set_electrum_server_as_default": "Nastavit {server} jako výchozí electrum server?", - "set_lndhub_as_default": "Nastavit {url} jako výchozí LNDHub server?", - "electrum_settings_server": "Electrum Server", - "electrum_settings_explain": "Chcete-li použít výchozí nastavení, ponechte pole prázdné.", - "electrum_status": "Status", - "electrum_clear_alert_title": "Smazat historii?", - "electrum_clear_alert_message": "Chcete vymazat historii serverů electrum?", - "electrum_clear_alert_cancel": "Zrušit", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Vybrat", - "electrum_reset": "Obnovit do základního nastavení", + "electrum_saved": "Vaše změny byly úspěšně uloženy. Změny se projeví až po restartu aplikace BlueWallet.", + "set_electrum_server_as_default": "Nastavit {server} jako výchozí Electrum server?", + "set_lndhub_as_default": "Nastavit {url} jako výchozí LNDhub server?", + "electrum_settings_server": "Electrum server", + "electrum_status": "Stav", + "electrum_preferred_server": "Upřednostňovaný server", + "electrum_preferred_server_description": "Zadejte server, který má vaše peněženka používat pro všechny bitcoinové aktivity. Po nastavení bude vaše peněženka používat výhradně tento server ke kontrole zůstatků, odesílání transakcí a načítání síťových dat. Před nastavením se ujistěte, že tomuto serveru důvěřujete.", "electrum_unable_to_connect": "Nelze se připojit k {server}.", - "electrum_history": "Historie serveru", - "electrum_reset_to_default": "Opravdu chcete obnovit nastavení Electrum na výchozí?", - "electrum_clear": "Vymazat", - "tor_supported": "Podpora Toru", - "tor_unsupported": "Připojení pomocí Toru nejsou podporována.", + "electrum_history": "Historie", + "electrum_reset_to_default": "Toto nastavení nechá aplikaci BlueWallet náhodně vybrat server ze seznamu navrhovaných.", + "electrum_reset": "Obnovit do výchozího nastavení", + "electrum_reset_to_default_and_clear_history": "Obnovit výchozí nastavení a vymazat historii", "encrypt_decrypt": "Dešifrovat úložiště", - "encrypt_decrypt_q": "Opravdu chcete dešifrovat své úložiště? To umožní přístup k vašim peněženkám bez hesla.", - "encrypt_enc_and_pass": "Šifrováno a chráněno heslem", - "encrypt_title": "Bezpečnost", - "encrypt_tstorage": "Uložiště", + "encrypt_decrypt_q": "Opravdu chcete dešifrovat úložiště? To umožní přistupovat k vašim peněženkám bez hesla.", + "encrypt_enc_and_pass": "Chráněno heslem", + "encrypt_storage_explanation_headline": "Povolit šifrování úložiště", + "encrypt_storage_explanation_description_line1": "Povolení šifrování úložiště přidává další vrstvu zabezpečení pro vaši aplikaci způsobem, jakým jsou vaše data ukládána na vašem zařízení. Toto komukoliv ztěžuje přístup k vašim informacím bez vašeho povolení.", + "encrypt_storage_explanation_description_line2": "Nicméně je důležité vědět, že toto šifrování chrání pouze před přístupem k peněženkám uloženým na vašem zařízení. Peněženky tímto nejsou nijak zaheslovány nebo ochráněny další vrstvou zabezpečení.", + "i_understand": "Rozumím", + "block_explorer": "Block Explorer", + "block_explorer_preferred": "Použít upřednostněný prohlížeč bloků", + "block_explorer_error_saving_custom": "Během ukládání upřednostněného prohlížeče bloků nastala chyba", + "encrypt_title": "Zabezpečení", + "encrypt_tstorage": "Úložiště", "encrypt_use": "Použít {type}", - "encrypt_use_expl": "{type} bude použit k potvrzení vaší identity před provedením transakce, odemknutím, exportem nebo smazáním peněženky. {type} nebude použit k odemknutí zašifrovaného úložiště.", + "set_as_preferred": "Nastavit jako preferovaný", + "set_as_preferred_electrum": "Nastavení {host}:{port} jako upřednostňovaného serveru zakáže náhodné připojování k navrhovanému serveru.", + "encrypted_feature_disabled": "Tato funkce nemůže být použita, pokud je povoleno zašifrované úložiště.", + "encrypt_use_expl": "{type} bude použit(a) k potvrzení vaší identity před provedením transakce, odemknutím, exportováním nebo odstraněním peněženky.", + "biometrics_fail": "Pokud {type} není povolen, nebo selže při odemykání, můžete jako alternativu použít přístupový kód vašeho zařízení.", "general": "Obecné", - "general_adv_mode": "Pokročilý mód", - "general_adv_mode_e": "Pokud je povoleno, uvidíte rozšířené možnosti, například různé typy peněženek, možnost určit instanci LNDHub, ke které se chcete připojit, a vlastní entropii během vytváření peněženky.", "general_continuity": "Kontinuita", - "general_continuity_e": "Pokud je povoleno, budete moci prohlížet vybrané peněženky a transakce pomocí ostatních zařízení připojených k Apple iCloud.", - "groundcontrol_explanation": "GroundControl je bezplatný opensource server push notifikací pro bitcoinové peněženky. Můžete si nainstalovat svůj vlastní server GroundControl a sem vložit jeho URL, abyste se nespoléhali na infrastrukturu BlueWallet. Chcete-li použít výchozí, ponechte pole prázdné", + "general_continuity_e": "Pokud je povolena, budete moci prohlížet vybrané peněženky a transakce pomocí ostatních zařízení připojených k Apple iCloud.", + "groundcontrol_explanation": "GroundControl je bezplatný open source server push oznámení pro bitcoinové peněženky. Můžete si nainstalovat svůj vlastní server GroundControl a vložit sem jeho URL, abyste se nespoléhali na infrastrukturu BlueWallet. Chcete-li použít výchozí server, ponechte pole prázdné.", "header": "Nastavení", "language": "Jazyk", - "last_updated": "Poslední aktualizace", - "language_isRTL": "Aby se jazyková orientace projevila, je nutné restartovat BlueWallet.", - "lightning_error_lndhub_uri": "Neplatná LNDHub URI", - "lightning_saved": "Vaše změny byly úspěšně uloženy", - "lightning_settings": "Lightning settings", - "tor_settings": "Nastavení Tor", - "lightning_settings_explain": "Chcete-li se připojit k vlastnímu uzlu LND, nainstalujte si LNDHub a v nastavení zadejte jeho adresu URL. Upozorňujeme, že k zadanému LNDHubu se připojí pouze peněženky vytvořené po uložení změn.", + "last_updated": "Naposledy aktualizováno", + "language_isRTL": "Aby se projevila změna nastavení jazyka, je nutné restartovat BlueWallet.", + "license": "Licence", + "lightning_error_lndhub_uri": "Neplatný LNDhub URI", + "lightning_error_lndhub_uri_tor": "Neplatná LNDhub URI. Ujistěte se prosím, že aplikace Orbot je připojena a zkuste to znovu.", + "lightning_saved": "Vaše změny byly úspěšně uloženy.", + "lightning_settings": "Nastavení Lightning", + "lightning_settings_explain": "Nainstalujte si prosím LNDhub a vložte jeho URL zde v nastavení, abyste se mohli připojit k vlastnímu LND uzlu. Vezměte prosím na vědomí, že ke specifikovanému LNDhubu se připojí pouze peněženky vytvořené až po uložení změn.", + "lndhub_github": "Repozitář na GitHubu", "network": "Síť", - "network_broadcast": "Vyslat transakci", + "network_broadcast": "Odeslat transakci", "network_electrum": "Electrum server", + "electrum_suggested_description": "Pokud nebude nastaven preferovaný server, bude náhodně vybrán navrhovaný server.", "not_a_valid_uri": "Neplatná URI", "notifications": "Oznámení", "open_link_in_explorer": "Otevřít odkaz v průzkumníku", "password": "Heslo", - "password_explain": "Vytořte si heslo k zašifrování úložiště.", - "passwords_do_not_match": "Hesla se neshodují", + "password_explain": "Zadejte heslo, které budete používat pro odemčení úložiště.", "plausible_deniability": "Věrohodná popiratelnost", "privacy": "Soukromí", "privacy_read_clipboard": "Kopírovat ze schránky", "privacy_system_settings": "Systémová nastavení", "privacy_quickactions": "Zástupci peněženky", "privacy_quickactions_explanation": "Dlouhý stisk ikony BlueWallet na vaší výchozí obrazovce zobrazí váš zůstatek.", - "privacy_clipboard_explanation": "Pokud se ve vaší schránce nachází adresa nebo faktura, zadejte zástupce.", - "privacy_do_not_track": "Zakázat Analytiku", - "privacy_do_not_track_explanation": "Informace o výkonu a spolehlivosti nebudou odeslány k analýze.", - "push_notifications": "Push notifikace", - "rate": "Sazba", - "retype_password": "Zadejte heslo znovu", + "privacy_clipboard_explanation": "Poskytnout zkratky, pokud se ve vaší schránce nachází adresa nebo faktura.", + "privacy_do_not_track": "Zakázat analytiku", + "privacy_do_not_track_explanation": "Informace o výkonu a spolehlivosti nebudou odesílány k analýze.", + "rate": "Kurz", + "push_notifications_explanation": "Povolením oznámení bude token vašeho zařízení odeslán na server společně s adresami peněženky a transakčními ID od všech peněženek a transakcí provedených po povolení oznámení. Token zařízení je využíván k odesílání oznámení a informace o peněžence nám umožňuje informovat vás o příchozích bitcoinech nebo o potvrzení transakcí.\n\nJsou vysílány pouze informace od té doby, co povolíte oznámení – z dřívější doby nejsou žádné informace shromažďovány.\n\nZakázáním oznámení odstraníte všechny tyto informace ze serveru. Odstraněním peněženky z aplikace taktéž dojde k odebrání jejích souvisejících informací ze serveru.", "selfTest": "Autotest", "save": "Uložit", "saved": "Uloženo", - "success_transaction_broadcasted": "Úspěch! Vaše transakce byla odvysílána!", + "success_transaction_broadcasted": "Vaše transakce byla úspěšně odeslána!", "total_balance": "Celkový zůstatek", "total_balance_explanation": "Zobrazte celkový zůstatek všech vašich peněženek na widgetech na domovské obrazovce.", "widgets": "Widgety", @@ -322,288 +324,381 @@ }, "notifications": { "would_you_like_to_receive_notifications": "Chcete dostávat oznámení o příchozích platbách?", - "no_and_dont_ask": "Ne, a už se mě znovu neptejte", - "ask_me_later": "Zeptejte se mě později" + "notifications_subtitle": "Příchozí platby a potvrzení transakcí", + "no_and_dont_ask": "Ne, a už se mě znovu neptejte.", + "permission_denied_message": "Nemáte povolené oprávnění pro zasílání oznámení. Jestliže chcete dostávat oznámení, povolte oprávnění v nastavení vašeho zařízení." }, "transactions": { "cancel_explain": "Tuto transakci nahradíme takovou, která vám zaplatí a bude mít vyšší poplatky. Tím se zruší aktuální transakce. Toto se nazývá RBF – Replace by Fee.", - "cancel_no": "Tato transakce není vyměnitelná", + "cancel_no": "Tato transakce není nahraditelná.", "cancel_title": "Zrušit tuto transakci (RBF)", + "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": "Kopí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 - dítě platí za rodiče.", - "cpfp_no_bump": "Tuto transakci nelze popostrč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).", + "cpfp_no_bump": "Tuto transakci nelze popostrčit.", "cpfp_title": "Poplatek za popostrčení (CPFP)", "details_balance_hide": "Skrýt zůstatek", "details_balance_show": "Zobrazit zůstatek", - "details_block": "Výška bloku", - "details_copy": "Kopírovat", - "details_copy_amount": "Kopírovat částku", - "details_copy_block_explorer_link": "Kopírovat odkaz na průzkumník bloků", - "details_copy_note": "Kopírovat poznámku", - "details_copy_txid": "Kopírovat ID transakce", - "details_from": "Vstup", + "details_copy": "Zkopírovat", + "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_inputs": "Vstupy", "details_outputs": "Výstupy", "date": "Datum", "details_received": "Přijato", - "transaction_note_saved": "Transakční poznámka byla úspěšně uložena.", - "details_show_in_block_explorer": "Ukázat v block exploreru", + "details_view_in_browser": "Zobrazit v prohlížeči", "details_title": "Transakce", + "incoming_transaction": "Příchozí transakce", + "outgoing_transaction": "Odchozí transakce", + "expired_transaction": "Expirovaná transakce", + "pending_transaction": "Čekající transakce", + "offchain": "Off-chain", + "onchain": "On-chain", "details_to": "Výstup", "enable_offline_signing": "Tato peněženka se nepoužívá ve spojení s offline podpisem. Chcete ho nyní povolit?", "list_conf": "Potvrz: {number}", "pending": "Čekající", "pending_with_amount": "Čekající {amt1} ({amt2})", "received_with_amount": "+{amt1} ({amt2})", - "eta_10m": "Cca: Za ~10 minut", - "eta_3h": "Cca: Za ~3 hodiny", - "eta_1d": "Cca: Za ~1 den", - "view_wallet": "Zobrazit {walletLabel}", + "eta_10m": "Doručení: Za ~10 minut", + "eta_3h": "Doručení: Za ~3 hodiny", + "eta_1d": "Doručení: Za ~1 den", "list_title": "Transakce", - "open_url_error": "Nebylo možné otevřít odkaz ve výchozím prohlížeči. Prosím, změňte svůj výchozí prohlížeč a zkuste to znovu.", - "rbf_explain": "Tuto transakci nahradíme transakcí s vyšším poplatkem, aby byla těžena rychleji. Toto se nazývá RBF – Replace by Fee.", - "rbf_title": "Poplatek za popostrčení (CPFP)", + "list_title_sent": "Odeslané", + "list_title_received": "Přijato", + "transaction": "Transakce", + "open_url_error": "Nebylo možné otevřít odkaz ve výchozím prohlížeči. Změňte prosím svůj výchozí prohlížeč a zkuste to znovu.", + "rbf_explain": "Tuto transakci nahradíme transakcí s vyšším poplatkem, aby byla vytěžena rychleji. Toto se nazývá RBF – Replace by Fee.", + "rbf_title": "Poplatek za popostrčení (RBF)", "status_bump": "Poplatek za popostrčení", "status_cancel": "Zrušit transakci", "transactions_count": "Počet transakcí", - "txid": "Transakční ID", - "updating": "Aktualizace..." + "txid": "ID transakce", + "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.", + "custom_fee_warning_title": "Varování", + "custom_fee_warning_description": "Poplatky ve výši pod 1 sat/vB jsou platné, ale nemusí být vyslány dále do sítě z důvodu zásad uzlu.", + "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_note": "Poznámka", + "details_add_note": "přidat", + "details_advanced": "Pokročilé", + "details_fee_rate": "Sazba poplatku", + "details_size": "Velikost", + "details_virtual_size": "Virtuální velikost", + "details_tx_hex": "Hex transakce", + "details_inputs_count": "Vstupy ({count})", + "details_outputs_count": "Výstupy ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Jednoduchá a výkonná bitcoinová peněženka", "add_create": "Vytvořit", - "add_entropy_generated": "{gen} bajtů generované entropie", - "add_entropy_provide": "Poskytněte entropii pomocí hodu kostkami", - "add_entropy_remain": "{gen} bajtů generované entropie. Zbývající {rem} bajty budou získány od generátoru náhodných čísel systému.", + "total_balance": "Celkový zůstatek", + "add_entropy_reset_title": "Obnovit entropii", + "add_entropy_reset_message": "Změna typu peněženky obnoví aktuální entropii. Přejete si pokračovat?", + "add_entropy": "Entropie", + "add_entropy_bytes": "{bytes} bajtů entropie", + "add_entropy_generated": "{gen} bajtů vygenerované entropie", + "add_entropy_provide": "Poskytnout entropii pomocí hodu kostkami", + "add_entropy_remain": "{gen} bajtů vygenerované entropie. Zbývající bajty ({rem}) budou získány od systémového generátoru náhodných čísel.", "add_import_wallet": "Importovat peněženku", "add_lightning": "Lightning", "add_lightning_explain": "Pro utrácení s okamžitými transakcemi", - "add_lndhub": "Připojit se k vašemu LNDHub", - "add_lndhub_error": "Zadaná adresa uzlu je neplatný LNDHub uzel.", + "add_lndhub": "Připojte se ke svému LNDhubu", + "add_lndhub_error": "Uvedená adresa uzlu je neplatný LNDhub uzel.", "add_lndhub_placeholder": "Adresa vašeho uzlu", "add_placeholder": "moje první peněženka", "add_title": "Přidat peněženku", "add_wallet_name": "Název peněženky", "add_wallet_type": "Typ", - "balance": "Zůstatek", - "clipboard_bitcoin": "Ve své schránce máte bitcoinovou adresu. Chcete jej použít pro transakci?", + "add_wallet_seed_length": "Délka seedu", + "add_wallet_seed_length_12": "12 slov", + "add_wallet_seed_length_24": "24 slov", + "clipboard_bitcoin": "Ve schránce máte bitcoinovou adresu. Chcete ji použít pro transakci?", "clipboard_lightning": "Ve schránce máte Lightning fakturu. Chcete ji použít pro transakci?", + "clear_clipboard_on_import": "Vyprázdnit schránku při importu", "details_address": "Adresa", "details_advanced": "Pokročilé", - "details_are_you_sure": "Jste si jistý?", + "details_are_you_sure": "Jste si jisti?", "details_connected_to": "Připojeno k", - "details_del_wb_err": "Poskytnutá částka zůstatku neodpovídá zůstatku této peněženky. Prosím zkuste to znovu", - "details_del_wb_q": "Tato peněženka má zůstatek. Než budete pokračovat, uvědomte si, že bez přístupové fráze této peněženky nebudete moci získat prostředky zpět. Abyste se vyhnuli náhodnému odstranění, zadejte prosím zůstatek peněženky ve výši {balance} satoshi.", + "details_del_wb_err": "Poskytnutá částka zůstatku neodpovídá zůstatku této peněženky. Prosím zkuste to znovu.", + "details_del_wb_q": "Tato peněženka má nějaký zůstatek. Než budete pokračovat, uvědomte si, že bez přístupové fráze této peněženky nebudete moci tyto prostředky získat zpět. Abyste se vyhnuli náhodnému odstranění, zadejte prosím zůstatek peněženky ve výši {balance} satoshi.", "details_delete": "Smazat", "details_delete_wallet": "Smazat peněženku", "details_derivation_path": "derivační cesta", - "details_display": "Zobrazit v seznamu peněženek", - "details_export_backup": "Exportovat / zálohovat", - "details_export_history": "Export historie do CSV", + "details_display": "Zobrazit na domovské obrazovce", + "details_export_backup": "Exportovat/zálohovat", + "details_export_history": "Exportovat historii do CSV", "details_master_fingerprint": "Hlavní otisk", "details_multisig_type": "multisig", - "details_no_cancel": "Ne, zrušit", - "details_save": "Uložit", - "details_show_xpub": "Ukázat XPUB", + "details_show_xpub": "Zobrazit XPUB peněženky", "details_show_addresses": "Zobrazit adresy", "details_title": "Peněženka", + "wallets": "Peněženky", + "swipe_balance_hide": "Skrýt", + "swipe_balance_show": "Zobrazit", + "drag_to_reorder": "Přetažením změníte pořadí", + "clear_search": "Vymazat vyhledávání", "details_type": "Typ", "details_use_with_hardware_wallet": "Použít s hardwarovou peněženkou", - "details_wallet_updated": "Peněženka byla aktualizována", "details_yes_delete": "Ano, smazat", - "enter_bip38_password": "Zadejte heslo pro odšifrování.", + "enter_bip38_password": "Zadejte heslo pro dešifrování", "export_title": "Exportovat peněženku", "import_do_import": "Importovat", "import_passphrase": "Přístupová fráze", - "import_passphrase_title": "Fráze", + "import_passphrase_title": "Přístupová fráze", "import_passphrase_message": "Zadejte přístupovou frázi, pokud jste nějakou použili", "import_error": "Chyba při importu. Prosím ujistěte se, že poskytnutá data jsou správná.", - "import_explanation": "Zadejte seed slova, veřejný klíč, WIF nebo cokoli, co máte. BlueWallet se bude snažit uhodnout správný formát a importovat vaši peněženku.", - "import_imported": "Importováno", - "import_scan_qr": "Naskenujte nebo importujte soubor", + "import_explanation": "Zadejte slova seedu, veřejný klíč, WIF nebo cokoliv, co máte. BlueWallet se bude snažit uhodnout správný formát a importovat vaši peněženku.", + "import_imported": "Importovaná", + "import_scan_qr": "Skenovat nebo importovat soubor", "import_success": "Vaše peněženka byla úspěšně importována.", - "import_success_watchonly": "Vaše peněženka byla úspěšně importována. UPOZORNĚNÍ: Tato peněženka je určena pouze pro sledování, nelze z ní utrácet.", + "import_success_watchonly": "Vaše peněženka byla úspěšně importována. UPOZORNĚNÍ: Tato peněženka je určena pouze pro sledování, nelze z ní utrácet prostředky.", "import_search_accounts": "Vyhledat účty", - "import_title": "importovat", + "import_title": "Importovat", + "learn_more": "Zjistěte více", "import_discovery_title": "Objevování", - "import_discovery_subtitle": "Zvolte si objevenou peněženku", + "import_discovery_subtitle": "Vyberte si objevenou peněženku", "import_discovery_derivation": "Použít vlastní derivační cestu", "import_discovery_no_wallets": "Nebyly nalezeny žádné peněženky.", - "import_derivation_found": "nalezeno", - "import_derivation_found_not": "nenalezeno", - "import_derivation_loading": "načítání...", + "import_discovery_offline": "Aplikace BlueWallet je aktuálně v režimu offline. V tomto režimu nemůže ověřovat existenci peněženky, takže budete muset tu správnou vybrat ručně.", + "import_derivation_found": "Nalezeno", + "import_derivation_found_not": "Nenalezeno", + "import_derivation_loading": "Načítání…", "import_derivation_subtitle": "Zadejte vlastní derivační cestu a my se pokusíme objevit vaši peněženku.", "import_derivation_title": "Derivační cesta", - "import_derivation_unknown": "neznámá", - "import_wrong_path": "špatná derivační cesta", + "import_derivation_unknown": "Neznámá", + "import_wrong_path": "Špatná derivační cesta", "list_create_a_button": "Přidat nyní", "list_create_a_wallet": "Přidat peněženku", - "list_create_a_wallet_text": "Je to zdarma a můžete jich vytvořit \nkolik budete chtít", - "list_empty_txs1": "Zde budou zobrazeny vaše transakce,", - "list_empty_txs1_lightning": "Lightning peněženka by měla být použita pro vaše každodenní transakce. Poplatky jsou nespravedlivě levné a rychlost je vysoká.", + "list_create_a_wallet_text": "Je to zdarma a můžete jich vytvořit \nkolik jen budete chtít.", + "list_empty_txs1": "Zde budou zobrazeny vaše transakce.", + "list_empty_txs1_lightning": "Lightning peněženku byste měli používat pro vaše každodenní transakce. Poplatky jsou nespravedlivě levné a rychlost je blesková.", "list_empty_txs2": "Začněte s peněženkou.", - "list_empty_txs2_lightning": "\nChcete-li začít používat, klepněte na „spravované prostředky“ a doplňte zůstatek.", + "list_empty_txs2_lightning": "\nChcete-li ji začít používat, klepněte na „Správa prostředků“ a doplňte zůstatek.", "list_latest_transaction": "Poslední transakce", - "list_ln_browser": "Prohlížeč LApp", "list_long_choose": "Vybrat fotku", - "list_long_clipboard": "Kopírovat ze schránky", + "paste_from_clipboard": "Vložit", + "import_file": "Importovat soubor", "list_long_scan": "Naskenovat QR kód", "list_title": "Peněženky", - "list_tryagain": "Zkuste to znovu", - "no_ln_wallet_error": "Před zaplacením Lightning faktury musíte nejprve přidat Lightning peněženku.", - "looks_like_bip38": "Tohle vypadá jako soukromý klíč chráněný heslem (BIP38)", - "reorder_title": "Seřadit peěženky", - "reorder_instructions": "Klepněte a podržte peněženku a přetáhněte ji po seznamu.", - "please_continue_scanning": "Pokračujte ve skenování", + "list_tryagain": "Opakovat", + "no_ln_wallet_error": "Před zaplacením Lightning faktury musíte nejdříve přidat Lightning peněženku.", + "looks_like_bip38": "Tohle vypadá jako soukromý klíč chráněný heslem (BIP38).", + "manage_title": "Spravovat peněženky", + "no_results_found": "Nebyly nalezeny žádné výsledky.", + "please_continue_scanning": "Pokračujte ve skenování.", "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. Vytvořte nebo importujte jednu.", + "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": "Varování! Nezveřejňujte", - "add_ln_wallet_first": "Nejprve musíte přidat Lightning peněženku.", + "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.", + "write_down_header": "Vytvořit ruční zálohu", + "write_down": "Zapište si a bezpečně uložte tato slova. Lze je použít pro pozdější obnovu vaší peněženky.", + "wallet_type_this": "Typ této peněženky je {type}.", + "share_number": "Sdílet {number}", + "copy_ln_url": "Zkopírujte a bezpečně si uložte tuto URL pro pozdější obnovení vaší peněženky.", + "copy_ln_public": "Zkopírujte a bezpečně si tuto informaci uložte pro pozdější obnovení vaší peněženky.", + "add_ln_wallet_first": "Nejdříve musíte přidat Lightning peněženku.", "identity_pubkey": "Identity Pubkey", - "xpub_title": "XPUB peněženky" + "xpub_title": "XPUB peněženky", + "manage_wallets_search_placeholder": "Prohledávejte peněženky, adresy, transakce a poznámky", + "more_info": "Další informace", + "details_delete_wallet_error_message": "Došlo k problému během potvrzování, zda byla tato peněženka odstraněna z oznámení. Toto může být způsobeno problémem se sítí nebo kvůli špatnému připojení. Pokud budete pokračovat, je možné, že i nadále budete dostávat oznámení o transakcích souvisejících s touto peněženkou, přestože už bude odstraněna.", + "details_delete_anyway": "Přesto odstranit" + }, + "total_balance_view": { + "display_in_bitcoin": "Zobrazit v bitcoinech", + "hide": "Skrýt", + "display_in_sats": "Zobrazit v satoshi", + "display_in_fiat": "Zobrazit v {currency}", + "title": "Celkový zůstatek", + "explanation": "Zobrazit celkový zůstatek všech vašich peněženek na přehledové obrazovce." }, "multisig": { - "multisig_vault": "Uložiště", - "default_label": "Vícepodpisové uložiště", + "multisig_vault": "Vícepodpisové Úložiště", + "default_label": "Vícepodpisové Úložiště", "multisig_vault_explain": "Nejlepší zabezpečení pro velké částky", "provide_signature": "Poskytnout podpis", - "vault_key": "Klíč úložiště {number}", - "required_keys_out_of_total": "Požadované klíče z celkového počtu", + "provide_signature_details": "K podpisu této transakce použijte své zařízení a peněženku, ve které je klíč umístěn", + "provide_signature_details_bluewallet": "V aplikaci BlueWallet přejděte do nabídky Odeslat a vyberte", + "provide_signature_next_steps": "Naskenujte nebo importujte podepsanou transakci", + "provide_signature_next_steps_details": "Poté, co vaše peněženka úspěšně podepsala transakci, naskenujte uvedený QR kód, nebo importujte doprovodný soubor, a poté zkontrolujte veškeré podrobnosti transakce před tím, než ji odešlete.", + "vault_key": "{number}. klíč Úložiště", + "required_keys_out_of_total": "Požadovaných klíčů z celkového počtu", "fee": "Poplatek: {number}", "fee_btc": "{number} BTC", "confirm": "Potvrdit", "header": "Odeslat", - "share": "Sdílet", - "view": "Podívat se", + "share": "Sdílet…", + "view": "Zobrazit", + "shared_key_detected": "Sdílený spolupodepisovatel", + "shared_key_detected_question": "Byl s vámi sdílen spolupodepisovatel. Chcete jej importovat?", "manage_keys": "Spravovat klíče", "how_many_signatures_can_bluewallet_make": "kolik podpisů může BlueWallet udělat", "signatures_required_to_spend": "Vyžadují se {number} podpisy", - "signatures_we_can_make": "může vytvořit {number}", + "signatures_we_can_make": "ze {number}", "scan_or_import_file": "Naskenujte nebo importujte soubor", "export_coordination_setup": "Nastavení koordinace exportu", - "cosign_this_transaction": "Spolupodepsat transakci?", + "cosign_this_transaction": "Spolupodepsat tuto transakci?", "lets_start": "Začněme", "create": "Vytvořit", "native_segwit_title": "Nejlepší praxe", "wrapped_segwit_title": "Nejlepší kompatibilita", - "legacy_title": "Starší", + "legacy_title": "Zastaralé (legacy)", "co_sign_transaction": "Podepsat transakci", - "what_is_vault": "Uložiště je", - "what_is_vault_numberOfWallets": " {m}-z-{n} multisig ", + "what_is_vault": "Úložiště je", + "what_is_vault_numberOfWallets": " {m} z(e) {n} vícepodpisová ", "what_is_vault_wallet": "peněženka.", - "vault_advanced_customize": "Nastavení úložiště...", + "vault_advanced_customize": "Nastavení Úložiště", "needs": "Potřebuje", - "what_is_vault_description_number_of_vault_keys": " {m} klíčů uložiště", - "what_is_vault_description_to_spend": "k utracení a třetí můžete\npoužít jako zálohu.", - "what_is_vault_description_to_spend_other": "k utracení.", - "quorum": "{m} z {n} kvorum", - "quorum_header": "Kvorum", - "of": "z", + "what_is_vault_description_number_of_vault_keys": " {m} klíčů Úložiště ", + "what_is_vault_description_to_spend": "k utracení prostředků a třetí\nmůžete použít jako zálohu.", + "what_is_vault_description_to_spend_other": "k utracení prostředků.", + "quorum": "kvórum {m} z(e) {n}", + "quorum_header": "Kvórum", + "of": "z(e)", "wallet_type": "Typ peněženky", - "invalid_mnemonics": "Zdá se, že tato mnemotechnická fráze není platná", + "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", - "create_new_key": "Vytvořit novou", + "not_a_multisignature_xpub": "Toto není XPUB z vícepodpisové peněženky!", + "invalid_cosigner_format": "Nesprávný spolupodepisující: toto není spolupodepisující pro {format} formát.", + "create_new_key": "Vytvořit nový", "scan_or_open_file": "Naskenujte nebo otevřete soubor", - "i_have_mnemonics": "Mám seed pro tento klíč...", - "type_your_mnemonics": "Vložte seed k importu stávajícího klíče uložiště", - "this_is_cosigners_xpub": "Toto je xpub spolupodepisujícího připravený k importu do jiné peněženky. Je bezpečné ho sdílet.", - "wallet_key_created": "Klíč k ulož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": "Opravdu? Vaš mnemotechnický seed budou ztracen, pokud nemáte zálohu", - "forget_this_seed": "Zapomeňte na tento seed a použijte XPUB", + "i_have_mnemonics": "Mám seed pro tento klíč.", + "type_your_mnemonics": "Vložte seed pro importování vašeho stávajícího klíče Úložiště.", + "this_is_cosigners_xpub": "Toto je XPUB spolupodepisujícího připravený k importu do jiné peněženky. Je bezpečné ho sdílet.", + "this_is_cosigners_xpub_airdrop": "Pokud sdílíte přes AirDrop, příjemci musí být na obrazovce koordinace.", + "wallet_key_created": "Klíč k Úložišti byl vytvořen. Udělejte si chvíli a bezpečně zálohujte svůj mnemotechnický seed.", + "are_you_sure_seed_will_be_lost": "Jste si jisti? Váš mnemotechnický seed bude ztracen, pokud nemáte zálohu.", + "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", - "export_signed_psbt": "Exportovat podepsanou PSBT", - "input_fp": "Zadejte otisk prstu", + "this_cosigner_is_already_imported": "Tento spolupodepisující je již importován.", + "export_signed_psbt": "Exportovat podepsanou PSBT (částečně podepsanou bitcoinovou transakci)", + "input_fp": "Zadejte otisk", "input_fp_explain": "Přeskočit a použít výchozí (00000000)", "input_path": "Vložit derivační cestu", "input_path_explain": "Přeskočit a použít výchozí ({default})", - "ms_help": "Pomoc", - "ms_help_title": "Jak fungují Vícepodpisová uložiště. Tipy a triky", - "ms_help_text": "Peněženka s více klíči, k exponenciálnímu zvýšení bezpečnosti nebo ke sdílené úschově.", - "ms_help_title1": "Doporučujeme použít více zařízení", - "ms_help_1": "Uložiště bude fungovat s dalšími aplikacemi BlueWallet a peněženkami kompatibilními s PSBT, jako jsou Electrum, Specter, Coldcard, Cobo vault atd.", + "ms_help": "Nápověda", + "ms_help_title": "Jak fungují vícepodpisová Úložiště: Tipy a triky", + "ms_help_text": "Peněženka s více klíči, k exponenciálnímu zvýšení bezpečnosti nebo ke sdílené úschově", + "ms_help_title1": "Doporučujeme použít více zařízení.", + "ms_help_1": "Úložiště bude fungovat s dalšími aplikacemi BlueWallet a peněženkami kompatibilními s PSBT (částečně podepsanými bitcoinovými transakcemi), jako jsou Electrum, Specter, Coldcard, Cobo Vault atd.", "ms_help_title2": "Editace klíčů", - "ms_help_2": "V tomto zařízení můžete vytvořit všechny klíče uložiště a později je odebrat nebo upravit. Pokud máte všechny klíče ve stejném zařízení, je jejich zabezpečení stejné jako u běžné Bitcoin peněženky.", - "ms_help_title3": "Zálohy úložiště", - "ms_help_3": "V tomto zařízení můžete vytvořit všechny klíče uložiště a později je odebrat nebo upravit. Pokud máte všechny klíče ve stejném zařízení, je jejich zabezpečení stejné jako u běžné Bitcoin peněženky.", - "ms_help_title4": "Import uložiště", - "ms_help_4": "Chcete-li importovat multisig, použijte záložní soubor multisig a použijte funkci importu. Pokud máte pouze rozšířené klíče a seedy, můžete použít jednotlivá pole importu v procesu Přidat uložiště.", - "ms_help_title5": "Pokročilé možnosti", - "ms_help_5": "Ve výchozím nastavení vygeneruje BlueWallet uložiště 2 ze 3. Chcete-li vytvořit jiné kvorum nebo změnit typ adresy, aktivujte pokročilé možnosti v Nastavení." + "ms_help_2": "V tomto zařízení můžete vytvořit všechny klíče Úložiště a později je odebrat nebo upravit. Pokud máte všechny klíče ve stejném zařízení, je jejich zabezpečení stejné jako u běžné softwarové bitcoinové peněženky.", + "ms_help_title3": "Zálohy Úložiště", + "ms_help_3": "V možnostech peněženky najdete zálohu Úložiště a zálohu peněženek pouze pro sledování. Tato záloha je jako mapa do vaší peněženky. Je nezbytná pro obnovení peněženky v případě, že ztratíte jeden z vašich seedů.", + "ms_help_title4": "Importování Úložiště", + "ms_help_4": "Chcete-li importovat vícepodpisovou peněženku, použijte záložní soubor a funkci importu. Pokud máte pouze seedy a XPUBs (veřejné klíče), můžete použít jednotlivá pole importu v procesu přidání Úložiště.", + "ms_help_title5": "Pokročilý režim", + "ms_help_5": "Ve výchozím nastavení BlueWallet vygeneruje Úložiště 2 ze 3. Chcete-li vytvořit jiné kvórum, nebo změnit typ adres, aktivujte Pokročilý režim v nastavení aplikace." }, "is_it_my_address": { "title": "Je to moje adresa?", - "owns": "{label} vlastní {address}", - "enter_address": "Zadat adresu", + "owns": "{label} vlastní adresu {address}", + "enter_address": "Zadejte adresu", "check_address": "Zkontrolovat adresu", "no_wallet_owns_address": "Žádná z dostupných peněženek nevlastní uvedenou adresu.", "view_qrcode": "Zobrazit QR kód" }, + "autofill_word": { + "title": "Poslední slovo seedu", + "enter": "Zadejte svoji částečnou mnemotechnickou frázi", + "generate_word": "Vygenerovat finální slovo", + "error": "Zadaný vstup není 11 či 23slovná částečná mnemotechnická fráze. Zkuste to prosím znovu." + }, "cc": { "change": "Změnit", "coins_selected": "Vybrané mince ({number})", "selected_summ": "{value} vybráno", - "empty": "Na této peněžence nejsou v tuto chvíli žádné mince.", + "empty": "Na této peněžence v tuto chvíli nejsou žádné mince.", "freeze": "Zmrazit", "freezeLabel": "Zmrazit", "freezeLabel_un": "Uvolnit", - "header": "Kontrola mincí", + "header": "Řízení mincí", "use_coin": "Použít minci", "use_coins": "Použít mince", - "tip": "Umožňuje zobrazit, označit, zmrazit nebo vybrat mince pro lepší správu peněženky. Klepnutím na barevné kruhy můžete vybrat více mincí." + "tip": "Tato funkce umožňuje zobrazit, označit, zmrazit nebo vybrat mince pro lepší správu peněženky. Klepnutím na barevné kruhy můžete vybrat více mincí.", + "sort_asc": "Vzestupně", + "sort_desc": "Sestupně", + "sort_height": "Výška", + "sort_value": "Hodnota", + "sort_label": "Popisek", + "sort_status": "Stav", + "sort_by": "Seřadit podle" }, "units": { "BTC": "BTC", - "MAX": "Max", + "MAX": "Max.", "sat_vbyte": "sat/vByte", "sats": "sats" }, "addresses": { + "copy_private_key": "Zkopírovat soukromý klíč", + "sensitive_private_key": "Varování: soukromé klíče jsou extrémně důvěrné. Pokračovat?", "sign_title": "Podepsat/Ověřit zprávu", - "sign_help": "Zde můžete vytvořit nebo ověřit kryptografický podpis založený na Bitcoinové adrese.", + "sign_help": "Zde můžete vytvořit nebo ověřit kryptografický podpis založený na bitcoinové adrese.", "sign_sign": "Podepsat", "sign_verify": "Ověřit", - "sign_signature_correct": "Úspěšné ověření!", - "sign_signature_incorrect": "Nezdařené ověření!", + "sign_signature_correct": "Ověření bylo úspěšné!", + "sign_signature_incorrect": "Ověření bylo neúspěšné!", "sign_placeholder_address": "Adresa", "sign_placeholder_message": "Zpráva", "sign_placeholder_signature": "Podpis", "addresses_title": "Adresy", - "type_change": "Změnit", - "type_receive": "Příjmout", - "type_used": "Použité", + "type_change": "Drobné", + "type_receive": "Přijímací", + "type_used": "Použitá", "transactions": "Transakce" }, "lnurl_auth": { "register_question_part_1": "Chcete zaregistrovat účet na", "register_question_part_2": "použitím vaší Lightning peněženky?", - "register_answer": "Účet byl úspěšně registrován na {hostname}!", + "register_answer": "Účet byl úspěšně zaregistrován na {hostname}!", "login_question_part_1": "Chcete se přihlásit na", "login_question_part_2": "použitím vaší Lightning peněženky?", - "login_answer": "Úspěšné přihlášení na {hostname}!", + "login_answer": "Úspěšně jste se přihlásili na {hostname}!", "link_question_part_1": "Chcete propojit váš účet na", - "link_question_part_2": "do vaší Lightning peněženky?", + "link_question_part_2": "s vaší Lightning peněženkou?", "link_answer": "Vaše Lightning peněženka byla úspěšně propojena s vaším účtem na {hostname}!", - "auth_question_part_1": "Chcete být ověřován/a na", + "auth_question_part_1": "Chcete být ověřován(a) na", "auth_question_part_2": "použitím vaší Lightning peněženky?", "auth_answer": "Ověření bylo úspěšné na {hostname}!", - "could_not_auth": "Nemohli jsme Vás ověřit na {hostname}.", + "could_not_auth": "Nemohli jsme vás ověřit na {hostname}.", "authenticate": "Ověřit" }, "bip47": { "payment_code": "Platební kód", - "payment_codes_list": "Seznam platebních kódů", - "who_can_pay_me": "Kdo mi může zaplatit:", - "purpose": "Opakovaně použitelný a sdílený kód (BIP47)", - "not_found": "Nebyl nalezen kód platby" + "contacts": "Kontakty", + "bip47_explain": "Znovu použitelný a sdílitelný kód", + "bip47_explain_subtitle": "BIP47", + "purpose": "Opakovaně použitelný kód, který je možné sdílet (BIP47)", + "pay_this_contact": "Zaplatit tomuto kontaktu", + "rename_contact": "Přejmenovat kontakt", + "copy_payment_code": "Zkopírovat platební kód", + "hide_contact": "Skrýt kontakt", + "rename": "Přejmenovat", + "provide_name": "Poskytněte nové jméno pro tento kontakt", + "add_contact": "Přidat kontakt", + "provide_payment_code": "Poskytnout platební kód", + "invalid_pc": "Neplatný platební kód", + "notification_tx_unconfirmed": "Oznamovací transakce ještě není potvrzena, počkejte prosím", + "failed_create_notif_tx": "Vytvoření on-chain transakce selhalo", + "onchain_tx_needed": "Je vyžadována on-chain transakce", + "notif_tx_sent": "Oznamovací transakce byla odeslána. Počkejte prosím, než bude potvrzena", + "notif_tx": "Oznamovací transakce", + "not_found": "Platební kód nebyl nalezen" } } diff --git a/loc/cy.json b/loc/cy.json index 510bd828d8e..606e5029226 100644 --- a/loc/cy.json +++ b/loc/cy.json @@ -3,24 +3,49 @@ "bad_password": "Cyfrinair anghywir. Tria eto. ", "cancel": "Canslo", "continue": "Parhau", + "clipboard": "Clipfwrdd", + "copied": "Wedi'i gopïo!", + "discard_changes": "Gwaredu newidiadau?", + "discard_changes_explain": "Mae gennyt newidiadau heb eu safio. Wyt ti'n siŵr dy fod eisiau eu gwaredu a gadael y sgrin?", "enter_password": "Cyfrinair", "never": "Byth", "of": "{number} o {total}", "ok": "Iawn", - "storage_is_encrypted": "Mae'r storfa wedi encryptio. Mae angen Cyfrinair i'w ddad-gryptio. ", + "enter_url": "Mewnosod URL", + "storage_is_encrypted": "Mae'r storfa wedi'i hamgryptio. Mae angen cyfrinair i'w dadgryptio.", "yes": "Ie", "no": "Na", - "save": "Safio", - "seed": "Hadyn", + "save": "Safio...", + "seed": "Seed", "success": "Llwyddiant", - "wallet_key": "Allwedd waled" + "wallet_key": "Allwedd waled", + "close": "Cau", + "change_input_currency": "Newid arian mewnbwn", + "refresh": "Adnewyddu", + "pick_image": "Dewis o'r llyfrgell", + "pick_file": "Dewis ffeil", + "enter_amount": "Mewnosod swm", + "qr_custom_input_button": "Tapio 10 gwaith i fewnosod mewnbwn arbennig", + "unlock": "Datgloi", + "port": "Porth", + "ssl_port": "Porth SSL", + "suggested": "Awgrymir" }, "azteco": { - "success": "Llwyddiant" + "codeIs": "Dy god taleb ydi", + "errorBeforeRefeem": "Cyn actifadu, rhaid yn gyntaf adio waled Bitcoin.", + "errorSomething": "Aeth rhywbeth o'i le. Ydi'r daleb hon dal yn ddilys?", + "redeem": "Actifadu i'r waled", + "redeemButton": "Actifadu", + "success": "Llwyddiant", + "successMessage": "Daleb wedi'i hactifadu'n llwyddiannus! Dylai dy gyllid gyrraedd dy waled Bitcoin yn fuan.", + "title": "Actifadu taleb Azte.co" }, "entropy": { "save": "Safio", - "undo": "Dad-wneud" + "title": "Entropi", + "undo": "Dad-wneud", + "amountOfEntropy": "{bits} o {limit} did" }, "errors": { "broadcast": "Methu darlledu", @@ -28,11 +53,13 @@ "network": "Camgymeriad rhwydwaith" }, "lnd": { - "errorInvoiceExpired": "Anfoneb wedi gorffen", - "expired": "Gorffen", + "errorInvoiceExpired": "Mae'r anfoneb wedi dod i ben.", + "expired": "Wedi dod i ben", + "expiresIn": "Yn dod i ben mewn {time} munud", "payButton": "Talu", - "placeholder": "Anfoneb", - "potentialFee": "Ffi Tebygol: {fee}", + "payment": "Taliad", + "placeholder": "Anfoneb neu gyfeiriad", + "potentialFee": "Ffi posib: {fee}", "refill": "Ail-lenwi", "refill_create": "Er mwyn parhau, ffurfia waled Bitcoin i'w ail-lenwi. ", "refill_external": "Ail-lenwi efo Waled Allanol", @@ -44,29 +71,51 @@ "additional_info": "Gwybodaeth Ychwanegol", "for": "Ar Gyfer:", "lightning_invoice": "Anfoneb Mellten", - "open_direct_channel": "Agor sianel uniongyrchol efo'r nodyn hwn:", + "please_pay_between_and": "Tala rhwng {min} a {max}", "please_pay": "Talu", - "wasnt_paid_and_expired": "Ni chafodd yr anfoneb ei thalu, ac mae wedi gorffen." + "preimage": "Cynddelw", + "date_time": "Dyddiad ac Amser", + "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_password": "Creu cyfrinair", + "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.", "password_should_not_match": "Mae'r cyfrinair yn cael ei ddefnyddio'n barod. Tria gyfrinair gwahanol.", - "passwords_do_not_match": "Cyfrineiriau ddim yn gweddu. Tria eto.", - "retype_password": "Ail-deipio'r cyfrinair", - "success": "Llwyddiant" + "title": "Gwadu credadwy" + }, + "pleasebackup": { + "ask": "Wyt ti wedi safio ymadrodd cofiadwy dy waled? Mae angen yr ymadrodd hwn i gael mynediad at dy gyllid os byddi'n colli'r ddyfais. Heb yr ymadrodd cofiadwy, bydd dy gyllid yn cael ei golli'n barhaol.", + "ask_no": "Na, dydw i ddim.", + "ask_yes": "Ydw, rwyf wedi.", + "ok": "Iawn, rwyf wedi ei ysgrifennu.", + "ok_lnd": "Iawn, rwyf wedi ei safio.", + "text": "Cymer foment i ysgrifennu'r ymadrodd cofiadwy yma ar ddarn o bapur.\nDyma dy gopi wrth gefn a gelli ei ddefnyddio i adfer y waled.", + "text_lnd": "Safia'r copi wrth gefn yma. Mae'n caniatáu iti adfer y waled os byddi'n ei cholli.", + "title": "Mae dy waled wedi cael ei chreu." }, "receive": { "details_create": "Creu", "details_label": "Disgrifiad", "details_setAmount": "Derbyn efo swm", - "details_share": "Rhannu", - "header": "Derbyn" + "details_share": "Rhannu...", + "address_not_found": "Methu cynhyrchu cyfeiriad derbyn.", + "header": "Derbyn", + "reset": "Ailosod", + "maxSats": "Swm uchafswm yw {max} sats", + "maxSatsFull": "Swm uchafswm yw {max} sats neu {currency}", + "minSats": "Swm lleiafswm yw {min} sats", + "minSatsFull": "Swm lleiafswm yw {min} sats neu {currency}", + "qrcode_for_the_address": "Cod QR ar gyfer y cyfeiriad", + "bip47_explanation": "Mae codau taliad yn gyfeiriad cyffredinol sy'n osgoi datgelu cyfeiriadau dy waled. Ni fydd pob gwasanaeth yn eu cefnogi." }, "send": { + "provided_address_is_invoice": "Mae'r cyfeiriad hwn yn edrych fel anfoneb Mellten. Dos i dy waled Mellten er mwyn talu'r anfoneb hon.", "broadcastButton": "Darlledu", "broadcastError": "Camgymeriad", + "broadcastNone": "Mewnosod hex y trafodyn", "broadcastPending": "Disgwyl", "broadcastSuccess": "Llwyddiant", "confirm_header": "Cadarnhau", @@ -76,151 +125,580 @@ "create_copy": "Copïo a darlledu'n hwyrach", "create_details": "Manylion", "create_fee": "Ffi", + "create_memo": "Nodyn", + "create_satoshi_per_vbyte": "Satoshi y vByte", + "create_this_is_hex": "Dyma hex dy drafodyn—wedi'i lofnodi ac yn barod i'w ddarlledu i'r rhwydwaith.", "create_to": "At", "create_tx_size": "Maint Trafodyn", + "create_verify": "Gwirio ar coinb.in", + "details_insert_contact": "Mewnosod Cyswllt", "details_add_rec_add": "Adio Derbynwr", "details_add_rec_rem": "Tynnu Derbynwr", + "details_add_recc_rem_all_alert_description": "Wyt ti'n siŵr dy fod eisiau tynnu pob derbynnydd?", + "details_add_rec_rem_all": "Tynnu Pob Derbynnydd", + "details_recipients_title": "Derbynwyr", + "details_recipient_title": "Derbynnydd #{number} o #{total}", + "please_complete_recipient_title": "Derbynnydd anghyflawn", + "please_complete_recipient_details": "Cwblha fanylion derbynnydd #{number} cyn adio un newydd.", "details_address": "Cyfeiriad", "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)", "details_amount_field_is_not_valid": "Dydi'r swm ddim yn ddilys.", + "details_amount_field_is_less_than_minimum_amount_sat": "Mae'r swm a nodwyd yn rhy fach. Nodi swm sy'n fwy na 500 sats.", "details_create": "Creu Anfoneb", + "details_error_decode": "Methu datgodio cyfeiriad Bitcoin", + "details_fee_field_is_not_valid": "Dydi'r ffi ddim yn ddilys.", + "details_frozen": "Mae {amount} BTC wedi'i rewi.", "details_next": "Nesaf", + "details_no_signed_tx": "Nid yw'r ffeil a ddewiswyd yn cynnwys trafodyn y gellir ei fewnforio.", "details_note_placeholder": "Hunan Nodyn", "details_scan": "Sganio", + "details_scan_hint": "Tapio dwbl i sganio neu fewnforio cyrchfan", + "details_scan_error": "Camgymeriad sganio", + "details_total_exceeds_balance": "Mae'r swm anfon yn fwy na'r balans sydd ar gael.", + "details_total_exceeds_balance_frozen": "Mae'r swm anfon yn fwy na'r balans sydd ar gael. Sylwer bod darnau arian wedi'u rhewi yn cael eu heithrio.", + "details_unrecognized_file_format": "Fformat ffeil heb ei gydnabod", "details_wallet_before_tx": "Cyn creu trafodyn, mae angen adio waled Bitcoin.", + "dynamic_init": "Cychwyn", "dynamic_next": "Nesaf", "dynamic_prev": "Blaenorol", "dynamic_start": "Dechrau", "dynamic_stop": "Stopio", + "fee_3h": "3a", + "fee_custom": "Arbennig", + "insert_custom_fee": "Mewnosod ffi", "fee_fast": "Cyflym", "fee_medium": "Canolig", + "fee_replace_minvb": "Dylai'r gyfradd ffi gyfan (satoshi y vByte) yr wyt eisiau ei thalu fod yn uwch na {min} sat/vByte.", + "fee_satvbyte": "mewn sat/vByte", "fee_slow": "Araf", "header": "Anfon", "input_clear": "Clirio", "input_done": "Wedi Cwblhau", "input_paste": "Pastio", "input_total": "Cyfanswm:", - "permission_camera_message": "Angen dy ganiatad i ddefnyddio dy gamera.", - "permission_camera_title": "Caniatad i ddefnyddio'r camera", + "permission_camera_message": "Angen dy ganiatâd i ddefnyddio dy gamera.", "psbt_sign": "Arwyddo trafodyn", + "invalid_psbt": "PSBT annilys.", "open_settings": "Agor Gosodiadau", - "success_done": "Wedi Cwblhau" + "permission_storage_denied_message": "Mae BlueWallet yn methu safio'r ffeil hon. Agor gosodiadau dy ddyfais a galluogi Caniatâd Storfa.", + "permission_storage_title": "Caniatâd Mynediad Storfa", + "psbt_clipboard": "Copïo i'r Clipfwrdd", + "psbt_this_is_psbt": "Dyma Drafodyn Bitcoin wedi'i Lofnodi'n Rhannol (PSBT). Cwblha'r llofnodi efo dy waled caledwedd.", + "psbt_tx_export": "Allforio i ffeil", + "no_tx_signing_in_progress": "Nid oes unrhyw lofnodi trafodyn ar y gweill.", + "outdated_rate": "Diweddarwyd y gyfradd ddiwethaf: {date}", + "psbt_tx_open": "Agor Trafodyn wedi'i Lofnodi", + "psbt_tx_scan": "Sganio Trafodyn wedi'i Lofnodi", + "qr_error_no_qrcode": "Methu canfod Cod QR dilys yn y ddelwedd a ddewiswyd. Sicrha bod y ddelwedd yn cynnwys dim ond Cod QR a dim cynnwys ychwanegol fel testun na botymau.", + "reset_amount": "Ailosod Swm", + "reset_amount_confirm": "A hoffet ailosod y swm?", + "success_done": "Wedi Cwblhau", + "txSaved": "Mae'r ffeil trafodyn ({filePath}) wedi'i safio.", + "file_saved_at_path": "Mae'r ffeil ({filePath}) wedi'i safio.", + "cant_send_to_silentpayment_adress": "Ni all y waled hon anfon at gyfeiriadau Silent Payments", + "cant_send_to_bip47": "Ni all y waled hon anfon at godau taliad BIP47", + "cant_find_bip47_notification": "Adia'r Cod Taliad hwn at gysylltiadau yn gyntaf", + "problem_with_psbt": "Problem efo'r PSBT", + "fee_10m": "10m", + "fee_1d": "1d" }, "settings": { + "about": "Amdano", + "about_awesome": "Wedi'i adeiladu gyda'r gwych", + "about_backup": "Cofia wneud copi wrth gefn o'th allweddi bob amser!", + "about_free": "Mae BlueWallet yn brosiect am ddim a chod-agored. Wedi'i grefftio gan ddefnyddwyr Bitcoin.", + "about_license": "Trwydded MIT", + "about_release_notes": "Nodiadau rhyddhau", + "about_review": "Gad adolygiad i ni", + "performance_score": "Sgôr perfformiad: {num}", + "run_performance_test": "Profi perfformiad", + "about_selftest": "Cynnal hunan-brawf", + "block_explorer_invalid_custom_url": "Mae'r URL a ddarparwyd yn annilys. Mewnosod URL dilys sy'n dechrau gyda http:// neu https://.", + "about_selftest_electrum_disabled": "Nid yw hunan-brofi ar gael gyda Modd All-lein Electrum. Analluoga'r modd all-lein a thria eto.", + "about_selftest_ok": "Mae pob prawf mewnol wedi pasio'n llwyddiannus. Mae'r waled yn gweithio'n iawn.", + "about_sm_github": "GitHub", + "about_sm_telegram": "Sianel Telegram", + "privacy_temporary_screenshots": "Caniatáu Cipio Sgrin", + "privacy_temporary_screenshots_instructions": "Bydd amddiffyniad cipio sgrin yn cael ei ddiffodd dros dro, gan alluogi sgrinluniau a recordiadau sgrin. Bydd yr amddiffyniad yn ailalluogi'n awtomatig pan fyddi'n cau ac ailagor BlueWallet.", + "biometrics": "Biometreg", + "biometrics_no_longer_available": "Mae gosodiadau dy ddyfais wedi newid ac nid ydynt bellach yn cyfateb i'r gosodiadau diogelwch a ddewiswyd yn yr ap. Ailalluoga biometreg neu god mynediad, yna ailddechrau'r ap i gymhwyso'r newidiadau.", + "biom_10times": "Rwyt wedi ceisio mewnosod dy gyfrinair 10 gwaith. A hoffet ailosod dy storfa? Bydd hyn yn gwaredu pob waled ac yn datgryptio dy storfa.", + "biom_conf_identity": "Cadarnha dy hunaniaeth.", + "biom_no_passcode": "Nid oes gan dy ddyfais god mynediad na biometreg wedi'u galluogi. Er mwyn parhau, ffurfweddi god mynediad neu fiometreg yn yr ap Gosodiadau.", + "biom_remove_decrypt": "Bydd pob un o'th waledi yn cael ei gwaredu a bydd dy storfa yn cael ei datgryptio. Wyt ti'n siŵr dy fod eisiau parhau?", "currency": "Arian", - "default_wallets": "Gweld Pob Waled", + "currency_source": "Cyfradd wedi'i chael o", + "currency_fetch_error": "Bu camgymeriad wrth gael y gyfradd ar gyfer yr arian a ddewiswyd.", + "default_title": "Wrth Gychwyn", + "donate": "Cyfrannu", + "donate_description": "Cynorthwya ni i gadw Blue yn rhad ac am ddim!", "electrum_connected": "Wedi Cysylltu", "electrum_connected_not": "Heb Gysylltu", - "electrum_clear_alert_cancel": "Canslo", - "electrum_clear_alert_ok": "Iawn", - "electrum_select": "Dewis", - "electrum_clear": "Clirio", + "electrum_error_connect": "Methu cysylltu â'r gweinydd Electrum a ddarparwyd", + "electrum_error_connect_tor": "Methu cysylltu â'r gweinydd Electrum a ddarparwyd. Sicrha bod yr ap Orbot wedi'i gysylltu a thria eto.", + "lndhub_uri": "E.e., {example}", + "electrum_host": "E.e., {example}", + "electrum_offline_mode": "Modd All-lein", + "electrum_offline_description": "Pan fydd wedi'i alluogi, ni fydd dy waledi Bitcoin yn ceisio nôl balansau na thrafodion.", + "electrum_port": "Porth, fel arfer {example}", + "use_ssl": "Defnyddio SSL", + "electrum_saved": "Mae dy newidiadau wedi cael eu safio'n llwyddiannus. Efallai bydd angen ailddechrau BlueWallet i'r newidiadau gael effaith.", + "set_electrum_server_as_default": "Gosod {server} fel y gweinydd Electrum diofyn?", + "set_lndhub_as_default": "Gosod {url} fel y gweinydd LNDhub diofyn?", + "electrum_settings_server": "Gweinydd Electrum", + "electrum_status": "Statws", + "electrum_preferred_server": "Gweinydd Dewisol", + "electrum_preferred_server_description": "Mewnosod y gweinydd yr wyt eisiau i'th waled ei ddefnyddio ar gyfer pob gweithgaredd Bitcoin. Unwaith ei osod, bydd dy waled yn defnyddio'r gweinydd hwn yn unig i wirio balansau, anfon trafodion a nôl data rhwydwaith. Sicrha dy fod yn ymddiried yn y gweinydd hwn cyn ei osod.", + "electrum_unable_to_connect": "Methu cysylltu â {server}.", + "electrum_history": "Hanes", + "electrum_reset_to_default": "Bydd hyn yn caniatáu i BlueWallet ddewis gweinydd ar hap o'r rhestr gweinyddion.", + "electrum_reset": "Ailosod i'r diofyn", + "electrum_reset_to_default_and_clear_history": "Ailosod i'r diofyn a chlirio'r hanes", "encrypt_decrypt": "Dadgryptio'r Storfa", + "encrypt_decrypt_q": "Wyt ti'n siŵr dy fod eisiau datgryptio dy storfa? Bydd hyn yn caniatáu mynediad i'th waledi heb gyfrinair.", + "encrypt_enc_and_pass": "Wedi'i Ddiogelu â Chyfrinair", + "encrypt_storage_explanation_headline": "Galluogi Amgryptio'r Storfa", + "encrypt_storage_explanation_description_line1": "Mae galluogi Amgryptio'r Storfa yn ychwanegu haen ychwanegol o ddiogelwch i'th ap drwy ddiogelu sut mae dy ddata yn cael ei storio ar dy ddyfais. Mae hyn yn ei gwneud yn anoddach i unrhyw un gael mynediad at dy wybodaeth heb ganiatâd.", + "encrypt_storage_explanation_description_line2": "Fodd bynnag, mae'n bwysig gwybod mai'r amgryptio hwn sy'n diogelu mynediad at dy waledi a storir yng nghadwyn allweddi dy ddyfais yn unig. Nid yw'n rhoi cyfrinair na diogelwch ychwanegol ar y waledi eu hunain.", + "i_understand": "Rwy'n deall", + "block_explorer": "Archwiliwr Blociau", + "block_explorer_preferred": "Defnyddio archwiliwr blociau dewisol", + "block_explorer_error_saving_custom": "Camgymeriad wrth safio'r archwiliwr blociau dewisol", "encrypt_title": "Diogelwch", "encrypt_tstorage": "Storfa", "encrypt_use": "Defnydd {type}", + "set_as_preferred": "Gosod fel dewisol", + "set_as_preferred_electrum": "Bydd gosod {host}:{port} fel gweinydd dewisol yn analluogi cysylltu â gweinydd awgrymedig ar hap.", + "encrypted_feature_disabled": "Ni ellir defnyddio'r nodwedd hon gydag amgryptio'r storfa wedi'i alluogi.", + "encrypt_use_expl": "Defnyddir {type} i gadarnhau dy hunaniaeth cyn gwneud trafodyn, datgloi, allforio neu ddileu waled.", + "biometrics_fail": "Os nad yw {type} wedi'i alluogi, neu'n methu â datgloi, gelli ddefnyddio cod mynediad dy ddyfais fel dewis arall.", "general": "Cyffredinol", + "general_continuity": "Parhad", + "general_continuity_e": "Pan fydd wedi'i alluogi, byddi'n gallu gweld waledi a thrafodion dethol gan ddefnyddio dy ddyfeisiau Apple iCloud eraill sydd wedi'u cysylltu.", + "groundcontrol_explanation": "Mae GroundControl yn weinydd hysbysiadau gwthio rhad ac am ddim, cod-agored ar gyfer waledi Bitcoin. Gelli osod dy weinydd GroundControl dy hun a rhoi ei URL yma er mwyn peidio â dibynnu ar seilwaith BlueWallet. Gad yn wag i ddefnyddio gweinydd diofyn GroundControl.", "header": "Gosodiadau", "language": "Iaith", + "last_updated": "Diweddarwyd ddiwethaf", "language_isRTL": "Mae angen ail ddechrau BlueWallet i'r newidiadau iaith gael effaith.", - "lightning_saved": "Mae dy newidiadau wedi cael eu safio'n llwyddianus.", + "license": "Trwydded", + "lightning_error_lndhub_uri": "URI LNDhub annilys", + "lightning_error_lndhub_uri_tor": "URI LNDhub annilys. Sicrha bod yr ap Orbot wedi'i gysylltu a thria eto.", + "lightning_saved": "Mae dy newidiadau wedi cael eu safio'n llwyddiannus.", "lightning_settings": "Gosodiadau Mellten", + "lightning_settings_explain": "I gysylltu â'th nod LND dy hun, gosoda LNDhub a rho ei URL yma yn y gosodiadau. Sylwer mai dim ond waledi a grëwyd ar ôl safio'r newidiadau fydd yn cysylltu â'r LNDhub penodol.", + "lndhub_github": "Storfa GitHub", "network": "Rhwydwaith", "network_broadcast": "Darlledu Trafodyn", - "notifications": "Hysbysebion", + "network_electrum": "Gweinydd Electrum", + "electrum_suggested_description": "Pan na fydd gweinydd dewisol wedi'i osod, dewisir gweinydd awgrymedig ar hap.", + "not_a_valid_uri": "URI annilys", + "notifications": "Hysbysiadau", + "open_link_in_explorer": "Agor dolen yn yr archwiliwr", "password": "Cyfrinair", - "password_explain": "Creu'r cyfrinair y byddi'n defnyddio i ddadgryptio'r storfa.", - "passwords_do_not_match": "Cyfrineiriau ddim yn cyfateb.", - "privacy": "Prefiatrwydd", - "retype_password": "Ail-deipio cyfrinair", + "password_explain": "Mewnosod y cyfrinair y byddi'n ei ddefnyddio i ddatgloi dy storfa.", + "plausible_deniability": "Gwadu credadwy", + "privacy": "Preifatrwydd", + "privacy_read_clipboard": "Darllen Clipfwrdd", + "privacy_system_settings": "Gosodiadau System", + "privacy_quickactions": "Llwybrau Byr Waled", + "privacy_quickactions_explanation": "Cyffwrdd a dal eicon yr ap BlueWallet i weld balans dy waled yn gyflym.", + "privacy_clipboard_explanation": "Darparu llwybrau byr os canfyddir cyfeiriad neu anfoneb yn dy glipfwrdd.", + "privacy_do_not_track": "Analluogi Dadansoddeg", + "privacy_do_not_track_explanation": "Ni fydd gwybodaeth perfformiad a dibynadwyedd yn cael ei chyflwyno i'w dadansoddi.", + "rate": "Cyfradd", + "push_notifications_explanation": "Drwy alluogi hysbysiadau, bydd tocyn dy ddyfais yn cael ei anfon at y gweinydd, ynghyd â chyfeiriadau waled a IDs trafodion ar gyfer pob waled a thrafodyn a wnaed ar ôl galluogi hysbysiadau. Defnyddir tocyn y ddyfais i anfon hysbysiadau, ac mae gwybodaeth y waled yn caniatáu i ni dy hysbysu am Bitcoin sy'n dod i mewn neu gadarnhad trafodion.\n\nDim ond gwybodaeth o ar ôl iti alluogi hysbysiadau a drosglwyddir—ni chesglir unrhyw beth o cyn hynny.\n\nBydd analluogi hysbysiadau yn gwaredu'r holl wybodaeth hon o'r gweinydd. Yn ogystal, bydd dileu waled o'r ap hefyd yn gwaredu ei gwybodaeth gysylltiedig o'r gweinydd.", + "selfTest": "Hunan-brawf", "save": "Safio", "saved": "Wedi Safio", - "total_balance": "Balans Llawn" + "success_transaction_broadcasted": "Mae dy drafodyn wedi'i ddarlledu'n llwyddiannus!", + "total_balance": "Balans Llawn", + "total_balance_explanation": "Dangos cyfanswm balans pob un o'th waledi ar dy declyn sgrin gartref.", + "widgets": "Teclynnau", + "tools": "Offer" + }, + "notifications": { + "would_you_like_to_receive_notifications": "A hoffet dderbyn hysbysiadau pan fydd taliadau yn dod i mewn?", + "notifications_subtitle": "Taliadau sy'n dod i mewn a chadarnhad trafodion", + "no_and_dont_ask": "Na, a phaid â gofyn eto.", + "permission_denied_message": "Rwyt wedi gwrthod caniatâd i anfon hysbysiadau atat. Os hoffet dderbyn hysbysiadau, galluoga nhw yng ngosodiadau dy ddyfais." }, "transactions": { + "cancel_explain": "Byddwn yn disodli'r trafodyn hwn gydag un sy'n dy dalu di ac sydd â ffioedd uwch. Mae hyn yn canslo'r trafodyn presennol yn effeithiol. Gelwir hyn yn RBF—Replace by Fee.", + "cancel_no": "Ni ellir disodli'r trafodyn hwn.", + "cancel_title": "Canslo'r trafodyn hwn (RBF)", + "transaction_loading_error": "Cafwyd problem wrth lwytho'r trafodyn. Tria eto'n hwyrach.", + "transaction_not_available": "Trafodyn ddim ar gael", + "confirmations_lowercase": "{confirmations} o gadarnhadau", + "expand_note": "Ehangu Nodyn", "cpfp_create": "Creu", + "cpfp_exp": "Byddwn yn creu trafodyn arall sy'n gwario dy drafodyn heb ei gadarnhau. Bydd y ffi gyfan yn uwch na ffi'r trafodyn gwreiddiol, felly dylai gael ei gloddio'n gyflymach. Gelwir hyn yn CPFP—Child Pays for Parent.", + "cpfp_no_bump": "Ni ellir codi ffi'r trafodyn hwn.", + "cpfp_title": "Codi'r Ffi (CPFP)", "details_balance_hide": "Cuddio Balans", "details_balance_show": "Dangos Balans", "details_copy": "Copïo", - "details_from": "Mewnbwn", + "details_copy_block_explorer_link": "Copïo Dolen yr Archwiliwr Blociau", + "details_copy_note": "Copïo Nodyn", + "details_copy_txid": "Copïo ID y Trafodyn", "details_inputs": "Mewnbynau", "details_outputs": "Allbynau", + "date": "Dyddiad", "details_received": "Derbynwyd", + "details_view_in_browser": "Gweld yn y Porwr", "details_title": "Trafodyn", + "incoming_transaction": "Trafodyn yn Dod i Mewn", + "outgoing_transaction": "Trafodyn yn Mynd Allan", + "expired_transaction": "Trafodyn Wedi Dod i Ben", + "pending_transaction": "Trafodyn yn Aros", + "offchain": "Oddi ar y gadwyn", + "onchain": "Ar y gadwyn", "details_to": "Allbwn", + "enable_offline_signing": "Nid yw'r waled hon yn cael ei defnyddio ar y cyd â llofnodi all-lein. A hoffet ei alluogi nawr?", + "list_conf": "Cad: {number}", "pending": "Disgwyl", - "list_title": "Trafodion" + "pending_with_amount": "Yn aros {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: Mewn ~10 munud", + "eta_3h": "ETA: Mewn ~3 awr", + "eta_1d": "ETA: Mewn ~1 diwrnod", + "list_title": "Trafodion", + "list_title_sent": "Anfonwyd", + "list_title_received": "Derbynwyd", + "transaction": "Trafodyn", + "open_url_error": "Methu agor y ddolen gyda'r porwr diofyn. Newida dy borwr diofyn a thria eto.", + "rbf_explain": "Byddwn yn disodli'r trafodyn hwn gydag un â ffi uwch fel y caiff ei gloddio'n gyflymach. Gelwir hyn yn RBF—Replace by Fee.", + "rbf_title": "Cyflymu (RBF)", + "status_bump": "Cyflymu", + "status_cancel": "Canslo", + "transactions_count": "Nifer y Trafodion", + "txid": "ID y Trafodyn", + "updating": "Diweddaru...", + "watchOnlyWarningTitle": "Rhybudd diogelwch", + "watchOnlyWarningDescription": "Bydd yn ofalus o dwyllwyr sy'n aml yn defnyddio waledi “gwylio'n unig” i dwyllo defnyddwyr. Nid yw'r waledi hyn yn caniatáu iti reoli na anfon cyllid; dim ond y balans y maent yn caniatáu iti ei weld.", + "custom_fee_warning_title": "Rhybudd", + "custom_fee_warning_description": "Mae ffioedd o dan 1 sat/vB yn ddilys, ond efallai na chânt eu hailddarlledu oherwydd polisïau nodau.", + "details_eta_analyzing": "Dadansoddi...", + "details_sent": "Anfonwyd", + "details_section": "Manylion", + "details_explorer": "archwiliwr", + "details_network_fee": "Ffi Rhwydwaith", + "details_to_address": "At", + "details_note": "Nodyn", + "details_add_note": "adio", + "details_advanced": "Arbenigol", + "details_fee_rate": "Cyfradd ffi", + "details_size": "Maint", + "details_virtual_size": "Maint rhithwir", + "details_tx_hex": "Hex y Trafodyn", + "details_inputs_count": "Mewnbynau ({count})", + "details_outputs_count": "Allbynau ({count})", + "details_id": "ID" }, "wallets": { - "add_bitcoin_explain": "Waled Bitcoin syml a cryf", + "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Waled Bitcoin syml a chryf", "add_create": "Creu", + "total_balance": "Balans Llawn", + "add_entropy_reset_title": "Ailosod Entropi", + "add_entropy_reset_message": "Bydd newid math y waled yn ailosod yr entropi cyfredol. Wyt ti eisiau parhau?", + "add_entropy": "Entropi", + "add_entropy_bytes": "{bytes} beit o entropi", + "add_entropy_generated": "{gen} beit o entropi a gynhyrchwyd", + "add_entropy_provide": "Darparu entropi drwy rolio dis", + "add_entropy_remain": "{gen} beit o entropi a gynhyrchwyd. Bydd y {rem} beit sy'n weddill yn cael eu cael o gynhyrchydd rhifau ar hap y System.", "add_import_wallet": "Mewnforio waled", "add_lightning": "Mellten", "add_lightning_explain": "Er mwyn gwario yn syth", + "add_lndhub": "Cysylltu â'th LNDhub", + "add_lndhub_error": "Mae cyfeiriad y nod a ddarparwyd yn nod LNDhub annilys.", + "add_lndhub_placeholder": "Cyfeiriad dy Nod", + "add_placeholder": "fy waled gyntaf", "add_title": "Adio waled", "add_wallet_name": "Enw", "add_wallet_type": "Math", + "add_wallet_seed_length": "Hyd y Seed", + "add_wallet_seed_length_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?", "clipboard_lightning": "Mae gen ti anfoneb Mellten ar dy glipfwrdd. Wyt ti isio ei ddefnyddio ar gyfer trafodyn?", + "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.", "details_delete": "Gwaredu", "details_delete_wallet": "Gwaredu Waled", - "details_no_cancel": "Na, canslo", - "details_save": "Safio", + "details_derivation_path": "llwybr deilliant", + "details_display": "Dangos ar y Sgrin Gartref", + "details_export_backup": "Allforio/Copi wrth gefn", + "details_export_history": "Allforio Hanes i CSV", + "details_master_fingerprint": "Ôl Bys y Brif Allwedd", + "details_multisig_type": "aml-lofnod", + "details_show_xpub": "Dangos xpub y Waled", "details_show_addresses": "Dangos cyfeiriadau", + "details_title": "Waled", + "wallets": "Waledi", + "swipe_balance_hide": "Cuddio", + "swipe_balance_show": "Dangos", + "drag_to_reorder": "Llusgo i aildrefnu", + "clear_search": "Clirio chwiliad", "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", + "import_passphrase_title": "Geiriau pas", + "import_passphrase_message": "Mewnosod geiriau pas os defnyddiaist rai", + "import_error": "Methu mewnforio. Sicrha bod y data a ddarparwyd yn ddilys.", + "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", + "learn_more": "Dysgu mwy", + "import_discovery_title": "Darganfod", + "import_discovery_subtitle": "Dewis waled a ddarganfuwyd", + "import_discovery_derivation": "Defnyddio llwybr deilliant arbennig", + "import_discovery_no_wallets": "Ni chanfuwyd unrhyw waledi.", + "import_discovery_offline": "Mae BlueWallet ar hyn o bryd yn y modd all-lein. Yn y modd hwn, ni all wirio bodolaeth y waled, felly bydd angen iti ddewis yr un cywir â llaw", + "import_derivation_found": "Wedi'i ganfod", + "import_derivation_found_not": "Heb ei ganfod", + "import_derivation_loading": "Llwytho...", + "import_derivation_subtitle": "Mewnosod llwybr deilliant arbennig, a byddwn yn ceisio darganfod dy waled.", + "import_derivation_title": "Llwybr deilliant", + "import_derivation_unknown": "Anhysbys", + "import_wrong_path": "Llwybr deilliant anghywir", "list_create_a_button": "Adio rwan", "list_create_a_wallet": "Adio waled", + "list_create_a_wallet_text": "Mae'n rhad ac am ddim, a gelli greu \ncymaint ag y dymuni.", "list_empty_txs1": "Bydd trafodion yn dangos yma.", + "list_empty_txs1_lightning": "Dylid defnyddio waled Mellten ar gyfer dy drafodion dyddiol. Mae'r ffioedd yn annheg o rad a'r cyflymder yn ofnadwy o sydyn.", "list_empty_txs2": "Dechrau efo dy waled", + "list_empty_txs2_lightning": "\nI ddechrau ei defnyddio, tapio ar Reoli Cyllid ac ail-lenwi dy falans.", "list_latest_transaction": "Trafodyn Diweddaraf", "list_long_choose": "Dewis Llun", - "list_long_clipboard": "Copio o'r Clipfwrdd", + "paste_from_clipboard": "Pastio", + "import_file": "Mewnforio ffeil", + "list_long_scan": "Sganio Cod QR", "list_title": "Waledi", "list_tryagain": "Trio eto", + "no_ln_wallet_error": "Cyn talu anfoneb Mellten, rhaid yn gyntaf adio waled Mellten.", + "looks_like_bip38": "Mae hwn yn edrych fel allwedd breifat wedi'i diogelu â chyfrinair (BIP38).", + "manage_title": "Rheoli Waledi", + "no_results_found": "Ni chanfuwyd unrhyw ganlyniadau.", + "please_continue_scanning": "Parhau i sganio.", + "select_no_bitcoin": "Nid oes unrhyw waledi Bitcoin ar gael ar hyn o bryd.", + "select_no_bitcoin_exp": "Mae angen waled Bitcoin i ail-lenwi waledi Mellten. Creu neu fewnforio un.", "select_wallet": "Dewis waled", - "xpub_copiedToClipboard": "Wedi gopio i'r clipfwrdd.", "pull_to_refresh": "Tynnu i Adnewyddu", - "warning_do_not_disclose": "Rhybudd! Paid ei ddatgelu." + "warning_do_not_disclose": "Paid â rhannu'r wybodaeth isod byth", + "scan_import": "Sganio'r cod QR hwn i fewnforio dy waled mewn ap arall.", + "write_down_header": "Creu copi wrth gefn â llaw", + "write_down": "Ysgrifenna a storia'r geiriau hyn yn ddiogel. Defnyddia hwy i adfer dy waled yn hwyrach.", + "wallet_type_this": "{type} yw math y waled hon.", + "share_number": "Rhannu {number}", + "copy_ln_url": "Copïa a storia'r URL hwn yn ddiogel i adfer dy waled yn hwyrach.", + "copy_ln_public": "Copïa a storia'r wybodaeth hon yn ddiogel i adfer dy waled yn hwyrach.", + "add_ln_wallet_first": "Rhaid yn gyntaf adio waled Mellten.", + "identity_pubkey": "Allwedd Gyhoeddus Hunaniaeth", + "xpub_title": "xpub y Waled", + "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" + }, + "total_balance_view": { + "display_in_bitcoin": "Dangos mewn Bitcoin", + "hide": "Cuddio", + "display_in_sats": "Dangos mewn sats", + "display_in_fiat": "Dangos mewn {currency}", + "title": "Balans Llawn", + "explanation": "Gweld cyfanswm balans pob un o'th waledi yn y sgrin trosolwg." }, "multisig": { + "multisig_vault": "Claddgell Aml-lofnod", + "default_label": "Claddgell Aml-lofnod", + "multisig_vault_explain": "Y diogelwch gorau ar gyfer symiau mawr", + "provide_signature": "Darparu llofnod", + "provide_signature_details": "Defnyddia dy ddyfais a waled lle mae'r allwedd yn byw i lofnodi'r trafodyn hwn", + "provide_signature_details_bluewallet": "Yn BlueWallet, dos i ddewislen y sgrin Anfon a dewisa ", + "provide_signature_next_steps": "Sganio neu Fewnforio Trafodyn wedi'i Lofnodi", + "provide_signature_next_steps_details": "Unwaith y bydd dy waled wedi llofnodi'r trafodyn yn llwyddiannus, sgania'r cod QR a ddarparwyd neu fewnforia'r ffeil gysylltiedig, ac yna adolyga holl fanylion y trafodyn cyn ei ddarlledu.", + "vault_key": "Allwedd Claddgell {number}", + "required_keys_out_of_total": "Allweddi gofynnol o'r cyfanswm", + "fee": "Ffi: {number}", + "fee_btc": "{number} BTC", "confirm": "Cadarnhau", "header": "Anfon", - "share": "Rhannu", + "share": "Rhannu...", "view": "Golwg", + "shared_key_detected": "Cyd-lofnodwr wedi'i rannu", + "shared_key_detected_question": "Rhannwyd cyd-lofnodwr efo ti, wyt ti eisiau ei fewnforio?", + "manage_keys": "Rheoli Allweddi", + "how_many_signatures_can_bluewallet_make": "faint o lofnodion all BlueWallet eu gwneud", + "signatures_required_to_spend": "Llofnodion gofynnol {number}", + "signatures_we_can_make": "gall wneud {number}", + "scan_or_import_file": "Sganio neu fewnforio ffeil", + "export_coordination_setup": "Allforio Gosodiad Cydlynu", + "cosign_this_transaction": "Cyd-lofnodi'r trafodyn hwn?", + "lets_start": "Gad i ni ddechrau", "create": "Creu", + "native_segwit_title": "Arfer gorau", + "wrapped_segwit_title": "Cydnawsedd gorau", + "legacy_title": "Etifeddiaeth", "co_sign_transaction": "Arwyddo trafodyn", - "wallet_type": "Math o Waled" + "what_is_vault": "Mae Claddgell yn", + "what_is_vault_numberOfWallets": " waled aml-lofnod {m}-o-{n} ", + "what_is_vault_wallet": ".", + "vault_advanced_customize": "Gosodiadau Claddgell", + "needs": "Mae angen", + "what_is_vault_description_number_of_vault_keys": " {m} allwedd claddgell ", + "what_is_vault_description_to_spend": "i wario a thrydydd y gelli ei \nddefnyddio fel copi wrth gefn.", + "what_is_vault_description_to_spend_other": "i wario.", + "quorum": "cworwm {m} o {n}", + "quorum_header": "Cworwm", + "of": "o", + "wallet_type": "Math o Waled", + "invalid_mnemonics": "Nid yw'r ymadrodd cofiadwy hwn yn ymddangos yn ddilys.", + "invalid_cosigner": "Data cyd-lofnodwr annilys", + "not_a_multisignature_xpub": "Nid xpub o waled aml-lofnod yw hwn!", + "invalid_cosigner_format": "Cyd-lofnodwr anghywir: Nid yw hwn yn gyd-lofnodwr ar gyfer fformat {format}.", + "create_new_key": "Creu Newydd", + "scan_or_open_file": "Sganio neu agor ffeil", + "i_have_mnemonics": "Mae gen i hadyn ar gyfer yr allwedd hon.", + "type_your_mnemonics": "Mewnosod hadyn i fewnforio dy allwedd Claddgell bresennol.", + "this_is_cosigners_xpub": "Dyma xpub y cyd-lofnodwr—yn barod i'w fewnforio i waled arall. Mae'n ddiogel ei rannu.", + "this_is_cosigners_xpub_airdrop": "Os byddi'n rhannu drwy AirDrop, rhaid i'r derbynwyr fod ar y sgrin gydlynu.", + "wallet_key_created": "Crëwyd dy allwedd Claddgell. Cymer foment i wneud copi wrth gefn diogel o'th hadyn cofiadwy.", + "are_you_sure_seed_will_be_lost": "Wyt ti'n siŵr? Bydd dy hadyn cofiadwy yn cael ei golli os nad oes gennyt gopi wrth gefn.", + "forget_this_seed": "Anghofio'r hadyn hwn a defnyddio'r xpub yn ei le.", + "view_edit_cosigners": "Gweld/Golygu Cyd-lofnodwyr", + "this_cosigner_is_already_imported": "Mae'r cyd-lofnodwr hwn eisoes wedi'i fewnforio.", + "export_signed_psbt": "Allforio PSBT wedi'i Lofnodi", + "input_fp": "Mewnosod ôl bys", + "input_fp_explain": "Hepgor i ddefnyddio'r un diofyn (00000000)", + "input_path": "Mewnosod Llwybr Deilliant", + "input_path_explain": "Hepgor i ddefnyddio'r un diofyn ({default})", + "ms_help": "Cymorth", + "ms_help_title": "Sut Mae Claddgelloedd Aml-lofnod yn Gweithio: Awgrymiadau a Thriciau", + "ms_help_text": "Waled gydag allweddi lluosog, ar gyfer diogelwch uwch neu warchodaeth rhanedig", + "ms_help_title1": "Argymhellir dyfeisiau lluosog.", + "ms_help_1": "Bydd y Claddgell yn gweithio gydag apiau BlueWallet eraill a waledi cydnaws PSBT, megis Electrum, Specter, Coldcard, Cobo Vault, ac ati.", + "ms_help_title2": "Golygu Allweddi", + "ms_help_2": "Gelli greu'r holl allweddi Claddgell yn y ddyfais hon a'u gwaredu neu eu golygu yn hwyrach. Mae cael yr holl allweddi ar yr un ddyfais yn cyfateb i ddiogelwch waled Bitcoin arferol.", + "ms_help_title3": "Copïau Wrth Gefn o'r Claddgell", + "ms_help_3": "Ar opsiynau'r waled, fe ddoi o hyd i'th gopi wrth gefn o'r Claddgell a'r copi wrth gefn gwylio'n unig. Mae'r copi wrth gefn hwn fel map i'th waled. Mae'n hanfodol ar gyfer adfer y waled os byddi'n colli un o'th hadau.", + "ms_help_title4": "Mewnforio Claddgelloedd", + "ms_help_4": "I fewnforio aml-lofnod, defnyddia'r ffeil copi wrth gefn a'r nodwedd Mewnforio. Os mai dim ond hadau a xpubs sydd gennyt, gelli ddefnyddio'r botwm Mewnforio unigol wrth greu allweddi Claddgell.", + "ms_help_title5": "Modd Arbenigol", + "ms_help_5": "Yn ddiofyn, bydd BlueWallet yn cynhyrchu Claddgell 2-o-3. I greu cworwm gwahanol neu newid math y cyfeiriad, actifada'r Modd Arbenigol yn y Gosodiadau." + }, + "is_it_my_address": { + "title": "Ai fy nghyfeiriad i ydyw?", + "owns": "Mae {label} yn berchen ar {address}", + "enter_address": "Mewnosod cyfeiriad", + "check_address": "Gwirio cyfeiriad", + "no_wallet_owns_address": "Nid yw'r un o'r waledi sydd ar gael yn berchen ar y cyfeiriad a ddarparwyd.", + "view_qrcode": "Gweld Cod QR" + }, + "autofill_word": { + "title": "Gair olaf yr hadyn", + "enter": "Mewnosod dy ymadrodd cofiadwy rhannol", + "generate_word": "Cynhyrchu'r gair olaf", + "error": "Nid yw'r mewnbwn yn ymadrodd cofiadwy rhannol o 11 nac 23 gair. Tria eto." }, "cc": { - "change": "Newid" + "change": "Newid", + "coins_selected": "Darnau Arian wedi'u Dewis ({number})", + "selected_summ": "{value} wedi'i ddewis", + "empty": "Nid oes gan y waled hon unrhyw ddarnau arian ar hyn o bryd.", + "freeze": "Rhewi", + "freezeLabel": "Rhewi", + "freezeLabel_un": "Dadrewi", + "header": "Rheoli UTXO", + "use_coin": "Defnyddio Darn Arian", + "use_coins": "Defnyddio Darnau Arian", + "tip": "Mae'r nodwedd hon yn caniatáu iti weld, labelu, rhewi neu ddewis darnau arian ar gyfer rheoli waled gwell. Gelli ddewis darnau arian lluosog drwy dapio ar y cylchoedd lliw.", + "sort_asc": "Esgynnol", + "sort_desc": "Disgynnol", + "sort_height": "Uchder", + "sort_value": "Gwerth", + "sort_status": "Statws", + "sort_by": "Trefnu yn ôl", + "sort_label": "Label" }, "units": { - "MAX": "Mwyafswm" + "BTC": "BTC", + "MAX": "Mwyafswm", + "sat_vbyte": "sat/vByte", + "sats": "sats" }, "addresses": { + "copy_private_key": "Copïo allwedd breifat", + "sensitive_private_key": "Rhybudd: mae allweddi preifat yn hynod sensitif. Parhau?", + "sign_title": "Llofnodi/Gwirio Neges", + "sign_help": "Yma gelli greu neu wirio llofnod cryptograffig yn seiliedig ar gyfeiriad Bitcoin.", "sign_sign": "Arwyddo", "sign_verify": "Gwirio", + "sign_signature_correct": "Gwiriad llwyddiannus!", + "sign_signature_incorrect": "Gwiriad wedi methu!", "sign_placeholder_address": "Cyfeiriad", "sign_placeholder_message": "Neges", "sign_placeholder_signature": "Llofnod", "addresses_title": "Cyfeiriadau", "type_change": "Newid", "type_receive": "Derbyn", + "type_used": "Defnyddiwyd", "transactions": "Trafodion" + }, + "lnurl_auth": { + "register_question_part_1": "A hoffet gofrestru cyfrif yn", + "register_question_part_2": "gan ddefnyddio dy waled Mellten?", + "register_answer": "Rwyt wedi cofrestru cyfrif yn llwyddiannus yn {hostname}!", + "login_question_part_1": "A hoffet fewngofnodi yn", + "login_question_part_2": "gan ddefnyddio dy waled Mellten?", + "login_answer": "Rwyt wedi mewngofnodi yn llwyddiannus yn {hostname}!", + "link_question_part_1": "A hoffet gysylltu dy gyfrif yn", + "link_question_part_2": "i'th waled Mellten?", + "link_answer": "Mae dy waled Mellten wedi'i chysylltu'n llwyddiannus â'th gyfrif yn {hostname}!", + "auth_question_part_1": "A hoffet gael dy ddilysu yn", + "auth_question_part_2": "gan ddefnyddio dy waled Mellten?", + "auth_answer": "Rwyt wedi cael dy ddilysu'n llwyddiannus yn {hostname}!", + "could_not_auth": "Ni allem dy ddilysu i {hostname}.", + "authenticate": "Dilysu" + }, + "bip47": { + "payment_code": "Cod Taliad", + "contacts": "Cysylltiadau", + "bip47_explain": "Cod ailddefnyddiadwy a rhanadwy", + "bip47_explain_subtitle": "BIP47", + "purpose": "Cod ailddefnyddiadwy a rhanadwy (BIP47)", + "pay_this_contact": "Talu'r cyswllt hwn", + "rename_contact": "Ailenwi cyswllt", + "copy_payment_code": "Copïo Cod Taliad", + "hide_contact": "Cuddio cyswllt", + "rename": "Ailenwi", + "provide_name": "Darparu enw newydd i'r cyswllt hwn", + "add_contact": "Adio Cyswllt", + "provide_payment_code": "Darparu Cod Taliad", + "invalid_pc": "Cod Taliad annilys", + "notification_tx_unconfirmed": "Nid yw'r trafodyn hysbysu wedi'i gadarnhau eto, aros", + "failed_create_notif_tx": "Methu creu trafodyn ar y gadwyn", + "onchain_tx_needed": "Mae angen trafodyn ar y gadwyn", + "notif_tx_sent": "Anfonwyd y trafodyn hysbysu. Aros iddo gadarnhau", + "notif_tx": "Trafodyn hysbysu", + "not_found": "Cod taliad heb ei ganfod" } } diff --git a/loc/da_dk.json b/loc/da_dk.json index 997badb7227..c833fb705e0 100644 --- a/loc/da_dk.json +++ b/loc/da_dk.json @@ -6,38 +6,110 @@ "never": "aldrig", "ok": "OK", "storage_is_encrypted": "Lageret er krypteret. Indtast adgangskode for at dekryptere", - "save": "save", - "success": "Succes" + "success": "Succes", + "continue": "Fortsæt", + "clipboard": "Udklipsholder", + "copied": "Kopieret!", + "discard_changes": "Kassér ændringer?", + "discard_changes_explain": "Du har ugemte ændringer. Er du sikker på, at du vil kassere dem og forlade skærmen?", + "of": "{number} af {total}", + "enter_url": "Indtast URL", + "yes": "Ja", + "no": "Nej", + "save": "Gem...", + "seed": "Seed", + "wallet_key": "Wallet-nøgle", + "close": "Luk", + "change_input_currency": "Skift inputvaluta", + "refresh": "Opdater", + "pick_image": "Vælg fra bibliotek", + "pick_file": "Vælg fil", + "enter_amount": "Indtast beløb", + "qr_custom_input_button": "Tryk 10 gange for at indtaste brugerdefineret input", + "unlock": "Lås op", + "port": "Port", + "ssl_port": "SSL-port", + "suggested": "Foreslået" }, "azteco": { - "success": "Succes" + "success": "Succes", + "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 wallet", + "redeemButton": "Indløs", + "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": "Transmission mislykkedes.", + "error": "Fejl", + "network": "Netværksfejl" }, "lnd": { "refill": "Genopfyld", "refill_lnd_balance": "Genopfyld Lightning wallet", - "title": "Administration" + "title": "Administration", + "errorInvoiceExpired": "Faktura udløbet.", + "expired": "Udløbet", + "expiresIn": "Udløber om {time} minutter", + "payButton": "Betal", + "payment": "Betaling", + "placeholder": "Faktura eller adresse", + "potentialFee": "Potentielt gebyr: {fee}", + "refill_create": "For at fortsætte skal du oprette en Bitcoin-wallet at genopfylde med.", + "refill_external": "Genopfyld med ekstern wallet", + "sameWalletAsInvoiceError": "Du kan ikke betale en faktura med den samme wallet, som blev brugt til at oprette den." + }, + "lndViewInvoice": { + "additional_info": "Yderligere information", + "for": "Til:", + "lightning_invoice": "Lightning-faktura", + "please_pay_between_and": "Betal venligst mellem {min} og {max}", + "please_pay": "Betal venligst", + "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_password": "Opret adgangskode", + "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", - "passwords_do_not_match": "Adgangskoden er ikke den samme, prøv igen", - "retype_password": "Indtast adgangskoden igen", - "success": "Succes", "title": "Sandsynlig benægtelse" }, + "pleasebackup": { + "ask": "Har du gemt din wallets seed-frase? Denne seed-frase er nødvendig for at få adgang til dine midler, hvis du mister denne enhed. Uden seed-frasen vil dine midler være permanent tabt.", + "ask_no": "Nej, det har jeg ikke.", + "ask_yes": "Ja, det har jeg.", + "ok": "OK, jeg har skrevet det ned.", + "ok_lnd": "OK, jeg har gemt den.", + "text": "Tag dig venligst et øjeblik til at skrive denne mnemonic ned på et stykke papir.\nDet er din backup, og du kan bruge den til at gendanne wallet'en.", + "text_lnd": "Gem venligst denne wallet-backup. Den giver dig mulighed for at gendanne wallet'en i tilfælde af tab.", + "title": "Din wallet er blevet oprettet." + }, "receive": { "details_create": "Opret", "details_label": "Beskrivelse", "details_setAmount": "Modtag med beløb", - "details_share": "del", - "header": "Modtag" + "header": "Modtag", + "details_share": "Del...", + "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": "Payment Codes er en universel adresse, der undgår at afsløre dine wallet-adresser. Ikke alle tjenester understøtter dem." }, "send": { "broadcastButton": "Transmitter", @@ -52,78 +124,581 @@ "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" + "success_done": "Udført", + "provided_address_is_invoice": "Denne adresse ser ud til at være en Lightning-faktura. Gå venligst til din Lightning-wallet for at betale denne faktura.", + "broadcastError": "Fejl", + "broadcastNone": "Indsæt transaktions-hex", + "broadcastPending": "Afventende", + "create_copy": "Kopier og transmitter senere", + "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", + "details_add_rec_rem": "Fjern modtager", + "details_add_recc_rem_all_alert_description": "Er du sikker på, at du vil fjerne alle modtagere?", + "details_add_rec_rem_all": "Fjern alle modtagere", + "details_recipients_title": "Modtagere", + "details_recipient_title": "Modtager #{number} af #{total}", + "please_complete_recipient_title": "Ufuldstændig modtager", + "please_complete_recipient_details": "Udfyld venligst detaljerne for modtager #{number}, før du tilføjer en ny modtager.", + "details_adv_fee_bump": "Tillad gebyrforøgelse", + "details_adv_full": "Brug fuld saldo", + "details_adv_full_sure": "Er du sikker på, at du vil bruge din wallets fulde saldo til denne transaktion?", + "details_adv_full_sure_frozen": "Er du sikker på, at du vil bruge din wallets fulde saldo til denne transaktion? Bemærk, at frosne coins er udelukket.", + "details_adv_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.", + "details_error_decode": "Kunne ikke afkode Bitcoin-adresse", + "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_hint": "Dobbelttryk for at scanne eller importere en destination", + "details_scan_error": "Scanningsfejl", + "details_total_exceeds_balance_frozen": "Sendebeløbet overstiger den tilgængelige saldo. Bemærk, at frosne coins er udelukket.", + "details_unrecognized_file_format": "Filformat ikke genkendt", + "details_wallet_before_tx": "Før du opretter en transaktion, skal du først tilføje en Bitcoin-wallet.", + "dynamic_init": "Initialiserer", + "dynamic_next": "Næste", + "dynamic_prev": "Forrige", + "dynamic_start": "Start", + "dynamic_stop": "Stop", + "fee_10m": "10m", + "fee_1d": "1d", + "fee_3h": "3t", + "fee_custom": "Brugerdefineret", + "insert_custom_fee": "Indsæt gebyr", + "fee_fast": "Hurtig", + "fee_medium": "Mellem", + "fee_replace_minvb": "Den samlede gebyrsats (satoshi per vByte), du vil betale, skal være højere end {min} sat/vByte.", + "fee_satvbyte": "i sat/vByte", + "fee_slow": "Langsom", + "header": "Send", + "input_clear": "Ryd", + "input_paste": "Indsæt", + "input_total": "I alt:", + "permission_camera_message": "Vi har brug for din tilladelse til at bruge dit kamera.", + "psbt_sign": "Signer en transaktion", + "invalid_psbt": "Ugyldig PSBT angivet.", + "open_settings": "Åbn indstillinger", + "permission_storage_denied_message": "BlueWallet kan ikke gemme denne fil. Åbn venligst dine enhedsindstillinger og aktivér lagertilladelse.", + "permission_storage_title": "Tilladelse til lageradgang", + "psbt_clipboard": "Kopier til udklipsholder", + "psbt_this_is_psbt": "Dette er en Partially Signed Bitcoin Transaction (PSBT). Færdiggør venligst signeringen med din hardware wallet.", + "psbt_tx_export": "Eksportér til fil", + "no_tx_signing_in_progress": "Der er ingen transaktionssignering i gang.", + "outdated_rate": "Kurs blev sidst opdateret: {date}", + "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 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", - "electrum_clear_alert_cancel": "Annuller", - "general_adv_mode": "Enable advanced mode", - "header": "indstillinger", + "header": "Indstillinger", "language": "Sprog", - "lightning_settings": "Lightning settings", + "lightning_settings": "Lightning-indstillinger", "password": "Adgangskode", - "password_explain": "Indtast den adgangskode du vil bruge til at kryptere lageret", - "passwords_do_not_match": "Adgangskoden er ikke den samme", "plausible_deniability": "Sandsynlig benægtelse...", - "retype_password": "Gentag adgangskoden", - "save": "save" + "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", + "about_review": "Efterlad os en anmeldelse", + "performance_score": "Ydeevnescore: {num}", + "run_performance_test": "Test ydeevne", + "about_selftest": "Kør selvtest", + "block_explorer_invalid_custom_url": "Den angivne URL er ugyldig. Indtast venligst en gyldig URL, der starter med http:// eller https://.", + "about_selftest_electrum_disabled": "Selvtest er ikke tilgængelig med Electrum offline-tilstand. Deaktiver venligst offline-tilstand og prøv igen.", + "about_selftest_ok": "Alle interne tests er bestået. Wallet'en fungerer fint.", + "about_sm_github": "GitHub", + "about_sm_telegram": "Telegram-kanal", + "privacy_temporary_screenshots": "Tillad skærmoptagelse", + "privacy_temporary_screenshots_instructions": "Beskyttelse mod skærmoptagelse vil midlertidigt blive slået fra, hvilket tillader skærmbilleder og skærmoptagelser. Beskyttelsen genaktiveres automatisk, når du lukker og genåbner BlueWallet.", + "biometrics": "Biometri", + "biometrics_no_longer_available": "Dine enhedsindstillinger er ændret og matcher ikke længere de valgte sikkerhedsindstillinger i appen. Genaktivér venligst biometri eller kode, og genstart derefter appen for at anvende disse ændringer.", + "biom_10times": "Du har forsøgt at indtaste din adgangskode 10 gange. Vil du nulstille dit lager? Dette vil fjerne alle wallets og dekryptere dit lager.", + "biom_conf_identity": "Bekræft venligst din identitet.", + "biom_no_passcode": "Din enhed har ikke en kode eller biometri aktiveret. For at fortsætte skal du konfigurere en kode eller biometri i indstillingsappen.", + "biom_remove_decrypt": "Alle dine wallets vil blive fjernet, og dit lager vil blive dekrypteret. Er du sikker på, at du vil fortsætte?", + "currency_source": "Kursen hentes fra", + "currency_fetch_error": "Der opstod en fejl under hentning af kursen for den valgte valuta.", + "default_title": "Ved opstart", + "donate": "Doner", + "donate_description": "Hjælp os med at holde Blue gratis!", + "electrum_connected": "Forbundet", + "electrum_connected_not": "Ikke forbundet", + "electrum_error_connect": "Kan ikke oprette forbindelse til den angivne Electrum-server", + "electrum_error_connect_tor": "Kan ikke oprette forbindelse til den angivne Electrum-server. Sørg venligst for, at Orbot-appen er forbundet, og prøv igen.", + "lndhub_uri": "F.eks. {example}", + "electrum_host": "F.eks. {example}", + "electrum_offline_mode": "Offline-tilstand", + "electrum_offline_description": "Når aktiveret, vil dine Bitcoin-wallets ikke forsøge at hente saldi eller transaktioner.", + "electrum_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.", + "set_electrum_server_as_default": "Indstil {server} som standard Electrum-server?", + "set_lndhub_as_default": "Indstil {url} som standard LNDhub-server?", + "electrum_settings_server": "Electrum-server", + "electrum_status": "Status", + "electrum_preferred_server": "Foretrukken server", + "electrum_preferred_server_description": "Indtast den server, du vil have din wallet til at bruge til alle Bitcoin-aktiviteter. Når den er indstillet, vil din wallet udelukkende bruge denne server til at tjekke saldi, sende transaktioner og hente netværksdata. Sørg for, at du har tillid til denne server, før du indstiller den.", + "electrum_unable_to_connect": "Kan ikke oprette forbindelse til {server}.", + "electrum_history": "Historik", + "electrum_reset_to_default": "Dette vil lade BlueWallet vælge en server tilfældigt fra serverlisten.", + "electrum_reset": "Nulstil til standard", + "electrum_reset_to_default_and_clear_history": "Nulstil til standard og ryd historik", + "encrypt_decrypt": "Dekrypter lager", + "encrypt_decrypt_q": "Er du sikker på, at du vil dekryptere dit lager? Dette vil tillade adgang til dine wallets uden en adgangskode.", + "encrypt_enc_and_pass": "Adgangskodebeskyttet", + "encrypt_storage_explanation_headline": "Aktivér lagerkryptering", + "encrypt_storage_explanation_description_line1": "Aktivering af lagerkryptering tilføjer et ekstra lag af beskyttelse til din app ved at sikre den måde, dine data lagres på din enhed. Dette gør det sværere for nogen at få adgang til dine oplysninger uden tilladelse.", + "encrypt_storage_explanation_description_line2": "Det er dog vigtigt at vide, at denne kryptering kun beskytter adgangen til dine wallets, der er gemt i enhedens keychain. Den sætter ikke en adgangskode eller ekstra beskyttelse på selve wallets.", + "i_understand": "Jeg forstår", + "block_explorer": "Block Explorer", + "block_explorer_preferred": "Brug foretrukken block explorer", + "block_explorer_error_saving_custom": "Fejl ved lagring af foretrukken block explorer", + "encrypt_title": "Sikkerhed", + "encrypt_tstorage": "Lager", + "encrypt_use": "Brug {type}", + "set_as_preferred": "Indstil som foretrukken", + "set_as_preferred_electrum": "Indstilling af {host}:{port} som foretrukken server vil deaktivere tilfældig forbindelse til en foreslået server.", + "encrypted_feature_disabled": "Denne funktion kan ikke bruges med krypteret lager aktiveret.", + "encrypt_use_expl": "{type} vil blive brugt til at bekræfte din identitet, før du foretager en transaktion, låser op, eksporterer eller sletter en wallet.", + "biometrics_fail": "Hvis {type} ikke er aktiveret eller ikke kan låse op, kan du bruge din enheds kode som alternativ.", + "general": "Generelt", + "general_continuity": "Kontinuitet", + "general_continuity_e": "Når aktiveret, vil du kunne se udvalgte wallets og transaktioner ved hjælp af dine andre Apple iCloud-tilsluttede enheder.", + "groundcontrol_explanation": "GroundControl er en gratis, open source push notifikationsserver til Bitcoin-wallets. Du kan installere din egen GroundControl-server og indsætte dens URL her for ikke at være afhængig af BlueWallets infrastruktur. Lad være tom for at bruge GroundControls standardserver.", + "last_updated": "Sidst opdateret", + "language_isRTL": "Genstart af BlueWallet er nødvendig, for at sprogretningen træder i kraft.", + "license": "Licens", + "lightning_error_lndhub_uri": "Ugyldig LNDhub-URI", + "lightning_error_lndhub_uri_tor": "Ugyldig LNDhub-URI. Sørg venligst for, at Orbot-appen er forbundet, og prøv igen.", + "lightning_saved": "Dine ændringer er blevet gemt.", + "lightning_settings_explain": "For at oprette forbindelse til din egen LND-node skal du installere LNDhub og indsætte dens URL her i indstillingerne. Bemærk, at kun tegnebøger oprettet efter at have gemt ændringer vil oprette forbindelse til den angivne LNDhub.", + "lndhub_github": "GitHub-arkiv", + "network": "Netværk", + "network_broadcast": "Transmitter transaktion", + "network_electrum": "Electrum-server", + "electrum_suggested_description": "Når en foretrukken server ikke er indstillet, vil en foreslået server blive valgt til brug tilfældigt.", + "not_a_valid_uri": "Ugyldig URI", + "notifications": "Notifikationer", + "open_link_in_explorer": "Åbn link i explorer", + "password_explain": "Indtast den adgangskode, du vil bruge til at låse dit lager op.", + "privacy": "Privatliv", + "privacy_read_clipboard": "Læs udklipsholder", + "privacy_system_settings": "Systemindstillinger", + "privacy_quickactions": "Wallet-genveje", + "privacy_quickactions_explanation": "Tryk og hold på BlueWallet-appikonet for hurtigt at se din wallets saldo.", + "privacy_clipboard_explanation": "Tilbyd genveje, hvis en adresse eller faktura findes i din udklipsholder.", + "privacy_do_not_track": "Deaktiver analyse", + "privacy_do_not_track_explanation": "Oplysninger om ydeevne og pålidelighed vil ikke blive indsendt til analyse.", + "rate": "Kurs", + "push_notifications_explanation": "Ved at aktivere notifikationer vil dit enhedstoken blive sendt til serveren sammen med wallet-adresser og transaktions-id'er for alle wallets og transaktioner foretaget efter aktivering af notifikationer. Enhedstokenet bruges til at sende notifikationer, og wallet-informationen tillader os at give dig besked om indgående Bitcoin eller transaktionsbekræftelser.\n\nKun oplysninger fra efter du aktiverer notifikationer overføres—intet fra før indsamles.\n\nDeaktivering af notifikationer vil fjerne alle disse oplysninger fra serveren. Derudover vil sletning af en wallet fra appen også fjerne dens tilknyttede oplysninger fra serveren.", + "selfTest": "Selvtest", + "saved": "Gemt", + "success_transaction_broadcasted": "Din transaktion er blevet transmitteret!", + "total_balance": "Samlet saldo", + "total_balance_explanation": "Vis den samlede saldo for alle dine wallets på din startskærms-widgets.", + "widgets": "Widgets", + "tools": "Værktøjer" + }, + "notifications": { + "would_you_like_to_receive_notifications": "Vil du modtage notifikationer, når du får indgående betalinger?", + "notifications_subtitle": "Indgående betalinger og transaktionsbekræftelser", + "no_and_dont_ask": "Nej, og spørg mig ikke igen.", + "permission_denied_message": "Du har afvist tilladelsen til at sende dig notifikationer. Hvis du gerne vil modtage notifikationer, skal du aktivere dem i dine enhedsindstillinger." }, "transactions": { "cpfp_create": "Opret", "details_copy": "Kopier", - "details_from": "Fra", - "details_show_in_block_explorer": "Vis i block-explorer", "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.", + "cancel_title": "Annuller denne transaktion (RBF)", + "transaction_loading_error": "Der opstod et problem ved indlæsning af transaktionen. Prøv venligst igen senere.", + "transaction_not_available": "Transaktion ikke tilgængelig", + "confirmations_lowercase": "{confirmations} bekræftelser", + "expand_note": "Udvid note", + "cpfp_exp": "Vi vil oprette en anden transaktion, der bruger din ubekræftede transaktion. Det samlede gebyr vil være højere end det oprindelige transaktionsgebyr, så den bør udvindes hurtigere. Dette kaldes CPFP—Child Pays for Parent.", + "cpfp_no_bump": "Denne transaktions gebyr kan ikke forøges.", + "cpfp_title": "Forøg gebyr (CPFP)", + "details_balance_hide": "Skjul saldo", + "details_balance_show": "Vis saldo", + "details_copy_block_explorer_link": "Kopier block explorer-link", + "details_copy_note": "Kopier note", + "details_copy_txid": "Kopier transaktions-ID", + "details_inputs": "Input", + "details_outputs": "Output", + "date": "Dato", + "details_received": "Modtaget", + "details_view_in_browser": "Vis i browser", + "incoming_transaction": "Indgående transaktion", + "outgoing_transaction": "Udgående transaktion", + "expired_transaction": "Udløbet transaktion", + "pending_transaction": "Afventende transaktion", + "offchain": "Offchain", + "onchain": "Onchain", + "enable_offline_signing": "Denne wallet bruges ikke sammen med offline-signering. Vil du aktivere det nu?", + "list_conf": "Bekr.: {number}", + "pending": "Afventende", + "pending_with_amount": "Afventende {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: Om ~10 minutter", + "eta_3h": "ETA: Om ~3 timer", + "eta_1d": "ETA: Om ~1 dag", + "list_title_sent": "Sendt", + "list_title_received": "Modtaget", + "open_url_error": "Kunne ikke åbne linket med standardbrowseren. Skift venligst din standardbrowser, og prøv igen.", + "rbf_explain": "Vi vil erstatte denne transaktion med en med et højere gebyr, så den vil blive udvundet hurtigere. Dette kaldes RBF—Replace by Fee.", + "rbf_title": "Fremskynd (RBF)", + "status_bump": "Fremskynd", + "status_cancel": "Annuller", + "transactions_count": "Antal transaktioner", + "txid": "Transaktions-ID", + "updating": "Opdaterer...", + "watchOnlyWarningTitle": "Sikkerhedsadvarsel", + "watchOnlyWarningDescription": "Vær forsigtig med svindlere, som ofte bruger watch-only wallets til at narre brugere. Disse wallets giver dig ikke mulighed for at kontrollere eller sende midler; de lader dig kun se saldoen.", + "custom_fee_warning_title": "Advarsel", + "custom_fee_warning_description": "Gebyrer under 1 sat/vB er gyldige, men videresendes muligvis ikke på grund af nodepolitikker.", + "details_eta_analyzing": "Analyserer...", + "details_sent": "Sendt", + "details_section": "Detaljer", + "details_explorer": "explorer", + "details_network_fee": "Netværksgebyr", + "details_to_address": "Til", + "details_id": "ID", + "details_note": "Note", + "details_add_note": "tilføj", + "details_advanced": "Avanceret", + "details_fee_rate": "Gebyrsats", + "details_size": "Størrelse", + "details_virtual_size": "Virtuel størrelse", + "details_tx_hex": "Tx hex", + "details_inputs_count": "Input ({count})", + "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_no_cancel": "Nej, annuller", - "details_save": "Gem", "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", - "reorder_title": "Ændre rækkefølgen af wallets", "select_wallet": "Vælg wallet", - "xpub_copiedToClipboard": "Kopieret til udklipsholder." + "add_bitcoin": "Bitcoin", + "total_balance": "Samlet saldo", + "add_entropy_reset_title": "Nulstil entropi", + "add_entropy_reset_message": "Ændring af wallet-typen vil nulstille den aktuelle entropi. Vil du fortsætte?", + "add_entropy": "Entropi", + "add_entropy_bytes": "{bytes} bytes entropi", + "add_entropy_generated": "{gen} bytes genereret entropi", + "add_entropy_provide": "Tilvejebring entropi via terningekast", + "add_entropy_remain": "{gen} bytes genereret entropi. Resterende {rem} bytes vil blive hentet fra systemets tilfældighedsgenerator.", + "add_lightning": "Lightning", + "add_lightning_explain": "Til forbrug med øjeblikkelige transaktioner", + "add_lndhub": "Forbind til din LNDhub", + "add_lndhub_error": "Den angivne nodeadresse er en ugyldig LNDhub-node.", + "add_lndhub_placeholder": "Din nodeadresse", + "add_placeholder": "min første wallet", + "add_wallet_seed_length": "Seed-længde", + "add_wallet_seed_length_12": "12 ord", + "add_wallet_seed_length_24": "24 ord", + "clipboard_bitcoin": "Du har en Bitcoin-adresse i din udklipsholder. Vil du bruge den til en transaktion?", + "clipboard_lightning": "Du har en Lightning-faktura i din udklipsholder. Vil du bruge den til en transaktion?", + "clear_clipboard_on_import": "Ryd udklipsholder ved import", + "details_advanced": "Avanceret", + "details_connected_to": "Forbundet til", + "details_del_wb_err": "Det angivne saldobeløb matcher ikke denne wallets saldo. Prøv venligst igen.", + "details_del_wb_q": "Denne wallet har en saldo. Før du fortsætter, skal du være opmærksom på, at du ikke vil være i stand til at genskabe midlerne uden denne wallets seed-frase. For at undgå utilsigtet fjernelse skal du indtaste din wallets saldo på {balance} satoshis.", + "details_delete_wallet": "Slet wallet", + "details_derivation_path": "afledningssti", + "details_display": "Vis på startskærm", + "details_export_history": "Eksportér historik til CSV", + "details_master_fingerprint": "Master fingerprint", + "details_multisig_type": "multisig", + "details_show_addresses": "Vis adresser", + "swipe_balance_hide": "Skjul", + "swipe_balance_show": "Vis", + "drag_to_reorder": "Træk for at omarrangere", + "clear_search": "Ryd søgning", + "details_use_with_hardware_wallet": "Brug med hardware wallet", + "enter_bip38_password": "Indtast adgangskode for at dekryptere", + "import_passphrase": "Adgangsfrase", + "import_passphrase_title": "Adgangsfrase", + "import_passphrase_message": "Indtast adgangsfrase, hvis du har brugt en", + "import_explanation": "Indtast venligst dine seed-ord, offentlige nøgle, WIF eller hvad du har. BlueWallet vil gøre sit bedste for at gætte det korrekte format og importere din wallet.", + "import_success_watchonly": "Din wallet er blevet importeret. ADVARSEL: Dette er en watch-only wallet, du kan IKKE sende fra den.", + "import_search_accounts": "Søg konti", + "learn_more": "Lær mere", + "import_discovery_title": "Opdagelse", + "import_discovery_subtitle": "Vælg en opdaget wallet", + "import_discovery_derivation": "Brug brugerdefineret afledningssti", + "import_discovery_no_wallets": "Ingen wallets blev fundet.", + "import_discovery_offline": "BlueWallet er i øjeblikket i offline-tilstand. I denne tilstand kan den ikke verificere eksistensen af wallet'en, så du skal vælge den korrekte manuelt", + "import_derivation_found": "Fundet", + "import_derivation_found_not": "Ikke fundet", + "import_derivation_loading": "Indlæser...", + "import_derivation_subtitle": "Indtast brugerdefineret afledningssti, og vi vil forsøge at opdage din wallet.", + "import_derivation_title": "Afledningssti", + "import_derivation_unknown": "Ukendt", + "import_wrong_path": "Forkert afledningssti", + "list_create_a_wallet_text": "Det er gratis, og du kan oprette \nså mange du vil.", + "list_empty_txs1_lightning": "Lightning-wallet bør bruges til dine daglige transaktioner. Gebyrer er uretfærdigt billige, og hastigheden er lynhurtig.", + "list_empty_txs2": "Start med din wallet.", + "list_empty_txs2_lightning": "\nFor at begynde at bruge den skal du trykke på Administrer midler og fylde op på din saldo.", + "list_long_choose": "Vælg foto", + "paste_from_clipboard": "Indsæt", + "import_file": "Importér fil", + "list_long_scan": "Scan QR-kode", + "list_tryagain": "Prøv igen", + "no_ln_wallet_error": "Før du betaler en Lightning-faktura, skal du først tilføje en Lightning-wallet.", + "looks_like_bip38": "Dette ligner en adgangskodebeskyttet privat nøgle (BIP38).", + "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-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 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 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": "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 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": { + "display_in_bitcoin": "Vis i Bitcoin", + "hide": "Skjul", + "display_in_sats": "Vis i sats", + "display_in_fiat": "Vis i {currency}", + "title": "Samlet saldo", + "explanation": "Vis den samlede saldo for alle dine wallets på oversigtsskærmen." }, "multisig": { "confirm": "Bekræft", - "share": "del", "create": "Opret", - "ms_help_title5": "Enable advanced mode" + "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": "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 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", + "share": "Del...", + "view": "Vis", + "shared_key_detected": "Delt co-signer", + "shared_key_detected_question": "En co-signer er blevet delt med dig, vil du importere den?", + "manage_keys": "Administrer nøgler", + "how_many_signatures_can_bluewallet_make": "hvor mange signaturer kan BlueWallet lave", + "signatures_required_to_spend": "Påkrævede signaturer {number}", + "signatures_we_can_make": "kan lave {number}", + "scan_or_import_file": "Scan eller importér fil", + "export_coordination_setup": "Eksportér koordineringsopsætning", + "cosign_this_transaction": "Co-sign denne transaktion?", + "lets_start": "Lad os starte", + "native_segwit_title": "Bedste praksis", + "wrapped_segwit_title": "Bedste kompatibilitet", + "legacy_title": "Legacy", + "co_sign_transaction": "Signer en transaktion", + "what_is_vault": "En Vault er en", + "what_is_vault_numberOfWallets": " {m}-af-{n} multisig ", + "what_is_vault_wallet": "wallet.", + "vault_advanced_customize": "Vault-indstillinger", + "needs": "Den kræver", + "what_is_vault_description_number_of_vault_keys": " {m} Vault-nøgler ", + "what_is_vault_description_to_spend": "til at sende og en tredje, som du \nkan bruge som backup.", + "what_is_vault_description_to_spend_other": "til at sende.", + "quorum": "{m} af {n} quorum", + "quorum_header": "Quorum", + "of": "af", + "wallet_type": "Wallet-type", + "invalid_mnemonics": "Denne mnemonic ser ikke ud til at være gyldig.", + "invalid_cosigner": "Ugyldige co-signer-data", + "not_a_multisignature_xpub": "Dette er ikke en XPUB fra en multisignatur-wallet!", + "invalid_cosigner_format": "Forkert co-signer: Dette er ikke en co-signer til {format}-formatet.", + "create_new_key": "Opret ny", + "scan_or_open_file": "Scan eller åbn fil", + "i_have_mnemonics": "Jeg har en seed til denne nøgle.", + "type_your_mnemonics": "Indsæt en seed for at importere din eksisterende Vault-nøgle.", + "this_is_cosigners_xpub": "Dette er co-signerens XPUB—klar til at blive importeret til en anden wallet. Det er sikkert at dele den.", + "this_is_cosigners_xpub_airdrop": "Hvis du deler via AirDrop, skal modtagerne være på koordineringsskærmen.", + "wallet_key_created": "Din Vault-nøgle er oprettet. Tag et øjeblik til sikkert at sikkerhedskopiere dit mnemonic seed.", + "are_you_sure_seed_will_be_lost": "Er du sikker? Dit mnemonic seed vil gå tabt, hvis du ikke har en backup.", + "forget_this_seed": "Glem denne seed og brug XPUB i stedet.", + "view_edit_cosigners": "Vis/rediger co-signers", + "this_cosigner_is_already_imported": "Denne co-signer er allerede importeret.", + "export_signed_psbt": "Eksportér signeret PSBT", + "input_fp": "Indtast fingerprint", + "input_fp_explain": "Spring over for at bruge standardværdien (00000000)", + "input_path": "Indsæt afledningssti", + "input_path_explain": "Spring over for at bruge standardværdien ({default})", + "ms_help": "Hjælp", + "ms_help_title": "Sådan fungerer Multisig Vaults: Tips og tricks", + "ms_help_text": "En wallet med flere nøgler, til øget sikkerhed eller delt opbevaring", + "ms_help_title1": "Flere enheder anbefales.", + "ms_help_1": "Vault'en vil fungere med andre BlueWallet-apps og PSBT-kompatible wallets såsom Electrum, Specter, Coldcard, Cobo Vault osv.", + "ms_help_title2": "Redigering af nøgler", + "ms_help_2": "Du kan oprette alle Vault-nøgler på denne enhed og fjerne eller redigere dem senere. At have alle nøgler på samme enhed har samme sikkerhed som en almindelig Bitcoin-wallet.", + "ms_help_title3": "Vault-backups", + "ms_help_3": "Under wallet-indstillingerne finder du din Vault-backup og watch-only-backup. Denne backup er som et kort til din wallet. Den er afgørende for wallet-gendannelse, hvis du mister et af dine seeds.", + "ms_help_title4": "Import af Vaults", + "ms_help_4": "For at importere en multisig skal du bruge din backup-fil og Import-funktionen. Hvis du kun har seeds og XPUB'er, kan du bruge den individuelle Import-knap, når du opretter Vault-nøgler.", + "ms_help_5": "Som standard vil BlueWallet generere en 2-af-3 Vault. For at oprette et andet quorum eller ændre adressetypen skal du aktivere Avanceret tilstand i indstillingerne." + }, + "is_it_my_address": { + "title": "Er det min adresse?", + "owns": "{label} ejer {address}", + "enter_address": "Indtast adresse", + "check_address": "Tjek adresse", + "no_wallet_owns_address": "Ingen af de tilgængelige wallets ejer den angivne adresse.", + "view_qrcode": "Vis QR-kode" + }, + "autofill_word": { + "title": "Sidste seed-ord", + "enter": "Indtast din delvise mnemonic", + "generate_word": "Generér det sidste ord", + "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": "Coins valgt ({number})", + "selected_summ": "{value} valgt", + "empty": "Denne wallet har ingen coins i øjeblikket.", + "freeze": "Frys", + "freezeLabel": "Frys", + "freezeLabel_un": "Frigør", + "use_coin": "Brug coin", + "use_coins": "Brug coins", + "tip": "Denne funktion giver dig mulighed for at se, mærke, fryse eller vælge coins for forbedret wallet-styring. Du kan vælge flere coins ved at trykke på de farvede cirkler.", + "sort_asc": "Stigende", + "sort_desc": "Faldende", + "sort_height": "Højde", + "sort_value": "Værdi", + "sort_by": "Sortér efter" + }, + "units": { + "BTC": "BTC", + "MAX": "Maks", + "sat_vbyte": "sat/vByte", + "sats": "sats" }, "addresses": { - "sign_placeholder_address": "adresse", + "sign_placeholder_address": "Adresse", "type_receive": "Modtag", - "transactions": "transaktioner" + "transactions": "Transaktioner", + "copy_private_key": "Kopier privat nøgle", + "sensitive_private_key": "Advarsel: private nøgler er ekstremt følsomme. Fortsæt?", + "sign_title": "Signer/verificer besked", + "sign_help": "Her kan du oprette eller verificere en kryptografisk signatur baseret på en Bitcoin-adresse.", + "sign_sign": "Signer", + "sign_verify": "Verificer", + "sign_signature_correct": "Verificering lykkedes!", + "sign_signature_incorrect": "Verificering mislykkedes!", + "sign_placeholder_message": "Besked", + "sign_placeholder_signature": "Signatur", + "addresses_title": "Adresser", + "type_change": "Byttepenge", + "type_used": "Brugt" + }, + "lnurl_auth": { + "register_question_part_1": "Vil du registrere en konto hos", + "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-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-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-wallet?", + "auth_answer": "Du er blevet godkendt hos {hostname}!", + "could_not_auth": "Vi kunne ikke godkende dig hos {hostname}.", + "authenticate": "Godkend" + }, + "bip47": { + "payment_code": "Payment Code", + "contacts": "Kontakter", + "bip47_explain": "Genbrugelig og delbar kode", + "bip47_explain_subtitle": "BIP47", + "purpose": "Genbrugelig og delbar kode (BIP47)", + "pay_this_contact": "Betal denne kontakt", + "rename_contact": "Omdøb kontakt", + "copy_payment_code": "Kopier Payment Code", + "hide_contact": "Skjul kontakt", + "rename": "Omdøb", + "provide_name": "Angiv nyt navn til denne kontakt", + "add_contact": "Tilføj kontakt", + "provide_payment_code": "Angiv Payment Code", + "invalid_pc": "Ugyldig Payment Code", + "notification_tx_unconfirmed": "Notification transaction er endnu ikke bekræftet, vent venligst", + "failed_create_notif_tx": "Kunne ikke oprette onchain-transaktion", + "onchain_tx_needed": "Onchain-transaktion er nødvendig", + "notif_tx_sent": "Notification transaction sendt. Vent venligst på, at den bekræftes", + "notif_tx": "Notification transaction", + "not_found": "Payment Code ikke fundet" } } diff --git a/loc/de_de.json b/loc/de_de.json index 04be97109c9..a742b286e05 100644 --- a/loc/de_de.json +++ b/loc/de_de.json @@ -4,46 +4,48 @@ "cancel": "Abbrechen", "continue": "Fortsetzen", "clipboard": "Zwischenablage", + "copied": "Kopiert!", + "discard_changes": "Änderungen verwerfen?", + "discard_changes_explain": "Die ungespeicherten Änderungen verwerfen und den Bildschirm verlassen?", "enter_password": "Passwort eingeben", "never": "nie", - "disabled": "Deaktiviert", "of": "{number} von {total}", "ok": "OK", + "enter_url": "URL eingeben", "storage_is_encrypted": "Zum Entschlüsseln des Speichers das Passwort eingeben.", "yes": "Ja", "no": "Nein", - "save": "Speichern", + "save": "Speichern...", "seed": "Seed", "success": "Erfolg", "wallet_key": "Wallet Schlüssel", - "invalid_animated_qr_code_fragment": "Ungültig animiertes QR-Code-Fragment. Bitte erneut versuchen.", - "file_saved": "Die Datei {filePath} wurde im Ordner {destination} gespeichert.", - "downloads_folder": "Download-Ordner", "close": "Schließen", - "change_input_currency": "Eingangswährung ändern", + "change_input_currency": "Eingabewährung ändern", "refresh": "Aktualisieren", - "more": "Mehr", - "pick_image": "Bild aus Bibliothek wählen", + "pick_image": "Aus der Bibliothek wählen", "pick_file": "Datei auswählen", "enter_amount": "Betrag eingeben", - "qr_custom_input_button": "10x antippen für individuelle Eingabe" - }, - "alert": { - "default": "Warnung" + "qr_custom_input_button": "10x antippen für individuelle Eingabe", + "unlock": "Entsperren", + "port": "Port", + "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! Deine Gelder sollten in Kürze in deiner Bitcoin-Wallet ankommen.", "title": "Azte.co Gutschein einlösen" }, "entropy": { "save": "Speichern", "title": "Entropie", - "undo": "Zurück" + "undo": "Zurück", + "amountOfEntropy": "{bits} von {limit} Bits" }, "errors": { "broadcast": "Übertragung fehlgeschlagen", @@ -51,83 +53,66 @@ "network": "Netzwerkfehler" }, "lnd": { - "active": "Aktiv", - "inactive": "Inaktiv", - "channels": "Kanäle", - "no_channels": "Keine Kanäle", - "claim_balance": "Saldo von {balance} beanspruchen", - "close_channel": "Kanal schließen", - "new_channel": "Neuer Kanal", - "errorInvoiceExpired": "Rechnung verfallen", - "force_close_channel": "Kanal zwangsweise schließen?", + "errorInvoiceExpired": "Rechnung verfallen.", "expired": "Abgelaufen", - "node_alias": "Knoten-Alias", "expiresIn": "Läuft in {time} Minuten ab", "payButton": "Zahlen", + "payment": "Zahlung", "placeholder": "Rechnung oder Adresse", - "open_channel": "Kanal öffnen", - "funding_amount_placeholder": "Finanzierungsbetrag, z.B. 0.001", - "opening_channnel_for_from": "Kanal für Wallet {forWalletLabel} finanziert durch Wallet {fromWalletLabel} eröffnen.", - "are_you_sure_open_channel": "Diesen Kanal definitiv eröffnen?", "potentialFee": "Geschätzte Gebühr: {fee}", - "remote_host": "Entfernter Rechner", "refill": "Aufladen", - "reconnect_peer": "Erneut zu Peer verbinden", - "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 kann nicht mit der gleichen Wallet beglichen werden, die sie erstellt hat.", - "title": "Beträge verwalten", - "can_send": "Kann senden", - "can_receive": "Kann empfangen", - "view_logs": "Protokolle anzeigen" + "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": { "additional_info": "Weiterführende Informationen", "for": "Für:", "lightning_invoice": "Lightning Rechnung", - "open_direct_channel": "Direkten Kanal zu diesem Knoten eröffnen:", - "please_pay_between_and": "Zahlen Sie zwischen {min} und {max}", + "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." }, "plausibledeniability": { "create_fake_storage": "Verschlüsselten Speicher erstellen", - "create_password": "Erstelle ein Passwort", "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. Solltest du gezwungen werden dein Passwort preiszugeben, kannst du dieses anstelle des richtigen Passwortes nennen. BlueWallet öffnet dann die Wallet, welche du im zweiten Speicher zur Täuschung angelegt hast und dein 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", - "passwords_do_not_match": "Passwörter stimmen nicht überein. Bitte erneut versuchen.", - "retype_password": "Passwort wiederholen", - "success": "Erfolg!", "title": "Glaubhafte Täuschung" }, "pleasebackup": { - "ask": "Hast du die Wiederherstellungs-Phrase deines Wallets gesichert? Ohne Sie kannst du nicht mehr auf deine bitcoin zugreifen und sie wären für immer verloren, sollte dein 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, ich habe sie notiert.", - "ok_lnd": "OK. Die Sicherung ist erstellt.", - "text": "Nimm Dir Zeit die mnemonischen Wörter auf ein Papier zu schreiben.\nDie Wörter sind dein Backup zur Wallet-Wiederherstellung.", - "text_lnd": "Zur Wiederherstellung des Wallet im Verlustfall bitte dieses Wallet-Backup sichern. ", - "title": "Dein Wallet ist erstellt." + "ok": "Ok, sie sind notiert.", + "ok_lnd": "Die Sicherung ist erstellt.", + "text": "Unbedingt die Seed-Wörter auf Papier schreiben.\nDiese Wörter sind das Backup zur Wallet-Wiederherstellung.", + "text_lnd": "Zur Wiederherstellung der Wallet im Verlustfall bitte dieses Wallet-Backup sichern.", + "title": "Deine Wallet ist erstellt." }, "receive": { "details_create": "Erstelle", "details_label": "Beschreibung", "details_setAmount": "Zu erhaltender Betrag", "details_share": "Teilen", + "address_not_found": "Empfangsadresse nicht generierbar.", "header": "Erhalten", + "reset": "Zurücksetzen", "maxSats": "Der größtmögliche Betrag ist {max} sats", "maxSatsFull": "Der größtmögliche Betrag ist {max} sats oder {currency}", "minSats": "Der kleinstmögliche Betrag ist {min} sats", - "minSatsFull": "Der kleinstmögliche Betrag ist {min} sats oder {currency}" + "minSatsFull": "Der kleinstmögliche Betrag ist {min} sats oder {currency}", + "qrcode_for_the_address": "QR-Code für die Adresse", + "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", @@ -142,18 +127,25 @@ "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", + "details_insert_contact": "Kontakt einfügen", "details_add_rec_add": "Empfänger hinzufügen", "details_add_rec_rem": "Empfänger entfernen", + "details_add_recc_rem_all_alert_description": "Wirklich alle Empfänger entfernen?", + "details_add_rec_rem_all": "Alle Empfänger entfernen", + "details_recipients_title": "Empfänger", + "details_recipient_title": "Empfänger #{number} von #{total}", + "please_complete_recipient_title": "Unvollständiger Empfänger", + "please_complete_recipient_details": "Details für Empfänger #{number} vor dem Hinzufügen eines neuen fertig ausfüllen.", "details_address": "Adresse", "details_address_field_is_not_valid": "Adresseingabe ist nicht korrekt", "details_adv_fee_bump": "Erhöhung TRX-Gebühr nach Senden erlauben", "details_adv_full": "Gesamtes Guthaben senden", - "details_adv_full_sure": "Bist du sicher, dass du das gesamte Guthaben für diese Transaktion verwenden willst?", - "details_adv_full_sure_frozen": "Bist Du sicher, dass du das gesamte Guthaben für diese Transaktion verwenden willst? Bitte beachte, dass eingefrorene Münzen ausgeschlossen werden.", + "details_adv_full_sure": "Wirklich das gesamte Guthaben für diese Transaktion verwenden?", + "details_adv_full_sure_frozen": "Wirklich das gesamte Guthaben für diese Transaktion verwenden? Hinweis: Eingefrorene Münzen bleiben ausgeschlossen.", "details_adv_import": "Transaktion importieren", "details_adv_import_qr": "Transaktion scannen (QR)", "details_amount_field_is_not_valid": "Betragseingabe ist ungültig.", @@ -161,12 +153,13 @@ "details_create": "Erstellen", "details_error_decode": "Bitcoin-Adresse kann nicht dekodiert werden", "details_fee_field_is_not_valid": "Gebühreneingabe ist nicht korrekt", - "details_frozen": "{amount} BTC ist eingefroren", + "details_frozen": "{amount} BTC ist eingefroren.", "details_next": "Weiter", "details_no_signed_tx": "Die ausgewählte Datei enthält keine importierbare signierte Transaktion.", "details_note_placeholder": "Eigene Bezeichnung", "details_scan": "Scannen", "details_scan_hint": "Zum Importieren / Scannen zweimal tippen", + "details_scan_error": "Scan-Fehler", "details_total_exceeds_balance": "Der zu sendende Betrag ist größer als der verfügbare Betrag.", "details_total_exceeds_balance_frozen": "Der zu sendende Betrag übersteigt das verfügbare Guthaben. Bitte beachte, dass eingefrorene Münzen ausgeschlossen wurden.", "details_unrecognized_file_format": "Dateiformat unbekannt", @@ -175,11 +168,12 @@ "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", "fee_custom": "Benutzerdefiniert", + "insert_custom_fee": "Gebühr angeben", "fee_fast": "Schnell", "fee_medium": "Durchschnitt", "fee_replace_minvb": "Die gewählte Transaktionsgebühr (Satoshi per vByte), sollte höher sein als {min} sat/vByte.", @@ -190,30 +184,33 @@ "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_later": "Später beantworten", - "permission_storage_message": "BlueWallet braucht zur Speicherung dieser Datei die Erlaubnis auf den internen Speicher zuzugreifen.", "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). Signiere sie mithilfe Deiner Hardware-Wallet final.", + "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}", "psbt_tx_open": "Öffne signierte Transaktion.", "psbt_tx_scan": "Signierte Transaktion scannen", - "qr_error_no_qrcode": "Der QR-Code konnte nicht ausgelesen werden. Achte darauf, dass er ohne zusätzliche Inhalte wie Text, Grafiken oder Bilder vorliegt.", + "qr_error_no_qrcode": "Der QR-Code konnte nicht gelesen werden. Achte darauf, dass beim Scannen alleine der QR-Code, ohne andere Grafik- oder Textinhalte, im Bild ist.", "reset_amount": "Betrag zurücksetzen", - "reset_amount_confirm": "Möchten Du den Betrag zurücksetzen?", + "reset_amount_confirm": "Den Betrag wirklich zurücksetzen?", "success_done": "Fertig", - "txSaved": "Die Transaktionsdatei ({filePath}) wurde im Download-Ordner gespeichert.", + "txSaved": "Die Transaktionsdatei ({filePath}) wurde gespeichert.", + "file_saved_at_path": "Die Datei ({filePath}) wurde gespeichert.", + "cant_send_to_silentpayment_adress": "Diese Wallet kann nicht an Silent-Payment-Adressen senden", + "cant_send_to_bip47": "Diese Wallet kann nicht an BIP47-Zahlungscodes senden", + "cant_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", @@ -222,62 +219,66 @@ "performance_score": "Leistungskennzahl: {num}", "run_performance_test": "Leistung testen", "about_selftest": "Selbsttest ausführen", + "block_explorer_invalid_custom_url": "Ungültige URL. Geben Sie eine gültige URL ein, die mit http:// oder https:// beginnt.", "about_selftest_electrum_disabled": "Deaktiviere den Electrum Offline-Modus, um den Selbsttest durchführen zu können.", "about_selftest_ok": "Alle internen Tests verliefen erfolgreich. Das Wallet funktioniert gut.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord Server", "about_sm_telegram": "Telegram-Channel", - "about_sm_twitter": "Folgt uns auf Twitter", - "advanced_options": "Erweiterte Optionen", + "privacy_temporary_screenshots": "Bildschirmaufnahme erlauben", + "privacy_temporary_screenshots_instructions": "Der Schutz vor Bildschirmaufnahmen wird vorübergehend deaktiviert, wodurch Screenshots und Bildschirmaufzeichnungen möglich sind. Der Schutz wird automatisch reaktiviert, wenn du BlueWallet schließt und erneut öffnest.", "biometrics": "Biometrie", - "biom_10times": "Sie haben 10 Mal versucht, Ihr Passwort einzugeben. Möchten Sie Ihren Speicher zurücksetzen? Dadurch werden alle Wallets entfernt und Ihr 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": "Dein Gerät verfügt über keinen Passcode. Um fortzufahren, diesen in den Geräteeinstellungen zuerst konfigurieren.", - "biom_remove_decrypt": "Alle Deine Wallets werden entfernt und der Speicher wird entschlüsselt. Bist du sicher, dass du fortfahren möchten?", + "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": "Die Preisangaben stammen von", + "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_desc": "Wenn deaktiviert öffnet BlueWallet beim Start die ausgewählte Wallet.", - "default_info": "Standard Info", "default_title": "Beim Start", - "default_wallets": "Alle Wallets anzeigen", + "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.", + "electrum_error_connect_tor": "Keine Verbindung zum Electrum-Server. Bitte Orbot-App verbinden und es erneut versuchen.", "lndhub_uri": "Z.b., {example}", "electrum_host": "Z.b., {example}", "electrum_offline_mode": "Offline-Modus", "electrum_offline_description": "Wenn aktiviert, rufen Wallets keine Kontostände und Transaktionen ab.", - "electrum_port": "Port, üblich {Beispiel}", + "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_lndhub_as_default": "{url} als Standard LNDHub-Server setzten?", - "electrum_settings_server": "Electrum Server", - "electrum_settings_explain": "Leer lassen, um den Standard zu verwenden.", + "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_status": "Status", - "electrum_clear_alert_title": "Historie löschen?", - "electrum_clear_alert_message": "Electrum-Serverhistorie löschen?", - "electrum_clear_alert_cancel": "Abbrechen", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Auswählen", - "electrum_reset": "Zurücksetzten", - "electrum_unable_to_connect": "Verbindung zu {Server} kann nicht hergestellt werden.", - "electrum_history": "Serverhistorie", - "electrum_reset_to_default": "Sollen die Electrum-Einstellungen wirklich auf die Standardwerte zurückgesetzt werden?", - "electrum_clear": "Löschen", - "tor_supported": "Tor unterstützt", - "tor_unsupported": "Tor-Verbindungen sind nicht unterstützt.", + "electrum_preferred_server": "Präferierter Server", + "electrum_preferred_server_description": "Den Server eingeben, der die Wallet für alle Bitcoin-Aktivitäten verwenden soll. Sobald festgelegt, wird die Wallet ausschließlich diesen Server verwenden, um Salden abzufragen, Transaktionen zu senden und Netzwerkdaten abzurufen. Prüfe vorher, dass der Server vertrauenswürdig ist.", + "electrum_unable_to_connect": "Verbindung zu {server} kann nicht hergestellt werden.", + "electrum_history": "Historie", + "electrum_reset_to_default": "Dies lässt BlueWallet zufällig einen Server aus der Liste der Server auswählen.", + "electrum_reset": "Zurücksetzen", + "electrum_reset_to_default_and_clear_history": "Auf die Standardeinstellungen zurücksetzen und den Verlauf löschen.", "encrypt_decrypt": "Speicher entschlüsseln", - "encrypt_decrypt_q": "Willst du die Speicherverschlüsselung wirklich aufheben? Hiermit wird dein Wallet ohne Passwortschutz direkt benutzbar. ", - "encrypt_enc_and_pass": "Verschlüsselt und passwortgeschützt", + "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 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.", "encrypt_title": "Sicherheit", "encrypt_tstorage": "Speicher", "encrypt_use": "Benutze {type}", - "encrypt_use_expl": "{type} wird zur Transaktionsdurchführung, zum Entsperren, dem Export oder der Löschung einer Wallet benötigt. {type} ersetzt nicht die Passworteingabe bei verschlüsseltem Speicher.", + "set_as_preferred": "Als bevorzugt festlegen", + "set_as_preferred_electrum": "Mit der Wahl von {host}:{port} als präferierten Server wird die zufällige Verbindung zu einem der vorgeschlagenen Servern abgeschaltet.", + "encrypted_feature_disabled": "Diese Funktion kann bei verschlüsseltem Speicher nicht genutzt werden.", + "encrypt_use_expl": "{type} wird zur Identitätsbestätigung verwendet, bevor eine Transaktion gesendet, ein Wallet entsperrt, exportiert oder gelöscht wird.", + "biometrics_fail": "Wenn {type} nicht aktiviert ist oder entsperrt werden kann, alternativ den Gerätepasscode verwenden.", "general": "Allgemein", - "general_adv_mode": "Erweiterter Modus", - "general_adv_mode_e": "Erlaubt, wenn aktiviert, verschiedene Wallet-Typen anzulegen, dabei eine benutzerdefinierte Entropie zu verwenden und die LNDHub-Instanz der Lightning Wallet frei zu definieren.", "general_continuity": "Kontinuität", "general_continuity_e": "Wenn aktiviert werden ausgewählte Wallets und deren Transaktionen auf deinen anderen Apple iCloud Geräten angezeigt.", "groundcontrol_explanation": "GroundControl ist ein kostenloser Open-Source Push-Benachrichtigungsdienst für Bitcoin-Wallets. Trage hier die URL eines selbst aufgesetzten GroundControl-Servers ein, um von BlueWallet unabhängig zu bleiben. Leer lassen, um die Standardeinstellung zu verwenden.", @@ -285,73 +286,78 @@ "language": "Sprache", "last_updated": "Zuletzt aktualisiert", "language_isRTL": "BlueWallet zur Aktivierung der Änderung der Schriftrichtung neu starten.", - "lightning_error_lndhub_uri": "Keine gültige LndHub URI", + "license": "Lizenz", + "lightning_error_lndhub_uri": "Ungültiger LNDhub-URI", + "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", - "tor_settings": "Tor Einstellungen", - "lightning_settings_explain": "Zur Verbindung mit einem eigenen LND-Knoten LNDHub installieren und dessen URL hier eingeben. Bitte beachte, dass nur Wallets, die nach dem Speichern der Änderungen erstellt werden, mit dem angegebenen LNDHub verbunden werden.", + "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", "open_link_in_explorer": "Link in Explorer öffnen", "password": "Passwort", - "password_explain": "Erstelle das Passwort zum Entschlüsseln des Speichers", - "passwords_do_not_match": "Passwörter stimmen nicht überein", + "password_explain": "Das Entschlüsselungpasswort für den Speicher eingeben.", "plausible_deniability": "Glaubhafte Täuschung", "privacy": "Privatsphäre", "privacy_read_clipboard": "Zwischenablage lesen", "privacy_system_settings": "Systemeinstellungen", "privacy_quickactions": "Walletverknüpfungen", - "privacy_quickactions_explanation": "Halte auf dem Startbildschirm das BlueWallet App-Symbol gedrückt, um rasch deinen Saldo zu sehen.", + "privacy_quickactions_explanation": "Auf dem Startbildschirm das BlueWallet App-Symbol gedrückt halten, um rasch den Saldo zu sehen.", "privacy_clipboard_explanation": "Nutzt Rechnungen und Adressen in der Zwischenablage zum Senden.", "privacy_do_not_track": "Diagnosedaten ausschalten", "privacy_do_not_track_explanation": "Leistungs- und Zuverlässigkeitsinformationen nicht zur Analyse einreichen.", - "push_notifications": "Push-Meldungen", "rate": "Kurs", - "retype_password": "Passwort wiederholen", + "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" }, "notifications": { - "would_you_like_to_receive_notifications": "Möchten Sie bei Zahlungseingängen eine Benachrichtigung erhalten?", - "no_and_dont_ask": "Nein und nicht erneut fragen", - "ask_me_later": "Später erneut fragen" + "would_you_like_to_receive_notifications": "Soll bei Zahlungseingängen eine Benachrichtigung zugestellt werden?", + "notifications_subtitle": "Zahlungseingänge und Transaktionsbestätigungen", + "no_and_dont_ask": "Nein und nicht erneut fragen.", + "permission_denied_message": "Die App-Berechtigung Benachrichtigungen zu erhalten ist nicht gesetzt. Zum Erhalt diese in den Geräteeinstellungen erteilen." }, "transactions": { "cancel_explain": "BlueWallet ersetzt diese Transaktion durch eine mit höherer Gebühr, welche den Betrag an Dich zurücküberweist. Die aktuelle Transaktion wird dadurch effektiv abgebrochen. Dieses Verfahren wird RBF - Replace By Fee - genannt.", "cancel_no": "Diese Transaktion ist nicht ersetzbar.", "cancel_title": "Diese Transaktion abbrechen (RBF)", + "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 deine unbestätigte Transaktion ausgibt. Die Gesamtgebühren werden höher als die der ursprünglichen Transaktion sein, daher sollte sie schneller ausgeführt werden. Dies wird CPFP genannt - Child Pays For Parent.", + "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).", "cpfp_no_bump": "Keine TRX-Gebührenerhöhung möglich", "cpfp_title": "TRX-Gebühr erhöhen (CPFP)", "details_balance_hide": "Guthaben verbergen", "details_balance_show": "Guthaben zeigen", - "details_block": "Blockhöhe", "details_copy": "Kopieren", - "details_copy_amount": "Betrag 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", "details_received": "Empfangen", - "transaction_note_saved": "Transaktionsbezeichnung erfolgreich gespeichert.", - "details_show_in_block_explorer": "Im Block-Explorer zeigen", + "details_view_in_browser": "Im Browser anzeigen", "details_title": "Transaktion", + "incoming_transaction": "Eingehende Transaktion", + "outgoing_transaction": "Ausgehende Transaktion", + "expired_transaction": "Abgelaufene Transaktion", + "pending_transaction": "Ausstehende Transaktion", + "offchain": "Offchain", + "onchain": "Onchain", "details_to": "Ausgang", "enable_offline_signing": "Diese Wallet wird ohne Offline-Signierung genutzt. Soll diese jetzt aktiviert werden?", "list_conf": "Bestätigungen: {number}", @@ -361,8 +367,10 @@ "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.", "rbf_explain": "BlueWallet ersetzt die aktuelle Transaktion zur schnelleren Überweisung durch eine Transaktion mit höherer Gebühr. Dieses Verfahren wird 'RBF - Replace By Fee' genannt.", "rbf_title": "TRX-Gebühr erhöhen (RBF)", @@ -370,118 +378,174 @@ "status_cancel": "Transaktion abbrechen", "transactions_count": "Anzahl Transaktionen", "txid": "Transaktions-ID", - "updating": "Aktualisiere...." + "updating": "Aktualisiere....", + "watchOnlyWarningTitle": "Sicherheitswarnung", + "watchOnlyWarningDescription": "Achtung: Betrüger verwenden „Watch-only\"-Wallets, um Nutzern echte Wallets vorzutäuschen. Mit dieser Wallet lassen sich keine Gelder kontrollieren oder senden, sondern nur der Kontostand einsehen.", + "custom_fee_warning_title": "Warnung", + "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", + "details_explorer": "Explorer", + "details_network_fee": "Netzwerkgebühr", + "details_to_address": "An", + "details_id": "ID", + "details_note": "Notiz", + "details_add_note": "Hinzufügen", + "details_advanced": "Fortgeschritten", + "details_fee_rate": "Gebührenrate", + "details_size": "Größe", + "details_virtual_size": "Virtuelle Größe", + "details_tx_hex": "Tx Hex", + "details_inputs_count": "Eingänge ({count})", + "details_outputs_count": "Ausgänge ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Einfache und leistungsstarke Bitcoin Wallet", "add_create": "Erstellen", - "add_entropy_generated": "{gen} Bytes an generierter Entropie ", + "total_balance": "Gesamtes Guthaben", + "add_entropy_reset_title": "Entropie zurücksetzen", + "add_entropy_reset_message": "Ein Wechsel des Wallet-Typs wird die Entropie zurücksetzen. Wirklich fortfahren?", + "add_entropy": "Entropie", + "add_entropy_bytes": "{bytes} Bytes 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": "LNDHub Verbindung", - "add_lndhub_error": "Die eingegebene Adresse ist kein gültiger LNDHub Konten.", - "add_lndhub_placeholder": "Bitcoin Knoten-Adresse", + "add_lndhub": "Mit deinem LNDhub verbinden", + "add_lndhub_error": "Die Adresse verweist auf einen ungültigen LNDhub-Knoten.", + "add_lndhub_placeholder": "Knoten-Adresse", "add_placeholder": "Mein Wallet", "add_title": "Wallet hinzufügen", "add_wallet_name": "Wallet Name", "add_wallet_type": "Typ", - "balance": "Saldo", - "clipboard_bitcoin": "Willst Du die Bitcoin Adresse in der Zwischenablage für eine Transaktion verwenden?", - "clipboard_lightning": "Willst Du die Lightning Rechnung in der Zwischenablage für eine Transaktion verwenden?", + "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_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": "Bist du dir sicher?", + "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": "In Wallet-Liste anzeigen", + "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_no_cancel": "Nein, abbrechnen", - "details_save": "Speichern", + "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_wallet_updated": "Wallet aktualisiert", "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", "import_discovery_title": "Treffer", "import_discovery_subtitle": "Wallet aus Trefferliste wählen", "import_discovery_derivation": "Eigener Ableitungspfad wählen", "import_discovery_no_wallets": "Es wurden keine Wallets gefunden.", - "import_derivation_found": "gefunden", - "import_derivation_found_not": "nicht gefunden", - "import_derivation_loading": "lade...", - "import_derivation_subtitle": "Eigener Ableitungspfad zur Ermittlung der genutzten Wallet eingeben", + "import_discovery_offline": "BlueWallet ist derzeit im Offline-Modus und kann die Existenz der Wallet nicht überprüfen. Bitte die richtige Wallet manuell auswählen.", + "import_derivation_found": "Gefunden", + "import_derivation_found_not": "Nicht gefunden", + "import_derivation_loading": "Lade...", + "import_derivation_subtitle": "Eigener Ableitungspfad zur Ermittlung der genutzten Wallet eingeben.", "import_derivation_title": "Ableitungspfad", - "import_derivation_unknown": "unbekannt", + "import_derivation_unknown": "Unbekannt", "import_wrong_path": "Falscher Ableitungspfad", "list_create_a_button": "Jetzt hinzufügen", "list_create_a_wallet": "Wallet hinzufügen", - "list_create_a_wallet_text": "Wallets sind kostenlos. \nErstelle so viel du magst.", + "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", - "list_ln_browser": "LApp Browser", "list_long_choose": "Foto auswählen", - "list_long_clipboard": "Aus der Zwischenablage kopieren", + "paste_from_clipboard": "Einfügen", + "import_file": "Datei importieren", "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.", - "reorder_title": "Wallets neu ordnen", - "reorder_instructions": "Tippen und halten Sie eine Wallet, um sie umzuplatzieren.", - "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.", + "manage_title": "Wallets verwalten", + "no_results_found": "Keine Ergebnisse gefunden.", + "please_continue_scanning": "Bitte Scanvorgang fortsetzen", + "select_no_bitcoin": "Es sind momentan keine Bitcoin-Wallets verfügbar.", + "select_no_bitcoin_exp": "Eine Bitcoin-Wallet ist Voraussetzung dafür, um eine Lightning-Wallet zu befüllen. Bitte erstelle oder importiere eine.", "select_wallet": "Wähle eine Wallet", - "xpub_copiedToClipboard": "In die Zwischenablage kopiert.", "pull_to_refresh": "Zum Aktualisieren ziehen", - "warning_do_not_disclose": "Warnung! Nicht veröffentlichen", + "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.", + "wallet_type_this": "Wallet vom Typ {type}.", + "share_number": "Teil {number}", + "copy_ln_url": "Diese URL kopieren und sicher speichern. Sie ist nötig, um die Wallet später wiederherzustellen.", + "copy_ln_public": "Diese Information kopieren und sicher speichern. Sie ist nötig, um die Wallet später wiederherzustellen.", "add_ln_wallet_first": "Bitte zuerst ein Lightning-Wallet hinzufügen.", "identity_pubkey": "Pubkey-Identität", - "xpub_title": "Wallet xPub" + "xpub_title": "Wallet xPub", + "manage_wallets_search_placeholder": "Wallets, Adressen, Transaktionen und Memos suchen", + "more_info": "Mehr Infos", + "details_delete_wallet_error_message": "Problem beim Entfernen der Wallet aus Benachrichtigungen – evtl. Netzwerkproblem oder schlechte Verbindung. Bei Fortfahren könnten Transaktions-Benachrichtigungen für diese Wallet weiterhin ankommen, selbst nach deren Löschung.", + "details_delete_anyway": "Trotzdem löschen" + }, + "total_balance_view": { + "display_in_bitcoin": "In bitcoin anzeigen", + "hide": "Verbergen", + "display_in_sats": "In Sats anzeigen", + "display_in_fiat": "In {currency} anzeigen", + "title": "Gesamtes Guthaben", + "explanation": "Auf dem Übersichtsbildschirm den Gesamtsaldo aller Wallets anzeigen." }, "multisig": { - "multisig_vault": "Tresor", + "multisig_vault": "Multisignatur Tresor", "default_label": "Multisignatur Tresor", "multisig_vault_explain": "Höchste Sicherheit für große Beträge", "provide_signature": "Schlüssel eingeben", + "provide_signature_details": "Mit dem Gerät und der Wallet, in der der Schlüssel ist, die Transaktion signieren.", + "provide_signature_details_bluewallet": "Zum Menü des Senden-Bildschirms gehen, dort auswählen", + "provide_signature_next_steps": "Signierte Transaktion scannen oder importieren", + "provide_signature_next_steps_details": "Sobald die Wallet die Transaktion signiert hat, den bereitgestellten QR-Code scannen oder die Datei importieren. Alle Transaktionsdetails vor dem Übertragen prüfen.", "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 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}", @@ -496,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", @@ -507,38 +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_cosigner_format": "Falscher Mitsignierer: Dies ist kein Mitsignierer für das Format {format} ", + "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.", - "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": "Bist du sicher? Dein mnemonischer Seed ist ohne Backup verloren!", + "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 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", - "this_cosigner_is_already_imported": "Dieser Mitsignierer ist schon vorhanden", + "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 um die Sicherheit exponentiell zu erhöhen.", + "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, Cobovault, 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, lade deine Multisignatur Backupdatei mittels Import-Funktion. Hast du nur die Seeds der erweiterten Schlüssel, fügst Du diese während der Tresor-Erstellung hinzu.", + "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?", @@ -548,18 +613,31 @@ "no_wallet_owns_address": "Keines der verfügbaren Wallet besitzt die eingegebene Adresse.", "view_qrcode": "QR-Code anzeigen" }, + "autofill_word": { + "enter": "Den unvollständigen Seed eingeben", + "generate_word": "Erzeuge das letzte Wort", + "error": "Die Eingabe ist keine unvollendete 11- oder 23-Wort-Phrase. Bitte erneut versuchen.", + "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", - "tip": "Wallet-Verwaltung zum Anzeigen, Beschriften, Einfrieren oder Auswählen von Münzen. Zur Mehrfachselektion auf die Farbkreise tippen." + "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", + "sort_height": "Höhe", + "sort_value": "Wert", + "sort_label": "Bezeichnung", + "sort_status": "Status", + "sort_by": "Sortiert nach" }, "units": { "BTC": "BTC", @@ -568,14 +646,16 @@ "sats": "sats" }, "addresses": { - "sign_title": "Meldung signieren/verifizieren", + "copy_private_key": "Privaten Schlüssel kopieren", + "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", @@ -584,7 +664,7 @@ "transactions": "Transaktionen" }, "lnurl_auth": { - "register_question_part_1": "Willst Du das Konto bei", + "register_question_part_1": "Ein Konto registrieren bei", "register_question_part_2": "mit deinem Lightning Wallet registrieren?", "register_answer": "Konto bei {hostname} erfolgreich registriert!", "login_question_part_1": "Mittels Lightning-Wallet auf", @@ -600,10 +680,25 @@ "authenticate": "Authentifizieren" }, "bip47": { - "payment_code": "Bezahlcode", - "payment_codes_list": "Bezahlcodeliste", - "who_can_pay_me": "Wer kann mich bezahlen:", - "purpose": "Wiederverwendbarer und teilbarer Code (BIP47)", - "not_found": "Bezahlcode nicht gefunden" + "payment_code": "Zahlungscode", + "contacts": "Kontakte", + "bip47_explain": "Teil- und wiederverwendbarer Code", + "bip47_explain_subtitle": "BIP47", + "purpose": "Teil- und wiederverwendbarer Code (BIP47)", + "pay_this_contact": "An diesen Kontakt zahlen", + "rename_contact": "Kontakt umbenennen", + "copy_payment_code": "Zahlungscode kopieren", + "hide_contact": "Kontakt ausblenden", + "rename": "Umbenennen", + "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 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" } } diff --git a/loc/el.json b/loc/el.json index 0aa93ee5840..c74ed7803a1 100644 --- a/loc/el.json +++ b/loc/el.json @@ -6,30 +6,46 @@ "clipboard": "Πρόχειρο", "enter_password": "Εισάγετε κωδικό", "never": "Ποτέ", - "disabled": "Απενεργοποιημένο", "of": "{number} από {total}", "ok": "Εντάξει", "storage_is_encrypted": "Το αρχείο σου είναι κρυπτογραφημένο. Χρειάζεται ένας κωδικός για να αποκρυπτογραφηθεί.", "yes": "Ναι", "no": "Όχι", - "save": "Αποθήκευση", "success": "Επιτυχία", "close": "Κλείσιμο", "refresh": "Ανανέωση", - "more": "Περισσότερα", - "pick_image": "Επιλογή εικόνας από βιβλιοθήκη", - "pick_file": "Επιλογή ενός αρχείου", - "enter_amount": "Εισαγωγή ενός ποσού" + "enter_amount": "Εισαγωγή ενός ποσού", + "copied": "Αντιγράφηκε!", + "discard_changes": "Απόρριψη αλλαγών;", + "discard_changes_explain": "Έχετε μη αποθηκευμένες αλλαγές. Είστε σίγουροι ότι θέλετε να τις απορρίψετε και να φύγετε από την οθόνη;", + "enter_url": "Εισαγωγή URL", + "save": "Αποθήκευση...", + "seed": "Μνημονική φράση", + "wallet_key": "Κλειδί πορτοφολιού", + "change_input_currency": "Αλλαγή νομίσματος εισαγωγής", + "pick_image": "Επιλογή από βιβλιοθήκη", + "pick_file": "Επιλογή αρχείου", + "qr_custom_input_button": "Πατήστε 10 φορές για προσαρμοσμένη εισαγωγή", + "unlock": "Ξεκλείδωμα", + "port": "Θύρα", + "ssl_port": "Θύρα SSL", + "suggested": "Προτεινόμενο" }, "azteco": { "codeIs": "Ο κωδικός κουπονιού είναι", "redeemButton": "Εξαργύρωση", - "success": "Επιτυχία" + "success": "Επιτυχία", + "errorBeforeRefeem": "Πριν την εξαργύρωση, πρέπει πρώτα να προσθέσετε ένα πορτοφόλι Bitcoin.", + "errorSomething": "Κάτι πήγε στραβά. Είναι ακόμα έγκυρο αυτό το κουπόνι;", + "redeem": "Εξαργύρωση στο πορτοφόλι", + "successMessage": "Το κουπόνι εξαργυρώθηκε με επιτυχία! Τα κεφάλαιά σας θα φτάσουν σύντομα στο Bitcoin πορτοφόλι σας.", + "title": "Εξαργύρωση κουπονιού Azte.co" }, "entropy": { "save": "Αποθήκευση", "title": "Εντροπία", - "undo": "Αναίρεση" + "undo": "Αναίρεση", + "amountOfEntropy": "{bits} από {limit} bits" }, "errors": { "broadcast": "Η μετάδοση απέτυχε.", @@ -37,66 +53,63 @@ "network": "Σφάλμα δικτύου" }, "lnd": { - "active": "Ενεργό", - "inactive": "Ανενεργό", - "channels": "Κανάλια", - "no_channels": "Δεν υπάρχουν κανάλια", - "close_channel": "Κλείσιμο καναλιού", - "new_channel": "Νέο κανάλι", - "errorInvoiceExpired": "Το τιμολόγιο έληξε", + "errorInvoiceExpired": "Το τιμολόγιο έληξε", "expired": "Έληξε", "expiresIn": "Λήγει σε {time} λεπτά", "payButton": "Πληρωμή", + "payment": "Πληρωμή", "placeholder": "Τιμολόγιο ή διεύθυνση", - "open_channel": "Άνοιγμα καναλιού", - "are_you_sure_open_channel": "Είσαι βέβαιος ότι θέλεις να ανοίξεις αυτό το κανάλι;", - "potentialFee": "Πιθανό κόστος: {fee}", - "remote_host": "Απομακρυσμένος υπολογιστής", "refill": "Γέμισμα πορτοφολιού", "refill_lnd_balance": "Γέμισε το πορτοφόλι Lightning", + "sameWalletAsInvoiceError": "Δεν μπορείς να εξοφλήσεις ένα τιμολόγιο από το ίδιο πορτοφόλι με το οποίο δημιουργήθηκε.", "title": "Διαχείριση χρημάτων", - "can_send": "Μπορεί να αποσταλεί", - "can_receive": "Μπορεί να ληφθεί" + "potentialFee": "Πιθανή προμήθεια: {fee}", + "refill_create": "Για να συνεχίσετε, παρακαλώ δημιουργήστε ένα Bitcoin πορτοφόλι για να γεμίσετε.", + "refill_external": "Γέμισμα με Εξωτερικό Πορτοφόλι" }, "lndViewInvoice": { "additional_info": "Επιπρόσθετη πληροφορία", "for": "Για:", - "lightning_invoice": "Τιμολόγιο Lightning", + "lightning_invoice": "Τιμολόγιο Lightning", "please_pay": "Παρακαλώ πληρώστε", + "wasnt_paid_and_expired": "Το τιμολόγιο δεν πληρώθηκε και έχει λήξει.", + "please_pay_between_and": "Παρακαλώ πληρώστε μεταξύ {min} και {max}", + "preimage": "Προεικόνα", "sats": "sats.", - "wasnt_paid_and_expired": "Το τιμολόγιο δεν πληρώθηκε και έχει λήξει." + "date_time": "Ημερομηνία και Ώρα" }, "plausibledeniability": { "create_fake_storage": "Δημιούργησε ένα ψεύτικο κρυπτογραφημένο αρχείο", - "create_password": "Δημιουργία ενός κωδικού", "create_password_explanation": "Ο κωδικός του ψεύτικου αρχείου δεν πρέπει να είναι ίδιος με τον κωδικό του πραγματικού αρχείου", "help": "Μπορεί κάποια στιγμή να υποχρεωθείτε να αποκαλύψετε τον κωδικό σας. Για να προστατέψετε τα χρήματά σας, το BlueWallet μπορεί να δημιουργήσει ένα εναλλακτικό κρυπτογραφημένο αρχείο με διαφορετικό κωδικό. Εάν σας υποχρεώσουν, μπορείτε να αποκαλύψετε αυτόν τον δεύτερο κωδικό. Κάποιος που θα τον βάλει στο BlueWallet θα δει ένα μόνο ένα ψεύτικο αρχείο που μοιάζει κανονικό, προστατεύοντας έτσι το κανονικό σας αρχείο και τα χρήματά σας.", "help2": "Το νέο αρχείο θα είναι πλήρως λειτουργικό, και μπορείτε να βάλετε εκεί κάποια ελάχιστα χρήματα για να μοιάζει αληθινό.", "password_should_not_match": "Ο κωδικός του ψεύτικου αρχείου δεν πρέπει να είναι ίδιος με τον κωδικό του πραγματικού αρχείου", - "passwords_do_not_match": "Οι κωδικοί δεν είναι ίδιοι, δοκίμασε ξανά", - "retype_password": "Ξαναδώσε τον κωδικό", - "success": "Επιτυχία", "title": "Εύλογη δυνατότητα άρνησης" }, "pleasebackup": { "ask": "Έχετε αποθηκεύσει αντίγραφο ασφαλείας της φράσης του πορτοφολιού; Αυτό το αντίγραφο ασφαλείας απαιτείται για να έχετε πρόσβαση στα κεφάλαια σας στην περίπτωση απώλειας της συσκευής σας. Χωρίς το αντίγραφο ασφαλείας της φράσης, τα κεφάλαια σας θα χαθούν οριστικά.", - "ask_no": "Όχι, δεν το έκανα", - "ask_yes": "Ναι, το έκανα", - "ok": "Εντάξει το έγραψα σε χαρτί", - "ok_lnd": "Εντάξει το αποθήκευσα", + "ask_no": "Όχι, δεν έχω.", + "ask_yes": "Ναι, έχω.", + "ok": "Εντάξει, το έγραψα.", + "ok_lnd": "Εντάξει, το αποθήκευσα.", "text": "Παρακαλώ αφιερώστε λίγο χρόνο να γράψετε σε ένα κομμάτι χαρτί την φράση απομνημόνευσης.\nΕίναι το αντίγραφο ασφαλείας σας και μπορείτε να την χρησιμοποιήσετε για να ανακτήσετε το πορτοφόλι σας.", - "title": "Το πορτοφόλι σας δημιουργήθηκε" + "title": "Το πορτοφόλι σας δημιουργήθηκε.", + "text_lnd": "Παρακαλώ αποθηκεύστε αυτό το αντίγραφο ασφαλείας του πορτοφολιού. Σας επιτρέπει να επαναφέρετε το πορτοφόλι σε περίπτωση απώλειας." }, "receive": { "details_create": "Δημιουργία", "details_label": "Περιγραφή", "details_setAmount": "Λήψη με ποσό", - "details_share": "Δώσε", "header": "Λήψη", "maxSats": "Το μέγιστο ποσό είναι {max} sats", "maxSatsFull": "Το μέγιστο ποσό είναι {max} sats ή {currency}", "minSats": "Το ελάχιστο ποσό είναι {min} sats", - "minSatsFull": "Το ελάχιστο ποσό είναι {min} sats ή {currency}" + "minSatsFull": "Το ελάχιστο ποσό είναι {min} sats ή {currency}", + "details_share": "Κοινοποίηση...", + "address_not_found": "Αδυναμία δημιουργίας διεύθυνσης λήψης.", + "reset": "Επαναφορά", + "qrcode_for_the_address": "Κωδικός QR για τη διεύθυνση", + "bip47_explanation": "Οι κωδικοί πληρωμής είναι μια καθολική διεύθυνση που αποφεύγει την αποκάλυψη των διευθύνσεων του πορτοφολιού σας. Δεν θα τους υποστηρίζουν όλες οι υπηρεσίες." }, "send": { "broadcastButton": "Μετάδοση", @@ -109,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 και είναι έτοιμη για αποστολή στο δίκτυο.", @@ -152,113 +165,254 @@ "input_paste": "Επικόλληση", "input_total": "Σύνολο:", "permission_camera_message": "Χρειαζόμαστε την άδεια σου για την χρήση της κάμερας.", + "psbt_sign": "Υπογραφή μιας συναλλαγής", "open_settings": "Άνοιγμα ρυθμίσεων", - "permission_storage_later": "Ρώτησε με αργότερα", - "permission_storage_message": "Το BlueWallet θέλει άδεια πρόσβασης στον αποθηκευτικό σας χώρο για να σώσει αυτό το αρχείο.", "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": "Εισαγωγή επαφής", + "details_add_recc_rem_all_alert_description": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε όλους τους παραλήπτες;", + "details_add_rec_rem_all": "Αφαίρεση όλων των παραληπτών", + "details_recipients_title": "Παραλήπτες", + "details_recipient_title": "Παραλήπτης #{number} από #{total}", + "please_complete_recipient_title": "Μη ολοκληρωμένος παραλήπτης", + "please_complete_recipient_details": "Παρακαλώ συμπληρώστε τα στοιχεία του παραλήπτη #{number} πριν προσθέσετε νέο παραλήπτη.", + "details_adv_fee_bump": "Επίτρεψε αύξηση προμήθειας", + "details_adv_full_sure": "Είστε σίγουροι ότι θέλετε να χρησιμοποιήσετε όλο το υπόλοιπο του πορτοφολιού σας για αυτή τη συναλλαγή;", + "details_adv_full_sure_frozen": "Είστε σίγουροι ότι θέλετε να χρησιμοποιήσετε όλο το υπόλοιπο του πορτοφολιού σας για αυτή τη συναλλαγή; Σημειώστε ότι τα παγωμένα νομίσματα εξαιρούνται.", + "details_frozen": "{amount} BTC είναι παγωμένα.", + "details_no_signed_tx": "Το επιλεγμένο αρχείο δεν περιέχει συναλλαγή που μπορεί να εισαχθεί.", + "details_scan_hint": "Διπλό πάτημα για σάρωση ή εισαγωγή προορισμού", + "details_scan_error": "Σφάλμα σάρωσης", + "details_total_exceeds_balance_frozen": "Το ποσό αποστολής υπερβαίνει το διαθέσιμο υπόλοιπο. Σημειώστε ότι τα παγωμένα νομίσματα εξαιρούνται.", + "dynamic_init": "Αρχικοποίηση", + "insert_custom_fee": "Εισαγωγή προμήθειας", + "fee_replace_minvb": "Ο συνολικός ρυθμός προμήθειας (satoshi ανά vByte) που θέλετε να πληρώσετε πρέπει να είναι υψηλότερος από {min} sat/vByte.", + "invalid_psbt": "Μη έγκυρο PSBT.", + "psbt_this_is_psbt": "Αυτή είναι μια Μερικώς Υπογεγραμμένη Συναλλαγή Bitcoin (PSBT). Παρακαλώ ολοκληρώστε την υπογραφή με το hardware πορτοφόλι σας.", + "no_tx_signing_in_progress": "Δεν υπάρχει συναλλαγή σε διαδικασία υπογραφής.", + "outdated_rate": "Η τιμή ενημερώθηκε τελευταία φορά: {date}", + "psbt_tx_open": "Άνοιγμα υπογεγραμμένης συναλλαγής", + "psbt_tx_scan": "Σάρωση υπογεγραμμένης συναλλαγής", + "qr_error_no_qrcode": "Δεν μπορέσαμε να βρούμε έναν έγκυρο κωδικό QR στην επιλεγμένη εικόνα. Βεβαιωθείτε ότι η εικόνα περιέχει μόνο έναν κωδικό QR και όχι επιπλέον περιεχόμενο όπως κείμενο ή κουμπιά.", + "reset_amount": "Επαναφορά ποσού", + "reset_amount_confirm": "Θέλετε να επαναφέρετε το ποσό;", + "txSaved": "Το αρχείο συναλλαγής ({filePath}) έχει αποθηκευτεί.", + "file_saved_at_path": "Το αρχείο ({filePath}) έχει αποθηκευτεί.", + "cant_send_to_silentpayment_adress": "Αυτό το πορτοφόλι δεν μπορεί να στείλει σε διευθύνσεις SilentPayment", + "cant_send_to_bip47": "Αυτό το πορτοφόλι δεν μπορεί να στείλει σε κωδικούς πληρωμής BIP47", + "cant_find_bip47_notification": "Προσθέστε πρώτα αυτόν τον κωδικό πληρωμής στις επαφές" }, "settings": { "about": "Σχετικά", "about_license": "Άδεια χρήσης MIT", "about_release_notes": "Σημειώσεις κυκλοφορίας.", "about_review": "Γράψτε μια κριτική", + "performance_score": "Σκορ επιδόσεων: {num}", "run_performance_test": "Δοκιμή επιδόσεων", "about_selftest": "Εκτέλεση διαγνωστικού ελέγχου", "about_sm_github": "GitHub", - "about_sm_discord": "Διακομιστής Discord", "about_sm_telegram": "Κανάλι Telegram", - "about_sm_twitter": "Ακολουθήστε μας στο Twitter", - "advanced_options": "Ειδικές επιλογές", "biometrics": "Βιομετρικά", - "biom_conf_identity": "Παρακαλώ επιβεβαιώστε την ταυτότητα σας.", + "biom_conf_identity": "Παρακαλώ επιβεβαιώστε την ταυτότητά σας.", "currency": "Νόμισμα", - "currency_source": "Η τιμή προέρχεται από", - "default_wallets": "Προβολή όλων των πορτοφολιών", + "default_title": "Στην έναρξη", "electrum_connected": "Σε σύνδεση", "electrum_connected_not": "Εκτός σύνδεσης", "lndhub_uri": "Π.χ., {example}", "electrum_host": "Π.χ., {example}", "electrum_offline_mode": "Κατάσταση εκτός σύνδεσης", + "electrum_port": "Θύρα, συνήθως {example}", "use_ssl": "Χρήση SSL", "electrum_settings_server": "Διακομιστής Electrum", "electrum_status": "Κατάσταση", - "electrum_clear_alert_title": "Καθαρισμός ιστορικού;", - "electrum_clear_alert_cancel": "Ακύρωση", - "electrum_clear_alert_ok": "Εντάξει", - "electrum_select": "Επιλογή", - "electrum_history": "Ιστορικό διακομιστή", - "electrum_clear": "Καθαρισμός", - "tor_supported": "Υποστήριξη Tor", + "electrum_unable_to_connect": "Αδυναμία σύνδεσης στον {server}.", + "electrum_reset": "Επαναφορά προεπιλογής", "encrypt_decrypt": "Αποκρυπτογράφηση αποθηκευτικού χώρου", "encrypt_title": "Ασφάλεια", "encrypt_use": "Χρήση {type}", "general": "Γενικά", - "general_adv_mode": "Enable advanced mode", "header": "Ρυθμίσεις", "language": "Γλώσσα", "last_updated": "Τελευταία ενημέρωση", - "lightning_error_lndhub_uri": "Μη έγκυρο LNDHub URI", - "lightning_saved": "Η αλλαγές σας αποθηκεύτηκαν με επιτυχία.", + "license": "Άδεια χρήσης", + "lightning_saved": "Οι αλλαγές σας αποθηκεύτηκαν με επιτυχία.", "lightning_settings": "Ρυθμίσεις Lightning", - "tor_settings": "Ρυθμίσεις Tor", "network": "Δίκτυο", "network_broadcast": "Μετάδοση συναλλαγής", "network_electrum": "Διακομιστής Electrum", "not_a_valid_uri": "Μη έγκυρο URI", "notifications": "Ειδοποιήσεις", "password": "Κωδικός", - "password_explain": "Δώσε ένα κωδικό για την κρυπτογράφηση του αρχείου", - "passwords_do_not_match": "Οι κωδικοί δεν είναι ίδιοι", "plausible_deniability": "Εύλογη δυνατότητα άρνησης...", "privacy": "Ιδιωτικότητα", "privacy_read_clipboard": "Ανάγνωση προχείρου", "privacy_system_settings": "Ρυθμίσεις συστήματος", - "retype_password": "Ξαναδώσε τον κωδικό", + "privacy_do_not_track": "Απενεργοποίηση ανάλυσης", "selfTest": "Διαγνωστικός έλεγχος", "save": "Σώσε", "saved": "Αποθηκεύτηκε", - "success_transaction_broadcasted": "Επιτυχία! η συναλλαγής σας μεταδόθηκε", "total_balance": "Συνολικό υπόλοιπο", - "tools": "Εργαλεία" + "tools": "Εργαλεία", + "about_awesome": "Φτιαγμένο με το εξαιρετικό", + "about_backup": "Πάντα να κρατάτε αντίγραφο ασφαλείας των κλειδιών σας!", + "about_free": "Το BlueWallet είναι ένα ελεύθερο και ανοιχτού κώδικα έργο. Φτιαγμένο από χρήστες Bitcoin.", + "block_explorer_invalid_custom_url": "Η παρεχόμενη διεύθυνση URL δεν είναι έγκυρη. Παρακαλώ εισάγετε μια έγκυρη διεύθυνση URL που να ξεκινά με http:// ή https://.", + "about_selftest_electrum_disabled": "Ο διαγνωστικός έλεγχος δεν είναι διαθέσιμος με την κατάσταση εκτός σύνδεσης Electrum. Παρακαλώ απενεργοποιήστε την κατάσταση εκτός σύνδεσης και δοκιμάστε ξανά.", + "about_selftest_ok": "Όλοι οι εσωτερικοί έλεγχοι ολοκληρώθηκαν με επιτυχία. Το πορτοφόλι λειτουργεί καλά.", + "privacy_temporary_screenshots": "Επιτρέψτε την λήψη οθόνης", + "privacy_temporary_screenshots_instructions": "Η προστασία λήψης οθόνης θα απενεργοποιηθεί προσωρινά, επιτρέποντας στιγμιότυπα οθόνης και εγγραφές οθόνης. Η προστασία θα ενεργοποιηθεί ξανά αυτόματα όταν κλείσετε και ανοίξετε ξανά το BlueWallet.", + "biometrics_no_longer_available": "Οι ρυθμίσεις της συσκευής σας έχουν αλλάξει και δεν ταιριάζουν πλέον με τις επιλεγμένες ρυθμίσεις ασφαλείας της εφαρμογής. Παρακαλώ ενεργοποιήστε ξανά τα βιομετρικά ή τον κωδικό πρόσβασης και επανεκκινήστε την εφαρμογή για να εφαρμοστούν οι αλλαγές.", + "biom_10times": "Έχετε προσπαθήσει να εισάγετε τον κωδικό σας 10 φορές. Θέλετε να επαναφέρετε τον αποθηκευτικό σας χώρο; Αυτό θα αφαιρέσει όλα τα πορτοφόλια και θα αποκρυπτογραφήσει τον αποθηκευτικό σας χώρο.", + "biom_no_passcode": "Η συσκευή σας δεν έχει ενεργοποιημένο κωδικό πρόσβασης ή βιομετρικά. Για να συνεχίσετε, παρακαλώ ρυθμίστε έναν κωδικό πρόσβασης ή βιομετρικά στην εφαρμογή Ρυθμίσεις.", + "biom_remove_decrypt": "Όλα τα πορτοφόλια σας θα αφαιρεθούν και ο αποθηκευτικός σας χώρος θα αποκρυπτογραφηθεί. Είστε σίγουροι ότι θέλετε να προχωρήσετε;", + "currency_source": "Η τιμή λαμβάνεται από", + "currency_fetch_error": "Παρουσιάστηκε σφάλμα κατά τη λήψη της τιμής για το επιλεγμένο νόμισμα.", + "donate": "Δωρεά", + "donate_description": "Βοηθήστε μας να κρατήσουμε το Blue δωρεάν!", + "electrum_error_connect": "Αδυναμία σύνδεσης με τον παρεχόμενο διακομιστή Electrum", + "electrum_error_connect_tor": "Αδυναμία σύνδεσης με τον παρεχόμενο διακομιστή Electrum. Παρακαλώ βεβαιωθείτε ότι η εφαρμογή Orbot είναι συνδεδεμένη και δοκιμάστε ξανά.", + "electrum_offline_description": "Όταν είναι ενεργοποιημένη, τα πορτοφόλια σας Bitcoin δεν θα προσπαθήσουν να ανακτήσουν υπόλοιπα ή συναλλαγές.", + "electrum_saved": "Οι αλλαγές σας αποθηκεύτηκαν με επιτυχία. Μπορεί να απαιτείται επανεκκίνηση του BlueWallet για να τεθούν σε ισχύ οι αλλαγές.", + "set_electrum_server_as_default": "Ορισμός του {server} ως προεπιλεγμένο διακομιστή Electrum;", + "set_lndhub_as_default": "Ορισμός του {url} ως προεπιλεγμένο διακομιστή LNDhub;", + "electrum_preferred_server": "Προτιμώμενος διακομιστής", + "electrum_preferred_server_description": "Εισάγετε τον διακομιστή που θέλετε να χρησιμοποιεί το πορτοφόλι σας για όλες τις δραστηριότητες Bitcoin. Μόλις οριστεί, το πορτοφόλι σας θα χρησιμοποιεί αποκλειστικά αυτόν τον διακομιστή για τον έλεγχο υπολοίπων, την αποστολή συναλλαγών και τη λήψη δεδομένων δικτύου. Βεβαιωθείτε ότι εμπιστεύεστε αυτόν τον διακομιστή πριν τον ορίσετε.", + "electrum_history": "Ιστορικό", + "electrum_reset_to_default": "Αυτό θα επιτρέψει στο BlueWallet να επιλέξει τυχαία έναν διακομιστή από τη λίστα διακομιστών.", + "electrum_reset_to_default_and_clear_history": "Επαναφορά προεπιλογής και εκκαθάριση ιστορικού", + "encrypt_decrypt_q": "Είστε σίγουροι ότι θέλετε να αποκρυπτογραφήσετε τον αποθηκευτικό σας χώρο; Αυτό θα επιτρέψει την πρόσβαση στα πορτοφόλια σας χωρίς κωδικό πρόσβασης.", + "encrypt_enc_and_pass": "Προστατευμένο με κωδικό πρόσβασης", + "encrypt_storage_explanation_headline": "Ενεργοποίηση κρυπτογραφημένης αποθήκευσης", + "encrypt_storage_explanation_description_line1": "Η ενεργοποίηση της κρυπτογραφημένης αποθήκευσης προσθέτει ένα επιπλέον επίπεδο προστασίας στην εφαρμογή σας ασφαλίζοντας τον τρόπο με τον οποίο αποθηκεύονται τα δεδομένα σας στη συσκευή σας. Αυτό κάνει πιο δύσκολο για οποιονδήποτε να αποκτήσει πρόσβαση στις πληροφορίες σας χωρίς άδεια.", + "encrypt_storage_explanation_description_line2": "Ωστόσο, είναι σημαντικό να γνωρίζετε ότι αυτή η κρυπτογράφηση προστατεύει μόνο την πρόσβαση στα πορτοφόλια σας που αποθηκεύονται στην keychain της συσκευής. Δεν βάζει κωδικό πρόσβασης ή κάποια επιπλέον προστασία στα ίδια τα πορτοφόλια.", + "i_understand": "Καταλαβαίνω", + "block_explorer": "Εξερευνητής μπλοκ", + "block_explorer_preferred": "Χρήση προτιμώμενου εξερευνητή μπλοκ", + "block_explorer_error_saving_custom": "Σφάλμα κατά την αποθήκευση προτιμώμενου εξερευνητή μπλοκ", + "encrypt_tstorage": "Αποθηκευτικός χώρος", + "set_as_preferred": "Ορισμός ως προτιμώμενο", + "set_as_preferred_electrum": "Ο ορισμός του {host}:{port} ως προτιμώμενου διακομιστή θα απενεργοποιήσει τη σύνδεση σε προτεινόμενο διακομιστή τυχαία.", + "encrypted_feature_disabled": "Αυτή η λειτουργία δεν μπορεί να χρησιμοποιηθεί με ενεργοποιημένη την κρυπτογραφημένη αποθήκευση.", + "encrypt_use_expl": "Το {type} θα χρησιμοποιηθεί για να επιβεβαιώσει την ταυτότητά σας πριν την πραγματοποίηση συναλλαγής, το ξεκλείδωμα, την εξαγωγή ή τη διαγραφή ενός πορτοφολιού.", + "biometrics_fail": "Εάν το {type} δεν είναι ενεργοποιημένο ή αποτύχει να ξεκλειδώσει, μπορείτε να χρησιμοποιήσετε τον κωδικό πρόσβασης της συσκευής σας ως εναλλακτική.", + "general_continuity": "Continuity", + "general_continuity_e": "Όταν είναι ενεργοποιημένο, θα μπορείτε να βλέπετε επιλεγμένα πορτοφόλια και συναλλαγές χρησιμοποιώντας τις άλλες συσκευές Apple iCloud που είναι συνδεδεμένες.", + "groundcontrol_explanation": "Το GroundControl είναι ένας δωρεάν, ανοιχτού κώδικα διακομιστής push notifications για πορτοφόλια Bitcoin. Μπορείτε να εγκαταστήσετε τον δικό σας διακομιστή GroundControl και να βάλετε τη διεύθυνση URL του εδώ ώστε να μην βασίζεστε στην υποδομή του BlueWallet. Αφήστε κενό για να χρησιμοποιήσετε τον προεπιλεγμένο διακομιστή του GroundControl.", + "language_isRTL": "Απαιτείται επανεκκίνηση του BlueWallet για να τεθεί σε ισχύ ο προσανατολισμός της γλώσσας.", + "lightning_error_lndhub_uri": "Μη έγκυρο LNDhub URI", + "lightning_error_lndhub_uri_tor": "Μη έγκυρο LNDhub URI. Παρακαλώ βεβαιωθείτε ότι η εφαρμογή Orbot είναι συνδεδεμένη και δοκιμάστε ξανά.", + "lightning_settings_explain": "Για να συνδεθείτε στον δικό σας LND κόμβο, παρακαλώ εγκαταστήστε το LNDhub και βάλτε τη διεύθυνση URL του εδώ στις ρυθμίσεις. Σημειώστε ότι μόνο τα πορτοφόλια που δημιουργούνται μετά την αποθήκευση των αλλαγών θα συνδέονται στο καθορισμένο LNDhub.", + "lndhub_github": "Αποθετήριο GitHub", + "electrum_suggested_description": "Όταν δεν έχει οριστεί προτιμώμενος διακομιστής, ένας προτεινόμενος διακομιστής θα επιλέγεται για χρήση τυχαία.", + "open_link_in_explorer": "Άνοιγμα συνδέσμου στον εξερευνητή", + "password_explain": "Εισάγετε τον κωδικό που θα χρησιμοποιείτε για να ξεκλειδώνετε τον αποθηκευτικό σας χώρο.", + "privacy_quickactions": "Συντομεύσεις πορτοφολιού", + "privacy_quickactions_explanation": "Αγγίξτε και κρατήστε το εικονίδιο της εφαρμογής BlueWallet για να δείτε γρήγορα το υπόλοιπο του πορτοφολιού σας.", + "privacy_clipboard_explanation": "Παρέχει συντομεύσεις εάν βρεθεί μια διεύθυνση ή ένα τιμολόγιο στο πρόχειρό σας.", + "privacy_do_not_track_explanation": "Πληροφορίες απόδοσης και αξιοπιστίας δεν θα υποβάλλονται για ανάλυση.", + "rate": "Τιμή", + "push_notifications_explanation": "Με την ενεργοποίηση των ειδοποιήσεων, το token της συσκευής σας θα σταλεί στον διακομιστή, μαζί με τις διευθύνσεις πορτοφολιών και τα IDs συναλλαγών για όλα τα πορτοφόλια και τις συναλλαγές που πραγματοποιούνται μετά την ενεργοποίηση των ειδοποιήσεων. Το token της συσκευής χρησιμοποιείται για την αποστολή ειδοποιήσεων, και οι πληροφορίες πορτοφολιού μας επιτρέπουν να σας ειδοποιούμε για εισερχόμενα Bitcoin ή επιβεβαιώσεις συναλλαγών.\n\nΜόνο πληροφορίες από τη στιγμή που ενεργοποιείτε τις ειδοποιήσεις μεταδίδονται—τίποτα πριν από αυτό δεν συλλέγεται.\n\nΗ απενεργοποίηση των ειδοποιήσεων θα αφαιρέσει όλες αυτές τις πληροφορίες από τον διακομιστή. Επιπλέον, η διαγραφή ενός πορτοφολιού από την εφαρμογή θα αφαιρέσει επίσης τις σχετικές πληροφορίες από τον διακομιστή.", + "success_transaction_broadcasted": "Η συναλλαγή σας μεταδόθηκε με επιτυχία!", + "total_balance_explanation": "Εμφάνιση του συνολικού υπολοίπου όλων των πορτοφολιών σας στα widgets της αρχικής οθόνης.", + "widgets": "Widgets" }, "notifications": { - "no_and_dont_ask": "Όχι, να μην ερωτηθώ ξανά", - "ask_me_later": "Ρώτησε με αργότερα" + "would_you_like_to_receive_notifications": "Θέλετε να λαμβάνετε ειδοποιήσεις όταν έχετε εισερχόμενες πληρωμές;", + "notifications_subtitle": "Εισερχόμενες πληρωμές και επιβεβαιώσεις συναλλαγών", + "no_and_dont_ask": "Όχι, και μην με ξαναρωτήσεις.", + "permission_denied_message": "Έχετε αρνηθεί την άδεια αποστολής ειδοποιήσεων. Εάν θέλετε να λαμβάνετε ειδοποιήσεις, παρακαλώ ενεργοποιήστε τις στις ρυθμίσεις της συσκευής σας." }, "transactions": { - "copy_link": "Αντιγραφή συνδέσμου", + "cancel_no": "Αυτή η συναλλαγή δεν μπορεί να αντικατασταθεί.", + "confirmations_lowercase": "{confirmations} επιβεβαιώσεις", "expand_note": "Επέκταση σημείωσης", "cpfp_create": "Δημιουργία", "details_balance_hide": "Απόκρυψη υπολοίπου", "details_balance_show": "Εμφάνιση υπολοίπου", "details_copy": "Αντέγραψε", - "details_copy_amount": "Αντιγραφή ποσού", "details_copy_note": "Αντιγραφή σημείωσης", - "details_from": "Εισερχόμενες διευθύνσεις", "date": "Ημερομηνία", - "details_show_in_block_explorer": "Προβολή στον block explorer", + "details_received": "Ελήφθη", "details_title": "Συναλλαγή", "details_to": "Εξερχόμενες διευθύνσεις", "pending": "Σε επεξεργασία", "received_with_amount": "+{amt1} ({amt2})", - "view_wallet": "Προβολή {walletLabel}", "list_title": "Συναλλαγές", - "status_cancel": "Ακύρωση συναλλαγής" + "list_title_received": "Ελήφθη", + "transaction": "Συναλλαγή", + "status_cancel": "Ακύρωση συναλλαγής", + "txid": "ID συναλλαγής", + "cancel_explain": "Θα αντικαταστήσουμε αυτή τη συναλλαγή με μία που πληρώνει εσάς και έχει υψηλότερη προμήθεια. Αυτό ουσιαστικά ακυρώνει την τρέχουσα συναλλαγή. Αυτό λέγεται RBF—Replace by Fee.", + "cancel_title": "Ακύρωση αυτής της συναλλαγής (RBF)", + "transaction_loading_error": "Παρουσιάστηκε πρόβλημα κατά τη φόρτωση της συναλλαγής. Παρακαλώ δοκιμάστε ξανά αργότερα.", + "transaction_not_available": "Η συναλλαγή δεν είναι διαθέσιμη", + "cpfp_exp": "Θα δημιουργήσουμε μια άλλη συναλλαγή που ξοδεύει την ανεπιβεβαίωτη συναλλαγή σας. Η συνολική προμήθεια θα είναι υψηλότερη από την αρχική προμήθεια της συναλλαγής, οπότε θα πρέπει να εξορυχθεί γρηγορότερα. Αυτό λέγεται CPFP—Child Pays for Parent.", + "cpfp_no_bump": "Αυτή η συναλλαγή δεν μπορεί να αυξηθεί.", + "cpfp_title": "Αύξηση προμήθειας (CPFP)", + "details_copy_block_explorer_link": "Αντιγραφή συνδέσμου εξερευνητή μπλοκ", + "details_copy_txid": "Αντιγραφή ID συναλλαγής", + "details_inputs": "Είσοδοι", + "details_outputs": "Έξοδοι", + "details_view_in_browser": "Προβολή στον περιηγητή", + "incoming_transaction": "Εισερχόμενη συναλλαγή", + "outgoing_transaction": "Εξερχόμενη συναλλαγή", + "expired_transaction": "Ληγμένη συναλλαγή", + "pending_transaction": "Εκκρεμής συναλλαγή", + "offchain": "off-chain", + "onchain": "on-chain", + "enable_offline_signing": "Αυτό το πορτοφόλι δεν χρησιμοποιείται σε συνδυασμό με υπογραφή εκτός σύνδεσης. Θα θέλατε να την ενεργοποιήσετε τώρα;", + "list_conf": "Επιβ: {number}", + "pending_with_amount": "Σε εκκρεμότητα {amt1} ({amt2})", + "eta_10m": "ΕΧΩΑ: Σε ~10 λεπτά", + "eta_3h": "ΕΧΩΑ: Σε ~3 ώρες", + "eta_1d": "ΕΧΩΑ: Σε ~1 ημέρα", + "list_title_sent": "Στάλθηκε", + "open_url_error": "Αδυναμία ανοίγματος του συνδέσμου με τον προεπιλεγμένο περιηγητή. Παρακαλώ αλλάξτε τον προεπιλεγμένο περιηγητή σας και δοκιμάστε ξανά.", + "rbf_explain": "Θα αντικαταστήσουμε αυτή τη συναλλαγή με μία με υψηλότερη προμήθεια ώστε να εξορυχθεί γρηγορότερα. Αυτό λέγεται RBF—Replace by Fee.", + "rbf_title": "Επιτάχυνση (RBF)", + "status_bump": "Επιτάχυνση", + "transactions_count": "Πλήθος συναλλαγών", + "updating": "Ενημέρωση...", + "watchOnlyWarningTitle": "Προειδοποίηση ασφαλείας", + "watchOnlyWarningDescription": "Να είστε προσεκτικοί με τους απατεώνες που συχνά χρησιμοποιούν πορτοφόλια “μόνο για παρακολούθηση” για να εξαπατήσουν χρήστες. Αυτά τα πορτοφόλια δεν σας επιτρέπουν να ελέγχετε ή να στέλνετε κεφάλαια· σας επιτρέπουν μόνο να βλέπετε το υπόλοιπο.", + "custom_fee_warning_title": "Προειδοποίηση", + "custom_fee_warning_description": "Προμήθειες κάτω από 1 sat/vB είναι έγκυρες, αλλά μπορεί να μην μεταδοθούν λόγω πολιτικών των κόμβων.", + "details_eta_analyzing": "Ανάλυση...", + "details_sent": "Στάλθηκε", + "details_section": "Λεπτομέρειες", + "details_explorer": "εξερευνητής", + "details_network_fee": "Προμήθεια δικτύου", + "details_to_address": "Προς", + "details_id": "ID", + "details_note": "Σημείωση", + "details_add_note": "προσθήκη", + "details_advanced": "Για προχωρημένους", + "details_fee_rate": "Ρυθμός προμήθειας", + "details_size": "Μέγεθος", + "details_virtual_size": "Εικονικό μέγεθος", + "details_tx_hex": "Hex συναλλαγής", + "details_inputs_count": "Είσοδοι ({count})", + "details_outputs_count": "Έξοδοι ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Απλό και ισχυρό πορτοφόλι Bitcoin", "add_create": "Δημιουργία", + "total_balance": "Συνολικό υπόλοιπο", + "add_entropy": "Εντροπία", "add_import_wallet": "Εισαγωγή πορτοφολιού", "add_lightning": "Lightning", + "add_lndhub_placeholder": "Η διεύθυνση του κόμβου σας", + "add_placeholder": "το πρώτο μου πορτοφόλι", "add_title": "Προσθήκη πορτοφολιού", "add_wallet_name": "Όνομα", "add_wallet_type": "Τύπος", - "balance": "Υπόλοιπο", "details_address": "Διεύθυνση", "details_advanced": "Για προχωρημένους", "details_are_you_sure": "Είσαι σίγουρος;", @@ -266,65 +420,237 @@ "details_delete": "Διαγραφή", "details_delete_wallet": "Διαγραφή πορτοφολιού", "details_export_backup": "Εξήγαγε / δημιούργησε αντίγραφο ασφαλείας", - "details_no_cancel": "Όχι, ακύρωσε", - "details_save": "Αποθήκευση", "details_show_xpub": "Προβολή XPUB του πορτοφολιού", "details_show_addresses": "Εμφάνιση διευθύνσεων", "details_title": "Πορτοφόλι", + "wallets": "Πορτοφόλια", "details_type": "Τύπος", "details_use_with_hardware_wallet": "Χρήση με hardware πορτοφόλι", - "details_wallet_updated": "Το πορτοφόλι ενημερώθηκε", "details_yes_delete": "Ναι, διαγραφή", "export_title": "Εξαγωγή πορτοφολιού", "import_do_import": "Εισήγαγε", + "import_passphrase": "Φράση κωδικός", + "import_passphrase_title": "Φράση κωδικός", "import_error": "Η εισαγωγή απέτυχε. Παρακαλούμε σιγουρευτείτε ότι τα δεδομένα που εισάγετε είναι σωστά.", "import_imported": "Εισήχθη", "import_scan_qr": "ή θέλεις θα σκανάρεις ένα QR code;", "import_success": "Επιτυχία εισαγωγής του πορτοφολιού σας", "import_search_accounts": "Αναζήτηση λογαριασμών", "import_title": "Εισαγωγή", - "import_derivation_found": "βρέθηκε", - "import_derivation_found_not": "δεν βρέθηκε", - "import_derivation_unknown": "άγνωστο", + "import_discovery_no_wallets": "Δεν βρέθηκε κανένα πορτοφόλι", + "import_derivation_found": "Βρέθηκε", + "import_derivation_found_not": "Δεν βρέθηκε", + "import_derivation_loading": "Φορτώνεται...", + "import_derivation_unknown": "Άγνωστο", "list_create_a_button": "Προσθήκη τώρα", "list_create_a_wallet": "Προσθέστε ένα πορτοφόλι", "list_empty_txs1": "Οι συναλλαγές θα εμφανιστούν εδώ,", "list_latest_transaction": "Τελευταία συναλλαγή", "list_long_choose": "Επιλογή φωτογραφίας", - "list_long_clipboard": "Αντιγραφή από το πρόχειρο", + "paste_from_clipboard": "Επικόλληση", + "list_long_scan": "Σάρωση QR Code", "list_title": "Πορτοφόλια", "list_tryagain": "Προσπαθήστε ξανά", - "reorder_title": "Αναδιάταξη των πορτοφολιών", "select_wallet": "Επιλογή πορτοφολιού", - "xpub_copiedToClipboard": "Αντιγράφηκε στο πρόχειρο", - "xpub_title": "XPUB του πορτοφολιού" + "xpub_title": "XPUB του πορτοφολιού", + "add_entropy_reset_title": "Επαναφορά εντροπίας", + "add_entropy_reset_message": "Η αλλαγή του τύπου πορτοφολιού θα επαναφέρει την τρέχουσα εντροπία. Θέλετε να προχωρήσετε;", + "add_entropy_bytes": "{bytes} bytes εντροπίας", + "add_entropy_generated": "{gen} bytes παραγόμενης εντροπίας", + "add_entropy_provide": "Παροχή εντροπίας μέσω ζαριών", + "add_entropy_remain": "{gen} bytes παραγόμενης εντροπίας. Τα υπόλοιπα {rem} bytes θα ληφθούν από τη γεννήτρια τυχαίων αριθμών του συστήματος.", + "add_lightning_explain": "Για ξόδεμα με άμεσες συναλλαγές", + "add_lndhub": "Σύνδεση στο LNDhub σας", + "add_lndhub_error": "Η παρεχόμενη διεύθυνση κόμβου είναι μη έγκυρος κόμβος LNDhub.", + "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": "Εκκαθάριση προχείρου κατά την εισαγωγή", + "details_del_wb_err": "Το παρεχόμενο ποσό υπολοίπου δεν ταιριάζει με το υπόλοιπο αυτού του πορτοφολιού. Παρακαλώ δοκιμάστε ξανά.", + "details_del_wb_q": "Αυτό το πορτοφόλι έχει υπόλοιπο. Πριν προχωρήσετε, παρακαλώ έχετε υπόψη ότι δεν θα μπορέσετε να ανακτήσετε τα κεφάλαια χωρίς τη μνημονική φράση αυτού του πορτοφολιού. Για να αποφύγετε την κατά λάθος αφαίρεση, παρακαλώ εισάγετε το υπόλοιπο του πορτοφολιού σας {balance} satoshis.", + "details_derivation_path": "μονοπάτι παραγωγής", + "details_display": "Εμφάνιση στην αρχική οθόνη", + "details_export_history": "Εξαγωγή ιστορικού σε CSV", + "details_master_fingerprint": "Αποτύπωμα κύριου κλειδιού", + "details_multisig_type": "multisig", + "swipe_balance_hide": "Απόκρυψη", + "swipe_balance_show": "Εμφάνιση", + "drag_to_reorder": "Σύρετε για αναδιάταξη", + "clear_search": "Εκκαθάριση αναζήτησης", + "enter_bip38_password": "Εισάγετε κωδικό για αποκρυπτογράφηση", + "import_passphrase_message": "Εισάγετε κωδική φράση εάν έχετε χρησιμοποιήσει κάποια", + "import_explanation": "Παρακαλώ εισάγετε τις λέξεις της μνημονικής φράσης σας, το δημόσιο κλειδί, το WIF ή οτιδήποτε έχετε. Το BlueWallet θα κάνει ό,τι μπορεί για να μαντέψει τη σωστή μορφή και να εισάγει το πορτοφόλι σας.", + "import_success_watchonly": "Το πορτοφόλι σας εισήχθη με επιτυχία. ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό είναι ένα πορτοφόλι μόνο για παρακολούθηση, ΔΕΝ μπορείτε να ξοδεύετε από αυτό.", + "learn_more": "Μάθετε περισσότερα", + "import_discovery_title": "Ανακάλυψη", + "import_discovery_subtitle": "Επιλέξτε ένα ανακαλυμμένο πορτοφόλι", + "import_discovery_derivation": "Χρήση προσαρμοσμένου μονοπατιού παραγωγής", + "import_discovery_offline": "Το BlueWallet βρίσκεται αυτή τη στιγμή σε κατάσταση εκτός σύνδεσης. Σε αυτή την κατάσταση, δεν μπορεί να επαληθεύσει την ύπαρξη του πορτοφολιού, οπότε θα πρέπει να επιλέξετε το σωστό χειροκίνητα", + "import_derivation_subtitle": "Εισάγετε προσαρμοσμένο μονοπάτι παραγωγής και θα προσπαθήσουμε να ανακαλύψουμε το πορτοφόλι σας.", + "import_derivation_title": "Μονοπάτι παραγωγής", + "import_wrong_path": "Λάθος μονοπάτι παραγωγής", + "list_create_a_wallet_text": "Είναι δωρεάν, και μπορείτε να δημιουργήσετε \nόσα θέλετε.", + "list_empty_txs1_lightning": "Το πορτοφόλι Lightning πρέπει να χρησιμοποιείται για τις καθημερινές σας συναλλαγές. Οι προμήθειες είναι αδίκως φθηνές και η ταχύτητα εξαιρετικά γρήγορη.", + "list_empty_txs2": "Ξεκινήστε με το πορτοφόλι σας.", + "list_empty_txs2_lightning": "\nΓια να ξεκινήσετε να το χρησιμοποιείτε, πατήστε στη Διαχείριση χρημάτων και γεμίστε το υπόλοιπό σας.", + "import_file": "Εισαγωγή αρχείου", + "no_ln_wallet_error": "Πριν πληρώσετε ένα τιμολόγιο Lightning, πρέπει πρώτα να προσθέσετε ένα πορτοφόλι Lightning.", + "looks_like_bip38": "Αυτό μοιάζει με προστατευμένο με κωδικό ιδιωτικό κλειδί (BIP38).", + "manage_title": "Διαχείριση πορτοφολιών", + "no_results_found": "Δεν βρέθηκαν αποτελέσματα.", + "please_continue_scanning": "Παρακαλώ συνεχίστε τη σάρωση.", + "select_no_bitcoin": "Δεν υπάρχουν διαθέσιμα Bitcoin πορτοφόλια αυτή τη στιγμή.", + "select_no_bitcoin_exp": "Απαιτείται ένα Bitcoin πορτοφόλι για να γεμίσετε πορτοφόλια Lightning. Παρακαλώ δημιουργήστε ή εισάγετε ένα.", + "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": "Αντιγράψτε και αποθηκεύστε με ασφάλεια αυτή τη διεύθυνση URL για να επαναφέρετε το πορτοφόλι σας αργότερα.", + "copy_ln_public": "Αντιγράψτε και αποθηκεύστε με ασφάλεια αυτές τις πληροφορίες για να επαναφέρετε το πορτοφόλι σας αργότερα.", + "add_ln_wallet_first": "Πρέπει πρώτα να προσθέσετε ένα πορτοφόλι Lightning.", + "identity_pubkey": "Identity Pubkey", + "manage_wallets_search_placeholder": "Αναζήτηση πορτοφολιών, διευθύνσεων, συναλλαγών και σημειώσεων", + "more_info": "Περισσότερες πληροφορίες", + "details_delete_wallet_error_message": "Παρουσιάστηκε πρόβλημα κατά την επιβεβαίωση εάν αυτό το πορτοφόλι αφαιρέθηκε από τις ειδοποιήσεις—αυτό μπορεί να οφείλεται σε πρόβλημα δικτύου ή κακή σύνδεση. Εάν συνεχίσετε, μπορεί να συνεχίσετε να λαμβάνετε ειδοποιήσεις για συναλλαγές που σχετίζονται με αυτό το πορτοφόλι, ακόμη και μετά τη διαγραφή του.", + "details_delete_anyway": "Διαγραφή ούτως ή άλλως" + }, + "total_balance_view": { + "title": "Συνολικό υπόλοιπο", + "display_in_bitcoin": "Εμφάνιση σε Bitcoin", + "hide": "Απόκρυψη", + "display_in_sats": "Εμφάνιση σε sats", + "display_in_fiat": "Εμφάνιση σε {currency}", + "explanation": "Δείτε το συνολικό υπόλοιπο όλων των πορτοφολιών σας στην οθόνη επισκόπησης." }, "multisig": { - "multisig_vault": "Θησαυροφυλάκιο", "provide_signature": "Παροχή υπογραφής", + "fee_btc": "{number} BTC", "confirm": "Επιβεβαίωση", "header": "Αποστολή", - "share": "Δώσε", "view": "Προβολή", "manage_keys": "Διαχείριση κλειδιών", + "scan_or_import_file": "Σάρωση ή εισαγωγή αρχείου", "lets_start": "Ας ξεκινήσουμε", "create": "Δημιούργησε", - "wallet_type": "Τύπος πορτοφολιου", + "native_segwit_title": "Καλές πρακτικές", + "co_sign_transaction": "Υπογραφή μιας συναλλαγής", + "what_is_vault_wallet": "πορτοφόλι.", + "wallet_type": "Τύπος πορτοφολιού", "create_new_key": "Δημιουργία νέου", - "ms_help": "Βοήθεια" + "scan_or_open_file": "Σάρωση ή άνοιγμα αρχείου", + "ms_help": "Βοήθεια", + "ms_help_title5": "Enable advanced mode", + "multisig_vault": "Χρηματοκιβώτιο Multisig", + "default_label": "Χρηματοκιβώτιο Multisig", + "multisig_vault_explain": "Καλύτερη ασφάλεια για μεγάλα ποσά", + "provide_signature_details": "Χρησιμοποιήστε τη συσκευή και το πορτοφόλι σας όπου βρίσκεται το κλειδί για να υπογράψετε αυτή τη συναλλαγή", + "provide_signature_details_bluewallet": "Στο BlueWallet, πηγαίνετε στο μενού της οθόνης Αποστολής και επιλέξτε ", + "provide_signature_next_steps": "Σάρωση ή εισαγωγή υπογεγραμμένης συναλλαγής", + "provide_signature_next_steps_details": "Μόλις το πορτοφόλι σας υπογράψει επιτυχώς τη συναλλαγή, σαρώστε τον παρεχόμενο κωδικό QR ή εισάγετε το συνοδευτικό αρχείο και στη συνέχεια ελέγξτε όλες τις λεπτομέρειες της συναλλαγής πριν τη μεταδώσετε.", + "vault_key": "Κλειδί χρηματοκιβωτίου {number}", + "required_keys_out_of_total": "Απαιτούμενα κλειδιά από το σύνολο", + "fee": "Προμήθεια: {number}", + "share": "Κοινοποίηση...", + "shared_key_detected": "Κοινόχρηστος συν-υπογράφων", + "shared_key_detected_question": "Σας μοιράστηκε ένας συν-υπογράφων, θέλετε να τον εισάγετε;", + "how_many_signatures_can_bluewallet_make": "πόσες υπογραφές μπορεί να κάνει το BlueWallet", + "signatures_required_to_spend": "Απαιτούμενες υπογραφές {number}", + "signatures_we_can_make": "μπορεί να κάνει {number}", + "export_coordination_setup": "Εξαγωγή Ρύθμισης Συντονισμού", + "cosign_this_transaction": "Συν-υπογραφή αυτής της συναλλαγής;", + "wrapped_segwit_title": "Καλύτερη συμβατότητα", + "legacy_title": "Legacy", + "what_is_vault": "Ένα Χρηματοκιβώτιο είναι ένα", + "what_is_vault_numberOfWallets": " {m}-από-{n} multisig ", + "vault_advanced_customize": "Ρυθμίσεις χρηματοκιβωτίου", + "needs": "Χρειάζεται", + "what_is_vault_description_number_of_vault_keys": " {m} κλειδιά χρηματοκιβωτίου ", + "what_is_vault_description_to_spend": "για να ξοδέψει και ένα τρίτο που \nμπορείτε να χρησιμοποιήσετε ως αντίγραφο ασφαλείας.", + "what_is_vault_description_to_spend_other": "για να ξοδέψει.", + "quorum": "{m} από {n} απαρτία", + "quorum_header": "Απαρτία", + "of": "από", + "invalid_mnemonics": "Αυτή η μνημονική φράση δεν φαίνεται να είναι έγκυρη.", + "invalid_cosigner": "Μη έγκυρα δεδομένα συν-υπογράφοντος", + "not_a_multisignature_xpub": "Αυτό δεν είναι XPUB από πορτοφόλι multisig!", + "invalid_cosigner_format": "Λανθασμένος συν-υπογράφων: Αυτός δεν είναι συν-υπογράφων για τη μορφή {format}.", + "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_title": "Πώς λειτουργούν τα Χρηματοκιβώτια Multisig: Συμβουλές και κόλπα", + "ms_help_text": "Ένα πορτοφόλι με πολλαπλά κλειδιά, για αυξημένη ασφάλεια ή κοινή φύλαξη", + "ms_help_title1": "Συνιστώνται πολλαπλές συσκευές.", + "ms_help_1": "Το Χρηματοκιβώτιο θα λειτουργεί με άλλες εφαρμογές BlueWallet και πορτοφόλια συμβατά με PSBT, όπως Electrum, Specter, Coldcard, Cobo Vault κ.λπ.", + "ms_help_title2": "Επεξεργασία κλειδιών", + "ms_help_2": "Μπορείτε να δημιουργήσετε όλα τα κλειδιά Χρηματοκιβωτίου σε αυτή τη συσκευή και να τα αφαιρέσετε ή να τα επεξεργαστείτε αργότερα. Έχοντας όλα τα κλειδιά στην ίδια συσκευή έχει την ισοδύναμη ασφάλεια ενός κανονικού πορτοφολιού Bitcoin.", + "ms_help_title3": "Αντίγραφα ασφαλείας Χρηματοκιβωτίου", + "ms_help_3": "Στις επιλογές του πορτοφολιού, θα βρείτε το αντίγραφο ασφαλείας του Χρηματοκιβωτίου σας και το αντίγραφο ασφαλείας μόνο για παρακολούθηση. Αυτό το αντίγραφο ασφαλείας είναι σαν χάρτης για το πορτοφόλι σας. Είναι απαραίτητο για την ανάκτηση του πορτοφολιού σε περίπτωση που χάσετε μία από τις μνημονικές φράσεις σας.", + "ms_help_title4": "Εισαγωγή Χρηματοκιβωτίων", + "ms_help_4": "Για να εισάγετε ένα multisig, χρησιμοποιήστε το αρχείο αντιγράφου ασφαλείας και τη λειτουργία εισαγωγής. Εάν έχετε μόνο μνημονικές φράσεις και XPUBs, μπορείτε να χρησιμοποιήσετε το κουμπί ατομικής εισαγωγής κατά τη δημιουργία κλειδιών Χρηματοκιβωτίου.", + "ms_help_5": "Από προεπιλογή, το BlueWallet θα δημιουργήσει ένα 2-από-3 Χρηματοκιβώτιο. Για να δημιουργήσετε διαφορετική απαρτία ή να αλλάξετε τον τύπο διεύθυνσης, ενεργοποιήστε την κατάσταση για προχωρημένους στις Ρυθμίσεις." }, "is_it_my_address": { - "enter_address": "Εισαγωγή διεύθυνσης" + "title": "Είναι δική μου διεύθυνση;", + "enter_address": "Εισαγωγή διεύθυνσης", + "check_address": "Έλεγχος διεύθυνσης", + "view_qrcode": "Προβολή κωδικού QR", + "owns": "{label} κατέχει {address}", + "no_wallet_owns_address": "Κανένα από τα διαθέσιμα πορτοφόλια δεν κατέχει την παρεχόμενη διεύθυνση." + }, + "autofill_word": { + "title": "Τελική λέξη μνημονικής φράσης", + "enter": "Εισάγετε τη μερική μνημονική σας φράση", + "generate_word": "Δημιουργία της τελικής λέξης", + "error": "Η εισαγωγή δεν είναι μερική μνημονική φράση 11 ή 23 λέξεων. Παρακαλώ δοκιμάστε ξανά." }, "cc": { - "change": "Αλλαγή" + "change": "Αλλαγή", + "sort_label": "Ετικέτα", + "sort_status": "Κατάσταση", + "coins_selected": "Επιλεγμένα νομίσματα ({number})", + "selected_summ": "{value} επιλεγμένα", + "empty": "Αυτό το πορτοφόλι δεν έχει νομίσματα αυτή τη στιγμή.", + "freeze": "Πάγωμα", + "freezeLabel": "Πάγωμα", + "freezeLabel_un": "Ξεπάγωμα", + "header": "Διαχείριση UTXO", + "use_coin": "Χρήση νομίσματος", + "use_coins": "Χρήση νομισμάτων", + "tip": "Αυτή η λειτουργία σας επιτρέπει να βλέπετε, να επισημαίνετε, να παγώνετε ή να επιλέγετε νομίσματα για βελτιωμένη διαχείριση πορτοφολιού. Μπορείτε να επιλέξετε πολλαπλά νομίσματα πατώντας στους χρωματιστούς κύκλους.", + "sort_asc": "Αύξουσα", + "sort_desc": "Φθίνουσα", + "sort_height": "Ύψος", + "sort_value": "Αξία", + "sort_by": "Ταξινόμηση κατά" }, "units": { "BTC": "BTC", + "MAX": "Μέγιστο", "sat_vbyte": "sat/vByte", "sats": "sats" }, "addresses": { + "copy_private_key": "Αντιγραφή ιδιωτικού κλειδιού", + "sign_sign": "Υπογραφή", + "sign_verify": "Επιβεβαίωση", + "sign_signature_correct": "Επιτυχής επιβεβαίωση!", + "sign_signature_incorrect": "Ανεπιτυχής επιβεβαίωση!", "sign_placeholder_address": "Διεύθυνση", "sign_placeholder_message": "Μήνυμα", "sign_placeholder_signature": "Υπογραφή", @@ -332,6 +658,47 @@ "type_change": "Αλλαγή", "type_receive": "Λήψη", "type_used": "Έχει γίνει χρήση", - "transactions": "Συναλλαγές" + "transactions": "Συναλλαγές", + "sensitive_private_key": "Προειδοποίηση: τα ιδιωτικά κλειδιά είναι εξαιρετικά ευαίσθητα. Συνέχεια;", + "sign_title": "Υπογραφή/Επαλήθευση μηνύματος", + "sign_help": "Εδώ μπορείτε να δημιουργήσετε ή να επαληθεύσετε μια κρυπτογραφική υπογραφή βασισμένη σε μια διεύθυνση Bitcoin." + }, + "lnurl_auth": { + "register_question_part_1": "Θέλετε να εγγραφείτε για λογαριασμό στο", + "register_question_part_2": "χρησιμοποιώντας το πορτοφόλι Lightning σας;", + "register_answer": "Εγγραφήκατε επιτυχώς για λογαριασμό στο {hostname}!", + "login_question_part_1": "Θέλετε να συνδεθείτε στο", + "login_question_part_2": "χρησιμοποιώντας το πορτοφόλι Lightning σας;", + "login_answer": "Συνδεθήκατε επιτυχώς στο {hostname}!", + "link_question_part_1": "Θέλετε να συνδέσετε τον λογαριασμό σας στο", + "link_question_part_2": "με το πορτοφόλι Lightning σας;", + "link_answer": "Το πορτοφόλι Lightning σας συνδέθηκε επιτυχώς με τον λογαριασμό σας στο {hostname}!", + "auth_question_part_1": "Θέλετε να πιστοποιηθείτε στο", + "auth_question_part_2": "χρησιμοποιώντας το πορτοφόλι Lightning σας;", + "auth_answer": "Πιστοποιηθήκατε επιτυχώς στο {hostname}!", + "could_not_auth": "Δεν μπορέσαμε να σας πιστοποιήσουμε στο {hostname}.", + "authenticate": "Πιστοποίηση" + }, + "bip47": { + "payment_code": "Κωδικός πληρωμής", + "not_found": "Ο κωδικός πληρωμής δεν βρέθηκε", + "contacts": "Επαφές", + "bip47_explain": "Επαναχρησιμοποιήσιμος και διαμοιραζόμενος κωδικός", + "bip47_explain_subtitle": "BIP47", + "purpose": "Επαναχρησιμοποιήσιμος και διαμοιραζόμενος κωδικός (BIP47)", + "pay_this_contact": "Πλήρωσε αυτή την επαφή", + "rename_contact": "Μετονομασία επαφής", + "copy_payment_code": "Αντιγραφή κωδικού πληρωμής", + "hide_contact": "Απόκρυψη επαφής", + "rename": "Μετονομασία", + "provide_name": "Δώστε νέο όνομα για αυτή την επαφή", + "add_contact": "Προσθήκη επαφής", + "provide_payment_code": "Δώστε κωδικό πληρωμής", + "invalid_pc": "Μη έγκυρος κωδικός πληρωμής", + "notification_tx_unconfirmed": "Η συναλλαγή ειδοποίησης δεν έχει επιβεβαιωθεί ακόμη, παρακαλώ περιμένετε", + "failed_create_notif_tx": "Αποτυχία δημιουργίας on-chain συναλλαγής", + "onchain_tx_needed": "Απαιτείται on-chain συναλλαγή", + "notif_tx_sent": "Η συναλλαγή ειδοποίησης στάλθηκε. Παρακαλώ περιμένετε να επιβεβαιωθεί", + "notif_tx": "Συναλλαγή ειδοποίησης" } } diff --git a/loc/en.json b/loc/en.json index 5b1ddf74e62..18f67838a7c 100644 --- a/loc/en.json +++ b/loc/en.json @@ -4,32 +4,32 @@ "cancel": "Cancel", "continue": "Continue", "clipboard": "Clipboard", + "copied": "Copied!", + "discard_changes": "Discard changes?", + "discard_changes_explain": "You have unsaved changes. Are you sure you want to discard them and leave the screen?", "enter_password": "Enter password", "never": "Never", - "disabled": "Disabled", "of": "{number} of {total}", "ok": "OK", + "enter_url": "Enter URL", "storage_is_encrypted": "Your storage is encrypted. Password is required to decrypt it.", "yes": "Yes", "no": "No", - "save": "Save", + "save": "Save...", "seed": "Seed", "success": "Success", "wallet_key": "Wallet key", - "invalid_animated_qr_code_fragment": "Invalid animated QRCode fragment. Please try again.", - "file_saved": "File {filePath} has been saved in your {destination}.", - "downloads_folder": "Downloads Folder", "close": "Close", "change_input_currency": "Change input currency", "refresh": "Refresh", - "more": "More", - "pick_image": "Choose image from library", - "pick_file": "Choose a file", + "pick_image": "Choose from library", + "pick_file": "Choose file", "enter_amount": "Enter amount", - "qr_custom_input_button": "Tap 10 times to enter custom input" - }, - "alert": { - "default": "Alert" + "qr_custom_input_button": "Tap 10 times to enter custom input", + "unlock": "Unlock", + "port": "Port", + "ssl_port": "SSL Port", + "suggested": "Suggested" }, "azteco": { "codeIs": "Your voucher code is", @@ -38,12 +38,14 @@ "redeem": "Redeem to wallet", "redeemButton": "Redeem", "success": "Success", + "successMessage": "Voucher redeemed successfully! Your funds should arrive in your Bitcoin wallet shortly.", "title": "Redeem Azte.co voucher" }, "entropy": { "save": "Save", "title": "Entropy", - "undo": "Undo" + "undo": "Undo", + "amountOfEntropy": "{bits} of {limit} bits" }, "errors": { "broadcast": "Broadcast failed.", @@ -51,80 +53,63 @@ "network": "Network Error" }, "lnd": { - "active": "Active", - "inactive": "Inactive", - "channels": "Channels", - "no_channels": "No channels", - "claim_balance": "Claim balance {balance}", - "close_channel": "Close channel", - "new_channel": "New channel", - "errorInvoiceExpired": "Invoice expired", - "force_close_channel": "Force close channel?", + "errorInvoiceExpired": "Invoice expired.", "expired": "Expired", - "node_alias": "Node alias", "expiresIn": "Expires in {time} minutes", "payButton": "Pay", + "payment": "Payment", "placeholder": "Invoice or address", - "open_channel": "Open Channel", - "funding_amount_placeholder": "Funding amount, for example 0.001", - "opening_channnel_for_from": "Opening channel for wallet {forWalletLabel}, by funding from {fromWalletLabel}", - "are_you_sure_open_channel": "Are you sure you want to open this channel?", - "potentialFee": "Potential Fee: {fee}", - "remote_host": "Remote host", + "potentialFee": "Potential fee: {fee}", "refill": "Refill", - "reconnect_peer": "Reconnect peer", "refill_create": "In order to proceed, please create a Bitcoin wallet to refill with.", "refill_external": "Refill with External Wallet", "refill_lnd_balance": "Refill Lightning Wallet Balance", - "sameWalletAsInvoiceError": "You can't pay an invoice with the same wallet used to create it.", - "title": "Manage Funds", - "can_send": "Can Send", - "can_receive": "Can Receive", - "view_logs": "View Logs" + "sameWalletAsInvoiceError": "You cannot pay an invoice with the same wallet used to create it.", + "title": "Manage Funds" }, "lndViewInvoice": { "additional_info": "Additional Information", "for": "For:", "lightning_invoice": "Lightning Invoice", - "open_direct_channel": "Open direct channel with this node:", "please_pay_between_and": "Please pay between {min} and {max}", "please_pay": "Please pay", - "preimage": "Preimage", + "preimage": "Pre-image", "sats": "sats.", + "date_time": "Date and Time", "wasnt_paid_and_expired": "This invoice was not paid and has expired." }, "plausibledeniability": { "create_fake_storage": "Create Encrypted Storage", - "create_password": "Create a password", "create_password_explanation": "Password for the fake storage should not match the password for your main storage.", "help": "Under certain circumstances, you might be forced to disclose a password. To keep your coins safe, BlueWallet can create another encrypted storage with a different password. Under pressure, you may disclose this password to a third party. If entered in BlueWallet, it will unlock a new “fake” storage. This will seem legit to the third party, but it will secretly keep your main storage with coins safe.", "help2": "The new storage will be fully functional, and you can store some minimum amounts there so that it looks more believable.", "password_should_not_match": "Password is currently in use. Please try a different password.", - "passwords_do_not_match": "Passwords do not match. Please try again.", - "retype_password": "Re-type password", - "success": "Success", "title": "Plausible Deniability" }, "pleasebackup": { "ask": "Have you saved your wallet’s backup phrase? This backup phrase is required to access your funds in case you lose this device. Without the backup phrase, your funds will be permanently lost.", - "ask_no": "No, I have not", - "ask_yes": "Yes, I have", - "ok": "OK, I wrote it down", - "ok_lnd": "OK, I have saved it", + "ask_no": "No, I have not.", + "ask_yes": "Yes, I have.", + "ok": "OK, I wrote it down.", + "ok_lnd": "OK, I have saved it.", "text": "Please take a moment to write down this mnemonic phrase on a piece of paper.\nIt’s your backup and you can use it to recover the wallet.", "text_lnd": "Please save this wallet backup. It allows you to restore the wallet in case of loss.", - "title": "Your wallet has been created" + "title": "Your wallet has been created." }, "receive": { "details_create": "Create", "details_label": "Description", "details_setAmount": "Receive with amount", - "details_share": "Share", + "details_share": "Share...", + "address_not_found": "Unable to generate receiving address.", "header": "Receive", + "reset": "Reset", "maxSats": "Maximum amount is {max} sats", "maxSatsFull": "Maximum amount is {max} sats or {currency}", "minSats": "Minimal amount is {min} sats", - "minSatsFull": "Minimal amount is {min} sats or {currency}" + "minSatsFull": "Minimal amount is {min} sats or {currency}", + "qrcode_for_the_address": "QR Code for the address", + "bip47_explanation": "Payment codes are a universal address that avoids disclosing your wallet addresses. Not all services will support them." }, "send": { "provided_address_is_invoice": "This address appears to be for a Lightning invoice. Please, go to your Lightning wallet in order to make a payment for this invoice.", @@ -146,8 +131,15 @@ "create_to": "To", "create_tx_size": "Transaction Size", "create_verify": "Verify on coinb.in", + "details_insert_contact": "Insert Contact", "details_add_rec_add": "Add Recipient", "details_add_rec_rem": "Remove Recipient", + "details_add_recc_rem_all_alert_description": "Are you sure you want to remove all recipients?", + "details_add_rec_rem_all": "Remove All Recipients", + "details_recipients_title": "Recipients", + "details_recipient_title": "Recipient #{number} of #{total}", + "please_complete_recipient_title": "Incomplete Recipient", + "please_complete_recipient_details": "Please complete the details of recipient #{number} before adding a new recipient.", "details_address": "Address", "details_address_field_is_not_valid": "The address is not valid.", "details_adv_fee_bump": "Allow Fee Bump", @@ -161,12 +153,13 @@ "details_create": "Create Invoice", "details_error_decode": "Unable to decode Bitcoin address", "details_fee_field_is_not_valid": "The fee is not valid.", - "details_frozen": "{amount} BTC is frozen", + "details_frozen": "{amount} BTC is frozen.", "details_next": "Next", - "details_no_signed_tx": "The selected file doesn’t contain a transaction that can be imported.", + "details_no_signed_tx": "The selected file does not contain a transaction that can be imported.", "details_note_placeholder": "Note to Self", "details_scan": "Scan", "details_scan_hint": "Double tap to scan or import a destination", + "details_scan_error": "Scan error", "details_total_exceeds_balance": "The sending amount exceeds the available balance.", "details_total_exceeds_balance_frozen": "The sending amount exceeds the available balance. Please note that frozen coins are excluded.", "details_unrecognized_file_format": "Unrecognized file format", @@ -180,6 +173,7 @@ "fee_1d": "1d", "fee_3h": "3h", "fee_custom": "Custom", + "insert_custom_fee": "Insert fee", "fee_fast": "Fast", "fee_medium": "Medium", "fee_replace_minvb": "The total fee rate (satoshi per vByte) you want to pay should be higher than {min} sat/vByte.", @@ -192,9 +186,8 @@ "input_total": "Total:", "permission_camera_message": "We need your permission to use your camera.", "psbt_sign": "Sign a transaction", + "invalid_psbt": "Invalid PSBT provided.", "open_settings": "Open Settings", - "permission_storage_later": "Ask me later", - "permission_storage_message": "BlueWallet needs your permission to access your storage to save this file.", "permission_storage_denied_message": "BlueWallet is unable to save this file. Please open your device settings and enable Storage Permission.", "permission_storage_title": "Storage Access Permission", "psbt_clipboard": "Copy to Clipboard", @@ -204,11 +197,15 @@ "outdated_rate": "Rate was last updated: {date}", "psbt_tx_open": "Open Signed Transaction", "psbt_tx_scan": "Scan Signed Transaction", - "qr_error_no_qrcode": "We were unable to find a QR Code in the selected image. Make sure the image contains only a QR Code and no additional content such as text, or buttons.", + "qr_error_no_qrcode": "We were unable to find a valid QR Code in the selected image. Make sure the image contains only a QR Code and no additional content such as text or buttons.", "reset_amount": "Reset Amount", "reset_amount_confirm": "Would you like to reset the amount?", "success_done": "Done", - "txSaved": "The transaction file ({filePath}) has been saved in your Downloads folder.", + "txSaved": "The transaction file ({filePath}) has been saved.", + "file_saved_at_path": "The file ({filePath}) has been saved.", + "cant_send_to_silentpayment_adress": "This wallet cannot send to SilentPayment addresses", + "cant_send_to_bip47": "This wallet cannot send to BIP47 payment codes", + "cant_find_bip47_notification": "Add this Payment Code to contacts first", "problem_with_psbt": "Problem with PSBT" }, "settings": { @@ -222,28 +219,29 @@ "performance_score": "Performance score: {num}", "run_performance_test": "Test performance", "about_selftest": "Run self-test", + "block_explorer_invalid_custom_url": "The URL provided is invalid. Please enter a valid URL starting with http:// or https://.", "about_selftest_electrum_disabled": "Self-testing is not available with Electrum Offline Mode. Please disable offline mode and try again.", "about_selftest_ok": "All internal tests have passed successfully. The wallet works well.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord Server", "about_sm_telegram": "Telegram channel", - "about_sm_twitter": "Follow us on Twitter", - "advanced_options": "Advanced Options", + "privacy_temporary_screenshots": "Allow Screen Capture", + "privacy_temporary_screenshots_instructions": "Screen capture protection will be temporarily turned off, enabling screenshots and screen recordings. The protection will automatically reactivate when you close and reopen BlueWallet.", "biometrics": "Biometrics", + "biometrics_no_longer_available": "Your device settings have changed and no longer match the selected security settings in the app. Please re-enable biometrics or passcode, then restart the app to apply these changes.", "biom_10times": "You have attempted to enter your password 10 times. Would you like to reset your storage? This will remove all wallets and decrypt your storage.", "biom_conf_identity": "Please confirm your identity.", - "biom_no_passcode": "Your device does not have a passcode. In order to proceed, please configure a passcode in the Settings app.", + "biom_no_passcode": "Your device does not have a passcode or biometrics enabled. In order to proceed, please configure a passcode or biometric in the Settings app.", "biom_remove_decrypt": "All your wallets will be removed and your storage will be decrypted. Are you sure you want to proceed?", "currency": "Currency", - "currency_source": "Price is obtained from", + "currency_source": "Rate is obtained from", "currency_fetch_error": "There was an error while obtaining the rate for the selected currency.", - "default_desc": "When disabled, BlueWallet will immediately open the selected wallet at launch.", - "default_info": "Default info", "default_title": "On Launch", - "default_wallets": "View All Wallets", + "donate": "Donate", + "donate_description": "Help us keep Blue free!", "electrum_connected": "Connected", "electrum_connected_not": "Not Connected", - "electrum_error_connect": "Can’t connect to the provided Electrum server", + "electrum_error_connect": "Cannot connect to the provided Electrum server", + "electrum_error_connect_tor": "Cannot connect to the provided Electrum server. Please make sure Orbot app is connected and try again.", "lndhub_uri": "E.g., {example}", "electrum_host": "E.g., {example}", "electrum_offline_mode": "Offline Mode", @@ -252,32 +250,35 @@ "use_ssl": "Use SSL", "electrum_saved": "Your changes have been saved successfully. Restarting BlueWallet may be required for the changes to take effect.", "set_electrum_server_as_default": "Set {server} as the default Electrum server?", - "set_lndhub_as_default": "Set {url} as the default LNDHub server?", + "set_lndhub_as_default": "Set {url} as the default LNDhub server?", "electrum_settings_server": "Electrum Server", - "electrum_settings_explain": "Leave blank to use the default.", "electrum_status": "Status", - "electrum_clear_alert_title": "Clear history?", - "electrum_clear_alert_message": "Do you want to clear electrum servers history?", - "electrum_clear_alert_cancel": "Cancel", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Select", - "electrum_reset": "Reset to default", + "electrum_preferred_server": "Preferred Server", + "electrum_preferred_server_description": "Enter the server you want your wallet to use for all Bitcoin activities. Once set, your wallet will exclusively use this server to check balances, send transactions, and fetch network data. Ensure you trust this server before setting it.", "electrum_unable_to_connect": "Unable to connect to {server}.", - "electrum_history": "Server history", - "electrum_reset_to_default": "Are you sure to want to reset your Electrum settings to default?", - "electrum_clear": "Clear", - "tor_supported": "Tor supported", - "tor_unsupported": "Tor connections are not supported.", + "electrum_history": "History", + "electrum_reset_to_default": "This will let BlueWallet randomly choose a server from the server list.", + "electrum_reset": "Reset to default", + "electrum_reset_to_default_and_clear_history": "Reset to default and clear history", "encrypt_decrypt": "Decrypt Storage", "encrypt_decrypt_q": "Are you sure you want to decrypt your storage? This will allow your wallets to be accessed without a password.", - "encrypt_enc_and_pass": "Encrypted and Password Protected", + "encrypt_enc_and_pass": "Password Protected", + "encrypt_storage_explanation_headline": "Enable Storage Encryption", + "encrypt_storage_explanation_description_line1": "Enabling Storage Encryption adds an extra layer of protection to your app by securing the way your data is stored on your device. This makes it harder for anyone to access your information without permission.", + "encrypt_storage_explanation_description_line2": "However, it's important to know that this encryption only protects the access to your wallets stored on the device's keychain. It doesn't put a password or any extra protection on the wallets themselves.", + "i_understand": "I understand", + "block_explorer": "Block Explorer", + "block_explorer_preferred": "Use preferred block explorer", + "block_explorer_error_saving_custom": "Error saving preferred block explorer", "encrypt_title": "Security", "encrypt_tstorage": "Storage", "encrypt_use": "Use {type}", - "encrypt_use_expl": "{type} will be used to confirm your identity before making a transaction, unlocking, exporting, or deleting a wallet. {type} will not be used to unlock encrypted storage.", + "set_as_preferred": "Set as preferred", + "set_as_preferred_electrum": "Setting {host}:{port} as preferred server will disable connecting to a suggested server at random.", + "encrypted_feature_disabled": "This feature cannot be used with encrypted storage enabled.", + "encrypt_use_expl": "{type} will be used to confirm your identity before making a transaction, unlocking, exporting, or deleting a wallet.", + "biometrics_fail": "If {type} is not enabled, or fails to unlock, you can use your device passcode as an alternative.", "general": "General", - "general_adv_mode": "Advanced Mode", - "general_adv_mode_e": "When enabled, you will see advanced options such as different wallet types, the ability to specify the LNDHub instance you wish to connect to, and custom entropy during wallet creation.", "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.", @@ -285,36 +286,37 @@ "language": "Language", "last_updated": "Last Updated", "language_isRTL": "Restarting BlueWallet is required for the language orientation to take effect.", - "lightning_error_lndhub_uri": "Invalid LNDHub URI", + "license": "License", + "lightning_error_lndhub_uri": "Invalid LNDhub URI", + "lightning_error_lndhub_uri_tor": "Invalid LNDhub URI. Please make sure Orbot app is connected and try again.", "lightning_saved": "Your changes have been saved successfully.", "lightning_settings": "Lightning Settings", - "tor_settings": "Tor Settings", - "lightning_settings_explain": "To connect to your own LND node, please install LNDHub and put its URL here in settings. Please note that only wallets created after saving changes will connect to the specified LNDHub.", + "lightning_settings_explain": "To connect to your own LND node, please install LNDhub and put its URL here in settings. Please note that only wallets created after saving changes will connect to the specified LNDhub.", + "lndhub_github": "GitHub Repository", "network": "Network", "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", - "password_explain": "Create the password you will use to decrypt the storage.", - "passwords_do_not_match": "Passwords do not match.", + "password_explain": "Enter the password you will use to unlock your storage.", "plausible_deniability": "Plausible Deniability", "privacy": "Privacy", "privacy_read_clipboard": "Read Clipboard", "privacy_system_settings": "System Settings", "privacy_quickactions": "Wallet Shortcuts", - "privacy_quickactions_explanation": "Touch and hold the BlueWallet app icon on your Home Screen to quickly view your wallet’s balance.", + "privacy_quickactions_explanation": "Touch and hold the BlueWallet app icon to quickly view your wallet’s balance.", "privacy_clipboard_explanation": "Provide shortcuts if an address or invoice is found in your clipboard.", "privacy_do_not_track": "Disable Analytics", "privacy_do_not_track_explanation": "Performance and reliability information will not be submitted for analysis.", - "push_notifications": "Push Notifications", "rate": "Rate", - "retype_password": "Re-type password", + "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": "Success! Your transaction has been broadcasted!", + "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.", "widgets": "Widgets", @@ -322,15 +324,17 @@ }, "notifications": { "would_you_like_to_receive_notifications": "Would you like to receive notifications when you get incoming payments?", - "no_and_dont_ask": "No, and don’t ask me again", - "ask_me_later": "Ask me later" + "notifications_subtitle": "Incoming payments and transaction confirmations", + "no_and_dont_ask": "No, and do not ask me again.", + "permission_denied_message": "You have denied permission to send you notifications. If you would like to receive notifications, please enable them in your device settings." }, "transactions": { "cancel_explain": "We will replace this transaction with one that pays you and has higher fees. This effectively cancels the current transaction. This is called RBF—Replace by Fee.", "cancel_no": "This transaction is not replaceable.", "cancel_title": "Cancel this transaction (RBF)", + "transaction_loading_error": "There was an issue loading the transaction. Please try again later.", + "transaction_not_available": "Transaction not available", "confirmations_lowercase": "{confirmations} confirmations", - "copy_link": "Copy Link", "expand_note": "Expand Note", "cpfp_create": "Create", "cpfp_exp": "We will create another transaction that spends your unconfirmed transaction. The total fee will be higher than the original transaction fee, so it should be mined faster. This is called CPFP—Child Pays for Parent.", @@ -338,20 +342,22 @@ "cpfp_title": "Bump Fee (CPFP)", "details_balance_hide": "Hide Balance", "details_balance_show": "Show Balance", - "details_block": "Block Height", "details_copy": "Copy", - "details_copy_amount": "Copy Amount", "details_copy_block_explorer_link": "Copy Block Explorer Link", "details_copy_note": "Copy Note", "details_copy_txid": "Copy Transaction ID", - "details_from": "Input", "details_inputs": "Inputs", "details_outputs": "Outputs", "date": "Date", "details_received": "Received", - "transaction_note_saved": "Transaction note has been successfully saved.", - "details_show_in_block_explorer": "View in Block Explorer", + "details_view_in_browser": "View in Browser", "details_title": "Transaction", + "incoming_transaction": "Incoming Transaction", + "outgoing_transaction": "Outgoing Transaction", + "expired_transaction": "Expired Transaction", + "pending_transaction": "Pending Transaction", + "offchain": "Offchain", + "onchain": "Onchain", "details_to": "Output", "enable_offline_signing": "This wallet is not being used in conjunction with an offline signing. Would you wish to enable it now?", "list_conf": "Conf: {number}", @@ -361,59 +367,93 @@ "eta_10m": "ETA: In ~10 minutes", "eta_3h": "ETA: In ~3 hours", "eta_1d": "ETA: In ~1 day", - "view_wallet": "View {walletLabel}", "list_title": "Transactions", + "list_title_sent": "Sent", + "list_title_received": "Received", + "transaction": "Transaction", "open_url_error": "Unable to open the link with the default browser. Please change your default browser and try again.", "rbf_explain": "We will replace this transaction with one with a higher fee so that it will be mined faster. This is called RBF—Replace by Fee.", - "rbf_title": "Bump Fee (RBF)", - "status_bump": "Bump Fee", - "status_cancel": "Cancel Transaction", - "transactions_count": "Transactions Count", + "rbf_title": "Speed Up (RBF)", + "status_bump": "Speed Up", + "status_cancel": "Cancel", "txid": "Transaction ID", - "updating": "Updating..." + "updating": "Updating...", + "watchOnlyWarningTitle": "Security warning", + "watchOnlyWarningDescription": "Be cautious of scammers who often use “watch-only” wallets to deceive users. These wallets do not allow you to control or send funds; they only let you view the balance.", + "custom_fee_warning_title": "Warning", + "custom_fee_warning_description": "Fees below 1 sat/vB are valid, but may not be relayed due to node policies.", + "details_eta_analyzing": "Analyzing...", + "details_sent": "Sent", + "details_section": "Details", + "details_explorer": "explorer", + "details_network_fee": "Network Fee", + "details_to_address": "To", + "details_id": "ID", + "details_note": "Note", + "details_add_note": "add", + "details_advanced": "Advanced", + "details_fee_rate": "Fee rate", + "details_size": "Size", + "details_virtual_size": "Virtual size", + "details_tx_hex": "Tx Hex", + "details_inputs_count": "Inputs ({count})", + "details_outputs_count": "Outputs ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Simple and powerful Bitcoin wallet", "add_create": "Create", + "total_balance": "Total Balance", + "add_entropy_reset_title": "Reset Entropy", + "add_entropy_reset_message": "Changing the wallet type will reset the current entropy. Do you want to proceed?", + "add_entropy": "Entropy", + "add_entropy_bytes": "{bytes} bytes of entropy", "add_entropy_generated": "{gen} bytes of generated entropy", "add_entropy_provide": "Provide entropy via dice rolls", "add_entropy_remain": "{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.", "add_import_wallet": "Import wallet", "add_lightning": "Lightning", "add_lightning_explain": "For spending with instant transactions", - "add_lndhub": "Connect to your LNDHub", - "add_lndhub_error": "The provided node address is an invalid LNDHub node.", + "add_lndhub": "Connect to your LNDhub", + "add_lndhub_error": "The provided node address is an invalid LNDhub node.", "add_lndhub_placeholder": "Your Node Address", "add_placeholder": "my first wallet", "add_title": "Add Wallet", "add_wallet_name": "Name", "add_wallet_type": "Type", - "balance": "Balance", + "add_wallet_seed_length": "Seed Length", + "add_wallet_seed_length_12": "12 words", + "add_wallet_seed_length_24": "24 words", "clipboard_bitcoin": "You have a Bitcoin address on your clipboard. Would you like to use it for a transaction?", "clipboard_lightning": "You have a Lightning invoice on your clipboard. Would you like to use it for a transaction?", + "clear_clipboard_on_import": "Clear clipboard on import", "details_address": "Address", "details_advanced": "Advanced", "details_are_you_sure": "Are you sure?", "details_connected_to": "Connected to", - "details_del_wb_err": "The provided balance amount doesn’t match this wallet’s balance. Please try again.", + "details_del_wb_err": "The provided balance amount does not match this wallet’s balance. Please try again.", "details_del_wb_q": "This wallet has a balance. Before proceeding, please be aware that you will not be able to recover the funds without this wallet’s seed phrase. In order to avoid accidental removal, please enter your wallet’s balance of {balance} satoshis.", "details_delete": "Delete", "details_delete_wallet": "Delete Wallet", "details_derivation_path": "derivation path", - "details_display": "Display in Wallets List", + "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_no_cancel": "No, cancel", - "details_save": "Save", + "details_options": "Options", "details_show_xpub": "Show Wallet XPUB", "details_show_addresses": "Show addresses", + "details_stats_coins": "Coins", "details_title": "Wallet", + "wallets": "Wallets", + "swipe_balance_hide": "Hide", + "swipe_balance_show": "Show", + "drag_to_reorder": "Drag to reorder", + "clear_search": "Clear search", "details_type": "Type", "details_use_with_hardware_wallet": "Use with Hardware Wallet", - "details_wallet_updated": "Wallet updated", "details_yes_delete": "Yes, delete", "enter_bip38_password": "Enter password to decrypt", "export_title": "Wallet Export", @@ -429,59 +469,85 @@ "import_success_watchonly": "Your wallet has been successfully imported. WARNING: This is a watch-only wallet, you can NOT spend from it.", "import_search_accounts": "Search accounts", "import_title": "Import", + "learn_more": "Learn more", "import_discovery_title": "Discovery", "import_discovery_subtitle": "Choose a discovered wallet", "import_discovery_derivation": "Use custom derivation path", "import_discovery_no_wallets": "No wallets were found.", - "import_derivation_found": "found", - "import_derivation_found_not": "not found", - "import_derivation_loading": "loading...", - "import_derivation_subtitle": "Enter custom derivation path and we will try to discover your wallet", + "import_discovery_offline": "BlueWallet is currently in offline mode. In this mode, it can't verify the existence of the wallet, so you'll need to select the correct one manually", + "import_derivation_found": "Found", + "import_derivation_found_not": "Not found", + "import_derivation_loading": "Loading...", + "import_derivation_subtitle": "Enter custom derivation path, and we will try to discover your wallet.", "import_derivation_title": "Derivation path", - "import_derivation_unknown": "unknown", - "import_wrong_path": "wrong derivation path", + "import_derivation_unknown": "Unknown", + "import_wrong_path": "Wrong derivation path", "list_create_a_button": "Add now", "list_create_a_wallet": "Add a wallet", - "list_create_a_wallet_text": "It’s free and you can create \nas many as you like.", + "list_create_a_wallet_text": "It’s free, and you can create \nas many as you like.", "list_empty_txs1": "Your transactions will appear here.", "list_empty_txs1_lightning": "Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and the speed is blazing fast.", "list_empty_txs2": "Start with your wallet.", "list_empty_txs2_lightning": "\nTo start using it, tap on Manage Funds and topup your balance.", "list_latest_transaction": "Latest Transaction", - "list_ln_browser": "LApp Browser", "list_long_choose": "Choose Photo", - "list_long_clipboard": "Copy from Clipboard", + "paste_from_clipboard": "Paste", + "import_file": "Import File", "list_long_scan": "Scan QR Code", "list_title": "Wallets", "list_tryagain": "Try again", "no_ln_wallet_error": "Before paying a Lightning invoice, you must first add a Lightning wallet.", "looks_like_bip38": "This looks like a password-protected private key (BIP38).", - "reorder_title": "Re-order Wallets", - "reorder_instructions": "Tap and hold a wallet to drag it across the list.", + "manage_title": "Manage Wallets", + "no_results_found": "No results found.", "please_continue_scanning": "Please continue scanning.", "select_no_bitcoin": "There are currently no Bitcoin wallets available.", "select_no_bitcoin_exp": "A Bitcoin wallet is required to refill Lightning wallets. Please create or import one.", "select_wallet": "Select Wallet", - "xpub_copiedToClipboard": "Copied to clipboard.", "pull_to_refresh": "Pull to Refresh", - "warning_do_not_disclose": "Warning! Do not disclose.", + "warning_do_not_disclose": "Never share the information below", + "scan_import": "Scan this QR code to import your wallet in another application.", + "write_down_header": "Create a manual backup", + "write_down": "Write down and securely store these words. Use them to restore your wallet at a later time.", + "wallet_type_this": "This wallet type is {type}.", + "share_number": "Share {number}", + "copy_ln_url": "Copy and securely store this URL to restore your wallet at a later time.", + "copy_ln_public": "Copy and securely store this information to restore your wallet at a later time.", "add_ln_wallet_first": "You must first add a Lightning wallet.", "identity_pubkey": "Identity Pubkey", - "xpub_title": "Wallet XPUB" + "xpub_title": "Wallet XPUB", + "manage_wallets_search_placeholder": "Search wallets, addresses, transactions and memos", + "more_info": "More Info", + "details_delete_wallet_error_message": "There was an issue confirming if this wallet was removed from notifications—this could be due to a network issue or poor connection. If you continue, you might still receive notifications for transactions related to this wallet, even after it is deleted.", + "details_delete_anyway": "Delete anyway" + }, + "total_balance_view": { + "display_in_bitcoin": "Display in Bitcoin", + "hide": "Hide", + "display_in_sats": "Display in sats", + "display_in_fiat": "Display in {currency}", + "title": "Total Balance", + "explanation": "View the total balance of all your wallets in the overview screen." }, "multisig": { - "multisig_vault": "Vault", + "multisig_vault": "Multisig Vault", "default_label": "Multisig Vault", "multisig_vault_explain": "Best security for large amounts", "provide_signature": "Provide signature", + "provide_signature_details": "Use your device and wallet where the key resides to sign this transaction", + "provide_signature_details_bluewallet": "In BlueWallet, go to the Send screen menu and select ", + "provide_signature_next_steps": "Scan or Import Signed Transaction", + "provide_signature_next_steps_details": "Once your wallet has successfully signed the transaction, scan the provided QR code or import the accompanying file, and then review all the transaction details before broadcasting it.", "vault_key": "Vault Key {number}", "required_keys_out_of_total": "Required keys out of the total", "fee": "Fee: {number}", "fee_btc": "{number} BTC", "confirm": "Confirm", "header": "Send", - "share": "Share", + "share": "Share...", "view": "View", + "shared_key_detected": "Shared co-signer", + "shared_key_detected_question": "A co-signer was shared with you, do you want to import it?", "manage_keys": "Manage Keys", "how_many_signatures_can_bluewallet_make": "how many signatures can BlueWallet make", "signatures_required_to_spend": "Signatures required {number}", @@ -507,20 +573,21 @@ "quorum_header": "Quorum", "of": "of", "wallet_type": "Wallet Type", - "invalid_mnemonics": "This mnemonic phrase doesn’t seem to be valid.", - "invalid_cosigner": "Invalid cosigner data", + "invalid_mnemonics": "This mnemonic phrase does not seem to be valid.", + "invalid_cosigner": "Invalid co-signer data", "not_a_multisignature_xpub": "This is not an XPUB from a multisignature wallet!", - "invalid_cosigner_format": "Incorrect cosigner: This is not a cosigner for {format} format.", + "invalid_cosigner_format": "Incorrect co-signer: This is not a co-signer for {format} format.", "create_new_key": "Create New", "scan_or_open_file": "Scan or open file", "i_have_mnemonics": "I have a seed for this key.", "type_your_mnemonics": "Insert a seed to import your existing Vault key.", - "this_is_cosigners_xpub": "This is the cosigner’s XPUB—ready to be imported into another wallet. It is safe to share it.", + "this_is_cosigners_xpub": "This is the co-signer’s XPUB—ready to be imported into another wallet. It is safe to share it.", + "this_is_cosigners_xpub_airdrop": "If you share via AirDrop the receivers have to be in the coordination screen.", "wallet_key_created": "Your Vault key was created. Take a moment to safely backup your mnemonic seed.", "are_you_sure_seed_will_be_lost": "Are you sure? Your mnemonic seed will be lost if you don’t have a backup.", "forget_this_seed": "Forget this seed and use the XPUB instead.", - "view_edit_cosigners": "View/Edit Cosigners", - "this_cosigner_is_already_imported": "This cosigner is already imported.", + "view_edit_cosigners": "View/Edit Co-signers", + "this_cosigner_is_already_imported": "This co-signer is already imported.", "export_signed_psbt": "Export Signed PSBT", "input_fp": "Enter fingerprint", "input_fp_explain": "Skip to use the default one (00000000)", @@ -546,20 +613,33 @@ "enter_address": "Enter address", "check_address": "Check address", "no_wallet_owns_address": "None of the available wallets own the provided address.", - "view_qrcode": "View QRCode" + "view_qrcode": "View QR Code" + }, + "autofill_word": { + "title": "Seed final word", + "enter": "Enter your partial mnemonic phrase", + "generate_word": "Generate the final word", + "error": "The input is not an 11- or 23-word partial mnemonic. Please try again." }, "cc": { "change": "Change", "coins_selected": "Coins Selected ({number})", "selected_summ": "{value} selected", - "empty": "This wallet doesn’t have any coins at the moment.", + "empty": "This wallet does not have any coins at the moment.", "freeze": "Freeze", "freezeLabel": "Freeze", "freezeLabel_un": "Unfreeze", "header": "Coin Control", "use_coin": "Use Coin", "use_coins": "Use Coins", - "tip": "This feature allows you to see, label, freeze or select coins for improved wallet management. You can select multiple coins by tapping on the colored circles." + "tip": "This feature allows you to see, label, freeze or select coins for improved wallet management. You can select multiple coins by tapping on the colored circles.", + "sort_asc": "Ascending", + "sort_desc": "Descending", + "sort_height": "Height", + "sort_value": "Value", + "sort_label": "Label", + "sort_status": "Status", + "sort_by": "Sort by" }, "units": { "BTC": "BTC", @@ -568,6 +648,8 @@ "sats": "sats" }, "addresses": { + "copy_private_key": "Copy private key", + "sensitive_private_key": "Warning: private keys are extremely sensitive. Continue?", "sign_title": "Sign/Verify Message", "sign_help": "Here you can create or verify a cryptographic signature based on a Bitcoin address.", "sign_sign": "Sign", @@ -601,9 +683,24 @@ }, "bip47": { "payment_code": "Payment Code", - "payment_codes_list": "Payment Codes List", - "who_can_pay_me": "Who can pay me:", + "contacts": "Contacts", + "bip47_explain": "Reusable and shareable code", + "bip47_explain_subtitle": "BIP47", "purpose": "Reusable and shareable code (BIP47)", + "pay_this_contact": "Pay this contact", + "rename_contact": "Rename contact", + "copy_payment_code": "Copy Payment Code", + "hide_contact": "Hide contact", + "rename": "Rename", + "provide_name": "Provide new name for this contact", + "add_contact": "Add Contact", + "provide_payment_code": "Provide Payment Code", + "invalid_pc": "Invalid Payment Code", + "notification_tx_unconfirmed": "Notification transaction is not confirmed yet, please wait", + "failed_create_notif_tx": "Failed to create on-chain transaction", + "onchain_tx_needed": "On-chain transaction needed", + "notif_tx_sent" : "Notification transaction sent. Please wait for it to confirm", + "notif_tx": "Notification transaction", "not_found": "Payment code not found" } } diff --git a/loc/es.json b/loc/es.json index 190eb51c180..96239fea660 100644 --- a/loc/es.json +++ b/loc/es.json @@ -2,26 +2,34 @@ "_": { "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_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", + "save": "Guardar...", + "close": "Cerrar", + "change_input_currency": "Cambiar divisa de entrada", + "refresh": "Actualizar", + "pick_image": "Elegir de la biblioteca", + "pick_file": "Elegir archivo", + "enter_amount": "Introduce la cantidad", + "qr_custom_input_button": "Toca 10 veces para introducir entrada personalizada", + "unlock": "Desbloquear", + "port": "Puerto", + "ssl_port": "Puerto SSL", + "suggested": "Sugerido", "never": "Nunca", - "disabled": "Deshabilitado", "of": "{number} de {total}", "ok": "OK", "storage_is_encrypted": "Tu almacenamiento está cifrado. Se requiere la contraseña para descifrarlo.", "yes": "Sí", "no": "No", - "save": "Guardar", "seed": "Semilla", "success": "Completado", - "wallet_key": "Llave de la cartera", - "invalid_animated_qr_code_fragment" : "Fragmento de código QR inválido. Por favor inténtalo de nuevo.", - "file_saved": "El archivo {filePath} se ha guardado en tu {destination}.", - "downloads_folder": "Carpeta de descargas" - }, - "alert": { - "default": "Atención" + "wallet_key": "Llave de la cartera" }, "azteco": { "codeIs": "El código de tu cupón es", @@ -30,80 +38,61 @@ "redeem": "Canjear y mover a la cartera", "redeemButton": "Canjear", "success": "Completado", + "successMessage": "¡Cupón canjeado correctamente! Tus fondos llegarán pronto a tu cartera de Bitcoin.", "title": "Canjear cupón Azte.co" }, "entropy": { "save": "Guardar", - "title": "Entropía ", - "undo": "Deshacer" + "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": { - "active":"Activo", - "inactive":"Inactivo", - "channels": "Canales", - "no_channels": "Sin canales", - "claim_balance": "Reclamar saldo {balance}", - "close_channel": "Cerrar canal", - "new_channel" : "Nuevo canal", - "errorInvoiceExpired": "Factura expirada", - "force_close_channel": "¿Forzar el cierre del canal?", + "errorInvoiceExpired": "Factura caducada.", "expired": "Expirado", - "node_alias": "Alias del nodo", "expiresIn": "Expira en {time} minutos", "payButton": "Pagar", + "payment": "Pago", "placeholder": "Factura o dirección", - "open_channel": "Abrir canal", - "funding_amount_placeholder": "Importe de la financiación, por ejemplo 0,001", - "opening_channnel_for_from":"Abrir canal para la cartera {forWalletLabel}, financiado desde {fromWalletLabel}", - "are_you_sure_open_channel": "¿Estás seguro de que quieres abrir este canal?", "potentialFee": "Comisión estimada: {fee}", - "remote_host": "Host remoto", "refill": "Recargar", - "reconnect_peer": "Reconectar a los pares", "refill_create": "Para continuar, por favor crea una cartera de Bitcoin con la que recargar.", "refill_external": "Recargar con una cartera externa", "refill_lnd_balance": "Recargar saldo de la cartera Lightning", "sameWalletAsInvoiceError": "No puedes pagar una factura con la misma cartera que usaste para crearla.", - "title": "Administrar fondos", - "can_send": "Puede enviar", - "can_receive": "Puede recibir", - "view_logs": "Ver registros" + "title": "Administrar fondos" }, "lndViewInvoice": { "additional_info": "Información adicional", "for": "Para:", - "lightning_invoice": "Factura Lighting", - "open_direct_channel": "Abrir un canal directo con este nodo:", + "lightning_invoice": "Factura Lightning", "please_pay_between_and": "Paga entre {min} y {max}", - "please_pay": "Por favor, pague", - "preimage": "Preimage", - "sats": "sats.", - "wasnt_paid_and_expired": "Esta factura no fue pagada y ha expirado." + "please_pay": "Por favor, paga", + "preimage": "Preimagen", + "date_time": "Fecha y hora", + "wasnt_paid_and_expired": "Esta factura no fue pagada y ha expirado.", + "sats": "sats" }, "plausibledeniability": { "create_fake_storage": "Crear almacenamiento cifrado", - "create_password": "Crear contraseña", "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.", - "passwords_do_not_match": "Las contraseñas no coinciden. Por favor, inténtalo de nuevo.", - "retype_password": "Volver a escribir contraseña", - "success": "Completado", "title": "Negación plausible" }, "pleasebackup": { "ask": "¿Has guardado la frase de respaldo de tu cartera? Esta frase de respaldo es necesaria para acceder a tus fondos si pierdes este dispositivo. Sin la frase de respaldo, tus fondos se perderán permanentemente.", - "ask_no": "No, no lo he hecho", - "ask_yes": "Sí, lo he hecho", - "ok": "OK, ya lo 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.", + "ask_no": "No, no la he guardado.", + "ask_yes": "Sí, la he guardado.", + "ok": "OK, ya la he anotado.", + "ok_lnd": "OK, lo he guardado.", + "text": "Por favor, apunta esta frase mnemotécnica en un papel.\nSerá tu copia de seguridad y te permitirá restaurar la cartera en otro dispositivo.", "text_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" }, @@ -111,12 +100,16 @@ "details_create": "Crear", "details_label": "Descripción", "details_setAmount": "Recibir con monto", - "details_share": "Compartir", + "details_share": "Compartir...", + "address_not_found": "No se ha podido generar la dirección de recepción.", "header": "Recibir", + "reset": "Restablecer", "maxSats": "La cantidad máxima es {max} sats", "maxSatsFull": "La cantidad máxima es {max} sats o {currency}", "minSats": "La cantidad mínima es {min} sats", - "minSatsFull": "La cantidad mínima es {min} sats o {currency}" + "minSatsFull": "La cantidad mínima es {min} sats o {currency}", + "qrcode_for_the_address": "Código QR de la dirección", + "bip47_explanation": "Los códigos de pago son una dirección universal que evita revelar las direcciones de tu cartera. No todos los servicios los admiten." }, "send": { "provided_address_is_invoice": "Esta dirección parece ser para una factura Lightning. Por favor, ve a tu cartera Lightning para realizar el pago de esta factura.", @@ -129,38 +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_frozen": "{amount} de BTC está congelado", + "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", @@ -185,142 +177,162 @@ "permission_camera_message": "Necesitamos permiso para usar tu cámara.", "psbt_sign": "Firmar una transacción", "open_settings": "Abrir configuración", - "permission_storage_later": "Pregúntame luego", - "permission_storage_message": "BlueWallet necesita permiso de acceso a tu almacenamiento para guardar este archivo.", "permission_storage_denied_message": "BlueWallet no puede guardar este archivo. Por favor, abre los ajustes de tu dispositivo y permite el acceso al almacenamiento.", "permission_storage_title": "Permiso de acceso al almacenamiento", "psbt_clipboard": "Copiar al portapapeles", "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", - "qr_error_no_qrcode": "No hemos podido encontrar un código QR en la imagen seleccionada. Por favor asegúrate de que la imagen solo contiene un código QR y no otro tipo de contendido, como texto o botones.", "reset_amount": "Cantidad predeterminada", "reset_amount_confirm": "¿Quieres volver a la cantidad predeterminada?", "success_done": "Completado", - "txSaved": "El archivo de la transacción ({filePath}) ha sido guardado en tu carpeta de descargas.", + "details_insert_contact": "Insertar contacto", + "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}", + "please_complete_recipient_title": "Destinatario incompleto", + "please_complete_recipient_details": "Por favor, completa los datos del destinatario #{number} antes de añadir un nuevo destinatario.", + "details_frozen": "{amount} BTC está congelado.", + "details_scan_error": "Error al escanear", + "insert_custom_fee": "Introducir comisión", + "invalid_psbt": "PSBT inválido.", + "qr_error_no_qrcode": "No hemos podido encontrar un código QR válido en la imagen seleccionada. Asegúrate de que la imagen contiene únicamente un código QR y nada más, como texto o botones.", + "txSaved": "El archivo de la transacción ({filePath}) se ha guardado.", + "file_saved_at_path": "El archivo ({filePath}) se ha guardado.", + "cant_send_to_silentpayment_adress": "Esta cartera no puede enviar a direcciones Silent Payments", + "cant_send_to_bip47": "Esta cartera no puede enviar a códigos de pago BIP47", + "cant_find_bip47_notification": "Añade primero este código de pago a contactos", "problem_with_psbt": "Problema con PSBT" }, "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_discord": "Servidor de Discord", "about_sm_telegram": "Canal de Telegram", - "about_sm_twitter": "Síguenos en Twitter", - "advanced_options": "Opciones avanzadas", "biometrics": "Biometría", "biom_10times": "Has intentado introducir tu contraseña 10 veces. ¿Te gustaría reestablecer tu almacenamiento? Esta acción eliminará todas las carteras y desencriptará tu almacenamiento.", "biom_conf_identity": "Por favor confirma tu identidad.", - "biom_no_passcode": "Tu dispositivo no tiene una contraseña. Para continuar, por favor configura una contraseña en Ajustes.", "biom_remove_decrypt": "Todas tus carteras se eliminarán y tu almacenado será desencriptado. ¿Estás seguro de que quieres continuar?", "currency": "Divisa", - "currency_source": "El precio se obtiene de", "currency_fetch_error": "Se ha producido un error al obtener el tipo de cambio de la divisa seleccionada.", - "default_desc": "Cuando esté deshabilitado, BlueWallet abrirá por defecto la cartera seleccionada al iniciar la aplicación.", - "default_info": "Información por defecto", "default_title": "Al iniciar", - "default_wallets": "Ver todas las carteras", "electrum_connected": "Conectado", "electrum_connected_not": "Desconectado", - "electrum_error_connect": "No se ha podido conectar al servidor de Electrum", "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?", - "set_lndhub_as_default": "¿Establecer {url} como servidor LNDHub por defecto?", "electrum_settings_server": "Servidor Electrum", - "electrum_settings_explain": "Déjalo en blanco para usar el predeterminado.", "electrum_status": "Estado", - "electrum_clear_alert_title": "¿Limpiar historial?", - "electrum_clear_alert_message": "¿Quieres eliminar el historial de los servidores de Electrum?", - "electrum_clear_alert_cancel": "Cancelar", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Seleccionar", + "electrum_unable_to_connect": "Imposible conectar a {server}.", "electrum_reset": "Restablecer valores predeterminados", - "electrum_unable_to_connect": "Imposible conectar a {server}. ", - "electrum_history": "Historial del servidor", - "electrum_reset_to_default": "¿Estás seguro de querer reiniciar sus ajustes de Electrum por defecto? ", - "electrum_clear": "Limpiar", - "tor_supported": "Compatible con Tor", - "tor_unsupported": "Las conexiones Tor no son compatibles.", "encrypt_decrypt": "Desencriptar almacenamiento", "encrypt_decrypt_q": "¿Seguro que quieres desencriptar tu almacenamiento? Al hacerlo, se podrá acceder a tus carteras sin contraseña.", - "encrypt_enc_and_pass": "Encriptado y protegido mediante contraseña", "encrypt_title": "Seguridad", "encrypt_tstorage": "Almacenamiento", "encrypt_use": "Usar {type}", - "encrypt_use_expl": "{type} se utilizará para confirmar tu identidad antes de realizar una transacción, desbloquear, exportar o eliminar una billetera. {type} no se utilizará para desbloquear el almacenamiento encriptado.", "general": "General", - "general_adv_mode": "Modo avanzado", - "general_adv_mode_e": "Al activarlo podrás ver opciones avanzadas, como varios tipos de carteras, la posibilidad de especificar el LNDHub al que quieres conectar y entropía personalizada al crear una cartera.", "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", "last_updated": "Última actualización", "language_isRTL": "Al seleccionar otro idioma, será necesario reiniciar BlueWallet para mostrar los cambios.", - "lightning_error_lndhub_uri": "LndHub URI no válida", "lightning_saved": "Tus cambios se han guardado correctamente", "lightning_settings": "Configuración de Lightning", - "tor_settings": "Configuración de Tor", - "lightning_settings_explain": "Para conectar a tu propio nodo LND, por favor instala LndHub y escribe su URL aquí, en la pantalla de configuración. Las carteras creadas tras guardar los cambios se conectarán al LNDHub especificado.", "network": "Red", "network_broadcast": "Emitir transacción", "network_electrum": "Servidor Electrum", "not_a_valid_uri": "URI no válida", "notifications": "Notificaciones", - "open_link_in_explorer" : "Abrir enlace en el navegador", + "open_link_in_explorer": "Abrir enlace en el navegador", "password": "Contraseña", - "password_explain": "Crea la contraseña que usarás para descifrar el almacenamiento", - "passwords_do_not_match": "Contraseñas deben ser iguales", "plausible_deniability": "Negación plausible", "privacy": "Privacidad", "privacy_read_clipboard": "Leer portapapeles", "privacy_system_settings": "Configuración del sistema", "privacy_quickactions": "Atajos para tus carteras", - "privacy_quickactions_explanation": "Toca y mantén pulsado el icono de BlueWallet en tu pantalla de inicio para ver rápidamente el balance de tu cartera.", "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.", - "push_notifications": "Notificaciones push", "rate": "Tasa", - "retype_password": "Introduce la contraseña otra vez", - "selfTest": "Self-Test", + "selfTest": "Autodiagnóstico", "save": "Guardar", "saved": "Guardado", - "success_transaction_broadcasted" : "¡Listo! ¡Tu transacción ha sido emitida!", "total_balance": "Balance total", "total_balance_explanation": "Muestra el balance total de todas tus carteras en los widgets de tu pantalla principal.", "widgets": "Widgets", - "tools": "Herramientas" + "tools": "Herramientas", + "performance_score": "Puntuación de rendimiento: {num}", + "run_performance_test": "Probar rendimiento", + "block_explorer_invalid_custom_url": "La URL proporcionada no es válida. Por favor, introduce una URL válida que empiece por http:// o https://.", + "privacy_temporary_screenshots": "Permitir captura de pantalla", + "privacy_temporary_screenshots_instructions": "La protección contra captura de pantalla se desactivará temporalmente, permitiendo realizar capturas y grabaciones de pantalla. La protección se reactivará automáticamente cuando cierres y vuelvas a abrir BlueWallet.", + "biometrics_no_longer_available": "Los ajustes de tu dispositivo han cambiado y ya no coinciden con los ajustes de seguridad seleccionados en la aplicación. Por favor, vuelve a habilitar la biometría o el código de acceso y reinicia la aplicación para aplicar estos cambios.", + "biom_no_passcode": "Tu dispositivo no tiene un código de acceso ni biometría habilitados. Para continuar, por favor configura un código de acceso o biometría en la aplicación Ajustes.", + "currency_source": "La tasa se obtiene de", + "donate": "Donar", + "donate_description": "¡Ayúdanos a mantener Blue gratis!", + "electrum_error_connect": "No se puede conectar al servidor Electrum proporcionado", + "electrum_error_connect_tor": "No se puede conectar al servidor Electrum proporcionado. Por favor, asegúrate de que la aplicación Orbot está conectada y vuelve a intentarlo.", + "set_lndhub_as_default": "¿Establecer {url} como servidor LNDhub por defecto?", + "electrum_preferred_server": "Servidor preferido", + "electrum_preferred_server_description": "Introduce el servidor que quieres que tu cartera utilice para todas las actividades de Bitcoin. Una vez establecido, tu cartera usará exclusivamente este servidor para comprobar balances, enviar transacciones y obtener datos de la red. Asegúrate de confiar en este servidor antes de configurarlo.", + "electrum_history": "Historial", + "electrum_reset_to_default": "Esto permitirá que BlueWallet elija aleatoriamente un servidor de la lista de servidores.", + "electrum_reset_to_default_and_clear_history": "Restablecer valores predeterminados y borrar historial", + "encrypt_enc_and_pass": "Protegido con contraseña", + "encrypt_storage_explanation_headline": "Habilitar cifrado del almacenamiento", + "encrypt_storage_explanation_description_line1": "Habilitar el cifrado del almacenamiento añade una capa adicional de protección a tu aplicación al asegurar la forma en que tus datos se almacenan en el dispositivo. Esto dificulta que cualquiera acceda a tu información sin permiso.", + "encrypt_storage_explanation_description_line2": "Sin embargo, es importante saber que este cifrado solo protege el acceso a tus carteras almacenadas en el keychain del dispositivo. No pone una contraseña ni ninguna protección adicional a las carteras en sí.", + "i_understand": "Entiendo", + "block_explorer": "Explorador de bloques", + "block_explorer_preferred": "Usar el explorador de bloques preferido", + "block_explorer_error_saving_custom": "Error al guardar el explorador de bloques preferido", + "set_as_preferred": "Establecer como preferido", + "set_as_preferred_electrum": "Establecer {host}:{port} como servidor preferido deshabilitará la conexión aleatoria a un servidor sugerido.", + "encrypted_feature_disabled": "Esta función no puede usarse con el almacenamiento cifrado habilitado.", + "encrypt_use_expl": "Se usará {type} para confirmar tu identidad antes de realizar una transacción, desbloquear, exportar o eliminar una cartera.", + "biometrics_fail": "Si {type} no está habilitado, o no consigue desbloquear, puedes usar el código de acceso de tu dispositivo como alternativa.", + "license": "Licencia", + "lightning_error_lndhub_uri": "URI de LNDhub no válida", + "lightning_error_lndhub_uri_tor": "URI de LNDhub no válida. Por favor, asegúrate de que la aplicación Orbot está conectada y vuelve a intentarlo.", + "lightning_settings_explain": "Para conectarte a tu propio nodo LND, por favor instala LNDhub e introduce su URL aquí en los ajustes. Ten en cuenta que solo las carteras creadas después de guardar los cambios se conectarán al LNDhub especificado.", + "lndhub_github": "Repositorio de GitHub", + "electrum_suggested_description": "Cuando no hay un servidor preferido configurado, se seleccionará aleatoriamente un servidor sugerido para usar.", + "password_explain": "Introduce la contraseña que usarás para desbloquear tu almacenamiento.", + "privacy_quickactions_explanation": "Mantén pulsado el icono de la aplicación BlueWallet para ver rápidamente el balance de tu cartera.", + "push_notifications_explanation": "Al habilitar las notificaciones, el token de tu dispositivo se enviará al servidor, junto con las direcciones de las carteras y los IDs de transacción de todas las carteras y transacciones realizadas después de habilitar las notificaciones. El token del dispositivo se usa para enviar notificaciones, y la información de la cartera nos permite avisarte sobre Bitcoin entrante o confirmaciones de transacciones.\n\nSolo se transmite información posterior a la habilitación de las notificaciones—no se recopila nada anterior.\n\nDeshabilitar las notificaciones eliminará toda esta información del servidor. Además, eliminar una cartera de la aplicación también eliminará su información asociada del servidor.", + "success_transaction_broadcasted": "¡Tu transacción se ha emitido correctamente!" }, "notifications": { - "would_you_like_to_receive_notifications": "¿Quires recibir notificaciones cuando detectemos transferencias entrantes?", - "no_and_dont_ask": "No, y no vuelvas a preguntarme", - "ask_me_later": "Pregúntame después" + "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." }, "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—Reemplazo por comisión.", "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.", @@ -328,91 +340,112 @@ "cpfp_title": "Aumentar comisión (CPFP)", "details_balance_hide": "Esconder balance", "details_balance_show": "Mostrar balance", - "details_block": "Altura del bloque", "details_copy": "Copiar", - "details_copy_amount": "Copiar cantidad", "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", - "transaction_note_saved": "La nota de la transacción ha sido guardada.", - "details_show_in_block_explorer": "Mostrar en explorador de bloques", - "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": "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", + "incoming_transaction": "Transacción entrante", + "outgoing_transaction": "Transacción saliente", + "expired_transaction": "Transacción caducada", + "pending_transaction": "Transacción pendiente", + "offchain": "off-chain", + "onchain": "on-chain", + "list_title_sent": "Enviado", + "watchOnlyWarningTitle": "Aviso de seguridad", + "watchOnlyWarningDescription": "Ten cuidado con los estafadores que suelen usar carteras de solo lectura para engañar a los usuarios. Estas carteras no te permiten controlar ni enviar fondos; solo te dejan ver el balance.", + "custom_fee_warning_title": "Aviso", + "custom_fee_warning_description": "Las comisiones por debajo de 1 sat/vB son válidas, pero pueden no ser retransmitidas debido a las políticas de los nodos.", + "details_eta_analyzing": "Analizando...", + "details_sent": "Enviado", + "details_section": "Detalles", + "details_explorer": "explorador", + "details_network_fee": "Comisión de red", + "details_to_address": "Para", + "details_note": "Nota", + "details_add_note": "añadir", + "details_advanced": "Avanzado", + "details_fee_rate": "Tasa de comisión", + "details_size": "Tamaño", + "details_virtual_size": "Tamaño virtual", + "details_tx_hex": "Hex de la tx", + "details_inputs_count": "Entradas ({count})", + "details_outputs_count": "Salidas ({count})", + "details_id": "ID" }, "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_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", - "add_lndhub": "Conecta a tu LDNHub", - "add_lndhub_error": "La dirección proporcionada no es válida para un nodo LNDHub.", "add_lndhub_placeholder": "La dirección de tu nodo", "add_placeholder": "Mi primera cartera", "add_title": "Añadir cartera", "add_wallet_name": "Nombre", "add_wallet_type": "Tipo de cartera", - "balance": "Balance", "clipboard_bitcoin": "Tienes una dirección de Bitcoin en tu portapapeles. ¿Quieres usarla para una transacción?", "clipboard_lightning": "Tienes una factura Lightning en tu portapapeles. ¿Quieres usarla para una transacción?", "details_address": "Dirección", "details_advanced": "Avanzado", "details_are_you_sure": "¿Estás seguro?", "details_connected_to": "Conectado a", - "details_del_wb_err": "El balance introducido no coincide con el balance de esta cartera. Por favor, inténtelo de nuevo.", "details_del_wb_q": "Esta cartera tiene saldo. Antes de proceder, ten en cuenta que no podrás recuperar los fondos sin la semilla de esta cartera. Para evitar el borrado accidental, por favor introduce los {balance} satoshis que contiene esta cartera.", "details_delete": "Eliminar", "details_delete_wallet": "Borrar cartera", "details_derivation_path": "ruta de derivación", - "details_display": "mostrar en la lista de carteras", "details_export_backup": "Exportar / Guardar", "details_export_history": "Exportar el historial a CSV", "details_master_fingerprint": "Huella dactilar maestra", "details_multisig_type": "multifirma", - "details_no_cancel": "No, cancelar", - "details_save": "Guardar", "details_show_xpub": "Mostrar el XPUB de la cartera", "details_show_addresses": "Mostrar dirección", "details_title": "Cartera", + "wallets": "Carteras", "details_type": "Tipo", "details_use_with_hardware_wallet": "Usar con cartera de hardware", - "details_wallet_updated": "Cartera actualizada", - "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.", @@ -422,44 +455,80 @@ "import_discovery_subtitle": "Elige una cartera descubierta", "import_discovery_derivation": "Utilizar una ruta de derivación personalizada", "import_discovery_no_wallets": "No se encontraron carteras.", - "import_derivation_found": "encontrada", - "import_derivation_found_not": "no encontrada", - "import_derivation_loading": "cargando...", - "import_derivation_subtitle": "Introduce la ruta de derivación personalizada e intentaremos de descubrir tu cartera", "import_derivation_title": "Ruta de derivación", - "import_derivation_unknown": "desconocida", - "import_wrong_path": "ruta de derivación incorrecta", "list_create_a_button": "Añadir", "list_create_a_wallet": "Añadir cartera", - "list_create_a_wallet_text": "Es gratis y puedes crear\ntodas las que quieras", "list_empty_txs1": "Tus transacciones aparecerán aquí", "list_empty_txs1_lightning": "Usa carteras Lightning para tus transacciones diarias. Tienen comisiones muy bajas y una velocidad de vértigo.", "list_empty_txs2": "Empieza con tu cartera.", "list_empty_txs2_lightning": "\nPara comenzar a usarlo, toca en \"administrar fondos\" y añade algo de saldo.", "list_latest_transaction": "Última transacción", - "list_ln_browser": "Navegador LApp", "list_long_choose": "Elegir foto", - "list_long_clipboard": "Copiar del portapapeles", + "paste_from_clipboard": "Pegar", + "import_file": "Importar archivo", "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).", - "reorder_title": "Reorganizar carteras", - "reorder_instructions": "Toca y arrastra una wallet a lo largo de la lista.", "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", - "warning_do_not_disclose": "¡Advertencia! No comparta esta información", "add_ln_wallet_first": "Primero tienes que agregar una cartera Lightning.", - "identity_pubkey": "Identity Pubkey", - "xpub_title": "XPUB de la cartera" + "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?", + "add_entropy_bytes": "{bytes} bytes de entropía", + "add_lndhub": "Conectar a tu LNDhub", + "add_lndhub_error": "La dirección del nodo proporcionada no es un nodo LNDhub válido.", + "add_wallet_seed_length": "Longitud de la semilla", + "add_wallet_seed_length_12": "12 palabras", + "add_wallet_seed_length_24": "24 palabras", + "clear_clipboard_on_import": "Borrar portapapeles al importar", + "details_del_wb_err": "La cantidad de balance proporcionada no coincide con el balance de esta cartera. Por favor, inténtalo de nuevo.", + "details_display": "Mostrar en la pantalla de inicio", + "swipe_balance_hide": "Ocultar", + "swipe_balance_show": "Mostrar", + "drag_to_reorder": "Arrastra para reordenar", + "clear_search": "Borrar búsqueda", + "import_success_watchonly": "Tu cartera ha sido importada correctamente. ADVERTENCIA: esta es una cartera de solo lectura, NO puedes gastar desde ella.", + "learn_more": "Más información", + "import_discovery_offline": "BlueWallet está actualmente en modo sin conexión. En este modo, no puede verificar la existencia de la cartera, por lo que tendrás que seleccionar la correcta manualmente", + "import_derivation_found": "Encontrada", + "import_derivation_found_not": "No encontrada", + "import_derivation_loading": "Cargando...", + "import_derivation_subtitle": "Introduce una ruta de derivación personalizada e intentaremos descubrir tu cartera.", + "import_derivation_unknown": "Desconocido", + "import_wrong_path": "Ruta de derivación incorrecta", + "list_create_a_wallet_text": "Es gratis, y puedes crear \ntantas como quieras.", + "manage_title": "Administrar carteras", + "no_results_found": "No se han encontrado resultados.", + "warning_do_not_disclose": "Nunca compartas la información de abajo", + "scan_import": "Escanea este código QR para importar tu cartera en otra aplicación.", + "write_down_header": "Crear una copia de seguridad manual", + "write_down": "Apunta y guarda de forma segura estas palabras. Úsalas para restaurar tu cartera más tarde.", + "wallet_type_this": "El tipo de esta cartera es {type}.", + "share_number": "Compartir {number}", + "copy_ln_url": "Copia y guarda de forma segura esta URL para restaurar tu cartera más tarde.", + "copy_ln_public": "Copia y guarda de forma segura esta información para restaurar tu cartera más tarde.", + "manage_wallets_search_placeholder": "Buscar carteras, direcciones, transacciones y comentarios", + "more_info": "Más información", + "details_delete_wallet_error_message": "Ha habido un problema al confirmar si esta cartera fue eliminada de las notificaciones—esto podría deberse a un problema de red o una conexión deficiente. Si continúas, podrías seguir recibiendo notificaciones de transacciones relacionadas con esta cartera, incluso después de eliminarla.", + "details_delete_anyway": "Eliminar de todos modos" + }, + "total_balance_view": { + "title": "Balance total", + "display_in_bitcoin": "Mostrar en Bitcoin", + "hide": "Ocultar", + "display_in_sats": "Mostrar en sats", + "display_in_fiat": "Mostrar en {currency}", + "explanation": "Visualiza el balance total de todas tus carteras en la pantalla de resumen." }, "multisig": { - "multisig_vault": "Vault", + "multisig_vault": "Vault multifirma", "default_label": "Vault multifirma", "multisig_vault_explain": "La mejor seguridad para grandes cantidades", "provide_signature": "Proporcionar firma", @@ -469,15 +538,14 @@ "fee_btc": "{number} BTC", "confirm": "Confirmar", "header": "Enviar", - "share": "Compartir", "view": "Ver", "manage_keys": "Administrar claves", "how_many_signatures_can_bluewallet_make": "Cuántas firmas puede hacer BlueWallet", "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", @@ -496,20 +564,14 @@ "quorum_header": "quórum", "of": "de", "wallet_type": "Tipo de cartera", - "invalid_mnemonics": "Esta frase mnemotécnica no es válida", - "invalid_cosigner": "Los datos del co-firmante no son válidos", "not_a_multisignature_xpub": "¡Esto no es el XPUB de una cartera multifirma!", - "invalid_cosigner_format": "Co-firmante incorrecto: no es un co-firmante de {format}.", "create_new_key": "Crear una nueva", "scan_or_open_file": "Escanear o abrir archivo", "i_have_mnemonics": "Tengo una semilla para esta llave.", "type_your_mnemonics": "Introduce una semilla para importar la llave de tu Vault", - "this_is_cosigners_xpub": "Este es el XPUB del co-firmante, listo para ser importado en otra cartera. Es seguro compartirla.", "wallet_key_created": "La clave de tu Vault ha sido creada. Tómate un momento para anotar la semilla mnemotécnica.", "are_you_sure_seed_will_be_lost": "¿Estás seguro? Tu semilla mnemotécnica se perderá si no tienes una copia de seguridad", "forget_this_seed": "Olvida esta semilla y usa XPUB", - "view_edit_cosigners": "Ver/editar co-firmantes", - "this_cosigner_is_already_imported": "Este co-firmante ya ha sido importado", "export_signed_psbt": "Exportar PSBT firmado", "input_fp": "introduce la huella dactilar", "input_fp_explain": "Déjalo en blanco para usar el predeterminado (00000000)", @@ -518,16 +580,30 @@ "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", - "ms_help_5": "Por defecto, BlueWallet creará una Vault 2-de-3. Para crear una con diferente quórum o para cambiar el tipo de dirección, activa las opciones avanzadas en Ajustes - General." + "ms_help_5": "Por defecto, BlueWallet creará una Vault 2-de-3. Para crear una con diferente quórum o para cambiar el tipo de dirección, activa las opciones avanzadas en Ajustes - General.", + "provide_signature_details": "Usa tu dispositivo y la cartera donde reside la clave para firmar esta transacción", + "provide_signature_details_bluewallet": "En BlueWallet, ve al menú de la pantalla Enviar y selecciona ", + "provide_signature_next_steps": "Escanea o importa la transacción firmada", + "provide_signature_next_steps_details": "Una vez que tu cartera haya firmado correctamente la transacción, escanea el código QR proporcionado o importa el archivo correspondiente y, a continuación, revisa todos los detalles de la transacción antes de emitirla.", + "share": "Compartir...", + "shared_key_detected": "Cofirmante compartido", + "shared_key_detected_question": "Se ha compartido un cofirmante contigo, ¿quieres importarlo?", + "invalid_mnemonics": "Esta frase mnemónica no parece ser válida.", + "invalid_cosigner": "Datos de cofirmante no válidos", + "invalid_cosigner_format": "Cofirmante incorrecto: este no es un cofirmante para el formato {format}.", + "this_is_cosigners_xpub": "Este es el XPUB del cofirmante—listo para ser importado en otra cartera. Es seguro compartirlo.", + "this_is_cosigners_xpub_airdrop": "Si compartes vía AirDrop, los receptores deben estar en la pantalla de coordinación.", + "view_edit_cosigners": "Ver/editar cofirmantes", + "this_cosigner_is_already_imported": "Este cofirmante ya está importado." }, "is_it_my_address": { "title": "¿Es esta mi dirección?", @@ -541,14 +617,21 @@ "change": "Cambio", "coins_selected": "({number}) monedas (coins) seleccionadas", "selected_summ": "{value} seleccionado", - "empty": "Esta cartera no tiene fondos en este momento", "freeze": "Congelar", "freezeLabel": "Congelar", "freezeLabel_un": "Descongelar", "header": "Coin control", "use_coin": "Usar moneda (coin)", "use_coins": "Usar monedas", - "tip": "Te permite ver, etiquetar, congelar o seleccionar monedas para mejorar la organización de las carteras." + "tip": "Te permite ver, etiquetar, congelar o seleccionar monedas para mejorar la organización de las carteras.", + "sort_label": "Etiqueta", + "sort_status": "Estado", + "empty": "Esta cartera no tiene monedas en este momento.", + "sort_asc": "Ascendente", + "sort_desc": "Descendente", + "sort_height": "Altura", + "sort_value": "Valor", + "sort_by": "Ordenar por" }, "units": { "BTC": "BTC", @@ -557,6 +640,8 @@ "sats": "sats" }, "addresses": { + "copy_private_key": "Copiar llave privada", + "sensitive_private_key": "Advertencia: las llaves privadas son extremadamente sensibles. ¿Continuar?", "sign_title": "Firmar/Verificar mensaje", "sign_help": "Aquí puedes crear o verificar una firma criptográfica basada en una dirección de Bitcoin.", "sign_sign": "Firmar", @@ -574,18 +659,46 @@ }, "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" + }, + "autofill_word": { + "title": "Última palabra de la semilla", + "enter": "Introduce tu frase mnemónica parcial", + "generate_word": "Generar la última palabra", + "error": "La entrada no es una frase mnemónica parcial de 11 o 23 palabras. Por favor, inténtalo de nuevo." + }, + "bip47": { + "payment_code": "Código de pago", + "contacts": "Contactos", + "bip47_explain": "Código reutilizable y compartible", + "bip47_explain_subtitle": "BIP47", + "purpose": "Código reutilizable y compartible (BIP47)", + "pay_this_contact": "Pagar a este contacto", + "rename_contact": "Renombrar contacto", + "copy_payment_code": "Copiar código de pago", + "hide_contact": "Ocultar contacto", + "rename": "Renombrar", + "provide_name": "Proporciona un nuevo nombre para este contacto", + "add_contact": "Añadir contacto", + "provide_payment_code": "Proporciona el código de pago", + "invalid_pc": "Código de pago no válido", + "notification_tx_unconfirmed": "La transacción de notificación aún no está confirmada, por favor espera", + "failed_create_notif_tx": "Error al crear la transacción on-chain", + "onchain_tx_needed": "Se necesita una transacción on-chain", + "notif_tx_sent": "Transacción de notificación enviada. Por favor, espera a que se confirme", + "notif_tx": "Transacción de notificación", + "not_found": "Código de pago no encontrado" } } diff --git a/loc/es_419.json b/loc/es_419.json index faf3d4fb0ff..4c62d96c5cf 100644 --- a/loc/es_419.json +++ b/loc/es_419.json @@ -2,34 +2,34 @@ "_": { "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", - "disabled": "Desactivado", "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", "wallet_key": "Clave de la billetera", - "invalid_animated_qr_code_fragment": "Fragmento inválido de Código QR animado. Por favor intenta de nuevo.", - "file_saved": "El archivo {filePath} se ha guardado en tu {destination}.", - "downloads_folder": "Carpeta de descargas", "close": "Cerrar", "change_input_currency": "Cambiar moneda de entrada", "refresh": "Actualizar", - "more": "Más", - "pick_image": "Elegir imagen de la biblioteca", - "pick_file": "Escoge un archivo", + "pick_image": "Elige de la biblioteca", + "pick_file": "Elegir archivo", "enter_amount": "Ingresa la cantidad", - "qr_custom_input_button": "Pulsa 10 veces para ingresar una entrada personalizada" - }, - "alert": { - "default": "Alerta" + "qr_custom_input_button": "Pulsa 10 veces para ingresar una entrada personalizada", + "unlock": "Desbloquear", + "port": "Puerto", + "ssl_port": "Puerto SSL", + "suggested": "Sugerido" }, "azteco": { "codeIs": "Tu código de cupón es", @@ -38,12 +38,14 @@ "redeem": "Canjear en billetera", "redeemButton": "Canjear", "success": "Éxito", + "successMessage": "¡Cupón canjeado correctamente! Los fondos deberían llegar a tu billetera Bitcoin en breve.", "title": "Canjear cupón Azte.co" }, "entropy": { "save": "Guardar", - "title": "Entropía ", - "undo": "Deshacer" + "title": "Entropía", + "undo": "Deshacer", + "amountOfEntropy": "{bits} de {limit} bits" }, "errors": { "broadcast": "Error de transmisión", @@ -51,84 +53,67 @@ "network": "Error de red" }, "lnd": { - "active": "Activo", - "inactive": "Inactivo", - "channels": "Canales", - "no_channels": "Sin canales", - "claim_balance": "Reclamar saldo {balance}", - "close_channel": "Cerrar canal", - "new_channel": "Nuevo canal", - "errorInvoiceExpired": "Factura expirada", - "force_close_channel": "¿Forzar el cierre del canal?", + "errorInvoiceExpired": "Factura caducada.", "expired": "Expirado", - "node_alias": "Alias del nodo", "expiresIn": "Expira en {time} minutos", "payButton": "Pagar", + "payment": "Pago", "placeholder": "Factura o dirección", - "open_channel": "Abrir canal", - "funding_amount_placeholder": "Importe de la financiación, por ejemplo 0,001", - "opening_channnel_for_from": "Abrir canal para la billetera {forWalletLabel}, por la financiación de {fromWalletLabel}", - "are_you_sure_open_channel": "¿Estás seguro de que quieres abrir este canal?", - "potentialFee": "Tasas potenciales: {fee}", - "remote_host": "Host remoto", + "potentialFee": "Comisión potencial: {fee}", "refill": "Recarga", - "reconnect_peer": "Reconectar a los pares", "refill_create": "Para continuar, crea una billetera Bitcoin para recargar.", "refill_external": "Recarga con billetera externa", "refill_lnd_balance": "Recargar el saldo de la billetera Lightning", - "sameWalletAsInvoiceError": "No se puede pagar una factura con la misma billetera utilizada para crearla.", - "title": "Manejar fondos", - "can_send": "Puede enviar", - "can_receive": "Puede recibir", - "view_logs": "Ver registros" + "sameWalletAsInvoiceError": "No puedes pagar una factura con la misma billetera que usaste para crearla.", + "title": "Manejar fondos" }, "lndViewInvoice": { "additional_info": "Información adicional", "for": "Para:", "lightning_invoice": "Factura Lightning", - "open_direct_channel": "Abrir un canal directo con este nodo:", "please_pay_between_and": "Paga entre {min} y {max}", - "please_pay": "Pagar por favor", + "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." }, "plausibledeniability": { "create_fake_storage": "Crear almacenamiento encriptado", - "create_password": "Crear una contraseña", "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.", - "passwords_do_not_match": "Las contraseñas no coinciden, intenta nuevamente", - "retype_password": "Vuelve a escribir la contraseña", - "success": "Éxito", "title": "Negación plausible" }, "pleasebackup": { "ask": "¿Has guardado la frase de respaldo de tu billetera? Esta frase de respaldo es necesaria para acceder a tus fondos en caso de que pierdas este dispositivo. Sin la frase de respaldo, tus fondos se perderán permanentemente.", - "ask_no": "No la tengo", - "ask_yes": "Sí, la tengo", - "ok": "OK, lo escribí", - "ok_lnd": "OK, lo he guardado", + "ask_no": "No, no tengo.", + "ask_yes": "Sí tengo.", + "ok": "OK, lo escribí.", + "ok_lnd": "OK, lo he guardado.", "text": "Tómate un momento para escribir esta frase mnemotécnica en una hoja de papel.\nEs tu copia de seguridad y puedes usarla para recuperar la billetera.", "text_lnd": "Guarda esta copia de seguridad de la billetera. Te permite restaurar la billetera en caso de pérdida.", - "title": "Tu billetera ha sido creada" + "title": "Tu Billetera está creada." }, "receive": { "details_create": "Crear", "details_label": "Descripción", "details_setAmount": "Recibir con monto", - "details_share": "Compartir", + "details_share": "Compartir…", + "address_not_found": "No es posible generar la dirección de recepción.", "header": "Recibir", + "reset": "Reiniciar", "maxSats": "La cantidad máxima es {max} sats", "maxSatsFull": "La cantidad máxima es {max} sats o {currency}", "minSats": "La cantidad mínima es {min} sats", - "minSatsFull": "La cantidad mínima es {min} sats o {currency}" + "minSatsFull": "La cantidad mínima es {min} sats o {currency}", + "qrcode_for_the_address": "Código QR para la dirección", + "bip47_explanation": "Los códigos de pago son una dirección universal que evita revelar la dirección de tu billetera. No todos los servicios los admiten." }, "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", @@ -139,15 +124,22 @@ "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.", "create_to": "A", "create_tx_size": "Tamaño de transacción", "create_verify": "Verificar en coinb.in", + "details_insert_contact": "Insertar contacto", "details_add_rec_add": "Agregar destinatario", "details_add_rec_rem": "Eliminar destinatario", + "details_add_recc_rem_all_alert_description": "¿Estás seguro de que quieres eliminar todos los destinatarios?", + "details_add_rec_rem_all": "Eliminar todos los destinatarios", + "details_recipients_title": "Destinatarios", + "details_recipient_title": "Destinatario #{number} de #{total}", + "please_complete_recipient_title": "Destinatario incompleto", + "please_complete_recipient_details": "Completa los detalles del destinatario #{number} antes de agregar un nuevo destinatario.", "details_address": "Dirección", "details_address_field_is_not_valid": "La dirección no es válida.", "details_adv_fee_bump": "Permitir aumento de tarifas", @@ -160,13 +152,14 @@ "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_frozen": "{amount} BTC está congelado", + "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", "details_scan": "Escanear", "details_scan_hint": "Toca dos veces para escanear o importar un destino", + "details_scan_error": "Error de escaneo", "details_total_exceeds_balance": "La cantidad de envío excede el saldo disponible.", "details_total_exceeds_balance_frozen": "El monto del envío excede el saldo disponible. Ten en cuenta que las monedas congeladas están excluidas.", "details_unrecognized_file_format": "Formato de archivo no reconocido", @@ -180,9 +173,10 @@ "fee_1d": "1d", "fee_3h": "3h", "fee_custom": "Personalizado", + "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,23 +186,26 @@ "input_total": "Total:", "permission_camera_message": "Necesitamos tu permiso para usar tu cámara", "psbt_sign": "Firmar una transacción", + "invalid_psbt": "PSBT proporcionado no válido.", "open_settings": "Abrir configuraciones", - "permission_storage_later": "Pregúntame luego", - "permission_storage_message": "BlueWallet necesita su permiso para acceder a su almacenamiento para guardar este archivo.", "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 en la imagen seleccionada. Asegúrate de que la imagen contenga solo un código QR y no contenga contenido adicional como texto o botones.", + "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.", "reset_amount": "Restablecer monto", "reset_amount_confirm": "¿Te gustaría restablecer la cantidad?", "success_done": "Hecho", - "txSaved": "El archivo ({filePath}) se ha guardado en tu carpeta de Descargas.", + "txSaved": "El archivo de transacción ({filePath}) se ha guardado.", + "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", "problem_with_psbt": "Problema con PSBT" }, "settings": { @@ -222,28 +219,30 @@ "performance_score": "Puntuación de rendimiento: {num}", "run_performance_test": "Prueba de rendimiento", "about_selftest": "Ejecutar auto-prueba", - "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. ", + "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_ok": "Todas las pruebas internas han pasado satisfactoriamente. La billetera funciona bien.", "about_sm_github": "GitHub", - "about_sm_discord": "Servidor Discord", - "about_sm_telegram": "Chat de Telegram ", - "about_sm_twitter": "Siguenos en Twitter", - "advanced_options": "Opciones Avanzadas", + "about_sm_telegram": "Chat de Telegram", + "privacy_temporary_screenshots": "Permitir captura de pantalla", + "privacy_temporary_screenshots_instructions": "La protección contra capturas de pantalla se desactivará temporalmente, lo que permitirá realizar capturas y grabaciones de pantalla. La protección se reactivará automáticamente cuando cierres y vuelvas a abrir BlueWallet.", "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. Para continuar, configura un código de acceso 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": "El precio 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_desc": "Cuando está deshabilitado, BlueWallet abrirá inmediatamente la billetera seleccionada al inicio", - "default_info": "Información por defecto", "default_title": "Al inicio", - "default_wallets": "Ver todas las Billeteras", + "general": "General", + "donate": "Donar", + "donate_description": "¡Ayúdanos a mantener Blue libre!", "electrum_connected": "Conectado", "electrum_connected_not": "No Conectado", "electrum_error_connect": "No se puede conectar al servidor Electrum proporcionado", + "electrum_error_connect_tor": "No se puede conectar al servidor Electrum proporcionado. Asegúrate de que la aplicación Orbot esté conectada y vuelve a intentarlo.", "lndhub_uri": "Ej.: {example}", "electrum_host": "Ej.: {example}", "electrum_offline_mode": "Modo offline", @@ -251,170 +250,209 @@ "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_lndhub_as_default": "¿Establecer {url} como servidor LNDHub 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_settings_explain": "Déjalo en blanco para usar el predeterminado.", "electrum_status": "Estado", - "electrum_clear_alert_title": "¿Borrar historial?", - "electrum_clear_alert_message": "¿Quieres borrar el historial de los servidores de Electrum?", - "electrum_clear_alert_cancel": "Cancelar", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Seleccionar", - "electrum_reset": "Restablecer a predeterminado", + "electrum_preferred_server": "Servidor preferido", + "electrum_preferred_server_description": "Introduce el servidor que deseas que tu billetera utilice para todas las actividades de Bitcoin. Una vez configurado, tu billetera utilizará exclusivamente este servidor para comprobar saldos, enviar transacciones y obtener datos de la red. Asegúrate de que confías en este servidor antes de configurarlo.", "electrum_unable_to_connect": "No se puede conectar al {server}.", - "electrum_history": "Historial del servidor", - "electrum_reset_to_default": "¿Estás seguro de querer restablecer la configuración de Electrum a los valores predeterminados?", - "electrum_clear": "Limpiar", - "tor_supported": "Tor soportado", - "tor_unsupported": "Las conexiones Tor no son compatibles.", + "electrum_history": "Historial", + "electrum_reset_to_default": "Esto permitirá que BlueWallet elija aleatoriamente un servidor de la lista de servidores.", + "electrum_reset": "Restablecer a predeterminado", + "electrum_reset_to_default_and_clear_history": "Restablecer valores predeterminados y borrar historial", "encrypt_decrypt": "Descifrar Almacenamiento", "encrypt_decrypt_q": "¿Estás seguro de que deseas descifrar tu almacenamiento? Esto permitirá acceder a tus billeteras sin una contraseña.", - "encrypt_enc_and_pass": "Encriptado y protegido con contraseña", + "encrypt_enc_and_pass": "Protegido con contraseña", + "encrypt_storage_explanation_headline": "Habilitar cifrado de almacenamiento", + "encrypt_storage_explanation_description_line1": "Habilitar el cifrado de almacenamiento agrega una capa adicional de protección a tu aplicación al proteger la forma en que se almacenan tus datos en tu dispositivo. Esto hace que sea más difícil para cualquier persona acceder a tu información sin permiso.", + "encrypt_storage_explanation_description_line2": "Sin embargo, es importante saber que este cifrado sólo protege el acceso a tus billeteras almacenadas en el llavero del dispositivo. No pone una contraseña ni ninguna protección adicional en las billeteras.", + "i_understand": "Entiendo", + "block_explorer": "Explorador de bloques", + "block_explorer_preferred": "Utiliza el explorador de bloques preferido", + "block_explorer_error_saving_custom": "Error al guardar el explorador de bloques preferido", "encrypt_title": "Seguridad", "encrypt_tstorage": "Almacenamiento", "encrypt_use": "Usar {type}", - "encrypt_use_expl": "{type} se utilizará para confirmar tu identidad antes de realizar una transacción, desbloquear, exportar o eliminar una billetera. {type} no se utilizará para desbloquear el almacenamiento encriptado.", - "general": "General", - "general_adv_mode": "Modo Avanzado", - "general_adv_mode_e": "Cuando esté habilitado, verás opciones avanzadas como diferentes tipos de billetera, la capacidad de especificar la instancia de LNDHub a la que deseas conectarte y la entropía personalizada durante la creación de la billetera.", + "set_as_preferred": "Establecer como preferido", + "set_as_preferred_electrum": "Establecer {host}:{port} como servidor preferido deshabilitará la conexión a un servidor sugerido al azar.", + "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_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", "last_updated": "Última actualización", "language_isRTL": "Es necesario reiniciar BlueWallet para que la orientación del idioma surta efecto.", - "lightning_error_lndhub_uri": "URI de LNDHub inválido", + "license": "Licencia", + "lightning_error_lndhub_uri": "URI de LNDhub no válido", + "lightning_error_lndhub_uri_tor": "La URL de LNDhub no es válida. Asegúrate de que la aplicación Orbot esté conectada y vuelve a intentarlo.", "lightning_saved": "Tus cambios han sido guardados correctamente.", "lightning_settings": "Configuración de Lightning", - "tor_settings": "Configuración de Tor", - "lightning_settings_explain": "Para conectarte a tu propio nodo LND, instala LNDHub y coloca tu 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.", + "lightning_settings_explain": "Para conectarte a tu propio nodo LND, instala LNDhub y coloca su URL aquí en la configuración. Ten en cuenta que solo las billeteras creadas después de guardar los cambios se conectarán al LNDhub especificado.", + "lndhub_github": "Repositorio de GitHub", "network": "Red", "network_broadcast": "Publicar transacción", "network_electrum": "Servidor Electrum", + "electrum_suggested_description": "Cuando no se establece un servidor preferido, se seleccionará un servidor sugerido para su uso al azar.", "not_a_valid_uri": "URI inválido", "notifications": "Notificaciones", "open_link_in_explorer": "Abrir enlace en el explorador", "password": "Contraseña", - "password_explain": "Crea la contraseña que usarás para desencriptar el almacenamiento", - "passwords_do_not_match": "Las contraseñas no coinciden.", + "password_explain": "Ingresa la contraseña que usarás para desbloquear tu almacenamiento.", "plausible_deniability": "Negación Plausible", "privacy": "Privacidad", "privacy_read_clipboard": "Leer portapapeles", "privacy_system_settings": "Configuración de sistema", "privacy_quickactions": "Atajos de la Billetera", - "privacy_quickactions_explanation": "Mantén presionado el ícono de la aplicación BlueWallet en tu pantalla de inicio para ver rápidamente el saldo de tu billetera.", + "privacy_quickactions_explanation": "Mantén pulsado el icono de la aplicación BlueWallet para ver rápidamente el saldo de tu 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.", - "push_notifications": "Notificaciones Push", "rate": "Tasa", - "retype_password": "Ingresa la contraseña nuevamente", + "push_notifications_explanation": "Al habilitar las notificaciones, el token de tu dispositivo se enviará al servidor, junto con las direcciones de la billetera y los identificadores de transacciones de todas las billeteras y transacciones realizadas después de habilitar las notificaciones. El token del dispositivo se utiliza para enviar notificaciones, y la información de la billetera nos permite notificarte sobre la llegada de Bitcoin o las confirmaciones de transacciones.\n\nSolo se transmite la información que se recibe después de habilitar las notificaciones; no se recopila nada anterior.\n\nSi deshabilitas las notificaciones, se eliminará toda esta información del servidor. Además, si eliminas una billetera de la aplicación, también se eliminará la información asociada a ella del servidor.", "selfTest": "Auto-Test", "save": "Guardar", "saved": "Guardado", - "success_transaction_broadcasted": "¡Genial! ¡Tu transacción ha sido retransmitida!", + "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?", - "no_and_dont_ask": "No, y no me vuelvas a preguntar", - "ask_me_later": "Pregúntame luego" + "notifications_subtitle": "Pagos entrantes y confirmaciones de transacciones", + "no_and_dont_ask": "No, y no me vuelvas a preguntar.", + "permission_denied_message": "Has denegado el envío de notificaciones. Si deseas recibirlas, actívalas en la configuración de tu dispositivo." }, "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_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.", + "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).", "cpfp_no_bump": "Esta transacción no se puede acelerar.", "cpfp_title": "Aumentar Comisión (CPFP)", "details_balance_hide": "Ocultar Balance", "details_balance_show": "Mostrar Balance", - "details_block": "Altura del Bloque", "details_copy": "Copiar", - "details_copy_amount": "Importe de la copia", "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", "details_received": "Recibido", - "transaction_note_saved": "La nota de transacción se ha guardado correctamente.", - "details_show_in_block_explorer": "Ver en el Explorador de Bloques", + "details_view_in_browser": "Ver en el navegador", "details_title": "Transacción", + "incoming_transaction": "Transacción entrante", + "outgoing_transaction": "Transacción saliente", + "expired_transaction": "Transacción vencida", + "pending_transaction": "Transacción pendiente", + "offchain": "Fuera de cadena", + "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", + "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 con una tarifa más alta para que se extraiga más rápido. Esto se llama RBF—Replace by Fee.", + "rbf_explain": "Reemplazaremos esta transacción con una con una tarifa más alta para que se extraiga más rápido. Esto se llama RBF (Replace by Fee)", "rbf_title": "Aumentar Comisión (RBF)", "status_bump": "Aumentar Comisión", "status_cancel": "Cancelar Transacción", "transactions_count": "Número de Transacciones", "txid": "ID de Transacción", - "updating": "Actualizando..." + "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.", + "custom_fee_warning_title": "Advertencia", + "custom_fee_warning_description": "Las tarifas inferiores a 1 sat/vB son válidas, pero puede que no se retransmitan debido a las políticas de los nodos.", + "details_eta_analyzing": "Analizando...", + "details_sent": "Enviado", + "details_section": "Detalles", + "details_explorer": "explorador", + "details_network_fee": "Comisión de red", + "details_to_address": "A", + "details_note": "Nota", + "details_add_note": "agregar", + "details_advanced": "Avanzado", + "details_fee_rate": "Tasa de comisión", + "details_size": "Tamaño", + "details_virtual_size": "Tamaño virtual", + "details_tx_hex": "Hex de transacción", + "details_inputs_count": "Entradas ({count})", + "details_outputs_count": "Salidas ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Billetera Bitcoin simple y potente", "add_create": "Crear", + "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_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", "add_entropy_remain": "{gen} bytes de entropía generada. Los {rem} bytes restantes se obtendrán del generador de números aleatorios del sistema.", "add_import_wallet": "Importar billetera", "add_lightning": "Lightning", "add_lightning_explain": "Para gastar con transacciones instantáneas", - "add_lndhub": "Conectar a tu LNDHub", - "add_lndhub_error": "La dirección de nodo proporcionada es un nodo LNDHub inválido.", + "add_lndhub": "Conéctate a tu LNDhub", + "add_lndhub_error": "La dirección de nodo proporcionada es un nodo LNDhub no válido.", "add_lndhub_placeholder": "Tu Dirección de Nodo", "add_placeholder": "mi primera billetera", "add_title": "Agregar Billetera", "add_wallet_name": "Nombre", "add_wallet_type": "Tipo", - "balance": "Balance", - "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?", + "add_wallet_seed_length": "Longitud de la semilla", + "add_wallet_seed_length_12": "12 palabras", + "add_wallet_seed_length_24": "24 palabras", + "clipboard_bitcoin": "Tienes una dirección de Bitcoin en tu portapapeles. ¿Te gustaría usarla para una transacción?", + "clipboard_lightning": "Tienes una factura Lightning en tu portapapeles. ¿Te gustaría usarla para una transacción?", + "clear_clipboard_on_import": "Limpiar el portapapeles al importar", "details_address": "Dirección", "details_advanced": "Avanzado", "details_are_you_sure": "¿Estás seguro?", "details_connected_to": "Conectado a", - "details_del_wb_err": "El saldo proporcionado no coincide con el saldo de esta billetera. Inténtalo de nuevo.", + "details_del_wb_err": "El monto del saldo proporcionado no coincide con el saldo de esta billetera. Inténtalo de nuevo.", "details_del_wb_q": "Esta billetera tiene saldo. Antes de continuar, por favor, ten en cuenta que no podrás recuperar los fondos sin la frase inicial de esta billetera. Para evitar una eliminación accidental, introduce el saldo de tu billetera de {balance} satoshis.", "details_delete": "Eliminar", "details_delete_wallet": "Eliminar Billetera", "details_derivation_path": "ruta de derivación", - "details_display": "Mostrar en la lista de Billeteras", + "details_display": "Mostrar en la pantalla de inicio", "details_export_backup": "Exportar / Copia de seguridad", "details_export_history": "Exportar historial a CSV", "details_master_fingerprint": "Huella Digital Maestra", "details_multisig_type": "multifirma", - "details_no_cancel": "No, cancelar", - "details_save": "Guardar", "details_show_xpub": "Mostrar el XPUB de la Billetera", "details_show_addresses": "Mostrar direcciones", "details_title": "Billetera", + "wallets": "Billeteras", + "swipe_balance_hide": "Ocultar", + "swipe_balance_show": "Mostrar", + "drag_to_reorder": "Arrastra para reordenar", + "clear_search": "Limpiar búsqueda", "details_type": "Tipo", "details_use_with_hardware_wallet": "Usar con Billetera de Hardware", - "details_wallet_updated": "Billetera actualizada", - "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", @@ -426,65 +464,91 @@ "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", "import_discovery_title": "Descubrimiento", "import_discovery_subtitle": "Elige una billetera descubierta", "import_discovery_derivation": "Utilizar una ruta de derivación personalizada", "import_discovery_no_wallets": "No se encontraron billeteras.", - "import_derivation_found": "encontrada", - "import_derivation_found_not": "no encontrada", - "import_derivation_loading": "cargando...", - "import_derivation_subtitle": "Introduce la ruta de derivación personalizada y trataremos de descubrir tu billetera", + "import_discovery_offline": "BlueWallet se encuentra actualmente en modo sin conexión. En este modo, no puede verificar la existencia de la billetera, por lo que deberás seleccionar la correcta manualmente.", + "import_derivation_found": "Encontrado", + "import_derivation_found_not": "No se ha encontrado", + "import_derivation_loading": "Cargando...", + "import_derivation_subtitle": "Introduce la ruta de derivación personalizada e intentaremos descubrir tu billetera.", "import_derivation_title": "Ruta de derivación", - "import_derivation_unknown": "desconocida", - "import_wrong_path": "ruta de derivación incorrecta", + "import_derivation_unknown": "Desconocido", + "import_wrong_path": "Ruta de derivación incorrecta", "list_create_a_button": "Agrega ahora", "list_create_a_wallet": "Agrega una billetera", - "list_create_a_wallet_text": "Es gratis y puedes crear \ntantas como quieras.", + "list_create_a_wallet_text": "Es gratis y puedes crear \ntantos como quieras.", "list_empty_txs1": "Tus transacciones aparecerán aquí.", "list_empty_txs1_lightning": "La billetera Lightning debe usarse para tus transacciones diarias. Las tarifas son injustamente baratas y la velocidad es increíblemente rápida.", "list_empty_txs2": "Comienza con tu billetera.", "list_empty_txs2_lightning": "\nPara comenzar a usarla, toca Administrar Fondos y recarga tu saldo.", "list_latest_transaction": "Última Transacción", - "list_ln_browser": "Navegador LApp", "list_long_choose": "Elige una Foto", - "list_long_clipboard": "Copiar desde el Portapapeles", + "paste_from_clipboard": "Pegar", + "import_file": "Importar Archivo", "list_long_scan": "Escanear Código QR", "list_title": "Billeteras", "list_tryagain": "Intenta otra vez", "no_ln_wallet_error": "Antes de pagar una factura Lightning, primero debes agregar una billetera Lightning.", "looks_like_bip38": "Esto parece una clave privada protegida por contraseña (BIP38).", - "reorder_title": "Reorganizar Billeteras", - "reorder_instructions": "Toca y mantén presionada una billetera para arrastrarla por la lista.", + "manage_title": "Administrar billeteras", + "no_results_found": "No se han encontrado resultados.", "please_continue_scanning": "Por favor continúa escaneando.", "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": "¡Advertencia! No revelar.", + "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.", + "write_down_header": "Crear una copia de seguridad manual", + "write_down": "Anota y guarda estas palabras de forma segura. Úsalas para recuperar tu billetera más adelante.", + "wallet_type_this": "Este tipo de billetera es {type}.", + "share_number": "Compartir {number}", + "copy_ln_url": "Copia y guarda de forma segura esta URL para restaurar tu billetera más tarde.", + "copy_ln_public": "Copia y guarda de forma segura esta información para restaurar tu billetera más adelante.", "add_ln_wallet_first": "Primero debes agregar una billetera Lightning.", "identity_pubkey": "Identidad Pubkey", - "xpub_title": "XPUB de la billetera" + "xpub_title": "XPUB de la billetera", + "manage_wallets_search_placeholder": "Búsqueda de billeteras, direcciones, transacciones y memos", + "more_info": "Más información", + "details_delete_wallet_error_message": "Hubo un problema al confirmar si esta billetera se eliminó de las notificaciones, lo que podría deberse a un problema de red o a una mala conexión. Si continúas, es posible que aún recibas notificaciones de transacciones relacionadas con esta billetera, incluso después de que se elimine.", + "details_delete_anyway": "Borrar de todos modos" + }, + "total_balance_view": { + "display_in_bitcoin": "Mostrar en Bitcoin", + "hide": "Ocultar", + "display_in_sats": "Mostrar en sats", + "display_in_fiat": "Mostrar en {currency}", + "title": "Balance Total", + "explanation": "Ve el saldo total de todas tus billeteras en la pantalla de descripción general." }, "multisig": { - "multisig_vault": "Bóveda", + "multisig_vault": "Bóveda Multifirma", "default_label": "Bóveda Multifirma", "multisig_vault_explain": "La mejor seguridad para grandes cantidades", "provide_signature": "Proporcionar firma", + "provide_signature_details": "Usa tu dispositivo y billetera donde reside la llave para firmar esta transacción", + "provide_signature_details_bluewallet": "En BlueWallet, ve al menú de la pantalla Enviar y selecciona", + "provide_signature_next_steps": "Escanea o importa la transacción firmada", + "provide_signature_next_steps_details": "Una vez que tu billetera haya firmado con éxito la transacción, escanea el código QR proporcionado o importa el archivo que lo acompaña, y luego revisa todos los detalles de la transacción antes de transmitirla.", "vault_key": "Clave de la Bóveda {number}", "required_keys_out_of_total": "Llaves requeridas del total", "fee": "Tarifa: {number}", "fee_btc": "{number} BTC", "confirm": "Confirmar", "header": "Enviar", - "share": "Compartir", + "share": "Compartir…", "view": "Vista", + "shared_key_detected": "Cofirmante compartido", + "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", @@ -503,38 +567,39 @@ "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 ser válida.", - "invalid_cosigner": "Datos de cofirmante inválidos", + "invalid_mnemonics": "Esta frase mnemotécnica no parece válida.", + "invalid_cosigner": "Datos del cofirmante no válidos", "not_a_multisignature_xpub": "¡Esto no es un XPUB de una billetera con múltiples firmas!", "invalid_cosigner_format": "Cofirmante incorrecto: este no es un cofirmante para el formato {format}.", "create_new_key": "Crear Nuevo", "scan_or_open_file": "Escanear o abrir archivo", "i_have_mnemonics": "Tengo una semilla para esta clave.", "type_your_mnemonics": "Inserta una semilla para importar tu clave de la Bóveda existente.", - "this_is_cosigners_xpub": "Este es el XPUB del cofirmante, listo para ser importado a otra billetera. Es seguro compartirlo.", + "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", + "view_edit_cosigners": "Ver/editar cofirmantes", "this_cosigner_is_already_imported": "Este cofirmante ya está importado.", "export_signed_psbt": "Exportar PSBT firmado", "input_fp": "Ingresa huella digital", "input_fp_explain": "Omitir para usar el predeterminado (00000000)", "input_path": "Insertar Ruta de Derivación", - "input_path_explain": "Omitir para usar el predeterminado ({predeterminado})", + "input_path_explain": "Omitir para usar el predeterminado ({default})", "ms_help": "Ayuda", "ms_help_title": "Cómo funcionan las Bóvedas Multifirma: Consejos y Trucos", "ms_help_text": "Una billetera con varias llaves para mayor seguridad o custodia compartida", "ms_help_title1": "Se recomiendan varios dispositivos.", - "ms_help_1": "La Bóveda funcionará con otras apps de BlueWallet instalada en otros dispositivos y billeteras compatibles con PSBT, como Electrum, Spectre, Coldcard, Cobo Vault, etc.", + "ms_help_1": "La Bóveda funcionará con BlueWallet instalada en otros dispositivos y billeteras compatibles con PSBT, como Electrum, Spectre, Coldcard, Keystone, etc.", "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", @@ -545,8 +610,14 @@ "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.", - "view_qrcode": "Ver el código QR" + "no_wallet_owns_address": "Ninguna de las billeteras disponibles posee la dirección proporcionada.", + "view_qrcode": "Ver código QR" + }, + "autofill_word": { + "title": "Semilla palabra final", + "enter": "Ingresa tu frase mnemotécnica parcial", + "generate_word": "Generar la palabra final", + "error": "La entrada no es una mnemónica parcial de 11 o 23 palabras. Inténtalo de nuevo." }, "cc": { "change": "Cambio", @@ -559,7 +630,14 @@ "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", + "sort_value": "Valor", + "sort_label": "Etiqueta", + "sort_status": "Estado", + "sort_by": "Ordenar por" }, "units": { "BTC": "BTC", @@ -568,6 +646,8 @@ "sats": "sats" }, "addresses": { + "copy_private_key": "Copiar clave privada", + "sensitive_private_key": "Advertencia: las claves privadas son extremadamente sensibles. ¿Continuar?", "sign_title": "Firmar/Verificar mensaje", "sign_help": "Aquí puedes crear o verificar una firma criptográfica basada en una dirección de Bitcoin.", "sign_sign": "Firmar", @@ -601,9 +681,24 @@ }, "bip47": { "payment_code": "Código de pago", - "payment_codes_list": "Lista de códigos de pago", - "who_can_pay_me": "Quién puede pagarme:", + "contacts": "Contactos", + "bip47_explain": "Código reutilizable y compartible", + "bip47_explain_subtitle": "BIP47", "purpose": "Código reutilizable y compartible (BIP47)", + "pay_this_contact": "Paga a este contacto", + "rename_contact": "Renombrar contacto", + "copy_payment_code": "Copiar código de pago", + "hide_contact": "Ocultar contacto", + "rename": "Cambiar nombre", + "provide_name": "Proporciona un nuevo nombre para este contacto", + "add_contact": "Agregar contacto", + "provide_payment_code": "Proporciona código de pago", + "invalid_pc": "Código de pago no válido", + "notification_tx_unconfirmed": "La transacción de notificación aún no está confirmada, espera", + "failed_create_notif_tx": "No se pudo crear una transacción en cadena", + "onchain_tx_needed": "Se necesita transacción en cadena", + "notif_tx_sent": "Transacción de notificación enviada. Espera a que se confirme", + "notif_tx": "Transacción de notificación", "not_found": "Código de pago no encontrado" } } diff --git a/loc/et_EE.json b/loc/et_EE.json index 5c44ce9027e..80531098ecd 100644 --- a/loc/et_EE.json +++ b/loc/et_EE.json @@ -3,51 +3,702 @@ "bad_password": "Vale parool. Palun proovi uuesti.", "cancel": "Katkesta", "continue": "Jätka", + "clipboard": "Lõikelaud", + "copied": "Kopeeritud!", + "discard_changes": "Loobu muudatustest?", + "discard_changes_explain": "Sul on salvestamata muudatusi. Kas oled kindel, et soovid neist loobuda ja ekraanilt lahkuda?", "enter_password": "Sisesta parool", "never": "Mitte kunagi", "of": "{number} {total}-st", "ok": "OK", + "enter_url": "Sisesta URL", "storage_is_encrypted": "Sinu hoidla on krüpteeritud. Dekrüpteerimiseks on vajalik sisestada parool.", "yes": "Jah", "no": "Ei", - "save": "Salvesta", + "save": "Salvesta...", "seed": "Seeme", "success": "Toiming õnnestus", "wallet_key": "Rahakoti võti", - "invalid_animated_qr_code_fragment": "Vale animeeritud QR-koodi fragment. Palun proovi uuesti." + "close": "Sulge", + "change_input_currency": "Muuda sisestusvaluutat", + "refresh": "Värskenda", + "pick_image": "Vali galeriist", + "pick_file": "Vali fail", + "enter_amount": "Sisesta summa", + "qr_custom_input_button": "Koputa 10 korda kohandatud sisendi sisestamiseks", + "unlock": "Ava lukk", + "port": "Port", + "ssl_port": "SSL port", + "suggested": "Soovitatud" }, "azteco": { "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.", "title": "Lunasta Azte.co vautšer" }, "entropy": { "save": "Salvesta", "title": "Entroopia", - "undo": "Tühista muudatus" + "undo": "Tühista muudatus", + "amountOfEntropy": "{bits} {limit} bitist" }, "errors": { - "broadcast": "Ülekanne nurjus.", + "broadcast": "Leviedastus nurjus.", "error": "Viga", "network": "Võrgu viga" }, + "lnd": { + "errorInvoiceExpired": "Arve on aegunud.", + "expired": "Aegunud", + "expiresIn": "Aegub {time} minuti pärast", + "payButton": "Maksa", + "payment": "Makse", + "placeholder": "Arve või aadress", + "potentialFee": "Võimalik tasu: {fee}", + "refill": "Täienda", + "refill_create": "Jätkamiseks loo palun Bitcoini rahakott, millega täiendada.", + "refill_external": "Täienda välisest rahakotist", + "refill_lnd_balance": "Täienda Lightningi rahakoti saldot", + "sameWalletAsInvoiceError": "Sa ei saa arvet maksta sama rahakotiga, millega see loodi.", + "title": "Halda vahendeid" + }, + "lndViewInvoice": { + "additional_info": "Lisateave", + "for": "Kellele:", + "lightning_invoice": "Lightning arve", + "please_pay_between_and": "Palun maksa {min} ja {max} vahel", + "please_pay": "Palun maksa", + "preimage": "Eelpilt", + "sats": "satid.", + "date_time": "Kuupäev ja kellaaeg", + "wasnt_paid_and_expired": "Seda arvet ei makstud ja see on aegunud." + }, "plausibledeniability": { - "success": "Toiming õnnestus" + "create_fake_storage": "Loo krüpteeritud hoidla", + "create_password_explanation": "Võltshoidla parool ei tohi kattuda sinu peamise hoidla parooliga.", + "help": "Teatud asjaoludel võidakse sind sundida parooli avalikustama. Sinu müntide kaitseks saab BlueWallet luua veel ühe krüpteeritud hoidla teise parooliga. Surve all võid avalikustada selle parooli kolmandale isikule. Kui see sisestatakse BlueWallet'i, avab see uue „võlts“ hoidla. See tundub kolmandale isikule ehtne, kuid hoiab salaja sinu peamise müntidega hoidla turvaliselt.", + "help2": "Uus hoidla on täielikult kasutatav ja sinna võid hoida väikseid summasid, et see näeks usutavam välja.", + "password_should_not_match": "Parool on juba kasutusel. Palun proovi teist parooli.", + "title": "Usutav eitatavus" + }, + "pleasebackup": { + "ask": "Kas oled salvestanud oma rahakoti taastefraasi? See taastefraas on vajalik sinu vahenditele juurdepääsuks, kui kaotad selle seadme. Ilma taastefraasita kaovad sinu vahendid jäädavalt.", + "ask_no": "Ei, ei ole.", + "ask_yes": "Jah, olen.", + "ok": "OK, ma kirjutasin selle üles.", + "ok_lnd": "OK, olen selle salvestanud.", + "text": "Palun võta hetk aega ja kirjuta see mnemooniline fraas paberile üles.\nSee on sinu varundus ja saad seda kasutada rahakoti taastamiseks.", + "text_lnd": "Palun salvesta see rahakoti varundus. See võimaldab sul rahakott kaotuse korral taastada.", + "title": "Sinu rahakott on loodud." + }, + "receive": { + "details_create": "Loo", + "details_label": "Kirjeldus", + "details_setAmount": "Võta vastu summaga", + "details_share": "Jaga...", + "address_not_found": "Vastuvõtuaadressi loomine ebaõnnestus.", + "header": "Võta vastu", + "reset": "Lähtesta", + "maxSats": "Maksimaalne summa on {max} sats", + "maxSatsFull": "Maksimaalne summa on {max} sats või {currency}", + "minSats": "Minimaalne summa on {min} sats", + "minSatsFull": "Minimaalne summa on {min} sats või {currency}", + "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." }, "send": { + "provided_address_is_invoice": "See aadress näib olevat Lightning arve. Palun mine oma Lightningi rahakotti, et seda arvet maksta.", + "broadcastButton": "Leviedasta", "broadcastError": "Viga", + "broadcastNone": "Sisesta tehingu hex", + "broadcastPending": "Ootel", "broadcastSuccess": "Toiming õnnestus", - "create_to": "Sihtkoht" + "confirm_header": "Kinnita", + "confirm_sendNow": "Saada kohe", + "create_amount": "Summa", + "create_broadcast": "Leviedasta", + "create_copy": "Kopeeri ja leviedasta hiljem", + "create_details": "Üksikasjad", + "create_fee": "Tasu", + "create_memo": "Märkus", + "create_satoshi_per_vbyte": "Satoshit vByte kohta", + "create_this_is_hex": "See on sinu tehingu hex—allkirjastatud ja valmis võrku leviedastamiseks.", + "create_to": "Sihtkoht", + "create_tx_size": "Tehingu suurus", + "create_verify": "Kontrolli coinb.in-is", + "details_insert_contact": "Sisesta kontakt", + "details_add_rec_add": "Lisa saaja", + "details_add_rec_rem": "Eemalda saaja", + "details_add_recc_rem_all_alert_description": "Kas oled kindel, et soovid kõik saajad eemaldada?", + "details_add_rec_rem_all": "Eemalda kõik saajad", + "details_recipients_title": "Saajad", + "details_recipient_title": "Saaja #{number} #{total}-st", + "please_complete_recipient_title": "Mittetäielik saaja", + "please_complete_recipient_details": "Palun täida saaja #{number} andmed enne uue saaja lisamist.", + "details_address": "Aadress", + "details_address_field_is_not_valid": "Aadress ei ole kehtiv.", + "details_adv_fee_bump": "Luba tasu suurendamine", + "details_adv_full": "Kasuta kogu saldot", + "details_adv_full_sure": "Kas oled kindel, et soovid kasutada selle tehingu jaoks rahakoti kogu saldot?", + "details_adv_full_sure_frozen": "Kas oled kindel, et soovid kasutada selle tehingu jaoks rahakoti kogu saldot? Pane tähele, et külmutatud mündid on välja jäetud.", + "details_adv_import": "Impordi tehing", + "details_adv_import_qr": "Impordi tehing (QR)", + "details_amount_field_is_not_valid": "Summa ei ole kehtiv.", + "details_amount_field_is_less_than_minimum_amount_sat": "Määratud summa on liiga väike. Palun sisesta summa, mis on suurem kui 500 sats.", + "details_create": "Loo arve", + "details_error_decode": "Bitcoini aadressi dekodeerimine ebaõnnestus", + "details_fee_field_is_not_valid": "Tasu ei ole kehtiv.", + "details_frozen": "{amount} BTC on külmutatud.", + "details_next": "Edasi", + "details_no_signed_tx": "Valitud fail ei sisalda imporditavat tehingut.", + "details_note_placeholder": "Märkus endale", + "details_scan": "Skanni", + "details_scan_hint": "Topeltkoputa skannimiseks või sihtkoha importimiseks", + "details_scan_error": "Skannimise viga", + "details_total_exceeds_balance": "Saatmissumma ületab saadaoleva saldo.", + "details_total_exceeds_balance_frozen": "Saatmissumma ületab saadaoleva saldo. Pane tähele, et külmutatud mündid on välja jäetud.", + "details_unrecognized_file_format": "Tundmatu failivorming", + "details_wallet_before_tx": "Enne tehingu loomist tuleb esmalt lisada Bitcoini rahakott.", + "dynamic_init": "Käivitamine", + "dynamic_next": "Edasi", + "dynamic_prev": "Eelmine", + "dynamic_start": "Alusta", + "dynamic_stop": "Peata", + "fee_10m": "10min", + "fee_1d": "1p", + "fee_3h": "3t", + "fee_custom": "Kohandatud", + "insert_custom_fee": "Sisesta tasu", + "fee_fast": "Kiire", + "fee_medium": "Keskmine", + "fee_replace_minvb": "Kogu tasumäär (satoshit vByte kohta), mida soovid maksta, peab olema suurem kui {min} sat/vByte.", + "fee_satvbyte": "sat/vByte", + "fee_slow": "Aeglane", + "header": "Saada", + "input_clear": "Tühjenda", + "input_done": "Valmis", + "input_paste": "Kleebi", + "input_total": "Kokku:", + "permission_camera_message": "Vajame sinu luba kaamera kasutamiseks.", + "psbt_sign": "Allkirjasta tehing", + "invalid_psbt": "Esitatud PSBT on kehtetu.", + "open_settings": "Ava seaded", + "permission_storage_denied_message": "BlueWallet ei saa seda faili salvestada. Palun ava seadme seaded ja luba salvestusõigus.", + "permission_storage_title": "Salvestusruumi juurdepääsuluba", + "psbt_clipboard": "Kopeeri lõikelauale", + "psbt_this_is_psbt": "See on osaliselt allkirjastatud Bitcoini tehing (PSBT). Palun lõpeta selle allkirjastamine oma riistvaralise rahakotiga.", + "psbt_tx_export": "Ekspordi faili", + "no_tx_signing_in_progress": "Käimasolevat tehingu allkirjastamist ei ole.", + "outdated_rate": "Kurssi uuendati viimati: {date}", + "psbt_tx_open": "Ava allkirjastatud tehing", + "psbt_tx_scan": "Skanni allkirjastatud tehing", + "qr_error_no_qrcode": "Valitud pildilt ei leitud kehtivat QR-koodi. Veendu, et pilt sisaldab ainult QR-koodi, mitte muud sisu, nt teksti või nuppe.", + "reset_amount": "Lähtesta summa", + "reset_amount_confirm": "Kas soovid summa lähtestada?", + "success_done": "Valmis", + "txSaved": "Tehingu fail ({filePath}) on salvestatud.", + "file_saved_at_path": "Fail ({filePath}) on salvestatud.", + "cant_send_to_silentpayment_adress": "See rahakott ei saa saata SilentPayment aadressidele", + "cant_send_to_bip47": "See rahakott ei saa saata BIP47 maksekoodidele", + "cant_find_bip47_notification": "Lisa see maksekood esmalt kontaktidesse", + "problem_with_psbt": "Probleem PSBT-ga" }, "settings": { - "electrum_clear_alert_cancel": "Katkesta", - "save": "Salvesta" + "about": "Teave", + "about_awesome": "Ehitatud suurepärase", + "about_backup": "Varunda alati oma võtmed!", + "about_free": "BlueWallet on tasuta ja avatud lähtekoodiga projekt. Loodud Bitcoini kasutajate poolt.", + "about_license": "MIT litsents", + "about_release_notes": "Versiooni märkmed", + "about_review": "Jäta meile arvustus", + "performance_score": "Jõudluse hinne: {num}", + "run_performance_test": "Testi jõudlust", + "about_selftest": "Käivita enesetest", + "block_explorer_invalid_custom_url": "Esitatud URL on kehtetu. Palun sisesta kehtiv URL, mis algab http:// või https:// -ga.", + "about_selftest_electrum_disabled": "Enesetestimine ei ole Electrumi võrguta režiimis saadaval. Palun keela võrguta režiim ja proovi uuesti.", + "about_selftest_ok": "Kõik sisemised testid läbisid edukalt. Rahakott töötab hästi.", + "about_sm_github": "GitHub", + "about_sm_telegram": "Telegrami kanal", + "privacy_temporary_screenshots": "Luba ekraani jäädvustamine", + "privacy_temporary_screenshots_instructions": "Ekraani jäädvustamise kaitse lülitatakse ajutiselt välja, võimaldades kuvatõmmiseid ja ekraanisalvestusi. Kaitse aktiveerub automaatselt uuesti, kui sulged ja avad BlueWallet'i.", + "biometrics": "Biomeetria", + "biometrics_no_longer_available": "Sinu seadme seaded on muutunud ega vasta enam rakenduses valitud turvaseadetele. Palun luba biomeetria või pääsukood uuesti, seejärel taaskäivita rakendus, et muudatused jõustuksid.", + "biom_10times": "Oled proovinud parooli sisestada 10 korda. Kas soovid oma hoidla lähtestada? See eemaldab kõik rahakotid ja dekrüpteerib hoidla.", + "biom_conf_identity": "Palun kinnita oma isikusamasus.", + "biom_no_passcode": "Sinu seadmel ei ole pääsukoodi ega biomeetriat lubatud. Jätkamiseks seadista palun pääsukood või biomeetria seadme seadetes.", + "biom_remove_decrypt": "Kõik sinu rahakotid eemaldatakse ja hoidla dekrüpteeritakse. Kas oled kindel, et soovid jätkata?", + "currency": "Valuuta", + "currency_source": "Kurss saadakse", + "currency_fetch_error": "Valitud valuuta kursi hankimisel tekkis viga.", + "default_title": "Käivitamisel", + "donate": "Anneta", + "donate_description": "Aita meil Blue tasuta hoida!", + "electrum_connected": "Ühendatud", + "electrum_connected_not": "Pole ühendatud", + "electrum_error_connect": "Ei saa määratud Electrumi serveriga ühendust", + "electrum_error_connect_tor": "Ei saa määratud Electrumi serveriga ühendust. Palun veendu, et Orboti rakendus on ühendatud, ja proovi uuesti.", + "lndhub_uri": "Nt {example}", + "electrum_host": "Nt {example}", + "electrum_offline_mode": "Võrguta režiim", + "electrum_offline_description": "Kui see on lubatud, ei proovi sinu Bitcoini rahakotid saldosid ega tehinguid hankida.", + "electrum_port": "Port, tavaliselt {example}", + "use_ssl": "Kasuta SSL-i", + "electrum_saved": "Sinu muudatused on edukalt salvestatud. Muudatuste jõustumiseks võib olla vajalik BlueWallet'i taaskäivitamine.", + "set_electrum_server_as_default": "Määra {server} vaikimisi Electrumi serveriks?", + "set_lndhub_as_default": "Määra {url} vaikimisi LNDhubi serveriks?", + "electrum_settings_server": "Electrumi server", + "electrum_status": "Olek", + "electrum_preferred_server": "Eelistatud server", + "electrum_preferred_server_description": "Sisesta server, mida soovid, et sinu rahakott kasutaks kõigi Bitcoini toimingute jaoks. Pärast määramist kasutab rahakott eranditult seda serverit saldode kontrollimiseks, tehingute saatmiseks ja võrguandmete hankimiseks. Veendu, et usaldad seda serverit enne määramist.", + "electrum_unable_to_connect": "Ei saa ühendust serveriga {server}.", + "electrum_history": "Ajalugu", + "electrum_reset_to_default": "See laseb BlueWallet'il juhuslikult valida serveri serverite loendist.", + "electrum_reset": "Lähtesta vaikeväärtustele", + "electrum_reset_to_default_and_clear_history": "Lähtesta vaikeväärtustele ja kustuta ajalugu", + "encrypt_decrypt": "Dekrüpteeri hoidla", + "encrypt_decrypt_q": "Kas oled kindel, et soovid oma hoidla dekrüpteerida? See võimaldab rahakottidele juurde pääseda ilma paroolita.", + "encrypt_enc_and_pass": "Parooliga kaitstud", + "encrypt_storage_explanation_headline": "Luba hoidla krüpteerimine", + "encrypt_storage_explanation_description_line1": "Hoidla krüpteerimise lubamine lisab rakendusele täiendava kaitsekihi, turvates seadmesse salvestatud andmete viisi. See teeb teistel raskemaks sinu teabele loata juurdepääsu.", + "encrypt_storage_explanation_description_line2": "Siiski on oluline teada, et see krüpteerimine kaitseb ainult seadme võtmehoidlas hoitavate rahakottide juurdepääsu. See ei pane parooli ega täiendavat kaitset rahakottidele endile.", + "i_understand": "Saan aru", + "block_explorer": "Plokiuurija", + "block_explorer_preferred": "Kasuta eelistatud plokiuurijat", + "block_explorer_error_saving_custom": "Eelistatud plokiuurija salvestamisel tekkis viga", + "encrypt_title": "Turvalisus", + "encrypt_tstorage": "Hoidla", + "encrypt_use": "Kasuta {type}", + "set_as_preferred": "Määra eelistatuks", + "set_as_preferred_electrum": "{host}:{port} määramine eelistatud serveriks keelab juhusliku ühenduse soovitatud serveritega.", + "encrypted_feature_disabled": "Seda funktsiooni ei saa kasutada, kui krüpteeritud hoidla on lubatud.", + "encrypt_use_expl": "{type} kasutatakse sinu isikusamasuse kinnitamiseks enne tehingu tegemist, lukust avamist, eksportimist või rahakoti kustutamist.", + "biometrics_fail": "Kui {type} ei ole lubatud või lukust avamine ebaõnnestub, võid alternatiivina kasutada seadme pääsukoodi.", + "general": "Üldine", + "general_continuity": "Pidevus", + "general_continuity_e": "Kui see on lubatud, saad valitud rahakotte ja tehinguid vaadata oma teistest Apple iCloud-iga ühendatud seadmetest.", + "groundcontrol_explanation": "GroundControl on tasuta avatud lähtekoodiga tõuketeavituste server Bitcoini rahakottidele. Sa võid paigaldada oma GroundControl serveri ja sisestada selle URL-i siia, et mitte sõltuda BlueWallet'i infrastruktuurist. Jäta tühjaks, et kasutada GroundControli vaikeserverit.", + "header": "Seaded", + "language": "Keel", + "last_updated": "Viimati uuendatud", + "language_isRTL": "Keele suuna jõustumiseks on vajalik BlueWallet'i taaskäivitamine.", + "license": "Litsents", + "lightning_error_lndhub_uri": "Kehtetu LNDhubi URI", + "lightning_error_lndhub_uri_tor": "Kehtetu LNDhubi URI. Palun veendu, et Orboti rakendus on ühendatud, ja proovi uuesti.", + "lightning_saved": "Sinu muudatused on edukalt salvestatud.", + "lightning_settings": "Lightningi seaded", + "lightning_settings_explain": "Et ühenduda oma LND sõlmega, paigalda palun LNDhub ja sisesta selle URL siia seadetesse. Pane tähele, et ainult pärast muudatuste salvestamist loodud rahakotid ühenduvad määratud LNDhubiga.", + "lndhub_github": "GitHubi hoidla", + "network": "Võrk", + "network_broadcast": "Leviedasta tehing", + "network_electrum": "Electrumi server", + "electrum_suggested_description": "Kui eelistatud server ei ole määratud, valitakse juhuslikult soovitatud server.", + "not_a_valid_uri": "Kehtetu URI", + "notifications": "Teavitused", + "open_link_in_explorer": "Ava link uurijas", + "password": "Parool", + "password_explain": "Sisesta parool, mida kasutad oma hoidla lukust avamiseks.", + "plausible_deniability": "Usutav eitatavus", + "privacy": "Privaatsus", + "privacy_read_clipboard": "Loe lõikelauda", + "privacy_system_settings": "Süsteemi seaded", + "privacy_quickactions": "Rahakoti otseteed", + "privacy_quickactions_explanation": "Puuduta ja hoia BlueWallet'i ikooni, et kiiresti vaadata oma rahakoti saldot.", + "privacy_clipboard_explanation": "Paku otseteid, kui lõikelaualt leitakse aadress või arve.", + "privacy_do_not_track": "Keela analüütika", + "privacy_do_not_track_explanation": "Jõudluse ja töökindluse teavet ei esitata analüüsiks.", + "rate": "Kurss", + "push_notifications_explanation": "Teavituste lubamisel saadetakse sinu seadme luba serverisse koos rahakoti aadresside ja tehingu ID-dega kõigi rahakottide ja teavituste lubamise järel tehtud tehingute kohta. Seadme luba kasutatakse teavituste saatmiseks ja rahakoti teabe abil saame sind teavitada saabuvast Bitcoinist või tehingu kinnitustest.\n\nEdastatakse ainult teave alates teavituste lubamisest—midagi enne seda ei koguta.\n\nTeavituste keelamine eemaldab kogu selle teabe serverist. Lisaks eemaldab rahakoti kustutamine rakendusest ka sellega seotud teabe serverist.", + "selfTest": "Enesetest", + "save": "Salvesta", + "saved": "Salvestatud", + "success_transaction_broadcasted": "Sinu tehing on edukalt leviedastatud!", + "total_balance": "Kogusaldo", + "total_balance_explanation": "Kuva kõigi rahakottide kogusaldo avakuva vidinatel.", + "widgets": "Vidinad", + "tools": "Tööriistad" + }, + "notifications": { + "would_you_like_to_receive_notifications": "Kas soovid saada teavitusi, kui saabuvad maksed?", + "notifications_subtitle": "Saabuvad maksed ja tehingu kinnitused", + "no_and_dont_ask": "Ei, ja ära küsi seda enam.", + "permission_denied_message": "Sa oled teavituste saatmiseks loa keelanud. Kui soovid teavitusi saada, luba need palun oma seadme seadetes." + }, + "transactions": { + "cancel_explain": "Asendame selle tehingu sellisega, mis maksab sulle ja millel on suuremad tasud. See tühistab tegelikult praeguse tehingu. Seda kutsutakse RBF—Replace by Fee.", + "cancel_no": "See tehing ei ole asendatav.", + "cancel_title": "Tühista see tehing (RBF)", + "transaction_loading_error": "Tehingu laadimisel tekkis probleem. Palun proovi hiljem uuesti.", + "transaction_not_available": "Tehing ei ole saadaval", + "confirmations_lowercase": "{confirmations} kinnitust", + "expand_note": "Laienda märkust", + "cpfp_create": "Loo", + "cpfp_exp": "Loome teise tehingu, mis kulutab sinu kinnitamata tehingu. Kogutasu on suurem kui algse tehingu tasu, seega peaks see kiiremini kaevandatama. Seda kutsutakse CPFP—Child Pays for Parent.", + "cpfp_no_bump": "Selle tehingu tasu ei saa suurendada.", + "cpfp_title": "Suurenda tasu (CPFP)", + "details_balance_hide": "Peida saldo", + "details_balance_show": "Näita saldot", + "details_copy": "Kopeeri", + "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", + "details_received": "Vastu võetud", + "details_view_in_browser": "Vaata brauseris", + "details_title": "Tehing", + "incoming_transaction": "Saabuv tehing", + "outgoing_transaction": "Väljuv tehing", + "expired_transaction": "Aegunud tehing", + "pending_transaction": "Ootel tehing", + "offchain": "Off-chain", + "onchain": "On-chain", + "details_to": "Väljund", + "enable_offline_signing": "Seda rahakotti ei kasutata koos võrguta allkirjastamisega. Kas soovid selle nüüd lubada?", + "list_conf": "Kinn: {number}", + "pending": "Ootel", + "pending_with_amount": "Ootel {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: ~10 minuti pärast", + "eta_3h": "ETA: ~3 tunni pärast", + "eta_1d": "ETA: ~1 päeva pärast", + "list_title": "Tehingud", + "list_title_sent": "Saadetud", + "list_title_received": "Vastu võetud", + "transaction": "Tehing", + "open_url_error": "Linki ei saa vaikebrauseriga avada. Palun muuda oma vaikebrauserit ja proovi uuesti.", + "rbf_explain": "Asendame selle tehingu sellisega, millel on suurem tasu, et see kaevandataks kiiremini. Seda kutsutakse RBF—Replace by Fee.", + "rbf_title": "Kiirenda (RBF)", + "status_bump": "Kiirenda", + "status_cancel": "Tühista", + "transactions_count": "Tehingute arv", + "txid": "Tehingu ID", + "updating": "Uuendamine...", + "watchOnlyWarningTitle": "Turvahoiatus", + "watchOnlyWarningDescription": "Ole ettevaatlik petturite suhtes, kes sageli kasutavad „ainult vaatamiseks“ rahakotte kasutajate petmiseks. Need rahakotid ei võimalda sul vahendeid kontrollida ega saata; nad lasevad sul ainult saldot vaadata.", + "custom_fee_warning_title": "Hoiatus", + "custom_fee_warning_description": "Tasud alla 1 sat/vB on kehtivad, kuid sõlmede reeglite tõttu ei pruugita neid edastada.", + "details_eta_analyzing": "Analüüsimine...", + "details_sent": "Saadetud", + "details_section": "Üksikasjad", + "details_explorer": "uurija", + "details_network_fee": "Võrgutasu", + "details_to_address": "Kellele", + "details_note": "Märkus", + "details_add_note": "lisa", + "details_advanced": "Täpsem", + "details_fee_rate": "Tasumäär", + "details_size": "Suurus", + "details_virtual_size": "Virtuaalne suurus", + "details_tx_hex": "Tehingu hex", + "details_inputs_count": "Sisendid ({count})", + "details_outputs_count": "Väljundid ({count})" }, "wallets": { - "details_save": "Salvesta" + "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Lihtne ja võimas Bitcoini rahakott", + "add_create": "Loo", + "total_balance": "Kogusaldo", + "add_entropy_reset_title": "Lähtesta entroopia", + "add_entropy_reset_message": "Rahakoti tüübi muutmine lähtestab praeguse entroopia. Kas soovid jätkata?", + "add_entropy": "Entroopia", + "add_entropy_bytes": "{bytes} baiti entroopiat", + "add_entropy_generated": "{gen} baiti loodud entroopiat", + "add_entropy_provide": "Esita entroopia täringuviskega", + "add_entropy_remain": "{gen} baiti loodud entroopiat. Ülejäänud {rem} baiti hangitakse süsteemi juhuarvugeneraatorist.", + "add_import_wallet": "Impordi rahakott", + "add_lightning": "Lightning", + "add_lightning_explain": "Kohese tehinguga kulutamiseks", + "add_lndhub": "Ühenda oma LNDhubiga", + "add_lndhub_error": "Esitatud sõlme aadress ei ole kehtiv LNDhubi sõlm.", + "add_lndhub_placeholder": "Sinu sõlme aadress", + "add_placeholder": "minu esimene rahakott", + "add_title": "Lisa rahakott", + "add_wallet_name": "Nimi", + "add_wallet_type": "Tüüp", + "add_wallet_seed_length": "Seemne pikkus", + "add_wallet_seed_length_12": "12 sõna", + "add_wallet_seed_length_24": "24 sõna", + "clipboard_bitcoin": "Sul on lõikelaual Bitcoini aadress. Kas soovid seda tehinguks kasutada?", + "clipboard_lightning": "Sul on lõikelaual Lightning arve. Kas soovid seda tehinguks kasutada?", + "clear_clipboard_on_import": "Tühjenda lõikelaud impordil", + "details_address": "Aadress", + "details_advanced": "Täpsem", + "details_are_you_sure": "Kas oled kindel?", + "details_connected_to": "Ühendatud", + "details_del_wb_err": "Esitatud saldo summa ei ühti selle rahakoti saldoga. Palun proovi uuesti.", + "details_del_wb_q": "Sellel rahakotil on saldo. Enne jätkamist pane tähele, et ilma selle rahakoti seemnefraasita ei saa vahendeid taastada. Juhusliku eemaldamise vältimiseks sisesta palun rahakoti saldo {balance} satoshit.", + "details_delete": "Kustuta", + "details_delete_wallet": "Kustuta rahakott", + "details_derivation_path": "tuletustee", + "details_display": "Kuva avakuval", + "details_export_backup": "Ekspordi/varunda", + "details_export_history": "Ekspordi ajalugu CSV-na", + "details_master_fingerprint": "Põhivõtme sõrmejälg", + "details_multisig_type": "multisig", + "details_show_xpub": "Näita rahakoti xpub-i", + "details_show_addresses": "Näita aadresse", + "details_title": "Rahakott", + "wallets": "Rahakotid", + "swipe_balance_hide": "Peida", + "swipe_balance_show": "Näita", + "drag_to_reorder": "Lohista ümberjärjestamiseks", + "clear_search": "Tühjenda otsing", + "details_type": "Tüüp", + "details_use_with_hardware_wallet": "Kasuta riistvaralise rahakotiga", + "details_yes_delete": "Jah, kustuta", + "enter_bip38_password": "Sisesta dekrüpteerimiseks parool", + "export_title": "Rahakoti eksport", + "import_do_import": "Impordi", + "import_passphrase": "Salafraas", + "import_passphrase_title": "Salafraas", + "import_passphrase_message": "Sisesta salafraas, kui oled seda kasutanud", + "import_error": "Importimine ebaõnnestus. Palun veendu, et esitatud andmed on kehtivad.", + "import_explanation": "Palun sisesta oma seemnesõnad, avalik võti, WIF või mis iganes sul on. BlueWallet teeb oma parima, et õige formaat ära arvata ja rahakott importida.", + "import_imported": "Imporditud", + "import_scan_qr": "Skanni või impordi fail", + "import_success": "Sinu rahakott on edukalt imporditud.", + "import_success_watchonly": "Sinu rahakott on edukalt imporditud. HOIATUS: see on ainult vaatamiseks rahakott, sa EI saa sellest kulutada.", + "import_search_accounts": "Otsi kontosid", + "import_title": "Impordi", + "learn_more": "Lisateave", + "import_discovery_title": "Avastamine", + "import_discovery_subtitle": "Vali avastatud rahakott", + "import_discovery_derivation": "Kasuta kohandatud tuletusteed", + "import_discovery_no_wallets": "Rahakotte ei leitud.", + "import_discovery_offline": "BlueWallet on praegu võrguta režiimis. Selles režiimis ei saa ta rahakoti olemasolu kontrollida, seega pead käsitsi valima õige.", + "import_derivation_found": "Leitud", + "import_derivation_found_not": "Ei leitud", + "import_derivation_loading": "Laadimine...", + "import_derivation_subtitle": "Sisesta kohandatud tuletustee ja me proovime su rahakoti avastada.", + "import_derivation_title": "Tuletustee", + "import_derivation_unknown": "Tundmatu", + "import_wrong_path": "Vale tuletustee", + "list_create_a_button": "Lisa kohe", + "list_create_a_wallet": "Lisa rahakott", + "list_create_a_wallet_text": "See on tasuta ja saad luua \nnii palju kui soovid.", + "list_empty_txs1": "Sinu tehingud ilmuvad siia.", + "list_empty_txs1_lightning": "Lightningi rahakotti tuleks kasutada igapäevasteks tehinguteks. Tasud on ebaausalt odavad ja kiirus välkkiire.", + "list_empty_txs2": "Alusta oma rahakotiga.", + "list_empty_txs2_lightning": "\nKasutamise alustamiseks koputa Halda vahendeid ja täienda oma saldot.", + "list_latest_transaction": "Viimane tehing", + "list_long_choose": "Vali foto", + "paste_from_clipboard": "Kleebi", + "import_file": "Impordi fail", + "list_long_scan": "Skanni QR-kood", + "list_title": "Rahakotid", + "list_tryagain": "Proovi uuesti", + "no_ln_wallet_error": "Enne Lightning arve maksmist tuleb esmalt lisada Lightningi rahakott.", + "looks_like_bip38": "See näeb välja kui parooliga kaitstud privaatvõti (BIP38).", + "manage_title": "Halda rahakotte", + "no_results_found": "Tulemusi ei leitud.", + "please_continue_scanning": "Palun jätka skannimist.", + "select_no_bitcoin": "Praegu pole saadaval ühtegi Bitcoini rahakotti.", + "select_no_bitcoin_exp": "Lightningi rahakottide täiendamiseks on vajalik Bitcoini rahakott. Palun loo või impordi see.", + "select_wallet": "Vali rahakott", + "pull_to_refresh": "Tõmba värskendamiseks", + "warning_do_not_disclose": "Ära kunagi jaga alltoodud teavet", + "scan_import": "Skanni see QR-kood, et importida oma rahakott teise rakendusse.", + "write_down_header": "Loo käsitsi varundus", + "write_down": "Kirjuta need sõnad üles ja hoia turvaliselt. Kasuta neid oma rahakoti hilisemaks taastamiseks.", + "wallet_type_this": "Selle rahakoti tüüp on {type}.", + "share_number": "Jaga {number}", + "copy_ln_url": "Kopeeri ja hoia see URL turvaliselt, et hiljem rahakott taastada.", + "copy_ln_public": "Kopeeri ja hoia see teave turvaliselt, et hiljem rahakott taastada.", + "add_ln_wallet_first": "Sa pead esmalt lisama Lightningi rahakoti.", + "identity_pubkey": "Identiteedi avalik võti", + "xpub_title": "Rahakoti xpub", + "manage_wallets_search_placeholder": "Otsi rahakotte, aadresse, tehinguid ja märkusi", + "more_info": "Lisateave", + "details_delete_wallet_error_message": "Selle rahakoti teavitustest eemaldamise kinnitamisel tekkis probleem—see võib olla tingitud võrguprobleemist või halvast ühendusest. Kui jätkad, võid endiselt saada teavitusi selle rahakotiga seotud tehingute kohta ka pärast selle kustutamist.", + "details_delete_anyway": "Kustuta ikkagi" + }, + "total_balance_view": { + "display_in_bitcoin": "Kuva Bitcoinis", + "hide": "Peida", + "display_in_sats": "Kuva satides", + "display_in_fiat": "Kuva {currency}", + "title": "Kogusaldo", + "explanation": "Vaata kõigi oma rahakottide kogusaldot ülevaate ekraanil." + }, + "multisig": { + "multisig_vault": "Multisig seif", + "default_label": "Multisig seif", + "multisig_vault_explain": "Parim turvalisus suurte summade jaoks", + "provide_signature": "Anna allkiri", + "provide_signature_details": "Kasuta seadet ja rahakotti, kus võti asub, et see tehing allkirjastada", + "provide_signature_details_bluewallet": "BlueWallet'is mine Saada ekraani menüüsse ja vali ", + "provide_signature_next_steps": "Skanni või impordi allkirjastatud tehing", + "provide_signature_next_steps_details": "Kui sinu rahakott on tehingu edukalt allkirjastanud, skanni esitatud QR-kood või impordi kaasasolev fail ja vaata enne leviedastamist üle kõik tehingu üksikasjad.", + "vault_key": "Seifi võti {number}", + "required_keys_out_of_total": "Vajalikud võtmed koguarvust", + "fee": "Tasu: {number}", + "fee_btc": "{number} BTC", + "confirm": "Kinnita", + "header": "Saada", + "share": "Jaga...", + "view": "Vaata", + "shared_key_detected": "Jagatud kaasallkirjastaja", + "shared_key_detected_question": "Sinuga on jagatud kaasallkirjastaja, kas soovid selle importida?", + "manage_keys": "Halda võtmeid", + "how_many_signatures_can_bluewallet_make": "mitu allkirja saab BlueWallet luua", + "signatures_required_to_spend": "Vajalikke allkirju {number}", + "signatures_we_can_make": "saab luua {number}", + "scan_or_import_file": "Skanni või impordi fail", + "export_coordination_setup": "Ekspordi koordineerimise seadistus", + "cosign_this_transaction": "Allkirjasta see tehing koos?", + "lets_start": "Alustame", + "create": "Loo", + "native_segwit_title": "Parim tava", + "wrapped_segwit_title": "Parim ühilduvus", + "legacy_title": "Pärand", + "co_sign_transaction": "Allkirjasta tehing", + "what_is_vault": "Seif on", + "what_is_vault_numberOfWallets": " {m}-{n}-st multisig ", + "what_is_vault_wallet": "rahakott.", + "vault_advanced_customize": "Seifi seaded", + "needs": "See vajab", + "what_is_vault_description_number_of_vault_keys": " {m} seifivõtit ", + "what_is_vault_description_to_spend": "kulutamiseks ja kolmandat võid \nkasutada varundusena.", + "what_is_vault_description_to_spend_other": "kulutamiseks.", + "quorum": "{m} {n}-st kvoorum", + "quorum_header": "Kvoorum", + "of": "/", + "wallet_type": "Rahakoti tüüp", + "invalid_mnemonics": "See mnemooniline fraas ei tundu kehtiv.", + "invalid_cosigner": "Kehtetud kaasallkirjastaja andmed", + "not_a_multisignature_xpub": "See ei ole xpub multisig rahakotist!", + "invalid_cosigner_format": "Vale kaasallkirjastaja: see ei ole {format} formaadi kaasallkirjastaja.", + "create_new_key": "Loo uus", + "scan_or_open_file": "Skanni või ava fail", + "i_have_mnemonics": "Mul on selle võtme jaoks seeme.", + "type_your_mnemonics": "Sisesta seeme oma olemasoleva seifivõtme importimiseks.", + "this_is_cosigners_xpub": "See on kaasallkirjastaja xpub—valmis teise rahakotti importimiseks. Seda on turvaline jagada.", + "this_is_cosigners_xpub_airdrop": "Kui jagad AirDropi kaudu, peavad vastuvõtjad olema koordineerimise ekraanil.", + "wallet_key_created": "Sinu seifivõti on loodud. Võta hetk aega, et oma mnemooniline seeme turvaliselt varundada.", + "are_you_sure_seed_will_be_lost": "Kas oled kindel? Sinu mnemooniline seeme läheb kaotsi, kui sul pole varundust.", + "forget_this_seed": "Unusta see seeme ja kasuta selle asemel xpub-i.", + "view_edit_cosigners": "Vaata/muuda kaasallkirjastajaid", + "this_cosigner_is_already_imported": "See kaasallkirjastaja on juba imporditud.", + "export_signed_psbt": "Ekspordi allkirjastatud PSBT", + "input_fp": "Sisesta sõrmejälg", + "input_fp_explain": "Vahele jätmisel kasutatakse vaikeväärtust (00000000)", + "input_path": "Sisesta tuletustee", + "input_path_explain": "Vahele jätmisel kasutatakse vaikeväärtust ({default})", + "ms_help": "Abi", + "ms_help_title": "Kuidas multisig seifid töötavad: nõuanded ja nipid", + "ms_help_text": "Mitme võtmega rahakott, suurema turvalisuse või jagatud hoolduse jaoks", + "ms_help_title1": "Soovitatav on kasutada mitut seadet.", + "ms_help_1": "Seif töötab teiste BlueWallet'i rakenduste ja PSBT-ga ühilduvate rahakottidega, nagu Electrum, Specter, Coldcard, Cobo Vault jne.", + "ms_help_title2": "Võtmete muutmine", + "ms_help_2": "Saad luua kõik seifi võtmed sellel seadmel ja need hiljem eemaldada või muuta. Kõikide võtmete hoidmine samal seadmel on samaväärse turvalisusega tavalise Bitcoini rahakotiga.", + "ms_help_title3": "Seifi varundused", + "ms_help_3": "Rahakoti valikutest leiad oma seifi varunduse ja ainult vaatamiseks varunduse. See varundus on nagu kaart sinu rahakotini. See on hädavajalik rahakoti taastamiseks, kui kaotad mõne seemne.", + "ms_help_title4": "Seifide importimine", + "ms_help_4": "Multisigi importimiseks kasuta oma varundusfaili ja impordi funktsiooni. Kui sul on ainult seemned ja xpub-id, saad seifivõtmete loomisel kasutada üksikut Impordi nuppu.", + "ms_help_title5": "Täpsem režiim", + "ms_help_5": "Vaikimisi genereerib BlueWallet 2-3-st seifi. Erineva kvoorumi loomiseks või aadressitüübi muutmiseks aktiveeri seadetes täpsem režiim." + }, + "is_it_my_address": { + "title": "Kas see on minu aadress?", + "owns": "{label} omab {address}", + "enter_address": "Sisesta aadress", + "check_address": "Kontrolli aadressi", + "no_wallet_owns_address": "Ükski saadaolev rahakott ei oma esitatud aadressi.", + "view_qrcode": "Vaata QR-koodi" + }, + "autofill_word": { + "title": "Seemne lõppsõna", + "enter": "Sisesta oma osaline mnemooniline fraas", + "generate_word": "Genereeri lõppsõna", + "error": "Sisend ei ole 11- või 23-sõnaline osaline mnemoonik. Palun proovi uuesti." + }, + "cc": { + "change": "Vahetusraha", + "coins_selected": "Valitud münte ({number})", + "selected_summ": "{value} valitud", + "empty": "Selles rahakotis ei ole hetkel ühtegi münti.", + "freeze": "Külmuta", + "freezeLabel": "Külmuta", + "freezeLabel_un": "Sulata", + "header": "UTXO haldus", + "use_coin": "Kasuta münti", + "use_coins": "Kasuta münte", + "tip": "See funktsioon võimaldab näha, sildistada, külmutada või valida münte parema rahakoti haldamise jaoks. Saad valida mitu münti, koputades värvilistele ringidele.", + "sort_asc": "Kasvavalt", + "sort_desc": "Kahanevalt", + "sort_height": "Kõrgus", + "sort_value": "Väärtus", + "sort_label": "Silt", + "sort_status": "Olek", + "sort_by": "Sorteeri" + }, + "units": { + "BTC": "BTC", + "MAX": "Max", + "sat_vbyte": "sat/vByte", + "sats": "satid" + }, + "addresses": { + "copy_private_key": "Kopeeri privaatvõti", + "sensitive_private_key": "Hoiatus: privaatvõtmed on äärmiselt tundlikud. Kas jätkata?", + "sign_title": "Allkirjasta/kontrolli sõnumit", + "sign_help": "Siin saad luua või kontrollida krüptograafilist allkirja Bitcoini aadressi põhjal.", + "sign_sign": "Allkirjasta", + "sign_verify": "Kontrolli", + "sign_signature_correct": "Kontroll õnnestus!", + "sign_signature_incorrect": "Kontroll ebaõnnestus!", + "sign_placeholder_address": "Aadress", + "sign_placeholder_message": "Sõnum", + "sign_placeholder_signature": "Allkiri", + "addresses_title": "Aadressid", + "type_change": "Vahetusraha", + "type_receive": "Vastuvõtt", + "type_used": "Kasutatud", + "transactions": "Tehingud" + }, + "lnurl_auth": { + "register_question_part_1": "Kas soovid registreerida konto saidil", + "register_question_part_2": "kasutades oma Lightningi rahakotti?", + "register_answer": "Oled edukalt registreerinud konto saidil {hostname}!", + "login_question_part_1": "Kas soovid sisse logida saidil", + "login_question_part_2": "kasutades oma Lightningi rahakotti?", + "login_answer": "Oled edukalt sisse logitud saidil {hostname}!", + "link_question_part_1": "Kas soovid siduda oma konto saidil", + "link_question_part_2": "oma Lightningi rahakotiga?", + "link_answer": "Sinu Lightningi rahakott on edukalt seotud sinu kontoga saidil {hostname}!", + "auth_question_part_1": "Kas soovid autentida saidil", + "auth_question_part_2": "kasutades oma Lightningi rahakotti?", + "auth_answer": "Oled edukalt autenditud saidil {hostname}!", + "could_not_auth": "Me ei suutnud sind autentida saidil {hostname}.", + "authenticate": "Autendi" + }, + "bip47": { + "payment_code": "Maksekood", + "contacts": "Kontaktid", + "bip47_explain": "Korduvkasutatav ja jagatav kood", + "bip47_explain_subtitle": "BIP47", + "purpose": "Korduvkasutatav ja jagatav kood (BIP47)", + "pay_this_contact": "Maksa sellele kontaktile", + "rename_contact": "Nimeta kontakt ümber", + "copy_payment_code": "Kopeeri maksekood", + "hide_contact": "Peida kontakt", + "rename": "Nimeta ümber", + "provide_name": "Anna sellele kontaktile uus nimi", + "add_contact": "Lisa kontakt", + "provide_payment_code": "Esita maksekood", + "invalid_pc": "Kehtetu maksekood", + "notification_tx_unconfirmed": "Teavitustehing ei ole veel kinnitatud, palun oota", + "failed_create_notif_tx": "Ahelas tehingu loomine ebaõnnestus", + "onchain_tx_needed": "Vajalik on ahelas tehing", + "notif_tx_sent": "Teavitustehing saadetud. Palun oota selle kinnitamist", + "notif_tx": "Teavitustehing", + "not_found": "Maksekoodi ei leitud" } } diff --git a/loc/fa.json b/loc/fa.json index 677683c139c..5e01dbede71 100644 --- a/loc/fa.json +++ b/loc/fa.json @@ -4,28 +4,32 @@ "cancel": "لغو", "continue": "ادامه", "clipboard": "کلیپ‌بورد", + "copied": "کپی شد!", + "discard_changes": "نادیده‌گرفتن تغییرات", + "discard_changes_explain": "تغییرات ذخیره‌نشده‌ای دارید. آیا مطمئن هستید که می‌خواهید آن‌ها را نادیده بگیرید و از این صفحه خارج شوید؟", "enter_password": "گذرواژه را وارد کنید", "never": "هرگز", - "disabled": "غیرفعال", "of": "{number} از {total}", - "ok": "بله", + "ok": "تأیید", + "enter_url": "آدرس را وارد کنید", "storage_is_encrypted": "فضای ذخیره‌سازی شما رمزگذاری شده است. برای رمزگشایی آن به گذرواژه نیاز است.", "yes": "بله", "no": "خیر", - "save": "ذخیره", + "save": "ذخیره...", "seed": "سید", "success": "موفقیت‌آمیز بود", "wallet_key": "کلید کیف پول", - "invalid_animated_qr_code_fragment": "کد QR جزئی متحرک نامعتبر است. لطفاً دوباره امتحان کنید.", - "file_saved": "فایل {filePath} در {destination} شما ذخیره شد.", - "downloads_folder": "پوشهٔ دانلودها", "close": "بستن", + "change_input_currency": "ویرایش ارز ورودی", "refresh": "تازه‌سازی", - "more": "بیشتر", - "enter_amount": "مقدار را وارد کنید" - }, - "alert": { - "default": "هشدار" + "pick_image": "انتخاب از کتابخانه", + "pick_file": "انتخاب فایل", + "enter_amount": "مقدار را وارد کنید", + "qr_custom_input_button": "برای وارد کردن ورودی دلبخواه، 10 بار ضربه بزنید", + "unlock": "بازکردن قفل", + "port": "درگاه", + "ssl_port": "درگاه SSL", + "suggested": "پیشنهادشده" }, "azteco": { "codeIs": "کد تخفیف شما عبارت است از", @@ -34,12 +38,14 @@ "redeem": "افزودن به کیف پول", "redeemButton": "فعال‌سازی", "success": "موفقیت‌آمیز بود", + "successMessage": "کد تخفیف با موفقیت فعال شد! دارایی شما به‌زودی به کیف پول بیت‌کوین شما خواهد رسید.", "title": "فعال‌سازی کد تخفیف Azte.co" }, "entropy": { "save": "ذخیره", "title": "آنتروپی", - "undo": "بازگشت به حالت قبل" + "undo": "بازگشت به حالت قبل", + "amountOfEntropy": "{bits} از {limit} بیت" }, "errors": { "broadcast": "انتشار ناموفق بود.", @@ -47,65 +53,46 @@ "network": "خطای شبکه" }, "lnd": { - "active": "فعال", - "inactive": "غیرفعال", - "channels": "کانال‌ها", - "no_channels": "بدون کانال", - "claim_balance": "تسویهٔ موجودی {balance}", - "close_channel": "بستن کانال", - "new_channel": "کانال جدید", - "errorInvoiceExpired": "صورت‌حساب منقضی شد", - "force_close_channel": "بستن اجباری کانال؟", + "errorInvoiceExpired": "صورت‌حساب منقضی شده است.", "expired": "منقضی‌شده", - "node_alias": "نام مستعار گره", "expiresIn": "تا {time} دقیقهٔ دیگر منقضی می‌شود", "payButton": "پرداخت", - "open_channel": "بازکردن کانال", - "funding_amount_placeholder": "مقدار تأمین وجه، برای مثال، ۰٫۰۰۱", - "opening_channnel_for_from": "درحال بازکردن کانال برای کیف پول {forWalletLabel}، با تأمین وجه از {fromWalletLabel}", - "are_you_sure_open_channel": "آیا از بازکردن این کانال اطمینان دارید؟", + "payment": "پرداخت", + "placeholder": "صورت‌حساب یا آدرس", "potentialFee": "کارمزد احتمالی: {fee}", - "remote_host": "میزبان راه‌دور", "refill": "پرکردن", - "reconnect_peer": "اتصال مجدد به همتا", "refill_create": "جهت ادامه، لطفاً یک کیف پول بیت‌کوین جهت پرکردن ایجاد کنید.", "refill_external": "پرکردن با کیف پول خارجی", "refill_lnd_balance": "پرکردن موجودی کیف پول لایتنینگ", - "title": "مدیریت دارایی", - "can_send": "می‌تواند ارسال کند", - "can_receive": "می‌تواند دریافت کند", - "view_logs": "مشاهدهٔ رخدادها" + "sameWalletAsInvoiceError": "شما نمی‌توانید صورت‌حسابی را با همان کیف پولی که برای ایجاد آن استفاده کرده‌اید بپردازید.", + "title": "مدیریت دارایی" }, "lndViewInvoice": { "additional_info": "اطلاعات بیشتر", - "for": "برای: ", + "for": "برای:", "lightning_invoice": "صورت‌حساب لایتنینگ", - "open_direct_channel": "کانال مستقیمی با این گره باز کن:", "please_pay_between_and": "لطفاً بین {min} و {max} بپردازید", "please_pay": "لطفاً", - "preimage": "پیش‌نگاره", + "preimage": "پیش‌تصویر", "sats": "ساتوشی بپردازید.", + "date_time": "تاریخ و زمان", "wasnt_paid_and_expired": "این صورت‌حساب پرداخت نشده و منقضی شده است." }, "plausibledeniability": { "create_fake_storage": "ایجاد فضای ذخیره‌سازی رمزگذاری‌شده", - "create_password": "یک گذرواژه ایجاد کنید", "create_password_explanation": "گذرواژه برای فضای ذخیره‌سازی جعلی نباید با گذرواژهٔ فضای ذخیره‌سازی اصلی شما مطابقت داشته باشد.", "help": "تحت شرایط خاص، ممکن است مجبور شوید گذرواژه را فاش کنید. برای محفوظ‌نگه‌داشتن دارایی شما، BlueWallet می‌تواند یک فضای ذخیره‌سازی رمزگذاری‌شدهٔ دیگر را با گذرواژه‌ای متفاوت ایجاد کند. تحت فشار، می‌توانید این گذرواژه را برای شخص سوم افشا کنید. اگر این گذرواژه در BlueWallet وارد شود، کیف پول یک فضای ذخیره‌سازی «جعلی» جدید باز می‌کند. این فضا از دید شخص سوم معتبر به‌نظر می‌رسد، اما در عمل به‌صورت مخفیانه فضای ذخیره‌سازی اصلی شما و دارایی‌تان را محفوظ نگه می‌دارد.", "help2": "فضای ذخیره‌سازی جدید کاملاً کاربردی خواهد بود، و شما می‌توانید مقادیر کمی را در آنجا نگه دارید تا باورپذیرتر به‌نظر برسد.", "password_should_not_match": "گذرواژه در حال استفاده است. لطفاً گذرواژهٔ دیگری را امتحان کنید.", - "passwords_do_not_match": "گذرواژه‌ها مطابقت ندارند. لطفاً دوباره امتحان کنید.", - "retype_password": "گذرواژه را دوباره بنویسید", - "success": "موفقیت‌آمیز بود", "title": "انکار موجه" }, "pleasebackup": { "ask": "آیا کلمه‌های پشتیبان کیف پول خود را ذخیره کرده‌اید؟ درصورت ازدست‌دادن این دستگاه، این کلمه‌های پشتیبان برای دسترسی به دارایی شما لازم هستند. بدون کلمه‌های پشتیبان، دارایی شما برای همیشه ازدست خواهد رفت.", - "ask_no": "خیر، نکرده‌ام", - "ask_yes": "بله، کرده‌ام", + "ask_no": "خیر، ذخیره نکرده‌ام.", + "ask_yes": "بله، ذخیره کرده‌ام.", "ok": "خب، آن را نوشتم.", "ok_lnd": "خب، آن را ذخیره کردم.", - "text": "لطفاً درنگ کرده و این عبارت یادیار (mnemonic phrase) را روی یک تکه کاغذ یادداشت کنید. این کلمه‌ها نسخهٔ پشتیبان شما هستند، و می‌توانید از آن‌ها برای بازیابی کیف پول در دستگاه دیگری استفاده کنید.", + "text": "لطفاً درنگ کرده و این عبارت یادیار (mnemonic phrase) را روی یک تکه کاغذ یادداشت کنید.\nاین کلمه‌ها نسخهٔ پشتیبان شما هستند، و می‌توانید از آن‌ها برای بازیابی کیف پول در دستگاه دیگری استفاده کنید.", "text_lnd": "لطفاً این نسخهٔ پشتیبان کیف پول را ذخیره کنید. با داشتن آن قادر خواهید بود درصورت ازدست‌رفتن کیف پول آن را بازیابی کنید.", "title": "کیف پول شما ایجاد شد." }, @@ -113,12 +100,16 @@ "details_create": "ایجاد", "details_label": "شرح", "details_setAmount": "دریافت با مقدار", - "details_share": "اشتراک‌گذاری", + "details_share": "هم‌رسانی...", + "address_not_found": "ایجاد آدرس دریافت ممکن نیست.", "header": "دریافت", + "reset": "بازنشانی", "maxSats": "بیشترین مقدار {max} ساتوشی است.", "maxSatsFull": "بیشترین مقدار {max} ساتوشی یا {currency} است.", "minSats": "کمترین مقدار {min} ساتوشی است.", - "minSatsFull": "کمترین مقدار {min} ساتوشی یا {currency} است." + "minSatsFull": "کمترین مقدار {min} ساتوشی یا {currency} است.", + "qrcode_for_the_address": "کد QR برای آدرس", + "bip47_explanation": "کدهای پرداخت یک آدرس همگانی هستند که از افشای آدرس‌های کیف پول شما جلوگیری می‌کنند. همهٔ سرویس‌ها از آن‌ها پشتیبانی نخواهند کرد." }, "send": { "provided_address_is_invoice": "به‌نظر می‌آید این آدرس یک صورت‌حساب لایتنینگ است. جهت پرداخت این صورت‌حساب لطفاً به کیف پول لایتنینگ خود بروید.", @@ -140,8 +131,15 @@ "create_to": "به", "create_tx_size": "سایز تراکنش", "create_verify": "تأیید در coinb.in", + "details_insert_contact": "افزودن مخاطب", "details_add_rec_add": "افزودن گیرنده", "details_add_rec_rem": "حذف گیرنده", + "details_add_recc_rem_all_alert_description": "آیا مطمئن هستید که می‌خواهید همهٔ گیرندگان را حذف کنید؟", + "details_add_rec_rem_all": "حذف همهٔ گیرندگان", + "details_recipients_title": "گیرندگان", + "details_recipient_title": "گیرندهٔ شمارهٔ {number} از #{total}", + "please_complete_recipient_title": "گیرندهٔ ناقص", + "please_complete_recipient_details": "لطفاً قبل از افزودن گیرندهٔ جدید، جزئیات گیرندهٔ شمارهٔ {number} را تکمیل کنید.", "details_address": "آدرس", "details_address_field_is_not_valid": "آدرس معتبر نیست.", "details_adv_fee_bump": "امکان افزایش کارمزد", @@ -155,12 +153,13 @@ "details_create": "ایجاد صورت‌حساب", "details_error_decode": "ناموفق در رمزگشایی آدرس بیت‌کوین", "details_fee_field_is_not_valid": "کارمزد معتبر نیست.", - "details_frozen": "{amount} بیت‌کوین مسدود شده است.", + "details_frozen": "{amount} BTC مسدودشده است.", "details_next": "بعدی", "details_no_signed_tx": "فایل انتخاب‌شده حاوی تراکنشی نیست که بتوان آن را وارد کرد.", "details_note_placeholder": "یادداشت به خود", "details_scan": "اسکن", "details_scan_hint": "جهت اسکن یا واردکردن مقصد دو بار ضربه بزنید", + "details_scan_error": "خطای اسکن", "details_total_exceeds_balance": "مقدار ارسالی بیش از ماندهٔ موجود است.", "details_total_exceeds_balance_frozen": "مقدار ارسالی از موجودی حاضر بیشتر است. لطفاً توجه داشته باشید که کوین‌های مسدودشده لحاظ نشده‌اند.", "details_unrecognized_file_format": "قالب فایل ناشناخته", @@ -174,6 +173,7 @@ "fee_1d": "۱ روز", "fee_3h": "۳ ساعت", "fee_custom": "دستی", + "insert_custom_fee": "کارمزد را وارد کنید", "fee_fast": "سریع", "fee_medium": "متوسط", "fee_replace_minvb": "نرخ کل کارمزد (ساتوشی به‌ازای هر بایت مجازی) که قصد پرداخت آن را دارید باید بیشتر از {min} ساتوشی/بایت مجازی باشد.", @@ -186,23 +186,26 @@ "input_total": "مجموع:", "permission_camera_message": "برای استفاده از دوربین به اجازهٔ شما نیاز داریم.", "psbt_sign": "امضاکردن تراکنش", + "invalid_psbt": "PSBT ارائه‌شده نامعتبر است.", "open_settings": "بازکردن تنظیمات", - "permission_storage_later": "بعداً از من بپرس", - "permission_storage_message": "برنامهٔ BlueWallet جهت ذخیرهٔ این فایل به اجازهٔ شما برای دسترسی به فضای ذخیره‌سازی نیاز دارد.", "permission_storage_denied_message": "برنامهٔ BlueWallet قادر به ذخیرهٔ این فایل نیست. لطفاً تنظیمات دستگاه خود را باز کرده و «اجازهٔ ذخیره‌سازی» (Storage Permission) را فعال کنید.", "permission_storage_title": "مجوز دسترسی به فضای ذخیره‌سازی", "psbt_clipboard": "کپی به کلیپ‌بورد", - "psbt_this_is_psbt": "این یک تراکنش بیت‌کوین ناقص‌امضاشده (Partially Signed Bitcoin Transaction) است. لطفاً برای اتمام آن را در کیف پول سخت‌افزاری خود امضا کنید.", + "psbt_this_is_psbt": "این یک تراکنش بیت‌کوین ناقص امضاشده (Partially Signed Bitcoin Transaction) است. لطفاً برای اتمام آن را در کیف پول سخت‌افزاری خود امضا کنید.", "psbt_tx_export": "صادرکردن به فایل", "no_tx_signing_in_progress": "هیچ امضای تراکنشی درحال‌انجام نیست.", "outdated_rate": "آخرین به‌روزرسانی نرخ: {date}", "psbt_tx_open": "بازکردن تراکنش امضاشده", "psbt_tx_scan": "اسکن تراکنش امضاشده", - "qr_error_no_qrcode": "قادر به یافتن کد QR در تصویر انتخاب‌شده نبودیم. اطمینان حاصل کنید که تصویر تنها حاوی کد QR بوده و محتوای اضافی‌ای همچون متن یا دکمه درون آن وجود ندارد.", + "qr_error_no_qrcode": "نتوانستیم کد QR معتبری در تصویر انتخاب‌شده پیدا کنیم. مطمئن شوید تصویر فقط شامل کد QR است و محتوای اضافی مانند متن یا دکمه ندارد.", "reset_amount": "بازنشانی مقدار", "reset_amount_confirm": "آیا می‌خواهید مقدار را بازنشانی کنید؟", "success_done": "انجام شد", - "txSaved": "فایل تراکنش ({filePath}) در پوشهٔ دانلودهای شما ذخیره شده است.", + "txSaved": "فایل تراکنش ({filePath}) ذخیره شد.", + "file_saved_at_path": "فایل ({filePath}) ذخیره شد.", + "cant_send_to_silentpayment_adress": "این کیف پول نمی‌تواند به آدرس‌های Silent Payments ارسال کند", + "cant_send_to_bip47": "این کیف پول نمی‌تواند به کدهای پرداخت BIP47 ارسال کند", + "cant_find_bip47_notification": "ابتدا این کد پرداخت را به مخاطبان اضافه کنید", "problem_with_psbt": "مشکل با تراکنش ناقص‌امضاشده (PSBT)" }, "settings": { @@ -216,28 +219,29 @@ "performance_score": "امتیاز کارکرد: {num}", "run_performance_test": "بررسی کارکرد", "about_selftest": "اجرای خودآزمایی", + "block_explorer_invalid_custom_url": "آدرس ارائه‌شده نامعتبر است. لطفاً آدرسی معتبر را وارد کنید که با http:// یا https:// آغاز شود.", "about_selftest_electrum_disabled": "خودآزمایی در حالت آفلاین در دسترس نیست. لطفاً حالت آفلاین را غیرفعال کرده و مجدد تلاش کنید.", "about_selftest_ok": "تمام بررسی‌های داخلی با موفقیت انجام شدند. کیف پول به‌درستی کار می‌کند.", "about_sm_github": "گیت‌هاب", - "about_sm_discord": "سرور دیسکورد", "about_sm_telegram": "کانال تلگرام", - "about_sm_twitter": "ما را در توئیتر دنبال کنید", - "advanced_options": "گزینه‌های پیشرفته", + "privacy_temporary_screenshots": "اجازهٔ ضبط صفحه", + "privacy_temporary_screenshots_instructions": "محافظت در برابر ضبط صفحه به‌طور موقت غیرفعال می‌شود تا گرفتن عکس از صفحه و ضبط صفحه ممکن شود. پس از بستن و بازکردن مجدد BlueWallet، محافظت به‌طور خودکار دوباره فعال خواهد شد.", "biometrics": "بیومتریک", + "biometrics_no_longer_available": "تنظیمات دستگاه شما تغییر کرده و دیگر با تنظیمات امنیتی انتخاب‌شده در برنامه مطابقت ندارد. لطفاً بیومتریک یا رمز دستگاه را دوباره فعال کرده، سپس برنامه را برای اعمال این تغییرات راه‌اندازی مجدد کنید.", "biom_10times": "شما برای واردکردن گذرواژهٔ خود ۱۰ بار تلاش کرده‌اید. آیا می‌خواهید فضای ذخیره‌سازی خود را بازنشانی کنید؟ این کار تمام کیف پول‌ها را حذف و فضای ذخیره‌سازی شما را رمزگشایی خواهد کرد.", "biom_conf_identity": "لطفاً هویت خود را تأیید کنید.", - "biom_no_passcode": "دستگاه شما دارای گذرواژه نیست. برای ادامه، لطفاً گذرواژه‌ای را در تنظیمات دستگاه تعیین کنید.", + "biom_no_passcode": "دستگاه شما رمز دستگاه یا بیومتریک فعالی ندارد. برای ادامه، لطفاً در برنامهٔ تنظیمات یک رمز دستگاه یا بیومتریک پیکربندی کنید.", "biom_remove_decrypt": "تمام کیف پول‌ها حذف و فضای ذخیره‌سازی شما رمزگشایی خواهد شد. آیا مطمئن هستید که می‌خواهید ادامه دهید؟", "currency": "واحد پول", - "currency_source": "قیمت برگرفته از", + "currency_source": "نرخ از این منبع به‌دست می‌آید", "currency_fetch_error": "حین دریافت نرخ برای واحد پول انتخاب‌شده خطایی رخ داد.", - "default_desc": "درصورت غیرفعال‌کردن، BlueWallet بلافاصله کیف پول انتخابی را هنگام راه‌اندازی باز می‌کند.", - "default_info": "اطلاعات پیش‌فرض", "default_title": "هنگام راه‌اندازی", - "default_wallets": "مشاهدهٔ همهٔ کیف پول‌ها", + "donate": "اهدای کمک مالی", + "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}", "electrum_offline_mode": "حالت آفلاین", @@ -246,32 +250,35 @@ "use_ssl": "از SSL استفاده کن", "electrum_saved": "تغییرات شما با موفقیت ذخیره شدند. ممکن است برای اعمال تغییرات به راه‌اندازی مجدد برنامه نیاز داشته باشید.", "set_electrum_server_as_default": "آیا {server} به‌عنوان سرور پیش‌فرض الکترام تعیین شود؟", - "set_lndhub_as_default": "آیا {url} به‌عنوان سرور پیش‌فرض LNDHub تعیین شود؟", + "set_lndhub_as_default": "آیا {url} به‌عنوان سرور پیش‌فرض LNDhub تعیین شود؟", "electrum_settings_server": "سرور الکترام", - "electrum_settings_explain": "برای استفاده از تنظیمات پیش‌فرض خالی بگذارید.", "electrum_status": "وضعیت", - "electrum_clear_alert_title": "تاریخچه پاک شود؟", - "electrum_clear_alert_message": "آیا می‌خواهید تاریخچهٔ سرورهای الکترام را پاک کنید؟", - "electrum_clear_alert_cancel": "لغو", - "electrum_clear_alert_ok": "بله", - "electrum_select": "انتخاب", - "electrum_reset": "بازنشانی به پیش‌فرض", + "electrum_preferred_server": "سرور ترجیحی", + "electrum_preferred_server_description": "سروری را که می‌خواهید کیف پول شما برای همهٔ فعالیت‌های بیت‌کوین استفاده کند وارد کنید. پس از تنظیم، کیف پول شما به‌طور انحصاری از این سرور برای بررسی موجودی‌ها، ارسال تراکنش‌ها و دریافت داده‌های شبکه استفاده خواهد کرد. قبل از تنظیم، از قابل‌اعتماد بودن این سرور اطمینان حاصل کنید.", "electrum_unable_to_connect": "ناموفق در اتصال به {server}", - "electrum_history": "تاریخچهٔ سرورها", - "electrum_reset_to_default": "آیا از بازنشانی تنظیمات الکترام به حالت پیش‌فرض اطمینان دارید؟", - "electrum_clear": "پاک‌کردن", - "tor_supported": "پشتیبانی از تور", - "tor_unsupported": "اتصال‌های تور پشتیبانی نمی‌شوند.", + "electrum_history": "تاریخچه", + "electrum_reset_to_default": "این کار به BlueWallet اجازه می‌دهد به‌صورت تصادفی سروری را از فهرست سرورها انتخاب کند.", + "electrum_reset": "بازنشانی به پیش‌فرض", + "electrum_reset_to_default_and_clear_history": "بازنشانی به پیش‌فرض و پاک‌کردن تاریخچه", "encrypt_decrypt": "رمزگشایی فضای ذخیره‌سازی", "encrypt_decrypt_q": "آیا از رمزگشایی فضای ذخیره‌سازی خود اطمینان دارید؟ این کار اجازه می‌دهد تا کیف پول‌های شما بدون گذرواژه قابل‌دسترسی باشند.", - "encrypt_enc_and_pass": "رمزگذاری و محافظت با گذرواژه", + "encrypt_enc_and_pass": "محافظت‌شده با گذرواژه", + "encrypt_storage_explanation_headline": "فعال‌کردن رمزگذاری فضای ذخیره‌سازی", + "encrypt_storage_explanation_description_line1": "فعال‌کردن رمزگذاری فضای ذخیره‌سازی لایهٔ محافظتی اضافه‌ای به برنامهٔ شما می‌افزاید و شیوهٔ ذخیره‌سازی داده‌های شما در دستگاه را امن می‌کند. این کار دسترسی دیگران به اطلاعات شما را بدون اجازه دشوارتر می‌سازد.", + "encrypt_storage_explanation_description_line2": "بااین‌حال، مهم است بدانید که این رمزگذاری فقط از دسترسی به کیف پول‌های ذخیره‌شده در زنجیرکلید دستگاه محافظت می‌کند. این کار گذرواژه یا محافظت اضافی روی خود کیف پول‌ها قرار نمی‌دهد.", + "i_understand": "متوجه شدم", + "block_explorer": "مرورگر بلاک", + "block_explorer_preferred": "استفاده از مرورگر بلاک ترجیحی", + "block_explorer_error_saving_custom": "خطا در ذخیرهٔ مرورگر بلاک ترجیحی", "encrypt_title": "امنیت", "encrypt_tstorage": "فضای ذخیره‌سازی", "encrypt_use": "از {type} استفاده کنید", - "encrypt_use_expl": "از {type} برای تأیید هویت شما قبل از انجام تراکنش، بازکردن قفل، صادرکردن، یا حذف کیف پول استفاده خواهد شد. از {type} برای بازکردن فضای ذخیره‌سازی رمزگذاری‌شده استفاده نخواهد شد.", + "set_as_preferred": "تنظیم به‌عنوان ترجیحی", + "set_as_preferred_electrum": "تنظیم {host}:{port} به‌عنوان سرور ترجیحی، اتصال تصادفی به سرور پیشنهادی را غیرفعال خواهد کرد.", + "encrypted_feature_disabled": "این قابلیت با رمزگذاری فضای ذخیره‌سازی فعال قابل استفاده نیست.", + "encrypt_use_expl": "{type} برای تأیید هویت شما قبل از انجام تراکنش، بازکردن قفل، صادرکردن یا حذف کیف پول استفاده خواهد شد.", + "biometrics_fail": "اگر {type} فعال نباشد یا در بازکردن قفل موفق نشود، می‌توانید از رمز دستگاه به‌عنوان جایگزین استفاده کنید.", "general": "عمومی", - "general_adv_mode": "حالت پیشرفته", - "general_adv_mode_e": "درصورت فعال‌بودن، گزینه‌های پیشرفته‌ای مانند انواع مختلف کیف پول، امکان تعیین سرور LNDHub جهت اتصال، و آنتروپی سفارشی را در هنگام ایجاد کیف پول مشاهده خواهید کرد.", "general_continuity": "پیوستگی", "general_continuity_e": "درصورت فعال‌بودن، می‌توانید کیف پول‌های انتخاب‌شده و تراکنش‌ها را با استفاده از سایر دستگاه‌های متصل به Apple iCloud خود مشاهده کنید.", "groundcontrol_explanation": "سرویس GroundControl یک سرور اعلانات متن‌باز و رایگان برای کیف پول‌های بیت‌کوین است. شما می‌توانید سرور GroundControl خود را نصب کرده و آدرس آن را اینجا قرار دهید تا به زیرساخت‌های BlueWallet متکی نباشید. برای استفاده از تنظیمات پیش‌فرض خالی بگذارید.", @@ -279,19 +286,22 @@ "language": "زبان", "last_updated": "آخرین به‌روزرسانی", "language_isRTL": "راه‌اندازی مجدد BlueWallet جهت اعمال تغییرات چینش زبان ضروری است.", - "lightning_error_lndhub_uri": "یوآرآی LNDHub غیرمعتبر", + "license": "پروانه", + "lightning_error_lndhub_uri": "آدرس LNDhub نامعتبر است", + "lightning_error_lndhub_uri_tor": "آدرس LNDhub نامعتبر است. لطفاً مطمئن شوید که برنامهٔ Orbot متصل است و دوباره تلاش کنید.", "lightning_saved": "تغییرات شما با موفقیت ذخیره شدند.", "lightning_settings": "تنظیمات لایتنینگ", - "tor_settings": "تنظیمات تور", + "lightning_settings_explain": "برای اتصال به گرهٔ LND خود، لطفاً LNDhub را نصب کرده و آدرس آن را اینجا در تنظیمات قرار دهید. لطفاً توجه داشته باشید که فقط کیف پول‌های ساخته‌شده پس از ذخیرهٔ تغییرات به LNDhub تعیین‌شده متصل خواهند شد.", + "lndhub_github": "مخزن GitHub", "network": "شبکه", "network_broadcast": "انتشار تراکنش", "network_electrum": "سرور الکترام", + "electrum_suggested_description": "زمانی‌که سرور ترجیحی تنظیم نشده باشد، سرور پیشنهادی به‌صورت تصادفی برای استفاده انتخاب می‌شود.", "not_a_valid_uri": "یوآرآی غیرمعتبر", "notifications": "اعلانات", "open_link_in_explorer": "بازکردن پیوند در مرورگر", "password": "گذرواژه", - "password_explain": "گذرواژه‌ای را که برای رمزگشایی فضای ذخیره‌سازی استفاده خواهید کرد ایجاد کنید.", - "passwords_do_not_match": "گذرواژه‌ها مطابقت ندارند.", + "password_explain": "گذرواژه‌ای را که برای بازکردن قفل فضای ذخیره‌سازی خود استفاده خواهید کرد وارد کنید.", "plausible_deniability": "انکار موجه", "privacy": "حریم خصوصی", "privacy_read_clipboard": "خواندن کلیپ‌بورد", @@ -301,13 +311,12 @@ "privacy_clipboard_explanation": "اگر آدرس یا صورت‌حسابی در کلیپ‌بورد شما پیدا شد، میان‌بر ارائه می‌دهد.", "privacy_do_not_track": "غیرفعال‌کردن تحلیل", "privacy_do_not_track_explanation": "اطلاعات کارایی و پایداری جهت تحلیل ارسال نخواهد شد.", - "push_notifications": "پوش نوتیفیکیشن", "rate": "نرخ", - "retype_password": "گذرواژه را دوباره بنویسید", + "push_notifications_explanation": "با فعال‌کردن اعلانات، توکن دستگاه شما به‌همراه آدرس‌های کیف پول و شناسه‌های تراکنش برای همهٔ کیف پول‌ها و تراکنش‌های انجام‌شده پس از فعال‌کردن اعلانات به سرور ارسال خواهد شد. توکن دستگاه برای ارسال اعلانات استفاده می‌شود و اطلاعات کیف پول به ما اجازه می‌دهد دربارهٔ بیت‌کوین ورودی یا تأییدیه‌های تراکنش به شما اطلاع دهیم.\n\nفقط اطلاعات پس از فعال‌کردن اعلانات منتقل می‌شود—هیچ‌چیزی از قبل جمع‌آوری نمی‌شود.\n\nغیرفعال‌کردن اعلانات تمام این اطلاعات را از سرور حذف خواهد کرد. علاوه‌براین، حذف کیف پول از برنامه نیز اطلاعات مرتبط با آن را از سرور حذف می‌کند.", "selfTest": "خودآزمایی", "save": "ذخیره", "saved": "ذخیره شد", - "success_transaction_broadcasted": "موفقیت‌آمیز بود! تراکنش شما منتشر شد!", + "success_transaction_broadcasted": "تراکنش شما با موفقیت منتشر شد!", "total_balance": "موجودی کل", "total_balance_explanation": "نمایش موجودی کل تمام کیف پول‌های شما در ابزارک‌های صفحهٔ اصلی", "widgets": "ابزارک‌ها", @@ -315,15 +324,17 @@ }, "notifications": { "would_you_like_to_receive_notifications": "آیا می‌خواهید هنگام دریافت وجه اعلان دریافت کنید؟", - "no_and_dont_ask": "نه، و دیگر از من نپرس", - "ask_me_later": "بعداً از من بپرس" + "notifications_subtitle": "پرداخت‌های ورودی و تأییدیه‌های تراکنش", + "no_and_dont_ask": "خیر، و دیگر از من نپرس.", + "permission_denied_message": "شما اجازهٔ ارسال اعلانات را رد کرده‌اید. اگر می‌خواهید اعلانات دریافت کنید، لطفاً آن‌ها را در تنظیمات دستگاه خود فعال کنید." }, "transactions": { "cancel_explain": "ما این تراکنش را با تراکنشی که گیرندهٔ آن شما هستید و کارمزد بیشتری دارد جایگزین خواهیم کرد. این درعمل تراکنش کنونی را لغو می‌کند. این کار Replace by Fee (به‌اختصار RBF) نام دارد—جایگزینی با کارمزد.", "cancel_no": "این تراکنش قابل‌جایگزینی نیست.", "cancel_title": "این تراکنش را لغو کن (RBF)", + "transaction_loading_error": "در بارگذاری تراکنش مشکلی پیش آمد. لطفاً بعداً دوباره تلاش کنید.", + "transaction_not_available": "تراکنش در دسترس نیست", "confirmations_lowercase": "{confirmations} تأیید", - "copy_link": "کپی لینک", "expand_note": "نمایش کامل یادداشت", "cpfp_create": "ایجاد", "cpfp_exp": "ما تراکنش دیگری را ایجاد خواهیم کرد که تراکنش تأییدنشدهٔ شما را خرج می‌کند. کارمزد کل بیشتر از کارمزد تراکنش اولیه خواهد بود، بنابراین سریع‌تر تأیید می‌شود. این کار Child Pays for Parent (به‌اختصار CPFP) نام دارد—فرزند به‌جای والدین می‌پردازد.", @@ -331,20 +342,22 @@ "cpfp_title": "افزایش کارمزد (CPFP)", "details_balance_hide": "پنهان‌کردن موجودی", "details_balance_show": "نمایش موجودی", - "details_block": "ارتفاع بلاک", "details_copy": "کپی", - "details_copy_amount": "کپی مقدار", "details_copy_block_explorer_link": "کپی لینک مرورگر بلاک", "details_copy_note": "کپی یادداشت", "details_copy_txid": "کپی شناسهٔ تراکنش", - "details_from": "ورودی", "details_inputs": "ورودی‌ها", "details_outputs": "خروجی‌ها", "date": "تاریخ", "details_received": "دریافت‌شده", - "transaction_note_saved": "یادداشت تراکنش با موفقیت ذخیره شد.", - "details_show_in_block_explorer": "مشاهده در مرورگر بلاک", + "details_view_in_browser": "مشاهده در مرورگر", "details_title": "تراکنش", + "incoming_transaction": "تراکنش ورودی", + "outgoing_transaction": "تراکنش خروجی", + "expired_transaction": "تراکنش منقضی‌شده", + "pending_transaction": "تراکنش در انتظار ثبت", + "offchain": "خارج از زنجیره", + "onchain": "روی زنجیره", "details_to": "خروجی", "enable_offline_signing": "این کیف پول در کنار امضای آفلاین استفاده نمی‌شود. آیا مایل به فعال‌کردن این امکان هستید؟", "list_conf": "تأییدها: {number}", @@ -354,37 +367,67 @@ "eta_10m": "زمان تقریبی رسیدن: حدود ده دقیقه", "eta_3h": "زمان تقریبی رسیدن: حدود سه ساعت", "eta_1d": "زمان تقریبی رسیدن: حدود یک روز", - "view_wallet": "مشاهدهٔ {walletLabel}", "list_title": "تراکنش‌ها", + "list_title_sent": "ارسال‌شده", + "list_title_received": "دریافت‌شده", + "transaction": "تراکنش", "open_url_error": "ناموفق در بازکردن لینک با مرورگر پیش‌فرض. لطفاً مرورگر پیش‌فرض خود را تغییر داده و مجدد تلاش کنید.", "rbf_explain": "ما این تراکنش را با تراکنشی با کارمزد بالاتر جایگزین خواهیم کرد تا سریع‌تر تأیید شود. این کار Replace by Fee (به‌اختصار RBF) نام دارد—جایگزینی با کارمزد.", - "rbf_title": "افزایش کارمزد (RBF)", - "status_bump": "افزایش کارمزد", + "rbf_title": "تسریع (RBF)", + "status_bump": "تسریع", "status_cancel": "لغو تراکنش", "transactions_count": "تعداد تراکنش‌ها", "txid": "شناسهٔ تراکنش", - "updating": "درحال به‌روزرسانی…" + "updating": "درحال به‌روزرسانی…", + "watchOnlyWarningTitle": "هشدار امنیتی", + "watchOnlyWarningDescription": "مراقب کلاهبردارانی باشید که اغلب از کیف پول‌های «فقط‌خواندنی» برای فریب کاربران استفاده می‌کنند. این کیف پول‌ها به شما اجازهٔ کنترل یا ارسال وجه را نمی‌دهند؛ تنها امکان مشاهدهٔ موجودی را فراهم می‌کنند.", + "custom_fee_warning_title": "هشدار", + "custom_fee_warning_description": "کارمزدهای کمتر از ۱ sat/vB معتبر هستند، اما ممکن است به‌دلیل سیاست‌های گره‌ها بازپخش نشوند.", + "details_eta_analyzing": "درحال تحلیل...", + "details_sent": "ارسال‌شده", + "details_section": "جزئیات", + "details_explorer": "مرورگر", + "details_network_fee": "کارمزد شبکه", + "details_to_address": "به", + "details_id": "شناسه", + "details_note": "یادداشت", + "details_add_note": "افزودن", + "details_advanced": "پیشرفته", + "details_fee_rate": "نرخ کارمزد", + "details_size": "اندازه", + "details_virtual_size": "اندازهٔ مجازی", + "details_tx_hex": "هگزادسیمال تراکنش", + "details_inputs_count": "ورودی‌ها ({count})", + "details_outputs_count": "خروجی‌ها ({count})" }, "wallets": { "add_bitcoin": "بیت‌کوین", "add_bitcoin_explain": "کیف پول ساده و قدرتمند بیت‌کوین", "add_create": "ایجاد", + "total_balance": "موجودی کل", + "add_entropy_reset_title": "بازنشانی آنتروپی", + "add_entropy_reset_message": "تغییر نوع کیف پول، آنتروپی فعلی را بازنشانی خواهد کرد. آیا می‌خواهید ادامه دهید؟", + "add_entropy": "آنتروپی", + "add_entropy_bytes": "{bytes} بایت آنتروپی", "add_entropy_generated": "{gen} بایت از آنتروپی تولیدشده", "add_entropy_provide": "فراهم‌کردن آنتروپی از طریق انداختن تاس", "add_entropy_remain": "{gen} بایت از آنتروپی تولیدشده. {rem} بایت باقی‌مانده از تولیدکنندهٔ اعداد تصادفی سیستم گرفته خواهد شد.", "add_import_wallet": "واردکردن کیف پول", "add_lightning": "لایتنینگ", "add_lightning_explain": "برای خرج‌کردن با تراکنش‌های آنی", - "add_lndhub": "به LNDHub خود متصل شوید", - "add_lndhub_error": "آدرس گره ارائه‌شده گره LNDHub معتبری نیست.", + "add_lndhub": "اتصال به LNDhub خود", + "add_lndhub_error": "آدرس گرهٔ ارائه‌شده یک گرهٔ LNDhub نامعتبر است.", "add_lndhub_placeholder": "آدرس گره شما", "add_placeholder": "کیف پول اول من", "add_title": "افزودن کیف پول", "add_wallet_name": "نام", "add_wallet_type": "نوع", - "balance": "موجودی", + "add_wallet_seed_length": "طول سید", + "add_wallet_seed_length_12": "۱۲ کلمه", + "add_wallet_seed_length_24": "۲۴ کلمه", "clipboard_bitcoin": "شما یک آدرس بیت‌کوین در کلیپ‌بورد خود دارید. آیا می‌خواهید برای تراکنش از آن استفاده کنید؟", "clipboard_lightning": "شما در کلیپ‌بورد خود یک صورت‌حساب لایتنینگ دارید. آیا می‌خواهید برای تراکنش از آن استفاده کنید؟", + "clear_clipboard_on_import": "پاک‌کردن کلیپ‌بورد هنگام واردکردن", "details_address": "آدرس", "details_advanced": "پیشرفته", "details_are_you_sure": "مطمئن هستید؟", @@ -394,18 +437,21 @@ "details_delete": "حذف", "details_delete_wallet": "حذف کیف پول", "details_derivation_path": "مسیر اشتقاق", - "details_display": "نمایش در لیست کیف پول‌ها", + "details_display": "نمایش در صفحهٔ اصلی", "details_export_backup": "صادرکردن/نسخهٔ پشتیبان", + "details_export_history": "گرفتن خروجی تاریخچه به فرمت CSV", "details_master_fingerprint": "اثر انگشت اصلی", "details_multisig_type": "چندامضایی", - "details_no_cancel": "خیر، لغو کن", - "details_save": "ذخیره", "details_show_xpub": "نمایش XPUB کیف پول", "details_show_addresses": "نمایش آدرس‌ها", "details_title": "کیف پول", + "wallets": "کیف پول‌ها", + "swipe_balance_hide": "پنهان‌کردن", + "swipe_balance_show": "نمایش", + "drag_to_reorder": "برای ترتیب‌بندی بکشید", + "clear_search": "پاک‌کردن جستجو", "details_type": "نوع", "details_use_with_hardware_wallet": "استفاده همراه با کیف پول سخت‌افزاری", - "details_wallet_updated": "کیف پول به‌روز شد", "details_yes_delete": "بله، حذف کن", "enter_bip38_password": "گذرواژه را برای رمزگشایی وارد کنید", "export_title": "صادرکردن کیف پول", @@ -421,59 +467,85 @@ "import_success_watchonly": "کیف پول شما با موفقیت وارد شد. هشدار: این یک کیف پول ناظر است، نمی‌توانید از آن خرج کنید", "import_search_accounts": "جستجوی حساب‌ها", "import_title": "واردکردن", + "learn_more": "اطلاعات بیشتر", "import_discovery_title": "کشف", "import_discovery_subtitle": "کیف پول پیداشده را انتخاب کنید", "import_discovery_derivation": "استفاده از مسیر اشتقاق دلخواه", "import_discovery_no_wallets": "کیف پولی یافت نشد.", + "import_discovery_offline": "BlueWallet در حال حاضر در حالت آفلاین است. در این حالت نمی‌تواند وجود کیف پول را تأیید کند، بنابراین باید کیف پول صحیح را به‌صورت دستی انتخاب کنید", "import_derivation_found": "پیدا شد", "import_derivation_found_not": "پیدا نشد", "import_derivation_loading": "درحال‌بارگذاری…", - "import_derivation_subtitle": "مسیر اشتقاق دلخواه را وارد کرده، و ما تلاش خواهیم کرد کیف پول شما را پیدا کنیم.", + "import_derivation_subtitle": "مسیر اشتقاق دلخواه را وارد کنید و ما تلاش می‌کنیم کیف پول شما را کشف کنیم.", "import_derivation_title": "مسیر اشتقاق", "import_derivation_unknown": "نامشخص", - "import_wrong_path": "مسیر اشتقاق نادرست", + "import_wrong_path": "مسیر اشتقاق اشتباه", "list_create_a_button": "هم‌اکنون اضافه کن", "list_create_a_wallet": "افزودن کیف پول", - "list_create_a_wallet_text": "مجانی است، و می‌توانید هر تعداد\nکه دوست داشتید بسازید.", + "list_create_a_wallet_text": "رایگان است، و می‌توانید \nبه هر تعداد که خواستید بسازید.", "list_empty_txs1": "تراکنش‌های شما در اینجا نمایش داده خواهند شد.", "list_empty_txs1_lightning": "برای تراکنش‌های روزمره بهتر است از کیف پول لایتنینگ استفاده شود. کارمزدها به‌طرز غیرمنصفانه‌ای ارزان و سرعت فوق‌العاده بالاست.", "list_empty_txs2": "با کیف پول خود شروع کنید.", "list_empty_txs2_lightning": "\nبرای شروع استفاده، روی «مدیریت دارایی» بزنید و موجودی خود را شارژ کنید.", "list_latest_transaction": "آخرین تراکنش", - "list_ln_browser": "مرورگر LApp", "list_long_choose": "انتخاب عکس", - "list_long_clipboard": "کپی از کلیپ‌بورد", + "paste_from_clipboard": "چسباندن", + "import_file": "واردکردن فایل", "list_long_scan": "اسکن کد QR", "list_title": "کیف پول‌ها", "list_tryagain": "دوباره امتحان کنید", "no_ln_wallet_error": "قبل از پرداخت یک صورت‌حساب لایتنینگ، ابتدا باید یک کیف پول لایتنینگ اضافه کنید.", "looks_like_bip38": "این به کلید خصوصی محافظت‌شده با گذرواژه (BIP38) شباهت دارد.", - "reorder_title": "بازچینی کیف پول‌ها", - "reorder_instructions": "روی یک کیف پول بزنید و نگه دارید تا آن را در لیست جابه‌جا کنید.", + "manage_title": "مدیریت کیف پول‌ها", + "no_results_found": "نتیجه‌ای یافت نشد.", "please_continue_scanning": "لطفاً به اسکن‌کردن ادامه دهید.", "select_no_bitcoin": "هیچ کیف پول بیت‌کوینی درحال‌حاضر دردسترس نیست.", "select_no_bitcoin_exp": "یک کیف پول بیت‌کوین برای پرکردن کیف پول‌های لایتنینگ نیاز است. لطفاً یکی بسازید یا وارد کنید.", "select_wallet": "انتخاب کیف پول", - "xpub_copiedToClipboard": "در کلیپ‌بورد کپی شد.", "pull_to_refresh": "برای به‌روزسانی به پایین بکشید", - "warning_do_not_disclose": "هشدار! فاش نکنید.", + "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 کیف پول" + "xpub_title": "کلید XPUB کیف پول", + "manage_wallets_search_placeholder": "جستجوی کیف پول‌ها، آدرس‌ها، تراکنش‌ها و یادداشت‌ها", + "more_info": "اطلاعات بیشتر", + "details_delete_wallet_error_message": "در تأیید حذف این کیف پول از اعلانات مشکلی پیش آمد—این می‌تواند به‌دلیل مشکل شبکه یا اتصال ضعیف باشد. اگر ادامه دهید، ممکن است حتی پس از حذف کیف پول، همچنان برای تراکنش‌های مرتبط با این کیف پول اعلانات دریافت کنید.", + "details_delete_anyway": "به‌هرحال حذف کن" + }, + "total_balance_view": { + "display_in_bitcoin": "نمایش به بیت‌کوین", + "hide": "پنهان‌کردن", + "display_in_sats": "نمایش به ساتوشی", + "display_in_fiat": "نمایش به {currency}", + "title": "موجودی کل", + "explanation": "موجودی کل تمام کیف پول‌های خود را در صفحهٔ مرور مشاهده کنید." }, "multisig": { - "multisig_vault": "گاوصندوق", + "multisig_vault": "گاوصندوق چندامضایی", "default_label": "گاوصندوق چندامضایی", "multisig_vault_explain": "بالاترین امنیت برای مقادیر زیاد", "provide_signature": "ارائهٔ امضا", + "provide_signature_details": "از دستگاه و کیف پولی که کلید در آن قرار دارد برای امضای این تراکنش استفاده کنید", + "provide_signature_details_bluewallet": "در BlueWallet، به منوی صفحهٔ ارسال بروید و گزینهٔ زیر را انتخاب کنید ", + "provide_signature_next_steps": "اسکن یا واردکردن تراکنش امضاشده", + "provide_signature_next_steps_details": "پس از آنکه کیف پول شما تراکنش را با موفقیت امضا کرد، کد QR ارائه‌شده را اسکن کنید یا فایل همراه را وارد کنید، سپس قبل از انتشار، همهٔ جزئیات تراکنش را بررسی کنید.", "vault_key": "کلید گاوصندوق {number}", "required_keys_out_of_total": "کلیدهای موردنیاز از کل", "fee": "کارمزد: {number}", "fee_btc": "{number} بیت‌کوین", "confirm": "تأیید", "header": "ارسال", - "share": "اشتراک‌گذاری", + "share": "هم‌رسانی...", "view": "مشاهده", + "shared_key_detected": "هم‌امضاکنندهٔ به‌اشتراک‌گذاشته‌شده", + "shared_key_detected_question": "یک هم‌امضاکننده با شما به اشتراک گذاشته شده است، آیا می‌خواهید آن را وارد کنید؟", "manage_keys": "مدیریت کلیدها", "how_many_signatures_can_bluewallet_make": "امضاهایی که BlueWallet می‌تواند ایجاد کند", "signatures_required_to_spend": "امضاهای موردنیاز: {number}", @@ -499,20 +571,21 @@ "quorum_header": "حد نصاب", "of": "از", "wallet_type": "نوع کیف پول", - "invalid_mnemonics": "به‌نظر نمی‌رسد این عبارت یادیار (mnemonic phrase) معتبر باشد.", - "invalid_cosigner": "دادهٔ امضاکنندهٔ مشترک غیرمعتبر", + "invalid_mnemonics": "به‌نظر می‌رسد این عبارت یادیار معتبر نیست.", + "invalid_cosigner": "دادهٔ هم‌امضاکننده نامعتبر است", "not_a_multisignature_xpub": "این XPUB از یک کیف پول چندامضایی نیست!", - "invalid_cosigner_format": "امضاکنندهٔ مشترک نادرست: این یک امضاکنندهٔ مشترک برای قالب {format} نیست.", + "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": "این 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": "این امضاکنندهٔ مشترک قبلاً وارد شده است.", + "view_edit_cosigners": "مشاهده/ویرایش هم‌امضاکنندگان", + "this_cosigner_is_already_imported": "این هم‌امضاکننده قبلاً وارد شده است.", "export_signed_psbt": "صادرکردن PSBT امضاشده", "input_fp": "اثر انگشت را وارد کنید", "input_fp_explain": "جهت استفاده از تنظیمات پیش‌فرض (۰۰۰۰۰۰۰۰) رد کنید", @@ -540,18 +613,31 @@ "no_wallet_owns_address": "آدرس ارائه‌شده متعلق به هیچ‌کدام از کیف پول‌های موجود نیست.", "view_qrcode": "مشاهدهٔ کد QR" }, + "autofill_word": { + "title": "کلمهٔ نهایی سید", + "enter": "عبارت یادیار ناقص خود را وارد کنید", + "generate_word": "تولید کلمهٔ نهایی", + "error": "ورودی، عبارت یادیار ناقص ۱۱ یا ۲۳ کلمه‌ای نیست. لطفاً دوباره تلاش کنید." + }, "cc": { "change": "باقی‌مانده (change)", "coins_selected": "کوین‌های انتخاب‌شده ({number})", "selected_summ": "انتخاب‌شده: {value}", - "empty": "این کیف پول درحال‌حاضر هیچ کوینی ندارد.", + "empty": "این کیف پول در حال حاضر کوینی ندارد.", "freeze": "مسدود", "freezeLabel": "مسدودکردن", "freezeLabel_un": "عدم مسدودسازی", "header": "مدیریت کوین", "use_coin": "استفاده از کوین", "use_coins": "استفاده از کوین‌ها", - "tip": "به شما اجازه می‌دهد برای مدیریت بهتر کیف پول، کوین‌ها را مشاهده، برچسب‌گذاری، مسدود، یا انتخاب کنید. شما می‌توانید با زدن روی دایره‌های رنگی بیش از یک کوین را انتخاب کنید." + "tip": "به شما اجازه می‌دهد برای مدیریت بهتر کیف پول، کوین‌ها را مشاهده، برچسب‌گذاری، مسدود، یا انتخاب کنید. شما می‌توانید با زدن روی دایره‌های رنگی بیش از یک کوین را انتخاب کنید.", + "sort_asc": "صعودی", + "sort_desc": "نزولی", + "sort_height": "ارتفاع", + "sort_value": "ارزش", + "sort_label": "برچسب", + "sort_status": "وضعیت", + "sort_by": "مرتب‌سازی بر اساس" }, "units": { "BTC": "بیت‌کوین", @@ -560,6 +646,8 @@ "sats": "ساتوشی" }, "addresses": { + "copy_private_key": "کپی کلید خصوصی", + "sensitive_private_key": "هشدار: کلیدهای خصوصی بسیار حساس هستند. ادامه می‌دهید؟", "sign_title": "امضا/صحت‌سنجی پیام", "sign_help": "اینجا شما می‌توانید امضای رمزنگارانه‌ای را از روی یک آدرس بیت‌کوین تولید یا صحت‌سنجی کنید.", "sign_sign": "امضاکردن", @@ -593,7 +681,24 @@ }, "bip47": { "payment_code": "کد پرداخت", - "payment_codes_list": "فهرست کدهای پرداخت", + "contacts": "مخاطبان", + "bip47_explain": "کد بازکاربردپذیر و قابل هم‌رسانی", + "bip47_explain_subtitle": "BIP47", + "purpose": "کد بازکاربردپذیر و قابل همرسانی (BIP47)", + "pay_this_contact": "پرداخت به این مخاطب", + "rename_contact": "ویرایش نام مخاطب", + "copy_payment_code": "کپی کد پرداخت", + "hide_contact": "پنهان‌کردن مخاطب", + "rename": "ویرایش نام", + "provide_name": "نام جدیدی برای این مخاطب ارائه دهید", + "add_contact": "افزودن مخاطب", + "provide_payment_code": "کد پرداخت را ارائه دهید", + "invalid_pc": "کد پرداخت نامعتبر", + "notification_tx_unconfirmed": "تراکنش اعلان هنوز تأیید نشده است، لطفاً صبر کنید", + "failed_create_notif_tx": "ایجاد تراکنش روی زنجیره ناموفق بود", + "onchain_tx_needed": "به تراکنش روی زنجیره نیاز است", + "notif_tx_sent": "تراکنش اعلان ارسال شد. لطفاً منتظر تأیید آن باشید", + "notif_tx": "تراکنش اعلان", "not_found": "کد پرداخت یافت نشد" } } diff --git a/loc/fi_fi.json b/loc/fi_fi.json index 2cfdda4ce61..9d7a2a83202 100644 --- a/loc/fi_fi.json +++ b/loc/fi_fi.json @@ -1,41 +1,51 @@ { "_": { "bad_password": "Väärä salasana, yritä uudelleen.", + "copied": "Kopioitu!", "cancel": "Peruuta", "continue": "Jatka", "clipboard": "Leikepöytä", + "discard_changes": "Hylkää muutokset?", + "discard_changes_explain": "Sinulla on tallentamattomia muutoksia. Haluatko varmasti hylätä ne ja poistua näytöltä?", "enter_password": "Anna salasana", "never": "ei koskaan", - "disabled": "Poissa käytöstä", "of": "{number} / {total}", "ok": "OK", - "storage_is_encrypted": "Tallennustilasi on salattu. Sen purkamiseksi vaaditaan salasana", + "enter_url": "Syötä URL", + "storage_is_encrypted": "Tallennustilasi on salattu. Sen purkamiseksi vaaditaan salasana.", "yes": "Kyllä", "no": "Ei", - "save": "Tallenna", + "save": "Tallenna...", "seed": "Siemen", "success": "Onnistui", "wallet_key": "Lompakkoavain", - "invalid_animated_qr_code_fragment": "Virheellinen animoitu QRCode-fragmentti, yritä uudelleen", - "file_saved": "Tiedosto {filePath} on talletettu sinun {destination}.", - "downloads_folder": "Lataukset-kansio" - }, - "alert": { - "default": "Hälytys" + "close": "Sulje", + "change_input_currency": "Vaihda lähdevaluutta", + "refresh": "Päivitä", + "pick_image": "Valitse kirjastosta", + "pick_file": "Valitse tiedosto", + "enter_amount": "Syötä määrä", + "qr_custom_input_button": "Napauta 10 kertaa syöttääksesi mukautetun arvon", + "unlock": "Avaa lukitus", + "port": "Portti", + "ssl_port": "SSL Portti", + "suggested": "Ehdotettu" }, "azteco": { "codeIs": "Kuponkikoodisi on", "errorBeforeRefeem": "Ennen lunastamista sinun on ensin lisättävä Bitcoin-lompakko", "errorSomething": "Jotain meni pieleen. Onko tämä kuponki edelleen voimassa?", - "redeem": "Lunastus lompakkoon", - "redeemButton": "Lunastus", + "redeem": "Lunasta lompakkoon", + "redeemButton": "Lunasta", "success": "Onnistui", + "successMessage": "Kuponki lunastettu onnistuneesti! Varojesi pitäisi saapua Bitcoin-lompakkoosi pian.", "title": "Lunasta Azte.co kuponki" }, "entropy": { "save": "Tallenna", "title": "Entropia", - "undo": "Kumoa" + "undo": "Kumoa", + "amountOfEntropy": "{bits} / {limit} bittiä" }, "errors": { "broadcast": "Lähetys epäonnistui", @@ -43,65 +53,45 @@ "network": "Verkkovirhe" }, "lnd": { - "active": "Aktiivinen", - "inactive": "Passiivinen", - "channels": "Kanavat", - "no_channels": "Ei kanavia", - "claim_balance": "Lunasta saldo {balance}", - "close_channel": "Sulje kanava", - "new_channel": "Uusi kanava", - "errorInvoiceExpired": "Lasku vanheni", - "force_close_channel": "Pakota kanavan sulku?", + "errorInvoiceExpired": "Lasku vanheni.", "expired": "Erääntynyt", - "node_alias": "Solmun lempinimi", "expiresIn": "Vanhenee {time} minuutissa", "payButton": "Maksa", - "open_channel": "Avaa kanava", - "funding_amount_placeholder": "Rahoitettava määrä, esimerkiksi 0.001", - "opening_channnel_for_from": "Ota rahoitus {fromWalletLabel}:sta kanavan avaamiseksi lompakkoon {forWalletLabel}", - "are_you_sure_open_channel": "Oletko varma että haluat avata tämän kanavan?", - "potentialFee": "Mahdollinen siirtomaksu: {fee}", - "remote_host": "Etäpalvelin", + "payment": "Maksu", + "placeholder": "Lasku", + "potentialFee": "Mahdollinen siirtokulu: {fee}", "refill": "Täytä", - "reconnect_peer": "Palauta yhteys naapuriin", "refill_create": "Jatka luomalla Bitcoin-lompakko, jolla voit täyttää sen.", "refill_external": "Täytä ulkoisella lompakolla", "refill_lnd_balance": "Täytä Salamalompakon saldoa", "sameWalletAsInvoiceError": "Et voi maksaa laskua samalla lompakolla, jolla se on luotu.", - "title": "hallinnoi varoja", - "can_send": "Lähetettävissä", - "can_receive": "Vastaanotettavissa", - "view_logs": "Näytä lokitiedot" + "title": "hallinnoi varoja" }, "lndViewInvoice": { "additional_info": "Lisäinformaatio", "for": "Kenelle:", "lightning_invoice": "Salamalasku", - "open_direct_channel": "Avaa suora kanava tällä solmulla:", "please_pay_between_and": "Maksa vähintään {min} ja enintään {max}", "please_pay": "Ole hyvä ja maksa", "preimage": "Alkukuva", "sats": "sattia", + "date_time": "Päivämäärä ja aika", "wasnt_paid_and_expired": "Tätä laskua ei maksettu, ja se on vanhentunut." }, "plausibledeniability": { - "create_fake_storage": "Luo Salattu tallennustila", - "create_password": "Luo salasana", + "create_fake_storage": "Luo salattu tallennustila", "create_password_explanation": "Väärennetyn tallennustilan salasana ei tule täsmätä oikean tallennustilan salasanan kanssa", "help": "Joissain tilanteissa, saatat olla pakotettu kertomaan salasanasi. Pitääksesi kolikkosi turvassa, BlueWallet voi luoda toisen salatun tallennustilan, toisella salasanalla. Paineen alla, voit kertoa tämän salasanan kolmannelle osapuolelle. Jos se tulee sisään BlueWallet:iin, se avaa uuden \"väärennetyn\" tallennustilan. Se näyttää oikealta kolmannelle osapuolelle, mutta pitää oikean tallennustilasi kolikkoineen turvassa.", "help2": "Uusi tallennustila näyttää täysin toimivalta, ja voit säilyttää pieniä summia siellä, jotta se näyttää uskottavalta.", "password_should_not_match": "Salasana on käytössä. Ole hyvä, ja kokeile toista salasanaa.", - "passwords_do_not_match": "Salasana ei täsmää, yritä uudelleen.", - "retype_password": "Salasana uudelleen", - "success": "Onnistui", "title": "Uskottava Kiistettävyys" }, "pleasebackup": { "ask": "Oletko tallentanut lompakon varmuuskopion? Tämä varmuuskopio vaaditaan varojen käyttämiseen, jos kadotat tämän laitteen. Ilman varmuuskopiota varat menetetään lopullisesti.", - "ask_no": "Ei, en ole", - "ask_yes": "Kyllä, olen", - "ok": "OK, kirjoitin sen ylös", - "ok_lnd": "OK, Olen tallettanut sen", + "ask_no": "Ei, en ole.", + "ask_yes": "Kyllä, olen.", + "ok": "Ok, kirjoitin sen ylös", + "ok_lnd": "OK, olen tallentanut sen.", "text": "Varaa hetki aikaa ja kirjoita palautuslause (mnemonic) talteen paperille.\nSe on varmuuskopiosi ja voit käyttää sitä lompakon palauttamiseen.", "text_lnd": "Tallenna tämä lompakon varmuuskopio. Sen avulla voit palauttaa lompakon, jos lompakko katoaa.", "title": "Lompakkosi on luotu" @@ -110,18 +100,22 @@ "details_create": "Luo", "details_label": "Selite", "details_setAmount": "Vastaanotettava summa", - "details_share": "jaa", + "details_share": "Jaa...", + "address_not_found": "Vastaanottavan osoitteen luominen epäonnistui.", "header": "Vastaanota", + "reset": "Nollaa", "maxSats": "Enimmäismäärä on {max} satsia", - "maxSatsFull": "Enimmäismäärä on {max} satsia tai {valuutta}", + "maxSatsFull": "Enimmäismäärä on {max} satsia tai {currency}", "minSats": "Vähimmäismäärä on {min} satsia", - "minSatsFull": "Vähimmäismäärä on {min} satsia tai {valuutta}" + "minSatsFull": "Vähimmäismäärä on {min} satsia tai {currency}", + "qrcode_for_the_address": "QR-koodi osoitteelle", + "bip47_explanation": "Maksukoodit ovat yleisosoitteita, jotka eivät paljasta lompakkosi osoitteita. Kaikki palvelut eivät tue niitä." }, "send": { "provided_address_is_invoice": "Tämä osoite vaikuttaa olevan Salamalasku. Maksun suorittamiseksi, siirry Salamalompakkoosi.", "broadcastButton": "LÄHETÄ", "broadcastError": "virhe", - "broadcastNone": "Syötä siirtotapahtuman tiiviste", + "broadcastNone": "Syötä siirtotapahtuman hex", "broadcastPending": "odottaa", "broadcastSuccess": "onnistui", "confirm_header": "Vahvista", @@ -133,12 +127,19 @@ "create_fee": "Siirtomaksu", "create_memo": "Muistio", "create_satoshi_per_vbyte": "Satoshi per vByte", - "create_this_is_hex": "Tämä on siirtotapahtuman hex-numero, allekirjoitettu ja valmis lähetettävksi verkkoon.", + "create_this_is_hex": "Tämä on siirtotapahtuman hex, allekirjoitettu ja valmis lähetettäväksi verkkoon.", "create_to": "Vastaanottaja", "create_tx_size": "Siirtotapahtuman koko", "create_verify": "Varmenna coinb.in :ssä", + "details_insert_contact": "Lisää kontakti", "details_add_rec_add": "Lisää Vastaanottaja", "details_add_rec_rem": "Poista Vastaanottaja", + "details_add_recc_rem_all_alert_description": "Oletko varma, että haluat poistaa kaikki vastaanottajat?", + "details_add_rec_rem_all": "Poista kaikki vastaanottajat", + "details_recipients_title": "Vastaanottajat", + "details_recipient_title": "Vastaanottaja {number} / {total}", + "please_complete_recipient_title": "Epätäydellinen vastaanottaja", + "please_complete_recipient_details": "Täytä vastaanottajan {number} tiedot ennen kuin lisäät uuden vastaanottajan.", "details_address": "osoite", "details_address_field_is_not_valid": "Osoite ei kelpaa", "details_adv_fee_bump": "Salli siirtomaksun nosto", @@ -148,19 +149,20 @@ "details_adv_import": "Tuo Siirtotapahtuma", "details_adv_import_qr": "Tuo siirtotapahtuma (QR)", "details_amount_field_is_not_valid": "Määrä ei kelpaa", - "details_amount_field_is_less_than_minimum_amount_sat": "Määritetty määrä on liian pieni. Anna summa, joka on yli 500 sattia. ", + "details_amount_field_is_less_than_minimum_amount_sat": "Määritetty määrä on liian pieni. Anna summa, joka on yli 500 sattia.", "details_create": "Luo Lasku", - "details_error_decode": "Bitcoin-osoitetta ei voida dekoodata ", + "details_error_decode": "Bitcoin-osoitetta ei voida dekoodata", "details_fee_field_is_not_valid": "Siirtomaksu ei ole pätevä", - "details_frozen": "{amount} BTC on jäädytetty", + "details_frozen": "{amount} BTC on jäädytetty.", "details_next": "Seuraava", "details_no_signed_tx": "Valittu tiedosto ei sisällä tuotavaa siirtotapahtumaa.", "details_note_placeholder": "muistiinpano itselle", "details_scan": "Skannaa", - "details_scan_hint": "Scannaa tai tuo tupla-napauttamalla", + "details_scan_hint": "Skannaa tai tuo tuplanapauttamalla", + "details_scan_error": "Skannausvirhe", "details_total_exceeds_balance": "Lähetettävä summa ylittää katteen", "details_total_exceeds_balance_frozen": "Lähetysmäärä ylittää käytettävissä olevan saldon. Huomaa, että jäädytettyjä kolikoita ei oteta huomioon.", - "details_unrecognized_file_format": "Tunnistamaton tiedostomuoto ", + "details_unrecognized_file_format": "Tunnistamaton tiedostomuoto", "details_wallet_before_tx": "Ennen siirtotapahtuman luomista, sinun on ensin lisättävä Bitcoin-lompakko.", "dynamic_init": "Alustaa", "dynamic_next": "Seuraava", @@ -171,22 +173,22 @@ "fee_1d": "1 p", "fee_3h": "3 t", "fee_custom": "Mukautettu", + "insert_custom_fee": "Lisää palkkio", "fee_fast": "Nopea", "fee_medium": "Keskitaso", - "fee_replace_minvb": "Kokonais siirtomaksun (satoshi per vByte), jonka haluat maksaa, tulisi olla suurempi kuin {min} sat/vByte.", + "fee_replace_minvb": "Kokonaissiirtomaksun (satoshi per vByte), jonka haluat maksaa, tulisi olla suurempi kuin {min} sat/vByte.", "fee_satvbyte": "sat/vByte", "fee_slow": "Hidas", "header": "Lähetä", - "input_clear": "Tyhjää", + "input_clear": "Tyhjennä", "input_done": "Valmis", "input_paste": "Liitä", "input_total": "Yhteensä:", "permission_camera_message": "Tarvitsemme lupasi kameran käyttöön", "psbt_sign": "Allekirjoita siirtotapahtuma", + "invalid_psbt": "Annettu PSBT on virheellinen.", "open_settings": "Avaa Asetukset", - "permission_storage_later": "Kysy Minulta Myöhemmin", - "permission_storage_message": "BlueWallet tarvitsee lupasi käyttääkseen tallennustilaasi tämän tiedoston tallentamiseksi.", - "permission_storage_denied_message": "BlueWallet ei voinut tallettaa tätä tiedostoa. Aseta laitteesi sallimaan tallentaminen kyttkemällä Storage Permission päälle.", + "permission_storage_denied_message": "BlueWallet ei voinut tallettaa tätä tiedostoa. Aseta laitteesi sallimaan tallentaminen kytkemällä Storage Permission päälle.", "permission_storage_title": "Tallennustilan käyttöoikeus", "psbt_clipboard": "Kopioi Leikepöydälle", "psbt_this_is_psbt": "Tämä on osittain allekirjoitettu bitcoin-siirtotapahtuma (PSBT). Ole hyvä ja allekirjoita se hardware-lompakolla.", @@ -195,12 +197,16 @@ "outdated_rate": "Vaihtokurssi päivitettiin viimeksi: {date}", "psbt_tx_open": "Avaa allekirjoitettu siirtotapahtuma", "psbt_tx_scan": "Skannaa allekirjoitettu siirtotapahtuma", - "qr_error_no_qrcode": "Kuvasta ei löytynyt QR-koodia. Varmista että kuva sisältää ainoastaan QR-koodin eikä muita tietoja kuten tekstia tai nappeja.", "reset_amount": "Nollaa määrä", "reset_amount_confirm": "Haluaisitko nollata määrän?", "success_done": "Valmis", - "txSaved": "Siirtotapahtumatiedosto ({filePath}) on tallennettu Lataukset-kansioon.", - "problem_with_psbt": "Ongelma PSBT:n kanssa" + "txSaved": "Siirtotapahtumatiedosto ({filePath}) on tallennettu.", + "file_saved_at_path": "Tiedosto ({filePath}) on tallennettu.", + "cant_send_to_silentpayment_adress": "Tämä lompakko ei voi lähettää SilentPayment-osoitteisiin", + "cant_find_bip47_notification": "Lisää tämä maksukoodi yhteystietoihisi ensin", + "problem_with_psbt": "Ongelma PSBT:n kanssa", + "qr_error_no_qrcode": "Valitusta kuvasta ei löytynyt kelvollista QR-koodia. Varmista, että kuva sisältää vain QR-koodin eikä muuta sisältöä, kuten tekstiä tai painikkeita.", + "cant_send_to_bip47": "Tämä lompakko ei voi lähettää BIP47-maksukoodeihin" }, "settings": { "about": "Tietoa", @@ -211,30 +217,31 @@ "about_release_notes": "Julkaisutiedot", "about_review": "Jätä meille arvostelu", "performance_score": "Suorituskykypisteet: {num}", - "run_performance_test": "Testin suorituskyky", + "run_performance_test": "Testaa suorituskyky", "about_selftest": "Suorita itsetestaus", + "block_explorer_invalid_custom_url": "Annettu URL-osoite on virheellinen. Anna kelvollinen URL-osoite, joka alkaa http:// tai https://.", "about_selftest_electrum_disabled": "Electrum Self-Test toimintoa ei ole mahdollista käyttää offline-tilassa. Siirry pois offline-tilasta ja yritä uudelleen", - "about_selftest_ok": "Kaikki sisäiset testit on läpäisty onnistuneesti. Lompakko toimii hyvin. ", + "about_selftest_ok": "Kaikki sisäiset testit on läpäisty onnistuneesti. Lompakko toimii hyvin.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord-serveri", "about_sm_telegram": "Telegram-kanava", - "about_sm_twitter": "Seuraa meitä Twitterissä", - "advanced_options": "Lisäasetukset", + "privacy_temporary_screenshots": "Salli näytönkaappaus", + "privacy_temporary_screenshots_instructions": "Näytönkaappausesto poistetaan tilapäisesti käytöstä, jolloin näytönkaappaukset ja näytön tallenteet ovat käytössä. Esto aktivoituu automaattisesti uudelleen, kun suljet BlueWalletin ja avaat sen uudelleen.", "biometrics": "Biometriset tiedot", + "biometrics_no_longer_available": "Laitteesi asetukset ovat muuttuneet, eivätkä ne enää vastaa sovelluksessa valittuja suojausasetuksia. Ota biometria tai salasana uudelleen käyttöön ja käynnistä sovellus uudelleen, jotta muutokset tulevat voimaan.", "biom_10times": "Olet yrittänyt antaa salasanasi 10 kertaa. Haluatko nollata tallennustilan? Tämä poistaa kaikki lompakot ja purkaa tallennustilan salauksen.", "biom_conf_identity": "Vahvista identiteettisi.", - "biom_no_passcode": "Laitteellasi ei ole salasanaa. Jatkaaksesi määritä salasana Asetukset-sovelluksessa.", + "biom_no_passcode": "Laitteessasi ei ole salasanaa tai biometriaa käytössä. Jatkaaksesi määritä salasana tai biometria Asetukset-sovelluksessa.", "biom_remove_decrypt": "Kaikki lompakot poistetaan ja tallennustilasi puretaan. Haluatko varmasti jatkaa?", "currency": "Valuutta", - "currency_source": "Hinta saadaan seuraavista lähteistä", - "currency_fetch_error": "Valitu valuutan vaihtokurssin hakemisessa tapahtui virhe.", - "default_desc": "Kun on pois käytöstä, BlueWallet avaa valitun lompakon heti käynnistettäessä.", - "default_info": "Oletustiedot", + "currency_source": "Hinta haetaan palvelusta", + "currency_fetch_error": "Valitun valuutan vaihtokurssin hakemisessa tapahtui virhe.", "default_title": "Käynnistettäessä", - "default_wallets": "Näytä Kaikki Lompakot", + "donate": "Lahjoita", + "donate_description": "Auta meitä pitämään Blue ilmaisena!", "electrum_connected": "Yhdistetty", "electrum_connected_not": "Ei yhteyttä", "electrum_error_connect": "Ei voida yhdistää tarjottuun Electrum-palvelimeen", + "electrum_error_connect_tor": "Ei voi muodostaa yhteyttä annettuun Electrum-palvelimeen. Varmista, että Orbot-sovellus on yhteydessä, ja yritä uudelleen.", "lndhub_uri": "esim, {example}", "electrum_host": "esim, {example}", "electrum_offline_mode": "Offline-tila", @@ -245,30 +252,38 @@ "set_electrum_server_as_default": "Asetetaanko {server} oletus Electrum-palvelimeksi?", "set_lndhub_as_default": "Asetetaanko {url} oletus LNDHub-palvelimeksi?", "electrum_settings_server": "Electrum-palvelin", - "electrum_settings_explain": "Jos haluat käyttää oletusta, jätä tämä tyhjäksi.", "electrum_status": "Tila", - "electrum_clear_alert_title": "Tyhjennä historia?", - "electrum_clear_alert_message": "Haluatko tyhjentää Electrum-palvelinten historian?", - "electrum_clear_alert_cancel": "Peruuta", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Valitse", + "electrum_preferred_server": "Ensisijainen palvelin", + "electrum_preferred_server_description": "Anna palvelin, jota haluat lompakkosi käyttävän kaikissa Bitcoin-toiminnoissa. Kun tämä on määritetty, lompakkosi käyttää tätä palvelinta yksinomaan saldojen tarkistamiseen, tapahtumien lähettämiseen ja verkkodatan hakemiseen. Varmista, että luotat tähän palvelimeen ennen sen määrittämistä.", + "electrum_unable_to_connect": "Ei saada yhteyttä {server}.", + "electrum_history": "Historia", + "electrum_reset_to_default": "Tämä antaa BlueWalletin valita palvelimen satunnaisesti palvelinluettelosta.", "electrum_reset": "Palauta oletusasetuksiin", - "electrum_unable_to_connect": " Ei saada yhteyttä {server}. ", - "electrum_history": "Palvelimen historia", - "electrum_reset_to_default": "Haluatko varmasti palauttaa Electrumin asetukset oletusarvoihin? ", - "electrum_clear": "Tyhjennä", - "tor_supported": "Tor on tuettu", - "tor_unsupported": "Tor yhteyksiä ei tueta", + "electrum_reset_to_default_and_clear_history": "Palauta oletusasetukset ja tyhjennä historia", "encrypt_decrypt": "Pura tallennustilan salaus", "encrypt_decrypt_q": "Haluatko varmasti purkaa tallennustilan salauksen? Tämä mahdollistaa lompakkoihisi pääsyn ilman salasanaa.", - "encrypt_enc_and_pass": "Salattu ja Salasanalla suojattu", + "encrypt_enc_and_pass": "Salasanalla suojattu", + "encrypt_storage_explanation_headline": "Ota käyttöön tallennustilan salaus", + "encrypt_storage_explanation_description_line1": "Tallennustilan salauksen ottaminen käyttöön lisää sovellukseesi ylimääräisen suojauskerroksen suojaamalla tietojesi tallennustapaa laitteellasi. Tämä vaikeuttaa kenenkään pääsyä tietoihisi ilman lupaa.", + "encrypt_storage_explanation_description_line2": "On kuitenkin tärkeää tietää, että tämä salaus suojaa vain pääsyä lompakoihisi, jotka on tallennettu laitteen avainnippuun. Se ei aseta salasanaa tai mitään lisäsuojaa itse lompakoille.", + "set_as_preferred_electrum": "Kun {host}:{port} asetetaan ensisijaiseksi palvelimeksi, satunnaiseen ehdotettuun palvelimeen yhdistäminen poistetaan käytöstä.", + "encrypt_use_expl": "{type} käytetään henkilöllisyytesi vahvistamiseen ennen siirtotapahtuman tekemistä, lompakon avaamista, vientiä tai poistamista.", + "biometrics_fail": "Jos {type} ei ole käytössä tai sen avaaminen epäonnistuu, voit käyttää vaihtoehtona laitteesi pääsykoodia.", + "lightning_error_lndhub_uri": "Virheellinen LNDhub URI", + "lightning_error_lndhub_uri_tor": "Virheellinen LNDhub URI. Varmista, että Orbot-sovellus on yhteydessä, ja yritä uudelleen.", + "lightning_settings_explain": "Jos haluat yhdistää omaan LND-solmuusi, asenna LNDhub ja syötä sen URL-osoite tähän asetuksiin. Huomaa, että vain muutosten tallentamisen jälkeen luodut lompakot yhdistyvät määritettyyn LNDhubiin.", + "lndhub_github": "GitHub-arkisto", + "electrum_suggested_description": "Kun ensisijaista palvelinta ei ole asetettu, käyttöön valitaan satunnaisesti ehdotettu palvelin.", + "i_understand": "Ymmärrän", + "block_explorer": "Lohkoselain", + "block_explorer_preferred": "Käytä ensisijaista lohkoselainta", + "block_explorer_error_saving_custom": "Virhe tallennettaessa lohkoselainta", "encrypt_title": "Tietoturva", "encrypt_tstorage": "tallennustila", "encrypt_use": "Käytä {type}", - "encrypt_use_expl": "Ennen tapahtumia, avaamista vientiä tai lompakon poistoa identiteettis varmistetaan {type}:lla. {type} ei kuitenkaan voi käyttää salatun tallennustilan avaamiseen.", + "set_as_preferred": "Aseta ensisijaiseksi", + "encrypted_feature_disabled": "Tätä ominaisuutta ei voi käyttää salatun tallennustilan kanssa", "general": "Yleinen", - "general_adv_mode": "Lisäasetukset", - "general_adv_mode_e": "Kun tämä asetus on käytössä, näet lisäasetukset, kuten erilaiset lompakkotyypit, kyvyn määrittää LNDHub-instanssi, johon haluat muodostaa yhteyden ja mukautetun entropian lompakon luomisen aikana.", "general_continuity": "Jatkuvuus", "general_continuity_e": "Kun tämä asetus on käytössä, voit tarkastella valittuja lompakoita ja siirtotapahtumia muilla Apple iCloud -laitteilla.", "groundcontrol_explanation": "GroundControl on ilmainen avoimen lähdekoodin push-ilmoituspalvelin bitcoin-lompakoille. Voit asentaa oman GroundControl-palvelimen ja laittaa sen URL-osoitteen tähän, jotta et luota BlueWallet-infrastruktuuriin. Jätä tyhjäksi käyttääksesi oletusasetusta", @@ -276,11 +291,9 @@ "language": "Kieli", "last_updated": "Päivitetty viimeksi", "language_isRTL": "BlueWallet on käynnistettävä uudelleen, jotta kielisuuntaus tulee voimaan.", - "lightning_error_lndhub_uri": "Virheellinen LNDHub URI", + "license": "Lisenssi", "lightning_saved": "Muutoksesi on tallennettu onnistuneesti", "lightning_settings": "Salama-asetukset", - "tor_settings": "Tor-asetukset", - "lightning_settings_explain": "Jos haluat muodostaa yhteyden omaan LND-solmuun, asenna LNDHub ja laita sen URL-osoite tähän asetuksiin. Huomaa, että vain muutosten tallentamisen jälkeen luodut lompakot muodostavat yhteyden määritettyyn LNDHubiin.", "network": "Verkko", "network_broadcast": "Lähetä siirtotapahtuma", "network_electrum": "Electrum-palvelin", @@ -288,8 +301,7 @@ "notifications": "Ilmoitukset", "open_link_in_explorer": "Avaa linkki selaimessa", "password": "Salasana", - "password_explain": "Luo salasana, jota käytät tallennustilan salauksen purkamiseen", - "passwords_do_not_match": "Salasanat eivät täsmää", + "password_explain": "Anna salasana jolla avaat tallennustilan", "plausible_deniability": "Uskottava kiistettävyys", "privacy": "Yksityisyys", "privacy_read_clipboard": "Lue Leikepöytä", @@ -298,14 +310,13 @@ "privacy_quickactions_explanation": "Kosketa ja pidä Aloitusnäytön BlueWallet-sovelluskuvaketta nähdäksesi nopeasti lompakon saldon.", "privacy_clipboard_explanation": "Toimita pikakuvakkeet, jos leikepöydältä löytyy osoite tai lasku.", "privacy_do_not_track": "Poista analytiikka käytöstä", - "privacy_do_not_track_explanation": "Suorituskyky- ja luotettavuustietoja ei lähtetä analysoitavaksi.", - "push_notifications": "Push-ilmoitukset", + "privacy_do_not_track_explanation": "Suorituskyky- ja luotettavuustietoja ei lähetetä analysoitavaksi.", "rate": "Vaihtokurssi", - "retype_password": "Salasana uudelleen", - "selfTest": "Itsetestaus ", + "push_notifications_explanation": "Kun otat ilmoitukset käyttöön, laitetunnuksesi lähetetään palvelimelle yhdessä lompakkojen osoitteiden ja tapahtumatunnusten kanssa kaikille ilmoitusten käyttöönoton jälkeen tehdyille lompakoille ja tapahtumille. Laitetunnusta käytetään ilmoitusten lähettämiseen, ja lompakkotietojen avulla voimme ilmoittaa sinulle saapuvista Bitcoineista tai tapahtumavahvistuksista.\n\nVain ilmoitusten käyttöönoton jälkeiset tiedot lähetetään – mitään sitä edeltävältä ajalta ei kerätä.\n\nIlmoitusten poistaminen käytöstä poistaa kaikki nämä tiedot palvelimelta. Lisäksi lompakon poistaminen sovelluksesta poistaa myös siihen liittyvät tiedot palvelimelta.", + "selfTest": "Itsetestaus", "save": "Tallenna", "saved": "Tallennettu", - "success_transaction_broadcasted": "Siirtotapahtumasi on lähetetty onnistuneesti.", + "success_transaction_broadcasted": "Siirtotapahtumasi on lähetetty onnistuneesti!", "total_balance": "Kokonaissaldo", "total_balance_explanation": "Näytä kaikkien lompakoiden kokonaissaldo aloitusnäytön widgeteissä.", "widgets": "Widgetit", @@ -313,15 +324,17 @@ }, "notifications": { "would_you_like_to_receive_notifications": "Haluatko saada ilmoituksia, kun saat saapuvia maksuja?", - "no_and_dont_ask": "En, ja Älä Kysy Minulta Uudelleen", - "ask_me_later": "Kysy Minulta Myöhemmin" + "notifications_subtitle": "Saapuvat maksut ja tapahtumavahvistukset", + "no_and_dont_ask": "En, ja Älä Kysy Minulta Uudelleen.", + "permission_denied_message": "Olet kieltänyt luvan lähettää sinulle ilmoituksia. Jos haluat vastaanottaa ilmoituksia, ota ne käyttöön laitteesi asetuksista." }, "transactions": { "cancel_explain": "Korvaamme tämän siirtotapahtuman sellaisella, joka maksaa sinulle ja jossa on korkeammat siirtomaksut. Tämä käytännössä peruuttaa nykyisen siirtotapahtuman. Tätä kutsutaan nimellä RBF-Replace by Fee.", "cancel_no": "Tämä siirtotapahtuma ei ole vaihdettavissa", "cancel_title": "Peruuta tämä siirtotapahtuma (RBF)", - "confirmations_lowercase": "{confirmations} vahvistukset", - "copy_link": "Kopioi linkki", + "transaction_loading_error": "Maksutapahtuman lataamisessa tapahtui ongelma. Yritä myöhemmin uudelleen.", + "transaction_not_available": "Siirtotapahtuma ei saatavilla", + "confirmations_lowercase": "{confirmations} vahvistusta", "expand_note": "Laajenna huomautus", "cpfp_create": "Luo", "cpfp_exp": "Luomme toisen siirtotapahtuman, joka kuluttaa vahvistamattoman siirtotapahtuman. Kokonaiskulu on suurempi kuin alkuperäinen siirtokulu, joten sen pitäisi olla louhittu nopeammin. Tätä kutsutaan CPFP - Child Pays For Parent - Lapsi Maksaa Vanhemmalle.", @@ -329,31 +342,32 @@ "cpfp_title": "Nosta siirtomaksua (CPFP)", "details_balance_hide": "Piilota Saldo", "details_balance_show": "Näytä Saldo", - "details_block": "Lohkon järjestysnumero", "details_copy": "Kopioi", - "details_copy_amount": "Kopioi määrä", "details_copy_block_explorer_link": "Kopioi linkki lohkoketjuselaimeen", "details_copy_note": "Kopioi muistiinpanot", "details_copy_txid": "Kopioi tapahtumatunnus", - "details_from": "Syöte", "details_inputs": "Syötteet", "details_outputs": "Ulostulot", "date": "Päivämäärä", "details_received": "Vastaanotettu", - "transaction_note_saved": "Siirtotapahtumailmoitus on tallennettu.", - "details_show_in_block_explorer": "Näytä lohkoketjuselaimessa", + "details_view_in_browser": "Näytä selaimessa", "details_title": "Siirtotapahtuma", + "incoming_transaction": "Tulossa oleva siirtotapahtuma", + "outgoing_transaction": "Lähtevä siirtotapahtuma", + "expired_transaction": "Vanhentunut siirtotapahtuma", "details_to": "Ulostulo", - "enable_offline_signing": "Tätä lompakkoa ei käytetä offline-allekirjoituksen yhteydessä. Haluatko ottaa sen käyttöön nyt? ", + "enable_offline_signing": "Tätä lompakkoa ei käytetä offline-allekirjoituksen yhteydessä. Haluatko ottaa sen käyttöön nyt?", "list_conf": "conf: {number}", "pending": "Odottaa", - "pending_with_amount": "Odottaa {amt1} ({amd2})", - "received_with_amount": "+{amt1} ({amd2})", + "pending_with_amount": "Odottaa {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", "eta_10m": "Saapuu n.10 minuutissa", - "eta_3h": "Saapuu n.3 tunnissa", + "eta_3h": "Saapuu n.3 tunnissa", "eta_1d": "Saapuu noin vuorokaudessa", - "view_wallet": "Näytä {walletLabel}", "list_title": "Siirtotapahtumat", + "list_title_sent": "Lähetetty", + "list_title_received": "Vastaanotettu", + "transaction": "Siirtotapahtuma", "open_url_error": "Linkkiä ei voi avata oletusselaimella. Vaihda oletusselainta ja yritä uudelleen.", "rbf_explain": "Korvaamme tämän siirtotapahtuman korkeamman siirtomaksun sisältävällä siirtotapahtumalla, jotta se louhitaan nopeammin. Tätä kutsutaan nimellä RBF-Replace by Fee.", "rbf_title": "Nosta siirtomaksua (RBF)", @@ -361,50 +375,77 @@ "status_cancel": "Peruuta Siirtotapahtuma", "transactions_count": "Siirtotapahtumien määrä", "txid": "Siirtotapahtuman tunnus", - "updating": "Päivitetään..." + "updating": "Päivitetään...", + "watchOnlyWarningTitle": "Turvallisuusvaroitus", + "custom_fee_warning_title": "Varoitus", + "pending_transaction": "Vahvistusta odottava siirtotapahtuma", + "offchain": "Salamaverkossa", + "onchain": "Ketjussa", + "watchOnlyWarningDescription": "Varo huijareita, jotka käyttävät usein pelkästään katseltavia lompakoita käyttäjien huijaamiseen. Näiden lompakoiden avulla et voi hallita tai lähettää varoja; voit ainoastaan tarkastella saldoa.", + "custom_fee_warning_description": "Alle 1 sat/vB siirtomaksut ovat kelvollisia, mutta solmujen käytännöt saattavat estää niiden välittämisen.", + "details_eta_analyzing": "Analysoidaan...", + "details_sent": "Lähetetty", + "details_section": "Tarkemmat tiedot", + "details_explorer": "selain", + "details_network_fee": "Verkkomaksu", + "details_to_address": "Vastaanottaja", + "details_id": "Tunnus", + "details_note": "Muistiinpano", + "details_add_note": "lisää", + "details_advanced": "Edistynyt", + "details_fee_rate": "Siirtomaksun taso", + "details_size": "Koko", + "details_virtual_size": "Virtuaalinen koko", + "details_tx_hex": "Tx hex", + "details_inputs_count": "Syötteet ({count})", + "details_outputs_count": "Ulostulot ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Yksinkertainen ja tehokas Bitcoin-lompakko", "add_create": "Luo", + "total_balance": "Kokonaissaldo", + "add_entropy_reset_title": "Nollaa Entropia", + "add_entropy_reset_message": "Lompakon tyypin muuttaminen nollaa nykyisen entropian. Haluatko jatkaa?", + "add_entropy": "Entropia", + "add_entropy_bytes": "{bytes} tavua entropiaa", "add_entropy_generated": "{gen} tavua luotua entropiaa", "add_entropy_provide": "Hanki entropia nopanheiton kautta", "add_entropy_remain": "{gen} tavua luotua entropiaa. Jäljellä olevat {rem} tavut saadaan Järjestelmän satunnaislukugeneraattorilta.", "add_import_wallet": "Tuo lompakko", "add_lightning": "Salama", "add_lightning_explain": "Käytetään välittömiin siirtotapahtumiin", - "add_lndhub": "Yhdistä LNDHub:iisi", - "add_lndhub_error": "Annettu solmun osoite ei ole kelvollinen LNDHub solmu", + "add_lndhub": "Yhdistä omaan LNDhubiin", "add_lndhub_placeholder": "solmusi osoite", "add_placeholder": "minun lompakkoni", "add_title": "lisää lompakko", "add_wallet_name": "nimi", "add_wallet_type": "tyyppi", - "balance": "Saldo", + "add_wallet_seed_length_12": "12 sanaa", + "add_wallet_seed_length_24": "24 sanaa", "clipboard_bitcoin": "Sinulla on leikepöydällä Bitcoin-osoite. Haluatko käyttää sitä siirtotapahtumaan?", "clipboard_lightning": "Leikepöydälläsi on Salamalasku. Haluatko käyttää sitä siirtoon?", + "clear_clipboard_on_import": "Tyhjennä leikepöytä tuonnin yhteydessä", "details_address": "Osoite", "details_advanced": "Edistynyt", "details_are_you_sure": "Oletko varma?", "details_connected_to": "Yhdistetty", - "details_del_wb_err": "Annettu saldo ei vastaa tämän lompakon saldoa. Yritä uudelleen", - "details_del_wb_q": "Lompakossa on varoja. Ennenkuin jatkat, ymmärrä että tarvitset lompakon palautukseen tulevaisuudeessa palautuslauseen. Varmistaaksemme ettet tuhoa lompakkoa vahingossa, tulee sinun syöttää saldosi {balance} satosheina.", + "details_del_wb_err": "Annettu saldo ei vastaa tämän lompakon saldoa. Yritä uudelleen.", + "details_del_wb_q": "Lompakossa on varoja. Ennen kuin jatkat, ymmärrä että tarvitset lompakon palautukseen tulevaisuudessa palautuslauseen. Varmistaaksemme ettet tuhoa lompakkoa vahingossa, tulee sinun syöttää saldosi {balance} satosheina.", "details_delete": "Poista", "details_delete_wallet": "Poista lompakko", - "details_derivation_path": "johdantopolku", - "details_display": "näkyy lompakkojen listassa", + "details_derivation_path": "johtamispolku", + "details_display": "Näytä kotinäkymässä", "details_export_backup": "Vie / varmuuskopioi", "details_export_history": "Vie historia CSV:ksi", - "details_master_fingerprint": "Pää sormenjälki", + "details_master_fingerprint": "Pääsormenjälki", "details_multisig_type": "multisig", - "details_no_cancel": "Ei, peruuta", - "details_save": "Tallenna", "details_show_xpub": "Näytä lompakon XPUB", "details_show_addresses": "Näytä osoitteet", "details_title": "Lompakko", + "wallets": "lompakot", "details_type": "Tyyppi", "details_use_with_hardware_wallet": "Käytä hardware-lompakon kanssa", - "details_wallet_updated": "Lompakko päivitetty", "details_yes_delete": "Kyllä, poista", "enter_bip38_password": "Syötä salasana salauksen purkamiseksi", "export_title": "lompakon vienti", @@ -413,65 +454,92 @@ "import_passphrase_title": "tunnuslause", "import_passphrase_message": "Anna tunnuslause, mikäli olet käyttänyt sellaista", "import_error": "Tuonti epäonnistui. Varmista, että annettu tieto on oikein", - "import_explanation": "Kirjoita siemensanasi, julkinen avain, WIF tai mikä tahansa sinulla on. BlueWallet tekee parhaansa arvatakseen oikean formaatin ja tuodakseen lompakkosi. ", + "import_explanation": "Kirjoita siemensanasi, julkinen avain, WIF tai mikä tahansa sinulla on. BlueWallet tekee parhaansa arvatakseen oikean formaatin ja tuodakseen lompakkosi.", "import_imported": "Tuotu", "import_scan_qr": "Skannaa tai tuo tiedosto", "import_success": "Lompakkosi tuonti onnistui.", "import_success_watchonly": "Lompakkosi on onnistuneesti tuotu. VAROITUS: Tämä lompakko on pelkkä tarkastelulompakko, et voi käyttää siitä rahaa.", "import_search_accounts": "Hae tilejä", "import_title": "tuo", + "learn_more": "Lue lisää", "import_discovery_title": "Etsiminen", "import_discovery_subtitle": "Valitse löydetty lompakko", "import_discovery_derivation": "Vaihtoehtoinen derivation path", "import_discovery_no_wallets": "Lompakkoja ei löytynyt", + "import_discovery_offline": "BlueWallet on tällä hetkellä offline-tilassa. Tässä tilassa se ei voi varmistaa lompakon olemassaoloa, joten sinun on valittava oikea lompakko manuaalisesti.", "import_derivation_found": "löytyi", - "import_derivation_found_not": "ei löytynyt", - "import_derivation_loading": "ladataan...", - "import_derivation_subtitle": "Syötä vaihtoehtoinen derivation path, niin yritämme etsiä lompakkosi", + "import_derivation_found_not": "Ei löytynyt", + "import_derivation_loading": "Ladataan...", + "import_derivation_subtitle": "Syötä vaihtoehtoinen derivation path, niin yritämme etsiä lompakkosi.", "import_derivation_title": "Derivation path", - "import_derivation_unknown": "tuntematon", + "import_derivation_unknown": "Tuntematon", "import_wrong_path": "Väärä derivation path", "list_create_a_button": "Lisää nyt", "list_create_a_wallet": "Lisää lompakko", - "list_create_a_wallet_text": "Se on ilmainen ja voit luoda\nniin monta kuin haluat", + "list_create_a_wallet_text": "Se on ilmainen ja voit luoda\nniin monta kuin haluat.", "list_empty_txs1": "Siirtotapahtumasi näkyvät tässä,", "list_empty_txs1_lightning": "Salamalompakkoa voit käyttää päivittäisiin siirtoihin. Siirtomaksut ovat kohtuuttoman halvat ja se toimii todella nopeasti.", - "list_empty_txs2": "Aloita lompakostasi. ", + "list_empty_txs2": "Aloita lompakostasi.", "list_empty_txs2_lightning": "Aloita lompakon käyttäminen napsauttamalla \"hallinnoi varoja\" ja lisää saldoasi.\n", "list_latest_transaction": "Viimeisin Siirtotapahtuma", - "list_ln_browser": "LApp-selain", "list_long_choose": "Valitse Kuva", - "list_long_clipboard": "Kopioi Leikepöydältä", + "paste_from_clipboard": "Liitä", + "import_file": "Tuo tiedosto", "list_long_scan": "Skannaa QR-koodi", "list_title": "lompakot", "list_tryagain": "Yritä uudelleen", "no_ln_wallet_error": "Ennen kuin maksat Salamalaskun, sinun on ensin lisättävä Salamalompakko.", "looks_like_bip38": "Tämä näyttää salasanalla suojatulta yksityiseltä avaimelta (BIP38)", - "reorder_title": "Järjestele Lompakot", - "reorder_instructions": "Siirrä listalla, ensin napauttamalla ja pitämällä, sitten vetäen.", + "manage_title": "Hallinnoi Lompakkoja", + "no_results_found": "Ei tuloksia", "please_continue_scanning": "Jatka skannausta", "select_no_bitcoin": "Bitcoin-lompakkoa ei tällä hetkellä ole saatavana.", "select_no_bitcoin_exp": "Bitcoin-lompakkoa vaaditaan Salamalompakkojen täyttämiseksi. Luo tai tuo yksi.", "select_wallet": "Valitse Lompakko", - "xpub_copiedToClipboard": "Kopioitu leikepöydälle.", "pull_to_refresh": "vedä päivittääksesi", - "warning_do_not_disclose": "Varoitus! Älä paljasta", + "warning_do_not_disclose": "Älä koskaan jaa tätä informaatiota alla", + "scan_import": "Skannaa tämä QR-koodi tuodaksesi lompakkosi toiseen sovellukseen.", + "write_down_header": "Luo manuaalinen varmuuskopio", + "write_down": "Kirjoita nämä sanat muistiin ja säilytä niitä turvallisesti. Käytä niitä lompakkosi palauttamiseen myöhemmin.", + "wallet_type_this": "Tämän lompakon tyyppi on {type}.", + "share_number": "Jaa {number}", + "copy_ln_url": "Kopioi ja tallenna tämä URL-osoite turvallisesti palauttaaksesi lompakkosi myöhemmin.", + "copy_ln_public": "Kopioi ja tallenna nämä tiedot turvallisesti palauttaaksesi lompakkosi tiedot myöhemmin.", "add_ln_wallet_first": "Sinun on ensin lisättävä Salamalompakko.", - "identity_pubkey": "Tunnus Pubkey", - "xpub_title": "lompakon XPUB" + "identity_pubkey": "Tunnuksen Pubkey", + "xpub_title": "lompakon XPUB", + "manage_wallets_search_placeholder": "Etsi lompakoista, osoitteista, siirtotapahtumista ja muistiinpanoista", + "more_info": "Lisää tietoa", + "details_delete_anyway": "Poista silti", + "add_lndhub_error": "Annettu solmun osoite on virheellinen LNDhub-solmu.", + "add_wallet_seed_length": "Palautuslauseen pituus", + "swipe_balance_hide": "Piilota", + "swipe_balance_show": "Näytä", + "drag_to_reorder": "Vedä järjestääksesi uudelleen", + "clear_search": "Tyhjennä haku", + "details_delete_wallet_error_message": "Tämän lompakon ilmoituksista poistamisen vahvistamisessa ilmeni ongelma — tämä voi johtua verkko-ongelmasta tai heikosta yhteydestä. Jos jatkat, saatat silti saada ilmoituksia tähän lompakkoon liittyvistä siirtotapahtumista myös sen poistamisen jälkeen." + }, + "total_balance_view": { + "display_in_bitcoin": "Näytä Bitcoineissa", + "hide": "Piilota", + "display_in_sats": "Näytä satosheissa", + "display_in_fiat": "Näytä valuutassa {currency}", + "title": "Kokonaissaldo", + "explanation": "Näe kaikkien lompakkojen yhteenlasketun saldon yleiskatsausnäytössä." }, "multisig": { - "multisig_vault": "Vault", - "default_label": "Multisig Vault", + "multisig_vault": "Multisig-holvi", + "default_label": "Multisig-holvi", "multisig_vault_explain": "Paras turvallisuus suurille summille", "provide_signature": "Toimita allekirjoitus", + "provide_signature_next_steps": "Skannaa tai tuo allekirjoitettu siirtotapahtuma", "vault_key": "Vault-avain {number}", "required_keys_out_of_total": "Vaaditut avaimet kokonaismäärästä", "fee": "Siirtomaksu: {number}", "fee_btc": "{number} BTC", "confirm": "Vahvista", "header": "Lähetä", - "share": "Jaa", + "share": "Jaa...", "view": "Näytä", "manage_keys": "Hallitse Avaimia", "how_many_signatures_can_bluewallet_make": "kuinka monta allekirjoitusta BlueWallet voi tehdä", @@ -482,27 +550,27 @@ "cosign_this_transaction": "Allekirjoitetaanko tämä siirtotapahtuma?", "lets_start": "Aloitetaan", "create": "Luo", - "native_segwit_title": "Paras harjoitus", + "native_segwit_title": "Paras käytäntö", "wrapped_segwit_title": "Paras yhteensopivuus", - "legacy_title": "Perintö", + "legacy_title": "Vanha", "co_sign_transaction": "Allekirjoita siirtotapahtuma", "what_is_vault": "Vault on", "what_is_vault_numberOfWallets": "{m} {n} multisig", "what_is_vault_wallet": "lompakko", "vault_advanced_customize": "Vault-asetukset...", - "needs": "Tarpeet", + "needs": "Se tarvitsee", "what_is_vault_description_number_of_vault_keys": "{m} vault-avaimet", "what_is_vault_description_to_spend": "kuluttaa ja kolmas sinulle\nvoidaan käyttää varmuuskopiona.", "what_is_vault_description_to_spend_other": "kuluttaa.", "quorum": "{m} {n} quorum", - "quorum_header": "Quorum", + "quorum_header": "Kynnys", "of": "n", "wallet_type": "Lompakon tyyppi", - "invalid_mnemonics": "Tämä muistilauseke ei näytä olevan pätevä", + "invalid_mnemonics": "Tämä muistilauseke ei näytä olevan pätevä.", "invalid_cosigner": "Virheellinen kanssa-allekirjoittajan tieto", "not_a_multisignature_xpub": "Tämä ei ole xpub multisignature-lompakosta!", - "invalid_cosigner_format": "Virheellinen allekirjoittaja: tämä ei ole muodon {format} allekirjoittaja", - "create_new_key": "Luo Uusi", + "invalid_cosigner_format": "Virheellinen allekirjoittaja: tämä ei ole muodon {format} allekirjoittaja.", + "create_new_key": "Luo uusi", "scan_or_open_file": "Skannaa tai avaa tiedosto", "i_have_mnemonics": "Minulla on siemen tälle avaimelle...", "type_your_mnemonics": "Lisää siemen tuodaksesi Vault-avaimesi", @@ -510,12 +578,12 @@ "wallet_key_created": "Vault-avaimesi luotiin. Käytä hetki muistisanojen turvalliseen varmuuskopioimiseen", "are_you_sure_seed_will_be_lost": "Oletko varma? Muistisiemenesi menetetään, jos sinulla ei ole varmuuskopiota", "forget_this_seed": "Unohda tämä siemen ja käytä XPUB:ia", - "view_edit_cosigners": "Tarkastele/muokkaa allekirjoittajia", + "view_edit_cosigners": "Tarkastele/Muokkaa allekirjoittajia", "this_cosigner_is_already_imported": "Tämä allekirjoittaja on jo tuotu.", "export_signed_psbt": "Vie Allekirjoitettu PSBT", "input_fp": "Syötä sormenjälki", "input_fp_explain": "ohita käyttääksesi oletusarvoa (00000000)", - "input_path": "Syöte johtamisen polku", + "input_path": "Syötä johtamispolku", "input_path_explain": "ohita käyttääksesi oletusarvoa ({default})", "ms_help": "Apu", "ms_help_title": "Kuinka Multisig Vault toimii. Vinkit ja neuvot.", @@ -523,13 +591,19 @@ "ms_help_title1": "Useita laitteita suositellaan", "ms_help_1": "Vault toimii muiden BlueWallet-sovellusten ja PSBT-yhteensopivien lompakoiden kanssa, kuten Electrum, Spectre, Coldcard, Cobo vault jne.", "ms_help_title2": "Avainten Muokkaus", - "ms_help_2": "Voit luoda holvi-avaimia tässä laitteessa. Ja sitten myöhemmin poistaa ja muokate niitä. Kaikkien avaimien pitäminen samassa laitteessa on yhtä turvallista kuin tavallisessa Bitcoin-lompakossakin.", + "ms_help_2": "Voit luoda holvi-avaimia tässä laitteessa. Ja sitten myöhemmin poistaa ja muokata niitä. Kaikkien avaimien pitäminen samassa laitteessa on yhtä turvallista kuin tavallisessa Bitcoin-lompakossakin.", "ms_help_title3": "Vault-varmuuskopiot", - "ms_help_3": "Holvi- ja katselu varmuuskopiot löydät lompakon asetuksista. Tämä varmuuskopio on kartta lompakollesi. Se on välttämätön lompakon palauttamiseksi, mikäli ole hukannut yhdenkään siemensanan tai lauseen.", - "ms_help_title4": "Tuodaan Vault:ia", - "ms_help_4": "Voit tuoda multisig:in käyttämällä varmuuskopiotiedostoasi ja Tuo-ominaisuutta. Jos sinulla on vain laajennettuja avaimia ja siemensanoja, voit käyttää yksittäistä Tuo-näppäintä, kun luot Vault -avaimia.", + "ms_help_3": "Holvi- ja katseluvarmuuskopiot löydät lompakon asetuksista. Tämä varmuuskopio on kartta lompakollesi. Se on välttämätön lompakon palauttamiseksi, mikäli olet hukannut yhdenkään siemensanan tai lauseen.", + "ms_help_title4": "Vaultin tuonti", + "ms_help_4": "Voit tuoda multisigin käyttämällä varmuuskopiotiedostoasi ja Tuo-ominaisuutta. Jos sinulla on vain laajennettuja avaimia ja siemensanoja, voit käyttää yksittäistä Tuo-näppäintä, kun luot Vault-avaimia.", "ms_help_title5": "Lisäasetukset", - "ms_help_5": "Oletuksena BlueWallet luo 2/3 Vaultin. Jos haluat luoda erilaisen päätösvallan tai muuttaa osoitetyyppiä, aktivoi Lisäasetukset Asetuksista." + "ms_help_5": "Oletuksena BlueWallet luo 2/3 Vaultin. Jos haluat luoda erilaisen päätösvallan tai muuttaa osoitetyyppiä, aktivoi Lisäasetukset Asetuksista.", + "provide_signature_details": "Käytä laitetta ja lompakkoa, jossa avain sijaitsee, allekirjoittaaksesi tämän siirtotapahtuman", + "provide_signature_details_bluewallet": "BlueWalletissa siirry Lähetä-näytön valikkoon ja valitse ", + "provide_signature_next_steps_details": "Kun lompakkosi on onnistuneesti allekirjoittanut siirtotapahtuman, skannaa toimitettu QR-koodi tai tuo siihen liittyvä tiedosto, ja tarkista sitten kaikki siirtotapahtuman tiedot ennen sen lähettämistä verkkoon.", + "shared_key_detected": "Jaettu osa-allekirjoittaja", + "shared_key_detected_question": "Sinulle on jaettu osa-allekirjoittaja, haluatko tuoda sen?", + "this_is_cosigners_xpub_airdrop": "Jos jaat AirDropin kautta, vastaanottajien on oltava koordinointinäytöllä." }, "is_it_my_address": { "title": "Onko se osoitteeni?", @@ -539,26 +613,41 @@ "no_wallet_owns_address": "Mikään käytettävissä olevista lompakoista ei omista annettua osoitetta.", "view_qrcode": "Näytä QR-koodi" }, + "autofill_word": { + "generate_word": "Muodosta viimeinen sana", + "title": "Palautuslauseen viimeinen sana", + "enter": "Syötä osittainen palautuslauseesi", + "error": "Syöte ei ole 11 tai 23 sanan osittainen palautuslause. Yritä uudelleen." + }, "cc": { "change": "vaihto", - "coins_selected": "Kolikot valittu ({number})", + "coins_selected": "Kolikoita valittu ({number})", "selected_summ": "{value} valittuna", - "empty": "Tässä lompakossa ei ole tällä hetkellä kolikoita", + "empty": "Tässä lompakossa ei ole tällä hetkellä kolikoita.", "freeze": "jäädytä", "freezeLabel": "Jäädytä", "freezeLabel_un": "Vapauta", "header": "Kolikoiden hallinta", - "use_coin": "Käytä kolikko", - "use_coins": "Käytä kolikkoja", - "tip": "Antaa sinun nähdä, merkitä, jäädyttää tai valita kolikoita parempaan lompakon hallintaan. Voit valita useita kolikoita napauttamalla värillisiä ympyröitä. " + "use_coin": "Käytä kolikkoa", + "use_coins": "Käytä kolikoita", + "tip": "Antaa sinun nähdä, merkitä, jäädyttää tai valita kolikoita parempaan lompakon hallintaan. Voit valita useita kolikoita napauttamalla värillisiä ympyröitä.", + "sort_asc": "Nouseva", + "sort_desc": "Laskeva", + "sort_height": "Korkeus", + "sort_value": "Arvo", + "sort_label": "Etiketti", + "sort_status": "Tila", + "sort_by": "Järjestä" }, "units": { "BTC": "BTC", "MAX": "MAX", - "sat_vbyte": "sat/tavu", + "sat_vbyte": "sat/vByte", "sats": "sattia" }, "addresses": { + "copy_private_key": "Kopioi yksityinen avain", + "sensitive_private_key": "Varoitus: yksityiset avaimet ovat erittäin arkaluontoisia. Jatketaanko?", "sign_title": "Allekirjoita/Varmenna viesti", "sign_help": "Täällä voit luoda tai varmentaa Bitcoin-osoitteeseen perustuvan kryptografisen allekirjoituksen.", "sign_sign": "Allekirjoita", @@ -592,8 +681,24 @@ }, "bip47": { "payment_code": "Maksukoodi", - "payment_codes_list": "Maksukoodiluettelo", - "who_can_pay_me": "Kuka voi maksaa minulle:", - "purpose": "Uudelleenkäytettävä ja jaettavissa oleva koodi (BIP47)" + "contacts": "Yhteystiedot", + "bip47_explain_subtitle": "BIP47", + "purpose": "Uudelleenkäytettävä ja jaettavissa oleva koodi (BIP47)", + "pay_this_contact": "Maksa tälle yhteystiedolle", + "rename_contact": "Nimeä uudelleen yhteystieto", + "copy_payment_code": "Kopioi maksukoodi", + "hide_contact": "Piilota yhteystieto", + "rename": "Nimeä uudelleen", + "provide_name": "Anna uusi nimi tälle kontaktille", + "add_contact": "Lisää yhteystieto", + "not_found": "Maksukoodia ei löytynyt", + "bip47_explain": "Uudelleenkäytettävä ja jaettavissa oleva koodi", + "provide_payment_code": "Anna maksukoodi", + "invalid_pc": "Virheellinen maksukoodi", + "notification_tx_unconfirmed": "Ilmoitustapahtumaa ei ole vielä vahvistettu, odota", + "failed_create_notif_tx": "Ketjussa olevan siirtotapahtuman luominen epäonnistui", + "onchain_tx_needed": "Ketjussa oleva siirtotapahtuma tarvitaan", + "notif_tx_sent": "Ilmoitustapahtuma lähetetty. Odota sen vahvistumista", + "notif_tx": "Ilmoitustapahtuma" } } diff --git a/loc/fo.json b/loc/fo.json new file mode 100644 index 00000000000..fe5f049e19e --- /dev/null +++ b/loc/fo.json @@ -0,0 +1,704 @@ +{ + "_": { + "bad_password": "Loyniorðið er skeivt. Vinaliga royn aftur.", + "cancel": "Ógilda", + "continue": "Halt áfram", + "clipboard": "Setiborð", + "discard_changes": "Avlýs broytingar?", + "discard_changes_explain": "Tú hevur framt broytingar uttan at goyma tær. Ynskir tú at vraka tær og fara úr skíggjamyndini?", + "enter_password": "Inntøppa loyniorð", + "never": "Ongantíð", + "of": "{number} av {total}", + "ok": "Í lagi", + "enter_url": "Inntøppa URL", + "storage_is_encrypted": "Tín goymsla er brongla. Tørvur er á loyniorði fyri at avbrongla hana.", + "yes": "Ja", + "no": "Nei", + "save": "Goym…", + "seed": "Rótorð", + "success": "Eydnaðist", + "wallet_key": "Almennur mappulykil", + "close": "Aftur", + "copied": "Avritað!", + "change_input_currency": "Skift inntøppingargjaldoyra", + "refresh": "Dagfør", + "pick_image": "Vel úr savni", + "pick_file": "Vel fílu", + "enter_amount": "Inntøppa upphædd", + "qr_custom_input_button": "Trýst 10 ferðir fyri nýtaratillagaða inntøppan", + "unlock": "Lat upp", + "port": "Portur", + "ssl_port": "SSL Portur", + "suggested": "Í uppskoti" + }, + "azteco": { + "codeIs": "Tín virðiskota er", + "errorBeforeRefeem": "Tú noyðist at innleggja eina Bitcoin mappu, áðrenn tú útloysir.", + "errorSomething": "Ókend villa fór fram. Er virðiskotan galdandi enn?", + "redeem": "Útloys til mappu", + "redeemButton": "Útloys", + "success": "Eydnaðist", + "successMessage": "Tað eydnaðist at útloysa virðiskotuna! Tú skuldi móttiki peningin, í tína Bitcoin mappu, um stutta tíð.", + "title": "Útloys Azte.co virðiskotu" + }, + "entropy": { + "save": "Goym", + "title": "Entropi", + "undo": "Angra", + "amountOfEntropy": "{bits} av {limit} bitum" + }, + "errors": { + "broadcast": "Útvarping miseydnaðist.", + "error": "Villa", + "network": "Net villa" + }, + "lnd": { + "errorInvoiceExpired": "Gjaldsumbønin er fyrnað.", + "expired": "Fyrnað", + "expiresIn": "Fyrnar um {time} minuttir", + "payButton": "Rinda", + "payment": "Gjald", + "placeholder": "Gjaldsumbøn ella adressa", + "potentialFee": "Møguligt avgjald: {fee}", + "refill": "Set inn", + "refill_create": "Vinaliga ger, ella innles, eina Bitcoin mappu at innseta frá, fyri at halda áfram.", + "refill_external": "Set inn úr ytri mappu", + "refill_lnd_balance": "Innseting á Lightning mappu", + "sameWalletAsInvoiceError": "Tað ber ikki til at rinda eina gjaldsumbøn við somu mappu ið hevur gjørt gjaldsumbønina.", + "title": "Umsit pening" + }, + "lndViewInvoice": { + "additional_info": "Smálutir", + "for": "Frágreiðing:", + "lightning_invoice": "Lightning gjaldsumbøn", + "please_pay_between_and": "Vinaliga rinda millum {min} og {max}", + "please_pay": "Vinaliga rinda", + "preimage": "Frumvirði", + "sats": "sats.", + "date_time": "Dagfesting og tíð", + "wasnt_paid_and_expired": "Gjaldsumbønin er ógoldin og fyrnað." + }, + "plausibledeniability": { + "create_fake_storage": "Ger bronglaða goymslu", + "create_password_explanation": "Loyniorðið fyri skálskagoymsluna skal vera ólíkt loyniorðinum til tína vanligu goymslu.", + "help": "Í óynsktum føri kanst tú verða kroystur at útvega eitt loyniorð. BlueWallet kann gera eina aðra bronglaða goymslu við einum øðrum loyniorði, fyri at tryggja tín pening. Undir tvingsli kanst tú geva tað loyniorðið til triðja partin. Tá tað loyniorðið verður inntøppa í BlueWallet letur tað eina \"skálka\" goymslu upp. Hendan skal tykast trúlig fyri møguligum triðja parti, soleiðis at høvuðsgoymslan, við helminginum av tíni upphædd, ikki verður avdúka.", + "help2": "Nýggja goymslan er virkin. Tú kanst hava eina líttla upphædd á eini mappu í henni, soleiðis at tað tykist trúligt.", + "password_should_not_match": "Loyniorðið er longu í nýtslu. Vinaliga áset eitt annað loyniorð.", + "title": "Haldgóð avsannan" + }, + "pleasebackup": { + "ask": "Hevur tú goymt mappu rótorðini? Rótorðini eru neyðug fyri at brúka peningin, frá øðrum eindum ella í fall hendan eindin verður burturmist. Uttan rótorðini, ið virka sum trygdaravrit, fæst eingin atgongd til peningin.", + "ask_no": "Nei, tað havi eg ikki.", + "ask_yes": "Ja, tað havi eg.", + "ok": "Ja, eg havi goymt tey á tryggan hátt.", + "ok_lnd": "Ja, eg havi goymt tað.", + "text": "Vinaliga gev tær stundir at niðurskriva hesa áminningarramsuna á pappír.\nTað er títt trygdaravrit ið kann nýtast til at endurinnlesa mappuna.", + "text_lnd": "Vinaliga goym mapputrygdaravritið. Tað loyvir tær at endurinnlesa mappuna, í fall hon verður burturmist.", + "title": "Tín mappa er gjørd." + }, + "receive": { + "details_create": "Ger", + "details_label": "Tekstboð", + "details_setAmount": "Inngjald við vegleiðandi upphædd", + "details_share": "Deil…", + "address_not_found": "Bar ikki til at framleiða inngjaldsadressu.", + "header": "Inngjald", + "reset": "Tómstilla", + "maxSats": "Hámarksupphæddin er {max} sats", + "maxSatsFull": "Hámarksupphæddin er {max} sats ella {currency}", + "minSats": "Minsta upphædd er {min} sats", + "minSatsFull": "Minsta upphædd er {min} sats ella {currency}", + "qrcode_for_the_address": "Adressan sum QR kota", + "bip47_explanation": "BIP47 gjaldkotur eru algildar fastar adressur, ið avleiða serskildar og óbrúktar adressur millum tveir partar, uttan at avdúka aðrar adressur og ognir. Tó kann nýtsla av myntum í BIP47 avleiddum adressum, saman við myntum úr vanligum adressum í somu flyting, avdúka ognarskap. Ikki allar mappur og tænastur hava hentleika fyri BIP47 gjaldkotum." + }, + "send": { + "provided_address_is_invoice": "Adressan tykist at vera ein Lightning gjaldsumbøn. Vinaliga far inná tína Lightning mappu fyri at rinda gjaldsumbønina.", + "broadcastButton": "Útvarpa", + "broadcastError": "Villa", + "broadcastNone": "Inntøppa flytingardátur, sum sekstandatøl/hexadecimal", + "broadcastPending": "Ávegis", + "broadcastSuccess": "Eydnaðist", + "confirm_header": "Vátta", + "confirm_sendNow": "Góðkenn", + "create_amount": "Upphædd", + "create_broadcast": "Útvarpa", + "create_copy": "Avrita, fyri síðan at útvarpa seinni", + "create_details": "Smálutir", + "create_fee": "Avgjald", + "create_memo": "Viðmerking", + "create_satoshi_per_vbyte": "Satoshi fyri tBýt", + "create_this_is_hex": "Hetta er flytingin, sum sekstandatøl/hexadecimal — undirritað og klár at útvarpa til netið.", + "create_to": "Til", + "create_tx_size": "Flytingarstødd", + "create_verify": "Kanna á coinb.in", + "details_insert_contact": "Tilskila BIP47 móttakara", + "details_add_rec_add": "Legg móttakara afturat", + "details_add_rec_rem": "Strika móttakara", + "details_add_recc_rem_all_alert_description": "Ynskir tú at strika allar móttakararnar?", + "details_add_rec_rem_all": "Strika allar móttakarar", + "details_recipients_title": "Móttakarir", + "details_recipient_title": "Móttakaraadressa nr. {number} av {total}", + "please_complete_recipient_title": "Ófulfíggjaður móttakari", + "please_complete_recipient_details": "Vinaliga fulfíggja upplýsingarnar tilhoyrandi móttakara nr. {number}, áðrenn tú leggur ein afturat.", + "details_address": "Adressa", + "details_address_field_is_not_valid": "Adressan er ógildig.", + "details_adv_fee_bump": "Loyv avgjaldshækkan", + "details_adv_full": "Nýt alla salduna", + "details_adv_full_sure": "Ynskir tú at nýta alla salduna, í mappuni, til hesa flytingina?", + "details_adv_full_sure_frozen": "Ynskir tú at brúka alla salduna, í mappuni, til hesa flytingina? Hav í huga at læstir myntir ikki eru íroknaðar.", + "details_adv_import": "Innles flyting", + "details_adv_import_qr": "Innles flyting (QR)", + "details_amount_field_is_not_valid": "Upphæddin er ógildig.", + "details_amount_field_is_less_than_minimum_amount_sat": "Ásetta upphæddin er ov lítil. Vinaliga áset eina upphædd ið er hægri enn 500 sats.", + "details_create": "Stovnað gjaldsumbøn", + "details_error_decode": "Bar ikki til at avkota Bitcoin adressu", + "details_fee_field_is_not_valid": "Avgjaldið er ógildigt.", + "details_frozen": "{amount} BTC er læst.", + "details_next": "Hald áfram", + "details_no_signed_tx": "Valda fílan inniheldur ikki nakra flyting ið kann innlesast.", + "details_note_placeholder": "Egin viðmerking", + "details_scan": "Skanna", + "details_scan_hint": "Tvíklikk fyri at skanna ella innlesa", + "details_scan_error": "Villa undir skanning", + "details_total_exceeds_balance": "Útgjaldið er hægri enn tøka saldan.", + "details_total_exceeds_balance_frozen": "Útgjaldið er hægri enn tøka saldan. Hav í huga at læstir myntir ikki eru íroknaðar.", + "details_unrecognized_file_format": "Ókent fíluforsnið", + "details_wallet_before_tx": "Tú noyðist at innleggja eina Bitcoin mappu, fyri at gera eina flyting.", + "dynamic_init": "Innleiður", + "dynamic_next": "Næsta", + "dynamic_prev": "Fyrra", + "dynamic_start": "Starta", + "dynamic_stop": "Steðga", + "fee_10m": "10m", + "fee_1d": "1d", + "fee_3h": "3t", + "fee_custom": "Nýtaraásett", + "insert_custom_fee": "Inntøppa avgjald", + "fee_fast": "Skjót", + "fee_medium": "Miðal", + "fee_replace_minvb": "Tú eigur at gjalda ein samlaðan avgjaldssats (satoshi fyri tBýt) ið er hægri enn {min} sat/tBýt.", + "fee_satvbyte": "í sat/tBýt", + "fee_slow": "Sein", + "header": "Útgjald", + "input_clear": "Tómstilla", + "input_done": "Liðugt", + "input_paste": "Innset", + "input_total": "Samlað upphædd:", + "permission_camera_message": "Appini tørvar loyvi, frá tær, at brúka títt myndatól.", + "psbt_sign": "Undirrita eina flyting", + "invalid_psbt": "Ógildig PSBT innlisin.", + "open_settings": "Lat upp kervisstillingar", + "permission_storage_denied_message": "Bar ikki til, hjá BlueWallet, at goyma fíluna. Vinaliga lat upp kervisstillingar og játta BlueWallet atgongd til goymsluna.", + "permission_storage_title": "Goymsluatgongdarloyvi", + "psbt_clipboard": "Avrita til setiborð", + "psbt_this_is_psbt": "Hetta er ein Partvís Undirritað Bitcoin Flyting (PSBT). Vinaliga útinn undirritanina við tíni tólbúnaðarmappu.", + "psbt_tx_export": "Tak út sum fíla", + "no_tx_signing_in_progress": "Eingin flytingarundirritan er í gongd.", + "outdated_rate": "Gjaldoyrakostnaðurin varð síðst dagførdur tann: {date}", + "psbt_tx_open": "Innles undirritaða flyting", + "psbt_tx_scan": "Skanna undirritaða flyting", + "qr_error_no_qrcode": "Miseydnaðist at finna eina gildiga QR kotu í valdu myndini. Syrg fyri at myndin einans inniheldur eina QR kotu, og ikki annað sum t.d. tekst ella knøttar.", + "reset_amount": "Tómstilla upphædd", + "reset_amount_confirm": "Ynskir tú at tómstilla upphæddina?", + "success_done": "Avgreitt", + "txSaved": "Flytingarfílan ({filePath}) er goymd.", + "file_saved_at_path": "Fílan ({filePath}) er goymd.", + "cant_send_to_silentpayment_adress": "Ber ikki til at senda til SilentPayment adressur frá hesi mappuni", + "cant_send_to_bip47": "Ber ikki til at senda til BIP47 gjaldkotur frá hesi mappuni", + "cant_find_bip47_notification": "Tú noyðist at leggja gjaldkotuna í BIP47 móttakaralistan fyrst", + "problem_with_psbt": "PSBT villa" + }, + "settings": { + "about": "Um", + "about_awesome": "Ment við teimum framúrskarandi", + "about_backup": "Hav altíð trygdaravrit av tínum lyklum!", + "about_free": "BlueWallet er ein fræls verkætlan har gjøgnumskygda frumforritið er alment tøkt. Ment av Bitcoin nýtarum.", + "about_license": "MIT loyvið", + "about_release_notes": "Sleppingarskriv", + "about_review": "Gev okkum eitt ummæli", + "performance_score": "Avriksstig: {num}", + "run_performance_test": "Koyr avriksroynd", + "about_selftest": "Koyr sjálvkanning", + "block_explorer_invalid_custom_url": "Inntøppaða URL'ið er ógildigt. Vinaliga inntøppa eitt gildigt URL ið byrjar við http:// ella https://.", + "about_selftest_electrum_disabled": "Tað ber ikki til at koyra sjálvkanning í Electrum-avlinjustøðu. Vinaliga óvirkja avlinjustøðu og royn aftur.", + "about_selftest_ok": "Allar innankerviskanningarnar eydnaðust. Tí koyrur appin álítandi.", + "about_sm_github": "GitHub", + "about_sm_telegram": "Telegram rás", + "privacy_temporary_screenshots": "Loyv skíggjaavmyndan", + "privacy_temporary_screenshots_instructions": "Skíggjaavmyndan verður fyribils loyvd, soleiðis at skíggjamyndir og upptøkur kunnu takast. Verndin verður sjálvvirkandi virkja aftur, undir næstu koyring av BlueWallet.", + "biometrics": "Lívmátilig atgongd", + "biometrics_no_longer_available": "Kervisstillingarnar á tíni eind eru broyttar og samsvara ikki longur við trygdarstillingarnar í appini. Vanliga áset lívmátiliga atgongd ella atgonguloynital aftur, og endurbyrja appina fyri at virkja stillingarnar.", + "biom_10times": "Tú hevur roynt títt loyniorð 10 ferðir, uttan at tað eydnaðist. Ynskir tú at tómstilla goymsluna? Tað strikar allar mappurnar og avbronglar goymsluna.", + "biom_conf_identity": "Vinaliga vátta tín samleika.", + "biom_no_passcode": "Eindin hevur ikki virkjaða atgonguloynital ella lívmátiliga atgongdarvernd. Vinaliga áset atgonguloynital ella lívmátiliga atgongd í kervisstillingum.", + "biom_remove_decrypt": "Innlisnu mappurnar verða strikaðar og goymslan verður avbrongla. Ynskir tú at halda áfram?", + "currency": "Gjaldoyra", + "currency_source": "Gjaldoyrakostnaðurin er frá", + "currency_fetch_error": "Vegna villu bar ikki til at fáa gjaldoyrakostnaðin fyri valda gjaldoyrað.", + "default_title": "Byrjan", + "donate": "Veit fíggjarligan stuðul", + "donate_description": "Stuðla sjálvbodnu menningini, so BlueWallet framhaldandi kann verða frælst og ókeypis!", + "electrum_connected": "Sambundin", + "electrum_connected_not": "Ikki sambundin", + "electrum_error_connect": "Bar ikki til at sambinda við ásetta Electrum ambætaran", + "electrum_error_connect_tor": "Bar ikki til at sambinda við ásetta Electrum ambætaran Vinaliga syrg fyri at Orbot appin er sambundin og royn so aftur.", + "lndhub_uri": "T.d., {example}", + "electrum_host": "T.d., {example}", + "electrum_offline_mode": "Avlinjustøða", + "electrum_offline_description": "Í avlinjustøðu verða saldur og flytingar, á Bitcoin mappunum, ikki umbidnar.", + "electrum_port": "Portur, í flestu førum {example}", + "use_ssl": "Brúka SSL", + "electrum_saved": "Tínar broytingar eru goymdar. Tørvur kann verða á endurbyrjan av BlueWallet, fyri at broytingarnar fáa virknað.", + "set_electrum_server_as_default": "Áset {server} sum forsettan Electrum ambætara?", + "set_lndhub_as_default": "Áset {url} sum forsettan LNDhub ambætara?", + "electrum_settings_server": "Electrum ambætari", + "electrum_status": "Støða", + "electrum_preferred_server": "Ásettur ambætari", + "electrum_preferred_server_description": "Áset Electrum ambætaran ið mappurnar skulu nýta til alt Bitcoin virksemi. Mappurnar nýta bert hendan ambætaran; til at kanna saldur, útvarpa flytingar, og útvega sær netdátur. Tryggja tær álit á ambætaranum tú velur at brúka.", + "electrum_unable_to_connect": "Bar ikki til at sambinda við {server}.", + "electrum_history": "Undanfarnir", + "electrum_reset_to_default": "Hetta letur BlueWallet velja ein tilvildarligan ambætara úr ambætaralistanum.", + "electrum_reset": "Tómstilla, til forsettar-stillingar", + "electrum_reset_to_default_and_clear_history": "Tómstilla og strika undanfarnar ambætarar", + "encrypt_decrypt": "Avbrongla goymslu", + "encrypt_decrypt_q": "Ynskir tú at avbrongla goymsluna? Um so er, gerast mappurnar atkomiligar uttan eitt loyniorð.", + "encrypt_enc_and_pass": "Loyniorðsvernd", + "encrypt_storage_explanation_headline": "Virkja goymslubronglan", + "encrypt_storage_explanation_description_line1": "Goymslubronglan bronglar dáturnar, ið BlueWallet appin viðgerð og goymur á tíni eind, og økir harvið um trygdina. Hetta ger tað truplari, hjá øðrum, at fáa atgongd til tínar dátur uttan loyvi.", + "encrypt_storage_explanation_description_line2": "Men tað er umráðandi, at hava í huga, at bronglanin bert verjur atgongd til tínar mappur ið eru goymdar á hesi eindini. Bronglanin leggur ikki loyniorð, ella eyka trygd, á sjálvar mappurnar.", + "i_understand": "Eg skilji", + "block_explorer": "Blokksjóneyka", + "block_explorer_preferred": "Brúka aðra blokksjóneyku", + "block_explorer_error_saving_custom": "Villa undir goymslu av valdu blokksjóneyku", + "encrypt_title": "Trygd", + "encrypt_tstorage": "Goymsla", + "encrypt_use": "Nýt {type}", + "set_as_preferred": "Vel framum", + "set_as_preferred_electrum": "Eftirsum ambætarin {host}:{port} verður ásettur, verður ikki sambundið til ein tilvildarligan ambætara, av teimum ið eru í uppskoti.", + "encrypted_feature_disabled": "Hentleikin kann ikki virkjast, samstundis sum goymslubronglan er virki.", + "encrypt_use_expl": "{type} verður brúkt at vátta tín samleika áðrenn eina flyting, við upplating, og úttøku og striking av mappu.", + "biometrics_fail": "Um {type} ikki er virkjað, ella tað miseydnast at lata upp, kanst tú ístaðin nýta atgonguloynitalið til eindina.", + "general": "Yvirskipaðar", + "general_continuity": "Framhald", + "general_continuity_e": "Um virkjað kanst tú síggja ávísar mappur, og flytingar, á øðrum eindum knýttar at tínum Apple iCloud.", + "groundcontrol_explanation": "GroundControl er ein frælsur push-fráboðanarambætari, har gjøgnumskygda frumforritið er alment tøkt. Fyri at gerast óbundin av undirstøðukervinum hjá BlueWallet, hevur tú møgulleika at innleggja og uppseta egnan GroundControl ambætara, og inntøppa tilhoyrandi URL í hendan teigin. Lat teigin verða tóman fyri at brúka forsetta GroundControl ambætaran.", + "header": "Stillingar", + "language": "Mál", + "last_updated": "Dagført", + "language_isRTL": "Broytta tekstkósin fær virknað við endurbyrjan av BlueWallet.", + "license": "Loyvi", + "lightning_error_lndhub_uri": "Ógildigt LNDhub URI", + "lightning_error_lndhub_uri_tor": "Ógildigt LNDhub URI. Vinaliga syrg fyri at Orbot appin er sambundin og royn so aftur.", + "lightning_saved": "Tað eydnaðist at goyma tínar broytingar.", + "lightning_settings": "Lightning stillingar", + "lightning_settings_explain": "Fyri at sambinda við egnan LND knút er neyðugt at hava innlagt LNDhub ritbúnað, og inntøppa tilhoyrandi URL niðanfyri. Hav í huga: Stillingin hevur einans virknað fyri mappur ið verða innlagdar eftir at stillingin er goymd.", + "lndhub_github": "Verkætlanin á GitHub", + "network": "Net", + "network_broadcast": "Útvarpa flyting", + "network_electrum": "Electrum ambætari", + "electrum_suggested_description": "Um eingin ambætari er ásettur verður ein tilvildarligur valdur, út frá teimum ið eru í uppskoti.", + "not_a_valid_uri": "Ógildigt URI", + "notifications": "Fráboðanir", + "open_link_in_explorer": "Lat leinkju upp í kaga", + "password": "Loyniorð", + "password_explain": "Inntøppa loyniorðið tú ynskir at brúka til at lata goymsluna upp.", + "plausible_deniability": "Haldgóð avsannan", + "privacy": "Dátuvernd", + "privacy_read_clipboard": "Lesa setiborð", + "privacy_system_settings": "Stýrikervisstillingar", + "privacy_quickactions": "Mappu-snarknappar", + "privacy_quickactions_explanation": "Halt á BlueWallet app ímyndini, vel síðani mappu fyri at síggja salduna á mappuni.", + "privacy_clipboard_explanation": "Virkjar snarknappar, ið fall ein adressa ella gjaldsumbøn verður funnin í setiborðinum.", + "privacy_do_not_track": "Óvirkja greiningar", + "privacy_do_not_track_explanation": "Ritbúnaðar avriks- og dygdarupplýsingar verða ikki innsendir til greiningar.", + "rate": "Gjaldoyrakostnaður", + "push_notifications_explanation": "Verða fráboðanir virkjaðar, so verður eindar-eyðmerkið sent til ambætaran, saman við mappu-adressum og flytingar-eyðmerkjum, tilhoyrandi allar mappur, fremdar eftir at fráboðanir eru virkjaðar. Eindar-eyðmerkið verður brúkt til at senda fráboðanina, og mappu-upplýsingarnir gera ambætaran føran fyri at nágreina og fráboða tær um tíni inngjøld og váttanir.\n\nHettar hevur ikki afturvirkandi kraft. Upplýsingar verða einans sendir, frá tí at fráboðanir verða virkjaðar.\n\nÓvirkjan av fráboðanum elvir til at allar omanfyrinevndu upplýsingar verða strikaðar frá ambætaranum. Umframt tað verða tilhoyrandi upplýsingar eisini strikaðar frá ambætaranum, tá ein mappa verður strika úr appini.", + "selfTest": "Sjálvkanning", + "save": "Goym", + "saved": "Goymt", + "success_transaction_broadcasted": "Tað eydnaðist at útvarpa tína flyting!", + "total_balance": "Samlað salda", + "total_balance_explanation": "Vís samlaðu salduna av øllum tínum mappum í skíggjaluti á startsíðuni", + "widgets": "Skíggjalutir", + "tools": "Hentleikar" + }, + "notifications": { + "would_you_like_to_receive_notifications": "Ynskir tú at fáa fráboðanir fyri inngjøld?", + "notifications_subtitle": "Inngjøld og váttanir fyri flytingar.", + "no_and_dont_ask": "Nei, og spyr ikki aftur.", + "permission_denied_message": "Tú hevur noktað at fáa fráboðanir. Vinaliga loyv og virkja fráboðanir undir kervisstillingum, um tú ynskir fáa fráboðanir." + }, + "transactions": { + "cancel_explain": "Ein nýggj flyting verður gjørd, ið brúkar somu myntir, men rindar tær og eitt hægri avgjald. Verður hon váttað verður undanfarna flytingin ógildað. Hetta verður nevnt RBF—Replace by Fee.", + "cancel_no": "Flytingin kann ikki avlýsast.", + "cancel_title": "Avlýs flytingina (RBF)", + "transaction_loading_error": "Villa undir innlesing av flyting. Vinaliga royn aftur seinni.", + "transaction_not_available": "Flytingin er ikki tøk", + "confirmations_lowercase": "{confirmations} váttanir", + "expand_note": "Víðka tekstlut", + "cpfp_create": "Ger", + "cpfp_exp": "Ein nýggj flyting verður gjørd, ið brúkar tína óváttaðu flyting. Avgjaldið verður hægri enn á upprunaligu flytingini, so at hon skjótari verður útvunnin/váttað. Hetta verður nevnt CPFP—Child Pays for Parent.", + "cpfp_no_bump": "Avgjaldhækkan er ikki møgulig, fyri flytingina.", + "cpfp_title": "Hækka avgjald (CPFP)", + "details_balance_hide": "Fjal saldu", + "details_balance_show": "Vís saldu", + "details_copy": "Avrita", + "details_copy_block_explorer_link": "Avrita blokksjóneykuleinkju", + "details_copy_note": "Avrita tekst", + "details_copy_txid": "Avrita flytingareyðmerkið.", + "details_inputs": "Inntøk", + "details_outputs": "Úttøk", + "date": "Dagfesting og tíð", + "details_received": "Móttikin tann", + "details_view_in_browser": "Lat upp í kaga", + "details_title": "Flyting", + "incoming_transaction": "Inngjald", + "outgoing_transaction": "Útgjald", + "expired_transaction": "Fyrnað flyting", + "pending_transaction": "Óváttað flyting", + "offchain": "Avketu", + "onchain": "Áketu", + "details_to": "Úttøk", + "enable_offline_signing": "Mappan verður, sum er, ikki nýtt í.s.v. eina avlinjuundirritanareind. Ynskir tú at nýta mappuna í.s.v. eina avlinjuundirritanareind?", + "list_conf": "Váttanir: {number}", + "pending": "Óváttað", + "pending_with_amount": "Óváttað {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "Væntandi váttað um uml. 10 minuttir", + "eta_3h": "Væntandi váttað um uml. 3 tímar", + "eta_1d": "Væntandi váttað um uml. 1 dag", + "list_title": "Flytingar", + "list_title_sent": "Útgoldið", + "list_title_received": "Inngoldið", + "transaction": "Flyting", + "open_url_error": "Bar ikki til at latað leinkjuna upp í forsetta kaganum. Vinaliga broyt tín forsetta kaga og royn aftur.", + "rbf_explain": "Ein nýggj flyting verður gjørd, sum rindar eitt hægri avgjald, soleiðis at hon skjótari verður váttað. Hetta verður nevnt RBF—Replace by Fee.", + "rbf_title": "Hækka avgjald (RBF)", + "status_bump": "Hækka avgjald", + "status_cancel": "Avlýs flyting", + "transactions_count": "Flytingar", + "txid": "Flytingareyðmerki", + "updating": "Innlesur…", + "watchOnlyWarningTitle": "Ávaring", + "watchOnlyWarningDescription": "Ver varin fyri svikarum ið mangan nýta eygleiðingarmappur at lumpa nýtarar. Slíkar mappur loyva tær ikki at brúka ella senda peningarnar; tær loyva tær bara at síggja salduna.", + "custom_fee_warning_title": "Gev gætur", + "custom_fee_warning_description": "Avgjøld undir 1 sat/tB eru gildig, men verða ikki neyðturviliga framsend vegnað knútapolitikk.", + "details_eta_analyzing": "Greinar…", + "details_sent": "Útgoldið", + "details_section": "Smálutir", + "details_explorer": "sjóneyka", + "details_network_fee": "Net-avgjald", + "details_to_address": "Til", + "details_id": "ID", + "details_note": "Tekstur", + "details_add_note": "legg afturat", + "details_advanced": "Víðkað", + "details_fee_rate": "Avgjaldsskeið", + "details_size": "Stødd", + "details_virtual_size": "Virtuell stødd", + "details_tx_hex": "Flytingar-sekstandatøl", + "details_inputs_count": "Inntøk ({count})", + "details_outputs_count": "Úttøk ({count})" + }, + "wallets": { + "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Einføld og dugnalig Bitcoin mappa", + "add_create": "Ger mappu", + "total_balance": "Samlað salda", + "add_entropy_reset_title": "Tómstilla entropi", + "add_entropy_reset_message": "Skifti av mappuslagi tómstillar verandi entropi. Ynskir tú at halda áfram?", + "add_entropy": "Entropi", + "add_entropy_bytes": "{bytes} být av entropi", + "add_entropy_generated": "Framleitt {gen} být av entropii", + "add_entropy_provide": "Veit entropi við terningakøstum", + "add_entropy_remain": "{gen} být av entropi eru framleidd. {rem} írestandi být verða útvegað frá stýrikervistilvildartalframleiðaranum.", + "add_import_wallet": "Innles mappu", + "add_lightning": "Lightning", + "add_lightning_explain": "Til stundisligar flytingar", + "add_lndhub": "Sambind í tín LNDhub", + "add_lndhub_error": "Inntøppaða adressan vísur ikki til ein gildigan LNDhub knút.", + "add_lndhub_placeholder": "Adressan til tín knút", + "add_placeholder": "mín fyrsta mappa", + "add_title": "Legg mappu inn", + "add_wallet_name": "Navn", + "add_wallet_type": "Slag", + "add_wallet_seed_length": "Tal av rótorðum", + "add_wallet_seed_length_12": "12 orð", + "add_wallet_seed_length_24": "24 orð", + "clipboard_bitcoin": "Tú hevur eina Bitcoin adressu í setiborðinum. Ynskir tú at brúka hana í eini flyting?", + "clipboard_lightning": "Tú hevur eina Lightning gjaldsumbøn í tínum setiborði. Ynskir tú at gjalda hana?", + "clear_clipboard_on_import": "Reinsa setiborð eftir innsetan", + "clear_search": "Reinsa leiting", + "drag_to_reorder": "Drag fyri at umraða", + "swipe_balance_hide": "Fjal", + "swipe_balance_show": "Vís", + "details_address": "Adressa", + "details_advanced": "Víðkaðar stillingar", + "details_are_you_sure": "Ert tú vís/ur?", + "details_connected_to": "Bundin at", + "details_del_wb_err": "Upphæddin samsvarar ikki við saldu mappunnar. Vinaliga royn aftur.", + "details_del_wb_q": "Mappan hevur eina saldu. Áðrenn tú heldur áfram, skalt tú hava í huga at tú ikki kanst fáa mappuna, og tilhoyrandi pening, aftur uttan tilhoyrandi rótorð. Vinaliga inntøppa mappusalduna ið er {balance} satoshis, fyri at fyribyrgja ótilætlaða striking.", + "details_delete": "Strika", + "details_delete_wallet": "Strika mappu", + "details_derivation_path": "avleiðsluleið", + "details_display": "Vís á startsíðuni", + "details_export_backup": "Tak út/Trygdaravrita", + "details_export_history": "Tak flytingaryvirlit út sum CSV", + "details_master_fingerprint": "Høvuðseyðkenni", + "details_multisig_type": "fjølundirritan", + "details_show_xpub": "Vís XPUB tilhoyrandi mappuna", + "details_show_addresses": "Vís adressur", + "details_title": "Mappa", + "wallets": "Mappur", + "details_type": "Slag", + "details_use_with_hardware_wallet": "Nýt við tólbúnaðarmappu", + "details_yes_delete": "Ja, strika hana", + "enter_bip38_password": "Inntøppa loyniorð fyri at avbrongla", + "export_title": "Mappu úttøka", + "import_do_import": "Innles", + "import_passphrase": "Loynisetning", + "import_passphrase_title": "Loynisetning", + "import_passphrase_message": "Inntøppa loynisetning, í fall tú hevur ein", + "import_error": "Innlesing miseydnaðist. Vinaliga syrg fyri at dáturnar eru gildigar.", + "import_explanation": "Vinaliga inntøppa tíni rótorð, almenna lykil, WIF, ella okkurt annað tú hevur. BlueWallet roynir, eftir besta førimuni, at finna rættað forsniðið og innlesa mappuna.", + "import_imported": "Innlisin", + "import_scan_qr": "Skanna ella innles úr fílu", + "import_success": "Tað eydnaðist at innlesa tína mappu.", + "import_success_watchonly": "Tað eydnaðist at innlesa tína mappu. GEV GÆTUR: Hetta er ein eygleiðingarmappa; tú kanst IKKI nýta úr henni.", + "import_search_accounts": "Leita eftir kontum", + "import_title": "Innles", + "learn_more": "Frætt meira", + "import_discovery_title": "Leitan", + "import_discovery_subtitle": "Vel eina funna mappu", + "import_discovery_derivation": "Brúka eina aðra avleiðsluleið", + "import_discovery_no_wallets": "Fann ongar mappur.", + "import_discovery_offline": "BlueWallet er í avlinjustøðu og tá ber ikki til, hjá BlueWallet, at vátta at ein mappa finst; tí noyðist tú sjálv/ur at nágreina tað røttu.", + "import_derivation_found": "Funnin", + "import_derivation_found_not": "Ikki funnin", + "import_derivation_loading": "Innlesur…", + "import_derivation_subtitle": "Inntøppa eina avleiðsluleið, so BlueWallet kann royna at finna tína mappu.", + "import_derivation_title": "Avleiðsluleið", + "import_derivation_unknown": "Ókend", + "import_wrong_path": "Skeiv avleiðsluleið", + "list_create_a_button": "Legg inn", + "list_create_a_wallet": "Legg mappu inn", + "list_create_a_wallet_text": "Tað er ókeypis og tú kanst\ngera so nógvar tær lystir.", + "list_empty_txs1": "Her verða tínar flytingar vístar.", + "list_empty_txs1_lightning": "Til dagligar flytingar eiga Lightning mappur at verða brúktar. Avgjøldini eru sera lág og uppgerðirnar kolandi skjótar.", + "list_empty_txs2": "Byrja við tíni mappu.", + "list_empty_txs2_lightning": "\nTrýst á 'Umsit pening' fyri at fylla á og kunna brúka hana.", + "list_latest_transaction": "Seinasta flyting", + "list_long_choose": "Vel mynd", + "paste_from_clipboard": "Innset", + "import_file": "Innles fílu", + "list_long_scan": "Skanna QR kotu", + "list_title": "Mappur", + "list_tryagain": "Royn aftur", + "no_ln_wallet_error": "Tú noyðist at innleggja eina Lightning mappu, fyri at rinda eina Lightning gjaldsumbøn.", + "looks_like_bip38": "Hetta tykist vera ein privatur lykil vardur av loyniorði (BIP38).", + "manage_title": "Umsit mappur", + "no_results_found": "Eingin mappa varð funnin", + "please_continue_scanning": "Hald áfram at leita.", + "select_no_bitcoin": "Ongar Bitcoin mappur eru tøkar.", + "select_no_bitcoin_exp": "Neyðugt er við eini Bitcoin mappu, at seta pening inná Lightning mappur. Vinaliga ger ella innles eina Bitcoin mappu.", + "select_wallet": "Vel mappu", + "pull_to_refresh": "Toga fyri at dagføra", + "warning_do_not_disclose": "Deil aldrin upplýsingarnar niðanfyri", + "scan_import": "Skanna hesa QR kotuna fyri at innlesa tína mappu í eitt annað forrit.", + "write_down_header": "Trygdaravrita", + "write_down": "Niðurfest og goym hesi orðini undir tryggum umstøðum. Tørvur er á teimum fyri at endurinnlesa mappuna.", + "wallet_type_this": "Mappan er av slagnum: {type}.", + "share_number": "{number}. partur", + "copy_ln_url": "Niðurfest og goym URLið, undir tryggum umstøðum, soleiðis at tú seinni kanst endurinnlesa mappuna.", + "copy_ln_public": "Niðurfest og goym fylgjandi upplýsingar, undir tryggum umstøðum, soleiðis at tú seinni kanst endurinnlesa mappuna.", + "add_ln_wallet_first": "Tú noyðist at leggja eina Lightning mappu inn fyrst.", + "identity_pubkey": "Pubkey samleiki", + "xpub_title": "Mappu XPUB", + "manage_wallets_search_placeholder": "Leita eftir mappum, adressum, flytingum og tekstboðum", + "more_info": "Fleiri upplýsingar", + "details_delete_wallet_error_message": "Fekk ikki váttan, frá fráboðanarambætaranum, um at mappueygleiðingin var strikað. Hetta kann vera orsakað av net trupulleika ella ónøktandi sambandi. Heldur tú áfram er møguligt at tú framhaldandi fær fráboðanir um flytingar tilhoyrandi hesa mappuna, hóast hon er strikað.", + "details_delete_anyway": "Strika mappuna kortini" + }, + "total_balance_view": { + "display_in_bitcoin": "Vís í Bitcoin", + "hide": "Fjal", + "display_in_sats": "Vís í satoshis", + "display_in_fiat": "Vís í {currency}", + "title": "Samlað salda", + "explanation": "Vís samlaðu salduna av øllum tínum mappum á yvirlit skíggjamyndini." + }, + "multisig": { + "multisig_vault": "Fjølundirritanarvirðisgoymsla", + "default_label": "Fjølundirritanarvirðisgoymsla", + "multisig_vault_explain": "Tryggast fyri stórar upphæddir", + "provide_signature": "Útvega undirskrift", + "provide_signature_details": "Undirrita flytingina við eindini og mappuni ið privati lykilin liggur á", + "provide_signature_details_bluewallet": "Í BlueWallet, far inná Útgjald og vel ", + "provide_signature_next_steps": "Skanna ella innles undirritaða flyting", + "provide_signature_next_steps_details": "Skanna útvegaðu QR kotuna ella innles útvegaðu fíluna, eftir at flytingin er undirrita. Met síðani um flytingarsmálutirnar áðrenn útvarping.", + "vault_key": "Virðisgoymslulykil {number}", + "required_keys_out_of_total": "Lyklar kravdir av samlaðu lyklunum", + "fee": "Avgjald: {number}", + "fee_btc": "{number} BTC", + "confirm": "Vátta", + "header": "Útgjald", + "share": "Deil…", + "view": "Vís", + "shared_key_detected": "Deildur samundirritari", + "shared_key_detected_question": "Ein samundirritari var deildur, ynskir tú at innlesa hann?", + "manage_keys": "Umsit lyklar", + "how_many_signatures_can_bluewallet_make": "undirritanir ið BlueWallet kann gera", + "signatures_required_to_spend": "{number} undirskriftir kravdar", + "signatures_we_can_make": "harav {number} kunnu undirritast beinleiðis á hesi eindini", + "scan_or_import_file": "Skanna ella innles úr fílu", + "export_coordination_setup": "Tak samskipanaruppsetan út", + "cosign_this_transaction": "Samundirrita flytingina?", + "lets_start": "Hald áfram", + "create": "Ger", + "native_segwit_title": "Besta siðvenja", + "wrapped_segwit_title": "Besta sínamillumvirkni", + "legacy_title": "Eldri", + "co_sign_transaction": "Undirrita eina flyting", + "what_is_vault": "Ein virðisgoymsla er ein", + "what_is_vault_numberOfWallets": " {m}-av-{n} fjølundirritanar", + "what_is_vault_wallet": "mappa.", + "vault_advanced_customize": "Virðisgoymslustillingar", + "needs": "Tørvur er á", + "what_is_vault_description_number_of_vault_keys": " {m} privatum lyklum,", + "what_is_vault_description_to_spend": "at brúka pening. Umframt ein triði privatur lykil \nat brúka t.d. sum trygdaravrit.", + "what_is_vault_description_to_spend_other": "at brúka pening.", + "quorum": "{m} av {n} undirritanartreyt", + "quorum_header": "Undirritanartreyt", + "of": "av", + "wallet_type": "Mappu slag", + "invalid_mnemonics": "Áminningarramsan tykist vera ógildig.", + "invalid_cosigner": "Ógildigar samundirritaradátur", + "not_a_multisignature_xpub": "XPUB hoyrur ikki til eina fjølundirritanarmappu!", + "invalid_cosigner_format": "Skeivt samundirritaraforsnið: Hetta er ikki eitt {format} samundirritaraforsnið.", + "create_new_key": "Ger nýggjan", + "scan_or_open_file": "Skanna ella innles úr fílu", + "i_have_mnemonics": "Eg havi rótorðini tilhoyrandi lykilin.", + "type_your_mnemonics": "Inntøppa rótorð fyri at innlesa ein lykil, til eina virðisgoymslu ið longu finst.", + "this_is_cosigners_xpub": "Hetta er XPUB hjá samundirritara — klárur at innlesast í aðra mappu. Hann kann ikki undirrita flytingar.", + "this_is_cosigners_xpub_airdrop": "Um tú deilir umvegis AirDrop noyðast móttakarir at verða í samskipanaruppsetanar-sýnininum.", + "wallet_key_created": "Ein privatur lykil, til virðisgoymsluna, er framleiddur. Tak tær stundir at trygdaravrita áminningarramsuna undir tryggum umstøðum.", + "are_you_sure_seed_will_be_lost": "Ert tú vís/ur? Hevur tú einki trygdaravrit, verða tíni rótorð/áminningarramsa burtur.", + "forget_this_seed": "Strika rótorðini og brúka XPUB ístaðin.", + "view_edit_cosigners": "Vís/Broyt samundirritarar", + "this_cosigner_is_already_imported": "Hesin samundirritarin er longu innlisin.", + "export_signed_psbt": "Tak undirritaða PSBT út", + "input_fp": "Inntøppa eyðkenni", + "input_fp_explain": "Leyp um og brúka forsetta eyðkenni (00000000)", + "input_path": "Inntøppa avleiðsluleið", + "input_path_explain": "Leyp um og brúka forsettu ({default})", + "ms_help": "Hjálp", + "ms_help_title": "Hvussu virka fjølundirritanarvirðisgoymslur: Frágreiðingar og ráð", + "ms_help_text": "Ein mappa við fleiri høvuðslyklum, fyri økta trygd og/ella felags varðveitslu", + "ms_help_title1": "Viðmælt er at brúka fleiri eindir.", + "ms_help_1": "Virðisgoymslan er sínamillumvirkin við appir og mappuforrit ið hava PSBT førleika, so sum BlueWallet, Electrum, Specter, Coldcard, Cobo Vault, o.s.fr.", + "ms_help_title2": "Broyta lyklar", + "ms_help_2": "Til ber at framleiða allar lyklarnar á hesi eindini, og seinni broyta ella strika teir. Um allir lyklarnir eru á somu eind er trygdin samsvarandi við eina vanliga Bitcoin mappu við einum høvuðslykli.", + "ms_help_title3": "Trygdaravrita virðisgoymslu", + "ms_help_3": "Undir mappustillingum hevur tú møgulleika at taka trygdaravrit og eygleiðingaravrit, av virðisgoymsluni, út. Trygdaravrit er alneyðugt fyri at endurinnlesa mappuna, í fall tú missur rótorð burtur.", + "ms_help_title4": "Innles virðisgoymslu", + "ms_help_4": "Hevur tú eina trygdaravritsfílu, kanst tú innlesa fleirundirritaramappuna við at brúka Innles hentleikan. Um tú einans hevur rótorð og XPUB-ar kanst tú brúka tilsvarandi Innles knøttar, undir ger av lyklum til virðisgoymsluna.", + "ms_help_title5": "Víðkaðar stillingar", + "ms_help_5": "Um annað ikki er tilskilað, ger BlueWallet eina mappu við eini 2 av 3 undirritanartreyt. Undir \"Virðisgoymslustillingar\" er møguligt at tilskila aðra undirritanartreyt og adressusløg." + }, + "is_it_my_address": { + "title": "Er adressan mín?", + "owns": "{address} er ogn hjá {label}", + "enter_address": "Inntøppa adressu", + "check_address": "Kanna adressu", + "no_wallet_owns_address": "Inntøppaða adressan var ikki funnin í tøku mappunum.", + "view_qrcode": "Vís QR kotu" + }, + "autofill_word": { + "title": "Síðsta áminningarramsu-orðið", + "enter": "Inntøppa tína áminningarramsu, tó uttan síðsta orðið", + "generate_word": "Útrokna síðsta orðið", + "error": "Inntakið er ikki ein 11 ella 23 orð lutvís áminningarramsa. Vinaliga royn umaftur." + }, + "cc": { + "change": "Vekslipeningur", + "coins_selected": "{number} myntir valdir", + "selected_summ": "{value} valt", + "empty": "Hendan mappan hevur, í løtuni, ongar myntir.", + "freeze": "Løst", + "freezeLabel": "Læs", + "freezeLabel_un": "Lat upp", + "header": "Mynt-val", + "use_coin": "Tilskila mynt", + "use_coins": "Tilskila myntir", + "tip": "Her hevur tú møgulleika at viðmerkja, læsa, og tilskila myntir at brúka í flytingini. Tú kanst velja fleiri myntir við at trýsta á farvaðu rundingarnar.", + "sort_asc": "Hækkandi", + "sort_desc": "Lækkandi", + "sort_height": "Hædd", + "sort_value": "Virði", + "sort_label": "Spjaldur", + "sort_status": "Støða", + "sort_by": "Raða eftir" + }, + "units": { + "BTC": "BTC", + "MAX": "Øll saldan", + "sat_vbyte": "sat/tBýt", + "sats": "sats" + }, + "addresses": { + "copy_private_key": "Avrita privata lykilin", + "sensitive_private_key": "Gev gætur: Privatir lyklar eru ógvuliga viðkvæmir. Ynskir tú at halda áfram?", + "sign_title": "Undirrita/Vátta boð", + "sign_help": "Her kanst tú undirrita, ella váttað eina dulmálsundirskrift, við eini Bitcoin adressu.", + "sign_sign": "Undirrita", + "sign_verify": "Vátta", + "sign_signature_correct": "Váttanin eydnaðist!", + "sign_signature_incorrect": "Váttanin miseydnaðist!", + "sign_placeholder_address": "Adressa", + "sign_placeholder_message": "Boð", + "sign_placeholder_signature": "Undirskrift", + "addresses_title": "Adressur", + "type_change": "Vekslipeningur", + "type_receive": "Inngjald", + "type_used": "Brúkt", + "transactions": "Flytingar" + }, + "lnurl_auth": { + "register_question_part_1": "Ynskir tú at stovna tær eina konto á", + "register_question_part_2": "við tíni Lightning mappu?", + "register_answer": "Tað eydnaðist at stovna tær eina konto á {hostname}!", + "login_question_part_1": "Ynskir tú at rita inn á", + "login_question_part_2": "við tíni Lightning mappu?", + "login_answer": "Tað eydnaðist tær at rita inn á {hostname}!", + "link_question_part_1": "Ynskir tú at knýta tína konto á", + "link_question_part_2": "til tína Lightning mappu?", + "link_answer": "Tað eydnaðist at knýta tína Lightning mappu at tíni konto á {hostname}!", + "auth_question_part_1": "Ynskir tú at samgildast á ", + "auth_question_part_2": "við tíni Lightning mappu?", + "auth_answer": "Tað eydnaðist at samgilda teg á {hostname}!", + "could_not_auth": "Tað eydnaðist ikki at samgilda teg á {hostname}.", + "authenticate": "Samgilda (Authenticate)" + }, + "bip47": { + "payment_code": "Gjaldkota", + "contacts": "BIP47 Móttakarar", + "bip47_explain": "Kota at deila og endurnýta", + "bip47_explain_subtitle": "BIP47", + "purpose": "Kota at deila og endurnýta (BIP47)", + "pay_this_contact": "Rinda hesum móttakaranum", + "rename_contact": "Nýnevn móttakara", + "copy_payment_code": "Avrita gjaldkotu", + "hide_contact": "Fjal móttakara", + "rename": "Nýnevn", + "provide_name": "Skriva nýggja navnið á móttakaranum", + "add_contact": "Legg móttakara afturat", + "provide_payment_code": "Inntøppa gjaldkotu", + "invalid_pc": "Ógildig gjaldkota", + "notification_tx_unconfirmed": "Fráboðanarflytingin er ikki váttað enn, vinaliga bíða", + "failed_create_notif_tx": "Miseydnaðist at gera áketuflyting", + "onchain_tx_needed": "Tørvur er á áketuflyting", + "notif_tx_sent": "Fráboðanarflytingin er send. Vinaliga bíða til hon verður váttað.", + "notif_tx": "BIP47 fráboðanarflyting", + "not_found": "Fann ikki gjaldkotu" + } +} diff --git a/loc/fr_fr.json b/loc/fr_fr.json index cbcf7cda75a..018c7962580 100644 --- a/loc/fr_fr.json +++ b/loc/fr_fr.json @@ -1,29 +1,35 @@ { "_": { - "bad_password": "Mauvais mot de passe, réessayer svp", + "bad_password": "Mot de passe incorrect. Veuillez réessayer.", "cancel": "Annuler", "continue": "Continuer", "clipboard": "Presse-papier", + "discard_changes": "Supprimer les changements ?", + "discard_changes_explain": "Certaines modifications n'ont pas été enregistrées. Êtes-vous sûr de vouloir les supprimer et quitter cet écran ?", "enter_password": "Saisir le mot de passe", "never": "Jamais", - "disabled": "Désactivé", "of": "{number} sur {total}", "ok": "OK", - "storage_is_encrypted": "L'espace de stockage est chiffré. le mot de passe est requis pour le déchiffrer.", + "enter_url": "Entrer une URL", + "storage_is_encrypted": "L'espace de stockage est chiffré. Le mot de passe est requis pour le déchiffrer.", "yes": "Oui", "no": "Non", - "save": "Enregistrer", + "save": "Enregistrer...", "seed": "Graine", "success": "Succès", "wallet_key": "Clé du portefeuille", - "invalid_animated_qr_code_fragment": "Fragment du QR Code animé invalide. Veuillez réessayer.", - "file_saved": "Le fichier {filePath} a été enregistré dans votre {destination}.", - "downloads_folder": "Dossier des téléchargements", + "close": "Fermer", + "change_input_currency": "Changer la devise d'entrée", + "refresh": "Actualiser", + "pick_image": "Copier depuis la librairie", "pick_file": "Choisir un fichier", - "enter_amount": "Entrer un montant" - }, - "alert": { - "default": "Alerte" + "enter_amount": "Entrer un montant", + "qr_custom_input_button": "Tapotez 10 fois pour accéder à une entrée personnalisée", + "unlock": "Déverrouiller", + "ssl_port": "Port SSL", + "suggested": "Suggéré", + "copied": "Copié !", + "port": "Port" }, "azteco": { "codeIs": "Votre code promo est", @@ -32,99 +38,84 @@ "redeem": "Collecter vers le portefeuille", "redeemButton": "Collecter", "success": "Succès", - "title": "Utiliser le bon Azte.com" + "title": "Utiliser le bon Azte.com", + "successMessage": "Bon utilisé avec succès ! Vos fonds devraient arriver dans votre portefeuille Bitcoin sous peu." }, "entropy": { "save": "Enregistrer", "title": "Entropie", - "undo": "Défaire" + "undo": "Défaire", + "amountOfEntropy": "{bits} de {limit} bits" }, "errors": { - "broadcast": "La diffusion a échouée.", + "broadcast": "La diffusion a échoué.", "error": "Erreur", "network": "Erreur réseau" }, "lnd": { - "active": "Actif", - "inactive": "Inactif", - "channels": "Canaux", - "no_channels": "Pas de canaux", - "claim_balance": "Réclamer le solde {balance}", - "close_channel": "Fermer le canal", - "new_channel": "Nouveau canal", - "errorInvoiceExpired": "Requête expirée", - "force_close_channel": "Forcer la fermeture du canal ?", + "errorInvoiceExpired": "Requête expirée.", "expired": "Expiré", - "node_alias": "Alias du node", "expiresIn": "Expire dans {time} minutes", "payButton": "Payer", + "payment": "Paiement", "placeholder": "Facture ou adresse", - "open_channel": "Ouverture canal", - "funding_amount_placeholder": "Montant financement, par exemple 0,001", - "opening_channnel_for_from": "Canal d'ouverture pour le portefeuille {forWalletLabel}, financement depuis {fromWalletLabel}", - "are_you_sure_open_channel": "Etes vous sûre de vouloir ouvrir ce canal?", "potentialFee": "Frais potentiels : {fee}", - "remote_host": "Hôte distant", "refill": "Déposer des fonds", - "reconnect_peer": "Reconnecter le pair", "refill_create": "Pour continuer, veuillez créer un portefeuille Bitcoin à partir duquel déposer des fonds.", "refill_external": "Déposer des fonds depuis un portefeuille externe", - "refill_lnd_balance": "Déposer des fonds dans votre portfeuille Lightning", - "sameWalletAsInvoiceError": "Vous ne pouvez pas payer une facture avec le même porte-feuille qui la crée", - "title": "gérer vos fonds", - "can_send": "Peut envoyer", - "can_receive": "Peut recevoir", - "view_logs": "Voir les logs" + "refill_lnd_balance": "Déposer des fonds dans votre portefeuille Lightning", + "sameWalletAsInvoiceError": "Vous ne pouvez pas payer une facture avec le même portefeuille utilisé pour la créer.", + "title": "Gérer vos fonds" }, "lndViewInvoice": { "additional_info": "Informations complémentaires", - "for": "A :", - "lightning_invoice": "Facture Ligthning", - "open_direct_channel": "Ouvrir un canal direct avec ce noeud :", + "for": "À :", + "lightning_invoice": "Facture Lightning", "please_pay_between_and": "Veuillez payer entre {min} et {max}", "please_pay": "Veuillez payer", "preimage": "Préimage", "sats": "sats", + "date_time": "Date et Heure", "wasnt_paid_and_expired": "Cette requête n'a pas été réglée et a expiré" }, "plausibledeniability": { "create_fake_storage": "Créer un faux espace de stockage chiffré", - "create_password": "Créer un mot de passe", "create_password_explanation": "Le mot de passe pour le faux espace de stockage ne doit pas être le même que celui du stockage principal", "help": "Dans certaines circonstances, vous serez peut-être forcé par un tiers à communiquer votre mot de passe. Pour protéger vos biens, BlueWallet permet de créer un autre espace de stockage, avec un mot de passe différent. Sous la contrainte, vous pourrez divulger ce mot de passe au tiers. Quand il est saisi, BlueWallet débloquera ce 'faux' espace de stockage. Le tiers pourra confondre ces données avec des données légitimes, et votre espace de stockage principal restera ainsi sécurisé et hors d'atteinte.", "help2": "Le nouvel espace de stockage sera totalement fonctionnel, et vous pouvez même y stocker de petits montants pour le rendre plus crédible.", "password_should_not_match": "Le mot de passe pour le faux espace de stockage ne doit pas être le même que celui du stockage principal", - "passwords_do_not_match": "Vos mot de passe ne sont pas identiques, veillez ré-essayer", - "retype_password": "Confirmation du mot de passe", - "success": "Succès", "title": "Déni plausible" }, "pleasebackup": { - "ask": "Avez-vous noté la phrase de sauvegarde de votre portefeuille ? Cette phrase de sauvegarde est nécessaire pour accéder à vos fonds en cas de perte de votre appareil. Sans ce backup, vos fonds pourraient être perdus pour toujours.", - "ask_no": "Non", - "ask_yes": "Oui", - "ok": "OK, je l'ai notée !", + "ask": "Avez-vous noté la phrase de sauvegarde de votre portefeuille ? Cette phrase de sauvegarde est nécessaire pour accéder à vos fonds en cas de perte de votre appareil. Sans cette sauvegarde, vos fonds pourraient être perdus pour toujours.", + "ask_no": "Non.", + "ask_yes": "Oui.", + "ok": "OK, je l'ai écrite sur un papier.", "ok_lnd": "OK, je l'ai sauvegardée.", - "text": "Veuillez prendre un moment pour noter cette phrase de sauvegarde sur papier.\nC'est votre backup et vous pouvez l'utiliser pour restaurer votre portefeuille.", + "text": "Veuillez prendre un moment pour noter cette phrase de sauvegarde sur papier.\nC'est votre sauvegarde et vous pouvez l'utiliser pour restaurer votre portefeuille.", "text_lnd": "Veuillez sauvegarder cette sauvegarde de portefeuille. Elle vous permettra de restaurer le portefeuille en cas de perte.", - "title": "Votre portefeuille a été créé" + "title": "Votre portefeuille est créé" }, "receive": { "details_create": "Créer", - "details_label": "Description", "details_setAmount": "Recevoir avec un montant spécifique", "details_share": "partager", "header": "Recevoir", + "reset": "Réinitialiser", "maxSats": "Le montant maximal est de {max} sats", "maxSatsFull": "Le montant maximal est de {max} sats ou {currency}", - "minSats": "Le montant minimalest de {min} sats", - "minSatsFull": "Le montant minimal est de {min} sats ou {currency}" + "minSats": "Le montant minimal est de {min} sats", + "minSatsFull": "Le montant minimal est de {min} sats ou {currency}", + "qrcode_for_the_address": "Code QR pour l'adresse", + "bip47_explanation": "Les codes de paiement sont une adresse universelle qui évite de divulguer vos adresses de portefeuille. Tous les services ne les prennent pas en charge.", + "address_not_found": "Impossible de générer l'adresse de réception.", + "details_label": "Description" }, "send": { "provided_address_is_invoice": "Cette adresse semble correspondre à une facture Lightning. Veuillez utiliser votre portefeuille Lightning pour payer cette facture.", - "broadcastButton": "DIFFUSER", + "broadcastButton": "Diffuser", "broadcastError": "erreur", - "broadcastNone": "Hash de l'input de la transaction", + "broadcastNone": "Insérez la transaction au format hexadécimal", "broadcastPending": "en cours", "broadcastSuccess": "succès", "confirm_header": "Confirmer", @@ -134,20 +125,27 @@ "create_copy": "Copier et diffuser plus tard", "create_details": "Détails", "create_fee": "Frais", - "create_memo": "Memo", + "create_memo": "Mémo", "create_satoshi_per_vbyte": "Satoshi par vByte", "create_this_is_hex": "Ceci est votre transaction au format hexadécimal, signée et prête à être diffusée sur le réseau.", "create_to": "À", "create_tx_size": "Taille de la Transaction (TX size)", "create_verify": "Vérifier sur coinb.in", + "details_insert_contact": "Insérez le contact", "details_add_rec_add": "Ajouter un destinataire", "details_add_rec_rem": "Retirer un destinataire", + "details_add_recc_rem_all_alert_description": "Êtes-vous sûr de vouloir supprimer tous les destinataires ?", + "details_add_rec_rem_all": "Effacer tous les destinataires", + "details_recipients_title": "Destinataires", + "details_recipient_title": "Destinataire n° {number} sur {total}", + "please_complete_recipient_title": "Destinataire incomplet", + "please_complete_recipient_details": "Veuillez compléter les détails du destinataire #{number} avant d'ajouter un nouveau destinataire.", "details_address": "adresse", "details_address_field_is_not_valid": "Champ adresse invalide", - "details_adv_fee_bump": "Autoriser le Fee Bum", + "details_adv_fee_bump": "Autoriser le Fee Bump", "details_adv_full": "Envoyer tout le solde", - "details_adv_full_sure": "Etes-vous sûr de vouloir utiliser la totalité de votre solde pour cette transaction ?", - "details_adv_full_sure_frozen": "Etes-vous sûre que vous voulez utiliser la totalité de votre portefeuille sur cette transaction? Veuillez noter que les pièces gelées sont exclus.", + "details_adv_full_sure": "Êtes-vous sûr de vouloir utiliser la totalité de votre solde pour cette transaction ?", + "details_adv_full_sure_frozen": "Êtes-vous sûr de vouloir utiliser la totalité de votre portefeuille pour cette transaction ? Veuillez noter que les pièces gelées sont exclues.", "details_adv_import": "Importer une transaction", "details_adv_import_qr": "Importer Transaction (QR)", "details_amount_field_is_not_valid": "Champ montant invalide", @@ -155,16 +153,16 @@ "details_create": "Créer la requête", "details_error_decode": "Impossible de décoder l'adresse bitcoin", "details_fee_field_is_not_valid": "Champ frais invalide", - "details_frozen": "{amount} BTC est gelé", + "details_frozen": "{amount} BTC est gelé.", "details_next": "Suivant", "details_no_signed_tx": "Le fichier sélectionné ne contient pas de transaction pouvant être importée.", - "details_note_placeholder": "note à moi même (optionnelle)", + "details_note_placeholder": "note à moi-même (optionnelle)", "details_scan": "Scanner", "details_scan_hint": "tapez deux fois pour scanner ou importer une destination", "details_total_exceeds_balance": "Le montant à envoyer excède le montant disponible.", "details_total_exceeds_balance_frozen": "Le montant d'envoi excède le montant disponible. Veuillez noter que les pièces gelées sont exclues.", "details_unrecognized_file_format": "Format fichier inconnu", - "details_wallet_before_tx": "Avant de créer une transaction, vous devez d'abord importer un ajouter un portefeuille Bitcoin.", + "details_wallet_before_tx": "Avant de créer une transaction, vous devez d'abord importer ou ajouter un portefeuille Bitcoin.", "dynamic_init": "Initialisation", "dynamic_next": "Suivant", "dynamic_prev": "Précédent", @@ -172,11 +170,11 @@ "dynamic_stop": "Arrêter", "fee_10m": "10min", "fee_1d": "1j", - "fee_3h": "3h", - "fee_custom": "Personnalisé ", + "fee_custom": "Personnalisé", + "insert_custom_fee": "Insérez frais", "fee_fast": "Rapide", "fee_medium": "Moyen", - "fee_replace_minvb": "Le taux de frais total (satoshi par vbyte) que vous voulez payer devrais être plus grand que {min} sat/vbyte.", + "fee_replace_minvb": "Le taux de frais total (satoshi par vbyte) que vous voulez payer devrait être plus grand que {min} sat/vbyte.", "fee_satvbyte": "en sat/vByte", "fee_slow": "Lent", "header": "Envoyer", @@ -186,227 +184,272 @@ "input_total": "Total :", "permission_camera_message": "Nous avons besoin de votre permission pour utiliser l'appareil photo", "psbt_sign": "Signer une transaction", - "open_settings": "Ouvrir les paramètres", - "permission_storage_later": "Redemander Plus Tard", - "permission_storage_message": "BlueWallet a besoin de votre permission pour accéder a votre stockage pour enregistrer ce fichier.", - "permission_storage_denied_message": "BlueWallet est incapable d'enregistrer ce fichier. Veuillez ouvrir les paramètres de votre appareil et activer les autorisation de stockage.", + "invalid_psbt": "PSBT fourni non valide.", + "open_settings": "Ouvrir les Paramètres", + "permission_storage_denied_message": "BlueWallet est incapable d'enregistrer ce fichier. Veuillez ouvrir les paramètres de votre appareil et activer les autorisations de stockage.", "permission_storage_title": "Permission d'accès au stockage pour BlueWallet", "psbt_clipboard": "Copier dans le presse-papier", "psbt_this_is_psbt": "Ceci est une Transaction Bitcoin Partiellement Signée (PSBT). Veuillez la compléter avec votre portefeuille matériel.", "psbt_tx_export": "Exporter sous forme de fichier", "no_tx_signing_in_progress": "Il n'y a pas de signature de transaction en cours.", - "outdated_rate": "Les taux ont été mis a jour: {date}", + "outdated_rate": "Les taux ont été mis à jour : {date}", "psbt_tx_open": "Ouvrir la transaction signée", "psbt_tx_scan": "Scanner la transaction signée", - "qr_error_no_qrcode": "Impossible de trouver un QR code dans l'image sélectionnée. Assurez-vous que l'image contienne uniquement un QR code et pas de contenu additionnel comme du texte ou des boutons.", + "qr_error_no_qrcode": "Nous n'avons pas pu trouver de code QR valide dans l'image sélectionnée. Assurez-vous que l'image ne contient qu'un code QR et aucun contenu supplémentaire tel que du texte ou des boutons.", "reset_amount": "Annuler le montant", - "reset_amount_confirm": "Voulez vous réinitialiser le montant ?", + "reset_amount_confirm": "Voulez-vous réinitialiser le montant ?", "success_done": "Terminé", - "txSaved": "Le fichier de transaction ({filePath}) a été enregistré dans votre dossier Téléchargements.", - "problem_with_psbt": "Problème avec PSBT" + "txSaved": "Le fichier de transaction ({filePath}) a été sauvegardé.", + "file_saved_at_path": "Le fichier ({filePath}) a été sauvegardé.", + "cant_send_to_silentpayment_adress": "Ce portefeuille ne peut pas envoyer à des adresses SilentPayment", + "cant_send_to_bip47": "Ce portefeuille ne peut pas envoyer à des codes de paiement BIP47", + "cant_find_bip47_notification": "Ajoutez d'abord ce code de paiement au contact", + "problem_with_psbt": "Problème avec PSBT", + "details_scan_error": "Erreur de scan", + "fee_3h": "3h" }, "settings": { "about": "À propos", "about_awesome": "Créé avec les incroyables", "about_backup": "Sauvegardez toujours vos clés !", "about_free": "BlueWallet est un projet gratuit et ouvert. Réalisé par des utilisateurs de Bitcoin.", - "about_license": "License MIT", + "about_license": "Licence MIT", "about_release_notes": "Notes de version", "about_review": "Laissez-nous votre avis", + "performance_score": "Score performance: {num}", + "run_performance_test": "Test de performance", "about_selftest": "Effectuer un auto-test", + "block_explorer_invalid_custom_url": "L'URL fournie n'est pas valide. Veuillez saisir une URL valide commençant par http:// ou https://.", "about_selftest_electrum_disabled": "L'auto-test n'est pas disponible avec le mode hors ligne Electrum. Veuillez désactiver le mode hors connexion et réessayer.", - "about_selftest_ok": "Tous les tests internes ont été passés avec succès. Le portefeuille fonctionne parfaitement. ", + "about_selftest_ok": "Tous les tests internes ont été passés avec succès. Le portefeuille fonctionne parfaitement.", "about_sm_github": "GitHub", - "about_sm_discord": "Serveur discord", "about_sm_telegram": "Chaîne Telegram", - "about_sm_twitter": "Nous suivre sur Twitter", - "advanced_options": "Options avancées", "biometrics": "Biométrie", - "biom_10times": "Vous avez tenter d'entrer votre mots de passe 10 fois. Voulez vous réinitialiser votre espace de stockage ? Ceci effacera tous les portefeuilles et déchiffrera votre espace de stockage.", + "biometrics_no_longer_available": "Les paramètres de votre appareil ont changé et ne correspondent plus aux paramètres de sécurité sélectionnés dans l'application. Veuillez réactiver la biométrie ou le mot de passe, puis redémarrer l'application pour appliquer ces modifications.", + "biom_10times": "Vous avez tenté d'entrer votre mot de passe 10 fois. Voulez-vous réinitialiser votre espace de stockage ? Ceci effacera tous les portefeuilles et déchiffrera votre espace de stockage.", "biom_conf_identity": "Veuillez confirmer votre identité.", - "biom_no_passcode": "Votre appareil n'as pas de code de passe. Pour continuer veuillez configurer un code de passe dans les paramètres.", - "biom_remove_decrypt": "Tous vos portefeuilles seront effacés et votre espace de stockage sera déchiffré. Etes vous sure de vouloir continuer ?", + "biom_no_passcode": "Votre appareil ne dispose pas d'un code d'accès ou de données biométriques activées. Pour continuer, veuillez configurer un code d'accès ou des données biométriques dans l'application Paramètres.", + "biom_remove_decrypt": "Tous vos portefeuilles seront effacés et votre espace de stockage sera déchiffré. Êtes-vous sûr de vouloir continuer ?", "currency": "Devise", - "currency_source": "Le prix est obtenu de", - "currency_fetch_error": "Il y a eu une erreur en essayant d'obteir le taux de la monnaie sélectionnée.", - "default_desc": "Si désactivé, BlueWallet ouvrira immédiatement le portefeuille sélectionné au lancement.", - "default_info": "Information par défaut", + "currency_source": "Taux obtenu de", + "currency_fetch_error": "Il y a eu une erreur en essayant d'obtenir le taux de la monnaie sélectionnée.", "default_title": "Au lancement", - "default_wallets": "Voir tous les portefeuilles", "electrum_connected": "Connecté", "electrum_connected_not": "Déconnecté", "electrum_error_connect": "Impossible de se connecter au serveur Electrum fourni", + "electrum_error_connect_tor": "Impossible de se connecter au serveur Electrum fourni. Veuillez vérifier que l'application Orbot est connectée et réessayer.", "lndhub_uri": "Par exemple, {example}", "electrum_host": "Par exemple, {example}", "electrum_offline_mode": "Mode hors-ligne", "electrum_offline_description": "Si activé, vos portefeuilles Bitcoin ne tenteront pas de récupérer des soldes ou des transactions.", "electrum_port": "Port, en général {example}", "use_ssl": "Utiliser SSL", - "electrum_saved": "Les changements ont bien été enregistrés. Un redémarrage sera peut-être nécessaires pour qu'ils prennent effet.", - "set_electrum_server_as_default": "Mettre {server} come serveur electrum par défaut ?", - "set_lndhub_as_default": "Mettre {url} comme serveur LNDHub par défaut?", + "electrum_saved": "Les changements ont bien été enregistrés. Un redémarrage sera peut-être nécessaire pour qu'ils prennent effet.", + "set_electrum_server_as_default": "Définir {server} comme serveur Electrum par défaut ?", + "set_lndhub_as_default": "Définir {url} comme serveur LNDhub par défaut ? ", "electrum_settings_server": "Serveur Electrum", - "electrum_settings_explain": "Laisser blanc pour utiliser l'option par défaut", "electrum_status": "Statut", - "electrum_clear_alert_title": "Effacer l'historique?", - "electrum_clear_alert_message": "Voulez vous effacer l'historique des serveurs electrum?", - "electrum_clear_alert_cancel": "Annuler", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Selectionner", - "electrum_reset": "Réinitialiser au valeurs par défaut", + "electrum_preferred_server": "Serveur préféré", + "electrum_preferred_server_description": "Saisissez le serveur que vous souhaitez que votre portefeuille utilise pour toutes les activités Bitcoin. Une fois défini, votre portefeuille utilisera exclusivement ce serveur pour vérifier les soldes, envoyer des transactions et récupérer les données du réseau. Assurez-vous de faire confiance à ce serveur avant de le configurer. ", "electrum_unable_to_connect": "Impossible de se connecter à {server}.", - "electrum_history": "Historique de serveur", - "electrum_reset_to_default": "Etes-vous sûr de vouloir réinitialiser vos paramètres Electrum ?", - "electrum_clear": "Effacer", - "tor_supported": "Tor supporté", - "tor_unsupported": "Les connexions à Tor ne sont pas prises en charge.", + "electrum_history": "Historique", + "electrum_reset_to_default": "Cela permettra à BlueWallet de choisir au hasard un serveur dans la liste des serveurs.", + "electrum_reset": "Réinitialiser aux valeurs par défaut", + "electrum_reset_to_default_and_clear_history": "Réinitialiser les paramètres par défaut et effacer l'historique", "encrypt_decrypt": "Déchiffrer le stockage", - "encrypt_decrypt_q": "Etes-vous sûr de vouloir déchiffrer le stockage ? L'accès à vos portefeuilles pourra alors se faire sans mot de passe.", - "encrypt_enc_and_pass": "Chiffré et protégé par un mot de passe", + "encrypt_decrypt_q": "Êtes-vous sûr de vouloir déchiffrer le stockage ? L'accès à vos portefeuilles pourra alors se faire sans mot de passe.", + "encrypt_storage_explanation_headline": "Activer le chiffrement du stockage", + "encrypt_storage_explanation_description_line1": "L'activation du chiffrement du stockage ajoute une couche de protection supplémentaire à votre application en sécurisant la manière dont vos données sont stockées sur votre appareil. Il est ainsi plus difficile pour quiconque d'accéder à vos informations sans autorisation. ", + "encrypt_storage_explanation_description_line2": "Cependant, il est important de savoir que ce cryptage ne protège que l'accès à vos portefeuilles stockés sur le trousseau de clés de l'appareil. Il n'ajoute pas de mot de passe ni de protection supplémentaire aux portefeuilles eux-mêmes. ", + "i_understand": "J'ai compris", + "block_explorer": "Explorateur de blocs", + "block_explorer_preferred": "Utiliser l'explorateur de blocs préféré", + "block_explorer_error_saving_custom": "Erreur lors de la sauvegarde de l'explorateur de blocs préféré", "encrypt_title": "Sécurité", - "encrypt_tstorage": "stockage", + "encrypt_tstorage": "Stockage", "encrypt_use": "Utiliser {type}", - "encrypt_use_expl": "{type} sera utilisé pour confirmer votre identité avant d'effectuer une transaction, de déverrouiller, d'exporter ou de supprimer un portefeuille. {type} ne sera pas utilisé pour déverrouiller le stockage chiffré.", + "set_as_preferred": "Définir comme préféré ", + "set_as_preferred_electrum": "Définir {host}:{port} comme serveur préféré désactivera la connexion aléatoire à un serveur suggéré.", + "encrypted_feature_disabled": "Cette fonctionnalité ne peut pas être utilisée avec le stockage crypté activé.", + "biometrics_fail": "Si {type} n'est pas activé ou ne parvient pas à se déverrouiller, vous pouvez utiliser le code d'accès de votre appareil comme alternative.", "general": "Général", - "general_adv_mode": "Mode avancé", - "general_adv_mode_e": "Si activé, vous aurez accès aux options avancées telles que la sélection parmi différents types de portefeuilles, la possibilité de spécifier une instance LNDHub de votre choix, et la création manuelle d'entropie lors de la création de portefeuille.", "general_continuity": "Continuité", "general_continuity_e": "Si activé, vous pourrez visualiser les portefeuilles sélectionnés ainsi que leurs transactions depuis vos autres appareils Apple iCloud connectés.", "groundcontrol_explanation": "GroundControl est un serveur de notifications push pour les portefeuilles Bitcoin gratuit et open source. Vous pouvez installer votre propre serveur GroundControl et insérer ici son URL pour ne pas reposer sur l'infrastructure de BlueWallet. Laissez vide pour conserver l'option par défaut.", - "header": "réglages", + "header": "Réglages", "language": "Langue", "last_updated": "Dernière mise à jour", "language_isRTL": "Il est nécessaire de redémarrer BlueWallet pour que le changement de langue prenne effet.", - "lightning_error_lndhub_uri": "LNDHub URI invalide", + "license": "Licence", + "lightning_error_lndhub_uri": "URI LNDhub invalide", + "lightning_error_lndhub_uri_tor": "URI LNDhub non valide. Veuillez vous assurer que l'application Orbot est connectée et réessayer.", "lightning_saved": "Les changements ont bien été enregistrés", "lightning_settings": "Paramètres Lightning", - "tor_settings": "Paramètres Tor", - "lightning_settings_explain": "Pour connecter votre propre noeud LND, veuillez installer LNDHub et insérez l'URL ici dans les paramètres. Veillez noter que seul les porte-feuilles crées après avoir sauvegardé les changements se connecteront au LNDHub spécifié. ", + "lightning_settings_explain": "Pour vous connecter à votre propre nœud LND, veuillez installer LNDhub et mettre son URL ici dans les paramètres. Veuillez noter que seuls les portefeuilles créés après avoir enregistré les modifications se connecteront au LNDhub spécifié.", "network": "Réseau", "network_broadcast": "Diffuser une transaction", "network_electrum": "Serveur Electrum", + "electrum_suggested_description": "Lorsqu'aucun serveur préféré n'est défini, un serveur suggéré sera sélectionné pour être utilisé au hasard.", "not_a_valid_uri": "URI invalide", - "notifications": "Notifications", "open_link_in_explorer": "Ouvrir le lien dans l'explorateur", "password": "Mot de passe", - "password_explain": "Créer le mot de passe utilisé pour déchiffrer l'espace de stockage principal", - "passwords_do_not_match": "Les mots de passe ne correspondent pas", + "password_explain": "Entrez le mot de passe que vous utiliserez pour déverrouiller votre stockage.", "plausible_deniability": "Déni plausible...", "privacy": "Vie privée", - "privacy_read_clipboard": "Lecture du presse-papier ", - "privacy_system_settings": "Paramètres système", + "privacy_read_clipboard": "Lecture du presse-papier", + "privacy_system_settings": "Paramètres Système", "privacy_quickactions": "Raccourci Portefeuille", "privacy_quickactions_explanation": "Touchez et maintenez l'icone BlueWallet sur votre écran d'accueil pour voir rapidement le solde de vos portefeuilles.", - "privacy_clipboard_explanation": "Fourni un raccourci si une adresse ou une facture est trouvée dans le presse-papier.", + "privacy_clipboard_explanation": "Fournit un raccourci si une adresse ou une facture est trouvée dans le presse-papier.", "privacy_do_not_track": "Désactiver l'analyse des données", "privacy_do_not_track_explanation": "Les informations de performance et de fiabilité ne seront pas soumises pour analyse.", - "push_notifications": "Notifications push", "rate": "Taux", - "retype_password": "Re-saisir votre mot de passe", + "push_notifications_explanation": "En activant les notifications, le jeton de votre appareil sera envoyé au serveur, ainsi que les adresses de portefeuille et les identifiants de transaction pour tous les portefeuilles et transactions effectuées après l'activation des notifications. Le jeton de l'appareil est utilisé pour envoyer des notifications, et les informations du portefeuille nous permettent de vous informer des Bitcoins entrants ou des confirmations de transaction.\n\nSeules les informations à partir du moment où vous avez activé les notifications sont transmises ; rien d'avant n'est collecté.\n\nLa désactivation des notifications supprimera toutes ces informations du serveur. De plus, la suppression d'un portefeuille de l'application supprimera également les informations associées du serveur.", "selfTest": "Auto-test", "save": "Enregistrer", "saved": "Enregistré", - "success_transaction_broadcasted": "Succès! Votre transaction a été diffusée!", + "success_transaction_broadcasted": "Succès ! Votre transaction a été diffusée !", "total_balance": "Solde total", "total_balance_explanation": "Afficher le solde total de tous vos portefeuilles sur l'écran d'accueil.", - "widgets": "Widgets", - "tools": "Outils" + "tools": "Outils", + "privacy_temporary_screenshots": "Autoriser la capture d'écran", + "privacy_temporary_screenshots_instructions": "La protection contre la capture d'écran sera temporairement désactivée, permettant les captures et les enregistrements d'écran. La protection sera automatiquement réactivée lorsque vous fermerez et rouvrirez BlueWallet.", + "donate": "Faire un don", + "donate_description": "Aidez-nous à garder Blue gratuit !", + "encrypt_enc_and_pass": "Protégé par mot de passe", + "encrypt_use_expl": "{type} sera utilisé pour confirmer votre identité avant d'effectuer une transaction, de déverrouiller, d'exporter ou de supprimer un portefeuille.", + "lndhub_github": "Dépôt GitHub", + "notifications": "Notifications", + "widgets": "Widgets" }, "notifications": { - "would_you_like_to_receive_notifications": "Voulez vous recevoir les notifications quand vous recevez des paiements entrants ?", - "no_and_dont_ask": "Non, et ne pas me redemander ", - "ask_me_later": "Me demander plus tard" + "would_you_like_to_receive_notifications": "Voulez-vous recevoir les notifications quand vous recevez des paiements entrants ?", + "notifications_subtitle": "Paiements entrants et confirmations de transactions", + "no_and_dont_ask": "Non, et ne plus me redemander.", + "permission_denied_message": "Vous avez refusé l'autorisation de vous envoyer des notifications. Si vous souhaitez recevoir des notifications, veuillez les activer dans les paramètres de votre appareil." }, "transactions": { "cancel_explain": "Nous allons remplacer cette transaction par celle où les fonds vous reviennent, avec de plus hauts frais. Cela annulera la transaction. On parle de RBF - Replace By Fee.", "cancel_no": "Cette transaction n'est pas remplaçable", "cancel_title": "Annuler cette transaction (RBF)", - "confirmations_lowercase": "{confirmations} confirmations", - "copy_link": "Copier le lien", + "transaction_loading_error": "Un problème est survenu lors du chargement de la transaction. Veuillez réessayer plus tard.", + "transaction_not_available": "Transaction indisponible", "expand_note": "Développer la note", "cpfp_create": "Créer", "cpfp_exp": "Nous allons créer une autre transaction qui dépense votre transaction non-confirmée. Les frais totaux seront supérieurs aux frais de la transaction originale, donc cela devrait être miné plus rapidement. On parle de CPFP - Child Pays For Parent.", "cpfp_no_bump": "Cette transaction n'est pas propulsable", - "cpfp_title": "Frais de propulsion (CPFP)", + "cpfp_title": "Augmenter les frais (CPFP)", "details_balance_hide": "Cacher le solde", "details_balance_show": "Montrer le solde", - "details_block": "Hauteur de bloc", "details_copy": "Copier", - "details_copy_amount": "Copier le montant", "details_copy_block_explorer_link": "Copier le lien Block Explorer", "details_copy_note": "Copier la note", "details_copy_txid": "Copier l'ID de transaction", - "details_from": "De", - "details_inputs": "Inputs", - "details_outputs": "Outputs", + "details_inputs": "Entrées", + "details_outputs": "Sorties", "details_received": "Reçu", - "transaction_note_saved": "La note de transaction a été enregistrée avec succès.", - "details_show_in_block_explorer": "Afficher dans le \"block explorer\"", - "details_title": "Transaction", + "details_view_in_browser": "Voir dans le navigateur", + "incoming_transaction": "Transaction entrante", + "outgoing_transaction": "Transaction sortante", + "expired_transaction": "Transaction expirée", + "pending_transaction": "Transaction en attente", + "offchain": "Offchain", + "onchain": "Onchain", "details_to": "À", - "enable_offline_signing": "Ce portefeuille n'est pas utilisé en conjonction avec une signature hors ligne. Voulez-vous l'activer maintenant ? ", - "list_conf": "Conf: {number}", + "enable_offline_signing": "Ce portefeuille n'est pas utilisé en conjonction avec une signature hors ligne. Voulez-vous l'activer maintenant ?", + "list_conf": "Conf. : {number}", "pending": "En attente", "pending_with_amount": "En attente {amt1} ({amt2})", "received_with_amount": "+{amt1} ({amt2})", - "eta_10m": "ETA: Dans ~10 minutes", - "eta_3h": "ETA: Dans ~3 heures", - "eta_1d": "ETA: Dans ~1 jour", - "view_wallet": "Voir {walletLabel}", + "eta_10m": "ETA : Dans ~10 minutes", + "eta_3h": "ETA : Dans ~3 heures", + "eta_1d": "ETA : Dans ~1 jour", "list_title": "transactions", + "list_title_received": "Reçu", "open_url_error": "Incapable d'ouvrir le lien avec l'explorateur par défaut. Veuillez changer votre explorateur par défaut et réessayer.", "rbf_explain": "Nous allons remplacer cette transaction par celle avec des frais plus élevés. Elle devrait donc être minée plus vite. On parle de RBF - Replace By Fee.", - "rbf_title": "Frais de propulsion (RBF)", - "status_bump": "Frais de propulsion", + "rbf_title": "Augmenter les frais (RBF)", + "status_bump": "Augmenter les frais", "status_cancel": "Annuler la transaction", "transactions_count": "Nombre de transactions", "txid": "ID de transaction", - "updating": "Chargement..." + "updating": "Chargement...", + "watchOnlyWarningTitle": "Avertissement de sécurité", + "watchOnlyWarningDescription": "Méfiez-vous des fraudeurs qui utilisent souvent des portefeuilles « lecture seule » pour tromper les utilisateurs. Ces portefeuilles ne vous permettent pas de contrôler ou d'envoyer des fonds ; ils vous permettent uniquement de consulter le solde.", + "list_title_sent": "Envoyé", + "custom_fee_warning_title": "Avertissement", + "custom_fee_warning_description": "Les frais inférieurs à 1 sat/vB sont valides, mais peuvent ne pas être relayés en raison des politiques des nœuds.", + "details_eta_analyzing": "Analyse en cours...", + "details_sent": "Envoyé", + "details_section": "Détails", + "details_explorer": "explorateur", + "details_network_fee": "Frais de réseau", + "details_to_address": "À", + "details_add_note": "ajouter", + "details_advanced": "Avancé", + "details_fee_rate": "Taux de frais", + "details_size": "Taille", + "details_virtual_size": "Taille virtuelle", + "details_tx_hex": "Hex de la transaction", + "details_inputs_count": "Entrées ({count})", + "details_outputs_count": "Sorties ({count})", + "confirmations_lowercase": "{confirmations} confirmations", + "date": "Date", + "details_title": "Transaction", + "transaction": "Transaction", + "details_id": "ID", + "details_note": "Note" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Portefeuille Bitcoin simple et puissant", "add_create": "Créer", + "total_balance": "Solde total", + "add_entropy_reset_title": "Réinitialiser l'entropie", + "add_entropy_reset_message": "Changer le type de portefeuille réinitialisera l’entropie actuelle. Voulez-vous continuer ?", + "add_entropy": "Entropie", + "add_entropy_bytes": "{bytes} octets d'entropie", "add_entropy_generated": "{gen} octets d'entropie générée", "add_entropy_provide": "Créer de l'entropie par des jets de dé", "add_entropy_remain": "{gen} octets d'entropie générée. Les {rem} octets restants seront obtenus auprès du générateur de nombres aléatoires du système.", "add_import_wallet": "Importer un portefeuille", "add_lightning": "Lightning", "add_lightning_explain": "Pour payer avec des transactions instantanées", - "add_lndhub": "Connexion à votre LNDHub", - "add_lndhub_error": "L'adresse de nœud fournie est un nœud LNDHub non valide.", + "add_lndhub": "Connectez-vous à votre LNDhub", + "add_lndhub_error": "L'adresse de nœud fournie est un nœud LNDhub non valide.", "add_lndhub_placeholder": "l'adresse de votre noeud", "add_placeholder": "mon premier portefeuille", - "add_title": "ajouter un portefeuille", - "add_wallet_name": "nom du portefeuille", + "add_title": "Ajouter Portefeuille", + "add_wallet_name": "nom", "add_wallet_type": "type", - "balance": "Solde", - "clipboard_bitcoin": "Vous avez une adresse bitcoin dans votre presse-papier. Voulez vous l'utiliser pour une transaction ?", - "clipboard_lightning": "Vous avez une facture Ligthning dans votre presse-papier. Voulez vous l'utiliser pour une transaction ?", + "add_wallet_seed_length": "Longueur de graine", + "add_wallet_seed_length_12": "12 mots", + "add_wallet_seed_length_24": "24 mots", + "clipboard_bitcoin": "Vous avez une adresse Bitcoin dans votre presse-papier. Voulez-vous l'utiliser pour une transaction ?", + "clipboard_lightning": "Vous avez une facture Lightning dans votre presse-papier. Voulez-vous l'utiliser pour une transaction ?", + "clear_clipboard_on_import": "Effacer le presse-papiers lors de l'importation", "details_address": "Adresse", "details_advanced": "Avancé", - "details_are_you_sure": "Êtes vous sur?", + "details_are_you_sure": "Êtes-vous sûr ?", "details_connected_to": "Connecté à", - "details_del_wb_err": "Le solde ne correspond pas à celui du portefeuille. Veuillez réessayer", + "details_del_wb_err": "Le solde ne correspond pas à celui du portefeuille. Veuillez réessayer.", "details_del_wb_q": "Ce portefeuille a un solde non nul. Avant de continuer, veuillez noter que vous ne serez pas en mesure de récupérer vos fonds sans la phrase mnémonique du portefeuille. Pour éviter toute suppression accidentelle du portefeuille, veuillez entrer son solde de {balance} satoshis.", "details_delete": "Supprimer", "details_delete_wallet": "Supprimer le portefeuille", "details_derivation_path": "chemin de dérivation", - "details_display": "Afficher dans la list des portefeuilles", + "details_display": "Afficher sur l'écran d'accueil", "details_export_backup": "Exporter / sauvegarder", - "details_master_fingerprint": "Empreinte maitresse", + "details_export_history": "Exporter l'historique au format CSV", + "details_master_fingerprint": "Empreinte maîtresse", "details_multisig_type": "multisig", - "details_no_cancel": "Non, annuler", - "details_save": "Enregistrer", "details_show_xpub": "Afficher XPUB du portefeuille", "details_show_addresses": "Montrer les adresses", "details_title": "Portefeuille", - "details_type": "Type", + "wallets": "Portefeuilles", "details_use_with_hardware_wallet": "Utiliser avec un portefeuille matériel", - "details_wallet_updated": "Portefeuille mis à jour", "details_yes_delete": "Oui, supprimer", - "enter_bip38_password": "Entrez le mots de passe de déchiffrement", - "export_title": "export du portefeuille", + "enter_bip38_password": "Entrez le mot de passe de déchiffrement", + "export_title": "Export du portefeuille", "import_do_import": "Importer", "import_passphrase": "Phrase secrète", "import_passphrase_title": "Phrase secrète", @@ -414,78 +457,110 @@ "import_error": "Échec de l'import. Merci, de vérifier que les données saisies sont valides.", "import_explanation": "Entrez ici votre mnémonique, clé privée, WIF, ou quoi que ce soit que vous ayez. BlueWallet fera de son mieux pour deviner le bon format et importer votre portefeuille", "import_imported": "Importé", - "import_scan_qr": "ou scaner un QR code", + "import_scan_qr": "ou scanner un QR code", "import_success": "Succès", + "import_success_watchonly": "Votre portefeuille a été importé avec succès. AVERTISSEMENT : Il s'agit d'un portefeuille en lecture seule, vous ne pouvez PAS dépenser avec celui-ci.", "import_search_accounts": "Rechercher des comptes", "import_title": "importer", + "learn_more": "En apprendre plus", "import_discovery_title": "Découverte", "import_discovery_subtitle": "Choisissez un portefeuille trouvé", "import_discovery_derivation": "Utiliser un chemin de dérivation personnalisé", - "import_discovery_no_wallets": "Aucaun portefeuille trouvé.", - "import_derivation_found": "trouvé", - "import_derivation_found_not": "non trouvé", + "import_discovery_no_wallets": "Aucun portefeuille trouvé.", + "import_discovery_offline": "BlueWallet est actuellement en mode hors ligne. Dans ce mode, il ne peut pas vérifier l'existence du portefeuille, vous devrez donc sélectionner le bon manuellement", + "import_derivation_found": "Trouvé", + "import_derivation_found_not": "Non trouvé", "import_derivation_loading": "Chargement...", "import_derivation_subtitle": "Entrez le chemin de dérivation personnalisé et nous essaierons de découvrir votre portefeuille", "import_derivation_title": "Chemin de dérivation", - "import_derivation_unknown": "inconnu", - "import_wrong_path": "chemin de dérivation erroné", + "import_derivation_unknown": "Inconnu", + "import_wrong_path": "Chemin de dérivation erroné", "list_create_a_button": "Ajouter maintenant", "list_create_a_wallet": "Ajouter un portefeuille", - "list_create_a_wallet_text": "Cest gratuit et vous pouvez en créer \nautant que vous voulez.", + "list_create_a_wallet_text": "C'est gratuit et vous pouvez en créer \nautant que vous voulez.", "list_empty_txs1": "Vos transactions apparaîtront ici,", "list_empty_txs1_lightning": "Un portefeuille Lightning devrait être utilisé pour les transactions quotidiennes. Les frais sont très bas et la vitesse est étourdissante.", "list_empty_txs2": "Commencez avec votre portefeuille.", - "list_empty_txs2_lightning": "\nPour commencer à l'utiliser taper sur \"Gérer les fonds\" et alimentez votre solde.", + "list_empty_txs2_lightning": "\nPour commencer à l'utiliser, tapez sur \"Gérer les fonds\" et alimentez votre solde.", "list_latest_transaction": "dernière transaction", - "list_ln_browser": "Selectionneur LApp", "list_long_choose": "Choisir une photo", - "list_long_clipboard": "Copier depuis le presse-papier", + "paste_from_clipboard": "Coller", + "import_file": "Importer le fichier", "list_long_scan": "Scanner le QR Code", - "list_title": "portefeuilles", + "list_title": "Portefeuilles", "list_tryagain": "Réessayer", - "no_ln_wallet_error": "Avant de payer une facture Ligthning, vous devez créer un portefeuille Ligthning.", - "looks_like_bip38": "Ceci ressemble a une clé privée protégée par un mot de passe (BIP38)", - "reorder_title": "Trier vos portefeuilles", - "reorder_instructions": "Appuyez et maintenez un portefeuille pour le déplacer à travers la liste.", - "please_continue_scanning": "Merci de continuer à scaner", + "no_ln_wallet_error": "Avant de payer une facture Lightning, vous devez créer un portefeuille Lightning.", + "looks_like_bip38": "Ceci ressemble à une clé privée protégée par un mot de passe (BIP38)", + "manage_title": "Gérer les portefeuilles", + "no_results_found": "Pas de résultats trouvés.", + "please_continue_scanning": "Merci de continuer à scanner", "select_no_bitcoin": "Il n'y a aucun portefeuille Bitcoin disponible pour le moment.", "select_no_bitcoin_exp": "Un portefeuille Bitcoin est nécessaire pour approvisionner les portefeuilles Lightning. Veuillez en créer ou en importer un.", "select_wallet": "Choix du portefeuille", - "xpub_copiedToClipboard": "Copié dans le presse-papiers.", "pull_to_refresh": "Tirer pour rafraichir", - "warning_do_not_disclose": "Attention! Ne pas divulguer", + "warning_do_not_disclose": "Ne partagez jamais les informations ci-dessous", + "scan_import": "Scannez ce QRcode pour importer votre portefeuille dans une autre application.", + "write_down_header": "Créer une sauvegarde manuelle", + "write_down": "Notez et conservez ces mots en toute sécurité. Utilisez-les pour restaurer votre portefeuille ultérieurement.", + "wallet_type_this": "Ce type de portefeuille est {type}.", + "share_number": "Partager {number}", + "copy_ln_url": "Copiez et stockez en toute sécurité cette URL pour restaurer votre portefeuille ultérieurement.", + "copy_ln_public": "Copiez et stockez ces informations en toute sécurité pour restaurer votre portefeuille ultérieurement.", "add_ln_wallet_first": "Vous devez d'abord ajouter un portefeuille Lightning.", "identity_pubkey": "Clé publique identité", - "xpub_title": "XPUB portefeuille" + "xpub_title": "XPUB portefeuille", + "manage_wallets_search_placeholder": "Chercher portefeuilles, adresses, transactions et memos", + "more_info": "Plus d'information", + "details_delete_wallet_error_message": "Un problème est survenu lors de la confirmation de la suppression de ce portefeuille des notifications. Cela pourrait être dû à un problème de réseau ou à une mauvaise connexion. Si vous continuez, vous pourriez continuer à recevoir des notifications pour les transactions liées à ce portefeuille, même après sa suppression.", + "details_delete_anyway": "Supprimer quand même", + "swipe_balance_hide": "Cacher", + "swipe_balance_show": "Montrer", + "drag_to_reorder": "Glisser pour réorganiser", + "clear_search": "Effacer la recherche", + "details_type": "Type" + }, + "total_balance_view": { + "display_in_bitcoin": "Afficher en Bitcoin", + "hide": "Cacher", + "display_in_sats": "Afficher en Satoshi", + "display_in_fiat": "Afficher en {currency}", + "title": "Solde total", + "explanation": "Consultez le solde total de tous vos portefeuilles dans l'écran d'aperçu." }, "multisig": { - "multisig_vault": "Coffre", + "multisig_vault": "Coffre-fort multi-signature", "default_label": "Coffre-fort multi-signature", - "multisig_vault_explain": "Meilleur sécurité pour les gros montants", + "multisig_vault_explain": "Meilleure sécurité pour les gros montants", "provide_signature": "Fournir la signature", + "provide_signature_details": "Utilisez votre appareil et votre portefeuille où se trouve la clé pour signer cette transaction", + "provide_signature_details_bluewallet": "Dans BlueWallet, accédez au menu de l'écran Envoyer et sélectionnez", + "provide_signature_next_steps": "Numériser ou importer une transaction signée", + "provide_signature_next_steps_details": "Une fois que votre portefeuille a signé avec succès la transaction, scannez le code QR fourni ou importez le fichier qui l'accompagne, puis vérifiez tous les détails de la transaction avant de la diffuser.", "vault_key": "Clé du coffre {number}", "required_keys_out_of_total": "Clés requises par rapport au total", - "fee": "Frais: {number}", + "fee": "Frais : {number}", "fee_btc": "{number} BTC", "confirm": "Confirmer", "header": "Envoyer", - "share": "Partager", - "view": "Vue", + "share": "partager", + "view": "Voir", + "shared_key_detected": "Cosignataire partagé", + "shared_key_detected_question": "Un cosignataire a été partagé avec vous, souhaitez-vous l'importer ?", "manage_keys": "Gérer les clés", "how_many_signatures_can_bluewallet_make": "Combien de signatures BlueWallet peut-il faire", - "signatures_required_to_spend": "Signatures necessaires {number}", + "signatures_required_to_spend": "Signatures nécessaires {number}", "signatures_we_can_make": "peut faire {number}", "scan_or_import_file": "Scanner ou importer fichier", "export_coordination_setup": "Exporter la configuration de coordination", - "cosign_this_transaction": "Co-signer cette transaction?", + "cosign_this_transaction": "Co-signer cette transaction ?", "lets_start": "C'est parti", "create": "Créer", "native_segwit_title": "Pratique recommandée", - "wrapped_segwit_title": "Meilleur compatibilité", + "wrapped_segwit_title": "Meilleure compatibilité", "legacy_title": "Ancien format", "co_sign_transaction": "Signer une transaction", - "what_is_vault": "Un coffre est un portefeuille de ", - "what_is_vault_numberOfWallets": " {m}-de-{n} multi signaures", + "what_is_vault": "Un coffre est un portefeuille de", + "what_is_vault_numberOfWallets": " {m}-de-{n} multi-signatures", "what_is_vault_wallet": ".", "vault_advanced_customize": "Paramètres du coffre", "needs": "Il a besoin", @@ -493,23 +568,23 @@ "what_is_vault_description_to_spend": "pour dépenser et une troisième \nque vous pouvez utiliser en backup.", "what_is_vault_description_to_spend_other": "pour dépenser.", "quorum": "{m} de {n} quorum", - "quorum_header": "Quorum", "of": "de", "wallet_type": "Type portefeuille", - "invalid_mnemonics": "Cette phrase mnémonique ne semble pas valide ", - "invalid_cosigner": "Données de cosigner invalides", + "invalid_mnemonics": "Cette phrase mnémonique ne semble pas valide.", + "invalid_cosigner": "Données de co-signeur invalides", "not_a_multisignature_xpub": "Ceci n'est pas un xpub provenant d'un portefeuille multisig", - "invalid_cosigner_format": "Co-signeur incorrecte: Ceci nest pas un co-signeur au {format} format", + "invalid_cosigner_format": "Co-signeur incorrect : Ceci n'est pas un co-signeur au format {format}.", "create_new_key": "Créer nouveau", "scan_or_open_file": "Scanner ou ouvrir fichier", "i_have_mnemonics": "J'ai une graine pour cette clé...", "type_your_mnemonics": "Insérez une graine pour importer votre clé de coffre existante", - "this_is_cosigners_xpub": "Ceci est l'XPUB du co-signeur, prêt a être importé dans un autre portefeuille. C'est sure de le partager.", - "wallet_key_created": "Votre clé de coffre a été créé. Prenez un moment pour sauvegarder votre graine sous forme de mnémonique ", - "are_you_sure_seed_will_be_lost": "Etes Vous sûr? ", + "this_is_cosigners_xpub": "Ceci est l'XPUB du co-signeur, prêt à être importé dans un autre portefeuille. Il est sûr de le partager.", + "this_is_cosigners_xpub_airdrop": "Si vous partagez via AirDrop, les récepteurs doivent être dans l'écran de coordination.", + "wallet_key_created": "Votre clé de coffre a été créée. Prenez un moment pour sauvegarder votre graine sous forme de mnémonique", + "are_you_sure_seed_will_be_lost": "Êtes-vous sûr ? Votre graine mnémonique sera perdue si vous n'avez pas de sauvegarde.", "forget_this_seed": "Oublier cette graine et utiliser l'XPUB à la place", "view_edit_cosigners": "Voir/Editer les co-signeurs", - "this_cosigner_is_already_imported": "Ce co-signeur a été déjà importé.", + "this_cosigner_is_already_imported": "Ce co-signeur a déjà été importé.", "export_signed_psbt": "Exporter la PSBT signée", "input_fp": "Entrer l'empreinte", "input_fp_explain": "Passer pour utiliser celle par défaut (00000000)", @@ -517,38 +592,52 @@ "input_path_explain": "Passer pour utiliser celui par défaut ({default})", "ms_help": "Aide", "ms_help_title": "Comment les coffres Multisig marchent. Conseils et astuces", - "ms_help_text": "Un portefeuille avec plusieurs clés, pour augmenter de façon exponentielle la sécurité ou pour partager la détention.", + "ms_help_text": "Un portefeuille avec plusieurs clés, pour augmenter la sécurité ou pour partager la détention", "ms_help_title1": "Plusieurs appareils est conseillé", - "ms_help_1": "Le coffre fonctionnera avec d'autre BlueWallet et les portefeuille compatible PSBT, comme Electrum, Specter, Coldcard, Cobo vault, etc...", + "ms_help_1": "Le coffre fonctionnera avec d'autres BlueWallet et les portefeuilles compatibles PSBT, comme Electrum, Specter, Coldcard, Keystone, etc.", "ms_help_title2": "Edition des clés", - "ms_help_2": "Vous pouvez créer toutes les clés Coffre dans cet appareil et les supprimer ou les modifier ultérieurement. Avoir toutes les clés sur le même appareil revient a avoir la sécurité équivalente d'un portefeuille Bitcoin ordinaire.", + "ms_help_2": "Vous pouvez créer toutes les clés Coffre dans cet appareil et les supprimer ou les modifier ultérieurement. Avoir toutes les clés sur le même appareil revient à avoir la sécurité équivalente d'un portefeuille Bitcoin ordinaire.", "ms_help_title3": "Sauvegardes coffre", - "ms_help_3": "Dans les options du portefeuille, vous trouverez votre sauvegarde Coffre et votre sauvegarde lecture seule. Cette sauvegarde est comme une carte de votre portefeuille. Il est essentiel pour la récupération du portefeuille au cas où vous perdriez une de vos graines.", - "ms_help_title4": "importation Coffre", - "ms_help_4": "Pour importer un coffre Multi-signature, utilisez votre fichier de sauvegarde multisig et utilisez la fonction d'importation. Si vous ne disposez que de clés étendues et des graines, vous pouvez utiliser les champs d'importation individuels du flux Ajouter un coffre.\n", + "ms_help_3": "Dans les options du portefeuille, vous trouverez votre sauvegarde Coffre et votre sauvegarde lecture seule. Cette sauvegarde est comme une carte de votre portefeuille. Elle est essentielle pour la récupération du portefeuille au cas où vous perdriez une de vos graines.", + "ms_help_title4": "Importation Coffre", + "ms_help_4": "Pour importer un coffre Multi-signature, utilisez votre fichier de sauvegarde multisig et utilisez la fonction d'importation. Si vous ne disposez que de clés étendues et des graines, vous pouvez utiliser les champs d'importation individuels du flux Ajouter un coffre.", "ms_help_title5": "Options avancées", - "ms_help_5": "Par défaut, BlueWallet générera un coffre 2de3. Pour créer un quorum différent ou pour changer le type d'adresse, activez les options avancées dans les paramètres." + "ms_help_5": "Par défaut, BlueWallet générera un coffre 2-de-3. Pour créer un quorum différent ou pour changer le type d'adresse, activez les options avancées dans les paramètres.", + "quorum_header": "Quorum" }, "is_it_my_address": { - "title": "Est ce mon adresse?", + "title": "Est-ce mon adresse ?", "owns": "{label} possède {address}", "enter_address": "Entrez l'adresse", "check_address": "Vérifiez l'adresse", "no_wallet_owns_address": "Aucun des portefeuilles ne possède l'adresse fournie.", "view_qrcode": "Voir QRCode" }, + "autofill_word": { + "enter": "Entrez votre phrase mnémonique partielle", + "generate_word": "Générez le dernier mot", + "error": "L'entrée n'est pas un mnémonique partiel de 11 ou 23 mots. Veuillez réessayer.", + "title": "Dernier mot de la graine" + }, "cc": { - "change": "monnaie rendu", - "coins_selected": "UTXO sélectionnées ({number})", - "selected_summ": "{value} sélectionnée ", - "empty": "Ce portefeuille ne possède aucune pièces en ce moment", + "change": "monnaie rendue", + "coins_selected": "UTXO sélectionnés ({number})", + "selected_summ": "{value} sélectionné", + "empty": "Ce portefeuille ne possède aucune pièce en ce moment.", "freeze": "Geler", "freezeLabel": "Gelé", "freezeLabel_un": "Dégeler", - "header": "Controle des UTXO", + "header": "Contrôle des UTXO", "use_coin": "Utiliser l'UTXO", "use_coins": "Utiliser les UTXO", - "tip": "Permet de voir, étiqueter, bloquer ou sélectionner des UTXOs pour une meilleure gestion du portefeuille. Vous pouvez sélectionner plusieurs UTXOs en appuyant sur les cercles colorés." + "tip": "Permet de voir, étiqueter, bloquer ou sélectionner des UTXOs pour une meilleure gestion du portefeuille. Vous pouvez sélectionner plusieurs UTXOs en appuyant sur les cercles colorés.", + "sort_asc": "Ascendant", + "sort_desc": "Descendant", + "sort_height": "Hauteur", + "sort_value": "Valeur", + "sort_label": "Libellé", + "sort_status": "Statut", + "sort_by": "Trier par" }, "units": { "BTC": "BTC", @@ -557,35 +646,59 @@ "sats": "sats" }, "addresses": { - "sign_title": "Signer/Verifier message", + "copy_private_key": "Copier clé privée", + "sensitive_private_key": "Attention : les clés privées sont extrêmement sensibles. Continuer ?", + "sign_title": "Signer/Vérifier message", "sign_help": "Ici vous pouvez créer ou vérifier une signature électronique basée sur une adresse bitcoin.", "sign_sign": "Signe", "sign_verify": "Vérifie", - "sign_signature_correct": "Vérifié avec succès!", + "sign_signature_correct": "Vérifié avec succès !", "sign_signature_incorrect": "Vérification échouée !", "sign_placeholder_address": "Adresse", - "sign_placeholder_message": "Message", - "sign_placeholder_signature": "Signature", "addresses_title": "Adresses", "type_change": "Monnaie rendue", "type_receive": "Réception", "type_used": "Utilisé", + "sign_placeholder_message": "Message", + "sign_placeholder_signature": "Signature", "transactions": "Transactions" }, "lnurl_auth": { - "register_question_part_1": "Voulez vous enregistrer un compte à:", - "register_question_part_2": "en utilisant votre protefeuille Ligthning?", - "register_answer": "Vous avez enregistré un compte avec succès à {hostname}!", - "login_question_part_1": "Voulez vous vous connecter à ", - "login_question_part_2": "en utilisant votre protefeuille Ligthning?", - "login_answer": "Vous vous êtes connecté avec succès à {hostname}!", - "link_question_part_1": "Voulez vous lier votre compte à", + "register_question_part_1": "Voulez-vous enregistrer un compte à :", + "register_question_part_2": "en utilisant votre portefeuille Lightning ?", + "register_answer": "Vous avez enregistré un compte avec succès à {hostname} !", + "login_question_part_1": "Voulez-vous vous connecter à", + "login_question_part_2": "en utilisant votre portefeuille Lightning ?", + "login_answer": "Vous vous êtes connecté avec succès à {hostname} !", + "link_question_part_1": "Voulez-vous lier votre compte à", "link_question_part_2": "à votre portefeuille Lightning ?", - "link_answer": "Votre portefeuille Ligthning a été lié avec succès a votre compte à {hostname}!", - "auth_question_part_1": "Voulez vous être authentifié à", - "auth_question_part_2": "en utilisant votre protefeuille Ligthning?", - "auth_answer": "Vous vous êtes authentifié avec succès à {hostname}!", + "link_answer": "Votre portefeuille Lightning a été lié avec succès à votre compte à {hostname} !", + "auth_question_part_1": "Voulez-vous être authentifié à", + "auth_question_part_2": "en utilisant votre portefeuille Lightning ?", + "auth_answer": "Vous vous êtes authentifié avec succès à {hostname} !", "could_not_auth": "Nous n'avons pas pu vous authentifier à {hostname}.", "authenticate": "Authentifier" + }, + "bip47": { + "payment_code": "Code de paiement", + "bip47_explain": "Code réutilisable et partageable", + "bip47_explain_subtitle": "BIP47", + "purpose": "Code réutilisable et partageable (BIP47)", + "pay_this_contact": "Payer ce contact", + "rename_contact": "Renommer contact", + "copy_payment_code": "Copier le code de paiement", + "hide_contact": "Cacher contact", + "rename": "Renommer", + "provide_name": "Fournir un nouveau nom pour ce contact", + "add_contact": "Ajouter contact", + "provide_payment_code": "Fournir le code de paiement", + "invalid_pc": "Code de paiement invalide", + "notification_tx_unconfirmed": "La transaction de notification n'est pas encore confirmée, veuillez patienter", + "failed_create_notif_tx": "Échec de la création d'une transaction on-chain", + "onchain_tx_needed": "Transaction on-chain nécessaire", + "notif_tx_sent": "Transaction de notification envoyée. Veuillez attendre qu'elle soit confirmée", + "notif_tx": "Transaction de notification", + "not_found": "Code de paiement introuvable", + "contacts": "Contacts" } } diff --git a/loc/he.json b/loc/he.json index fd3b30be6a9..57ce4f69a6c 100644 --- a/loc/he.json +++ b/loc/he.json @@ -4,32 +4,32 @@ "cancel": "ביטול", "continue": "המשך", "clipboard": "לוח גזירים", + "copied": "הועתק!", + "discard_changes": "ביטול שינויים?", + "discard_changes_explain": "יש לך שינויים לא שמורים. האם להיפטר משינויים אלה ולעזוב את המסך?", "enter_password": "הכניסו סיסמה", "never": "אף פעם", - "disabled": "מנוטרל", "of": "{number} מתוך {total}", "ok": "אישור", + "enter_url": "הכנסת URL", "storage_is_encrypted": "האחסון שלך מוצפן. נדרשת סיסמה לפענוח שלו.", "yes": "כן", "no": "לא", - "save": "שמירה", + "save": "שמירה...", "seed": "גרעין", "success": "הצלחה", "wallet_key": "מפתח ארנק", - "invalid_animated_qr_code_fragment": "קטע קוד QR מונפש לא תקין. אנא נסו שוב.", - "file_saved": "הקובץ {filePath} נשמר בתיקיית {destination} שלך.", - "downloads_folder": "תיקיית הורדות", "close": "סגירה", "change_input_currency": "שינוי מטבע קלט", "refresh": "רענון", - "more": "עוד", - "pick_image": "בחירת תמונה מספריה", + "pick_image": "בחירה מספרייה ", "pick_file": "בחירת קובץ", "enter_amount": "הכנסת סכום", - "qr_custom_input_button": "הקישו 10 פעמים כדי להכניס קלט מותאם" - }, - "alert": { - "default": "אזהרה" + "qr_custom_input_button": "הקישו 10 פעמים כדי להכניס קלט מותאם", + "unlock": "פתיחה", + "port": "פתחה", + "ssl_port": "פתחת SSL", + "suggested": "מוצע" }, "azteco": { "codeIs": "קוד השובר שלך הוא", @@ -38,12 +38,14 @@ "redeem": "מימוש לארנק", "redeemButton": "מימוש", "success": "הצלחה", + "successMessage": "שובר מומש בהצלחה! הכספים שלך אמורים להגיע לארנק הביטקוין שלך בקרוב.", "title": "מימוש שובר Azte.co" }, "entropy": { "save": "שמירה", "title": "אנטרופיה", - "undo": "ביטול" + "undo": "ביטול", + "amountOfEntropy": "{bits} מתוך {limit} סיביות" }, "errors": { "broadcast": "שידור כשל.", @@ -51,85 +53,69 @@ "network": "שגיאת רשת" }, "lnd": { - "active": "פעיל", - "inactive": "לא פעיל", - "channels": "ערוצים", - "no_channels": "אין ערוצים", - "claim_balance": "מאזן דרישה {balance}", - "close_channel": "סגירת ערוץ", - "new_channel": "ערוץ חדש", "errorInvoiceExpired": "חשבונית פגה", - "force_close_channel": "כפיית סגירת ערוץ?", "expired": "פג", - "node_alias": "כינוי צומת", "expiresIn": "פג בעוד {time} דקות", "payButton": "תשלום", + "payment": "תשלום", "placeholder": "חשבונית או כתובת", - "open_channel": "פתיחת ערוץ", - "funding_amount_placeholder": "סכום מימון, לדוגמה 0.001 ", - "opening_channnel_for_from": "פתיחת ערוץ עבור ארנק {forWalletLabel}, בעמצאות מימון מארנק {fromWalletLabel}", - "are_you_sure_open_channel": "האם אתם בטוחים שברצונכם לפתוח ערוץ זה?", "potentialFee": "עמלה פוטנציאלית: {fee}", - "remote_host": "מארח מרוחק", "refill": "טעינה", - "reconnect_peer": "התחברות מחדש לעמית", "refill_create": "כדי להמשיך, אנא צרו ארנק ביטקוין כדי לטעון באמצעותו.", "refill_external": "טעינה בעזרת ארנק חיצוני", "refill_lnd_balance": "מלאו את יתרת ארנק הברק", - "sameWalletAsInvoiceError": "אין ביכולתך לשלם חשבונית בעזרת אותו ארנק שיצר אותה.", - "title": "ניהול כספים", - "can_send": "ניתן לשלוח", - "can_receive": "ניתן לקבל", - "view_logs": "צפייה ביומנים" + "sameWalletAsInvoiceError": "לא ניתן לשלם חשבונית עם אותו הארנק שיצר אותה.", + "title": "ניהול כספים" }, "lndViewInvoice": { "additional_info": "מידע נוסף", "for": "עבור:", "lightning_invoice": "חשבונית ברק", - "open_direct_channel": "פתח ערוץ ישיר עם צומת זה:", "please_pay_between_and": "אנא שלמו בין {min} לבין {max}", "please_pay": "אנא שלמו", - "preimage": "Preimage", - "wasnt_paid_and_expired": "חשבונית זו לא שולמה ופגה" + "sats": "sats.", + "date_time": "תאריך ושעה", + "wasnt_paid_and_expired": "חשבונית זו לא שולמה ופגה", + "preimage": "preimage" }, "plausibledeniability": { "create_fake_storage": "צרו אחסון מוצפן", - "create_password": "צרו סיסמה", "create_password_explanation": "הסיסמה לאחסון המדומה צריכה להיות שונה מהסיסמה לאחסון הראשי", - "help": "בנסיבות מסוימות, יתכן ותאולצו לחשוף את סיסמת הארנק. כדי לשמור על המטבעות בטוחים, BlueWallet מאפשר ליצור אחסון מוצפן נוסף, עם סיסמה שונה. תחת לחץ, תוכלו לחשוף את סיסמה זו לצד שלישי. אם הסיסמה תוכנס ל- BlueWallet, אחסון 'מזויף' חדש יפתח. מצב זה יראה לגיטימי לצד השלישי, בזמן שהאחסון הראשי ישמר בסודיות עם כשהמטבעות מוגנים.", + "help": "בנסיבות מסוימות, יתכן ותאולצו לחשוף את סיסמת הארנק. כדי לשמור על המטבעות בטוחים, BlueWallet מאפשר ליצור אחסון מוצפן נוסף, עם סיסמה שונה. תחת לחץ, תוכלו לחשוף את סיסמה זו לצד שלישי. אם הסיסמה תוכנס ל- BlueWallet, אחסון 'מזויף' חדש יפתח. מצב זה יראה לגיטימי לצד השלישי, בזמן שהאחסון הראשי יישמר בסודיות והמטבעות יישארו מוגנים.", "help2": "האחסון החדש יתפקד באופן מלא, ותוכלו לאחסן בו סכומים מינימליים כך שיראה יותר מהימן.", "password_should_not_match": "הסיסמה כבר בשימוש. אנא נסו סיסמה אחרת.", - "passwords_do_not_match": "סיסמאות אינן תואמות, אנא נסו שוב", - "retype_password": "הכניסו שוב סיסמה", - "success": "הצלחה", "title": "יכולת הכחשה סבירה" }, "pleasebackup": { "ask": "האם שמרת את מילות הגיבוי שלך? מילות גיבוי אלה דרושות כדי לגשת לכספים שלך במקרה של אובדן מכשיר זה. ללא גיבוי זה, הכספים יאבדו לצמיתות.", "ask_no": "לא, לא גיביתי", "ask_yes": "כן, גיביתי", - "ok": "אוקיי, רשמתי את זה.", + "ok": "אוקיי, רשמתי את זה", "ok_lnd": "אוקיי, שמרתי את זה.", - "text": "אנא קחו רגע כדי לכתוב על דף נייר את מילות הגיבוי האלו. זה הגיבוי שלכם ואיתו תוכלו לשחזר את הארנק.", + "text": "אנא קחו רגע כדי לכתוב על דף נייר את מילות הגיבוי האלו.\nזה הגיבוי שלכם ואיתו תוכלו לשחזר את הארנק.", "text_lnd": "אנא קחו רגע כדי לשמור את אימות ה- LNDHub. זה הגיבוי איתו תוכלו לשחזר את הארנק על מכשיר אחר.", - "title": "ארנקך נוצר" + "title": "ארנקכם נוצר..." }, "receive": { "details_create": "יצירה", "details_label": "תיאור", "details_setAmount": "קבלה עם סכום", - "details_share": "שיתוף", + "details_share": "שיתוף...", + "address_not_found": "לא ניתן ליצר כתובת קבלה.", "header": "קבלה", + "reset": "איפוס", "maxSats": "סכום מקסימלי הינו {max} sats", "maxSatsFull": "סכום מקסימלי הינו {max} sats או {currency}", "minSats": "סכום מינימלי הינו {min} sats", - "minSatsFull": "סכום מינימלי הינו {min} sats או {currency}" + "minSatsFull": "סכום מינימלי הינו {min} sats או {currency}", + "qrcode_for_the_address": "קוד QR לכתובת", + "bip47_explanation": "קודי תשלום הם כתובת אוניברסלית שמונעת חשיפה של כתובות הארנק שלך. לא כל השירותים יתמכו בהם." }, "send": { "provided_address_is_invoice": "נראה שכתובת זו היא חשבונית ברק. אנא עברו לארנק הברק שלכם כדי לבצע תשלום עבור חשבונית זו.", "broadcastButton": "שידור", "broadcastError": "שגיאה", - "broadcastNone": "קלט גיבוב פעולה", + "broadcastNone": "הכנסת HEX של פעולה", "broadcastPending": "ממתין", "broadcastSuccess": "הצלחה", "confirm_header": "אישור", @@ -145,8 +131,15 @@ "create_to": "עבור", "create_tx_size": "גודל הפעולה", "create_verify": "אמתו ב- coinb.in", + "details_insert_contact": "הוספת איש קשר", "details_add_rec_add": "הוספת נמען", "details_add_rec_rem": "הסרת נמען", + "details_add_recc_rem_all_alert_description": "האם אתם בטוחים שברצונכם למחוק את כל הנמענים?", + "details_add_rec_rem_all": "מחיקת כל הנמענים", + "details_recipients_title": "נמענים", + "details_recipient_title": "נמען #{number} מתוך #{total}", + "please_complete_recipient_title": "נמען חסר", + "please_complete_recipient_details": "אנא השלימו את פרטי נמען #{number} לפני הוספת נמען חדש.", "details_address": "כתובת", "details_address_field_is_not_valid": "שדה כתובת לא תקין", "details_adv_fee_bump": "אפשר הקפצת עמלה", @@ -160,12 +153,13 @@ "details_create": "יצירת קבלה", "details_error_decode": "לא ניתן לפענח כתובת ביטקוין", "details_fee_field_is_not_valid": "שדה עמלה אינו תקין", - "details_frozen": "{amount} ביטקוין מוקפא", + "details_frozen": "מוקפאים {amount} BTC.", "details_next": "הבא", - "details_no_signed_tx": "הקובץ הנבחר אינו מכיל פעולה שניתן לייבא.", + "details_no_signed_tx": "הקובץ הנבחר אינו מכיל העברה שניתן לייבא.", "details_note_placeholder": "הערה לעצמך", "details_scan": "סריקה", "details_scan_hint": "לחיצה כפולה לסריקה או יבוא יעד", + "details_scan_error": "שגיאת סריקה", "details_total_exceeds_balance": "הסכום לשליחה חורג מהיתרה הזמינה.", "details_total_exceeds_balance_frozen": "סכום השליחה חרג מהיתרה הזמינה. אנא שימו לב שמטבעות מוקפאים מוחרגים.", "details_unrecognized_file_format": "פורמט קובץ לא מזוהה", @@ -179,9 +173,11 @@ "fee_1d": "1 י'", "fee_3h": "3 ש'", "fee_custom": "אחר", + "insert_custom_fee": "הכנסת עמלה", "fee_fast": "מהיר", "fee_medium": "בינוני", - "fee_replace_minvb": "שעור העמלה הכולל (סאטושי עבור vByte) שברצונך לשלם צריך להיות גבוה יותר מאשר {min} sat/vByte.", + "fee_replace_minvb": "שיעור העמלה הכולל (סאטושי עבור vByte) שברצונך לשלם צריך להיות גבוה יותר מאשר {min} sat/vByte.", + "fee_satvbyte": "ב- sat/vByte", "fee_slow": "איטי", "header": "שליחה", "input_clear": "ניקוי", @@ -190,9 +186,8 @@ "input_total": "סך הכל:", "permission_camera_message": "אנחנו צריכים את הרשאתך לשימוש במצלמה שלך.", "psbt_sign": "חתימה על פעולה", - "open_settings": "פתח הגדרות", - "permission_storage_later": "שאל אותי מאוחר יותר", - "permission_storage_message": "ארנק BlueWallet צריך את הרשאתך לגשת לאחסון שלך כדי לשמור את קובץ זה.", + "invalid_psbt": "סופק PSBT לא תקין.", + "open_settings": "פתיחת הגדרות", "permission_storage_denied_message": "ארנק BlueWallet אינו יכול לשמור קובץ זה. אנא פתחו את הגדרות המכשיר שלכם ואפשרו הרשאת אחסון.", "permission_storage_title": "הרשאת גישת אחסון", "psbt_clipboard": "העתקה ללוח", @@ -202,12 +197,16 @@ "outdated_rate": "תעריף עודכן לאחרונה: {date}", "psbt_tx_open": "פתחו פעולה חתומה", "psbt_tx_scan": "סרקו פעולה חתומה", - "qr_error_no_qrcode": "לא הצלחנו למצוא קוד QR בתמונה הנבחרת. אנא ודאו כי התמונה מכילה רק קוד QR, ולא תוכן נוסף כמו טקסט, או כפתורים.", "reset_amount": "איפוס סכום", "reset_amount_confirm": "האם ברצונך לאפס את הסכום?", "success_done": "בוצע", - "txSaved": "קובץ הפעולה ({filePath}) נשמר בספריית ההורדות שלך.", - "problem_with_psbt": "בעיה עם PBST" + "txSaved": "קובץ הפעולה ({filePath}) נשמר.", + "file_saved_at_path": "הקובץ ({filePath}) נשמר.", + "cant_send_to_silentpayment_adress": "ארנק זה אינו יכול לשלוח לכתובות תשלום שקט", + "cant_send_to_bip47": "ארנק אינו יכול לשלוח לקודי תשלום BIP47", + "cant_find_bip47_notification": "הוספת קוד תשלום זה לרשימת אנשי קשר", + "problem_with_psbt": "בעיה עם PBST", + "qr_error_no_qrcode": "לא הצלחנו למצוא קוד QR תקין בתמונה שנבחרה. ודא שהתמונה מכילה אך ורק קוד QR ללא תוכן נוסף כגון טקסט או כפתורים." }, "settings": { "about": "אודות", @@ -220,28 +219,30 @@ "performance_score": "ניקוד ביצועים: {num}", "run_performance_test": "בדיקת ביצועים", "about_selftest": "הרצת בדיקה עצמית", + "block_explorer_invalid_custom_url": "ה- URL שסופק אינו תקין. אנא הכנסו URL תקין המתחיל עם http:// או https://.", "about_selftest_electrum_disabled": "בדיקה עצמית אינה זמינה במצב אלקטרום לא-מקוון. אנא בטלו מצב לא-מקוון ונסו שוב.", "about_selftest_ok": "כל הבדיקות הפנימיות עברו בהצלחה. הארנק פועל כיאות.", "about_sm_github": "GitHub", - "about_sm_discord": "שרת דיסקורד", "about_sm_telegram": "צ'אט טלגרם", - "about_sm_twitter": "עקבו אחרינו בטוויטר", - "advanced_options": "אפשרויות מתקדמות", - "biometrics": "ביומטריה", + "privacy_temporary_screenshots": "אפשר צילומי מסך", + "privacy_temporary_screenshots_instructions": "ההגנה מפני צילומי מסך תושבת באופן זמני, מה שיאפשר צילומי מסך והקלטות מסך. ההגנה תופעל מחדש אוטומטית כשתסגור ותפתח מחדש את BlueWallet.", + "biometrics": "זיהוי ביומטרי", + "biometrics_no_longer_available": "הגדרות מכשירכם השתנו ולא מתאימים יותר להגדרות אבטחה הנבחרות ביישומון. אנא אפשרו מחדש זיהוי ביומטרי או סיסמה, ולאחר מכן הפעילו מחדש את היישומון כדי להחיל את השינויים.", "biom_10times": "ניסיתם להכניס את הסיסמה שלכם 10 פעמים. האם תרצו לאפס את האחסון שלכם? פעולה זאת תמחק את כל הארנקים ותפענח את האחסון שלכם.", "biom_conf_identity": "אנא אמתו את הזהות שלכם.", - "biom_no_passcode": "למכשירכם אין סיסמה. במטרה להמשיך אנא הגדירו סיסמה בהגדרות המכשיר.", + "biom_no_passcode": "למכשירכם אין סיסמה או זיהוי ביומטרי מופעל. במטרה להמשיך, אנא הגדירו סיסמה או זיהוי ביומטרי ביישומון ההגדרות.", "biom_remove_decrypt": "כל ארנקיכם ימחקו והאחסון יפוענח. האם אתם בטוחים שברצונכם להמשיך?", "currency": "מטבע", - "currency_source": "מחיר מתקבל מ- ", + "currency_source": "תעריף מתקבל מ- ", "currency_fetch_error": "התרחשה שגיאה בזמן השגת התעריף למטבע הנבחר.", - "default_desc": "כאשר מבוטל, BlueWallet יפתח אוטומטית את הארנק הנבחר בפתיחה.", - "default_info": "פתיחת ברירת מחדל", "default_title": "בעת פתיחה", - "default_wallets": "הצגת כל הארנקים", + "donate": "תרומה", + "donate_description": "עזרו לנו לשמור את Blue חינמי!", "electrum_connected": "מחובר", "electrum_connected_not": "לא מחובר", "electrum_error_connect": "לא ניתן להתחבר לשרת אלקטרום", + "electrum_error_connect_tor": "לא ניתן להתחבר לשרת האלקטרום שסופק. אנא ודא שאפליקציית Orbot מחוברת ונסה שוב.", + "electrum_preferred_server_description": "הכנס את השרת בו תרצה שהארנק שלך ישתמש עבור כל פעולות הביטקוין. לאחר הגדרתו, הארנק שלך ישתמש בלעדית בשרת זה לבדיקת יתרות, שליחת עסקאות וקבלת נתוני רשת. ודא שאתה סומך על שרת זה לפני הגדרתו.", "lndhub_uri": " למשל {example}", "electrum_host": "למשל {example}", "electrum_offline_mode": "מצב לא מקוון", @@ -252,50 +253,56 @@ "set_electrum_server_as_default": "הגדרת {server} כשרת אלקטרום ברירת מחדל?", "set_lndhub_as_default": "הגדרת {url} כשרת LNDHub ברירת מחדל?", "electrum_settings_server": "שרת אלקטרום", - "electrum_settings_explain": "השאירו ריק כדי להשתמש בברירת מחדל", "electrum_status": "מצב", - "electrum_clear_alert_title": "ניקוי היסטוריה?", - "electrum_clear_alert_message": "האם ברצונך לנקות היסטורית שרתי אלקטרום?", - "electrum_clear_alert_cancel": "ביטול", - "electrum_clear_alert_ok": "אישור", - "electrum_select": "בחירה", - "electrum_reset": "איפוס ברירת מחדל", + "electrum_preferred_server": "שרת מועדף", "electrum_unable_to_connect": "לא מסוגל להתחבר לשרת {server}.", - "electrum_history": "היסטוריית שרת", - "electrum_reset_to_default": "האם אתם בטוחים שברצונכם לשחזר את הגדרות האקלטרום שלכם לברירת מחדל?", - "electrum_clear": "ניקוי", - "tor_supported": "Tor נתמך", - "tor_unsupported": "חיבורי Tor לא נתמכים.", + "electrum_history": "היסטוריה", + "electrum_reset_to_default": "זה ייתן ל- BlueWallet לבחור באופן אקראי שרת מרשימת שרתים.", + "electrum_reset": "איפוס ברירת מחדל", + "electrum_reset_to_default_and_clear_history": "איפוס לברירת מחדל ומחיקת היסטוריה", "encrypt_decrypt": "פתיחת אחסון מוצפן", "encrypt_decrypt_q": "האם לפענח אחסון מוצפן? זה יאפשר לגשת לארנקים שלך ללא סיסמה.", - "encrypt_enc_and_pass": "מוצפן ומוגן על ידי סיסמה", + "encrypt_enc_and_pass": "מוגן בסיסמה", + "encrypt_storage_explanation_headline": "הפעלת הצפנת אחסון", + "encrypt_storage_explanation_description_line1": "הפעלת הצפנת אחסון מוסיפה שכבת הגנה נוספת לאפליקציה על ידי אבטחת אופן שמירת הנתונים שלך במכשיר. הדבר מקשה על כל אחד לגשת למידע שלך ללא הרשאה.", + "encrypt_storage_explanation_description_line2": "עם זאת, חשוב לדעת שהצפנה זו מגנה רק על הגישה לארנקים שלך השמורים במחזיק המפתחות של המכשיר. היא אינה מוסיפה סיסמה או הגנה נוספת לארנקים עצמם.", + "i_understand": "הבנתי", + "block_explorer": "סייר בלוקים", + "block_explorer_preferred": "שימוש בסייר בלוקים מועדף", + "block_explorer_error_saving_custom": "שגיאה בשמירת סייר בלוקים מועדף", "encrypt_title": "אבטחה", "encrypt_tstorage": "אחסון", "encrypt_use": "השתמש {type}", - "encrypt_use_expl": "{type} ישמש לאמת את זהותך לפני ביצוע פעולה, פתיחה, יצוא או מחיקה של ארנק. {type} אינו ישמש לפתיחת אחסון מוצפן.", + "encrypt_use_expl": "{type} ישמש לאישור זהותך לפני ביצוע עסקה, פתיחה, ייצוא או מחיקה של ארנק.", + "set_as_preferred": "הגדרה כמועדף", + "set_as_preferred_electrum": "הגדרת {host}:{port} כשרת מועדף תנטרל חיבור לשרת מוצע בצורה אקראית.", + "encrypted_feature_disabled": "לא ניתן להשתמש בתכונה זאת עם הצפנת אחסון מופעלת.", + "biometrics_fail": "אם {type} לא מאופשר, או נכשל בפתיחה, תוכלו להשתמש בסיסמת המכשיר שלכם בתור חלופה.", "general": "כללי", - "general_adv_mode": "מצב מתקדם", - "general_adv_mode_e": "כאשר מופעל, אפשרויות מתקדמות יוצגו כגון סוגי ארנק שונים, אפשרות להתחבר לצומת LNDHub לפי רצונך ואנטרופיה מותאמת בתהליך יצירת ארנק.", "general_continuity": "המשכיות", "general_continuity_e": "כאשר מופעל, תוכלו לצפות בארנקים ופעולות נבחרים, באמצעות מכשירי Apple iCloud מחוברים אחרים.", - "groundcontrol_explanation": "שרת GroundControl הינו שרת התראות חופשי בקוד פתוח בשביל ארנקי ביטקוין. באפשרותך להתקין שרת GroundControl אישי ולהכניס את ה- URL שלו כאן, כדי לא להסתמך על התשתית של BlueWallet. השאירו ריק כדי להשתמש בברירת המחדל", + "groundcontrol_explanation": "שרת GroundControl הינו שרת התראות חופשי בקוד פתוח בשביל ארנקי ביטקוין. באפשרותך להתקין שרת GroundControl אישי ולהכניס את ה- URL שלו כאן, כדי לא להסתמך על התשתית של BlueWallet. השאירו ריק כדי להשתמש בשרת ברירת המחדל של GroundControl.", "header": "הגדרות", "language": "שפה", "last_updated": "עודכן לאחרונה", "language_isRTL": "הפעלה מחדש של BlueWallet נדרשת לשינוי כיוון שפה.", + "license": "רישיון", "lightning_error_lndhub_uri": " LNDHub URI לא תקני", + "lightning_error_lndhub_uri_tor": "לא תקין LNDhub URI. אנא ודאו שאפליקציית Orbot מחוברת ונסו שוב.", "lightning_saved": "השינויים נשמרו בהצלחה", "lightning_settings": "הגדרות ברק", - "tor_settings": "הגדרות Tor", + "lightning_settings_explain": "כדי להתחבר לצומת LND שלכם עצמכם, אנא התקינו LNDHub והכניסו את כתובת ה- URL שלו כאן בהגדרות. שימו לב שרק ארנקים שנוצרו לאחר שמירת השינויים יתחברו לשרת LNDHub המוגדר.", + "lndhub_github": "מאגר GitHub", "network": "רשת", "network_broadcast": "שידור פעולה", "network_electrum": "שרת אלקטרום", + "electrum_suggested_description": "כאשר לא הוגדר שרת מועדף, שרת מוצע יבחר לשימוש בצורה אקראית.", "not_a_valid_uri": "URI לא תקני", "notifications": "התראות", + "push_notifications_explanation": "בהפעלת ההתראות, אסימון המכשיר שלך יישלח לשרת, יחד עם כתובות הארנקים ומזהי העסקאות עבור כל הארנקים והעסקאות שיתבצעו לאחר הפעלת ההתראות. אסימון המכשיר משמש לשליחת התראות, ומידע הארנק מאפשר לנו ליידע אותך על ביטקוין נכנס או על אישורי עסקאות.\n\nרק מידע מאחרי הפעלת ההתראות מועבר—שום דבר מלפני כן אינו נאסף.\n\nכיבוי ההתראות יסיר את כל המידע הזה מהשרת. בנוסף, מחיקת ארנק מהאפליקציה תסיר גם את המידע המשויך אליו מהשרת.", "open_link_in_explorer": "פתיחת קישור בסייר", "password": "סיסמה", - "password_explain": "צורו סיסמה שבה תשתמשו לפתיחת האחסון המוצפן", - "passwords_do_not_match": "סיסמאות לא תואמות", + "password_explain": "הזינו את הססמה שבה תשתמשו כדי לפתוח את האחסון שלכם.", "plausible_deniability": "יכולת הכחשה סבירה", "privacy": "פרטיות", "privacy_read_clipboard": "קריאה מקליפבורד", @@ -305,13 +312,11 @@ "privacy_clipboard_explanation": "מספק קיצורי דרך במקרה שכתובת, או חשבונית, נמצאות בקליפבורד שלך.", "privacy_do_not_track": "נטרול ניתוח", "privacy_do_not_track_explanation": "מידע ביצועים ומהימנות לא ישלח לניתוח.", - "push_notifications": "התראות", "rate": "תעריף", - "retype_password": "הכניסו שוב סיסמה", "selfTest": "בדיקה עצמית", "save": "שמירה", "saved": "נשמר", - "success_transaction_broadcasted": "הצלחה! הפעולה שלך שודרה!", + "success_transaction_broadcasted": "הפעולה שלך שודרה בהצלחה!", "total_balance": "יתרה כוללת", "total_balance_explanation": "הצגת היתרה הכוללת של כל הארנקים שלך ביישומונים של מסך הבית.", "widgets": "יישומונים", @@ -319,15 +324,17 @@ }, "notifications": { "would_you_like_to_receive_notifications": "האם ברצונך לקבל התראות כאשר מתקבלים תשלומים נכנסים?", - "no_and_dont_ask": "לא, ואל תשאל אותי שוב", - "ask_me_later": "שאל אותי מאוחר יותר" + "notifications_subtitle": "תשלומים נכנסים ואישורי פעולות", + "no_and_dont_ask": "לא, ואל תשאל אותי שוב.", + "permission_denied_message": "דחיתם הרשאה לשלוח לכם התראות. אם תרצו לקבל התראות, אנא אפשרו זאת בהגדרות המכשיר שלכם." }, "transactions": { "cancel_explain": "אנחנו נחליף את העברה זאת עם העברה לעצמך עם עמלות גבוהות יותר. זה למעשה מבטל את ההעברה. זה נקרא RBF—Replace by Fee.", "cancel_no": "פעולה זאת אינה ניתנת להחלפה", "cancel_title": "בטל פעולה זאת (RBF)", + "transaction_loading_error": "ישנה בעיה בטעינת פעולה. אנא נסו מאוחר יותר", + "transaction_not_available": "פעולה לא זמינה", "confirmations_lowercase": "{confirmations} אישורים", - "copy_link": "העתקת קישור", "expand_note": "הרחבת הערה", "cpfp_create": "צור", "cpfp_exp": "אנו ניצור פעולה נוספת שתשתמש בעודף שנשאר מהפעולה הקודמת שבוצעה. סך כל העמלה יהיה גבוה יותר מעמלת הפעולה המקורית, כך שמהירות קבלת האישור אמורה לעלות. פעולה זאת נקראת CPFP - Child Pays For Parent.", @@ -335,20 +342,20 @@ "cpfp_title": "הקפץ עמלה (CPFP)", "details_balance_hide": "הסתרת מאזן", "details_balance_show": "הצגת מאזן", - "details_block": "גובה הבלוק", "details_copy": "העתקה", - "details_copy_amount": "העתקת סכום", "details_copy_block_explorer_link": "העתקת קישור סייר בלוקים", "details_copy_note": "העתקת הערה", "details_copy_txid": "העתקת מזהה פעולה", - "details_from": "קלט", "details_inputs": "קלטים", "details_outputs": "פלטים", "date": "תאריך", "details_received": "התקבל", - "transaction_note_saved": "הערת פעולה נשמרה בהצלחה.", - "details_show_in_block_explorer": "צפייה בסייר בלוקים", + "details_view_in_browser": "תצוגה בדפדפן", "details_title": "פעולה", + "incoming_transaction": "פעולה נכנסת", + "outgoing_transaction": "פעולה יוצאת", + "expired_transaction": "פעולה פגה", + "pending_transaction": "פעולה ממתינה", "details_to": "פלט", "enable_offline_signing": "ארנק זה לא בשימוש בצירוף חיתום קר. האם ברצונך לאפשר זאת עכשיו?", "list_conf": "אישורים: {number}", @@ -357,8 +364,10 @@ "eta_10m": "ETA: תוך ~10 דקות", "eta_3h": "ETA: תוך ~3 שעות", "eta_1d": "ETA: תוך ~1 יום", - "view_wallet": "הצגת {walletLabel}", "list_title": "תנועות", + "list_title_sent": "נשלח", + "list_title_received": "התקבל", + "transaction": "פעולה", "open_url_error": "לא ניתן לפתוח קישור עם דפדפן ברירת המחדל. אנא שנו את דפדפן ברירת המחדל שלכם ונסו שוב", "rbf_explain": "אנו נחליף את פעולה זו בפעולה עם עמלה גבוהה יותר, כך שמהירות קבלת האישור אמורה לעלות. פעולה זאת נקראת RBF—Replace by Fee.", "rbf_title": "העלאת עמלה (RBF)", @@ -366,28 +375,59 @@ "status_cancel": "ביטול פעולה", "transactions_count": "מספר תנועות", "txid": "מזהה פעולה", - "updating": "מעדכן..." + "updating": "מעדכן...", + "watchOnlyWarningTitle": "אזהרת אבטחה", + "watchOnlyWarningDescription": "היזהרו מנוכלים אשר לעיתים קרובות משתמשים בארנקי ”צפייה-בלבד“ כדי להונות משתמשים. ארנקים אלה אינם מאפשרים לכם לשלוט בכספים או לשלוח כספים; הם מאפשרים לכם אך ורק לצפות במאזן.", + "custom_fee_warning_title": "אזהרה", + "custom_fee_warning_description": "עמלות מתחת ל- 1 sat/vB תקינות, אבל יתכן ולא ישודרו בשל מדיניות צומת.", + "offchain": "מחוץ לשרשרת", + "onchain": "בשרשרת", + "received_with_amount": "+{amt1} ({amt2})", + "details_eta_analyzing": "מנתח...", + "details_sent": "נשלח", + "details_section": "פרטים", + "details_explorer": "סייר", + "details_network_fee": "עמלת רשת", + "details_to_address": "אל", + "details_id": "מזהה", + "details_note": "הערה", + "details_add_note": "הוספה", + "details_advanced": "מתקדם", + "details_fee_rate": "שיעור עמלה", + "details_size": "גודל", + "details_virtual_size": "גודל וירטואלי", + "details_tx_hex": "hex של עסקה", + "details_inputs_count": "קלטים ({count})", + "details_outputs_count": "פלטים ({count})" }, "wallets": { "add_bitcoin": "ביטקוין", "add_bitcoin_explain": "ארנק ביטקוין פשוט וחזק", "add_create": "יצירה", - "add_entropy_generated": "{gen} ביתים של אנתרופיה", + "total_balance": "יתרה כוללת", + "add_entropy_reset_title": "איפוס אנטרופיה", + "add_entropy_reset_message": "שינוי סוג ארנק יאפס את האנטרופיה הנוכחית. האם ברצונך להמשיך?", + "add_entropy": "אנטרופיה", + "add_entropy_bytes": "{bytes} בייטים של אנטרופיה", + "add_entropy_generated": "{gen} בייטים של אנטרופיה", "add_entropy_provide": "אספקת אנטרופיה על ידי הטלת קוביות ", - "add_entropy_remain": "{gen} ביתים של אנטרופיה. שאר {rem} ביתים יתקבלו ממחולל המספרים הרנדומליים של המערכת.", + "add_entropy_remain": "{gen} בייטים של אנטרופיה. שאר {rem} בייטים יתקבלו ממחולל המספרים הרנדומליים של המערכת.", "add_import_wallet": "יבוא ארנק", "add_lightning": "ברק", "add_lightning_explain": "לבזבוז עם העברות מידיות", - "add_lndhub": "התחברו ל- LNDHub אישי", + "add_lndhub": "התחברו ל- LNDHub האישי שלכם", "add_lndhub_error": "כתובת הצומת שסופקה אינה צומת LNDHub תקין.", "add_lndhub_placeholder": "כתובת הצומת שלך", "add_placeholder": "הארנק הראשון שלי", "add_title": "הוספת ארנק", "add_wallet_name": "שם", "add_wallet_type": "סוג", - "balance": "יתרה", + "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_address": "כתובת", "details_advanced": "מתקדם", "details_are_you_sure": "האם אתם בטוחים?", @@ -397,19 +437,17 @@ "details_delete": "מחיקה", "details_delete_wallet": "מחיקת ארנק", "details_derivation_path": "נתיב גזירה", - "details_display": "הצגה ברשימת ארנקים", + "details_display": "הצגה במסך הבית", "details_export_backup": "יצוא / גיבוי", "details_export_history": "יצוא היסטוריה ל- CSV", "details_master_fingerprint": "טביעת אצבע ראשית", "details_multisig_type": "רב-חתימות", - "details_no_cancel": "לא, בטל", - "details_save": "שמירה", "details_show_xpub": "הצגת מפתח צפייה של הארנק", "details_show_addresses": "הצגת כתובות", "details_title": "ארנק", + "wallets": "ארנקים", "details_type": "סוג", "details_use_with_hardware_wallet": "שימוש עם ארנק חומרה", - "details_wallet_updated": "הארנק עודכן", "details_yes_delete": "כן, מחק", "enter_bip38_password": "הזינו סיסמה כדי לפענח", "export_title": "יצוא ארנק", @@ -425,6 +463,7 @@ "import_success_watchonly": "ארנקך יובא בהצלחה. אזהרה: זהו ארנק צפייה-בלבד, אין באפשרותך לבזבז ממנו.", "import_search_accounts": "חיפוש חשבונות", "import_title": "יבוא", + "learn_more": "למדו עוד", "import_discovery_title": "גילוי", "import_discovery_subtitle": "בחירת ארנק שהתגלה", "import_discovery_derivation": "שימוש בנתיב גזירה מותאם", @@ -432,7 +471,7 @@ "import_derivation_found": "נמצא", "import_derivation_found_not": "לא נמצא", "import_derivation_loading": "טעינה...", - "import_derivation_subtitle": "הכניסו נתיב גזירה מותאם ואנחנו ננסה לגלות את הארנק שלכם", + "import_derivation_subtitle": "הכניסו נתיב גזירה מותאם ואנחנו ננסה לגלות את הארנק שלכם.", "import_derivation_title": "נתיב גזירה", "import_derivation_unknown": "לא ידוע", "import_wrong_path": "נתיב גזירה שגוי", @@ -444,39 +483,70 @@ "list_empty_txs2": "התחילו שימוש בארנקכם.", "list_empty_txs2_lightning": "\nלהתחלת שימוש לחצו על \"ניהול כספים\" ומלאו את היתרה שלכם.", "list_latest_transaction": "פעולה אחרונה", - "list_ln_browser": "דפדפן LApp", "list_long_choose": "בחר תמונה", - "list_long_clipboard": "העתקה מלוח", + "paste_from_clipboard": "הדבק", + "import_file": "יבוא קובץ", "list_long_scan": "סריקת קוד QR", "list_title": "ארנקים", "list_tryagain": "נסו שוב", - "no_ln_wallet_error": "לפני תשלום חשבונית ברק, עלייך להוסיף ארנק ברק.", + "no_ln_wallet_error": "לפני תשלום חשבונית ברק, עליך להוסיף ארנק ברק.", "looks_like_bip38": "זה נראה כמו מפתח פרטי מוגן בסיסמה (BIP38)", - "reorder_title": "ארגון ארנקים מחדש ", - "reorder_instructions": "לחצו והחזיקו ארנק כדי לגרור אותו לאורך הרשימה.", + "manage_title": "ניהול ארנקים", + "no_results_found": "לא נמצאו תוצאות.", "please_continue_scanning": "אנא המשיכו בסריקה.", "select_no_bitcoin": "אין ארנקי ביטקוין זמינים.", "select_no_bitcoin_exp": "דרוש ארנק ביטקוין בכדי לטעון את ארנקי הברק. צרו או יבאו אחד.", "select_wallet": "בחירת ארנק", - "xpub_copiedToClipboard": "הועתק ללוח.", "pull_to_refresh": "משכו כדי לרענן", - "warning_do_not_disclose": "אזהרה! אין לחשוף.", - "add_ln_wallet_first": "עלייך להוסיף ארנק ברק קודם.", + "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_title": "מפתח צפייה של הארנק", + "manage_wallets_search_placeholder": "חיפוש ארנקים, כתובות, פעולות ותזכירים", + "more_info": "מידע נוסף", + "details_delete_anyway": "מחק בכל אופן", + "swipe_balance_hide": "הסתרה", + "swipe_balance_show": "הצג", + "drag_to_reorder": "גרור לסידור מחדש", + "clear_search": "נקה חיפוש", + "import_discovery_offline": "BlueWallet נמצא כעת במצב לא מקוון. במצב זה, לא ניתן לאמת את קיומו של הארנק, ולכן תצטרך לבחור את הארנק הנכון באופן ידני", + "details_delete_wallet_error_message": "אירעה בעיה באישור שהארנק הוסר מההתראות—הדבר עשוי לנבוע מבעיית רשת או חיבור חלש. אם תמשיך, ייתכן שתמשיך לקבל התראות על עסקאות הקשורות לארנק זה, גם לאחר מחיקתו." + }, + "total_balance_view": { + "display_in_bitcoin": "תצוגה בביטקוין", + "hide": "הסתרה", + "display_in_sats": "תצוגה בסאטושי", + "display_in_fiat": "תצוגה ב- {currency}", + "title": "יתרה כוללת", + "explanation": "הצגת המאזן הכולל של כל הארנקים שלך במסך הכללי." }, "multisig": { - "multisig_vault": "כספת", + "multisig_vault": "כספת רבת-חתימות", "default_label": "כספת רבת-חתימות", "multisig_vault_explain": "ההגנה הטובה ביותר לסכומים גדולים", "provide_signature": "ספקו חתימה", + "provide_signature_details": "השתמשו במכשיר שלכם ובארנק שבו המפתח נמצא כדי לחתום על פעולה זאת", + "provide_signature_details_bluewallet": "ב- BlueWallet, לכו לתפריט מסך השליחה ובחרו", + "provide_signature_next_steps": "סריקת או יבוא פעולה חתומה", + "provide_signature_next_steps_details": "לאחר שהארנק שלך חתם בהצלחה על העסקה, סרוק את קוד ה-QR שסופק או ייבא את הקובץ המצורף, ולאחר מכן בדוק את כל פרטי העסקה לפני שידורה.", "vault_key": "מפתח כספת {number}", "required_keys_out_of_total": " מפתחות נדרשים מתוך הסך הכולל", "fee": "עמלה: {number}", + "fee_btc": "{number} BTC", + "legacy_title": "Legacy", "confirm": "אישור", "header": "שליחה", - "share": "שיתוף", + "share": "שיתוף...", "view": "הצגה", + "shared_key_detected": "חותם-שותף משותף", + "shared_key_detected_question": "חותם-שותף שותף איתך, אם ברצונך לייבא אותו?", "manage_keys": "ניהול מפתחות", "how_many_signatures_can_bluewallet_make": "כמה חתימות ארנק BlueWallet יכול ליצור", "signatures_required_to_spend": "חתימות דרושות {number} ", @@ -504,12 +574,13 @@ "invalid_mnemonics": "צירוף מנמוני זה לא נראה תקין.", "invalid_cosigner": "נתוני חותם-שותף לא תקינים", "not_a_multisignature_xpub": "זה אינו מפתח תצוגה מארנק רב-חתימות!", - "invalid_cosigner_format": "חותם שותף שגוי: זה אינו חותם שותף לפורמט {format}", + "invalid_cosigner_format": "חותם שותף שגוי: זה אינו חותם שותף לפורמט {format}.", "create_new_key": "צרו חדש", "scan_or_open_file": "סריקה או פתיחת קובץ", "i_have_mnemonics": "יש לי גרעין למפתח זה.", "type_your_mnemonics": "הכניסו גרעין כדי לייבא את מפתח הכספת הקיים שלכם.", - "this_is_cosigners_xpub": "זה מפתח הצפייה של החותם השותף, מוכן ליבוא בארנק אחר. זה בטוח לשתף אותו.", + "this_is_cosigners_xpub": "זה מפתח הצפייה של החותם השותף, מוכן ליבוא בארנק אחר. ניתן לשתף אותו בבטחה.", + "this_is_cosigners_xpub_airdrop": "אם הנכם משתפים דרך AirDrop המקבלים חייבים להיות במסך התיאום.", "wallet_key_created": "מפתח הכספת שלכם נוצר. קחו רגע לגבות את הגרעין המנמוני שלכם בבטחה. ", "are_you_sure_seed_will_be_lost": "האם אתם בטוחים? הגרעין המנמוני שלכם יאבד אם אין ברשותכם גיבוי", "forget_this_seed": "שכח את גרעין זה והשתמש במפתח צפייה במקום.", @@ -536,30 +607,47 @@ }, "is_it_my_address": { "title": "האם זאת כתובת שלי?", - "owns": "הכתובת {address} שייכת לארנק {label} ", + "owns": "הכתובת {address} שייכת לארנק {label}", "enter_address": "הכנסת כתובת", "check_address": "בדיקת כתובת", "no_wallet_owns_address": "הכתובת שסופקה אינה שייכת לאחד מהארנקים הזמינים.", "view_qrcode": "הצגת קוד QR" }, + "autofill_word": { + "title": "מילת גרעין סופית", + "enter": "הכניסו את פסוקית המנמוני החלקית שלכם", + "generate_word": "יצירת המילה האחרונה", + "error": "קלט זה אינו מנמוני חלקי של 11 או 23 מילים. אנא נסו שוב." + }, "cc": { "change": "עודף", "coins_selected": "מטבעות נבחרו ({number})", "selected_summ": "{value} נבחרו", - "empty": "בארנק זה אין מטבעות כרגע", + "empty": "בארנק זה אין מטבעות כלל כרגע.", "freeze": "הקפאה", "freezeLabel": "הקפאה", "freezeLabel_un": "הפשרה", "header": "שליטת מטבעות", "use_coin": "שימוש במטבע", "use_coins": "שימוש במטבעות", - "tip": "מאפשר לך לראות, לתייג, להקפיא או לבחור מטבעות למען ניהול טוב יותר של הארנק." + "tip": "מאפשר לך לראות, לתייג, להקפיא או לבחור מטבעות למען ניהול טוב יותר של הארנק.", + "sort_asc": "עולה", + "sort_desc": "יורד", + "sort_height": "גובה", + "sort_value": "ערך", + "sort_label": "תווית", + "sort_status": "מצב", + "sort_by": "מיון לפי" }, "units": { - "BTC": "ביטקוין", - "MAX": "מקס'" + "MAX": "מקס'", + "BTC": "BTC", + "sat_vbyte": "sat/vByte", + "sats": "sats" }, "addresses": { + "copy_private_key": "העתקת מפתח פרטי", + "sensitive_private_key": "אזהרה: מפתחות פרטיים רגישים בצורה קיצונית. המשך?", "sign_title": "חתימת/אימות הודעה", "sign_help": "פה תוכלו ליצור או לאמת חתימה קריפטוגרפית מבוססת על כתובת ביטקוין.", "sign_sign": "חתימה", @@ -593,9 +681,24 @@ }, "bip47": { "payment_code": "קוד תשלום", - "payment_codes_list": "רשימת קודי תשלום", - "who_can_pay_me": "מי יכול לשלם לי:", + "contacts": "אנשי קשר", + "bip47_explain": "קוד רב פעמי ובר שיתוף", + "bip47_explain_subtitle": "BIP47", "purpose": "קוד רב-פעמי ובר-שיתוף (BIP47)", + "pay_this_contact": "תשלום לאיש קשר זה", + "rename_contact": "שינוי שם איש קשר", + "copy_payment_code": "העתקת קוד תשלום", + "hide_contact": "הסתרת איש קשר", + "rename": "שינוי שם", + "provide_name": "קבעו שם חדש לאיש קשר זה", + "add_contact": "הוספת איש קשר", + "provide_payment_code": "ספקו קוד תשלום", + "invalid_pc": "קוד תשלום לא תקין", + "notification_tx_unconfirmed": "פעולת התראה עדיין לא אושרה, אנא המתינו", + "failed_create_notif_tx": "נכשל ביצירת פעולת שרשרת", + "onchain_tx_needed": "נדרשת פעולת שרשרת", + "notif_tx_sent": "פעולת התראה נשלחה. אנא המתינו לאישורה", + "notif_tx": "פעולת התראה", "not_found": "קוד תשלום לא נמצא" } } diff --git a/loc/hr_hr.json b/loc/hr_hr.json index fb7e67e776b..dda5c184fb7 100644 --- a/loc/hr_hr.json +++ b/loc/hr_hr.json @@ -3,139 +3,702 @@ "bad_password": "Kriva lozinka, pokušaj ponovo", "cancel": "Otkaži", "continue": "Nastavi", + "clipboard": "Međuspremnik", + "copied": "Kopirano!", + "discard_changes": "Odbaciti promjene?", + "discard_changes_explain": "Imate nespremljene promjene. Jeste li sigurni da ih želite odbaciti i napustiti ovaj zaslon?", "enter_password": "Unesi lozinku", "never": "nikad", + "of": "{number} od {total}", "ok": "U redu", - "storage_is_encrypted": "Vaš spremnik je kriptiran. Za dekripcoju je potrebna lozinka.", + "enter_url": "Unesi URL", + "storage_is_encrypted": "Vaš spremnik je kriptiran. Za dekripciju je potrebna lozinka.", "yes": "Da", "no": "Ne", - "save": "Spremi", + "save": "Spremi...", "seed": "Izvor", - "success": "Uspjeh" + "success": "Uspjeh", + "wallet_key": "Ključ novčanika", + "close": "Zatvori", + "change_input_currency": "Promijeni valutu unosa", + "refresh": "Osvježi", + "pick_image": "Odaberi iz galerije", + "pick_file": "Odaberi datoteku", + "enter_amount": "Unesi iznos", + "qr_custom_input_button": "Tapni 10 puta za unos prilagođenog sadržaja", + "unlock": "Otključaj", + "ssl_port": "SSL port", + "suggested": "Predloženo", + "port": "Port" }, "azteco": { - "success": "Uspjeh" + "codeIs": "Tvoj kod bona je", + "errorBeforeRefeem": "Prije aktivacije morate prvo dodati Bitcoin novčanik.", + "errorSomething": "Nešto je pošlo po krivu. Je li ovaj bon još uvijek valjan?", + "redeem": "Aktiviraj u novčanik", + "redeemButton": "Aktiviraj", + "success": "Uspjeh", + "successMessage": "Bon je uspješno aktiviran! Vaša sredstva uskoro bi trebala stići u vaš Bitcoin novčanik.", + "title": "Aktiviraj Azte.co bon" }, "entropy": { - "save": "Spremi" + "save": "Spremi", + "title": "Entropija", + "undo": "Poništi", + "amountOfEntropy": "{bits} od {limit} bitova" + }, + "errors": { + "broadcast": "Objavljivanje nije uspjelo.", + "error": "Greška", + "network": "Mrežna greška" }, "lnd": { + "errorInvoiceExpired": "Faktura je istekla.", "expired": "Isteklo", + "expiresIn": "Ističe za {time} minuta", + "payButton": "Plati", + "payment": "Plaćanje", + "placeholder": "Faktura ili adresa", + "potentialFee": "Moguća naknada: {fee}", "refill": "Dopuni", - "refill_lnd_balance": "Dopuni Lightning volet saldo", - "title": "Uredi novčeke" + "refill_create": "Da biste nastavili, kreirajte Bitcoin novčanik za dopunu.", + "refill_external": "Dopuni iz vanjskog novčanika", + "refill_lnd_balance": "Dopuni saldo Lightning novčanika", + "sameWalletAsInvoiceError": "Ne možete platiti račun istim novčanikom s kojim ste ga stvorili.", + "title": "Upravljaj sredstvima" + }, + "lndViewInvoice": { + "additional_info": "Dodatne informacije", + "for": "Za:", + "lightning_invoice": "Lightning faktura", + "please_pay_between_and": "Plati iznos između {min} i {max}", + "please_pay": "Molimo plati", + "preimage": "Preimage", + "sats": "sats.", + "date_time": "Datum i vrijeme", + "wasnt_paid_and_expired": "Ova faktura nije plaćena i istekla je." }, "plausibledeniability": { - "create_fake_storage": "Stvori fejk enkriptirani spremnik", - "create_password": "Unesi lozinku", - "create_password_explanation": "Lozinka za fejk spremnik treba biti drugačija od lozinke za oriđi spremnik", - "help": "Pazi. Netko gadan te može u iznimnim okolnostima (pljačka, prijevremeni izbori, itd.) brutalno pritisnuti da mu otkriješ lozinku za svoj volet. BlueWallet ti čuva leđa buraz. Nemaš brige. Gledaj, stvoriti ćemo fejk volet sa drugačijom lozinkom. Haha, žišku? Pa kad se ovaj počne pjeniti, a ti vidiš da je vrag odnio šalu, samo mu podvali lozinku za ovaj drugi volet. Eto mu ga. Nek si cucla. ", - "help2": "Novi spremnik će biti posve funkcionalan, možeš pohraniti koliko misliš da je potrebno da izgleda uvjerljivo.", - "password_should_not_match": "Lozinka za fejk spremnik treba biti drugačija od lozinke za oriđi spremnik", - "passwords_do_not_match": "Lozinke ne pašu, pokušaj ponovo", - "retype_password": "Ponovi lozinku", - "success": "Uspjeh", - "title": "Fejk volet" + "create_fake_storage": "Stvori lažni enkriptirani spremnik", + "create_password_explanation": "Lozinka za lažni spremnik treba biti drugačija od lozinke za glavni spremnik.", + "help": "U određenim okolnostima možete biti prisiljeni otkriti lozinku. Kako bi vaši coini ostali sigurni, BlueWallet može stvoriti drugi enkriptirani spremnik s drugom lozinkom. Pod pritiskom možete otkriti tu lozinku trećoj strani. Ako se unese u BlueWallet, otključat će novi „lažni“ spremnik. To će se činiti uvjerljivim trećoj strani, ali vaš glavni spremnik s coinima ostat će tajno siguran.", + "help2": "Novi spremnik bit će potpuno funkcionalan i u njega možete pohraniti manje iznose kako bi izgledao uvjerljivije.", + "password_should_not_match": "Lozinka je trenutno u uporabi. Pokušajte s drugom lozinkom.", + "title": "Uvjerljivo poricanje" + }, + "pleasebackup": { + "ask": "Jeste li spremili sigurnosnu frazu novčanika? Ova fraza je potrebna za pristup vašim sredstvima ako izgubite uređaj. Bez nje vaša sredstva bit će trajno izgubljena.", + "ask_no": "Ne, nisam.", + "ask_yes": "Da, jesam.", + "ok": "U redu, zapisao sam.", + "ok_lnd": "U redu, spremio sam.", + "text": "Odvojite trenutak i zapišite ovu mnemoničku frazu na papir.\nOna je vaša sigurnosna kopija i možete je iskoristiti za obnovu novčanika.", + "text_lnd": "Molimo spremite ovu sigurnosnu kopiju novčanika. Omogućuje vam obnovu novčanika u slučaju gubitka.", + "title": "Vaš novčanik je kreiran." }, "receive": { "details_create": "Stvori", "details_label": "Opis", "details_setAmount": "Odredi iznos za primiti", - "details_share": "podijeli", - "header": "Primi" + "details_share": "Podijeli...", + "address_not_found": "Nije moguće generirati adresu za primanje.", + "header": "Primi", + "reset": "Poništi", + "maxSats": "Najveći iznos je {max} sats", + "maxSatsFull": "Najveći iznos je {max} sats ili {currency}", + "minSats": "Najmanji iznos je {min} sats", + "minSatsFull": "Najmanji iznos je {min} sats ili {currency}", + "qrcode_for_the_address": "QR kod za adresu", + "bip47_explanation": "Kodovi plaćanja su univerzalna adresa koja izbjegava otkrivanje adresa vašeg novčanika. Ne podržavaju ih sve usluge." }, "send": { + "provided_address_is_invoice": "Ova adresa izgleda kao Lightning faktura. Molimo otvorite svoj Lightning novčanik kako biste platili ovu fakturu.", "broadcastButton": "Objavi", + "broadcastError": "Greška", + "broadcastNone": "Unesi hex transakcije", + "broadcastPending": "U tijeku", "broadcastSuccess": "Uspjeh", "confirm_header": "Potvrdi", "confirm_sendNow": "Pošalji sad", "create_amount": "Iznos", "create_broadcast": "Objavi", + "create_copy": "Kopiraj i objavi kasnije", "create_details": "Detalji", - "create_fee": "Trošak slanja", + "create_fee": "Naknada za slanje", "create_memo": "Bilješka", - "create_this_is_hex": "Ovoj je hex transakcije, potpisan i spreman za objavljivanje na mrežu.", + "create_satoshi_per_vbyte": "Satoshi po vByte", + "create_this_is_hex": "Ovo je hex vaše transakcije—potpisana i spremna za objavu na mrežu.", "create_to": "Za", "create_tx_size": "TX veličina", + "create_verify": "Provjeri na coinb.in", + "details_insert_contact": "Umetni kontakt", + "details_add_rec_add": "Dodaj primatelja", + "details_add_rec_rem": "Ukloni primatelja", + "details_add_recc_rem_all_alert_description": "Jeste li sigurni da želite ukloniti sve primatelje?", + "details_add_rec_rem_all": "Ukloni sve primatelje", + "details_recipients_title": "Primatelji", + "details_recipient_title": "Primatelj #{number} od #{total}", + "please_complete_recipient_title": "Nepotpun primatelj", + "please_complete_recipient_details": "Molimo popunite podatke o primatelju #{number} prije dodavanja novog primatelja.", "details_address": "adresa", "details_address_field_is_not_valid": "Polje adrese nije ispravno", + "details_adv_fee_bump": "Dopusti povećanje naknade", + "details_adv_full": "Koristi cijeli saldo", + "details_adv_full_sure": "Jeste li sigurni da želite iskoristiti cijeli saldo novčanika za ovu transakciju?", + "details_adv_full_sure_frozen": "Jeste li sigurni da želite iskoristiti cijeli saldo novčanika za ovu transakciju? Imajte na umu da zamrznute kovanice nisu uključene.", + "details_adv_import": "Uvezi transakciju", + "details_adv_import_qr": "Uvezi transakciju (QR)", "details_amount_field_is_not_valid": "Iznos nije ispravan", + "details_amount_field_is_less_than_minimum_amount_sat": "Navedeni iznos je premalen. Molimo unesite iznos veći od 500 sats.", "details_create": "Stvori", + "details_error_decode": "Nije moguće dekodirati Bitcoin adresu", "details_fee_field_is_not_valid": "Ovo polje nije ispravno", + "details_frozen": "{amount} BTC je zamrznuto.", + "details_next": "Dalje", + "details_no_signed_tx": "Odabrana datoteka ne sadrži transakciju koja se može uvesti.", "details_note_placeholder": "bilješka za evidenciju", "details_scan": "Skeniraj", + "details_scan_hint": "Dvostruki tap za skeniranje ili uvoz odredišta", + "details_scan_error": "Greška pri skeniranju", "details_total_exceeds_balance": "Iznos je veći od raspoloživog.", + "details_total_exceeds_balance_frozen": "Iznos za slanje premašuje raspoloživi saldo. Imajte na umu da zamrznute kovanice nisu uključene.", + "details_unrecognized_file_format": "Neprepoznat format datoteke", + "details_wallet_before_tx": "Prije nego što kreirate transakciju, morate dodati Bitcoin novčanik.", + "dynamic_init": "Inicijalizacija", + "dynamic_next": "Dalje", + "dynamic_prev": "Natrag", + "dynamic_start": "Pokreni", + "dynamic_stop": "Zaustavi", + "fee_custom": "Prilagođeno", + "insert_custom_fee": "Unesi naknadu", + "fee_fast": "Brzo", + "fee_medium": "Srednje", + "fee_replace_minvb": "Ukupna stopa naknade (satoshi po vByte) koju želite platiti mora biti veća od {min} sat/vByte.", + "fee_satvbyte": "u sat/vByte", + "fee_slow": "Sporo", + "fee_10m": "10 min", + "fee_3h": "3 h", + "fee_1d": "1 d", "header": "Šalji", + "input_clear": "Očisti", "input_done": "U redu", - "success_done": "U redu" + "input_paste": "Zalijepi", + "input_total": "Ukupno:", + "permission_camera_message": "Potrebno nam je vaše dopuštenje za korištenje kamere.", + "psbt_sign": "Potpiši transakciju", + "invalid_psbt": "Nevažeći PSBT.", + "open_settings": "Otvori postavke", + "permission_storage_denied_message": "BlueWallet ne može spremiti ovu datoteku. Molimo otvorite postavke uređaja i omogućite dopuštenje za pohranu.", + "permission_storage_title": "Dopuštenje za pristup pohrani", + "psbt_clipboard": "Kopiraj u međuspremnik", + "psbt_this_is_psbt": "Ovo je djelomično potpisana Bitcoin transakcija (PSBT). Molimo dovršite potpisivanje hardverskim novčanikom.", + "psbt_tx_export": "Izvezi u datoteku", + "no_tx_signing_in_progress": "Nema potpisivanja transakcije u tijeku.", + "outdated_rate": "Tečaj je posljednji put ažuriran: {date}", + "psbt_tx_open": "Otvori potpisanu transakciju", + "psbt_tx_scan": "Skeniraj potpisanu transakciju", + "qr_error_no_qrcode": "Nismo mogli pronaći valjan QR kod na odabranoj slici. Provjerite da slika sadrži samo QR kod i ništa drugo poput teksta ili gumba.", + "reset_amount": "Poništi iznos", + "reset_amount_confirm": "Želite li poništiti iznos?", + "success_done": "U redu", + "txSaved": "Datoteka transakcije ({filePath}) je spremljena.", + "file_saved_at_path": "Datoteka ({filePath}) je spremljena.", + "cant_send_to_silentpayment_adress": "Ovaj novčanik ne može slati na Silent Payment adrese", + "cant_send_to_bip47": "Ovaj novčanik ne može slati na BIP47 kodove plaćanja", + "cant_find_bip47_notification": "Najprije dodajte ovaj kod plaćanja u kontakte", + "problem_with_psbt": "Problem s PSBT-om" }, "settings": { "about": "Informacije", + "about_awesome": "Izgrađeno uz pomoć", + "about_backup": "Uvijek napravite sigurnosnu kopiju svojih ključeva!", + "about_free": "BlueWallet je besplatan projekt otvorenog koda. Izradili su ga Bitcoin korisnici.", + "about_license": "MIT licenca", + "about_release_notes": "Bilješke o izdanju", + "about_review": "Ostavite nam recenziju", + "performance_score": "Ocjena izvedbe: {num}", + "run_performance_test": "Testiraj izvedbu", + "about_selftest": "Pokreni samotestiranje", + "block_explorer_invalid_custom_url": "Navedeni URL nije valjan. Molimo unesite valjan URL koji počinje s http:// ili https://.", + "about_selftest_electrum_disabled": "Samotestiranje nije dostupno u Electrum offline načinu rada. Molimo isključite offline način i pokušajte ponovno.", + "about_selftest_ok": "Svi unutarnji testovi su uspješno prošli. Novčanik radi ispravno.", + "about_sm_github": "GitHub", + "about_sm_telegram": "Telegram kanal", + "privacy_temporary_screenshots": "Dopusti snimanje zaslona", + "privacy_temporary_screenshots_instructions": "Zaštita od snimanja zaslona bit će privremeno isključena, što omogućuje snimanje zaslona i snimanje videozapisa zaslona. Zaštita će se automatski ponovno aktivirati kada zatvorite i ponovno otvorite BlueWallet.", + "biometrics": "Biometrija", + "biometrics_no_longer_available": "Postavke uređaja su se promijenile i više ne odgovaraju odabranim sigurnosnim postavkama u aplikaciji. Molimo ponovno omogućite biometriju ili kod za otključavanje, a zatim ponovno pokrenite aplikaciju kako bi promjene stupile na snagu.", + "biom_10times": "Pokušali ste unijeti lozinku 10 puta. Želite li resetirati svoj spremnik? Time ćete ukloniti sve novčanike i dešifrirati spremnik.", + "biom_conf_identity": "Molimo potvrdite svoj identitet.", + "biom_no_passcode": "Vaš uređaj nema omogućen kod za otključavanje ni biometriju. Da biste nastavili, molimo postavite kod za otključavanje ili biometriju u postavkama uređaja.", + "biom_remove_decrypt": "Svi vaši novčanici bit će uklonjeni, a vaš spremnik bit će dešifriran. Jeste li sigurni da želite nastaviti?", "currency": "Valuta", - "electrum_clear_alert_cancel": "Otkaži", - "general_adv_mode": "Enable advanced mode", + "currency_source": "Tečaj se dohvaća s", + "currency_fetch_error": "Došlo je do greške pri dohvaćanju tečaja za odabranu valutu.", + "default_title": "Pri pokretanju", + "donate": "Doniraj", + "donate_description": "Pomozite nam da Blue ostane besplatan!", + "electrum_connected": "Povezano", + "electrum_connected_not": "Nije povezano", + "electrum_error_connect": "Nije moguće povezati se s navedenim Electrum poslužiteljem", + "electrum_error_connect_tor": "Nije moguće povezati se s navedenim Electrum poslužiteljem. Molimo provjerite da je Orbot aplikacija povezana i pokušajte ponovno.", + "lndhub_uri": "Npr., {example}", + "electrum_host": "Npr., {example}", + "electrum_offline_mode": "Offline način rada", + "electrum_offline_description": "Kada je omogućeno, vaši Bitcoin novčanici neće pokušavati dohvatiti salde ili transakcije.", + "electrum_port": "Port, obično {example}", + "use_ssl": "Koristi SSL", + "electrum_saved": "Vaše promjene su uspješno spremljene. Možda će biti potrebno ponovno pokrenuti BlueWallet kako bi promjene stupile na snagu.", + "set_electrum_server_as_default": "Postaviti {server} kao zadani Electrum poslužitelj?", + "set_lndhub_as_default": "Postaviti {url} kao zadani LNDhub poslužitelj?", + "electrum_settings_server": "Electrum poslužitelj", + "electrum_status": "Status", + "electrum_preferred_server": "Preferirani poslužitelj", + "electrum_preferred_server_description": "Unesite poslužitelj koji želite da vaš novčanik koristi za sve Bitcoin aktivnosti. Nakon postavljanja, vaš novčanik koristit će isključivo ovaj poslužitelj za provjeru salda, slanje transakcija i dohvaćanje mrežnih podataka. Provjerite da imate povjerenja u ovaj poslužitelj prije nego što ga postavite.", + "electrum_unable_to_connect": "Nije moguće povezati se s {server}.", + "electrum_history": "Povijest", + "electrum_reset_to_default": "Time ćete dopustiti BlueWalletu da nasumično odabere poslužitelj s popisa poslužitelja.", + "electrum_reset": "Vrati na zadano", + "electrum_reset_to_default_and_clear_history": "Vrati na zadano i obriši povijest", + "encrypt_decrypt": "Dešifriraj spremnik", + "encrypt_decrypt_q": "Jeste li sigurni da želite dešifrirati svoj spremnik? Time će se omogućiti pristup vašim novčanicima bez lozinke.", + "encrypt_enc_and_pass": "Zaštićeno lozinkom", + "encrypt_storage_explanation_headline": "Omogući šifriranje spremnika", + "encrypt_storage_explanation_description_line1": "Omogućavanjem šifriranja spremnika dodajete dodatni sloj zaštite svojoj aplikaciji osiguravanjem načina na koji se vaši podaci pohranjuju na uređaju. Time se otežava neovlašteni pristup vašim podacima.", + "encrypt_storage_explanation_description_line2": "Međutim, važno je znati da ovo šifriranje štiti samo pristup vašim novčanicima pohranjenima u privjesku ključeva uređaja. Ne stavlja lozinku niti dodatnu zaštitu na same novčanike.", + "i_understand": "Razumijem", + "block_explorer": "Preglednik blokova", + "block_explorer_preferred": "Koristi preferirani preglednik blokova", + "block_explorer_error_saving_custom": "Greška pri spremanju preferiranog preglednika blokova", + "encrypt_title": "Sigurnost", + "encrypt_tstorage": "Spremnik", + "encrypt_use": "Koristi {type}", + "set_as_preferred": "Postavi kao preferirano", + "set_as_preferred_electrum": "Postavljanjem {host}:{port} kao preferiranog poslužitelja onemogućit ćete nasumično povezivanje s predloženim poslužiteljem.", + "encrypted_feature_disabled": "Ovu značajku nije moguće koristiti dok je omogućeno šifriranje spremnika.", + "encrypt_use_expl": "{type} će se koristiti za potvrdu vašeg identiteta prije izvođenja transakcije, otključavanja, izvoza ili brisanja novčanika.", + "biometrics_fail": "Ako {type} nije omogućeno ili otključavanje ne uspije, možete koristiti kod za otključavanje uređaja kao alternativu.", + "general": "Općenito", + "general_continuity": "Kontinuitet", + "general_continuity_e": "Kada je omogućeno, moći ćete pregledati odabrane novčanike i transakcije pomoću drugih Apple iCloud povezanih uređaja.", + "groundcontrol_explanation": "GroundControl je besplatan, open-source poslužitelj za push obavijesti za Bitcoin novčanike. Možete instalirati svoj vlastiti GroundControl poslužitelj i ovdje unijeti njegov URL kako ne biste ovisili o infrastrukturi BlueWalleta. Ostavite prazno za korištenje zadanog GroundControl poslužitelja.", "header": "Postavke", "language": "Jezik", + "last_updated": "Posljednje ažuriranje", + "language_isRTL": "Potrebno je ponovno pokrenuti BlueWallet kako bi orijentacija jezika stupila na snagu.", + "license": "Licenca", + "lightning_error_lndhub_uri": "Neispravan LNDhub URI", + "lightning_error_lndhub_uri_tor": "Neispravan LNDhub URI. Molimo provjerite da je Orbot aplikacija povezana i pokušajte ponovno.", + "lightning_saved": "Vaše promjene su uspješno spremljene.", "lightning_settings": "Lightning postavke", + "lightning_settings_explain": "Za povezivanje s vlastitim LND čvorom, molimo instalirajte LNDhub i unesite njegov URL ovdje u postavkama. Imajte na umu da će se samo novčanici stvoreni nakon spremanja promjena povezati s navedenim LNDhubom.", + "lndhub_github": "GitHub repozitorij", + "network": "Mreža", + "network_broadcast": "Objavi transakciju", + "network_electrum": "Electrum poslužitelj", + "electrum_suggested_description": "Kada preferirani poslužitelj nije postavljen, predloženi poslužitelj odabire se nasumično.", + "not_a_valid_uri": "Neispravan URI", + "notifications": "Obavijesti", + "open_link_in_explorer": "Otvori poveznicu u pregledniku", "password": "Lozinka", - "password_explain": "Upiši lozinku koja će dekriptirati spremnik.", - "passwords_do_not_match": "Lozinke su različite", - "plausible_deniability": "Fejk volet...", - "retype_password": "Ponovi lozinku", - "save": "Spremi" + "password_explain": "Unesite lozinku koju ćete koristiti za otključavanje svog spremnika.", + "plausible_deniability": "Uvjerljivo poricanje...", + "privacy": "Privatnost", + "privacy_read_clipboard": "Čitaj međuspremnik", + "privacy_system_settings": "Postavke sustava", + "privacy_quickactions": "Prečaci novčanika", + "privacy_quickactions_explanation": "Dodirnite i držite ikonu BlueWallet aplikacije za brzi pregled salda novčanika.", + "privacy_clipboard_explanation": "Pruža prečace ako se u međuspremniku pronađe adresa ili faktura.", + "privacy_do_not_track": "Isključi analitiku", + "privacy_do_not_track_explanation": "Informacije o izvedbi i pouzdanosti neće biti poslane na analizu.", + "rate": "Tečaj", + "push_notifications_explanation": "Omogućavanjem obavijesti, token vašeg uređaja bit će poslan na poslužitelj zajedno s adresama novčanika i ID-jevima transakcija za sve novčanike i transakcije obavljene nakon omogućavanja obavijesti. Token uređaja koristi se za slanje obavijesti, a podaci o novčaniku omogućuju nam da vas obavijestimo o pristiglom Bitcoinu ili potvrdama transakcija.\n\nPrenose se samo informacije nakon što omogućite obavijesti — ništa od prije se ne prikuplja.\n\nIsključivanje obavijesti uklonit će sve ove informacije s poslužitelja. Osim toga, brisanjem novčanika iz aplikacije također ćete s poslužitelja ukloniti njegove povezane informacije.", + "selfTest": "Samotestiranje", + "save": "Spremi", + "saved": "Spremljeno", + "success_transaction_broadcasted": "Vaša transakcija je uspješno objavljena!", + "total_balance": "Ukupni saldo", + "total_balance_explanation": "Prikaži ukupni saldo svih svojih novčanika na widgetima početnog zaslona.", + "widgets": "Widgeti", + "tools": "Alati" + }, + "notifications": { + "would_you_like_to_receive_notifications": "Želite li primati obavijesti kada primite dolazna plaćanja?", + "notifications_subtitle": "Dolazna plaćanja i potvrde transakcija", + "no_and_dont_ask": "Ne, i nemoj me više pitati.", + "permission_denied_message": "Odbili ste dopuštenje za slanje obavijesti. Ako želite primati obavijesti, omogućite ih u postavkama uređaja." }, "transactions": { + "cancel_explain": "Zamijenit ćemo ovu transakciju onom koja vam plaća i ima višu naknadu. Time se učinkovito poništava trenutna transakcija. Ovo se zove RBF — zamjena naknadom.", + "cancel_no": "Ova transakcija nije zamjenjiva.", + "cancel_title": "Otkaži ovu transakciju (RBF)", + "transaction_loading_error": "Došlo je do problema pri učitavanju transakcije. Pokušajte ponovno kasnije.", + "transaction_not_available": "Transakcija nije dostupna", + "confirmations_lowercase": "{confirmations} potvrda", + "expand_note": "Proširi bilješku", "cpfp_create": "Stvori", + "cpfp_exp": "Kreirat ćemo još jednu transakciju koja troši vašu nepotvrđenu transakciju. Ukupna naknada bit će veća od izvorne naknade transakcije, pa bi trebala biti brže rudarena. Ovo se zove CPFP — dijete plaća za roditelja.", + "cpfp_no_bump": "Ovoj transakciji nije moguće povećati naknadu.", + "cpfp_title": "Povećaj naknadu (CPFP)", + "details_balance_hide": "Sakrij saldo", + "details_balance_show": "Prikaži saldo", "details_copy": "Kopiraj", - "details_from": "Od", - "details_show_in_block_explorer": "Prikaži u blok eksploreru", + "details_copy_block_explorer_link": "Kopiraj poveznicu preglednika blokova", + "details_copy_note": "Kopiraj bilješku", + "details_copy_txid": "Kopiraj ID transakcije", + "details_inputs": "Ulazi", + "details_outputs": "Izlazi", + "date": "Datum", + "details_received": "Primljeno", + "details_view_in_browser": "Pogledaj u pregledniku", "details_title": "Transakcija", + "incoming_transaction": "Dolazna transakcija", + "outgoing_transaction": "Odlazna transakcija", + "expired_transaction": "Istekla transakcija", + "pending_transaction": "Transakcija u tijeku", + "offchain": "Off-chain", + "onchain": "On-chain", "details_to": "Za", - "list_title": "transakcije" + "enable_offline_signing": "Ovaj novčanik se ne koristi u kombinaciji s offline potpisivanjem. Želite li ga sada omogućiti?", + "list_conf": "Potvrde: {number}", + "pending": "U tijeku", + "pending_with_amount": "U tijeku {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "Procjena: za ~10 minuta", + "eta_3h": "Procjena: za ~3 sata", + "eta_1d": "Procjena: za ~1 dan", + "list_title": "transakcije", + "list_title_sent": "Poslano", + "list_title_received": "Primljeno", + "transaction": "Transakcija", + "open_url_error": "Nije moguće otvoriti poveznicu zadanim preglednikom. Promijenite svoj zadani preglednik i pokušajte ponovno.", + "rbf_explain": "Zamijenit ćemo ovu transakciju onom s većom naknadom kako bi se brže rudarila. Ovo se zove RBF — zamjena naknadom.", + "rbf_title": "Ubrzaj (RBF)", + "status_bump": "Ubrzaj", + "status_cancel": "Otkaži", + "transactions_count": "Broj transakcija", + "txid": "ID transakcije", + "updating": "Ažuriranje...", + "watchOnlyWarningTitle": "Sigurnosno upozorenje", + "watchOnlyWarningDescription": "Budite oprezni s prevarantima koji često koriste promatračke (watch-only) novčanike kako bi prevarili korisnike. Ovi novčanici ne omogućuju vam kontrolu niti slanje sredstava; omogućuju samo prikaz salda.", + "custom_fee_warning_title": "Upozorenje", + "custom_fee_warning_description": "Naknade niže od 1 sat/vB su valjane, ali ih čvorovi možda neće preusmjeravati zbog svojih pravila.", + "details_eta_analyzing": "Analiziranje...", + "details_sent": "Poslano", + "details_section": "Detalji", + "details_explorer": "preglednik", + "details_network_fee": "Mrežna naknada", + "details_to_address": "Za", + "details_id": "ID", + "details_note": "Bilješka", + "details_add_note": "dodaj", + "details_advanced": "Napredno", + "details_fee_rate": "Stopa naknade", + "details_size": "Veličina", + "details_virtual_size": "Virtualna veličina", + "details_tx_hex": "Tx hex", + "details_inputs_count": "Ulazi ({count})", + "details_outputs_count": "Izlazi ({count})" }, "wallets": { + "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Jednostavan i moćan Bitcoin novčanik", "add_create": "Stvori", - "add_import_wallet": "Unesi vanjski volet", - "add_title": "Dodaj volet", - "add_wallet_name": "ime voleta:", + "total_balance": "Ukupni saldo", + "add_entropy_reset_title": "Poništi entropiju", + "add_entropy_reset_message": "Promjenom tipa novčanika poništit ćete trenutnu entropiju. Želite li nastaviti?", + "add_entropy": "Entropija", + "add_entropy_bytes": "{bytes} bajtova entropije", + "add_entropy_generated": "{gen} bajtova generirane entropije", + "add_entropy_provide": "Dobavite entropiju bacanjem kockica", + "add_entropy_remain": "{gen} bajtova generirane entropije. Preostalih {rem} bajtova bit će dohvaćeno iz generatora slučajnih brojeva sustava.", + "add_import_wallet": "Uvezi novčanik", + "add_lightning": "Lightning", + "add_lightning_explain": "Za trošenje trenutnim transakcijama", + "add_lndhub": "Poveži se sa svojim LNDhubom", + "add_lndhub_error": "Navedena adresa čvora nije valjan LNDhub čvor.", + "add_lndhub_placeholder": "Adresa vašeg čvora", + "add_placeholder": "moj prvi novčanik", + "add_title": "Dodaj novčanik", + "add_wallet_name": "Ime", "add_wallet_type": "tip:", + "add_wallet_seed_length": "Duljina sigurnosne fraze", + "add_wallet_seed_length_12": "12 riječi", + "add_wallet_seed_length_24": "24 riječi", + "clipboard_bitcoin": "U međuspremniku imate Bitcoin adresu. Želite li je iskoristiti za transakciju?", + "clipboard_lightning": "U međuspremniku imate Lightning fakturu. Želite li je iskoristiti za transakciju?", + "clear_clipboard_on_import": "Očisti međuspremnik pri uvozu", "details_address": "Adresa", - "details_are_you_sure": "Jesi li ziher?", + "details_advanced": "Napredno", + "details_are_you_sure": "Jeste li sigurni?", + "details_connected_to": "Povezano s", + "details_del_wb_err": "Navedeni iznos salda ne odgovara saldu ovog novčanika. Pokušajte ponovno.", + "details_del_wb_q": "Ovaj novčanik ima saldo. Prije nastavka, imajte na umu da nećete moći vratiti sredstva bez sigurnosne fraze ovog novčanika. Kako biste izbjegli slučajno uklanjanje, unesite saldo novčanika od {balance} satoshija.", "details_delete": "Obriši", - "details_export_backup": "Izvoz / bekap", - "details_no_cancel": "Ne, otiaži", - "details_save": "Spremi", - "details_show_xpub": "Prikaži voletov XPUB", - "details_title": "Volet", + "details_delete_wallet": "Obriši novčanik", + "details_derivation_path": "put derivacije", + "details_display": "Prikaži na početnom zaslonu", + "details_export_backup": "Izvoz / sigurnosna kopija", + "details_export_history": "Izvezi povijest u CSV", + "details_master_fingerprint": "Otisak glavnog ključa", + "details_multisig_type": "multisig", + "details_show_xpub": "Prikaži XPUB novčanika", + "details_show_addresses": "Prikaži adrese", + "details_title": "Novčanik", + "wallets": "Novčanici", + "swipe_balance_hide": "Sakrij", + "swipe_balance_show": "Prikaži", + "drag_to_reorder": "Povucite za promjenu redoslijeda", + "clear_search": "Očisti pretragu", "details_type": "Tip", - "details_yes_delete": "Da, briši", - "export_title": "izvoz voleta", - "import_do_import": "Unesi", - "import_error": "Neuspješan unos. Molimo pažljivo provjerite ispravnost unesenih podataka.", - "import_imported": "Uneseno", + "details_use_with_hardware_wallet": "Koristi s hardverskim novčanikom", + "details_yes_delete": "Da, obriši", + "enter_bip38_password": "Unesi lozinku za dešifriranje", + "export_title": "Izvoz novčanika", + "import_do_import": "Uvezi", + "import_passphrase": "Kodna fraza", + "import_passphrase_title": "Kodna fraza", + "import_passphrase_message": "Unesite kodnu frazu ako ste je koristili", + "import_error": "Neuspješan uvoz. Molimo pažljivo provjerite ispravnost unesenih podataka.", + "import_explanation": "Molimo unesite svoje riječi sigurnosne fraze, javni ključ, WIF ili bilo što što imate. BlueWallet će dati sve od sebe da pogodi ispravan format i uveze vaš novčanik.", + "import_imported": "Uvezeno", "import_scan_qr": "ili skeniraj QR kod?", "import_success": "Uspjeh", - "import_title": "unesi", + "import_success_watchonly": "Vaš novčanik je uspješno uvezen. UPOZORENJE: Ovo je promatrački (watch-only) novčanik, NE MOŽETE trošiti iz njega.", + "import_search_accounts": "Pretraži račune", + "import_title": "uvezi", + "learn_more": "Saznaj više", + "import_discovery_title": "Otkrivanje", + "import_discovery_subtitle": "Odaberite otkriveni novčanik", + "import_discovery_derivation": "Koristi prilagođeni put derivacije", + "import_discovery_no_wallets": "Nije pronađen nijedan novčanik.", + "import_discovery_offline": "BlueWallet je trenutno u offline načinu rada. U ovom načinu ne može provjeriti postojanje novčanika, pa morate ručno odabrati ispravan.", + "import_derivation_found": "Pronađeno", + "import_derivation_found_not": "Nije pronađeno", + "import_derivation_loading": "Učitavanje...", + "import_derivation_subtitle": "Unesite prilagođeni put derivacije i pokušat ćemo otkriti vaš novčanik.", + "import_derivation_title": "Put derivacije", + "import_derivation_unknown": "Nepoznato", + "import_wrong_path": "Pogrešan put derivacije", "list_create_a_button": "dodaj sada", - "list_create_a_wallet": "Stvori novi volet", + "list_create_a_wallet": "Dodaj novčanik", + "list_create_a_wallet_text": "Besplatno je i možete kreirati \nkoliko god želite.", "list_empty_txs1": "Vaše transakcije će se pojaviti ovdje", + "list_empty_txs1_lightning": "Lightning novčanik treba koristiti za svakodnevne transakcije. Naknade su nepravedno niske, a brzina je vrtoglava.", + "list_empty_txs2": "Započnite sa svojim novčanikom.", + "list_empty_txs2_lightning": "\nDa biste ga počeli koristiti, tapnite na Uredi novčeke i dopunite saldo.", "list_latest_transaction": "posljednja transakcija", - "list_title": "Voleti", - "reorder_title": "Uredi volete", - "select_wallet": "Odaberi volet", - "xpub_copiedToClipboard": "Kopirano u međuspremnik.", - "xpub_title": "volet XPUB" + "list_long_choose": "Odaberi fotografiju", + "paste_from_clipboard": "Zalijepi", + "import_file": "Uvezi datoteku", + "list_long_scan": "Skeniraj QR kod", + "list_title": "Novčanici", + "list_tryagain": "Pokušaj ponovno", + "no_ln_wallet_error": "Prije plaćanja Lightning fakture morate dodati Lightning novčanik.", + "looks_like_bip38": "Ovo izgleda kao privatni ključ zaštićen lozinkom (BIP38).", + "manage_title": "Uredi novčanike", + "no_results_found": "Nema rezultata.", + "please_continue_scanning": "Molimo nastavite sa skeniranjem.", + "select_no_bitcoin": "Trenutno nema dostupnih Bitcoin novčanika.", + "select_no_bitcoin_exp": "Bitcoin novčanik je potreban za dopunu Lightning novčanika. Molimo kreirajte ili uvezite jedan.", + "select_wallet": "Odaberi novčanik", + "pull_to_refresh": "Povuci za osvježavanje", + "warning_do_not_disclose": "Nikada ne dijelite informacije ispod", + "scan_import": "Skenirajte ovaj QR kod kako biste uvezli svoj novčanik u drugu aplikaciju.", + "write_down_header": "Kreiraj ručnu sigurnosnu kopiju", + "write_down": "Zapišite i sigurno pohranite ove riječi. Iskoristite ih za obnovu novčanika kasnije.", + "wallet_type_this": "Tip ovog novčanika je {type}.", + "share_number": "Podijeli {number}", + "copy_ln_url": "Kopirajte i sigurno pohranite ovaj URL za obnovu novčanika kasnije.", + "copy_ln_public": "Kopirajte i sigurno pohranite ove informacije za obnovu novčanika kasnije.", + "add_ln_wallet_first": "Najprije morate dodati Lightning novčanik.", + "identity_pubkey": "Identity Pubkey", + "xpub_title": "XPUB novčanika", + "manage_wallets_search_placeholder": "Pretraži novčanike, adrese, transakcije i bilješke", + "more_info": "Više informacija", + "details_delete_wallet_error_message": "Došlo je do problema pri potvrdi je li ovaj novčanik uklonjen iz obavijesti — to može biti zbog mrežnog problema ili loše veze. Ako nastavite, možda ćete i dalje primati obavijesti za transakcije povezane s ovim novčanikom, čak i nakon što je izbrisan.", + "details_delete_anyway": "Ipak obriši" + }, + "total_balance_view": { + "display_in_bitcoin": "Prikaži u bitcoinima", + "hide": "Sakrij", + "display_in_sats": "Prikaži u sats", + "display_in_fiat": "Prikaži u {currency}", + "title": "Ukupni saldo", + "explanation": "Pogledajte ukupni saldo svih svojih novčanika na ekranu pregleda." }, "multisig": { + "multisig_vault": "Multisig trezor", + "default_label": "Multisig trezor", + "multisig_vault_explain": "Najbolja sigurnost za veće iznose", + "provide_signature": "Priloži potpis", + "provide_signature_details": "Koristite svoj uređaj i novčanik gdje se nalazi ključ kako biste potpisali ovu transakciju", + "provide_signature_details_bluewallet": "U BlueWalletu otvorite izbornik zaslona Šalji i odaberite ", + "provide_signature_next_steps": "Skeniraj ili uvezi potpisanu transakciju", + "provide_signature_next_steps_details": "Nakon što vaš novčanik uspješno potpiše transakciju, skenirajte priloženi QR kod ili uvezite popratnu datoteku, a zatim pregledajte sve detalje transakcije prije objavljivanja.", + "vault_key": "Ključ trezora {number}", + "required_keys_out_of_total": "Potrebni ključevi od ukupnog broja", + "fee": "Naknada: {number}", + "fee_btc": "{number} BTC", "confirm": "Potvrdi", "header": "Šalji", - "share": "podijeli", + "share": "Podijeli...", + "view": "Pogledaj", + "shared_key_detected": "Podijeljeni supotpisnik", + "shared_key_detected_question": "Supotpisnik je podijeljen s vama, želite li ga uvesti?", + "manage_keys": "Uredi ključeve", + "how_many_signatures_can_bluewallet_make": "koliko potpisa može BlueWallet napraviti", + "signatures_required_to_spend": "Potrebnih potpisa {number}", + "signatures_we_can_make": "može napraviti {number}", + "scan_or_import_file": "Skeniraj ili uvezi datoteku", + "export_coordination_setup": "Izvezi postavku koordinacije", + "cosign_this_transaction": "Suportpisati ovu transakciju?", + "lets_start": "Krenimo", "create": "Stvori", - "ms_help_title5": "Enable advanced mode" + "native_segwit_title": "Najbolja praksa", + "wrapped_segwit_title": "Najbolja kompatibilnost", + "legacy_title": "Legacy", + "co_sign_transaction": "Potpiši transakciju", + "what_is_vault": "Trezor je", + "what_is_vault_numberOfWallets": " {m}-od-{n} multisig ", + "what_is_vault_wallet": "novčanik.", + "vault_advanced_customize": "Postavke trezora", + "needs": "Potrebno mu je", + "what_is_vault_description_number_of_vault_keys": " {m} ključeva trezora ", + "what_is_vault_description_to_spend": "za trošenje, a treći možete \nkoristiti kao sigurnosnu kopiju.", + "what_is_vault_description_to_spend_other": "za trošenje.", + "quorum": "{m} od {n} kvorum", + "quorum_header": "Kvorum", + "of": "od", + "wallet_type": "Tip novčanika", + "invalid_mnemonics": "Ova mnemonička fraza ne izgleda valjana.", + "invalid_cosigner": "Neispravni podaci supotpisnika", + "not_a_multisignature_xpub": "Ovo nije XPUB iz multisig novčanika!", + "invalid_cosigner_format": "Neispravan supotpisnik: ovo nije supotpisnik za format {format}.", + "create_new_key": "Kreiraj novi", + "scan_or_open_file": "Skeniraj ili otvori datoteku", + "i_have_mnemonics": "Imam sigurnosnu frazu za ovaj ključ.", + "type_your_mnemonics": "Unesite sigurnosnu frazu za uvoz postojećeg ključa trezora.", + "this_is_cosigners_xpub": "Ovo je XPUB supotpisnika — spreman za uvoz u drugi novčanik. Sigurno ga je dijeliti.", + "this_is_cosigners_xpub_airdrop": "Ako dijelite putem AirDropa, primatelji moraju biti na zaslonu koordinacije.", + "wallet_key_created": "Vaš ključ trezora je kreiran. Odvojite trenutak za sigurno izrađivanje sigurnosne kopije mnemoničke sigurnosne fraze.", + "are_you_sure_seed_will_be_lost": "Jeste li sigurni? Vaša mnemonička sigurnosna fraza bit će izgubljena ako nemate sigurnosnu kopiju.", + "forget_this_seed": "Zaboravi ovu sigurnosnu frazu i koristi XPUB umjesto nje.", + "view_edit_cosigners": "Pregledaj/uredi supotpisnike", + "this_cosigner_is_already_imported": "Ovaj supotpisnik je već uvezen.", + "export_signed_psbt": "Izvezi potpisani PSBT", + "input_fp": "Unesi otisak", + "input_fp_explain": "Preskoči za korištenje zadanog (00000000)", + "input_path": "Unesi put derivacije", + "input_path_explain": "Preskoči za korištenje zadanog ({default})", + "ms_help": "Pomoć", + "ms_help_title": "Kako rade multisig trezori: savjeti i trikovi", + "ms_help_text": "Novčanik s više ključeva, za povećanu sigurnost ili dijeljeno skrbništvo", + "ms_help_title1": "Preporučuje se više uređaja.", + "ms_help_1": "Trezor radi s drugim BlueWallet aplikacijama i novčanicima koji podržavaju PSBT, poput Electrum, Specter, Coldcard, Cobo Vault, itd.", + "ms_help_title2": "Uređivanje ključeva", + "ms_help_2": "Sve ključeve trezora možete kreirati na ovom uređaju te ih kasnije ukloniti ili urediti. Imati sve ključeve na istom uređaju sigurnosno je ekvivalentno običnom Bitcoin novčaniku.", + "ms_help_title3": "Sigurnosne kopije trezora", + "ms_help_3": "U opcijama novčanika pronaći ćete sigurnosnu kopiju trezora i promatračku (watch-only) sigurnosnu kopiju. Ova kopija je poput karte do vašeg novčanika. Ključna je za oporavak novčanika u slučaju da izgubite neku od sigurnosnih fraza.", + "ms_help_title4": "Uvoz trezora", + "ms_help_4": "Za uvoz multisiga koristite svoju datoteku sigurnosne kopije i značajku uvoza. Ako imate samo sigurnosne fraze i XPUB-ove, možete koristiti pojedinačni gumb Uvezi prilikom kreiranja ključeva trezora.", + "ms_help_title5": "Napredni način", + "ms_help_5": "BlueWallet će prema zadanim postavkama generirati 2-od-3 trezor. Za kreiranje drugačijeg kvoruma ili promjenu tipa adrese, aktivirajte napredni način u postavkama." + }, + "is_it_my_address": { + "title": "Je li to moja adresa?", + "owns": "{label} posjeduje {address}", + "enter_address": "Unesi adresu", + "check_address": "Provjeri adresu", + "no_wallet_owns_address": "Nijedan od dostupnih novčanika ne posjeduje navedenu adresu.", + "view_qrcode": "Prikaži QR kod" + }, + "autofill_word": { + "title": "Posljednja riječ sigurnosne fraze", + "enter": "Unesite svoju djelomičnu mnemoničku frazu", + "generate_word": "Generiraj posljednju riječ", + "error": "Unos nije djelomična mnemonička fraza od 11 ili 23 riječi. Pokušajte ponovno." + }, + "cc": { + "change": "Ostatak", + "coins_selected": "Odabrane kovanice ({number})", + "selected_summ": "{value} odabrano", + "empty": "Ovaj novčanik trenutno nema kovanica.", + "freeze": "Zamrzni", + "freezeLabel": "Zamrzni", + "freezeLabel_un": "Odmrzni", + "header": "Upravljanje UTXO-ima", + "use_coin": "Koristi kovanicu", + "use_coins": "Koristi kovanice", + "tip": "Ova značajka omogućuje vam pregled, označavanje, zamrzavanje ili odabir kovanica radi boljeg upravljanja novčanikom. Možete odabrati više kovanica tapom na obojene krugove.", + "sort_asc": "Uzlazno", + "sort_desc": "Silazno", + "sort_height": "Visina", + "sort_value": "Vrijednost", + "sort_label": "Oznaka", + "sort_status": "Status", + "sort_by": "Sortiraj po" + }, + "units": { + "BTC": "BTC", + "MAX": "Max", + "sat_vbyte": "sat/vByte", + "sats": "sats" }, "addresses": { + "copy_private_key": "Kopiraj privatni ključ", + "sensitive_private_key": "Upozorenje: privatni ključevi su iznimno osjetljivi. Nastaviti?", + "sign_title": "Potpiši/provjeri poruku", + "sign_help": "Ovdje možete kreirati ili provjeriti kriptografski potpis temeljen na Bitcoin adresi.", + "sign_sign": "Potpiši", + "sign_verify": "Provjeri", + "sign_signature_correct": "Provjera uspješna!", + "sign_signature_incorrect": "Provjera neuspješna!", "sign_placeholder_address": "adresa", + "sign_placeholder_message": "Poruka", + "sign_placeholder_signature": "Potpis", + "addresses_title": "Adrese", + "type_change": "Ostatak", "type_receive": "Primi", + "type_used": "Iskorišteno", "transactions": "transakcije" + }, + "lnurl_auth": { + "register_question_part_1": "Želite li registrirati račun na", + "register_question_part_2": "pomoću svog Lightning novčanika?", + "register_answer": "Uspješno ste registrirali račun na {hostname}!", + "login_question_part_1": "Želite li se prijaviti na", + "login_question_part_2": "pomoću svog Lightning novčanika?", + "login_answer": "Uspješno ste se prijavili na {hostname}!", + "link_question_part_1": "Želite li povezati svoj račun na", + "link_question_part_2": "sa svojim Lightning novčanikom?", + "link_answer": "Vaš Lightning novčanik uspješno je povezan s vašim računom na {hostname}!", + "auth_question_part_1": "Želite li se autenticirati na", + "auth_question_part_2": "pomoću svog Lightning novčanika?", + "auth_answer": "Uspješno ste se autenticirali na {hostname}!", + "could_not_auth": "Nismo vas mogli autenticirati na {hostname}.", + "authenticate": "Autenticiraj" + }, + "bip47": { + "payment_code": "Kod plaćanja", + "contacts": "Kontakti", + "bip47_explain": "Višekratni i djeljivi kod", + "bip47_explain_subtitle": "BIP47", + "purpose": "Višekratni i djeljivi kod (BIP47)", + "pay_this_contact": "Plati ovom kontaktu", + "rename_contact": "Preimenuj kontakt", + "copy_payment_code": "Kopiraj kod plaćanja", + "hide_contact": "Sakrij kontakt", + "rename": "Preimenuj", + "provide_name": "Unesite novo ime za ovaj kontakt", + "add_contact": "Dodaj kontakt", + "provide_payment_code": "Unesi kod plaćanja", + "invalid_pc": "Neispravan kod plaćanja", + "notification_tx_unconfirmed": "Obavijesna transakcija još nije potvrđena, molimo pričekajte", + "failed_create_notif_tx": "Neuspješno kreiranje on-chain transakcije", + "onchain_tx_needed": "Potrebna je on-chain transakcija", + "notif_tx_sent": "Obavijesna transakcija poslana. Molimo pričekajte da se potvrdi", + "notif_tx": "Obavijesna transakcija", + "not_found": "Kod plaćanja nije pronađen" } } diff --git a/loc/hu_hu.json b/loc/hu_hu.json index d5ec9362f29..43b620988ca 100644 --- a/loc/hu_hu.json +++ b/loc/hu_hu.json @@ -4,38 +4,48 @@ "cancel": "Mégse", "continue": "Folytatás", "clipboard": "Vágólap", + "copied": "Másolva!", + "discard_changes": "Módosítások elvetése?", + "discard_changes_explain": "Nem mentett módosításaid vannak. Biztosan elveted őket, és elhagyod a képernyőt?", "enter_password": "Írd be a jelszót", + "enter_url": "URL megadása", + "save": "Mentés...", + "close": "Bezárás", + "change_input_currency": "Beviteli pénznem módosítása", + "refresh": "Frissítés", + "pick_image": "Választás a könyvtárból", + "pick_file": "Fájl kiválasztása", + "enter_amount": "Összeg megadása", + "unlock": "Feloldás", + "port": "Port", + "ssl_port": "SSL port", + "suggested": "Javasolt", + "qr_custom_input_button": "Érintsd meg 10-szer egyedi bevitelhez", "never": "soha", - "disabled": "Kikapcsolva", "of": "{number} / {total}", "ok": "OK", - "storage_is_encrypted": "A Tárhely titkosítva. Jelszó szükséges a dekódoláshoz", + "storage_is_encrypted": "A tárhely titkosítva. Jelszó szükséges a dekódoláshoz", "yes": "Igen", "no": "Nem", - "save": "Mentés", "seed": "jelszó sorozat", "success": "Sikeres", - "wallet_key": "Tárca kulcs", - "invalid_animated_qr_code_fragment": "Érvénytelen animált QR kód részlet, próbáld újra!", - "file_saved": "{filePath} elmentve a kijelölt helyen: {destination}.", - "downloads_folder": "Letöltések Mappa" - }, - "alert": { - "default": "Figyelem" + "wallet_key": "Tárca kulcs" }, "azteco": { - "codeIs": "A kuponkódod ", + "codeIs": "A kuponkódod", "errorBeforeRefeem": "Jóváírás előtt, először adj meg egy Bitcoin tárcát.", "errorSomething": "Hiba. A kupon érvényes?", "redeem": "Jóváírás a tárcába", - "redeemButton": "visszaváltás", + "redeemButton": "Beváltás", "success": "Sikeres", + "successMessage": "Kupon sikeresen beváltva! A pénzed hamarosan megérkezik a Bitcoin tárcádba.", "title": "Azte.co kupon jóváírása" }, "entropy": { "save": "Mentés", "title": "Entrópia", - "undo": "Visszavonás" + "undo": "Visszavonás", + "amountOfEntropy": "{bits} / {limit} bit" }, "errors": { "broadcast": "Sikertelen továbbítás", @@ -43,42 +53,25 @@ "network": "Hálózati hiba" }, "lnd": { - "active": "Aktív", - "inactive": "Inaktív", - "channels": "Csatornák", - "no_channels": "Nincsenek csatornák", - "claim_balance": "Egyenleg lefoglalása {balance}", - "close_channel": "Csatorna zárása", - "new_channel": "Új csatorna", - "errorInvoiceExpired": "A számla lejárt", - "force_close_channel": "Csatorna erőltetett zárása?", + "errorInvoiceExpired": "A számla lejárt.", "expired": "Lejárt", - "node_alias": "Node aliasz", "expiresIn": "{time} percen belül elévül", "payButton": "Fizess", "placeholder": "Számla", - "open_channel": "Csatorna nyitása", - "funding_amount_placeholder": "Feltöltési mennyiség, például 0.001", - "opening_channnel_for_from": "Csatornanyitás a {forWalletLabel} tárca számára, {fromWalletLabel} által finanszírozva.", - "are_you_sure_open_channel": "Biztosan meg akarja nyitni ezt a csatornát?", "potentialFee": "Várható díj: {fee}", - "remote_host": "Távoli host", "refill": "Feltölt", - "reconnect_peer": "Társ újracsatlakoztatása", "refill_create": "A folytatáshoz, hozz létre egy Bitcoin tárcát amire feltölthetsz.", "refill_external": "Feltöltés külső tárcáról", "refill_lnd_balance": "Lightning egyenleg feltöltése", - "sameWalletAsInvoiceError": "Számlát nem fizethesz be ugyanarról a tárcáról, mint amellyel létrehoztad.", - "title": "kezelés", - "can_send": "Tud küldeni", - "can_receive": "Tud fogadni", - "view_logs": "Eseménynapló Megtekintése" + "sameWalletAsInvoiceError": "Számlát nem fizethetsz be ugyanarról a tárcáról, mint amellyel létrehoztad.", + "title": "Pénzkezelés", + "payment": "Fizetés" }, "lndViewInvoice": { "additional_info": "További információk", + "date_time": "Dátum és idő", "for": "Cím:", - "lightning_invoice": "Villám Számla", - "open_direct_channel": "Közvetlen csatorna nyitása erre a csomópontra:", + "lightning_invoice": "Lightning számla", "please_pay_between_and": "Kérem fizessen {min} és {max} közötti összeget", "please_pay": "Kérlek fizess", "preimage": "Pre-image (hashlock feloldáshoz)", @@ -87,40 +80,44 @@ }, "plausibledeniability": { "create_fake_storage": "Hamis tárhely létrehozása", - "create_password": "Jelszó létrehozása", "create_password_explanation": "A hamis tárhely jelszava nem lehet ugyanaz, mint az igazi tárhelyé", - "help": "Bizonyos körülmények között arra kényszerülhetsz, hogy megadda jelszavadat. A pénzed biztonsága érdekében a BlueWallettel létrehozhatsz egy alternatív titkosított tárhelyet, alternatív jelszóval. Kényszer hatása alatt megadhatod az alternatív jelszavadat, ami után a BlueWallet az alternatív tárhelyedet fogja megnyitni. Ez ugyanúgy fog kinézni, mint egy igazi tárhely, azzal a különbséggel, hogy a pénzed teljes biztonságban lesz az elsődleges tárhelyen.", + "help": "Bizonyos körülmények között arra kényszerülhetsz, hogy megadd a jelszavadat. A pénzed biztonsága érdekében a BlueWallettel létrehozhatsz egy alternatív titkosított tárhelyet, alternatív jelszóval. Kényszer hatása alatt megadhatod az alternatív jelszavadat, ami után a BlueWallet az alternatív tárhelyedet fogja megnyitni. Ez ugyanúgy fog kinézni, mint egy igazi tárhely, azzal a különbséggel, hogy a pénzed teljes biztonságban lesz az elsődleges tárhelyen.", "help2": "Az alternatív tárhely teljesen működőképes, és akár egy kisebb összeget is elhelyezhetsz rajta, hogy hitelesebbnek tűnjön.", "password_should_not_match": "A hamis tárhely jelszava nem lehet ugyanaz, mint az igazi tárhelyé", - "passwords_do_not_match": "Jelszavak nem egyeznek, próbáld újra.", - "retype_password": "Jelszó megerősítése", - "success": "Hamis tárhely létrehozva!", "title": "Elfogadható tagadhatóság" }, "pleasebackup": { - "ask": "Készítettél másolatot a tárca visszaállításához szükséges jelszó sorozatról? Ez az elmentett jelszó sorozat nélkülözhetetlen ha elveszik ez az eszközt. A jelszó sorozat nélkül a pénzed végleg elveszik.", - "ask_no": "Nem, nincs", - "ask_yes": "Igen, van", - "ok": "OKÉ, leírtam", - "ok_lnd": "OKÉ, elmentettem", + "ask": "Készítettél másolatot a tárca visszaállításához szükséges seedről? Ez az elmentett seed nélkülözhetetlen, ha elvesztenéd ezt az eszközt. A seed nélkül a pénzed végleg elveszik.", + "ask_no": "Nem, nincs.", + "ask_yes": "Igen, van.", + "ok": "Rendben, leírtam!", + "ok_lnd": "OK, elmentettem.", "text": "Kérlek írd le az alábbi biztonsági szavakat egy papírlapra. \nEz egy biztonsági mentés, amellyel helyreállíthatod a tárcádat.", "text_lnd": "Készíts biztonsági másolatot erről az LNDHub hitelesítésről. Ezzel a biztonsági másolattal visszaállíthatod a tárcát más eszközön.", - "title": "A tárcája elkészült." + "title": "A tárcád elkészült..." }, "receive": { "details_create": "Létrehoz", "details_label": "Leírás", "details_setAmount": "Fogadandó összeg", - "details_share": "megosztás", - "header": "Fogadás" + "details_share": "Megosztás...", + "header": "Fogadás", + "address_not_found": "Nem sikerült fogadó címet generálni.", + "reset": "Visszaállítás", + "maxSats": "Maximális összeg {max} sat", + "maxSatsFull": "Maximális összeg {max} sat vagy {currency}", + "minSats": "Minimális összeg {min} sat", + "minSatsFull": "Minimális összeg {min} sat vagy {currency}", + "qrcode_for_the_address": "QR-kód a címhez", + "bip47_explanation": "A fizetési kódok olyan univerzális címek, amelyek elkerülik a tárcacímek felfedését. Nem minden szolgáltatás támogatja őket." }, "send": { - "provided_address_is_invoice": "Ez a cím Villám számlának tűnik. Kérem, mennyen be a Villám tárcájába és ott próbálkozzon ezen számla fizetésével.", + "provided_address_is_invoice": "Ez a cím Lightning számlának tűnik. Kérem, menjen be a Lightning tárcájába és ott próbálkozzon ezen számla fizetésével.", "broadcastButton": "Küldés", - "broadcastError": "hiba", - "broadcastNone": "Bejövő tranzakciós hash", - "broadcastPending": "függőben", - "broadcastSuccess": "sikeres", + "broadcastError": "Hiba", + "broadcastNone": "Tranzakció hex beillesztése", + "broadcastPending": "Függőben", + "broadcastSuccess": "Sikeres", "confirm_header": "Megerősítés", "confirm_sendNow": "Küldés most", "create_amount": "Összeg", @@ -128,33 +125,52 @@ "create_copy": "Másolás és továbbítás később", "create_details": "Részletek", "create_fee": "Díj", - "create_memo": "megjegyzés", + "create_memo": "Megjegyzés", + "create_satoshi_per_vbyte": "Satoshi vbájtonként", "create_this_is_hex": "Tranzakció hexadecimális formátumban, aláírva és küldésre készen.", "create_to": "Címzett", "create_tx_size": "Tranzakció mérete", "create_verify": "Igazolás a coinb.in-en", "details_add_rec_add": "Kedvezményezett hozzáadása", "details_add_rec_rem": "Kedvezményezett eltávolítása", - "details_address": "cím", + "details_add_rec_rem_all": "Összes címzett eltávolítása", + "details_add_recc_rem_all_alert_description": "Biztosan eltávolítod az összes címzettet?", + "details_recipients_title": "Címzettek", + "details_recipient_title": "#{number}. kedvezményezett a #{total}-ből", + "details_insert_contact": "Kapcsolat beszúrása", + "please_complete_recipient_title": "Hiányos címzett", + "please_complete_recipient_details": "Kérlek töltsd ki a(z) #{number}. kedvezményezett adatait, mielőtt új kedvezményezettet adsz hozzá.", + "details_scan_error": "Szkennelési hiba", + "insert_custom_fee": "Díj megadása", + "invalid_psbt": "Érvénytelen PSBT.", + "qr_error_no_qrcode": "Nem találtunk érvényes QR-kódot a kiválasztott képen. Győződj meg róla, hogy a kép csak QR-kódot tartalmaz, és nincs benne más, például szöveg vagy gombok.", + "txSaved": "A tranzakciós fájl ({filePath}) elmentve.", + "file_saved_at_path": "A fájl ({filePath}) elmentve.", + "cant_send_to_silentpayment_adress": "Ez a tárca nem tud küldeni Silent Payments címekre", + "cant_send_to_bip47": "Ez a tárca nem tud küldeni BIP47 fizetési kódokra", + "cant_find_bip47_notification": "Először add hozzá ezt a fizetési kódot a kapcsolatokhoz", + "details_address": "Cím", "details_address_field_is_not_valid": "Érvénytelen cím", "details_adv_fee_bump": "Kiváltás díjjal engedélyezve", "details_adv_full": "Használd a teljes egyenleget", "details_adv_full_sure": "Biztosan használni akarod a tárca teljes egyenlegét ehhez a tranzakcióhoz?", - "details_adv_full_sure_frozen": "Biztosan fel akarja használna a teljes egyenleget ehhez a tranzakcióhoz? Lefagyasztott érmék nem lesznek felhasználva.", + "details_adv_full_sure_frozen": "Biztosan fel akarja használni a teljes egyenleget ehhez a tranzakcióhoz? Lefagyasztott érmék nem lesznek felhasználva.", "details_adv_import": "Tranzakció importálása", "details_adv_import_qr": "Tranzakció Importálása (QR)", "details_amount_field_is_not_valid": "Érvénytelen összeg", "details_amount_field_is_less_than_minimum_amount_sat": "A megadott összeg túl kevés. Kérlek adj meg egy 500sat-nál nagyobb összeget.", "details_create": "Készíts számlát", "details_error_decode": "Nem lehet dekódolni a bitcoin címet", - "details_fee_field_is_not_valid": "Èrvénytelen tranzakciós díj", + "details_fee_field_is_not_valid": "Érvénytelen tranzakciós díj", + "details_frozen": "{amount} BTC fagyasztva áll.", "details_next": "Következő", "details_no_signed_tx": "A kiválasztott fájl nem tartalmaz importálható tranzakciót.", - "details_note_placeholder": "saját megjegyzés", + "details_note_placeholder": "Saját megjegyzés", "details_scan": "Szkennelés", "details_scan_hint": "Dupla érintéssel szkennelhet, vagy betölthet uticélt", "details_total_exceeds_balance": "A megadott összeg nagyobb, mint a tárca elérhető egyenlege", - "details_unrecognized_file_format": "Nemismert fálj formátum", + "details_total_exceeds_balance_frozen": "A küldeni kívánt összeg meghaladja az elérhető egyenleget. Lefagyasztott érmék nem voltak felhasználva.", + "details_unrecognized_file_format": "Ismeretlen fájlformátum", "details_wallet_before_tx": "Tranzakció előtt, először adj meg egy Bitcoin tárcát.", "dynamic_init": "Előkészítés", "dynamic_next": "Következő", @@ -164,321 +180,387 @@ "fee_10m": "10 perc", "fee_1d": "1 nap", "fee_3h": "3 óra", - "fee_custom": "beállított", + "fee_custom": "Egyedi", "fee_fast": "Gyors", "fee_medium": "Közepes", + "fee_replace_minvb": "A fizetendő teljes díj mértékének (satoshi per vbyte) magasabbnak kell lennie, mint {min} sat/vbyte.", "fee_satvbyte": "sat/vByte-ban", "fee_slow": "Lassú", "header": "Küldés", "input_clear": "Törlés", "input_done": "Kész", - "input_paste": "beillesztés", + "input_paste": "Beillesztés", "input_total": "Összesen:", "permission_camera_message": "Kamera használat engedélyezése", - "permission_camera_title": "Kamera használatának engedélyezése", "psbt_sign": "Egy tranzakció aláírása", "open_settings": "Beállítások megnyitása", - "permission_storage_later": "Később", - "permission_storage_message": "A fájl elmentéséhez engedélyezned kell a BlueWallet hozzáférését a háttértárhoz.", "permission_storage_denied_message": "BlueWallet nem képes elmenteni ezt a fájlt. Kérem nyissa meg a beállításokat és engedélyezze a tárhely hozzáférést az eszközén.", - "permission_storage_title": "Háttértár hozzáférés engedélyezés", + "permission_storage_title": "Háttértár hozzáférés engedélyezése", "psbt_clipboard": "Másolás vágólapra", - "psbt_this_is_psbt": "Ez egy részlegesen aláírt Bitcoin tranzakció (PSBT). Befejezheted a hardver tárcád aláírásával. ", + "psbt_this_is_psbt": "Ez egy részlegesen aláírt Bitcoin tranzakció (PSBT). Befejezheted a hardver tárcád aláírásával.", "psbt_tx_export": "Exportálás fájlba", "no_tx_signing_in_progress": "Tranzakció aláírás nincs folyamatban", "outdated_rate": "A ráta utoljára frissítve: {date}", "psbt_tx_open": "Aláírt tranzakció megnyitása", "psbt_tx_scan": "Aláírt tranzakció szkennelése", - "qr_error_no_qrcode": "Nem találtunk QR kódot a kiválasztott képen. Győződjön meg arról, hogy a kép csak QR kódot tartalmaz, és nem tartalmaz további tartalmat, például szöveget vagy gombokat.", "reset_amount": "Összeg Visszaállítása", "reset_amount_confirm": "Valóban visszaállítja az összeget?", "success_done": "Kész!", - "txSaved": "A tranzakciós fájl ({filePath}) elmentve a Letöltések mappába.", "problem_with_psbt": "Hiba a PSBT aláírásban." }, "settings": { "about": "Egyéb", "about_awesome": "Nagyszerűséggel fejlesztve", "about_backup": "Mindig készíts biztonsági másolatot a jelszó sorozatodról. ", - "about_free": "A BlueWallet alkalmazás ingyenes és nyílt forráskódú. Bitcoin használók fejleszteték. ", - "about_license": "MIT Licensz", + "about_free": "A BlueWallet alkalmazás ingyenes és nyílt forráskódú. Bitcoin használók fejlesztették.", + "about_license": "MIT Licenc", "about_release_notes": "Verzió megjegyzések", "about_review": "Írj értékelést", "about_selftest": "Önellenőrző teszt indítása", "about_selftest_electrum_disabled": "Az öntesztelés nem elérhető az Electrum Offline móddal. Kérjük, kapcsolja ki az offline módot, és próbálkozzon újra.", "about_selftest_ok": "Minden belső teszt sikeres. A tárca megfelelően működik.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord Szerver", "about_sm_telegram": "Telegram csevegés", - "about_sm_twitter": "Kövess minket Twitteren", - "advanced_options": "Haladó Beállítások", "biometrics": "Biometrikus azonosító", - "biom_10times": "10-szer próbálta meg megadni a jelszavát. Vissza szeretné állítani a tárhelyet? Ezzel eltávolítja az összes pénztárcát és visszafejti a titkosítást a tárhelyről.", + "biom_10times": "10-szer próbálta meg megadni a jelszavát. Vissza szeretné állítani a tárhelyet? Ezzel eltávolítja az összes pénztárcát és visszafejti a titkosítást a tárhelyről.", "biom_conf_identity": "Kérem azonosítsa magát.", - "biom_no_passcode": "A készülék nem rendelkezik jelszóval. A folytatáshoz kérjük, konfigurálja a jelszót a Beállítások menüpontban.", "biom_remove_decrypt": "Minden pénztárcáját eltávolítjuk, és a tárolójáról visszafejtjük a titkosítást. Biztosan folytatja?", "currency": "Valuta", - "currency_fetch_error": "Hibatörtént a ráta lekérdezésekor a kijelölt fiat pénznél.", - "default_desc": "Ha le van tiltva, a BlueWallet azonnal megnyitja a kiválasztott tárcát indításkor. ", - "default_info": "Alapértelmezett információ", - "default_title": "Indításkor ", - "default_wallets": "Összes tárca megtekintése", + "currency_fetch_error": "Hiba történt a ráta lekérdezésekor a kijelölt fiat pénznél.", + "default_title": "Indításkor", "electrum_connected": "Kapcsolódva", "electrum_connected_not": "Nincs kapcsolat", "electrum_error_connect": "Nem tud csatlakozni a kiválasztott Electrum szerverhez", - "lndhub_uri": "P.l., {example}", - "electrum_host": "P.l., {example}", + "lndhub_uri": "Pl., {example}", + "electrum_host": "Pl., {example}", "electrum_offline_mode": "Offline Mód", - "electrum_offline_description": "Ha engedélyezve van, a Bitcoin pénztárcájai nem kísérelnek meg egyenlegek vagy tranzakciók lekérdezésével.", - "electrum_port": "Port, álltalában {example}", + "electrum_offline_description": "Ha engedélyezve van, a Bitcoin pénztárcáid nem kísérelnek meg egyenleget vagy tranzakciókat lekérdezni.", + "electrum_port": "Port, általában {example}", "use_ssl": "SSL Használata", - "electrum_saved": "A változtatás sikeresen elmentve. Az új beállítások aktiválásához űjraindításra lehet szükség.", - "set_electrum_server_as_default": "{server} bállítása alapértelmezett Electrum szerverként?", - "set_lndhub_as_default": "{url} bállítása alapértelmezett LNDHub szerverként?", + "electrum_saved": "A változtatás sikeresen elmentve. Az új beállítások aktiválásához újraindításra lehet szükség.", + "set_electrum_server_as_default": "{server} beállítása alapértelmezett Electrum szerverként?", "electrum_settings_server": "Electrum Szerver", - "electrum_settings_explain": "Hagydja üresen ha az alapértelmezettet szerené használni.", "electrum_status": "Állapot", - "electrum_clear_alert_title": "Előzmények törlése?", - "electrum_clear_alert_message": "Kiszeretné törölni az Electrum Szerver előzményeket?", - "electrum_clear_alert_cancel": "Mégse", - "electrum_clear_alert_ok": "OK", - "electrum_select": "Kiválasztás", - "electrum_reset": "alapértelmezett beállítások visszaállítása", - "electrum_unable_to_connect": "Nincs kapcsolat {server} -hez.", - "electrum_history": "Szerver előzmények", - "electrum_reset_to_default": "Biztosan vissza akarja alapértelmezettre állitani az Electrum szerver beállításait?", - "electrum_clear": "Törlés", - "tor_supported": "Tor támogatott", - "tor_unsupported": "Tor-al való csatlakozás nincsen támogatva.", + "electrum_unable_to_connect": "Nincs kapcsolat {server}-hez.", + "electrum_reset": "Alapértelmezett beállítások visszaállítása", "encrypt_decrypt": "Háttértár titkosításának feloldása", - "encrypt_decrypt_q": "Biztosan feloldod a háttértár titkosítását? Ez lehetővé teszi a tárca jelszó nélküli használatát. ", - "encrypt_enc_and_pass": "Titkosítva és jelszóval védve", + "encrypt_decrypt_q": "Biztosan feloldod a háttértár titkosítását? Ez lehetővé teszi a tárca jelszó nélküli használatát.", "encrypt_title": "Biztonság", "encrypt_tstorage": "tárhely", "encrypt_use": "használj {type}", - "encrypt_use_expl": "Igazolja személyazonosságát a {type} a tranzakció végrehajtása, a pénztárca feloldása, exportálása vagy törlése előtt. A {type} nem használható a titkosított tárolás feloldására.", "general": "Általános", - "general_adv_mode": "Halandó mód bekapcsolása", - "general_adv_mode_e": "Ha engedélyezve van, további opciók is elérhetőek, mint különböző tárca típusok, Lightning LNDHub beállítások és entrópia beállítások tárca készítésénél. ", "general_continuity": "Folytonosság", "general_continuity_e": "Ha engedélyezve van, láthatod a kiválasztott tárcákat és tranzakciókat más, csatlakoztatott Apple iCloud eszközökön.", - "groundcontrol_explanation": "A GroundControl egy ingyenes, nyílt forráskodú, push üzenetküldő szerver Bitcoin tárcákhoz. Telepítheted a saját GroundControl szerveredet webcímed megadásával, ami függetlet lesz a BlueWallet infrastuktúrától. Hagyd üresen alapértelmezésben.", - "header": "beállítások", + "groundcontrol_explanation": "A GroundControl egy ingyenes, nyílt forráskódú, push üzenetküldő szerver Bitcoin tárcákhoz. Telepítheted a saját GroundControl szerveredet webcímed megadásával, ami független lesz a BlueWallet infrastruktúrájától. Hagyd üresen alapértelmezésben.", + "header": "Beállítások", "language": "Nyelv", "last_updated": "Utoljára Frissítve", "language_isRTL": "Az új nyelv használatához újra kell indítanod a BlueWallet alkalmazást.", - "lightning_error_lndhub_uri": "Nem megfelelő LNDHub URI", "lightning_saved": "A változtatások sikeresen elmentve", "lightning_settings": "Lightning Beállítások", - "tor_settings": "Tor Beállítások", - "lightning_settings_explain": "Ha saját LND node-hoz szeretne csatlakozni, telepítse az LNDHub alkalmazást, és adja meg az URL-címét a beállításokban. Hagyja üresen a BlueWallet LNDHub használatát. Kérjük, vegye figyelembe, hogy csak a módosítások mentése után létrehozott pénztárcák csatlakoznak a megadott LNDHub-hoz.", "network": "Hálózat", - "network_broadcast": "Tranzakció továbbitása", + "network_broadcast": "Tranzakció továbbítása", "network_electrum": "Electrum szerver", "not_a_valid_uri": "Nem megfelelő URI", "notifications": "Megjegyzések", "open_link_in_explorer": "Link megnyitása explorerben", "password": "Jelszó", - "password_explain": "Add meg a jelszót, amivel majd dekódolhatod a tárhelyet", - "passwords_do_not_match": "A megadott jelszavak különböznek!", "plausible_deniability": "Elfogadható tagadhatóság...", "privacy": "Személyes adatok", "privacy_read_clipboard": "Vágólap olvasása", "privacy_system_settings": "Rendszer beállítások", - "privacy_quickactions": "tárca gyorsbillentyűk", + "privacy_quickactions": "Tárca gyorsbillentyűk", "privacy_quickactions_explanation": "Érintsd meg és tartsd nyomva a BlueWallet alkalmazás ikont a képernyőn a tárca egyenleg gyors megtekintéséhez.", - "privacy_clipboard_explanation": "Jelentsen meg opciókat, ha cím vagy számla található a vágólapon. ", + "privacy_clipboard_explanation": "Jelenítsen meg opciókat, ha cím vagy számla található a vágólapon.", "privacy_do_not_track": "Analitika kikapcsolása", - "privacy_do_not_track_explanation": "A teljesítményre és a megbízhatóságra vonatkozó információk nem lessznek beküldve elemzésre.", - "push_notifications": "Push üzenet", + "privacy_do_not_track_explanation": "A teljesítményre és a megbízhatóságra vonatkozó információk nem lesznek beküldve elemzésre.", "rate": "Ráta", - "retype_password": "Jelszó megerősítése", "selfTest": "Önteszt", - "save": "Ment", + "save": "Mentés", "saved": "Elmentve", "success_transaction_broadcasted": "Sikeres! A tranzakciója továbbítva!", "total_balance": "Teljes egyenleg", "total_balance_explanation": "Jelenítse meg az összes pénztárca egyenlegét a kezdőképernyő moduljain.", "widgets": "Widgetek", - "tools": "Eszközök" + "tools": "Eszközök", + "performance_score": "Teljesítménypontszám: {num}", + "run_performance_test": "Teljesítményteszt", + "block_explorer_invalid_custom_url": "A megadott URL érvénytelen. Kérlek, adj meg egy érvényes URL-t, amely http:// vagy https:// előtaggal kezdődik.", + "privacy_temporary_screenshots": "Képernyőrögzítés engedélyezése", + "privacy_temporary_screenshots_instructions": "A képernyőrögzítés-védelem ideiglenesen kikapcsol, így lehetővé válnak a képernyőfotók és képernyőfelvételek. A védelem automatikusan újra bekapcsol, amikor bezárod és újra megnyitod a BlueWallet-et.", + "biometrics_no_longer_available": "Az eszköz beállításai megváltoztak, és már nem egyeznek meg az alkalmazásban kiválasztott biztonsági beállításokkal. Kérlek, engedélyezd újra a biometrikus azonosítást vagy a feloldó kódot, majd indítsd újra az alkalmazást a módosítások alkalmazásához.", + "biom_no_passcode": "Az eszközödön nincs feloldó kód vagy biometrikus azonosítás engedélyezve. A folytatáshoz kérlek, állíts be feloldó kódot vagy biometrikus azonosítást a Beállítások alkalmazásban.", + "currency_source": "A ráta forrása", + "donate": "Adományozás", + "donate_description": "Segíts, hogy a Blue ingyenes maradjon!", + "electrum_error_connect_tor": "Nem lehet csatlakozni a megadott Electrum szerverhez. Kérlek, győződj meg arról, hogy az Orbot alkalmazás csatlakoztatva van, és próbáld újra.", + "set_lndhub_as_default": "Beállítod a(z) {url} címet alapértelmezett LNDhub szerverként?", + "electrum_preferred_server": "Előnyben részesített szerver", + "electrum_preferred_server_description": "Add meg azt a szervert, amelyet a tárcád használni fog minden Bitcoin tevékenységhez. A beállítás után a tárca kizárólag ezt a szervert fogja használni egyenlegek ellenőrzésére, tranzakciók küldésére és hálózati adatok lekérdezésére. Győződj meg róla, hogy megbízol ebben a szerverben, mielőtt beállítod.", + "electrum_history": "Előzmények", + "electrum_reset_to_default": "Ez engedélyezi a BlueWallet számára, hogy véletlenszerűen válasszon szervert a szerverlistából.", + "electrum_reset_to_default_and_clear_history": "Visszaállítás alapértelmezettre és előzmények törlése", + "encrypt_enc_and_pass": "Jelszóval védett", + "encrypt_storage_explanation_headline": "Tárhely titkosításának engedélyezése", + "encrypt_storage_explanation_description_line1": "A tárhely titkosításának engedélyezése extra védelmi réteget ad az alkalmazásnak azáltal, hogy biztonságossá teszi az adatok eszközön való tárolásának módját. Ez megnehezíti, hogy bárki engedély nélkül hozzáférjen az adataidhoz.", + "encrypt_storage_explanation_description_line2": "Fontos azonban tudni, hogy ez a titkosítás csak az eszköz kulcstárolójában tárolt tárcákhoz való hozzáférést védi. Nem helyez jelszót vagy bármilyen extra védelmet magukra a tárcákra.", + "i_understand": "Értem", + "block_explorer": "Blokkböngésző", + "block_explorer_preferred": "Előnyben részesített blokkböngésző használata", + "block_explorer_error_saving_custom": "Hiba az előnyben részesített blokkböngésző mentésekor", + "set_as_preferred": "Beállítás előnyben részesítettként", + "set_as_preferred_electrum": "A(z) {host}:{port} beállítása előnyben részesített szerverként letiltja a véletlenszerű csatlakozást egy javasolt szerverhez.", + "encrypted_feature_disabled": "Ez a funkció nem használható, ha a titkosított tárhely engedélyezve van.", + "encrypt_use_expl": "A(z) {type} fog azonosítani téged tranzakció végrehajtása, feloldás, exportálás vagy tárca törlése előtt.", + "biometrics_fail": "Ha a(z) {type} nincs engedélyezve, vagy nem sikerül a feloldás, alternatívaként használhatod az eszköz feloldó kódját.", + "license": "Licenc", + "lightning_error_lndhub_uri": "Érvénytelen LNDhub URI", + "lightning_error_lndhub_uri_tor": "Érvénytelen LNDhub URI. Kérlek, győződj meg arról, hogy az Orbot alkalmazás csatlakoztatva van, és próbáld újra.", + "lightning_settings_explain": "A saját LND node-odhoz való csatlakozáshoz, kérjük, telepítsd az LNDhub-ot, és add meg annak URL-jét itt a beállításokban. Vedd figyelembe, hogy csak a változások mentése után létrehozott tárcák fognak csatlakozni a megadott LNDhub-hoz.", + "lndhub_github": "GitHub-tárhely", + "electrum_suggested_description": "Ha nincs beállítva előnyben részesített szerver, akkor egy javasolt szerver lesz véletlenszerűen kiválasztva használatra.", + "password_explain": "Add meg a jelszót, amelyet a tárhely feloldására fogsz használni.", + "push_notifications_explanation": "Az értesítések engedélyezésével az eszközöd token-je elküldésre kerül a szerverre, az értesítések engedélyezése után létrehozott összes tárca tárcacímével és tranzakcióazonosítójával együtt. Az eszköz token-jét az értesítések küldésére használjuk, a tárca információi pedig lehetővé teszik számunkra, hogy értesítsünk a beérkező Bitcoinokról vagy a tranzakciók megerősítéseiről.\n\nCsak az értesítések engedélyezése utáni információ kerül továbbításra — semmi nem kerül begyűjtésre korábbról.\n\nAz értesítések letiltása eltávolítja ezeket az információkat a szerverről. Továbbá, ha töröl egy tárcát az alkalmazásból, az azzal kapcsolatos információk is törlődnek a szerverről." }, "notifications": { - "would_you_like_to_receive_notifications": "Szeretnél értesítést a bejövő utalásokról? ", - "no_and_dont_ask": "Nem és ne kérdezd újra", - "ask_me_later": "Később" + "would_you_like_to_receive_notifications": "Szeretnél értesítést a bejövő utalásokról?", + "no_and_dont_ask": "Nem és ne kérdezd újra.", + "notifications_subtitle": "Bejövő fizetések és tranzakció-megerősítések", + "permission_denied_message": "Megtagadtad az értesítések küldésére vonatkozó engedélyt. Ha szeretnél értesítéseket kapni, kérlek, engedélyezd azokat az eszköz beállításaiban." }, "transactions": { + "cancel_explain": "Ezt a tranzakciót lecseréljük arra, amely fizet Önnek és magasabb díjakkal rendelkezik. Ez gyakorlatilag törli a tranzakciót. Ezt RBF-nek hívják - azaz díj cserének.", "cancel_no": "Ez a tranzakció nem helyettesíthető", "cancel_title": "Tranzakció törlése (RBF)", "confirmations_lowercase": "{confirmations} konfirmációk", - "copy_link": "Link másolása", "expand_note": "További részletek", "cpfp_create": "Létrehoz", - "cpfp_exp": "Új tranzakciót adunk a függő tranzakcióhoz. A teljes tranzakciós díj magasabb lesz, így hamarabb teljesül. Ezt hívják angolul \"CPFP - Child Pays For Parent / Gyerek fizet a szülőnek\".", + "cpfp_exp": "Új tranzakciót adunk a függő tranzakcióhoz. A teljes tranzakciós díj magasabb lesz, így hamarabb teljesül. Ezt hívják angolul \"CPFP - Child Pays For Parent / Gyerek fizet a szülőnek\".", "cpfp_no_bump": "Tranzakció nem kiváltható", "cpfp_title": "Kiváltási díj (CPFP)", "details_balance_hide": "Egyenleg elrejtése", "details_balance_show": "Egyenleg mutatása", - "details_block": "Blokkszámláló", "details_copy": "Másolás", - "details_copy_amount": "Mennyiség Másolása", "details_copy_block_explorer_link": "Blokk Böngésző Link Másolása", "details_copy_note": "Megjegyzés Másolása", "details_copy_txid": "Tranzakciós ID Másolása", - "details_from": "Bejövő utalás", - "details_inputs": "Bejövő utalások", - "details_outputs": "Kimenő utalások", + "details_id": "ID", + "details_inputs": "Bemenetek", + "details_outputs": "Kimenetek", "details_received": "Fogadott", - "transaction_note_saved": "Tranzakciós megjegyzés sikeresen elmentve.", - "details_show_in_block_explorer": "Mutasd a block explorerben", "details_title": "Tranzakció", - "details_to": "Kimenő utalás", + "details_to": "Kimenet", "enable_offline_signing": "Ezt a pénztárcát nem használják offline aláírással. Szeretné most engedélyezni?", - "list_conf": "megerősítés: {number}", - "pending": "függőben", + "list_conf": "Megerősítés: {number}", + "pending": "Függőben", "pending_with_amount": "Függőben {amt1} ({amt2})", "received_with_amount": "+{amt1} ({amt2})", "eta_10m": "ETA: ~10 percen belül", "eta_3h": "ETA: ~3 órán belül", "eta_1d": "ETA: ~1 napon belül", - "list_title": "tranzakciók", + "list_title": "Tranzakciók", + "list_title_received": "Fogadott", + "transaction": "Tranzakció", + "open_url_error": "Nem lehetett megnyitni az URL-t az alapértelmezett böngészővel. Kérem változtassa meg a böngészőjét és próbálkozzon meg újra.", + "rbf_explain": "Kiváltjuk ezt a tranzakciót egy magasabb tranzakciós díjjal járó tranzakcióval, így hamarabb teljesül. Ezt a megoldást Tranzakciós Díj Pótlásnak hívjuk, angolul RBF—Replace by Fee.", "rbf_title": "Kiváltási díj (RBF)", "status_bump": "Kiváltási díj", "status_cancel": "Tranzakció törlése", "transactions_count": "Tranzakciók száma", "txid": "Tranzakció azonosító", - "updating": "Frissítés..." + "updating": "Frissítés...", + "transaction_loading_error": "Hiba történt a tranzakció betöltésekor. Kérlek, próbáld újra később.", + "transaction_not_available": "A tranzakció nem érhető el", + "date": "Dátum", + "details_view_in_browser": "Megtekintés böngészőben", + "incoming_transaction": "Bejövő tranzakció", + "outgoing_transaction": "Kimenő tranzakció", + "expired_transaction": "Lejárt tranzakció", + "pending_transaction": "Függőben lévő tranzakció", + "offchain": "Off-chain", + "onchain": "On-chain", + "list_title_sent": "Elküldve", + "watchOnlyWarningTitle": "Biztonsági figyelmeztetés", + "watchOnlyWarningDescription": "Légy óvatos a csalókkal, akik gyakran használnak „csak megtekintésre” szolgáló tárcákat a felhasználók megtévesztésére. Ezek a tárcák nem teszik lehetővé a pénzeszközök kezelését vagy küldését; csak az egyenleg megtekintését engedik.", + "custom_fee_warning_title": "Figyelmeztetés", + "custom_fee_warning_description": "Az 1 sat/vB alatti díjak érvényesek, de előfordulhat, hogy a node-szabályzatok miatt nem kerülnek továbbításra.", + "details_eta_analyzing": "Elemzés...", + "details_sent": "Elküldve", + "details_section": "Részletek", + "details_explorer": "blokkböngésző", + "details_network_fee": "Hálózati díj", + "details_to_address": "Címzett", + "details_note": "Jegyzet", + "details_add_note": "hozzáad", + "details_advanced": "Haladó", + "details_fee_rate": "Díj-arány", + "details_size": "Méret", + "details_virtual_size": "Virtuális méret", + "details_tx_hex": "Tx hex", + "details_inputs_count": "Bemenetek ({count})", + "details_outputs_count": "Kimenetek ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Egyszerű és masszív Bitcoin tárca", "add_create": "Létrehoz", + "total_balance": "Teljes egyenleg", + "add_entropy": "Entrópia", "add_entropy_generated": "{gen} generált entrópia byte", - "add_entropy_provide": "Entrópia megadása véletlenszerűen ", + "add_entropy_provide": "Entrópia megadása véletlenszerűen", "add_entropy_remain": "{gen} byte generálva entrópiával. A megmaradt {rem} byte a rendszer véletlenszám generátorával készül.", "add_import_wallet": "Tárca importálása", "add_lightning": "Lightning", "add_lightning_explain": "Költés azonnali tranzakcióval", - "add_lndhub": "Kapcsolódj az LNDHub-hoz", - "add_lndhub_error": "A megadott LNDHub node cím nem megfelelő.", - "add_lndhub_placeholder": "a te node címed", + "add_lndhub_placeholder": "A te node címed", "add_placeholder": "az első tárcám", - "add_title": "új tárca", - "add_wallet_name": "név", - "add_wallet_type": "típus", - "balance": "Egyenleg", - "clipboard_bitcoin": "Egy Bitcoin tárca cím van a vágólapodon. Akarod használni a tranzakcióhoz? ", - "clipboard_lightning": "Egy Lightning tárca cím van a vágólapodon. Akarod használni a tranzakcióhoz? ", + "add_title": "Új tárca", + "add_wallet_name": "Név", + "add_wallet_type": "Típus", + "clipboard_bitcoin": "Egy Bitcoin tárca cím van a vágólapodon. Akarod használni a tranzakcióhoz?", + "clipboard_lightning": "Egy Lightning tárca cím van a vágólapodon. Akarod használni a tranzakcióhoz?", "details_address": "Cím", "details_advanced": "Haladó", "details_are_you_sure": "Biztos vagy benne?", - "details_connected_to": "Kapcsolódva: ", - "details_del_wb_err": "A megadott egyenleg összege nem egyezik a tárca egyenlegével. Próbáld újra. ", - "details_del_wb_q": "Ennek a tárcának van egyenlege. Mielőtt tovább lép, tudnia kell, hogy nem fogja tudni vissza szerezni ezen összeget ha nem rendelkezik ezen tárca biztonsági szavaival. A véletlen eltávolítások elkerülése érdekéven kérem adja meg a tárcán található {balance} egyenleget satoshiban.", + "details_connected_to": "Kapcsolódva:", + "details_del_wb_err": "A megadott egyenleg összege nem egyezik a tárca egyenlegével. Próbáld újra.", + "details_del_wb_q": "Ennek a tárcának van egyenlege. Mielőtt tovább lép, tudnia kell, hogy nem fogja tudni visszaszerezni ezen összeget ha nem rendelkezik ezen tárca biztonsági szavaival. A véletlen eltávolítások elkerülése érdekében kérem adja meg a tárcán található {balance} egyenleget satoshiban.", "details_delete": "Törlés", "details_delete_wallet": "Tárca törlése", "details_derivation_path": "derivációs út", - "details_display": "mutasd a tárcák listáján", "details_export_backup": "Exportálás / Biztonsági mentés", - "details_master_fingerprint": "Mester ujjlenyomat ", + "details_master_fingerprint": "Mester ujjlenyomat", "details_multisig_type": "multisig", - "details_no_cancel": "Nem, megszakít", - "details_save": "Ment", "details_show_xpub": "Mutasd a tárca XPUB kulcsát", "details_show_addresses": "Cím mutatása", "details_title": "Tárca", + "wallets": "Tárcák", "details_type": "Típus", "details_use_with_hardware_wallet": "Használat hardver tárcával", - "details_wallet_updated": "Tárca frissítve", "details_yes_delete": "Igen, töröld", "enter_bip38_password": "Írd be a jelszót a titkosításhoz", - "export_title": "tárca exportálása", + "export_title": "Tárca exportálása", "import_do_import": "Importálás", "import_passphrase": "Jelszó", "import_passphrase_title": "Jelszó", "import_passphrase_message": "Add meg a jelszavadat ha használsz olyat", "import_error": "Importálás sikertelen. Ellenőrizd, hogy helyes adatokat adtál-e meg.", - "import_explanation": "Kérjük, írja be a biztonsági szavakat, a nyilvános kulcsot, a WIF-et vagy bármi mást. A BlueWallet mindent megtesz a helyes formátum kitalálásáért és a pénztárca importálásáért. ", + "import_explanation": "Kérjük, írja be a biztonsági szavakat, a nyilvános kulcsot, a WIF-et vagy bármi mást. A BlueWallet mindent megtesz a helyes formátum kitalálásáért és a pénztárca importálásáért.", "import_imported": "Importálva", - "import_scan_qr": "vagy QR-kód szkennelése?", + "import_scan_qr": "Vagy QR-kód szkennelése?", "import_success": "Sikeres importálás!", "import_search_accounts": "Fiókok keresése", - "import_title": "importálás", + "import_title": "Importálás", "import_discovery_title": "Felfedezés", "import_discovery_subtitle": "Már felfedezett tárca választása", "import_discovery_derivation": "Egyedi derivációs útvonal használata", "import_discovery_no_wallets": "Nem található tárca.", - "import_derivation_found": "megtalálva", - "import_derivation_found_not": "nem található", - "import_derivation_loading": "töltés...", + "import_derivation_found": "Megtalálva", + "import_derivation_found_not": "Nem található", + "import_derivation_loading": "Töltés...", "import_derivation_subtitle": "Egyedi derivációs útvonal beírása és mi megpróbáljuk felfedezni a tárcáját", "import_derivation_title": "Derivációs útvonal", - "import_derivation_unknown": "ismeretlen", - "import_wrong_path": "hibás derivációs útvonal", - "list_create_a_button": "add hozzá", + "import_derivation_unknown": "Ismeretlen", + "import_wrong_path": "Hibás derivációs útvonal", + "list_create_a_button": "Hozzáadás", "list_create_a_wallet": "Új tárca", - "list_create_a_wallet_text": "Ingyenes, és annyit hozhatsz létre amennyit szeretnél", + "list_create_a_wallet_text": "Ingyenes, és annyit hozhatsz\nlétre amennyit szeretnél", "list_empty_txs1": "A tranzakcióid itt fognak megjelenni", "list_empty_txs1_lightning": "A Lightning tárcát a mindennapi tranzakcióidhoz használhatod. A tranzakciók azonnal végrehajtódnak, minimális átutalási díjjal.", "list_empty_txs2": "Kezd a tárcáddal.", "list_empty_txs2_lightning": "\nA kezdéshez kattints a \"Kezelés\"-re, és töltsd fel az egyenleged.", - "list_latest_transaction": "utolsó tranzakció", - "list_ln_browser": "LApp Böngésző", + "list_latest_transaction": "Utolsó tranzakció", "list_long_choose": "Válassz fényképet", - "list_long_clipboard": "Másolás vágólapról", - "list_long_scan": "QR kód szkennelése", - "list_title": "tárcák", + "paste_from_clipboard": "Beillesztés", + "import_file": "Fájl importálása", + "list_long_scan": "QR-kód szkennelése", + "list_title": "Tárcák", "list_tryagain": "Próbáld újra", "no_ln_wallet_error": "Mielőtt tudnál fizetni a villámhálózaton, először egy Lightning tárcát kell létrehoznod vagy betöltened.", "looks_like_bip38": "Ez egy jelszó védett privát kulcsnak (BIP38) tűnik", - "reorder_title": "Tárcák rendezése", - "reorder_instructions": "Érintse meg és tartsa lenyomva, hogy át húzzhassa a listán.", "please_continue_scanning": "Kérem szkenneljen folyamatosan.", "select_no_bitcoin": "Jelenleg nincs elérhető Bitcoin tárca.", "select_no_bitcoin_exp": "A Lightning tárca feltöltéséhez Bitcoin tárcára van szükség. Készíts vagy importálj egy Bitcoin tárcát.", "select_wallet": "Válassz tárcát", - "xpub_copiedToClipboard": "Vágólapra másolva", "pull_to_refresh": "Húzza le a frissítéshez", - "warning_do_not_disclose": "VESZÉLY! Ezt soha senkivel ne ossza meg!", - "add_ln_wallet_first": "Először egy Lightning Tárcát kell létrehoznod vagy beadnod.", + "add_ln_wallet_first": "Először egy Lightning tárcát kell létrehoznod vagy hozzáadnod.", "identity_pubkey": "Nyilvános kulcs", - "xpub_title": "a tárca XPUB kulcsa" + "xpub_title": "A tárca XPUB kulcsa", + "add_entropy_reset_title": "Entrópia visszaállítása", + "add_entropy_reset_message": "A tárca típusának módosítása visszaállítja a jelenlegi entrópiát. Folytatod?", + "add_entropy_bytes": "{bytes} bájt entrópia", + "add_lndhub": "Csatlakozás az LNDhub-hoz", + "add_lndhub_error": "A megadott node cím érvénytelen LNDhub node.", + "add_wallet_seed_length": "Mag-kifejezés hossza", + "add_wallet_seed_length_12": "12 szó", + "add_wallet_seed_length_24": "24 szó", + "clear_clipboard_on_import": "Vágólap törlése importáláskor", + "details_display": "Megjelenítés a kezdőképernyőn", + "details_export_history": "Előzmények exportálása CSV-be", + "swipe_balance_hide": "Elrejtés", + "swipe_balance_show": "Mutatás", + "drag_to_reorder": "Húzd az átrendezéshez", + "clear_search": "Keresés törlése", + "import_success_watchonly": "A tárcád sikeresen importálva. FIGYELEM: Ez egy csak megtekintésre szolgáló tárca, NEM tudsz róla költeni.", + "learn_more": "Tudj meg többet", + "import_discovery_offline": "A BlueWallet jelenleg offline módban van. Ebben a módban nem tudja ellenőrizni a tárca létezését, ezért manuálisan kell kiválasztanod a megfelelőt", + "manage_title": "Tárcák kezelése", + "no_results_found": "Nincs találat.", + "warning_do_not_disclose": "Soha ne oszd meg az alábbi információkat", + "scan_import": "Szkenneld be ezt a QR-kódot a tárca másik alkalmazásba történő importálásához.", + "write_down_header": "Manuális biztonsági mentés készítése", + "write_down": "Írd le, és tárold biztonságosan ezeket a szavakat. Használd őket a tárca későbbi visszaállításához.", + "wallet_type_this": "Ennek a tárcának a típusa: {type}.", + "share_number": "{number}. megosztása", + "copy_ln_url": "Másold ki, és tárold biztonságosan ezt az URL-t a tárca későbbi visszaállításához.", + "copy_ln_public": "Másold ki, és tárold biztonságosan ezeket az információkat a tárca későbbi visszaállításához.", + "manage_wallets_search_placeholder": "Tárcák, címek, tranzakciók és megjegyzések keresése", + "more_info": "További információ", + "details_delete_wallet_error_message": "Hiba történt annak megerősítésekor, hogy ezt a tárcát eltávolították-e az értesítésekből — ez hálózati probléma vagy gyenge kapcsolat miatt fordulhatott elő. Ha folytatod, előfordulhat, hogy a törlés után is kapsz értesítéseket a tárcához kapcsolódó tranzakciókról.", + "details_delete_anyway": "Törlés mindenképp" + }, + "total_balance_view": { + "title": "Teljes egyenleg", + "display_in_bitcoin": "Megjelenítés Bitcoinban", + "hide": "Elrejtés", + "display_in_sats": "Megjelenítés sat-ban", + "display_in_fiat": "Megjelenítés {currency}-ban", + "explanation": "Tekintsd meg az összes tárcád teljes egyenlegét az áttekintő képernyőn." }, "multisig": { - "multisig_vault": "Trezor", + "multisig_vault": "Multisig Tárca", "default_label": "Multisig Tárca", "multisig_vault_explain": "Legnagyobb biztonság nagy összegekhez", - "provide_signature": "Aláírás végrehajtása ", - "vault_key": "Trezor kulcs {number}", - "required_keys_out_of_total": "Szükséges kulcsok az összessből", + "provide_signature": "Aláírás végrehajtása", + "vault_key": "Vault kulcs {number}", + "required_keys_out_of_total": "Szükséges kulcsok az összesből", "fee": "Tranzakciós díj: {number}", "fee_btc": "{number} BTC", "confirm": "Megerősítés", "header": "Küldés", - "share": "Megosztás", "view": "Megnéz", "manage_keys": "Kulcsok kezelése", - "how_many_signatures_can_bluewallet_make": "mennyi szignatúrát tud a BlueWallet létrehozni", + "how_many_signatures_can_bluewallet_make": "Mennyi aláírást tud a BlueWallet létrehozni", "signatures_required_to_spend": "Aláírás szükséges {number}", - "signatures_we_can_make": "létretudsz hozni {number}-et/őt/at", + "signatures_we_can_make": "{number} aláírást tudsz létrehozni", "scan_or_import_file": "Szkennelés vagy fájl importálása", "export_coordination_setup": "koordinációs beállítások exportálása", "cosign_this_transaction": "Aláírod ezt a tranzakciót?", "lets_start": "Kezdjük", "create": "Létrehoz", "native_segwit_title": "Bevált gyakorlat", - "wrapped_segwit_title": "Legjobb kompatibilitás ", + "wrapped_segwit_title": "Legjobb kompatibilitás", "legacy_title": "Hagyomány", "co_sign_transaction": "Egy tranzakció aláírása", - "what_is_vault": "A Trezor egy", - "what_is_vault_numberOfWallets": " {m}-a-{n}-ból/ből multisig", + "what_is_vault": "A Vault egy", + "what_is_vault_numberOfWallets": " {m}-a-{n}-ból multisig ", "what_is_vault_wallet": "tárca", - "vault_advanced_customize": "Trezor beállítások", + "vault_advanced_customize": "Vault beállítások", "needs": "szükséges", - "what_is_vault_description_number_of_vault_keys": "{m} trezor kulcsok", - "what_is_vault_description_to_spend": "hogy elkölzsd és a harmadik amit te\nbiztonsági másolatként használhatsz.", - "what_is_vault_description_to_spend_other": "elköktésre elérhető.", - "quorum": "{m} a {n}-ból/ből kvórum ", + "what_is_vault_description_number_of_vault_keys": " {m} vault kulcs ", + "what_is_vault_description_to_spend": "hogy elköltsd és a harmadik amit te\nbiztonsági másolatként használhatsz.", + "what_is_vault_description_to_spend_other": "elköltésre elérhető.", + "quorum": "{m} a {n}-ból kvórum", "quorum_header": "Kvórum", "of": "/", "wallet_type": "Tárca típusa", @@ -489,12 +571,12 @@ "create_new_key": "Készíts újat", "scan_or_open_file": "Szkennelés vagy fájl megnyitása", "i_have_mnemonics": "Megvan a jelszó sorozatom ehhez a kulcshoz...", - "type_your_mnemonics": "Jelszó sorozat megadása egy létező trezor-kulcs megadásához", + "type_your_mnemonics": "Seed megadása egy létező Vault-kulcs megadásához", "this_is_cosigners_xpub": "Ez a másodaláíró XPUB-ja — készen áll egy másik pénztárcába történő importálásra. Megosztani biztonságos.", "wallet_key_created": "A Vault kulcs létrehozva. Szánjon egy percet arra, hogy biztonságosan, biztonsági másolatot készítsen a titkos kulcsszavakról.", "are_you_sure_seed_will_be_lost": "Biztos benne? A biztonsági mentése el fog veszni ha nincs létező másolata.", "forget_this_seed": "Felejtse el ezt a kulcsot és használjon XPUB-ot inkább.", - "view_edit_cosigners": "Társ aláírok megtekintése/szerkesztése ", + "view_edit_cosigners": "Társaláírók megtekintése/szerkesztése", "this_cosigner_is_already_imported": "Ezen másodaláíró már importálva van.", "export_signed_psbt": "Aláírt PSBT Exportálása", "input_fp": "Ujjlenyomat megadása", @@ -513,7 +595,15 @@ "ms_help_title4": "Széf Importálása", "ms_help_4": "Multisig importálásához használja a biztonsági mentési fájlt és az Import funkciót. Ha csak biztonsági mentés szavaival és XPUB-jaival rendelkezik, a Vault kulcsok létrehozásakor használhatja az egyedi Import gombot.", "ms_help_title5": "Haladó mód", - "ms_help_5": "Alapértelmezettként a BlueWallet egy 2-a-3-ból Vault-ot generál. Másik kvórum létrehozásához vagy a cím típusának megváltoztatásához aktiválja a Speciális módot a Beállításokban. " + "ms_help_5": "Alapértelmezettként a BlueWallet egy 2-a-3-ból Vault-ot generál. Másik kvórum létrehozásához vagy a cím típusának megváltoztatásához aktiválja a Speciális módot a Beállításokban.", + "provide_signature_details": "Használd azt az eszközt és tárcát, ahol a kulcs található, a tranzakció aláírásához", + "provide_signature_details_bluewallet": "A BlueWalletben menj a Küldés képernyő menüjébe, és válaszd ki: ", + "provide_signature_next_steps": "Aláírt tranzakció szkennelése vagy importálása", + "provide_signature_next_steps_details": "Miután a tárcád sikeresen aláírta a tranzakciót, szkenneld be a megjelenített QR-kódot, vagy importáld a kísérőfájlt, majd ellenőrizd a tranzakció összes részletét, mielőtt továbbítanád.", + "share": "Megosztás...", + "shared_key_detected": "Megosztott társaláíró", + "shared_key_detected_question": "Egy társaláírót osztottak meg veled, importálni szeretnéd?", + "this_is_cosigners_xpub_airdrop": "Ha AirDrop-on keresztül osztod meg, a fogadóknak a koordinációs képernyőn kell lenniük." }, "is_it_my_address": { "title": "Ez a saját címem?", @@ -524,17 +614,24 @@ "view_qrcode": "QRKód megtekintése" }, "cc": { - "change": "váltás", + "change": "Váltás", "coins_selected": "Érmék kiválasztva ({number})", "selected_summ": "{value} kijelölve", "empty": "Ez a tárca jelenleg üres.", - "freeze": "zárolás", - "freezeLabel": "zárolás", + "freeze": "Zárolás", + "freezeLabel": "Zárolás", "freezeLabel_un": "Zárolás feloldása", "header": "Érme Kontroll", - "use_coin": "Cryptovaluta használata ", + "use_coin": "Érme használata", "use_coins": "Érmék használata", - "tip": "Ez a funkció lehetővé teszi az érmék megtekintését, címkézését, befagyasztását vagy kiválasztását a pénztárca jobb kezelése érdekében. Több érmét is kiválaszthat a színes körök megérintésével." + "tip": "Ez a funkció lehetővé teszi az érmék megtekintését, címkézését, befagyasztását vagy kiválasztását a pénztárca jobb kezelése érdekében. Több érmét is kiválaszthat a színes körök megérintésével.", + "sort_label": "Címke", + "sort_status": "Állapot", + "sort_asc": "Növekvő", + "sort_desc": "Csökkenő", + "sort_height": "Magasság", + "sort_value": "Érték", + "sort_by": "Rendezés" }, "units": { "BTC": "BTC", @@ -543,8 +640,12 @@ "sats": "sats" }, "addresses": { + "sign_title": "Aláíró/Hitelesítő üzenet", + "sign_help": "Itt létrehozhat vagy ellenőrizhet kriptográfiai aláírást egy Bitcoin-cím alapján", "sign_sign": "Aláír", "sign_verify": "Hitelesít", + "sign_signature_correct": "Hitelesítés sikeres!", + "sign_signature_incorrect": "Sikertelen hitelesítés!", "sign_placeholder_address": "Cím", "sign_placeholder_message": "Üzenet", "sign_placeholder_signature": "Szignatúra", @@ -552,6 +653,52 @@ "type_change": "Váltópénz", "type_receive": "Fogadás", "type_used": "Használt", - "transactions": "Tranzakciók" + "transactions": "Tranzakciók", + "copy_private_key": "Privát kulcs másolása", + "sensitive_private_key": "Figyelmeztetés: a privát kulcsok rendkívül érzékenyek. Folytatod?" + }, + "autofill_word": { + "title": "Mag-kifejezés utolsó szava", + "enter": "Add meg a részleges mnemonikus kifejezést", + "generate_word": "Utolsó szó generálása", + "error": "A bevitel nem 11 vagy 23 szavas részleges mnemonikus kifejezés. Kérlek, próbáld újra." + }, + "lnurl_auth": { + "register_question_part_1": "Szeretnél fiókot regisztrálni itt:", + "register_question_part_2": "a Lightning tárcáddal?", + "register_answer": "Sikeresen regisztráltál fiókot a következő helyen: {hostname}!", + "login_question_part_1": "Szeretnél bejelentkezni itt:", + "login_question_part_2": "a Lightning tárcáddal?", + "login_answer": "Sikeresen bejelentkeztél itt: {hostname}!", + "link_question_part_1": "Szeretnéd a fiókodat összekapcsolni itt:", + "link_question_part_2": "a Lightning tárcáddal?", + "link_answer": "A Lightning tárcád sikeresen össze lett kapcsolva a fiókoddal itt: {hostname}!", + "auth_question_part_1": "Szeretnél hitelesíteni itt:", + "auth_question_part_2": "a Lightning tárcáddal?", + "auth_answer": "Sikeresen hitelesítve lettél itt: {hostname}!", + "could_not_auth": "Nem sikerült hitelesíteni a következő helyen: {hostname}.", + "authenticate": "Hitelesítés" + }, + "bip47": { + "payment_code": "Fizetési kód", + "contacts": "Kapcsolatok", + "bip47_explain": "Újrahasznosítható és megosztható kód", + "bip47_explain_subtitle": "BIP47", + "purpose": "Újrahasznosítható és megosztható kód (BIP47)", + "pay_this_contact": "Fizess ennek a kapcsolatnak", + "rename_contact": "Kapcsolat átnevezése", + "copy_payment_code": "Fizetési kód másolása", + "hide_contact": "Kapcsolat elrejtése", + "rename": "Átnevezés", + "provide_name": "Adj új nevet ennek a kapcsolatnak", + "add_contact": "Kapcsolat hozzáadása", + "provide_payment_code": "Add meg a fizetési kódot", + "invalid_pc": "Érvénytelen fizetési kód", + "notification_tx_unconfirmed": "Az értesítési tranzakció még nincs megerősítve, kérlek várj", + "failed_create_notif_tx": "Nem sikerült létrehozni az on-chain tranzakciót", + "onchain_tx_needed": "On-chain tranzakcióra van szükség", + "notif_tx_sent": "Értesítési tranzakció elküldve. Kérlek, várj, amíg megerősítésre kerül", + "notif_tx": "Értesítési tranzakció", + "not_found": "Fizetési kód nem található" } } diff --git a/loc/id_id.json b/loc/id_id.json index 7b18c30d265..80494af9796 100644 --- a/loc/id_id.json +++ b/loc/id_id.json @@ -3,47 +3,49 @@ "bad_password": "Kata sandi salah. Coba lagi.", "cancel": "Batalkan", "continue": "Lanjutkan", - "clipboard": "Clipboard", + "clipboard": "Papan klip", + "copied": "Disalin!", + "discard_changes": "Batalkan perubahan?", + "discard_changes_explain": "Anda memiliki perubahan yang belum disimpan. Apakah Anda yakin untuk membuang perubahan dan meninggalkan layar?", "enter_password": "Masukkan kata sandi", "never": "Tidak Pernah", - "disabled": "Tidak aktif", "of": "{number} dari {total}", "ok": "OK", - "storage_is_encrypted": "Ruang penyimpanan terenkripsi. Masukkan kata sandi untuk decript:", + "enter_url": "Masukkan URL", + "storage_is_encrypted": "Ruang penyimpanan terenkripsi. Masukkan kata sandi untuk mendekripsi.", "yes": "Ya", "no": "Tidak", - "save": "Simpan", - "seed": "Benih", + "save": "Simpan...", + "seed": "Seed", "success": "Sukses", "wallet_key": "Kunci Dompet", - "invalid_animated_qr_code_fragment": "QRCode fragment tidak dapat dibaca. Mohon coba lagi", - "file_saved": "Berkas {filePath} sudah tersimpan di {destination}", - "downloads_folder": "Folder unduhan", "close": "Tutup", "change_input_currency": "Ubah input mata uang", "refresh": "Segarkan", - "more": "Lainnya", - "pick_image": "Pilih gambar dari perpustakaan", - "pick_file": "Pilih berkas", + "pick_image": "Pilih dari pustaka", + "pick_file": "Pilih file", "enter_amount": "Masukkan nominal", - "qr_custom_input_button": "Ketuk 10 kali untuk memasuki input khusus" - }, - "alert": { - "default": "Peringatan" + "qr_custom_input_button": "Ketuk 10 kali untuk memasuki input khusus", + "unlock": "Buka kunci", + "port": "Port", + "ssl_port": "Port SSL", + "suggested": "Disarankan" }, "azteco": { "codeIs": "Kode voucher anda adalah", - "errorBeforeRefeem": "Sebelum menebus anda harus menambahkan dompet Bitcoin", - "errorSomething": "Ada yang salah. Apakah voucher ini benar ?", + "errorBeforeRefeem": "Sebelum menebus, Anda harus menambahkan dompet Bitcoin", + "errorSomething": "Ada yang salah. Apakah voucher ini benar?", "redeem": "Menebus ke dompet", "redeemButton": "Menebus", "success": "Sukses", + "successMessage": "Voucher berhasil ditebus! Dana Anda akan ada di dompet Bitcoin Anda dalam sesaat.", "title": "Menebus voucher Azte.co" }, "entropy": { "save": "Simpan", "title": "Entropi", - "undo": "Batalkan" + "undo": "Batalkan", + "amountOfEntropy": "{bits} dari {limit} bit" }, "errors": { "broadcast": "Gagal menyiarkan", @@ -51,81 +53,66 @@ "network": "Kesalahan Jaringan" }, "lnd": { - "active": "Aktif", - "inactive": "Tidak aktif", - "channels": "Kanal", - "no_channels": "Tidak ada kanal", - "claim_balance": "Klaim saldo {saldo}", - "close_channel": "Tutup kanal", - "new_channel": "Kanal baru", - "errorInvoiceExpired": "Faktur kadarluasa", - "force_close_channel": "Tutup paksa kanal?", + "errorInvoiceExpired": "Faktur kedaluwarsa", "expired": "Kadaluarsa", - "node_alias": "Alias node", - "expiresIn": "Kadaluwarsa dalam {waktu} menit", + "expiresIn": "Akan kedaluwarsa dalam {time} menit", "payButton": "Bayar", - "open_channel": "Buka kanal", - "funding_amount_placeholder": "Jumlah pendanaan, contoh 0,001", - "are_you_sure_open_channel": "Apakah Anda yakin ingin membuka kanal ini?", + "payment": "Pembayaran", + "placeholder": "Faktur atau alamat", "potentialFee": "Potensi biaya: {fee}", - "remote_host": "Host jarak jauh", "refill": "Isi ulang", - "reconnect_peer": "Sambungkan rekan kembali", "refill_create": "Untuk melanjutkan, buat dompet Bitcoin untuk diisi ulang.", "refill_external": "Isi ulang dengan Dompet Eksternal", "refill_lnd_balance": "Isi ulang saldo Lightning", - "sameWalletAsInvoiceError": "Anda tidak bisa membayar sebuah fraktur dengan dompet yang digunakan untuk membuat fraktur tersebut.", - "title": "Atur Dana", - "can_send": "Bisa Mengirim", - "can_receive": "Bisa Menerima", - "view_logs": "Lihat Catatan" + "sameWalletAsInvoiceError": "Kamu tidak bisa membayar invoice dengan dompet yang sama yang dipakai untuk membuat invoice.", + "title": "Atur Dana" }, "lndViewInvoice": { "additional_info": "Informasi Tambahan", "for": "Untuk:", - "lightning_invoice": "Fraktur Lightning", - "open_direct_channel": "Buka saluran langsung dengan node ini:", - "please_pay_between_and": "Silakan bayar antara {minimal} dan {maksimal}", + "lightning_invoice": "Faktur Lightning", + "please_pay_between_and": "Silakan bayar antara {min} dan {max}", "please_pay": "Silakan bayar", - "preimage": "Pra gambar", + "preimage": "Preimage", "sats": "sat.", + "date_time": "Tanggal dan Waktu", "wasnt_paid_and_expired": "Faktur ini belum dibayar dan telah kedaluwarsa" }, "plausibledeniability": { - "create_fake_storage": "Create fake encrypted storage", - "create_password": "Buat sebuah kata sandi", + "create_fake_storage": "Buat penyimpanan terenkripsi palsu", "create_password_explanation": "Kata sandi untuk penyimpanan palsu tidak seharusnya sama dengan kata sandi penyimpanan utama Anda", "help": "Under certain circumstances, you might be forced to disclose a password. To keep your coins safe, BlueWallet can create another encrypted storage, with a different password. Under pressure, you can disclose this password to a 3rd party. If entered in BlueWallet, it will unlock new 'fake' storage. This will seem legit to a 3rd party, but will secretly keep your main storage with coins safe.", "help2": "New storage will be fully functional, and you can store some minimum amounts there so it looks more believable.", "password_should_not_match": "Password for fake storage should not match password for your main storage", - "passwords_do_not_match": "Kata sandi tidak cocok, coba lagi", - "retype_password": "Ketik ulang kata sandi", - "success": "Sukses", "title": "Penyangkalan yang Masuk Akal" }, "pleasebackup": { "ask": "Sudahkah Anda menyimpan frase cadangan dompet Anda? Frase cadangan ini diperlukan untuk mengakses dana Anda jika Anda kehilangan perangkat ini. Tanpa frase cadangan, dana Anda akan hilang secara permanen.", - "ask_no": "Tidak, belum saya lakukan", - "ask_yes": "Ya, sudah", - "ok": "OK, sudah saya tulis", - "ok_lnd": "OK, sudah saya simpan", + "ask_no": "Tidak, belum.", + "ask_yes": "Ya, sudah.", + "ok": "OK, sudah saya tulis.", + "ok_lnd": "Oke, saya sudah menyimpannya.", "text": "Silakan tulis frasa mnemonik ini di atas kertas.\nIni adalah cadangan Anda dan Anda bisa menggunakannya untuk memulihkan dompet Anda.", "text_lnd": "Harap luangkan waktu sejenak untuk menyimpan otentikasi LNDHub ini. Ini cadangan Anda yang dapat Anda gunakan untuk memulihkan dompet di perangkat lain.", - "title": "Dompet Anda telah dibuat" + "title": "Dompet Anda telah dibuat ..." }, "receive": { "details_create": "Buat", "details_label": "Deskripsi", "details_setAmount": "Terima sejumlah", - "details_share": "Bagikan", + "details_share": "Bagikan...", + "address_not_found": "Tidak dapat menghasilkan alamat penerima.", "header": "Terima", - "maxSats": "Nominal maksimal adalah {maksimal} sat", - "maxSatsFull": "Nominal maksimal adalah {maksimal} sat atau {mata uang}", - "minSats": "Nominal minimal adalah {minimal} sat", - "minSatsFull": "Nominal minimal adalah {minimal} sat atau {mata uang}" + "reset": "Atur ulang", + "maxSats": "Nominal maksimal adalah {max} sat", + "maxSatsFull": "Nominal maksimal adalah {max} sat atau {currency}", + "minSats": "Nominal minimal adalah {min} sat", + "minSatsFull": "Nominal minimal adalah {min} sat atau {currency}", + "qrcode_for_the_address": "Kode QR untuk alamat ini", + "bip47_explanation": "Kode pembayaran adalah alamat universal yang menghindari pengungkapan alamat dompet Anda. Tidak semua layanan mendukungnya." }, "send": { - "provided_address_is_invoice": "Alamat ini tampaknya untuk sebuah fraktur Lightning. Silakan ke dompet Lightning Anda untuk membayar fraktur ini.", + "provided_address_is_invoice": "Alamat ini tampaknya untuk sebuah faktur Lightning. Silakan ke dompet Lightning Anda untuk membayar faktur ini.", "broadcastButton": "Siarkan", "broadcastError": "Kesalahan", "broadcastNone": "Masukkan hash transaksi", @@ -137,34 +124,42 @@ "create_broadcast": "Siarkan", "create_copy": "Salin dan siarkan nanti", "create_details": "Detail", - "create_fee": "Tarif", + "create_fee": "Biaya", "create_memo": "Memo", "create_satoshi_per_vbyte": "Satoshi per vByte", "create_this_is_hex": "Ini adalah hex transaksi, siap untuk disiarkan ke jaringan.", "create_to": "Ke", "create_tx_size": "Ukuran TX", "create_verify": "Verifikasi di coinb.in", + "details_insert_contact": "Sisipkan Kontak", "details_add_rec_add": "Tambah penerima", "details_add_rec_rem": "Hapus Penerima", + "details_add_recc_rem_all_alert_description": "Apakah Anda yakin ingin menghapus semua penerima?", + "details_add_rec_rem_all": "Hapus Semua Penerima", + "details_recipients_title": "Penerima", + "details_recipient_title": "Penerima #{number} dari #{total}", + "please_complete_recipient_title": "Penerima Tidak Lengkap", + "please_complete_recipient_details": "Harap lengkapi detail penerima #{number} sebelum menambahkan penerima baru.", "details_address": "alamat", "details_address_field_is_not_valid": "Alamat tidak valid", "details_adv_fee_bump": "Izinkan Lonjakan Biaya", - "details_adv_full": "Gunakan Semua Saldo ", + "details_adv_full": "Gunakan Semua Saldo", "details_adv_full_sure": "Apakah anda yakin ingin menggunakan saldo penuh dompet Anda untuk transaksi ini?", "details_adv_full_sure_frozen": "Apakah Anda yakin Anda ingin menggunakan seluruh saldo dompet Anda untuk transaksi ini? Harap dicatat bahwa koin yang dibekukan tidak termasuk", "details_adv_import": "Impor transaksi", - "details_adv_import_qr": "Transaksi Penting (QR)", + "details_adv_import_qr": "Impor Transaksi (QR)", "details_amount_field_is_not_valid": "Jumlah tidak valid", "details_amount_field_is_less_than_minimum_amount_sat": "Jumlah terlalu kecil. Tolong masukkan jumlah yang lebih besar dari 500 sats.", "details_create": "Buat", "details_error_decode": "Tidak dapat membaca alamat Bitcoin", - "details_fee_field_is_not_valid": "Tarif tidak valid", - "details_frozen": "{nominal} BTC dibekukan", + "details_fee_field_is_not_valid": "Biaya tidak valid", + "details_frozen": "{amount} BTC dibekukan.", "details_next": "Selanjutnya", "details_no_signed_tx": "File yang dipilih tidak berisi transaksi yang dapat diimpor.", "details_note_placeholder": "catatan pribadi", "details_scan": "Pindai", "details_scan_hint": "Ketuk dua kali untuk memindai atau mengimpor tujuan", + "details_scan_error": "Kesalahan pindai", "details_total_exceeds_balance": "Jumlah yang dikirim melebihi saldo.", "details_total_exceeds_balance_frozen": "Nominal yang dikirim melebihi saldo yang ada. Harap dicatat bahwa koin yang dibekukan tidak termasuk.", "details_unrecognized_file_format": "Format file tidak dikenal", @@ -177,10 +172,11 @@ "fee_10m": "10m", "fee_1d": "1h", "fee_3h": "3j", - "fee_custom": "Custom", + "fee_custom": "Khusus", + "insert_custom_fee": "Masukkan biaya", "fee_fast": "Cepat", "fee_medium": "Sedang", - "fee_replace_minvb": "Total nilai tarif (satoshi per vByte) yang Anda ingin bayar harus lebih tinggi dari {minimal} say/vByte.", + "fee_replace_minvb": "Total biaya (satoshi per vByte) yang Anda ingin bayar harus lebih tinggi dari {min} sat/vByte.", "fee_satvbyte": "dalam sat/vByte", "fee_slow": "Lambat", "header": "Kirim", @@ -190,23 +186,26 @@ "input_total": "Total:", "permission_camera_message": "Kami membutuhkan izin anda untuk menggunakan kamera.", "psbt_sign": "Tanda tangani transaksi", + "invalid_psbt": "PSBT yang diberikan tidak valid.", "open_settings": "Buka Setelan", - "permission_storage_later": "Tanyakan saya nanti", - "permission_storage_message": "BlueWallet membutuhkan izin anda untuk mengakses tempat penyimpanan anda untuk menyimpan file ini", "permission_storage_denied_message": "BlueWallet tidak bisa menyimpan berkas ini. Harap buka pengaturan perangkat Anda dan aktifkan Izin Penyimpanan.", "permission_storage_title": "Izin Akses Penyimpanan", "psbt_clipboard": "Salin ke Clipboard", "psbt_this_is_psbt": "Ini adalah Transaksi Bitcoin yang Ditandatangani Sebagian (PSBT). Harap selesaikan penandatanganan dengan dompet perangkat keras Anda.", "psbt_tx_export": "Ekspor ke file", "no_tx_signing_in_progress": "Tidak ada transaksi penandatanganan yang sedang berlangsung.", - "outdated_rate": "Tarif diperbaharui terakhir: {tanggal}", + "outdated_rate": "Tarif diperbaharui terakhir: {date}", "psbt_tx_open": "Buka Transaksi yang Ditandatangani", "psbt_tx_scan": "Pindai Transaksi yang Ditandatangani", - "qr_error_no_qrcode": "Kami tidak bisa menemukan QR Code pada gambar terpilih. Pastikan gambar hanya memiliki QR Code dan tidak ada konten tambahan seperti teks, atau tombol.", + "qr_error_no_qrcode": "Kami tidak dapat menemukan Kode QR yang valid pada gambar yang dipilih. Pastikan gambar hanya berisi Kode QR tanpa konten tambahan seperti teks atau tombol.", "reset_amount": "Atur ulang Nominal", "reset_amount_confirm": "Apakah Anda ingin mengatur ulang nominal?", "success_done": "Selesai", - "txSaved": "File transaksi ({filePath}) telah disimpan pada folder Unduhan.", + "txSaved": "File transaksi ({filePath}) telah disimpan.", + "file_saved_at_path": "File ({filePath}) telah disimpan.", + "cant_send_to_silentpayment_adress": "Dompet ini tidak dapat mengirim ke alamat SilentPayment", + "cant_send_to_bip47": "Dompet ini tidak dapat mengirim ke kode pembayaran BIP47", + "cant_find_bip47_notification": "Tambahkan Kode Pembayaran ini ke kontak terlebih dahulu", "problem_with_psbt": "Ada masalah dengan PSBT" }, "settings": { @@ -217,225 +216,428 @@ "about_license": "Izin MIT", "about_release_notes": "Catatan rilisan", "about_review": "Tinggalkan ulasan", - "performance_score": "Skor hasil: {nomor}", + "performance_score": "Skor hasil: {num}", "run_performance_test": "Uji hasil", "about_selftest": "Jalankan tes sendiri", + "block_explorer_invalid_custom_url": "URL yang diberikan tidak valid. Harap masukkan URL yang valid yang diawali dengan http:// atau https://.", + "about_selftest_electrum_disabled": "Tes mandiri tidak tersedia dengan Mode Luring Electrum. Harap nonaktifkan mode luring dan coba lagi.", "about_selftest_ok": "Semua pengujian internal telah berhasil. Dompet berfungsi dengan baik.", "about_sm_github": "GitHub", - "about_sm_discord": "Server Discord", "about_sm_telegram": "Channel Telegram", - "about_sm_twitter": "Ikuti kami di Twitter", - "advanced_options": "Opsi Lanjutan", + "privacy_temporary_screenshots": "Izinkan Pengambilan Layar", + "privacy_temporary_screenshots_instructions": "Perlindungan pengambilan layar akan dinonaktifkan sementara, memungkinkan tangkapan layar dan perekaman layar. Perlindungan akan otomatis aktif kembali saat Anda menutup dan membuka BlueWallet kembali.", "biometrics": "Biometrik", - "biom_10times": "Anda telah mencoba memasukkan kata sandi Anda 10 kali. Apakah Anda ingin mengatur ulang penyimpanan Anda? Semua dompet anda akan dihapus dan penyimpanan anda akan didekripsi. ", + "biometrics_no_longer_available": "Pengaturan perangkat Anda telah berubah dan tidak lagi sesuai dengan pengaturan keamanan yang dipilih di aplikasi. Harap aktifkan kembali biometrik atau kode sandi, kemudian mulai ulang aplikasi untuk menerapkan perubahan ini.", + "biom_10times": "Anda telah mencoba memasukkan kata sandi Anda 10 kali. Apakah Anda ingin mengatur ulang penyimpanan Anda? Semua dompet Anda akan dihapus dan penyimpanan Anda akan didekripsi.", "biom_conf_identity": "Mohon konfirmasi identitas Anda.", - "biom_no_passcode": "Perangkat Anda tidak memiliki kode sandi. Untuk melanjutkan, harap konfigurasikan kode sandi di aplikasi Pengaturan.", + "biom_no_passcode": "Perangkat Anda tidak memiliki kode sandi atau biometrik yang aktif. Untuk melanjutkan, harap konfigurasikan kode sandi atau biometrik di aplikasi Pengaturan.", "biom_remove_decrypt": "Semua dompet Anda akan dihapus dan penyimpanan Anda akan didekripsi. Anda yakin ingin melanjutkan?", "currency": "Mata Uang", - "currency_source": "Harga diperoleh dari", - "default_desc": "Saat dinonaktifkan, BlueWallet akan segera membuka dompet yang dipilih saat diluncurkan.", - "default_info": "Informasi standar", + "currency_source": "Kurs diperoleh dari", + "currency_fetch_error": "Terjadi kesalahan saat memperoleh kurs untuk mata uang yang dipilih.", "default_title": "Diluncurkan", - "default_wallets": "Lihat Semua Dompet", + "donate": "Donasi", + "donate_description": "Bantu kami untuk menjaga Blue tetap gratis!", "electrum_connected": "Terhubung", "electrum_connected_not": "Tidak Terhubung", - "electrum_error_connect": "Tidak dapat terhubung dengan server Electrum", - "lndhub_uri": "Contoh {contoh}", - "electrum_host": "Contoh {contoh}", + "electrum_error_connect": "Tidak dapat terhubung ke server Electrum yang diberikan", + "electrum_error_connect_tor": "Tidak dapat terhubung ke server Electrum yang diberikan. Harap pastikan aplikasi Orbot terhubung dan coba lagi.", + "lndhub_uri": "Contoh {example}", + "electrum_host": "Contoh {example}", "electrum_offline_mode": "Mode luring", + "electrum_offline_description": "Saat diaktifkan, dompet Bitcoin Anda tidak akan mencoba mengambil saldo atau transaksi.", + "electrum_port": "Port, biasanya {example}", "use_ssl": "Gunakan SSL", + "electrum_saved": "Perubahan Anda telah berhasil disimpan. Memulai ulang BlueWallet mungkin diperlukan agar perubahan diterapkan.", + "set_electrum_server_as_default": "Atur {server} sebagai server Electrum bawaan?", + "set_lndhub_as_default": "Atur {url} sebagai server LNDhub bawaan?", "electrum_settings_server": "Server Electrum", "electrum_status": "Status", - "electrum_clear_alert_title": "Hapus riwayat?", - "electrum_clear_alert_message": "Apakah Anda ingin menghapus riwayat server Electrum?", - "electrum_clear_alert_cancel": "Batalkan", - "electrum_clear_alert_ok": "OK", - "electrum_select": "Pilih", + "electrum_preferred_server": "Server Pilihan", + "electrum_preferred_server_description": "Masukkan server yang Anda ingin dompet Anda gunakan untuk semua aktivitas Bitcoin. Setelah diatur, dompet Anda akan secara eksklusif menggunakan server ini untuk memeriksa saldo, mengirim transaksi, dan mengambil data jaringan. Pastikan Anda mempercayai server ini sebelum mengaturnya.", + "electrum_unable_to_connect": "Tidak bisa menghubungkan ke {server}.", + "electrum_history": "Riwayat", + "electrum_reset_to_default": "Ini akan membiarkan BlueWallet memilih server secara acak dari daftar server.", "electrum_reset": "Atur ulang ke bawaan", - "electrum_unable_to_connect": "Tidak bisa mengubungkan ke {server}.", - "electrum_history": "Riwayat server", - "electrum_reset_to_default": "Apakah Anda yakin ingin mengatur ulang Electrum Anda ke pengaturan bawaan?", - "electrum_clear": "Hapus", - "tor_supported": "Tor didukung", - "encrypt_enc_and_pass": "Dienkripsi dan kata sandi dilindungi", + "electrum_reset_to_default_and_clear_history": "Atur ulang ke bawaan dan bersihkan riwayat", + "encrypt_decrypt": "Dekripsi Penyimpanan", + "encrypt_decrypt_q": "Apakah Anda yakin ingin mendekripsi penyimpanan Anda? Ini akan memungkinkan dompet Anda diakses tanpa kata sandi.", + "encrypt_enc_and_pass": "Dilindungi Kata Sandi", + "encrypt_storage_explanation_headline": "Aktifkan Enkripsi Penyimpanan", + "encrypt_storage_explanation_description_line1": "Mengaktifkan Enkripsi Penyimpanan menambahkan lapisan perlindungan ekstra ke aplikasi Anda dengan mengamankan cara data Anda disimpan di perangkat. Hal ini mempersulit siapa pun untuk mengakses informasi Anda tanpa izin.", + "encrypt_storage_explanation_description_line2": "Namun, penting untuk diketahui bahwa enkripsi ini hanya melindungi akses ke dompet Anda yang tersimpan di keychain perangkat. Ini tidak memberikan kata sandi atau perlindungan tambahan pada dompet itu sendiri.", + "i_understand": "Saya mengerti", + "block_explorer": "Penjelajah Blok", + "block_explorer_preferred": "Gunakan penjelajah blok pilihan", + "block_explorer_error_saving_custom": "Kesalahan menyimpan penjelajah blok pilihan", "encrypt_title": "Keamanan", "encrypt_tstorage": "Penyimpanan", - "encrypt_use": "Gunakan {ketik}", + "encrypt_use": "Gunakan {type}", + "set_as_preferred": "Atur sebagai pilihan", + "set_as_preferred_electrum": "Mengatur {host}:{port} sebagai server pilihan akan menonaktifkan koneksi ke server yang disarankan secara acak.", + "encrypted_feature_disabled": "Fitur ini tidak dapat digunakan dengan enkripsi penyimpanan diaktifkan.", + "encrypt_use_expl": "{type} akan digunakan untuk mengonfirmasi identitas Anda sebelum melakukan transaksi, membuka kunci, mengekspor, atau menghapus dompet.", + "biometrics_fail": "Jika {type} tidak diaktifkan, atau gagal membuka kunci, Anda dapat menggunakan kode sandi perangkat sebagai alternatif.", "general": "Umum", - "general_adv_mode": "Enable advanced mode", "general_continuity": "Keberlanjutan", - "header": "setting", + "general_continuity_e": "Saat diaktifkan, Anda akan dapat melihat dompet dan transaksi yang dipilih menggunakan perangkat Apple iCloud lainnya yang terhubung.", + "groundcontrol_explanation": "GroundControl adalah server notifikasi push gratis dan sumber terbuka untuk dompet Bitcoin. Anda dapat memasang server GroundControl Anda sendiri dan mencantumkan URL-nya di sini agar tidak bergantung pada infrastruktur BlueWallet. Biarkan kosong untuk menggunakan server bawaan GroundControl.", + "header": "Pengaturan", "language": "Bahasa", "last_updated": "Terakhir Diperbaharui", - "lightning_error_lndhub_uri": "LNDHub URI tidak valid", + "language_isRTL": "Memulai ulang BlueWallet diperlukan agar orientasi bahasa diterapkan.", + "license": "Lisensi", + "lightning_error_lndhub_uri": "URI LNDhub tidak valid", + "lightning_error_lndhub_uri_tor": "URI LNDhub tidak valid. Harap pastikan aplikasi Orbot terhubung dan coba lagi.", "lightning_saved": "Perubahan Anda telah berhasil disimpan.", "lightning_settings": "Pengaturan Lightning", - "tor_settings": "Pengaturan tor", + "lightning_settings_explain": "Untuk terhubung ke node LND Anda sendiri, harap pasang LNDhub dan masukkan URL-nya di sini di pengaturan. Harap perhatikan bahwa hanya dompet yang dibuat setelah menyimpan perubahan yang akan terhubung ke LNDhub yang ditentukan.", + "lndhub_github": "Repositori GitHub", "network": "Jaringan", "network_broadcast": "Siaran Transaksi", "network_electrum": "Server Electrum", + "electrum_suggested_description": "Saat server pilihan tidak diatur, server yang disarankan akan dipilih secara acak untuk digunakan.", "not_a_valid_uri": "URI tidak valid", "notifications": "Pemberitahuan", "open_link_in_explorer": "Buka tautan di peramban", "password": "kata sandi", - "password_explain": "Buat kata sandi untuk dekripsi penyimpanan", - "passwords_do_not_match": "Kata sandi tidak cocok", + "password_explain": "Masukkan kata sandi yang akan Anda gunakan untuk membuka kunci penyimpanan Anda.", "plausible_deniability": "Plausible deniability...", "privacy": "Privasi", "privacy_read_clipboard": "Baca Clipboard", "privacy_system_settings": "Pengaturan sistem", "privacy_quickactions": "Jalan Pintas Dompet", + "privacy_quickactions_explanation": "Sentuh dan tahan ikon aplikasi BlueWallet untuk melihat saldo dompet Anda dengan cepat.", + "privacy_clipboard_explanation": "Sediakan pintasan jika alamat atau faktur ditemukan di clipboard Anda.", "privacy_do_not_track": "Nonaktifkan Analitik", - "retype_password": "Ulangi kata sandi", + "privacy_do_not_track_explanation": "Informasi performa dan keandalan tidak akan dikirim untuk dianalisis.", + "rate": "Kurs", + "push_notifications_explanation": "Dengan mengaktifkan notifikasi, token perangkat Anda akan dikirim ke server, beserta alamat dompet dan ID transaksi untuk semua dompet dan transaksi yang dilakukan setelah notifikasi diaktifkan. Token perangkat digunakan untuk mengirim notifikasi, dan informasi dompet memungkinkan kami memberitahu Anda tentang Bitcoin masuk atau konfirmasi transaksi.\n\nHanya informasi setelah Anda mengaktifkan notifikasi yang dikirimkan—tidak ada data sebelumnya yang dikumpulkan.\n\nMenonaktifkan notifikasi akan menghapus semua informasi ini dari server. Selain itu, menghapus dompet dari aplikasi juga akan menghapus informasi terkait dompet tersebut dari server.", + "selfTest": "Tes Mandiri", "save": "simpan", "saved": "Tersimpan", - "success_transaction_broadcasted": "Berhasil! Transaksi Anda telah disiarkan!", + "success_transaction_broadcasted": "Transaksi Anda telah berhasil disiarkan!", "total_balance": "Total saldo", + "total_balance_explanation": "Tampilkan total saldo semua dompet Anda pada widget layar utama.", + "widgets": "Widget", "tools": "Alat" }, "notifications": { - "no_and_dont_ask": "Tidak, dan jangan tanya saya lagi", - "ask_me_later": "Tanyakan saya nanti" + "would_you_like_to_receive_notifications": "Apakah Anda ingin menerima notifikasi saat ada pembayaran masuk?", + "notifications_subtitle": "Pembayaran masuk dan konfirmasi transaksi", + "no_and_dont_ask": "Tidak, dan jangan tanya lagi.", + "permission_denied_message": "Anda telah menolak izin untuk mengirim notifikasi. Jika Anda ingin menerima notifikasi, harap aktifkan di pengaturan perangkat Anda." }, "transactions": { + "cancel_explain": "Kami akan mengganti transaksi ini dengan yang membayar Anda dan memiliki biaya lebih tinggi. Ini secara efektif membatalkan transaksi saat ini. Ini disebut RBF—Replace by Fee.", "cancel_no": "Transaksi ini tidak bisa digantikan.", "cancel_title": "Batalkan transaksi ini (RBF)", - "confirmations_lowercase": "{konfirmasi} konfirmasi", - "copy_link": "Salin tautan", + "transaction_loading_error": "Terjadi kesalahan saat memuat transaksi. Harap coba lagi nanti.", + "transaction_not_available": "Transaksi tidak tersedia", + "confirmations_lowercase": "{confirmations} konfirmasi", "expand_note": "Perluas Catatan", "cpfp_create": "Buat", + "cpfp_exp": "Kami akan membuat transaksi lain yang membelanjakan transaksi Anda yang belum dikonfirmasi. Total biaya akan lebih tinggi dari biaya transaksi awal, sehingga seharusnya ditambang lebih cepat. Ini disebut CPFP—Child Pays for Parent.", + "cpfp_no_bump": "Transaksi ini tidak dapat dinaikkan biayanya.", + "cpfp_title": "Naikkan Biaya (CPFP)", "details_balance_hide": "Sembunyikan Saldo", "details_balance_show": "Tunjukkan Saldo", "details_copy": "Salin", - "details_copy_amount": "Salin Nominal", + "details_copy_block_explorer_link": "Salin Tautan Penjelajah Blok", "details_copy_note": "Salin Catatan", "details_copy_txid": "Salin ID Transaksi", - "details_from": "Input", "details_inputs": "Input", + "details_outputs": "Keluaran", "date": "Tanggal", "details_received": "Diterima", - "transaction_note_saved": "Catatan transaksi telah berhasil tersimpan.", - "details_show_in_block_explorer": "Tampilkan di block explorer", + "details_view_in_browser": "Lihat di Peramban", "details_title": "Transaksi", + "incoming_transaction": "Transaksi Masuk", + "outgoing_transaction": "Transaksi Keluar", + "expired_transaction": "Transaksi Kedaluwarsa", + "pending_transaction": "Transaksi Tertunda", + "offchain": "Off-chain", + "onchain": "On-chain", + "details_to": "Keluaran", + "enable_offline_signing": "Dompet ini tidak digunakan bersama dengan penandatanganan luring. Apakah Anda ingin mengaktifkannya sekarang?", + "list_conf": "Konf: {number}", "pending": "tertunda", - "pending_with_amount": "Tertunda {nominal1} ({nominal2})", - "received_with_amount": "+{nominal1} ({nominal2})", + "pending_with_amount": "Tertunda {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", "eta_10m": "Estimasi: dalam ~10 menit", "eta_3h": "Estimasi: dalam ~3 jam", "eta_1d": "Estimasi: dalam ~1 hari", "list_title": "transaksi", + "list_title_sent": "Terkirim", + "list_title_received": "Diterima", + "transaction": "Transaksi", + "open_url_error": "Tidak dapat membuka tautan dengan peramban bawaan. Harap ubah peramban bawaan Anda dan coba lagi.", + "rbf_explain": "Kami akan mengganti transaksi ini dengan yang memiliki biaya lebih tinggi sehingga akan ditambang lebih cepat. Ini disebut RBF—Replace by Fee.", + "rbf_title": "Percepat (RBF)", + "status_bump": "Percepat", "status_cancel": "Batalkan Transaksi", + "transactions_count": "Jumlah Transaksi", "txid": "ID Transaksi", - "updating": "Memperbaharui..." + "updating": "Memperbaharui...", + "watchOnlyWarningTitle": "Peringatan keamanan", + "watchOnlyWarningDescription": "Berhati-hatilah terhadap penipu yang sering menggunakan dompet \"hanya-lihat\" untuk menipu pengguna. Dompet ini tidak memungkinkan Anda mengendalikan atau mengirim dana; mereka hanya membiarkan Anda melihat saldo.", + "custom_fee_warning_title": "Peringatan", + "custom_fee_warning_description": "Biaya di bawah 1 sat/vB valid, tetapi mungkin tidak diteruskan karena kebijakan node.", + "details_eta_analyzing": "Menganalisis...", + "details_sent": "Terkirim", + "details_section": "Detail", + "details_explorer": "penjelajah", + "details_network_fee": "Biaya Jaringan", + "details_to_address": "Ke", + "details_id": "ID", + "details_note": "Catatan", + "details_add_note": "tambah", + "details_advanced": "Lanjutan", + "details_fee_rate": "Tarif biaya", + "details_size": "Ukuran", + "details_virtual_size": "Ukuran virtual", + "details_tx_hex": "Hex Tx", + "details_inputs_count": "Input ({count})", + "details_outputs_count": "Keluaran ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Dompet Bitcoin yang sederhana dan canggih", "add_create": "Buat", + "total_balance": "Total saldo", + "add_entropy_reset_title": "Atur Ulang Entropi", + "add_entropy_reset_message": "Mengubah tipe dompet akan mengatur ulang entropi saat ini. Apakah Anda ingin melanjutkan?", + "add_entropy": "Entropi", + "add_entropy_bytes": "{bytes} bita entropi", + "add_entropy_generated": "{gen} bita entropi yang dihasilkan", + "add_entropy_provide": "Sediakan entropi melalui lemparan dadu", + "add_entropy_remain": "{gen} bita entropi yang dihasilkan. Sisa {rem} bita akan diperoleh dari generator angka acak Sistem.", "add_import_wallet": "Impor dompet", - "add_lndhub": "Hubungan ke LNDHub Anda", + "add_lightning": "Lightning", + "add_lightning_explain": "Untuk membelanjakan dengan transaksi instan", + "add_lndhub": "Hubungkan ke LNDhub Anda", + "add_lndhub_error": "Alamat node yang diberikan bukan node LNDhub yang valid.", + "add_lndhub_placeholder": "Alamat Node Anda", "add_placeholder": "dompet pertama saya", "add_title": "tambah dompet", "add_wallet_name": "nama dompet", "add_wallet_type": "tipe", - "balance": "Saldo", + "add_wallet_seed_length": "Panjang Frasa Pemulihan", + "add_wallet_seed_length_12": "12 kata", + "add_wallet_seed_length_24": "24 kata", + "clipboard_bitcoin": "Anda memiliki alamat Bitcoin di clipboard. Apakah Anda ingin menggunakannya untuk transaksi?", + "clipboard_lightning": "Anda memiliki faktur Lightning di clipboard. Apakah Anda ingin menggunakannya untuk transaksi?", + "clear_clipboard_on_import": "Bersihkan clipboard saat impor", "details_address": "Alamat", "details_advanced": "Lanjutan", "details_are_you_sure": "Yakin?", "details_connected_to": "Terhubung ke", + "details_del_wb_err": "Jumlah saldo yang diberikan tidak sesuai dengan saldo dompet ini. Harap coba lagi.", + "details_del_wb_q": "Dompet ini memiliki saldo. Sebelum melanjutkan, harap diketahui bahwa Anda tidak akan dapat memulihkan dana tanpa frasa pemulihan dompet ini. Untuk menghindari penghapusan yang tidak disengaja, harap masukkan saldo dompet Anda sebesar {balance} satoshi.", "details_delete": "Hapus", "details_delete_wallet": "Hapus Dompet", - "details_display": "Tampilkan dalam Daftar Dompet", + "details_derivation_path": "jalur derivasi", + "details_display": "Tampilkan di Layar Utama", "details_export_backup": "Ekspor / backup", "details_export_history": "Ekspor riwayat ke CSV", "details_master_fingerprint": "Sidik Jari Utama", - "details_no_cancel": "Tidak, batalkan", - "details_save": "Simpan", + "details_multisig_type": "multisig", "details_show_xpub": "Tampilkan XPUB dompet", "details_show_addresses": "Tunjukkan alamat", "details_title": "Dompet", + "wallets": "Dompet", + "swipe_balance_hide": "Sembunyikan", + "swipe_balance_show": "Tampilkan", + "drag_to_reorder": "Seret untuk menyusun ulang", + "clear_search": "Bersihkan pencarian", "details_type": "Tipe", + "details_use_with_hardware_wallet": "Gunakan dengan Dompet Perangkat Keras", "details_yes_delete": "Ya, hapus", + "enter_bip38_password": "Masukkan kata sandi untuk mendekripsi", "export_title": "ekspor dompet", "import_do_import": "Impor", "import_passphrase": "Frasa sandi", "import_passphrase_title": "Frasa sandi", + "import_passphrase_message": "Masukkan frasa sandi jika Anda menggunakannya", "import_error": "Gagal mengimpor. Pastikan data yang diketik benar.", + "import_explanation": "Harap masukkan kata-kata frasa pemulihan, kunci publik, WIF, atau apa pun yang Anda miliki. BlueWallet akan melakukan yang terbaik untuk menebak format yang benar dan mengimpor dompet Anda.", "import_imported": "Diimpor", "import_scan_qr": "atau mau pindai QR code?", "import_success": "Berhasil", + "import_success_watchonly": "Dompet Anda telah berhasil diimpor. PERINGATAN: Ini adalah dompet hanya-lihat, Anda TIDAK dapat membelanjakan dari dompet ini.", "import_search_accounts": "Cari akun", "import_title": "impor", + "learn_more": "Pelajari lebih lanjut", "import_discovery_title": "Penemuan", "import_discovery_subtitle": "Pilih dompet yang ditemukan", + "import_discovery_derivation": "Gunakan jalur derivasi khusus", "import_discovery_no_wallets": "Tidak ada dompet yang ditemukan", - "import_derivation_found": "ditemukan", - "import_derivation_found_not": "tidak ditemukan", - "import_derivation_loading": "memuat", - "import_derivation_unknown": "tidak diketahui", + "import_discovery_offline": "BlueWallet saat ini dalam mode luring. Dalam mode ini, BlueWallet tidak dapat memverifikasi keberadaan dompet, sehingga Anda perlu memilih yang benar secara manual", + "import_derivation_found": "Ditemukan", + "import_derivation_found_not": "Tidak ditemukan", + "import_derivation_loading": "Memuat...", + "import_derivation_subtitle": "Masukkan jalur derivasi khusus, dan kami akan mencoba menemukan dompet Anda.", + "import_derivation_title": "Jalur derivasi", + "import_derivation_unknown": "Tidak diketahui", + "import_wrong_path": "Jalur derivasi salah", "list_create_a_button": "tambah sekarang", "list_create_a_wallet": "Tambah dompet", - "list_create_a_wallet_text": "Gratis dan Anda bisa membuat\nsebanyak yang Anda suka.", + "list_create_a_wallet_text": "Gratis, dan Anda dapat membuat \nsebanyak yang Anda mau.", "list_empty_txs1": "Transaksimu akan muncul di sini,", + "list_empty_txs1_lightning": "Dompet Lightning sebaiknya digunakan untuk transaksi harian Anda. Biayanya sangat murah dan kecepatannya sangat cepat.", "list_empty_txs2": "Mulai dengan dompet Anda.", + "list_empty_txs2_lightning": "\nUntuk mulai menggunakannya, ketuk Atur Dana dan isi saldo Anda.", "list_latest_transaction": "transaksi terbaru", "list_long_choose": "Pilih Foto", + "paste_from_clipboard": "Tempel", + "import_file": "Impor File", "list_long_scan": "Pindai QR Code", "list_title": "Dompet", "list_tryagain": "Coba lagi", - "reorder_title": "Susun Dompet", + "no_ln_wallet_error": "Sebelum membayar faktur Lightning, Anda harus menambahkan dompet Lightning terlebih dahulu.", + "looks_like_bip38": "Ini tampaknya merupakan kunci privat yang dilindungi kata sandi (BIP38).", + "manage_title": "Kelola Dompet", + "no_results_found": "Tidak ada hasil yang ditemukan.", "please_continue_scanning": "Harap lanjutkan memindai.", + "select_no_bitcoin": "Saat ini tidak ada dompet Bitcoin yang tersedia.", + "select_no_bitcoin_exp": "Dompet Bitcoin diperlukan untuk mengisi ulang dompet Lightning. Harap buat atau impor satu.", "select_wallet": "Pilih dompet", - "xpub_copiedToClipboard": "Disalin ke clipboard.", "pull_to_refresh": "Tarik untuk Segarkan", - "xpub_title": "XPUB dompet" + "warning_do_not_disclose": "Jangan pernah membagikan informasi di bawah ini", + "scan_import": "Pindai kode QR ini untuk mengimpor dompet Anda di aplikasi lain.", + "write_down_header": "Buat cadangan manual", + "write_down": "Tulis dan simpan kata-kata ini dengan aman. Gunakan untuk memulihkan dompet Anda di kemudian hari.", + "wallet_type_this": "Tipe dompet ini adalah {type}.", + "share_number": "Bagikan {number}", + "copy_ln_url": "Salin dan simpan URL ini dengan aman untuk memulihkan dompet Anda di kemudian hari.", + "copy_ln_public": "Salin dan simpan informasi ini dengan aman untuk memulihkan dompet Anda di kemudian hari.", + "add_ln_wallet_first": "Anda harus menambahkan dompet Lightning terlebih dahulu.", + "identity_pubkey": "Pubkey Identitas", + "xpub_title": "XPUB dompet", + "manage_wallets_search_placeholder": "Cari dompet, alamat, transaksi dan memo", + "more_info": "Info Lainnya", + "details_delete_wallet_error_message": "Terjadi masalah saat mengonfirmasi apakah dompet ini telah dihapus dari notifikasi—ini bisa disebabkan oleh masalah jaringan atau koneksi yang buruk. Jika Anda melanjutkan, Anda mungkin masih menerima notifikasi untuk transaksi terkait dompet ini, bahkan setelah dompet dihapus.", + "details_delete_anyway": "Hapus saja" + }, + "total_balance_view": { + "display_in_bitcoin": "Tampilkan dalam Bitcoin", + "hide": "Sembunyikan", + "display_in_sats": "Tampilkan dalam sats", + "display_in_fiat": "Tampilkan dalam {currency}", + "title": "Total saldo", + "explanation": "Lihat total saldo semua dompet Anda di layar ikhtisar." }, "multisig": { - "multisig_vault": "Brankas", + "multisig_vault": "Brankas Multisig", + "default_label": "Brankas Multisig", "multisig_vault_explain": "Keamanan terbaik untuk nominal besar", "provide_signature": "Berikan tanda tangan", - "vault_key": "Kunci Brankas {nomor}", - "fee": "Tarif: {nomor}", - "fee_btc": "{nomor} BTC", + "provide_signature_details": "Gunakan perangkat dan dompet tempat kunci berada untuk menandatangani transaksi ini", + "provide_signature_details_bluewallet": "Di BlueWallet, buka menu layar Kirim dan pilih ", + "provide_signature_next_steps": "Pindai atau Impor Transaksi yang Ditandatangani", + "provide_signature_next_steps_details": "Setelah dompet Anda berhasil menandatangani transaksi, pindai kode QR yang disediakan atau impor file yang menyertainya, kemudian tinjau semua detail transaksi sebelum menyiarkannya.", + "vault_key": "Kunci Brankas {number}", + "required_keys_out_of_total": "Kunci yang diperlukan dari total", + "fee": "Biaya: {number}", + "fee_btc": "{number} BTC", "confirm": "Konfirmasi", "header": "Kirim", - "share": "bagikan", + "share": "Bagikan...", "view": "Lihat", + "shared_key_detected": "Penanda tangan bersama dibagikan", + "shared_key_detected_question": "Seorang penanda tangan bersama dibagikan kepada Anda, apakah Anda ingin mengimpornya?", "manage_keys": "Kelola kunci", "how_many_signatures_can_bluewallet_make": "BlueWallet bisa membuat berapa tanda tangan?", - "signatures_we_can_make": "bisa membuat {nomor}", + "signatures_required_to_spend": "Tanda tangan yang diperlukan {number}", + "signatures_we_can_make": "bisa membuat {number}", "scan_or_import_file": "Pindai atau impor berkas", + "export_coordination_setup": "Ekspor Pengaturan Koordinasi", + "cosign_this_transaction": "Tanda tangani bersama transaksi ini?", "lets_start": "Ayo mulai", "create": "Buat", + "native_segwit_title": "Praktik terbaik", + "wrapped_segwit_title": "Kompatibilitas terbaik", + "legacy_title": "Legacy", "co_sign_transaction": "Tanda tangani transaksi", "what_is_vault": "Sebuah Brankas adalah sebuah", + "what_is_vault_numberOfWallets": " multisig {m}-dari-{n} ", "what_is_vault_wallet": "dompet.", "vault_advanced_customize": "Pengaturan Brankas", "needs": "Ini membutuhkan", + "what_is_vault_description_number_of_vault_keys": " {m} kunci brankas ", + "what_is_vault_description_to_spend": "untuk membelanjakan dan yang ketiga bisa Anda \ngunakan sebagai cadangan.", + "what_is_vault_description_to_spend_other": "untuk dibelanjakan.", + "quorum": "kuorum {m} dari {n}", + "quorum_header": "Kuorum", "of": "dari", "wallet_type": "Tipe Dompet", + "invalid_mnemonics": "Frasa mnemonik ini tampaknya tidak valid.", + "invalid_cosigner": "Data penanda tangan bersama tidak valid", + "not_a_multisignature_xpub": "Ini bukan XPUB dari dompet multisignature!", + "invalid_cosigner_format": "Penanda tangan bersama tidak benar: Ini bukan penanda tangan bersama untuk format {format}.", "create_new_key": "Buat Baru", "scan_or_open_file": "Pindai atau buka berkas", + "i_have_mnemonics": "Saya memiliki frasa pemulihan untuk kunci ini.", + "type_your_mnemonics": "Masukkan frasa pemulihan untuk mengimpor kunci Brankas yang sudah ada.", + "this_is_cosigners_xpub": "Ini adalah XPUB penanda tangan bersama—siap untuk diimpor ke dompet lain. Aman untuk dibagikan.", + "this_is_cosigners_xpub_airdrop": "Jika Anda berbagi melalui AirDrop, penerima harus berada di layar koordinasi.", + "wallet_key_created": "Kunci Brankas Anda telah dibuat. Luangkan waktu sejenak untuk mencadangkan frasa pemulihan Anda dengan aman.", + "are_you_sure_seed_will_be_lost": "Apakah Anda yakin? Frasa pemulihan Anda akan hilang jika Anda tidak memiliki cadangan.", + "forget_this_seed": "Lupakan frasa pemulihan ini dan gunakan XPUB sebagai gantinya.", + "view_edit_cosigners": "Lihat/Edit Penanda Tangan Bersama", + "this_cosigner_is_already_imported": "Penanda tangan bersama ini sudah diimpor.", + "export_signed_psbt": "Ekspor PSBT yang Ditandatangani", "input_fp": "Masukkan sidik jari", + "input_fp_explain": "Lewati untuk menggunakan bawaan (00000000)", + "input_path": "Masukkan Jalur Derivasi", + "input_path_explain": "Lewati untuk menggunakan bawaan ({default})", "ms_help": "Bantuan", + "ms_help_title": "Cara Kerja Brankas Multisig: Tips dan Trik", + "ms_help_text": "Dompet dengan beberapa kunci, untuk keamanan yang lebih tinggi atau penyimpanan bersama", + "ms_help_title1": "Disarankan menggunakan beberapa perangkat.", + "ms_help_1": "Brankas akan bekerja dengan aplikasi BlueWallet lain dan dompet yang kompatibel dengan PSBT, seperti Electrum, Specter, Coldcard, Cobo Vault, dll.", "ms_help_title2": "Mengedit kunci", + "ms_help_2": "Anda dapat membuat semua kunci Brankas di perangkat ini dan menghapus atau mengeditnya nanti. Memiliki semua kunci di perangkat yang sama setara dengan keamanan dompet Bitcoin biasa.", "ms_help_title3": "Cadangan Brankas", + "ms_help_3": "Pada opsi dompet, Anda akan menemukan cadangan Brankas dan cadangan hanya-lihat Anda. Cadangan ini seperti peta ke dompet Anda. Ini sangat penting untuk pemulihan dompet jika Anda kehilangan salah satu frasa pemulihan Anda.", "ms_help_title4": "Mengimpor Brankas", - "ms_help_title5": "Enable advanced mode" + "ms_help_4": "Untuk mengimpor multisig, gunakan file cadangan Anda dan fitur Impor. Jika Anda hanya memiliki frasa pemulihan dan XPUB, Anda dapat menggunakan tombol Impor individual saat membuat kunci Brankas.", + "ms_help_title5": "Enable advanced mode", + "ms_help_5": "Secara bawaan, BlueWallet akan menghasilkan Brankas 2-dari-3. Untuk membuat kuorum yang berbeda atau mengubah tipe alamat, aktifkan Mode Lanjutan di Pengaturan." }, "is_it_my_address": { "title": "Apakah ini alamat saya?", - "owns": "{label} memiliki {alamat}", + "owns": "{label} memiliki {address}", "enter_address": "Masukkan alamat", "check_address": "Cek alamat", - "view_qrcode": "Lihat QRCode" + "no_wallet_owns_address": "Tidak ada dompet yang tersedia memiliki alamat yang diberikan.", + "view_qrcode": "Lihat Kode QR" + }, + "autofill_word": { + "title": "Kata terakhir frasa pemulihan", + "enter": "Masukkan sebagian frasa mnemonik Anda", + "generate_word": "Hasilkan kata terakhir", + "error": "Input bukan frasa mnemonik 11 atau 23 kata. Harap coba lagi." }, "cc": { "change": "Ubah", + "coins_selected": "Koin Terpilih ({number})", + "selected_summ": "{value} terpilih", + "empty": "Dompet ini saat ini tidak memiliki koin.", "freeze": "Bekukan", "freezeLabel": "Bekukan", "freezeLabel_un": "Jangan bekukan", + "header": "Kontrol koin", "use_coin": "Gunakan Koin", - "use_coins": "Gunakan koin" + "use_coins": "Gunakan koin", + "tip": "Fitur ini memungkinkan Anda melihat, memberi label, membekukan, atau memilih koin untuk pengelolaan dompet yang lebih baik. Anda dapat memilih beberapa koin dengan mengetuk lingkaran berwarna.", + "sort_asc": "Naik", + "sort_desc": "Turun", + "sort_height": "Tinggi", + "sort_value": "Nilai", + "sort_label": "Label", + "sort_status": "Status", + "sort_by": "Urutkan berdasarkan" }, "units": { "BTC": "BTC", @@ -444,15 +646,59 @@ "sats": "sats" }, "addresses": { + "copy_private_key": "Salin kunci privat", + "sensitive_private_key": "Peringatan: kunci privat sangatlah sensitif. Lanjutkan?", "sign_title": "Tanda tangani/Verifikasi Pesan", + "sign_help": "Di sini Anda dapat membuat atau memverifikasi tanda tangan kriptografi berdasarkan alamat Bitcoin.", "sign_sign": "Tanda tangani", "sign_verify": "Verifikasi", "sign_signature_correct": "Verifikasi berhasil!", + "sign_signature_incorrect": "Verifikasi gagal!", "sign_placeholder_address": "Alamat", "sign_placeholder_message": "Pesan", "sign_placeholder_signature": "Tanda Tangan", + "addresses_title": "Alamat", "type_change": "Ubah", "type_receive": "Terima", + "type_used": "Terpakai", "transactions": "transaksi" + }, + "lnurl_auth": { + "register_question_part_1": "Apakah Anda ingin mendaftarkan akun di", + "register_question_part_2": "menggunakan dompet Lightning Anda?", + "register_answer": "Anda berhasil mendaftarkan akun di {hostname}!", + "login_question_part_1": "Apakah Anda ingin masuk di", + "login_question_part_2": "menggunakan dompet Lightning Anda?", + "login_answer": "Anda berhasil masuk di {hostname}!", + "link_question_part_1": "Apakah Anda ingin menautkan akun Anda di", + "link_question_part_2": "ke dompet Lightning Anda?", + "link_answer": "Dompet Lightning Anda berhasil ditautkan ke akun Anda di {hostname}!", + "auth_question_part_1": "Apakah Anda ingin diautentikasi di", + "auth_question_part_2": "menggunakan dompet Lightning Anda?", + "auth_answer": "Anda berhasil terautentikasi di {hostname}!", + "could_not_auth": "Kami tidak dapat mengautentikasi Anda ke {hostname}.", + "authenticate": "Autentikasi" + }, + "bip47": { + "payment_code": "Kode Pembayaran", + "contacts": "Kontak", + "bip47_explain": "Kode yang dapat digunakan ulang dan dibagikan", + "bip47_explain_subtitle": "BIP47", + "purpose": "Kode yang dapat digunakan ulang dan dibagikan (BIP47)", + "pay_this_contact": "Bayar kontak ini", + "rename_contact": "Ubah nama kontak", + "copy_payment_code": "Salin Kode Pembayaran", + "hide_contact": "Sembunyikan kontak", + "rename": "Ubah nama", + "provide_name": "Berikan nama baru untuk kontak ini", + "add_contact": "Tambah Kontak", + "provide_payment_code": "Berikan Kode Pembayaran", + "invalid_pc": "Kode Pembayaran tidak valid", + "notification_tx_unconfirmed": "Transaksi pemberitahuan belum dikonfirmasi, harap tunggu", + "failed_create_notif_tx": "Gagal membuat transaksi on-chain", + "onchain_tx_needed": "Transaksi on-chain diperlukan", + "notif_tx_sent": "Transaksi pemberitahuan terkirim. Harap tunggu sampai dikonfirmasi", + "notif_tx": "Transaksi pemberitahuan", + "not_found": "Kode pembayaran tidak ditemukan" } } diff --git a/loc/index.ts b/loc/index.ts index 1fc0a85ac41..6b565e1c67c 100644 --- a/loc/index.ts +++ b/loc/index.ts @@ -1,21 +1,109 @@ -import Localization from 'react-localization'; +// Translation glossary: see ./vocabulary.md for canonical Bitcoin/Lightning term renderings per language. +// Update vocabulary.md whenever you add a new term or change a shipped translation in a locale .json. + import AsyncStorage from '@react-native-async-storage/async-storage'; +import BigNumber from 'bignumber.js'; import dayjs from 'dayjs'; -import relativeTime from 'dayjs/plugin/relativeTime'; import localizedFormat from 'dayjs/plugin/localizedFormat'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import Localization, { LocalizedStrings } from 'react-localization'; +import { I18nManager } from 'react-native'; import * as RNLocalize from 'react-native-localize'; -import BigNumber from 'bignumber.js'; +import { satoshiToLocalCurrency } from '../blue_modules/currency'; import { BitcoinUnit } from '../models/bitcoinUnits'; -import { AvailableLanguages } from './languages'; -import { I18nManager } from 'react-native'; -const currency = require('../blue_modules/currency'); +import { AvailableLanguages, LangCode } from './languages'; +import enJson from './en.json'; export const STORAGE_KEY = 'lang'; dayjs.extend(relativeTime); dayjs.extend(localizedFormat); +interface ILocalization1 extends LocalizedStrings {} + +// overriding formatString to only return string +interface ILocalization extends Omit { + formatString: (...args: Parameters) => string; +} + +// Lazy loaders for non-en langs +type LanguageDict = typeof enJson; +const languageLoaders: Record, () => LanguageDict> = { + ar: () => require('./ar.json'), + be: () => require('./be@tarask.json'), + bg_bg: () => require('./bg_bg.json'), + bqi: () => require('./bqi.json'), + ca: () => require('./ca.json'), + cs_cz: () => require('./cs_cz.json'), + cy: () => require('./cy.json'), + da_dk: () => require('./da_dk.json'), + de_de: () => require('./de_de.json'), + el: () => require('./el.json'), + es: () => require('./es.json'), + es_419: () => require('./es_419.json'), + et: () => require('./et_EE.json'), + fa: () => require('./fa.json'), + fi_fi: () => require('./fi_fi.json'), + fo: () => require('./fo.json'), + fr_fr: () => require('./fr_fr.json'), + he: () => require('./he.json'), + hr_hr: () => require('./hr_hr.json'), + hu_hu: () => require('./hu_hu.json'), + id_id: () => require('./id_id.json'), + it: () => require('./it.json'), + jp_jp: () => require('./jp_jp.json'), + 'kk@Cyrl': () => require('./kk@Cyrl.json'), + kn: () => require('./kn.json'), + ko_kr: () => require('./ko_KR.json'), + lrc: () => require('./lrc.json'), + ms: () => require('./ms.json'), + nb_no: () => require('./nb_no.json'), + ne: () => require('./ne.json'), + nl_nl: () => require('./nl_nl.json'), + pcm: () => require('./pcm.json'), + pl: () => require('./pl.json'), + pt_br: () => require('./pt_br.json'), + pt_pt: () => require('./pt_pt.json'), + ro: () => require('./ro.json'), + ru: () => require('./ru.json'), + si_lk: () => require('./si_LK.json'), + sk_sk: () => require('./sk_sk.json'), + sl_si: () => require('./sl_SI.json'), + sq_AL: () => require('./sq_AL.json'), + sr_rs: () => require('./sr_RS.json'), + sv_se: () => require('./sv_se.json'), + th_th: () => require('./th_th.json'), + tr_tr: () => require('./tr_tr.json'), + ua: () => require('./ua.json'), + vi_vn: () => require('./vi_vn.json'), + zar_afr: () => require('./zar_afr.json'), + zar_xho: () => require('./zar_xho.json'), + zh_cn: () => require('./zh_cn.json'), + zh_tw: () => require('./zh_tw.json'), +}; + +// Cache so toggling between languages does not re-parse the JSON. +export const parsedLanguages: Record = { en: enJson }; + +const loc = new Localization({ en: enJson }) as ILocalization; + +const isKnownLang = (lang: string): lang is Exclude => Object.prototype.hasOwnProperty.call(languageLoaders, lang); + +const applyLanguage = (lang: string) => { + if (lang === 'en' || !isKnownLang(lang)) { + loc.setContent({ en: enJson }); + loc.setLanguage('en'); + return; + } + if (!parsedLanguages[lang]) { + parsedLanguages[lang] = languageLoaders[lang](); + } + // `setContent` resets active language to interface; explicit setLanguage after, with `en` as fallback. + loc.setContent({ en: enJson, [lang]: parsedLanguages[lang] }); + loc.setLanguage(lang); +}; + const setDateTimeLocale = async () => { let lang = (await AsyncStorage.getItem(STORAGE_KEY)) ?? ''; let localeForDayJSAvailable = true; @@ -30,12 +118,19 @@ const setDateTimeLocale = async () => { lang = 'bg'; require('dayjs/locale/bg'); break; + case 'bqi': + lang = 'fa'; + require('dayjs/locale/fa'); + break; case 'ca': require('dayjs/locale/ca'); break; case 'cy': require('dayjs/locale/cy'); break; + case 'cs_cz': + require('dayjs/locale/cs'); + break; case 'da_dk': require('dayjs/locale/da'); break; @@ -56,11 +151,14 @@ const setDateTimeLocale = async () => { case 'et': require('dayjs/locale/et'); break; + case 'fa': + require('dayjs/locale/fa'); + break; case 'fi_fi': require('dayjs/locale/fi'); break; - case 'fa': - require('dayjs/locale/fa'); + case 'fo': + require('dayjs/locale/fo'); break; case 'fr_fr': require('dayjs/locale/fr'); @@ -84,25 +182,41 @@ const setDateTimeLocale = async () => { lang = 'ja'; require('dayjs/locale/ja'); break; + case 'kk@Cyrl': + lang = 'kk'; + require('dayjs/locale/kk'); + break; + case 'kn': + require('dayjs/locale/kn'); + break; case 'ko_kr': lang = 'ko'; require('dayjs/locale/ko'); break; - case 'kn': - require('dayjs/locale/kn'); + case 'lrc': + lang = 'fa'; + require('dayjs/locale/fa'); break; case 'ms': require('dayjs/locale/ms'); break; - case 'ne': - require('dayjs/locale/ne'); - break; case 'nb_no': require('dayjs/locale/nb'); break; + case 'ne': + require('dayjs/locale/ne'); + break; case 'nl_nl': require('dayjs/locale/nl'); break; + case 'pcm': + // Nigerian Pidgin - using English as closest match (pcm is English-based creole) + lang = 'en'; + require('dayjs/locale/en'); + break; + case 'pl': + require('dayjs/locale/pl'); + break; case 'pt_br': lang = 'pt-br'; require('dayjs/locale/pt-br'); @@ -111,9 +225,6 @@ const setDateTimeLocale = async () => { lang = 'pt'; require('dayjs/locale/pt'); break; - case 'pl': - require('dayjs/locale/pl'); - break; case 'ro': require('dayjs/locale/ro'); break; @@ -121,11 +232,15 @@ const setDateTimeLocale = async () => { require('dayjs/locale/ru'); break; case 'si_lk': - require('dayjs/locale/si.js'); + require('dayjs/locale/si'); break; case 'sk_sk': require('dayjs/locale/sk'); break; + case 'sq_AL': + lang = 'sq'; + require('dayjs/locale/sq'); + break; case 'sl_si': require('dayjs/locale/sl'); break; @@ -142,9 +257,20 @@ const setDateTimeLocale = async () => { case 'tr_tr': require('dayjs/locale/tr'); break; + case 'ua': + require('dayjs/locale/uk'); + break; case 'vi_vn': require('dayjs/locale/vi'); break; + case 'zar_afr': + require('dayjs/locale/af'); + break; + case 'zar_xho': + // Xhosa - no dayjs locale available, using English as closest match + lang = 'en'; + require('dayjs/locale/en'); + break; case 'zh_cn': lang = 'zh-cn'; require('dayjs/locale/zh-cn'); @@ -164,92 +290,26 @@ const setDateTimeLocale = async () => { } }; +// Fire-and-forget; `loc` starts as `{en}` until this resolves, so synchronous reads on a cold launch with non-en saved preference render English briefly. const init = async () => { - // finding out whether lang preference was saved const lang = await AsyncStorage.getItem(STORAGE_KEY); if (lang) { await saveLanguage(lang); - await loc.setLanguage(lang); - if (process.env.JEST_WORKER_ID === undefined) { - const foundLang = AvailableLanguages.find(language => language.value === lang); - I18nManager.allowRTL(foundLang?.isRTL ?? false); - I18nManager.forceRTL(foundLang?.isRTL ?? false); - } - await setDateTimeLocale(); } else { const locales = RNLocalize.getLocales(); - if (Object.values(AvailableLanguages).some(language => language.value === locales[0].languageCode)) { - await saveLanguage(locales[0].languageCode); - await loc.setLanguage(locales[0].languageCode); - if (process.env.JEST_WORKER_ID === undefined) { - I18nManager.allowRTL(locales[0].isRTL ?? false); - I18nManager.forceRTL(locales[0].isRTL ?? false); - } + const detected = locales[0]?.languageCode; + if (detected && AvailableLanguages.some(language => language.value === detected)) { + await saveLanguage(detected); } else { await saveLanguage('en'); - await loc.setLanguage('en'); - if (process.env.JEST_WORKER_ID === undefined) { - I18nManager.allowRTL(false); - I18nManager.forceRTL(false); - } } - await setDateTimeLocale(); } }; init(); -const loc = new Localization({ - en: require('./en.json'), - ar: require('./ar.json'), - be: require('./be@tarask.json'), - bg_bg: require('./bg_bg.json'), - ca: require('./ca.json'), - cy: require('./cy.json'), - cs_cz: require('./cs_cz.json'), - da_dk: require('./da_dk.json'), - de_de: require('./de_de.json'), - el: require('./el.json'), - es: require('./es.json'), - es_419: require('./es_419.json'), - et: require('./et_EE.json'), - fa: require('./fa.json'), - fi_fi: require('./fi_fi.json'), - fr_fr: require('./fr_fr.json'), - he: require('./he.json'), - hr_hr: require('./hr_hr.json'), - hu_hu: require('./hu_hu.json'), - id_id: require('./id_id.json'), - it: require('./it.json'), - jp_jp: require('./jp_jp.json'), - ko_kr: require('./ko_KR.json'), - ms: require('./ms.json'), - kn: require('./kn.json'), - ne: require('./ne.json'), - nb_no: require('./nb_no.json'), - nl_nl: require('./nl_nl.json'), - pt_br: require('./pt_br.json'), - pt_pt: require('./pt_pt.json'), - pl: require('./pl.json'), - ro: require('./ro.json'), - ru: require('./ru.json'), - si_lk: require('./si_LK.json'), - sk_sk: require('./sk_sk.json'), - sl_si: require('./sl_SI.json'), - sr_rs: require('./sr_RS.json'), - sv_se: require('./sv_se.json'), - th_th: require('./th_th.json'), - tr_tr: require('./tr_tr.json'), - ua: require('./ua.json'), - vi_vn: require('./vi_vn.json'), - zar_afr: require('./zar_afr.json'), - zar_xho: require('./zar_xho.json'), - zh_cn: require('./zh_cn.json'), - zh_tw: require('./zh_tw.json'), -}); - export const saveLanguage = async (lang: string) => { await AsyncStorage.setItem(STORAGE_KEY, lang); - loc.setLanguage(lang); + applyLanguage(lang); // even tho it makes no effect changing it in this run, it will on the next run, so we are doign it here: if (process.env.JEST_WORKER_ID === undefined) { const foundLang = AvailableLanguages.find(language => language.value === lang); @@ -259,10 +319,15 @@ export const saveLanguage = async (lang: string) => { await setDateTimeLocale(); }; -export const transactionTimeToReadable = (time: number) => { +export const transactionTimeToReadable = (time: number | string) => { if (time === -1) { return 'unknown'; } + if (+time < 1000000000000) { + // converting timestamp to milliseconds timestamp + // (we dont expect timestamps before September 9, 2001 so this conversion is fine) + time = +time * 1000; + } if (time === 0) { return loc._.never; } @@ -271,12 +336,26 @@ export const transactionTimeToReadable = (time: number) => { ret = dayjs(time).fromNow(); } catch (_) { console.warn('incorrect locale set for dayjs'); - return time; + return String(time); } return ret; }; -export const removeTrailingZeros = (value: number | string) => { +const ONE_DAY_MS = 24 * 60 * 60 * 1000; + +/** Formats a timestamp (milliseconds) for the transaction list. Uses relative time (e.g. "2 hours ago") for the past 24 hours, otherwise absolute date. */ +export const formatTransactionListDate = (timestampMs: number): string => { + const d = dayjs(timestampMs); + const now = dayjs(); + const diff = now.valueOf() - timestampMs; + if (diff >= 0 && diff < ONE_DAY_MS) { + return d.fromNow(); + } + const format = d.year() === now.year() ? 'MMM D, h:mm a' : 'MMM D, YYYY h:mm a'; + return d.format(format); +}; + +export const removeTrailingZeros = (value: number | string): string => { let ret = value.toString(); if (ret.indexOf('.') === -1) { @@ -295,17 +374,18 @@ export const removeTrailingZeros = (value: number | string) => { * @param withFormatting {boolean} Works only with `BitcoinUnit.SATS`, makes spaces wetween groups of 000 * @returns {string} */ -export function formatBalance(balance: number, toUnit: string, withFormatting = false) { +export function formatBalance(balance: number, toUnit: string, withFormatting = false): string { if (toUnit === undefined) { return balance + ' ' + loc.units[BitcoinUnit.BTC]; } if (toUnit === BitcoinUnit.BTC) { const value = new BigNumber(balance).dividedBy(100000000).toFixed(8); - return removeTrailingZeros(+value) + ' ' + loc.units[BitcoinUnit.BTC]; + return removeTrailingZeros(value) + ' ' + loc.units[BitcoinUnit.BTC]; } else if (toUnit === BitcoinUnit.SATS) { return (withFormatting ? new Intl.NumberFormat().format(balance).toString() : String(balance)) + ' ' + loc.units[BitcoinUnit.SATS]; - } else if (toUnit === BitcoinUnit.LOCAL_CURRENCY) { - return currency.satoshiToLocalCurrency(balance); + } else { + console.debug('[UnitSwitch/Fiat] formatBalance to fiat', { balance, unit: toUnit, withFormatting }); + return satoshiToLocalCurrency(balance); } } @@ -316,21 +396,19 @@ export function formatBalance(balance: number, toUnit: string, withFormatting = * @param withFormatting {boolean} Works only with `BitcoinUnit.SATS`, makes spaces wetween groups of 000 * @returns {string} */ -export function formatBalanceWithoutSuffix(balance = 0, toUnit: string, withFormatting = false) { +export function formatBalanceWithoutSuffix(balance = 0, toUnit: string, withFormatting = false): string | number { if (toUnit === undefined) { return balance; } - if (balance !== 0) { - if (toUnit === BitcoinUnit.BTC) { - const value = new BigNumber(balance).dividedBy(100000000).toFixed(8); - return removeTrailingZeros(value); - } else if (toUnit === BitcoinUnit.SATS) { - return withFormatting ? new Intl.NumberFormat().format(balance).toString() : String(balance); - } else if (toUnit === BitcoinUnit.LOCAL_CURRENCY) { - return currency.satoshiToLocalCurrency(balance); - } + if (toUnit === BitcoinUnit.BTC) { + const value = new BigNumber(balance).dividedBy(100000000).toFixed(8); + return removeTrailingZeros(value); + } else if (toUnit === BitcoinUnit.SATS) { + return withFormatting ? new Intl.NumberFormat().format(balance).toString() : String(balance); + } else { + console.debug('[UnitSwitch/Fiat] formatBalanceWithoutSuffix to fiat', { balance, unit: toUnit, withFormatting }); + return satoshiToLocalCurrency(balance); } - return balance.toString(); } /** @@ -342,9 +420,10 @@ export function formatBalanceWithoutSuffix(balance = 0, toUnit: string, withForm * @returns {string} */ export function formatBalancePlain(balance = 0, toUnit: string, withFormatting = false) { + console.debug('[UnitSwitch/Fiat] formatBalancePlain', { balance, unit: toUnit, withFormatting }); const newInputValue = formatBalanceWithoutSuffix(balance, toUnit, withFormatting); // eslint-disable-next-line @typescript-eslint/no-use-before-define - return _leaveNumbersAndDots(newInputValue); + return _leaveNumbersAndDots(newInputValue.toString()); } export function _leaveNumbersAndDots(newInputValue: string) { diff --git a/loc/it.json b/loc/it.json index 646e7a164ff..55a9fc5600d 100644 --- a/loc/it.json +++ b/loc/it.json @@ -4,32 +4,32 @@ "cancel": "Annulla", "continue": "Continua", "clipboard": "Appunti", + "discard_changes": "Annullare i cambiamenti?", + "discard_changes_explain": "Ci sono delle modifiche che non sono state salvate. Sei sicuro di voler eliminarle e abbandonare la schermata?", "enter_password": "Inserisci password", "never": "Mai", - "disabled": "Disabilitato", + "no": "No", "of": "{number} su {total}", "ok": "OK", + "enter_url": "Inserisci l'URL", "storage_is_encrypted": "Il tuo archivio è criptato. È necessaria una password per decriptarlo.", "yes": "Sì", - "no": "No", "save": "Salva", "seed": "Seed", "success": "Operazione avvenuta con successo", "wallet_key": "Chiave del portafoglio", - "invalid_animated_qr_code_fragment": "Frammento di codice QR animato non valido. Riprova.", - "file_saved": "Il file {filepath} è stato salvato in {destination}.", - "downloads_folder": "Cartella di download", "close": "Chiudi", "change_input_currency": "Cambia la valuta di input", "refresh": "Aggiorna", - "more": "Altro", - "pick_image": "Scegli immagine dalla libreria", - "pick_file": "Scegli un file", + "pick_image": "Scegli dalla lista", + "pick_file": "Scegli il file", "enter_amount": "Inserisci l'importo", - "qr_custom_input_button": "Tocca 10 volte per inserire un input personalizzato" - }, - "alert": { - "default": "Avviso" + "qr_custom_input_button": "Tocca 10 volte per inserire un input personalizzato", + "unlock": "Sblocca", + "port": "Porta", + "ssl_port": "Porta SSL", + "copied": "Copiato!", + "suggested": "Suggerito" }, "azteco": { "codeIs": "Il codice del tuo voucher è", @@ -38,12 +38,14 @@ "redeem": "Riscatta nel portafoglio", "redeemButton": "Riscattare", "success": "Fatto", - "title": "Riscatta un voucher Atze.co" + "title": "Riscatta un voucher Atze.co", + "successMessage": "Voucher riscattato con successo! I tuoi fondi dovrebbero arrivare a breve nel tuo portafoglio Bitcoin." }, "entropy": { "save": "Salva", "title": "Entropia", - "undo": "Annulla" + "undo": "Annulla", + "amountOfEntropy": "{bits} di {limit} bit" }, "errors": { "broadcast": "Trasmissione fallita", @@ -51,69 +53,48 @@ "network": "Errore di rete" }, "lnd": { - "active": "Attivo", - "inactive": "Inattivo", - "channels": "Canali", - "no_channels": "Nessun canale", - "claim_balance": "Richiedi il saldo {balance}", - "close_channel": "Chiudi il canale", - "new_channel": "Nuovo canale", "errorInvoiceExpired": "Fattura scaduta", - "force_close_channel": "Forza la chiusura del canale?", "expired": "Scaduto", - "node_alias": "Alias del nodo", "expiresIn": "Scade tra {time} minuti", "payButton": "Paga", + "payment": "Pagamento", "placeholder": "Fattura o indirizzo", - "open_channel": "Apri canale", - "funding_amount_placeholder": "Quantità di fondi, ad esempio 0.001", - "opening_channnel_for_from": "Apri canale per il wallet {forWalletLabel}, caricando fondi da {fromWalletLabel}", - "are_you_sure_open_channel": "Sei sicuro di voler aprie questo canale?", "potentialFee": "Commissioni potenziali: {fee}", - "remote_host": "Host remoto", "refill": "Ricarica", - "reconnect_peer": "Riconnetti peer", "refill_create": "Per continuare, crea un portafoglio Bitcoin dal quale ricaricare.", - "refill_external": "Ricarica con un wallet esterno", + "refill_external": "Ricarica con un portafoglio esterno", "refill_lnd_balance": "Ricarica saldo del portafoglio Lightning", - "sameWalletAsInvoiceError": "Non puoi pagare una fattura con lo stesso portafoglio che e' stato usato per crearla.", - "title": "Gestisci fondi", - "can_send": "Puoi inviare", - "can_receive": "Puoi ricevere", - "view_logs": "Visualizza i log" + "sameWalletAsInvoiceError": "Non puoi pagare una fattura con lo stesso portafoglio utilizzato per crearla.", + "title": "Gestisci fondi" }, "lndViewInvoice": { "additional_info": "Ulteriori Informazioni", "for": "Per:", "lightning_invoice": "Fattura Lightning", - "open_direct_channel": "Apri un canale diretto con questo nodo:", "please_pay_between_and": "Paga tra {min} e {max}", "please_pay": "Per favore paga", + "date_time": "Data e Ora", + "wasnt_paid_and_expired": "Questa fattura non è stata pagata ed è scaduta.", "preimage": "Preimage", - "sats": "sats.", - "wasnt_paid_and_expired": "Questa fattura non è stata pagata ed è scaduta." + "sats": "sat" }, "plausibledeniability": { "create_fake_storage": "Crea archivio criptato", - "create_password": "Crea una password", "create_password_explanation": "La password per il finto archivio non deve corrispondere a quella dell'archivio principale", "help": "In alcune circostanze, potresti essere costretto a rivelare una password. Per mantenere i tuoi bitcoin al sicuro, BlueWallet può creare un altro archivio criptato con una password diversa. Se costretto, potresti rivelare questa password alle terze parti. Se inserita in BlueWallet, sbloccherà un archivio \"finto\". Questo sembrerà legittimo alle terze parti, ma terrà segretamente al sicuro il tuo archivio principale con i bitcoin.", "help2": "Il nuovo archivio sarà completamente funzionante, e puoi conservarci piccole quantità così sembrerà più credibile.", "password_should_not_match": "La password è attualmente in uso. Per favore prova con una password diversa.", - "passwords_do_not_match": "Le password non corrispondono. Riprova.", - "retype_password": "Reinserisci la password", - "success": "Fatto", "title": "Negazione Plausibile" }, "pleasebackup": { "ask": "Hai salvato la frase di backup del tuo portafoglio? Questa è necessaria per accedere ai tuoi fondi nel caso in cui tu smarrisca questo dispositivo. Senza la frase di backup, i tuoi fondi saranno definitivamente persi. ", "ask_no": "No", "ask_yes": "Sì", - "ok": "OK, l'ho scritto", - "ok_lnd": "OK, l'ho salvato", + "ok": "Ok, prendo nota", + "ok_lnd": "OK, l'ho registrata.", "text": "Per favore prenditi un momento per scrivere questa frase mnemonica su un foglio di carta.\nÈ il tuo backup e puoi usarlo per ripristinare il portafoglio.", "text_lnd": "Per favore salva questo backup. Ti consente di ripristinare il portafoglio in caso di smarrimento.", - "title": "Il tuo portafoglio è stato creato" + "title": "Il tuo portafoglio è stato creato." }, "receive": { "details_create": "Crea", @@ -124,7 +105,11 @@ "maxSats": "L'importo massimo è {max} sats", "maxSatsFull": "L'importo massimo è {max} sats o {currency}", "minSats": "L'importo minimo è {min} sats", - "minSatsFull": "L'importo minimo è {min} sats o {currency}" + "minSatsFull": "L'importo minimo è {min} sats o {currency}", + "address_not_found": "Impossibile generare l'indirizzo di ricezione.", + "reset": "Reimposta", + "qrcode_for_the_address": "Codice QR per l'indirizzo", + "bip47_explanation": "I codici di pagamento sono un indirizzo universale che evita di rivelare gli indirizzi del tuo portafoglio. Non tutti i servizi li supportano." }, "send": { "provided_address_is_invoice": "Questo indirizzo sembra essere associato ad una fattura Lightning. Apri il tuo portafoglio Lightning per effettuare il pagamento di questa fattura.", @@ -140,7 +125,7 @@ "create_copy": "Copia e trasmetti in seguito", "create_details": "Dettagli", "create_fee": "Commissione", - "create_memo": "Memo", + "create_memo": "Nota", "create_satoshi_per_vbyte": "Satoshi per vByte", "create_this_is_hex": "Questo è l'hex della tua transazione—firmata e pronta ad essere trasmessa alla rete.", "create_to": "A", @@ -149,7 +134,7 @@ "details_add_rec_add": "Aggiungi Destinatario", "details_add_rec_rem": "Rimuovi Destinatario", "details_address": "Indirizzo", - "details_address_field_is_not_valid": "L'iIndirizzo non è valido.", + "details_address_field_is_not_valid": "L'indirizzo non è valido.", "details_adv_fee_bump": "Permetti l'aumento della commissione", "details_adv_full": "Utilizza l'intero ammontare", "details_adv_full_sure": "Sei sicuro di voler utilizzare l'intero ammontare del tuo portafoglio per questa transazione?", @@ -161,9 +146,7 @@ "details_create": "Crea Fattura", "details_error_decode": "Impossibile decodificare l'indirizzo Bitcoin", "details_fee_field_is_not_valid": "La commissione non è valida.", - "details_frozen": "{amount} BTC sono congelati", "details_next": "Avanti", - "details_no_signed_tx": "Il file selezionato non contiene una transazione che può essere importata.", "details_note_placeholder": "Nota", "details_scan": "Scansiona", "details_scan_hint": "Tocca due volte per scannerizzare o importare una destinazione", @@ -175,7 +158,7 @@ "dynamic_next": "Prossimo", "dynamic_prev": "Precedente", "dynamic_start": "Inizia", - "dynamic_stop": "Stop", + "dynamic_stop": "Ferma", "fee_10m": "10m", "fee_1d": "1g", "fee_3h": "3h", @@ -193,23 +176,37 @@ "permission_camera_message": "Abbiamo bisogno della tua autorizzazione per utilizzare la fotocamera.", "psbt_sign": "Firma una transazione", "open_settings": "Apri le impostazioni", - "permission_storage_later": "Chiedimelo dopo", - "permission_storage_message": "BlueWallet ha bisogno della tua autorizzazione per accedere allo spazio di archiviazione e salvare questo file.", "permission_storage_denied_message": "BlueWallet non è in grado di salvare questo file. Per favore apri le impostazioni del tuo dispositivo e abilita i Permessi di Archiviazione.", "permission_storage_title": "Permessi di accesso allo spazio di archiviazione", "psbt_clipboard": "Copia negli appunti", "psbt_this_is_psbt": "Questa è una Transazione Bitcoin Parzialmente Firmata (PSBT). Per favore completa la firma con il tuo portafoglio hardware.", "psbt_tx_export": "Esporta in un file", "no_tx_signing_in_progress": "Non c'è nessuna firma di transazione in corso.", - "outdated_rate": "La tariffa è stata aggiornata il: {data}", + "outdated_rate": "La tariffa è stata aggiornata il: {date}", "psbt_tx_open": "Apri una transazione firmata", "psbt_tx_scan": "Scansiona una transazione firmata", - "qr_error_no_qrcode": "Non siamo stati in grado di trovare un codice QR nell'immagine selezionata. Assicurati che l'immagine contenga solo un codice QR e nessun altro contenuto aggiuntivo, tipo testo o pulsanti.", "reset_amount": "Resetta l'importo", "reset_amount_confirm": "Vuoi resettare l'importo?", "success_done": "Fatto", - "txSaved": "Il file transazione ({filePath}) è stato salvato nella tua cartella Download.", - "problem_with_psbt": "Problema con la PSBT" + "problem_with_psbt": "Problema con la PSBT", + "details_insert_contact": "Inserisci contatto", + "details_add_recc_rem_all_alert_description": "Sei sicuro di voler rimuovere tutti i destinatari?", + "details_add_rec_rem_all": "Rimuovi tutti i destinatari", + "details_recipients_title": "Destinatari", + "details_recipient_title": "Destinatario #{number} di #{total}", + "please_complete_recipient_title": "Destinatario incompleto", + "please_complete_recipient_details": "Per favore completa i dettagli del destinatario #{number} prima di aggiungerne uno nuovo.", + "details_frozen": "{amount} BTC sono congelati.", + "details_no_signed_tx": "Il file selezionato non contiene una transazione che possa essere importata.", + "details_scan_error": "Errore di scansione", + "insert_custom_fee": "Inserisci commissione", + "invalid_psbt": "PSBT fornita non valida.", + "qr_error_no_qrcode": "Non è stato possibile trovare un codice QR valido nell'immagine selezionata. Assicurati che l'immagine contenga solo un codice QR e nessun altro contenuto come testo o pulsanti.", + "txSaved": "Il file della transazione ({filePath}) è stato salvato.", + "file_saved_at_path": "Il file ({filePath}) è stato salvato.", + "cant_send_to_silentpayment_adress": "Questo portafoglio non può inviare a indirizzi SilentPayment", + "cant_send_to_bip47": "Questo portafoglio non può inviare a codici di pagamento BIP47", + "cant_find_bip47_notification": "Aggiungi prima questo codice di pagamento ai contatti" }, "settings": { "about": "Informazioni", @@ -225,59 +222,34 @@ "about_selftest_electrum_disabled": "Il self-test non è disponibile con la Modalità Offline di Electrum. Per favore disattiva la modalità offline e riprova.", "about_selftest_ok": "Tutti i test interni sono stati superati con successo. Il portafoglio funziona correttamente.", "about_sm_github": "GitHub", - "about_sm_discord": "Server Discord", "about_sm_telegram": "Canale Telegram", - "about_sm_twitter": "Seguici su Twitter", - "advanced_options": "Opzioni avanzate", "biometrics": "Dati biometrici", "biom_10times": "Hai cercato di inserire la tua password 10 volte. Vuoi resettare il tuo archivio? Questo rimuoverà tutti i portafogli e decripterà lo spazio di archiviazione.", "biom_conf_identity": "Per favore conferma la tua identità.", - "biom_no_passcode": "Il tuo dispositivo non ha un codice di accesso. Per procedere, si prega di configurare un codice di accesso nell'app Impostazioni.", "biom_remove_decrypt": "Tutti i tuoi portafogli saranno rimossi e lo spazio di archiviazione sarà decriptato. Sei sicuro di voler procedere?", "currency": "Valuta", - "currency_source": "Il prezzo è ottenuto da", "currency_fetch_error": "C'è stato un errore nel tentativo di ottenere il tasso di cambio per la valuta selezionata.", - "default_desc": "Quando disabilitato, BlueWallet aprirà immediatamente il portafoglio selezionato all'avvio.", - "default_info": "Informazioni predefinite", "default_title": "All'avvio", - "default_wallets": "Vedi tutti i portafogli", "electrum_connected": "Connesso", "electrum_connected_not": "Non connesso", - "electrum_error_connect": "Impossibile connettersi al server Electrum specificato", "lndhub_uri": "Per esempio, {example}", "electrum_host": "Per esempio, {example}", "electrum_offline_mode": "Modalità offline", - "electrum_offline_description": "Quando attivo, i tuoi wallet Bitcoin non tenteranno di reperire i saldi disponibili o le transazioni.", + "electrum_offline_description": "Quando attivo, i tuoi portafogli Bitcoin non tenteranno di reperire i saldi disponibili o le transazioni.", "electrum_port": "Porta, solitamente {example}", "use_ssl": "Usa SSL", "electrum_saved": "Le tue modifiche sono state salvate con successo. Può essere necessario riavviare BlueWallet affinché le modifiche abbiano effetto.", "set_electrum_server_as_default": "Impostare {server} come server Electrum predefinito?", - "set_lndhub_as_default": "Impostare {url} come server LNDHub predefinito?", "electrum_settings_server": "Server Electrum", - "electrum_settings_explain": "Lascia vuoto per usare il valore predefinito.", "electrum_status": "Stato", - "electrum_clear_alert_title": "Cancella la cronologia?", - "electrum_clear_alert_message": "Desideri eliminare la cronologia dei server Electrum?", - "electrum_clear_alert_cancel": "Annulla", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Seleziona", - "electrum_reset": "Ripristino delle impostazioni predefinite", "electrum_unable_to_connect": "Impossibile collegarsi a {server}.", - "electrum_history": "Cronologia dei server", - "electrum_reset_to_default": "Desideri veramente ripristinare le impostazioni Electrum predefinite?", - "electrum_clear": "Cancella", - "tor_supported": "Tor supportato", - "tor_unsupported": "Le connessioni Tor non sono supportate.", + "electrum_reset": "Ripristino delle impostazioni predefinite", "encrypt_decrypt": "Decripta l'archivio", "encrypt_decrypt_q": "Desideri veramente decriptare il tuo archivio? Quest'azione avrà come conseguenza di permettere l'accesso ai portafogli senza una password.", - "encrypt_enc_and_pass": "Criptato e protetto da password", "encrypt_title": "Sicurezza", "encrypt_tstorage": "Archivio", "encrypt_use": "Usa {type}", - "encrypt_use_expl": "{type} verrà usato per confermare la tua identità prima di effettuare una transazione, sbloccare, esportare, o eliminare un wallet. {type} non sarà usato per sbloccare archivi cifrati.", "general": "Generali", - "general_adv_mode": "Enable advanced mode", - "general_adv_mode_e": "Una volta abilitato, vedrai opzioni avanzate come diversi tipi di portafoglio, la possibilità di specificare l'istanza di LNDHub a cui vuoi connetterti, e l'entropia personalizzata durante la creazione del portafoglio.", "general_continuity": "Continuity", "general_continuity_e": "Una volta abilitato, sarai in grado di visualizzare i portafogli selezionati e le transazioni, utilizzando i tuoi altri dispositivi collegati ad Apple iCloud.", "groundcontrol_explanation": "GroundControl è un server di notifiche push gratuito e open-source per i portafogli Bitcoin. Puoi installare il tuo server GroundControl e mettere il suo URL qui per non contare sull'infrastruttura di BlueWallet. Lascia il campo vuoto per usare il server predefinito di GroundControl.", @@ -285,75 +257,102 @@ "language": "Lingua", "last_updated": "Ultimo aggiornamento", "language_isRTL": "Il riavvio di BlueWallet è necessario affinché l'orientamento della lingua abbia effetto.", - "lightning_error_lndhub_uri": "LNDHub URI non valido", "lightning_saved": "I cambiamenti sono stati registrati con successo.", "lightning_settings": "Impostazioni Lightning", - "tor_settings": "Impostazioni Tor", - "lightning_settings_explain": "Per connetterti al tuo nodo LND, installa LNDHub e inserisci l'URL qui nelle impostazioni. Tieni presente che solo i portafogli che sono stati creati dopo aver salvato le modifiche saranno connessi all'LNDHub specificato.", "network": "Rete", "network_broadcast": "Trasmetti transazione", "network_electrum": "Server Electrum", "not_a_valid_uri": "URI non valido", "notifications": "Notifiche", - "open_link_in_explorer": "Apri il collegamento nel navigatore", + "open_link_in_explorer": "Apri il collegamento nel block explorer", "password": "Password", - "password_explain": "Crea la password che userai per decriptare l'archivio", - "passwords_do_not_match": "Le password non corrispondono", "plausible_deniability": "Negazione Plausibile", "privacy": "Privacy", "privacy_read_clipboard": "Leggi gli appunti", "privacy_system_settings": "Impostazioni di sistema", "privacy_quickactions": "Scorciatoie Wallet", - "privacy_quickactions_explanation": "Tocca e mantieni premuto l'icona dell'app BlueWallet sulla tua Schermata Home per visualizzare rapidamente il saldo del tuo wallet.", "privacy_clipboard_explanation": "Fornisci scorciatoie se viene rilevato un indirizzo o una ricevuta nella tua clipboard.", "privacy_do_not_track": "Disattiva Analytics", - "privacy_do_not_track_explanation": "Le informazioni circa la performance e l'affidabilità non saranno inviati per l'analisi.", - "push_notifications": "Notifiche Push", + "privacy_do_not_track_explanation": "Le informazioni sulle prestazioni e l'affidabilità non saranno inviate per l'analisi.", "rate": "Tariffa", - "retype_password": "Reinserisci password", "selfTest": "Auto-Test", "save": "Salva", "saved": "Salvato", - "success_transaction_broadcasted": "Successo! La transazione è stata trasmessa!", "total_balance": "Saldo totale", - "total_balance_explanation": "Mostra il saldo totale del tutti i tuoi portafogli in una widget sulla tua schermata home.", - "widgets": "Widgets", - "tools": "Strumenti" + "total_balance_explanation": "Mostra il saldo totale di tutti i tuoi portafogli in un widget sulla schermata home.", + "tools": "Strumenti", + "block_explorer_invalid_custom_url": "L'URL fornito non è valido. Per favore inserisci un URL valido che inizi con http:// o https://.", + "privacy_temporary_screenshots": "Consenti acquisizione schermo", + "privacy_temporary_screenshots_instructions": "La protezione contro l'acquisizione dello schermo sarà disattivata temporaneamente, abilitando screenshot e registrazioni dello schermo. La protezione si riattiverà automaticamente quando chiuderai e riaprirai BlueWallet.", + "biometrics_no_longer_available": "Le impostazioni del tuo dispositivo sono cambiate e non corrispondono più alle impostazioni di sicurezza selezionate nell'app. Per favore riabilita i dati biometrici o il codice di accesso, quindi riavvia l'app per applicare queste modifiche.", + "biom_no_passcode": "Il tuo dispositivo non ha un codice di accesso o i dati biometrici abilitati. Per procedere, configura un codice di accesso o un dato biometrico nell'app Impostazioni.", + "currency_source": "Tasso ottenuto da", + "donate": "Dona", + "donate_description": "Aiutaci a mantenere Blue gratuito!", + "electrum_error_connect": "Impossibile connettersi al server Electrum fornito", + "electrum_error_connect_tor": "Impossibile connettersi al server Electrum fornito. Per favore assicurati che l'app Orbot sia connessa e riprova.", + "set_lndhub_as_default": "Impostare {url} come server LNDhub predefinito?", + "electrum_preferred_server": "Server preferito", + "electrum_preferred_server_description": "Inserisci il server che vuoi che il tuo portafoglio utilizzi per tutte le attività Bitcoin. Una volta impostato, il tuo portafoglio utilizzerà esclusivamente questo server per controllare i saldi, inviare transazioni e recuperare i dati di rete. Assicurati di fidarti di questo server prima di impostarlo.", + "electrum_history": "Cronologia", + "electrum_reset_to_default": "Questo permetterà a BlueWallet di scegliere casualmente un server dalla lista dei server.", + "electrum_reset_to_default_and_clear_history": "Ripristina i valori predefiniti e cancella la cronologia", + "encrypt_enc_and_pass": "Protetto da password", + "encrypt_storage_explanation_headline": "Abilita cifratura dell'archivio", + "encrypt_storage_explanation_description_line1": "Abilitare la cifratura dell'archivio aggiunge un ulteriore livello di protezione alla tua app proteggendo il modo in cui i tuoi dati sono memorizzati sul tuo dispositivo. Questo rende più difficile per chiunque accedere alle tue informazioni senza permesso.", + "encrypt_storage_explanation_description_line2": "Tuttavia, è importante sapere che questa cifratura protegge solo l'accesso ai tuoi portafogli memorizzati nel portachiavi del dispositivo. Non imposta una password o una protezione aggiuntiva sui portafogli stessi.", + "i_understand": "Ho capito", + "block_explorer": "Block Explorer", + "block_explorer_preferred": "Usa il block explorer preferito", + "block_explorer_error_saving_custom": "Errore nel salvataggio del block explorer preferito", + "set_as_preferred": "Imposta come preferito", + "set_as_preferred_electrum": "Impostare {host}:{port} come server preferito disabiliterà la connessione casuale a un server suggerito.", + "encrypted_feature_disabled": "Questa funzione non può essere usata con l'archivio cifrato abilitato.", + "encrypt_use_expl": "{type} sarà utilizzato per confermare la tua identità prima di effettuare una transazione, sbloccare, esportare o eliminare un portafoglio.", + "biometrics_fail": "Se {type} non è abilitato, o non riesce a sbloccare, puoi usare il codice di accesso del dispositivo come alternativa.", + "license": "Licenza", + "lightning_error_lndhub_uri": "URI LNDhub non valido", + "lightning_error_lndhub_uri_tor": "URI LNDhub non valido. Per favore assicurati che l'app Orbot sia connessa e riprova.", + "lightning_settings_explain": "Per connetterti al tuo nodo LND, per favore installa LNDhub e inserisci qui il suo URL nelle impostazioni. Nota che solo i portafogli creati dopo aver salvato le modifiche si connetteranno al LNDhub specificato.", + "lndhub_github": "Repository GitHub", + "electrum_suggested_description": "Quando un server preferito non è impostato, un server suggerito verrà selezionato casualmente per l'utilizzo.", + "password_explain": "Inserisci la password che utilizzerai per sbloccare il tuo archivio.", + "privacy_quickactions_explanation": "Tocca e tieni premuta l'icona dell'app BlueWallet per visualizzare rapidamente il saldo del tuo portafoglio.", + "push_notifications_explanation": "Abilitando le notifiche, il token del tuo dispositivo sarà inviato al server, insieme agli indirizzi dei portafogli e agli ID delle transazioni per tutti i portafogli e le transazioni effettuate dopo aver abilitato le notifiche. Il token del dispositivo viene utilizzato per inviare notifiche, e le informazioni del portafoglio ci permettono di avvisarti riguardo a Bitcoin in arrivo o conferme di transazioni.\n\nSolo le informazioni successive all'abilitazione delle notifiche vengono trasmesse—nulla di precedente viene raccolto.\n\nDisattivando le notifiche, tutte queste informazioni saranno rimosse dal server. Inoltre, eliminando un portafoglio dall'app saranno rimosse anche le informazioni associate dal server.", + "success_transaction_broadcasted": "La tua transazione è stata trasmessa con successo!", + "widgets": "Widget" }, "notifications": { "would_you_like_to_receive_notifications": "Desideri ricevere delle notifiche quando ricevi dei pagamenti?", - "no_and_dont_ask": "No, e non chiedermelo più", - "ask_me_later": "Chiedimelo dopo" + "notifications_subtitle": "Pagamenti in arrivo e conferme di transazione", + "no_and_dont_ask": "No, e non chiedermelo più.", + "permission_denied_message": "Hai negato il permesso di inviarti notifiche. Se desideri ricevere notifiche, abilitale nelle impostazioni del tuo dispositivo." }, "transactions": { "cancel_explain": "Rimpiazzeremo questa transazione con una che effettua un pagamento a te stesso ed ha costi di transazione più alti. Questa cancella di fatto la transazione attualmente in corso. Questa procedura è chiamata RBF—Replace by Fee.", "cancel_no": "Questa transazione non può essere sostituita.", "cancel_title": "Cancella questa transazione (RBF)", "confirmations_lowercase": "{confirmations} conferme", - "copy_link": "Copia link", "expand_note": "Espandi Nota", "cpfp_create": "Crea", "cpfp_exp": "Creeremo una nuova transazione che spende la tua transazione non ancora confermata. Le commissioni totali saranno più elevate rispetto alla transazione originale, quindi dovrebbe venir confermata più rapidamente. Questa tecnica si chiama CPFP—Child Pays for Parent.", - "cpfp_no_bump": "Questa transizione non può essere rimpiazzata.", + "cpfp_no_bump": "Questa transazione non può essere rimpiazzata.", "cpfp_title": "Aumenta la commissione (CPFP)", "details_balance_hide": "Nascondi il saldo", "details_balance_show": "Mostra il saldo", - "details_block": "Altezza del blocco", "details_copy": "Copia", - "details_copy_amount": "Copia importo", "details_copy_block_explorer_link": "Copia link del Block Explorer", "details_copy_note": "Copia Nota", "details_copy_txid": "Copia ID di transazione", - "details_from": "Da", + "details_explorer": "explorer", + "details_id": "ID", "details_inputs": "Input", "details_outputs": "Output", "date": "Data", "details_received": "Ricevuto", - "transaction_note_saved": "La transazione è stata registrata con successo.", - "details_show_in_block_explorer": "Mostra sul block explorer", "details_title": "Transazione", "details_to": "A", - "enable_offline_signing": "Questo wallet non sta venendo usato con la firma offline. Desideri attivarla adesso?", + "enable_offline_signing": "Questo portafoglio non è impostato per la firma offline. Desideri attivarla adesso?", "list_conf": "Conferme: {number}", "pending": "Pendente", "pending_with_amount": "In attesa {amt1} ({amt2})", @@ -361,8 +360,9 @@ "eta_10m": "ETA: In ~10 minuti", "eta_3h": "ETA: In ~3 ore", "eta_1d": "ETA: In ~1 giorno", - "view_wallet": "Visualizza {walletLabel}", "list_title": "Transazioni", + "list_title_received": "Ricevuto", + "transaction": "Transazione", "open_url_error": "Non è stato possibile aprire il link con il browser predefinito. Per favore cambia il tuo browser predefinito e riprova.", "rbf_explain": "Rimpiazzeremo questa transazione con una che ha costi di transazione più alti così da poter essere minata più velocemente. Questa procedura è chiamata RBF—Replace by Fee.", "rbf_title": "Aumenta la commissione (RBF)", @@ -370,51 +370,74 @@ "status_cancel": "Annulla transazione", "transactions_count": "Conteggio transazioni", "txid": "ID della transazione", - "updating": "Aggiornamento..." + "updating": "Aggiornamento...", + "transaction_loading_error": "Si è verificato un problema nel caricamento della transazione. Per favore riprova più tardi.", + "transaction_not_available": "Transazione non disponibile", + "details_view_in_browser": "Visualizza nel browser", + "incoming_transaction": "Transazione in entrata", + "outgoing_transaction": "Transazione in uscita", + "expired_transaction": "Transazione scaduta", + "pending_transaction": "Transazione in attesa", + "offchain": "Off-chain", + "onchain": "On-chain", + "list_title_sent": "Inviato", + "watchOnlyWarningTitle": "Avviso di sicurezza", + "watchOnlyWarningDescription": "Fai attenzione ai truffatori che spesso usano portafogli \"sola lettura\" per ingannare gli utenti. Questi portafogli non ti permettono di controllare o inviare fondi; ti permettono solo di visualizzare il saldo.", + "custom_fee_warning_title": "Avviso", + "custom_fee_warning_description": "Le commissioni inferiori a 1 sat/vB sono valide, ma potrebbero non essere propagate a causa delle politiche dei nodi.", + "details_eta_analyzing": "Analisi in corso...", + "details_sent": "Inviato", + "details_section": "Dettagli", + "details_network_fee": "Commissione di rete", + "details_to_address": "A", + "details_note": "Nota", + "details_add_note": "aggiungi", + "details_advanced": "Avanzato", + "details_fee_rate": "Tasso di commissione", + "details_size": "Dimensione", + "details_virtual_size": "Dimensione virtuale", + "details_tx_hex": "Hex della transazione", + "details_inputs_count": "Input ({count})", + "details_outputs_count": "Output ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Portafoglio Bitcoin semplice e potente", "add_create": "Crea", + "total_balance": "Saldo totale", + "add_entropy": "Entropia", "add_entropy_generated": "{gen} byte di entropia generata", "add_entropy_provide": "Fornisci entropia con dei lanci di dadi", "add_entropy_remain": "{gen} byte di entropia generata. I restanti {rem} byte saranno ottenuti da generatore di numeri casuali del sistema operativo.", "add_import_wallet": "Importa Portafoglio", "add_lightning": "Lightning", "add_lightning_explain": "Per invio con transazioni istantanee", - "add_lndhub": "Connetti al tuo LNDHub", - "add_lndhub_error": "L'indirizzo fornito è un nodo LNDHub invalido.", "add_lndhub_placeholder": "L'indirizzo del tuo nodo", - "add_placeholder": "Il mio primo wallet", + "add_placeholder": "Il mio primo portafoglio", "add_title": "Aggiungi Portafoglio", "add_wallet_name": "Nome Portafoglio", "add_wallet_type": "Tipo", - "balance": "Saldo", "clipboard_bitcoin": "Negli appunti è presente un indirizzo Bitcoin. Desideri usarlo per una transazione?", "clipboard_lightning": "Negli appunti è presente una fattura Lightning. Desideri usarla per una transazione?", "details_address": "Indirizzo", "details_advanced": "Avanzato", "details_are_you_sure": "Sei sicuro?", "details_connected_to": "Connesso a", - "details_del_wb_err": "Il saldo fornito non coincide con il saldo di questo wallet. Si prega di riprovare.", - "details_del_wb_q": "Attenzione: questo wallet non è vuoto, non sarà possibile recuperare i fondi senza la seed phrase del wallet. Per evitare la rimozione accidentale, si prega di inserire il saldo del wallet {balance} satoshi.", + "details_del_wb_q": "Attenzione: questo portafoglio non è vuoto, non sarà possibile recuperare i fondi senza la frase seed del portafoglio. Per evitare la rimozione accidentale, inserisci il saldo del portafoglio: {balance} satoshi.", "details_delete": "Elimina", "details_delete_wallet": "Rimuovi portafoglio", "details_derivation_path": "derivation path", - "details_display": "Mostra la lista dei portafogli", "details_export_backup": "Esporta / Backup", "details_export_history": "Esporta la cronologia in un file CSV", "details_master_fingerprint": "Master Fingerprint", "details_multisig_type": "multisig", - "details_no_cancel": "No, annulla", - "details_save": "Salva", "details_show_xpub": "Mostra XPUB del portafoglio", "details_show_addresses": "Mostra indirizzi", "details_title": "Portafoglio", + "wallets": "Portafogli", "details_type": "Tipo", "details_use_with_hardware_wallet": "Usa con portafoglio hardware", - "details_wallet_updated": "Portafoglio aggiornato", - "details_yes_delete": "Si, elimina", + "details_yes_delete": "Sì, elimina", "enter_bip38_password": "Inserisci la password per decriptare", "export_title": "Esporta portafoglio", "import_do_import": "Importa", @@ -433,44 +456,79 @@ "import_discovery_subtitle": "Scegli un portafoglio scoperto", "import_discovery_derivation": "Usa un percorso di derivazione personalizzato", "import_discovery_no_wallets": "Non è stato trovato alcun portafoglio.", - "import_derivation_found": "trovato", - "import_derivation_found_not": "non trovato", - "import_derivation_loading": "caricamento...", - "import_derivation_subtitle": "Inserisci un percorso di derivazione personalizzato e proveremo a scoprire il tuo portafoglio.", "import_derivation_title": "Percorso di derivazione", - "import_derivation_unknown": "sconosciuto", - "import_wrong_path": "Percorso di derivazione errato", "list_create_a_button": "Aggiungi ora", "list_create_a_wallet": "Aggiungi un portafoglio", - "list_create_a_wallet_text": "È gratuito e puoi crearne\nquanti ne vuoi.", "list_empty_txs1": "Le tue transazioni appariranno qui,", "list_empty_txs1_lightning": "Il portafoglio Lightning dovrebbe essere utilizzato per le tue transazioni quotidiane. Le commissioni sono bassissime e la velocità delle transazioni è incredibile.", - "list_empty_txs2": "Inizia con il tuo wallet.", + "list_empty_txs2": "Inizia con il tuo portafoglio.", "list_empty_txs2_lightning": "\nPer iniziare ad usarlo, seleziona Gestisci Fondi e ricarica il tuo saldo.", "list_latest_transaction": "Transazioni recenti", - "list_ln_browser": "Browser LApp", "list_long_choose": "Scegli foto", - "list_long_clipboard": "Copia dagli appunti", + "paste_from_clipboard": "Incolla", "list_long_scan": "Scansiona un codice QR", "list_title": "Portafogli", "list_tryagain": "Riprova", "no_ln_wallet_error": "Prima di pagare una fattura Lightning, devi aggiungere un portafoglio Lightning.", "looks_like_bip38": "Questa sembra essere una chiave privata protetta da password (BIP38).", - "reorder_title": "Riordina Portafogli", - "reorder_instructions": "Tocca e tieni premuto su un portafoglio per trascinarlo da una parte all'altra della lista.", "please_continue_scanning": "Per favore continua ad effettuare la scansione.", "select_no_bitcoin": "Non è disponibile alcun portafoglio Bitcoin.", "select_no_bitcoin_exp": "È necessario un portafoglio Bitcoin per ricaricare i portafogli Lightning. Per favore creane o importane uno.", "select_wallet": "Seleziona Portafoglio", - "xpub_copiedToClipboard": "Copiata negli appunti.", "pull_to_refresh": "Tira verso il basso per aggiornare", - "warning_do_not_disclose": "Attenzione! Non rivelare.", "add_ln_wallet_first": "Devi prima aggiungere un portafoglio Lightning.", "identity_pubkey": "Identity Pubkey", - "xpub_title": "XPUB del Portafoglio" + "xpub_title": "XPUB del Portafoglio", + "add_entropy_reset_title": "Reimposta entropia", + "add_entropy_reset_message": "Cambiare il tipo di portafoglio reimposterà l'entropia corrente. Vuoi procedere?", + "add_entropy_bytes": "{bytes} byte di entropia", + "add_lndhub": "Connetti al tuo LNDhub", + "add_lndhub_error": "L'indirizzo del nodo fornito non è un nodo LNDhub valido.", + "add_wallet_seed_length": "Lunghezza del seed", + "add_wallet_seed_length_12": "12 parole", + "add_wallet_seed_length_24": "24 parole", + "clear_clipboard_on_import": "Cancella appunti dopo l'importazione", + "details_del_wb_err": "L'importo del saldo fornito non corrisponde al saldo di questo portafoglio. Per favore riprova.", + "details_display": "Mostra nella schermata Home", + "swipe_balance_hide": "Nascondi", + "swipe_balance_show": "Mostra", + "drag_to_reorder": "Trascina per riordinare", + "clear_search": "Cancella ricerca", + "learn_more": "Scopri di più", + "import_discovery_offline": "BlueWallet è attualmente in modalità offline. In questa modalità non può verificare l'esistenza del portafoglio, quindi dovrai selezionare quello corretto manualmente", + "import_derivation_found": "Trovato", + "import_derivation_found_not": "Non trovato", + "import_derivation_loading": "Caricamento...", + "import_derivation_subtitle": "Inserisci un percorso di derivazione personalizzato e proveremo a scoprire il tuo portafoglio.", + "import_derivation_unknown": "Sconosciuto", + "import_wrong_path": "Percorso di derivazione errato", + "list_create_a_wallet_text": "È gratis e puoi crearne \nquanti ne desideri.", + "import_file": "Importa file", + "manage_title": "Gestisci portafogli", + "no_results_found": "Nessun risultato trovato.", + "warning_do_not_disclose": "Non condividere mai le informazioni sottostanti", + "scan_import": "Scansiona questo codice QR per importare il tuo portafoglio in un'altra applicazione.", + "write_down_header": "Crea un backup manuale", + "write_down": "Scrivi e conserva in modo sicuro queste parole. Usale per ripristinare il tuo portafoglio in un secondo momento.", + "wallet_type_this": "Questo tipo di portafoglio è {type}.", + "share_number": "Condividi {number}", + "copy_ln_url": "Copia e conserva in modo sicuro questo URL per ripristinare il tuo portafoglio in un secondo momento.", + "copy_ln_public": "Copia e conserva in modo sicuro queste informazioni per ripristinare il tuo portafoglio in un secondo momento.", + "manage_wallets_search_placeholder": "Cerca portafogli, indirizzi, transazioni e note", + "more_info": "Maggiori informazioni", + "details_delete_wallet_error_message": "Si è verificato un problema nel confermare se questo portafoglio è stato rimosso dalle notifiche—questo potrebbe essere dovuto a un problema di rete o a una connessione scarsa. Se continui, potresti ancora ricevere notifiche per le transazioni relative a questo portafoglio, anche dopo che è stato eliminato.", + "details_delete_anyway": "Elimina comunque" + }, + "total_balance_view": { + "title": "Saldo totale", + "display_in_bitcoin": "Mostra in Bitcoin", + "hide": "Nascondi", + "display_in_sats": "Mostra in sats", + "display_in_fiat": "Mostra in {currency}", + "explanation": "Visualizza il saldo totale di tutti i tuoi portafogli nella schermata di panoramica." }, "multisig": { - "multisig_vault": "Cassaforte", + "multisig_vault": "Cassaforte Multisig", "default_label": "Cassaforte Multisig", "multisig_vault_explain": "La miglior sicurezza per grossi importi", "provide_signature": "Fornisci la firma", @@ -507,20 +565,14 @@ "quorum_header": "Quorum", "of": "di", "wallet_type": "Tipologia di portafoglio", - "invalid_mnemonics": "La frase mnemonica non è valida.", - "invalid_cosigner": "Dati del cofirmatario non validi", "not_a_multisignature_xpub": "Questa non è una XPUB appartenente ad un portafoglio multifirma!", - "invalid_cosigner_format": "Cofirmatario non corretto: Questo non è un cofirmatario per il formato {format}.", "create_new_key": "Crea nuovo", "scan_or_open_file": "Scansiona o apri file", "i_have_mnemonics": "Ho un seed per questa chiave.", "type_your_mnemonics": "Inserisci un seed per importare la tua chiave Vault esistente.", - "this_is_cosigners_xpub": "Questa è la XPUB del cofirmatario—pronta per essere importata in un altro portafoglio. È possibile condividerla.", "wallet_key_created": "La tua chiave Vault è stata creata. Prenditi un momento per creare un backup sicuro del tuo seed mnemonico.", "are_you_sure_seed_will_be_lost": "Sei sicuro/a? Il tuo seed mnemonico andrà perduto se non hai un backup.", "forget_this_seed": "Dimentica questo seed e invece usa la XPUB.", - "view_edit_cosigners": "Visualizza/Modifica i cofirmatari", - "this_cosigner_is_already_imported": "Questo cofirmatario è già stato importato.", "export_signed_psbt": "Esporta una PSBT firmata", "input_fp": "Inserisci fingerprint", "input_fp_explain": "Salta per usare il valore predefinito (00000000)", @@ -538,7 +590,20 @@ "ms_help_title4": "Importazione Vault in corso", "ms_help_4": "Per importare un portafoglio multifirma, usa il tuo file di backup e l'opzione Importa. Se hai solo dei seed o delle XPUB, puoi usare il pulsante Importa durante la procedura di creazione delle chiavi Vault.", "ms_help_title5": "Modo avanzato", - "ms_help_5": "Come configurazione predefinita, BlueWallet genererà una Vault 2-di-3. Per creare un quorum differente o cambiare la tipologia di indirizzo, attiva Modalità Avanzata nelle Impostazioni." + "ms_help_5": "Come configurazione predefinita, BlueWallet genererà una Vault 2-di-3. Per creare un quorum differente o cambiare la tipologia di indirizzo, attiva Modalità Avanzata nelle Impostazioni.", + "provide_signature_details": "Usa il tuo dispositivo e portafoglio dove risiede la chiave per firmare questa transazione", + "provide_signature_details_bluewallet": "In BlueWallet, vai al menu della schermata Invia e seleziona ", + "provide_signature_next_steps": "Scansiona o importa la transazione firmata", + "provide_signature_next_steps_details": "Una volta che il tuo portafoglio ha firmato con successo la transazione, scansiona il codice QR fornito o importa il file allegato, quindi verifica tutti i dettagli della transazione prima di trasmetterla.", + "shared_key_detected": "Cofirmatario condiviso", + "shared_key_detected_question": "Un cofirmatario è stato condiviso con te, vuoi importarlo?", + "invalid_mnemonics": "Questa frase mnemonica non sembra essere valida.", + "invalid_cosigner": "Dati cofirmatario non validi", + "invalid_cosigner_format": "Cofirmatario errato: non è un cofirmatario per il formato {format}.", + "this_is_cosigners_xpub": "Questa è la XPUB del cofirmatario—pronta per essere importata in un altro portafoglio. È sicuro condividerla.", + "this_is_cosigners_xpub_airdrop": "Se condividi tramite AirDrop, i destinatari devono trovarsi nella schermata di coordinazione.", + "view_edit_cosigners": "Visualizza/Modifica cofirmatari", + "this_cosigner_is_already_imported": "Questo cofirmatario è già stato importato." }, "is_it_my_address": { "title": "È il mio indirizzo?", @@ -546,20 +611,33 @@ "enter_address": "Inserisci l'indirizzo", "check_address": "Controlla l'indirizzo", "no_wallet_owns_address": "Nessuno dei portafogli disponibili è proprietario dell'indirizzo fornito.", - "view_qrcode": "Visualizza QRCode" + "view_qrcode": "Visualizza codice QR" + }, + "autofill_word": { + "title": "Parola finale del seed", + "enter": "Inserisci la tua frase mnemonica parziale", + "generate_word": "Genera la parola finale", + "error": "L'input non è una frase mnemonica parziale di 11 o 23 parole. Per favore riprova." }, "cc": { + "header": "Coin Control", "change": "Resto", "coins_selected": "Bitcoin selezionati ({number})", "selected_summ": "{value} selezionati", - "empty": "Questo wallet non contiene monete al momento.", "freeze": "Congela", "freezeLabel": "Congela", "freezeLabel_un": "Scongela", - "header": "Coin Control", "use_coin": "Usa bitcoin", "use_coins": "Usa bitcoin", - "tip": "Questa funzione ti consente di vedere, etichettare, congelare o selezionare bitcoin per una migliore gestione del portafoglio. Puoi selezionare più bitcoin toccando i cerchi colorati." + "tip": "Questa funzione ti consente di vedere, etichettare, congelare o selezionare bitcoin per una migliore gestione del portafoglio. Puoi selezionare più bitcoin toccando i cerchi colorati.", + "sort_label": "Etichetta", + "sort_status": "Stato", + "empty": "Questo portafoglio non ha bitcoin al momento.", + "sort_asc": "Crescente", + "sort_desc": "Decrescente", + "sort_height": "Altezza", + "sort_value": "Valore", + "sort_by": "Ordina per" }, "units": { "BTC": "BTC", @@ -581,7 +659,9 @@ "type_change": "Resto", "type_receive": "Ricevi", "type_used": "Usato", - "transactions": "Transazioni" + "transactions": "Transazioni", + "copy_private_key": "Copia chiave privata", + "sensitive_private_key": "Attenzione: le chiavi private sono estremamente sensibili. Continuare?" }, "lnurl_auth": { "register_question_part_1": "Vorresti registrare un account su", @@ -601,9 +681,24 @@ }, "bip47": { "payment_code": "Codice di pagamento", - "payment_codes_list": "Lista dei codici di pagamento", - "who_can_pay_me": "Chi può pagarmi:", "purpose": "Codice (BIP47) riutilizzabile e condivisibile", - "not_found": "Codice di pagamento non trovato" + "not_found": "Codice di pagamento non trovato", + "contacts": "Contatti", + "bip47_explain": "Codice riutilizzabile e condivisibile", + "bip47_explain_subtitle": "BIP47", + "pay_this_contact": "Paga questo contatto", + "rename_contact": "Rinomina contatto", + "copy_payment_code": "Copia codice di pagamento", + "hide_contact": "Nascondi contatto", + "rename": "Rinomina", + "provide_name": "Fornisci un nuovo nome per questo contatto", + "add_contact": "Aggiungi contatto", + "provide_payment_code": "Fornisci codice di pagamento", + "invalid_pc": "Codice di pagamento non valido", + "notification_tx_unconfirmed": "La transazione di notifica non è ancora confermata, per favore attendi", + "failed_create_notif_tx": "Creazione della transazione on-chain fallita", + "onchain_tx_needed": "Transazione on-chain necessaria", + "notif_tx_sent": "Transazione di notifica inviata. Per favore attendi che venga confermata", + "notif_tx": "Transazione di notifica" } } diff --git a/loc/jp_jp.json b/loc/jp_jp.json index 6410c53ff98..415fe01e094 100644 --- a/loc/jp_jp.json +++ b/loc/jp_jp.json @@ -4,32 +4,32 @@ "cancel": "中止", "continue": "続行", "clipboard": "クリップボード", + "copied": "コピーしました!", + "discard_changes": "変更を破棄しますか?", + "discard_changes_explain": "未保存の変更があります。破棄して画面を移動しますか?", "enter_password": "パスワードを入力", "never": "データなし", - "disabled": "無効", "of": "{number} / {total}", "ok": "OK", + "enter_url": "URLを入力", "storage_is_encrypted": "ウォレットは暗号化されています。復号にはパスワードが必要です。", "yes": "はい", "no": "いいえ", - "save": "保存", + "save": "保存...", "seed": "シード", "success": "成功", "wallet_key": "ウォレットキー", - "invalid_animated_qr_code_fragment": "無効なアニメーションQRCodeフラグメントです。再度お試しください。", - "file_saved": "ファイル {filePath} は {destination} に保存されました。", - "downloads_folder": "ダウンロードフォルダ", "close": "閉じる", "change_input_currency": "入金額を変更", "refresh": "更新", - "more": "もっと", - "pick_image": "ライブラリから画像を選択", + "pick_image": "ライブラリから選択", "pick_file": "ファイルを選択", "enter_amount": "額を入力", - "qr_custom_input_button": "10回タップしてカスタム入力" - }, - "alert": { - "default": "注意" + "qr_custom_input_button": "10回タップしてカスタム入力", + "unlock": "ロック解除", + "port": "ポート", + "ssl_port": "SSLポート", + "suggested": "サジェスト" }, "azteco": { "codeIs": "あなたのバウチャーコードは", @@ -38,12 +38,14 @@ "redeem": "ウォレットに交換する", "redeemButton": "交換する", "success": "成功", + "successMessage": "バウチャーの交換に成功しました! まもなくビットコインウォレットに入金されます。", "title": "Azte.co バウチャーを交換" }, "entropy": { "save": "保存", "title": "エントロピー", - "undo": "元に戻す" + "undo": "元に戻す", + "amountOfEntropy": "{limit}ビット中{bits}ビット" }, "errors": { "broadcast": "ブロードキャスト失敗", @@ -51,80 +53,63 @@ "network": "ネットワークエラー" }, "lnd": { - "active": "アクティブ", - "inactive": "非アクティブ", - "channels": "チャネル", - "no_channels": "チャネルなし", - "claim_balance": "残高{balance}を請求", - "close_channel": "チャネルを閉じる", - "new_channel": "新規チャネル", "errorInvoiceExpired": "インボイス失効", - "force_close_channel": "チャネルを強制的に閉じますか?", "expired": "失効", - "node_alias": "ノードエイリアス", "expiresIn": "失効まで{time}分", "payButton": "支払う", + "payment": "支払い", "placeholder": "インボイスまたはアドレス", - "open_channel": "チャネルを開く", - "funding_amount_placeholder": "デポジット額(例:0.001)", - "opening_channnel_for_from": "{fromWalletLabel} の資金を使い、ウォレット {forWalletLabel} のチャネルを開きます", - "are_you_sure_open_channel": "本当にこのチャネルを開きますか?", "potentialFee": "手数料推計: {fee}", - "remote_host": "リモートホスト", "refill": "送金", - "reconnect_peer": "ピアに再接続", "refill_create": "先に進むためには、ビットコインウォレットを作成して補充してください。", "refill_external": "外部ウォレットで補充", "refill_lnd_balance": "Lightning ウォレットへ送金", "sameWalletAsInvoiceError": "インボイスを作成したのと同じウォレットで支払うことはできません。", - "title": "資金の管理", - "can_send": "送金可能", - "can_receive": "受取可能", - "view_logs": "ログを見る" + "title": "資金の管理" }, "lndViewInvoice": { "additional_info": "追加情報", "for": "メモ:", "lightning_invoice": "ライトニングインボイス", - "open_direct_channel": "このノードの直接チャネルを作成:", "please_pay_between_and": "{min}と{max}の間で支払ってください", "please_pay": "取引額", "preimage": "プリイメージ", "sats": "sats", + "date_time": "日時", "wasnt_paid_and_expired": "この請求書は支払いが行われなかったため無効になりました" }, "plausibledeniability": { "create_fake_storage": "ダミーの暗号化ウォレットの作成", - "create_password": "パスワードの作成", "create_password_explanation": "ダミーのウォレットのパスワードはメインのウォレットのパスワードと異なる必要があります。", - "help": "BuleWallet のウォレットの復号に必要なパスワードを第三者に強要される場合、コインを安全に保護するためにメインのウォレットとは異なるパスワードで暗号化されたダミーのウォレットを作成することが可能です。第三者へ異なるパスワードを提供すれば、BlueWallet のダミーの暗号化ウォレットを復号することとなり、メインのウォレットは隠匿されコインは安全に保護されます。", + "help": "BlueWallet のウォレットの復号に必要なパスワードを第三者に強要される場合、コインを安全に保護するためにメインのウォレットとは異なるパスワードで暗号化されたダミーのウォレットを作成することが可能です。第三者へ異なるパスワードを提供すれば、BlueWallet のダミーの暗号化ウォレットを復号することとなり、メインのウォレットは隠匿されコインは安全に保護されます。", "help2": "新規のダミーのウォレットはメインと同様に機能します。少額のコインを入金しておくことでダミーと疑われないようにすることが可能です。", "password_should_not_match": "ダミーのウォレットのパスワードはメインのウォレットのパスワードと異なる必要があります。", - "passwords_do_not_match": "パスワードが一致しません。もう一度試してください。", - "retype_password": "パスワードの再入力", - "success": "成功", "title": "隠匿設定" }, "pleasebackup": { "ask": "ウォレットのバックアップフレーズは書きとめましたか?バックアップフレーズはこのデバイスを紛失した際に資金を失わないために必要になります。書きとめていなかった場合、資金を取り戻すことはできません。", "ask_no": "書きとめていません", "ask_yes": "書きとめました", - "ok": "はい、書き留めました", - "ok_lnd": "はい、保存しました", + "ok": "はい、書きとめました", + "ok_lnd": "はい、書きとめました", "text": "このニーモニック・フレーズを紙に書き留めておいてください。\nウォレットを復元するために使用するバックアップとなります。", "text_lnd": "このLNDHub認証を保存しておいてください。これはあなたのバックアップであり、他のデバイス上でウォレットを復元するために使用できます。", - "title": "ウォレットが作成されました" + "title": "ウォレットを作成しています..." }, "receive": { "details_create": "作成", "details_label": "概要", "details_setAmount": "入金額", - "details_share": "共有", + "details_share": "共有...", + "address_not_found": "受取アドレスを生成できません。", "header": "入金", + "reset": "リセット", "maxSats": "最大額は{max}satsです", "maxSatsFull": "最大額は{max}sats({currency})です", "minSats": "最小額は{min}satsです", - "minSatsFull": "最小額は{min}sats({currency})です" + "minSatsFull": "最小額は{min}sats({currency})です", + "qrcode_for_the_address": "アドレスのQRコード", + "bip47_explanation": "支払いコードは、ウォレットアドレスを開示せずに済む汎用のアドレスです。すべてのサービスがサポートしているわけではありません。" }, "send": { "provided_address_is_invoice": "これはライトニングインボイスのようです。ライトニングウォレットを使って請求書に対する支払いを行ってください。", @@ -146,11 +131,18 @@ "create_to": "送金先", "create_tx_size": "TX サイズ", "create_verify": "coinb.inで検証する", + "details_insert_contact": "連絡先を追加", "details_add_rec_add": "宛先を追加", "details_add_rec_rem": "宛先を削除", + "details_add_recc_rem_all_alert_description": "本当に宛先をすべて削除しますか?", + "details_add_rec_rem_all": "宛先をすべて削除", + "details_recipients_title": "宛先", + "details_recipient_title": "宛先#{number}/#{total}", + "please_complete_recipient_title": "宛先が不完全", + "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": "本当にこのウォレットの全残高をこのトランザクションに利用しますか? フリーズしたコインは除外されるのでご注意ください。", @@ -167,6 +159,7 @@ "details_note_placeholder": "ラベル", "details_scan": "読取り", "details_scan_hint": "ダブルタップして宛先をスキャンまたはインポート", + "details_scan_error": "読み取りエラー", "details_total_exceeds_balance": "送金額が利用可能残額を超えています。", "details_total_exceeds_balance_frozen": "送金額が利用可能残額を超えています。フリーズしたコインは使えないのでご注意ください。", "details_unrecognized_file_format": "認識できないファイルフォーマット", @@ -180,6 +173,7 @@ "fee_1d": "1日", "fee_3h": "3時間", "fee_custom": "カスタム", + "insert_custom_fee": "手数料を入力", "fee_fast": "高速", "fee_medium": "中速", "fee_replace_minvb": "希望する手数料レート(vByteあたりsatoshi)は{min} sat/vByteより高くするのが良いです。", @@ -192,9 +186,8 @@ "input_total": "合計:", "permission_camera_message": "カメラを使用するのに許可が必要です", "psbt_sign": "トランザクションに署名する", + "invalid_psbt": "無効なPSBTが入力されました。", "open_settings": "設定を開く", - "permission_storage_later": "後で聞く", - "permission_storage_message": "BlueWalletがこのファイルを保存するためストレージへのアクセス権を求めています。", "permission_storage_denied_message": "BlueWalletはファイルを保存できませんでした。デバイスの設定を開いてストレージのパーミッションを有効にしてください。", "permission_storage_title": "ストレージアクセス許可", "psbt_clipboard": "クリップボードにコピー", @@ -204,16 +197,20 @@ "outdated_rate": "レートの最終更新:{date}", "psbt_tx_open": "署名トランザクションを開く", "psbt_tx_scan": "署名トランザクションをスキャン", - "qr_error_no_qrcode": "選択した画像からQRコードが見つかりませんでした。画像がQRコードのみを含み、テキストやボタンなど別の内容を含まないようにしてください。", + "qr_error_no_qrcode": "選択した画像から有効なQRコードが見つかりませんでした。画像がQRコードのみを含み、テキストやボタンなど別の内容を含まないようにしてください。", "reset_amount": "額をリセット", "reset_amount_confirm": "額をリセットしますか?", "success_done": "完了", - "txSaved": "トランザクションファイル ({filePath}) はダウンロードフォルダに保存されました。", + "txSaved": "トランザクションファイル ({filePath}) を保存しました。", + "file_saved_at_path": "ファイル({filePath})を保存しました。", + "cant_send_to_silentpayment_adress": "このウォレットからはSilentPaymentアドレスに送金できません", + "cant_send_to_bip47": "このウォレットからはBIP47支払いコードに送金できません", + "cant_find_bip47_notification": "先にこの支払いコードを連絡先に追加してください", "problem_with_psbt": "PSBTに問題" }, "settings": { "about": "BlueWallet について", - "about_awesome": "Built with the awesome", + "about_awesome": "素晴らしいもので構築", "about_backup": "常に秘密鍵はバックアップしましょう!", "about_free": "BlueWalletはフリーでオープンソースのプロジェクトです。ビットコインユーザーによって作られています。", "about_license": "MIT ライセンス", @@ -222,28 +219,29 @@ "performance_score": "パフォーマンススコア:{num}", "run_performance_test": "パフォーマンステスト", "about_selftest": "セルフテストを実行", + "block_explorer_invalid_custom_url": "入力したURLは間違っています。http:// または https:// で始まる正しいURLを入力してください。", "about_selftest_electrum_disabled": "Electrumオフラインモードではセルフテストができません。オフラインモードを無効にしてもう一度やり直してください。", "about_selftest_ok": "全ての内部テストが成功しました。ウォレットは正しく機能しています。", "about_sm_github": "GitHub", - "about_sm_discord": "Discord サーバー", "about_sm_telegram": "テレグラムチャット", - "about_sm_twitter": "Twitterでフォロー", - "advanced_options": "高度な設定", + "privacy_temporary_screenshots": "スクリーンキャプチャを許可", + "privacy_temporary_screenshots_instructions": "スクリーンキャプチャ保護が一時的にオフになり、スクリーンショットと録画が可能です。BlueWalletを閉じて再度開くと保護は自動的に有効になります。", "biometrics": "生体認証", + "biometrics_no_longer_available": "デバイスの設定が変更され、アプリ内で選択したセキュリティ設定に適合しなくなりました。生体認証かパスコードを再度有効にしてから、アプリを再起動して変更を適用してください。", "biom_10times": "パスワードを10回入力しようとしました。ストレージをリセットしますか?これにより全てのウォレットが削除され、ストレージが復号化されます。", "biom_conf_identity": "個人情報を確認して下さい。", - "biom_no_passcode": "お使いの端末にはパスコードが設定されていません。続行するには、「設定」アプリでパスコードを設定してください。", + "biom_no_passcode": "お使いの端末でパスコードまたは生体認証が有効になっていません。続行するには、「設定」アプリでパスコードまたは生体認証を設定してください。", "biom_remove_decrypt": "全てのウォレットは削除され、ストレージは暗号化が解除されます。本当に続行してもいいですか?", "currency": "通貨", - "currency_source": "価格参照元:", + "currency_source": "レート参照元:", "currency_fetch_error": "選択した通貨のレートを得るときにエラーが発生しました。", - "default_desc": "無効にすれば、BlueWalletは起動時に選択したウォレットをすぐに開きます。", - "default_info": "デフォルト情報", "default_title": "起動時", - "default_wallets": "すべてのウォレットを確認", + "donate": "寄付", + "donate_description": "Blueが無料であり続けるためにご協力ください!", "electrum_connected": "接続済", "electrum_connected_not": "未接続", "electrum_error_connect": "指定されたElectrumサーバーに接続できません", + "electrum_error_connect_tor": "入力されたElectrumサーバーに接続できません。Orbotアプリが接続されていることを確認して再度お試しください。", "lndhub_uri": "例:{example}", "electrum_host": "例:{example}", "electrum_offline_mode": "オフラインモード", @@ -254,30 +252,33 @@ "set_electrum_server_as_default": "{server} をデフォルトの Electrum サーバーに設定しますか?", "set_lndhub_as_default": "{url} をデフォルトの LNDHub サーバーに設定しますか?", "electrum_settings_server": "Electrum サーバー", - "electrum_settings_explain": "空欄の場合デフォルトを使用します。", "electrum_status": "ステータス", - "electrum_clear_alert_title": "履歴を削除しますか?", - "electrum_clear_alert_message": "Electrumサーバーヒストリーをクリアしますか?", - "electrum_clear_alert_cancel": "キャンセル", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "選択", - "electrum_reset": "デフォルトの設定に戻す", + "electrum_preferred_server": "優先サーバー", + "electrum_preferred_server_description": "あなたのウォレットのすべてのビットコイン取引に使うサーバーを入力します。設定すると、あなたのウォレットは残高の確認、トランザクションの送信、ネットワークデータの取得にこのサーバーだけを使います。信用するサーバーを設定するようにしてください。", "electrum_unable_to_connect": "{server}に接続できません。", - "electrum_history": "サーバーヒストリー", - "electrum_reset_to_default": "Electrumの設定をデフォルトに戻してよろしいですか?", - "electrum_clear": "クリア", - "tor_supported": "Tor対応", - "tor_unsupported": "Tor接続には対応しません。", + "electrum_history": "履歴", + "electrum_reset_to_default": "サーバーリストからBlueWalletがランダムにサーバーを選択するようになります。", + "electrum_reset": "デフォルトの設定に戻す", + "electrum_reset_to_default_and_clear_history": "デフォルトにリセットして履歴を削除", "encrypt_decrypt": "ストレージ復号化", "encrypt_decrypt_q": "本当にストレージを復号化しますか?これによりウォレットがパスワードなしでアクセス可能になります。", - "encrypt_enc_and_pass": "暗号化しパスワードで保護", + "encrypt_enc_and_pass": "パスワード保護", + "encrypt_storage_explanation_headline": "ストレージ暗号化を有効にする", + "encrypt_storage_explanation_description_line1": "ストレージ暗号化を有効にすると、デバイスへのデータの保存方法を保護することにより、より一層の防護をアプリに追加します。それによって、許可の無い何者かがあなたの情報にアクセスするのがより困難になります。", + "encrypt_storage_explanation_description_line2": "ただし重要なのは、暗号化によって守られるのはデバイスのキーチェーンに保存されたウォレットへのアクセスだけだということです。ウォレット自体にパスワードやその他の保護がされるわけではありません。", + "i_understand": "理解した", + "block_explorer": "ブロックエクスプローラー", + "block_explorer_preferred": "お好みのブロックエクスプローラーを使用", + "block_explorer_error_saving_custom": "お好みのブロックエクスプローラー保存時のエラー", "encrypt_title": "セキュリティ", "encrypt_tstorage": "ストレージ", "encrypt_use": "{type} を使う", - "encrypt_use_expl": "{type} は、トランザクションの実行、ウォレットのロック解除、エクスポート、または削除を行う前の本人確認に使用されます。{type} は暗号化されたストレージのロック解除には使用されません。", + "set_as_preferred": "優先に設定", + "set_as_preferred_electrum": "優先サーバーとして {host}:{port} を設定することで、おすすめサーバーへのランダムな接続が無効になります。", + "encrypted_feature_disabled": "ストレージの暗号化が有効の場合この機能は使えません。", + "encrypt_use_expl": "{type} は、トランザクションの実行、ウォレットのロック解除、エクスポート、または削除を行う前の本人確認に使用されます。", + "biometrics_fail": "もし {type} が有効でない、または解除に失敗した場合、デバイスのパスコードを代わりに使うことができます。", "general": "一般情報", - "general_adv_mode": "上級者モード", - "general_adv_mode_e": "この機能を有効にすると、異なるウォレットタイプ、接続先の LNDHub インスタンスの指定、ウォレット作成時のカスタムエントロピーなどの高度なオプションが表示されます。", "general_continuity": "継続性", "general_continuity_e": "この機能を有効にすると、Apple iCloudに接続している他のデバイスを使用して、選択したウォレットやトランザクションを表示できるようになります。", "groundcontrol_explanation": "GroundControlはビットコインウォレットのための無料のオープンソースのプッシュ通知サーバーです。独自のGroundControlサーバーをインストールし、BlueWalletのインフラに依存しないようにURLをここに入力することができます。デフォルトを使用するには空白のままにしてください。", @@ -285,36 +286,37 @@ "language": "言語", "last_updated": "最終更新", "language_isRTL": "言語の向きを適用するにはBlueWalletの再起動が必要です。", - "lightning_error_lndhub_uri": "無効なLndHub URIです", + "license": "ライセンス", + "lightning_error_lndhub_uri": "無効なLNDhub URIです", + "lightning_error_lndhub_uri_tor": "無効なLNDhub URIです。Orbotアプリが接続されていることを確認して再度お試しください。", "lightning_saved": "変更は正常に保存されました", "lightning_settings": "Lightning 設定", - "tor_settings": "Tor設定", "lightning_settings_explain": "他の LND ノードへ接続するには LNDHub をインストール後、URL を入力してください。指定した LNDHub へ接続するのは変更保存後に作成されたウォレットのみですので注意してください。", + "lndhub_github": "GitHubリポジトリ", "network": "ネットワーク", "network_broadcast": "ブロードキャストトランザクション", "network_electrum": "Electrum サーバー", + "electrum_suggested_description": "優先サーバーが設定されていない場合、ランダムに選ばれたおすすめのサーバーが使われます。", "not_a_valid_uri": "無効なURI", "notifications": "通知", "open_link_in_explorer": "エクスプローラで開く", "password": "パスワード", - "password_explain": "ウォレットの復号に使用するパスワードを作成", - "passwords_do_not_match": "パスワードが一致しません", + "password_explain": "ストレージのアンロックに使うパスワードを入力", "plausible_deniability": "隠匿設定...", "privacy": "プライバシー", "privacy_read_clipboard": "クリップボードを読む", "privacy_system_settings": "システム設定", "privacy_quickactions": "ウォレットショートカット", - "privacy_quickactions_explanation": "ホーム画面でBlueWalletアプリのアイコンをタッチして長押しすると、ウォレットの残高を素早く確認することができます。", + "privacy_quickactions_explanation": "BlueWalletアプリのアイコンをタッチして長押しすると、ウォレットの残高を素早く確認することができます。", "privacy_clipboard_explanation": "アドレスやインボイスがクリップボードにあった場合、ショートカットを提供する。", "privacy_do_not_track": "分析を無効にする", "privacy_do_not_track_explanation": "パフォーマンスと信頼性の情報を分析のために送信しません。", - "push_notifications": "プッシュ通知", "rate": "レート", - "retype_password": "パスワードの再入力", + "push_notifications_explanation": "通知を有効にすると、あなたのデバイストークン、および通知を有効にした後に作られたウォレットアドレスやトランザクションIDがサーバーに送られます。デバイストークンは通知を送るために使われ、ウォレット情報によって受信したビットコインやトランザクションの確認を通知することができるようになります。\n\n送信されるのは通知を有効にした後の情報だけです。それ以前のものは収集されません。\n\n通知を無効にするとサーバーからこれらの情報がすべて削除されます。また、ウォレットをアプリから削除することによっても、紐づけられた情報がサーバーから削除されます。", "selfTest": "セルフテスト", "save": "保存", "saved": "保存済", - "success_transaction_broadcasted": "成功! 取引が配信されました!", + "success_transaction_broadcasted": "トランザクションの配信に成功しました!", "total_balance": "合計残高", "total_balance_explanation": "すべてのウォレットの合計残高をホーム画面のウィジェットに表示", "widgets": "ウィジェット", @@ -322,37 +324,57 @@ }, "notifications": { "would_you_like_to_receive_notifications": "支払いを受けた際に通知を受け取りますか?", + "notifications_subtitle": "受信した支払いやトランザクションの確認", "no_and_dont_ask": "いいえ。もう聞かないでください。", - "ask_me_later": "あとで" + "permission_denied_message": "通知が許可されていません。通知を受け取りたい場合は、デバイスの設定で許可してください。" }, "transactions": { "cancel_explain": "このトランザクションを、あなたに支払いを行う、より高い手数料を持つものに置き換えます。これは事実上、最初のトランザクションをキャンセルします。これはReplace By Fee - RBFと呼ばれています。", "cancel_no": "このトランザクションは交換可能ではありません", "cancel_title": "このトランザクションをキャンセル (RBF)", + "transaction_loading_error": "トランザクションの読み込み時のエラー。後でもう一度試してください。", + "transaction_not_available": "トランザクションがありません", "confirmations_lowercase": "{confirmations} コンファメーション", - "copy_link": "リンクをコピー", "expand_note": "ノートを展開", "cpfp_create": "作成", "cpfp_exp": "あなたの未承認トランザクションを消費する別のトランザクションを作成します。元のトランザクションの手数料よりも合計金額が高くなるため、より早くマイニングされます。これはCPFP - Child Pays For Parentと呼ばれています。", "cpfp_no_bump": "このトランザクションはバンプ可能ではありません", - "cpfp_title": "バンプ費用 (CPFP)", + "cpfp_title": "手数料をバンプ (CPFP)", "details_balance_hide": "残高を隠す", "details_balance_show": "残高を表示", - "details_block": "ブロック高", "details_copy": "コピー", - "details_copy_amount": "額をコピー", "details_copy_block_explorer_link": "ブロックエクスプローラーのリンクをコピー", "details_copy_note": "ノートをコピー", "details_copy_txid": "取引IDをコピー", - "details_from": "送り主", + "details_id": "ID", "details_inputs": "インプット", "details_outputs": "アウトプット", "date": "日付", "details_received": "受取り済", - "transaction_note_saved": "トランザクションノートが正常に保存されました。", - "details_show_in_block_explorer": "Block Explorer で表示", + "details_view_in_browser": "ブラウザで開く", "details_title": "取引", + "incoming_transaction": "受取りトランザクション", + "outgoing_transaction": "支払いトランザクション", + "expired_transaction": "期限切れトランザクション", + "pending_transaction": "保留中トランザクション", + "offchain": "オフチェーン", + "onchain": "オンチェーン", "details_to": "送り先", + "details_to_address": "送り先", + "details_eta_analyzing": "分析中...", + "details_sent": "送信済", + "details_section": "詳細", + "details_explorer": "エクスプローラー", + "details_network_fee": "ネットワーク手数料", + "details_note": "ノート", + "details_add_note": "追加", + "details_advanced": "詳細設定", + "details_fee_rate": "手数料レート", + "details_size": "サイズ", + "details_virtual_size": "仮想サイズ", + "details_tx_hex": "取引Hex", + "details_inputs_count": "インプット ({count})", + "details_outputs_count": "アウトプット ({count})", "enable_offline_signing": "このウォレットではオフライン署名が使われていません。今すぐ有効にしますか?", "list_conf": "コンファメーション: {number}", "pending": "保留中", @@ -361,8 +383,10 @@ "eta_10m": "到着予定:~10分以内", "eta_3h": "到着予定:~3時間以内", "eta_1d": "到着予定:~1日以内", - "view_wallet": "{walletLabel}を見る", "list_title": "取引", + "list_title_sent": "送信済", + "list_title_received": "受取り済", + "transaction": "取引", "open_url_error": "デフォルトブラウザでリンクを開けません。デフォルトブラウザを変更してやり直してください。", "rbf_explain": "このトランザクションを手数料の高いものに置き換えることで、マイニングが早く行われるようにします。これをRBF - Replace By Feeといいます。", "rbf_title": "手数料をバンプ (RBF)", @@ -370,12 +394,21 @@ "status_cancel": "トランザクションをキャンセル", "transactions_count": "トランザクションカウント", "txid": "トランザクションID", - "updating": "更新中…" + "updating": "更新中…", + "watchOnlyWarningTitle": "セキュリティ警告", + "watchOnlyWarningDescription": "「閲覧専用」ウォレットを使ってユーザーを騙す詐欺師に注意してください。このウォレットは資金の管理や送金ができず、残高を見ることだけができます。", + "custom_fee_warning_title": "警告", + "custom_fee_warning_description": "1 sat/vB 未満の手数料は有効ですが、ノードのポリシーによりリレーされない可能性があります。 " }, "wallets": { "add_bitcoin": "ビットコイン", "add_bitcoin_explain": "シンプルかつパワフルなBitcoinウォレット", "add_create": "作成", + "total_balance": "合計残高", + "add_entropy_reset_title": "エントロピーをリセット", + "add_entropy_reset_message": "ウォレットタイプを変更すると現在のエントロピーがリセットされます。よろしいですか?", + "add_entropy": "エントロピー", + "add_entropy_bytes": "{bytes}バイトのエントロピー", "add_entropy_generated": "生成されたエントロピーの {gen} バイト", "add_entropy_provide": "サイコロを振ってエントロピーを提供", "add_entropy_remain": "生成されたエントロピーの{gen}バイト。残りの{rem}バイトはシステム乱数発生器から取得されます。", @@ -389,9 +422,12 @@ "add_title": "ウォレットの追加", "add_wallet_name": "ウォレット名", "add_wallet_type": "タイプ", - "balance": "残高", + "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_address": "アドレス", "details_advanced": "高度な設定", "details_are_you_sure": "実行しますか?", @@ -401,19 +437,17 @@ "details_delete": "削除", "details_delete_wallet": "ウォレット削除", "details_derivation_path": "派生パス(derivation path)", - "details_display": "ウォレットリストで表示", + "details_display": "ホーム画面に表示", "details_export_backup": "エクスポート / バックアップ", "details_export_history": "履歴をCSVにエクスポート", "details_master_fingerprint": "マスタフィンガープリント", "details_multisig_type": "マルチシグ", - "details_no_cancel": "いいえ、中止します", - "details_save": "保存", "details_show_xpub": "ウォレット XPUB の表示", "details_show_addresses": "アドレスを表示", "details_title": "ウォレット", + "wallets": "ウォレット", "details_type": "タイプ", "details_use_with_hardware_wallet": "ハードウェアウォレットで使用", - "details_wallet_updated": "ウォレットアップデート済", "details_yes_delete": "はい、削除します", "enter_bip38_password": "復号化のためパスワードを入力", "export_title": "ウォレットのエクスポート", @@ -429,59 +463,89 @@ "import_success_watchonly": "ウォレットのインポートに成功しました。警告:これは閲覧専用ウォレットで、支払いはできません。", "import_search_accounts": "アカウントを検索", "import_title": "インポート", + "learn_more": "詳細を見る", "import_discovery_title": "発見", "import_discovery_subtitle": "発見したウォレットを選択", "import_discovery_derivation": "カスタム derivation path を使用", "import_discovery_no_wallets": "ウォレットは見つかりませんでした。", + "import_discovery_offline": "BlueWalletは現在オフラインモードです。このモードではウォレットの存在を検証できないため、手動で正しいものを選択する必要があります", "import_derivation_found": "発見", "import_derivation_found_not": "未発見", "import_derivation_loading": "読み込み中...", "import_derivation_subtitle": "カスタム derivation path を入力して、ウォレットの発見を試みます", - "import_derivation_title": "Derivation path", + "import_derivation_title": "導出パス", "import_derivation_unknown": "不明", "import_wrong_path": "正しくない derivation path", "list_create_a_button": "今すぐ追加", "list_create_a_wallet": "ウォレットを追加", - "list_create_a_wallet_text": "無料で好きなだけ作成できます", + "list_create_a_wallet_text": "無料で、\n好きなだけ作成できます。", "list_empty_txs1": "ここに取引が表示されます", "list_empty_txs1_lightning": "Lightning ウォレットを日常の取引にご利用ください。手数料は安く、送金はあっという間に完了します。", "list_empty_txs2": "あなたのウォレットから始めましょう。", "list_empty_txs2_lightning": "\n利用を開始するには\"資金の管理\"をタップしてウォレットへ送金してください。", "list_latest_transaction": "最新の取引", - "list_ln_browser": "Lapp Browser", "list_long_choose": "写真選択", - "list_long_clipboard": "クリップボードからコピー", + "paste_from_clipboard": "ペースト", + "import_file": "インポートファイル", "list_long_scan": "QRコードをスキャン", "list_title": "ウォレット", "list_tryagain": "再度試す", "no_ln_wallet_error": "ライトニングインボイスの支払いを行う前に、ライトニングウォレットを追加する必要があります。", "looks_like_bip38": "パスワード保護された秘密鍵(BIP38)のようです。", - "reorder_title": "ウォレットの並び替え", - "reorder_instructions": "ウォレットをタップ&ホールドして、リスト内でドラッグします。", + "manage_title": "ウォレット管理", + "no_results_found": "結果は見つかりませんでした。", "please_continue_scanning": "スキャンを続けてください。", "select_no_bitcoin": "現在利用可能なビットコインウォレットがありません。", "select_no_bitcoin_exp": "ライトニングウォレットのリフィルにはビットコインウォレットが必要です。作成するか、インポートしてください。", "select_wallet": "ウォレット選択", - "xpub_copiedToClipboard": "クリップボードにコピーしました。", "pull_to_refresh": "引っ張って更新する", - "warning_do_not_disclose": "警告! 公開しないこと。", + "warning_do_not_disclose": "以下の情報を絶対に他人に共有しないでください", + "scan_import": "このQRコードをスキャンして他のアプリにウォレットをインポートします。", + "write_down_header": "手動バックアップ作成", + "write_down": "これらの言葉を書きとめ、安全に保管してください。あとでウォレットを復元するために使います。", + "wallet_type_this": "ウォレットの種類は{type}。", + "share_number": "{number}を共有", + "copy_ln_url": "あとでウォレットを復元するために、このURLをコピーして安全に保管してください。", + "copy_ln_public": "あとでウォレットを復元するために、この情報を安全に保管してください。", "add_ln_wallet_first": "先にライトニングウォレットを追加する必要があります。", "identity_pubkey": "識別用公開鍵", - "xpub_title": "ウォレット XPUB" + "xpub_title": "ウォレット XPUB", + "manage_wallets_search_placeholder": "ウォレット・アドレス・トランザクション・メモを検索", + "more_info": "詳細情報", + "details_delete_wallet_error_message": "ウォレットが通知から削除されたかの確認に問題が生じました—ネットワークの問題か、接続が弱いためかもしれません。続行すると、ウォレットを削除した後でも、関連するトランザクションの通知を受け取る可能性があります。", + "details_delete_anyway": "とにかく削除", + "swipe_balance_hide": "非表示", + "swipe_balance_show": "表示", + "drag_to_reorder": "ドラッグして並び替え", + "clear_search": "検索をクリア" + }, + "total_balance_view": { + "display_in_bitcoin": "ビットコインで表示", + "hide": "非表示", + "display_in_sats": "satsで表示", + "display_in_fiat": "{currency}で表示", + "title": "合計残高", + "explanation": "すべてのウォレットの合計残高を概要画面に表示" }, "multisig": { - "multisig_vault": "金庫", + "multisig_vault": "マルチシグ金庫", "default_label": "マルチシグ金庫", "multisig_vault_explain": "大きな資産にベストなセキュリティ", "provide_signature": "署名を提供", + "provide_signature_details": "このトランザクションに署名するためのキーを持っているデバイスとウォレットを使ってください", + "provide_signature_details_bluewallet": "BlueWalletで、送金スクリーンメニューに移動し", + "provide_signature_next_steps": "署名済みトランザクションをスキャンまたはインポート", + "provide_signature_next_steps_details": "ウォレットを使ったトランザクションの署名が終わったら、QRコードをスキャンまたはファイルをインポートして、トランザクションの詳細をすべて確認してから配信してください。", "vault_key": "金庫キー {number}", "required_keys_out_of_total": "全体のうち必要なキー", - "fee": "費用: {number}", + "fee": "手数料: {number}", "fee_btc": "{number} BTC", "confirm": "承認", "header": "送る", - "share": "共有", + "share": "共有...", "view": "表示", + "shared_key_detected": "共同署名の共有", + "shared_key_detected_question": "共同署名が共有されました。インポートしますか?", "manage_keys": "管理キー", "how_many_signatures_can_bluewallet_make": "Bluewalletが作れる署名の数", "signatures_required_to_spend": "必要な署名 {number}", @@ -501,7 +565,7 @@ "vault_advanced_customize": "金庫設定", "needs": "支払いを行うには", "what_is_vault_description_number_of_vault_keys": "{m} 個の金庫キー", - "what_is_vault_description_to_spend": "が必要で、3つ目はバックアップとして使えます。", + "what_is_vault_description_to_spend": "が必要で、3つ目は\nバックアップとして使えます。", "what_is_vault_description_to_spend_other": "が必要です。", "quorum": "定足数 {n}分の{m}", "quorum_header": "定足数", @@ -516,6 +580,7 @@ "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を代わりに利用する", @@ -548,6 +613,12 @@ "no_wallet_owns_address": "利用可能なウォレットの中にそのアドレスを持つものはありません。", "view_qrcode": "QRコードを見る" }, + "autofill_word": { + "title": "シードの最後のワード", + "enter": "部分的なニーモニックフレーズを入力", + "generate_word": "最後のワードを生成", + "error": "入力されたのは11または23語の部分的なニーモニックではありません!" + }, "cc": { "change": "チェンジ", "coins_selected": "選択済コイン ({number})", @@ -559,7 +630,14 @@ "header": "コイン管理", "use_coin": "コインを利用", "use_coins": "コインを利用", - "tip": "ウォレットの閲覧、ラベル付け、フリーズ、コインの選択ができ、管理が向上する機能です。色付きの丸をタップすると複数のコインを選択できます。" + "tip": "ウォレットの閲覧、ラベル付け、フリーズ、コインの選択ができ、管理が向上する機能です。色付きの丸をタップすると複数のコインを選択できます。", + "sort_asc": "昇順", + "sort_desc": "降順", + "sort_height": "高さ", + "sort_value": "価格", + "sort_label": "ラベル", + "sort_status": "ステータス", + "sort_by": "ソート方法" }, "units": { "BTC": "BTC", @@ -568,6 +646,8 @@ "sats": "sats" }, "addresses": { + "copy_private_key": "秘密鍵をコピー", + "sensitive_private_key": "警告:秘密鍵はとても重要です。続行しますか?", "sign_title": "メッセージの署名/検証", "sign_help": "ここではビットコインアドレスに基づく暗号署名の作成・検証ができます。", "sign_sign": "署名", @@ -601,9 +681,24 @@ }, "bip47": { "payment_code": "支払コード", - "payment_codes_list": "支払コードリスト", - "who_can_pay_me": "あなたに支払いできる人:", + "contacts": "連絡先", + "bip47_explain": "再利用・共有可能なコード", + "bip47_explain_subtitle": "BIP47", "purpose": "再利用・共有可能なコード (BIP47)", + "pay_this_contact": "この連絡先に支払う", + "rename_contact": "連絡先をリネーム", + "copy_payment_code": "支払いコードをコピー", + "hide_contact": "連絡先を隠す", + "rename": "リネーム", + "provide_name": "連絡先に新しい名前を付ける", + "add_contact": "連絡先を追加", + "provide_payment_code": "支払いコードを渡す", + "invalid_pc": "無効な支払いコード", + "notification_tx_unconfirmed": "通知トランザクションが承認されていません、お待ちください", + "failed_create_notif_tx": "チェーン上のトランザクションの作成に失敗しました", + "onchain_tx_needed": "チェーン上のトランザクションが必要です", + "notif_tx_sent": "通知トランザクションを送りました。承認をお待ちください", + "notif_tx": "通知トランザクション", "not_found": "支払コードが見つかりません" } } diff --git a/loc/kk@Cyrl.json b/loc/kk@Cyrl.json new file mode 100644 index 00000000000..dac33c6fbc5 --- /dev/null +++ b/loc/kk@Cyrl.json @@ -0,0 +1,704 @@ +{ + "_": { + "bad_password": "Құпиясөз қате. Қайталап көріңіз.", + "cancel": "Бас тарту", + "continue": "Жалғастыру", + "clipboard": "Алмасу буфері", + "enter_password": "Құпиясөзді енгізіңіз", + "never": "Ешқашан", + "of": "{number} / {total}", + "ok": "ОК", + "yes": "Иә", + "no": "Жоқ", + "close": "Жабу", + "copied": "Көшірілді!", + "discard_changes": "Өзгерістерді жоюға болсын ба?", + "discard_changes_explain": "Сақталмаған өзгерістер бар. Оларды жойып, экраннан шығуға сенімдісіз бе?", + "enter_url": "URL енгізіңіз", + "storage_is_encrypted": "Сақтау орны шифрланған. Шифрды ашу үшін құпиясөз қажет.", + "save": "Сақтау...", + "seed": "Сид", + "success": "Сәтті", + "wallet_key": "Әмиян кілті", + "change_input_currency": "Енгізу валютасын өзгерту", + "refresh": "Жаңарту", + "pick_image": "Кітапханадан таңдау", + "pick_file": "Файлды таңдау", + "enter_amount": "Сома енгізіңіз", + "qr_custom_input_button": "Қолмен енгізу үшін 10 рет түртіңіз", + "unlock": "Құлпын ашу", + "port": "Порт", + "ssl_port": "SSL порты", + "suggested": "Ұсынылған" + }, + "entropy": { + "save": "Сақтау", + "title": "Энтропия", + "undo": "Болдырмау", + "amountOfEntropy": "{limit} биттің {bits}" + }, + "errors": { + "error": "Қате", + "broadcast": "Тарату сәтсіз аяқталды.", + "network": "Желі қатесі" + }, + "lnd": { + "expired": "Мерзімі біткен", + "expiresIn": "Мерзімі {time} минутта аяқталады", + "payButton": "Төлем жасау", + "payment": "Төлем", + "refill": "Қайта толтыру", + "errorInvoiceExpired": "Шот мерзімі біткен.", + "placeholder": "Шот немесе мекенжай", + "potentialFee": "Ықтимал комиссия: {fee}", + "refill_create": "Жалғастыру үшін толтыруға арналған Bitcoin әмиянын жасаңыз.", + "refill_external": "Сыртқы әмияннан толтыру", + "refill_lnd_balance": "Lightning әмиянының балансын толтыру", + "sameWalletAsInvoiceError": "Шотты жасаған әмиянмен сол шотты төлей алмайсыз.", + "title": "Қаражатты басқару" + }, + "lndViewInvoice": { + "lightning_invoice": "Lightning инвойсы", + "sats": "сат.", + "additional_info": "Қосымша ақпарат", + "for": "Үшін:", + "please_pay_between_and": "{min} және {max} аралығында төлеңіз", + "please_pay": "Төлеңіз", + "preimage": "Pre-image", + "date_time": "Күні мен уақыты", + "wasnt_paid_and_expired": "Бұл шот төленбеді және мерзімі бітті." + }, + "receive": { + "details_create": "Жасау", + "details_label": "Сипаттама", + "header": "Қабылдау", + "details_setAmount": "Сомамен қабылдау", + "details_share": "Бөлісу...", + "address_not_found": "Қабылдау мекенжайын жасау мүмкін болмады.", + "reset": "Қалпына келтіру", + "maxSats": "Ең үлкен сома — {max} сатоши", + "maxSatsFull": "Ең үлкен сома — {max} сатоши немесе {currency}", + "minSats": "Ең аз сома — {min} сатоши", + "minSatsFull": "Ең аз сома — {min} сатоши немесе {currency}", + "qrcode_for_the_address": "Мекенжайдың QR коды", + "bip47_explanation": "Төлем кодтары — әмиян мекенжайларыңызды ашпайтын әмбебап мекенжай. Барлық сервистер оларды қолдай бермейді." + }, + "send": { + "broadcastButton": "Тарату", + "broadcastError": "Қате", + "broadcastPending": "Күтілуде", + "broadcastSuccess": "Сәтті", + "confirm_header": "Растау", + "confirm_sendNow": "Қазір жіберу", + "create_broadcast": "Тарату", + "create_fee": "Комиссия", + "create_memo": "Жазба", + "create_satoshi_per_vbyte": "sat/vByte", + "details_add_rec_add": "Алушыны қосу", + "details_add_rec_rem": "Алушыны алып тастау", + "details_address": "Адрес", + "details_next": "Ары қарай", + "details_scan": "Скан жасау", + "dynamic_next": "Ары қарай", + "dynamic_start": "Бастау", + "dynamic_stop": "Тоқтау", + "fee_10m": "10 минут", + "fee_1d": "1 күн", + "fee_3h": "3 сағат", + "fee_satvbyte": "sat/vByte", + "header": "Жіберу", + "input_done": "Дайын", + "open_settings": "Баптауларды ашу", + "success_done": "Дайын", + "provided_address_is_invoice": "Бұл мекенжай Lightning шоты сияқты. Осы шотты төлеу үшін Lightning әмиянына өтіңіз.", + "broadcastNone": "Транзакция hex-ін енгізіңіз", + "create_amount": "Сома", + "create_copy": "Көшіріп алып, кейін тарату", + "create_details": "Мәліметтер", + "create_this_is_hex": "Бұл транзакцияңыздың hex-і — қол қойылған және желіге таратуға дайын.", + "create_to": "Кімге", + "create_tx_size": "Транзакция өлшемі", + "create_verify": "coinb.in-да тексеру", + "details_insert_contact": "Контактіні енгізу", + "details_add_recc_rem_all_alert_description": "Барлық алушыларды алып тастауға сенімдісіз бе?", + "details_add_rec_rem_all": "Барлық алушыларды алып тастау", + "details_recipients_title": "Алушылар", + "details_recipient_title": "{number}-алушы #{total}-нан", + "please_complete_recipient_title": "Толық емес алушы", + "please_complete_recipient_details": "Жаңа алушыны қосу алдында #{number} алушының мәліметтерін толтырыңыз.", + "details_address_field_is_not_valid": "Мекенжай жарамсыз.", + "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_create": "Шот жасау", + "details_error_decode": "Bitcoin мекенжайын декодтау мүмкін болмады", + "details_fee_field_is_not_valid": "Комиссия жарамсыз.", + "details_frozen": "{amount} BTC қатырылған.", + "details_no_signed_tx": "Таңдалған файлда импорттауға болатын транзакция жоқ.", + "details_note_placeholder": "Өзіңізге жазба", + "details_scan_hint": "Бару нүктесін сканерлеу немесе импорттау үшін екі рет түртіңіз", + "details_scan_error": "Сканерлеу қатесі", + "details_total_exceeds_balance": "Жіберілетін сома қолжетімді балансты асып түсті.", + "details_total_exceeds_balance_frozen": "Жіберілетін сома қолжетімді балансты асып түсті. Қатырылған тиындар қосылмайтынын ескеріңіз.", + "details_unrecognized_file_format": "Файл пішімі танылмады", + "details_wallet_before_tx": "Транзакция жасау алдында алдымен Bitcoin әмиянын қосуыңыз қажет.", + "dynamic_init": "Бастапқы баптау", + "dynamic_prev": "Артқа", + "fee_custom": "Қолмен", + "insert_custom_fee": "Комиссияны енгізіңіз", + "fee_fast": "Жылдам", + "fee_medium": "Орташа", + "fee_replace_minvb": "Сіз төлегіңіз келетін жалпы комиссия мөлшерлемесі (sat/vByte) {min} sat/vByte-тен жоғары болуы тиіс.", + "fee_slow": "Баяу", + "input_clear": "Тазалау", + "input_paste": "Қою", + "input_total": "Барлығы:", + "permission_camera_message": "Камераны пайдалану үшін рұқсатыңыз қажет.", + "psbt_sign": "Транзакцияға қол қою", + "invalid_psbt": "Жарамсыз PSBT берілді.", + "permission_storage_denied_message": "BlueWallet бұл файлды сақтай алмайды. Құрылғы баптауларын ашып, Сақтау рұқсатын қосыңыз.", + "permission_storage_title": "Сақтау рұқсаты", + "psbt_clipboard": "Алмасу буферіне көшіру", + "psbt_this_is_psbt": "Бұл — ішінара қол қойылған Bitcoin транзакциясы (PSBT). Аппараттық әмиянмен қол қоюды аяқтаңыз.", + "psbt_tx_export": "Файлға экспорттау", + "no_tx_signing_in_progress": "Қол қойылып жатқан транзакция жоқ.", + "outdated_rate": "Мөлшерлеме соңғы рет жаңартылған: {date}", + "psbt_tx_open": "Қол қойылған транзакцияны ашу", + "psbt_tx_scan": "Қол қойылған транзакцияны сканерлеу", + "qr_error_no_qrcode": "Таңдалған суреттен жарамды QR кодты таба алмадық. Суретте тек QR код болатынын, мәтін немесе түймелер сияқты қосымша мазмұны жоқ екенін тексеріңіз.", + "reset_amount": "Соманы қалпына келтіру", + "reset_amount_confirm": "Соманы қалпына келтіруді қалайсыз ба?", + "txSaved": "Транзакция файлы ({filePath}) сақталды.", + "file_saved_at_path": "Файл ({filePath}) сақталды.", + "cant_send_to_silentpayment_adress": "Бұл әмиян Silent Payments мекенжайларына жібере алмайды", + "cant_send_to_bip47": "Бұл әмиян BIP47 төлем кодтарына жібере алмайды", + "cant_find_bip47_notification": "Алдымен осы Төлем кодын контактілерге қосыңыз", + "problem_with_psbt": "PSBT-мен мәселе" + }, + "settings": { + "biometrics": "Биометрия", + "block_explorer": "Блок шолғышы", + "header": "Баптаулар", + "language": "Тіл", + "network_broadcast": "Транзакцияны тарату", + "network_electrum": "Electrum сервері", + "notifications": "Хабарламалар", + "password": "Құпиясөз", + "save": "Сақтау", + "about": "Қолданба туралы", + "about_awesome": "Тамаша құралдармен жасалған", + "about_backup": "Кілттеріңіздің резервтік көшірмесін әрқашан сақтаңыз!", + "about_free": "BlueWallet — тегін әрі ашық бастапқы кодты жоба. Bitcoin қолданушылары жасаған.", + "about_license": "MIT лицензиясы", + "about_release_notes": "Шығарылым жазбалары", + "about_review": "Бізге пікір қалдырыңыз", + "performance_score": "Өнімділік ұпайы: {num}", + "run_performance_test": "Өнімділікті тексеру", + "about_selftest": "Өзін-өзі тексеруді іске қосу", + "block_explorer_invalid_custom_url": "Берілген URL жарамсыз. http:// немесе https:// деп басталатын жарамды URL енгізіңіз.", + "about_selftest_electrum_disabled": "Electrum офлайн режимінде өзін-өзі тексеру қолжетімсіз. Офлайн режимін өшіріп, қайталап көріңіз.", + "about_selftest_ok": "Барлық ішкі тесттер сәтті өтті. Әмиян жақсы жұмыс істейді.", + "about_sm_github": "GitHub", + "about_sm_telegram": "Telegram арнасы", + "privacy_temporary_screenshots": "Экран түсіруге рұқсат ету", + "privacy_temporary_screenshots_instructions": "Экран түсіруден қорғау уақытша өшіріледі, скриншоттар мен экранды жазып алу мүмкіндіктері қосылады. BlueWallet-ті жауып, қайта ашқанда қорғау автоматты түрде қайта іске қосылады.", + "biometrics_no_longer_available": "Құрылғы баптаулары өзгерді және енді қолданбада таңдалған қауіпсіздік баптауларына сәйкес келмейді. Биометрияны немесе құрылғы кодын қайта қосып, өзгерістерді қолдану үшін қолданбаны қайта іске қосыңыз.", + "biom_10times": "Сіз құпиясөзді 10 рет енгізіп көрдіңіз. Сақтау орныңызды қалпына келтіргіңіз келе ме? Бұл барлық әмияндарды жояды және сақтау орнының шифрын ашады.", + "biom_conf_identity": "Жеке басыңызды растаңыз.", + "biom_no_passcode": "Құрылғыда құрылғы коды немесе биометрия қосылмаған. Жалғастыру үшін Баптаулар қолданбасында құрылғы кодын немесе биометрияны баптаңыз.", + "biom_remove_decrypt": "Барлық әмияндарыңыз жойылып, сақтау орныңыздың шифры ашылады. Жалғастыруға сенімдісіз бе?", + "currency": "Валюта", + "currency_source": "Мөлшерлеме қайдан алынады", + "currency_fetch_error": "Таңдалған валюта мөлшерлемесін алу барысында қате орын алды.", + "default_title": "Қосылған кезде", + "donate": "Қайырымдылық жасау", + "donate_description": "Blue-ді тегін ұстауға көмектесіңіз!", + "electrum_connected": "Қосылған", + "electrum_connected_not": "Қосылмаған", + "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_saved": "Өзгерістеріңіз сәтті сақталды. Өзгерістер күшіне енуі үшін BlueWallet-ті қайта іске қосу қажет болуы мүмкін.", + "set_electrum_server_as_default": "{server} серверін әдепкі Electrum сервері ретінде орнату керек пе?", + "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_history": "Журнал", + "electrum_reset_to_default": "Бұл BlueWallet-ке сервер тізімінен серверді кездейсоқ таңдауға мүмкіндік береді.", + "electrum_reset": "Әдепкіге қайтару", + "electrum_reset_to_default_and_clear_history": "Әдепкіге қайтару және журналды тазалау", + "encrypt_decrypt": "Сақтау орнының шифрын ашу", + "encrypt_decrypt_q": "Сақтау орныңыздың шифрын ашуға сенімдісіз бе? Бұл әмияндарыңызға құпиясөзсіз қол жеткізуге мүмкіндік береді.", + "encrypt_enc_and_pass": "Құпиясөзбен қорғалған", + "encrypt_storage_explanation_headline": "Сақтау орнының шифрлауын қосу", + "encrypt_storage_explanation_description_line1": "Сақтау орнының шифрлауын қосу деректерді құрылғыда сақтау тәсілін қорғау арқылы қолданбаға қосымша қорғаныс деңгейін қосады. Бұл рұқсатсыз ақпаратқа қол жеткізуді қиындатады.", + "encrypt_storage_explanation_description_line2": "Алайда бұл шифрлау тек құрылғы кілт қоймасында сақталған әмияндарға қол жеткізуді ғана қорғайтынын білген жөн. Ол әмияндарға құпиясөз немесе қосымша қорғаныс қоспайды.", + "i_understand": "Түсінемін", + "block_explorer_preferred": "Таңдаулы блок шолғышын пайдалану", + "block_explorer_error_saving_custom": "Таңдаулы блок шолғышын сақтау кезінде қате", + "encrypt_title": "Қауіпсіздік", + "encrypt_tstorage": "Сақтау орны", + "encrypt_use": "{type} қолдану", + "set_as_preferred": "Таңдаулы ретінде орнату", + "set_as_preferred_electrum": "{host}:{port} серверін таңдаулы ретінде орнату ұсынылған серверге кездейсоқ қосылуды өшіреді.", + "encrypted_feature_disabled": "Бұл функция шифрланған сақтау орны қосулы кезде қолданыла алмайды.", + "encrypt_use_expl": "{type} транзакция жасамас бұрын, әмиянды құлпын ашу, экспорттау немесе жою алдында жеке басыңызды растау үшін пайдаланылады.", + "biometrics_fail": "{type} қосылмаған немесе құлпын аша алмаған жағдайда, балама ретінде құрылғы кодын пайдалана аласыз.", + "general": "Жалпы", + "general_continuity": "Сабақтастық", + "general_continuity_e": "Қосылғанда таңдалған әмияндар мен транзакцияларды өзіңіздің басқа Apple iCloud-қа қосылған құрылғыларыңыз арқылы қарай аласыз.", + "groundcontrol_explanation": "GroundControl — Bitcoin әмияндарына арналған тегін, ашық бастапқы кодты push-хабарлама сервері. Өзіңіздің GroundControl серверіңізді орнатып, оның URL-ін осы жерге қойып, BlueWallet инфрақұрылымына тәуелді болмауға болады. Әдепкі GroundControl серверін пайдалану үшін бос қалдырыңыз.", + "last_updated": "Соңғы жаңарту", + "language_isRTL": "Тіл бағыты күшіне енуі үшін BlueWallet-ті қайта іске қосу қажет.", + "license": "Лицензия", + "lightning_error_lndhub_uri": "Жарамсыз LNDhub URI", + "lightning_error_lndhub_uri_tor": "Жарамсыз LNDhub URI. Orbot қолданбасының қосылғанына көз жеткізіп, қайталап көріңіз.", + "lightning_saved": "Өзгерістеріңіз сәтті сақталды.", + "lightning_settings": "Lightning баптаулары", + "lightning_settings_explain": "Өзіңіздің LND түйініңізге қосылу үшін LNDhub орнатып, оның URL-ін осы баптауларға енгізіңіз. Тек өзгерістерді сақтағаннан кейін жасалған әмияндар көрсетілген LNDhub-қа қосылатынын ескеріңіз.", + "lndhub_github": "GitHub репозиторийі", + "network": "Желі", + "electrum_suggested_description": "Таңдаулы сервер орнатылмаған жағдайда, ұсынылған сервер кездейсоқ таңдалады.", + "not_a_valid_uri": "Жарамсыз URI", + "open_link_in_explorer": "Сілтемені блок шолғышында ашу", + "password_explain": "Сақтау орнын ашу үшін қолданатын құпиясөзді енгізіңіз.", + "plausible_deniability": "Plausible Deniability", + "privacy": "Құпиялылық", + "privacy_read_clipboard": "Алмасу буферін оқу", + "privacy_system_settings": "Жүйелік баптаулар", + "privacy_quickactions": "Әмиян таңбашалары", + "privacy_quickactions_explanation": "Әмиян балансын жылдам көру үшін BlueWallet қолданба белгішесін басып тұрыңыз.", + "privacy_clipboard_explanation": "Алмасу буферінде мекенжай немесе шот табылса, таңбашаларды ұсыну.", + "privacy_do_not_track": "Аналитиканы өшіру", + "privacy_do_not_track_explanation": "Өнімділік пен сенімділік туралы ақпарат талдау үшін жіберілмейді.", + "rate": "Мөлшерлеме", + "push_notifications_explanation": "Хабарламаларды қосқанда құрылғы токеніңіз, сондай-ақ хабарламаларды қосқаннан кейін жасалған барлық әмияндар мен транзакциялардың әмиян мекенжайлары мен транзакция идентификаторлары серверге жіберіледі. Құрылғы токені хабарламалар жіберу үшін пайдаланылады, ал әмиян туралы ақпарат сізге кіріс Bitcoin немесе транзакция расталымдары туралы хабарлауға мүмкіндік береді.\n\nТек хабарламаларды қосқаннан кейінгі ақпарат беріледі — бұған дейінгі ешнәрсе жиналмайды.\n\nХабарламаларды өшіру осы ақпараттың барлығын серверден жояды. Сонымен қатар, әмиянды қолданбадан жою серверден сол әмиянға қатысты ақпаратты да жояды.", + "selfTest": "Өзін-өзі тексеру", + "saved": "Сақталды", + "success_transaction_broadcasted": "Транзакцияңыз сәтті таратылды!", + "total_balance": "Жалпы баланс", + "total_balance_explanation": "Үй экраны виджеттерінде барлық әмияндарыңыздың жалпы балансын көрсету.", + "widgets": "Виджеттер", + "tools": "Құралдар" + }, + "transactions": { + "cpfp_create": "Жасау", + "details_inputs": "Кірістер", + "details_outputs": "Шығыстар", + "details_title": "Транзакция", + "details_to": "Шығыс", + "offchain": "Off-chain", + "onchain": "On-chain", + "pending": "Күтілуде", + "rbf_title": "Комиссияны арттыру (RBF)", + "status_bump": "Комиссияны арттыру", + "status_cancel": "Транзакцияны болдырмау", + "transaction": "Транзакция", + "cancel_explain": "Бұл транзакцияны сізге төлейтін және жоғарырақ комиссиясы бар басқа транзакциямен ауыстырамыз. Бұл осы транзакцияны тиімді түрде болдырмайды. Бұл RBF — Replace by Fee деп аталады.", + "cancel_no": "Бұл транзакцияны ауыстыруға болмайды.", + "cancel_title": "Осы транзакцияны болдырмау (RBF)", + "transaction_loading_error": "Транзакцияны жүктеу кезінде мәселе туындады. Кейінірек қайталап көріңіз.", + "transaction_not_available": "Транзакция қолжетімсіз", + "confirmations_lowercase": "{confirmations} растау", + "expand_note": "Жазбаны жаю", + "cpfp_exp": "Расталмаған транзакцияңызды жұмсайтын тағы бір транзакция жасаймыз. Жалпы комиссия бастапқы транзакция комиссиясынан жоғары болады, сондықтан ол тезірек өндірілуі тиіс. Бұл CPFP — Child Pays for Parent деп аталады.", + "cpfp_no_bump": "Бұл транзакция үшін комиссияны арттыруға болмайды.", + "cpfp_title": "Комиссияны арттыру (CPFP)", + "details_balance_hide": "Балансты жасыру", + "details_balance_show": "Балансты көрсету", + "details_copy": "Көшіру", + "details_copy_block_explorer_link": "Блок шолғышы сілтемесін көшіру", + "details_copy_note": "Жазбаны көшіру", + "details_copy_txid": "Транзакция идентификаторын көшіру", + "date": "Күні", + "details_received": "Алынды", + "details_view_in_browser": "Браузерде қарау", + "incoming_transaction": "Кіріс транзакция", + "outgoing_transaction": "Шығыс транзакция", + "expired_transaction": "Мерзімі біткен транзакция", + "pending_transaction": "Күтілуде тұрған транзакция", + "enable_offline_signing": "Бұл әмиян офлайн қол қоюмен бірге пайдаланылмайды. Қазір қосуды қалайсыз ба?", + "list_conf": "Раст.: {number}", + "pending_with_amount": "Күтілуде {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "Болжамды уақыт: ~10 минуттан кейін", + "eta_3h": "Болжамды уақыт: ~3 сағаттан кейін", + "eta_1d": "Болжамды уақыт: ~1 күннен кейін", + "list_title": "Транзакциялар", + "list_title_sent": "Жіберілген", + "list_title_received": "Алынған", + "open_url_error": "Сілтемені әдепкі браузерде ашу мүмкін болмады. Әдепкі браузерді ауыстырып, қайталап көріңіз.", + "rbf_explain": "Бұл транзакцияны тезірек өндірілуі үшін жоғары комиссиямен ауыстырамыз. Бұл RBF — Replace by Fee деп аталады.", + "transactions_count": "Транзакциялар саны", + "txid": "Транзакция идентификаторы", + "updating": "Жаңартылуда...", + "watchOnlyWarningTitle": "Қауіпсіздік ескертуі", + "watchOnlyWarningDescription": "Қолданушыларды алдау үшін «тек қарау» әмияндарын жиі пайдаланатын алаяқтардан сақ болыңыз. Бұл әмияндар қаражатты басқаруға немесе жіберуге мүмкіндік бермейді; олар тек балансты көруге мүмкіндік береді.", + "custom_fee_warning_title": "Ескерту", + "custom_fee_warning_description": "1 sat/vB-тен төмен комиссиялар жарамды, бірақ түйін саясатына байланысты таратылмауы мүмкін.", + "details_eta_analyzing": "Талдау жүруде...", + "details_sent": "Жіберілді", + "details_section": "Мәліметтер", + "details_explorer": "блок шолғышы", + "details_network_fee": "Желілік комиссия", + "details_to_address": "Кімге", + "details_id": "Идентификатор", + "details_note": "Жазба", + "details_add_note": "қосу", + "details_advanced": "Қосымша", + "details_fee_rate": "Комиссия мөлшерлемесі", + "details_size": "Өлшем", + "details_virtual_size": "Виртуалды өлшем", + "details_tx_hex": "Tx hex", + "details_inputs_count": "Кірістер ({count})", + "details_outputs_count": "Шығыстар ({count})" + }, + "wallets": { + "add_create": "Жасау", + "add_bitcoin": "Bitcoin", + "add_lightning": "Lightning", + "details_address": "Адрес", + "details_derivation_path": "туынды жолы", + "details_master_fingerprint": "Негізгі саусақ ізі", + "details_title": "Әмиян", + "details_type": "Түрі", + "import_do_import": "Импорттау", + "import_passphrase": "Құпиясөйлем", + "import_passphrase_title": "Құпиясөйлем", + "import_search_accounts": "Шот іздеу", + "import_title": "Импорттау", + "list_title": "Әмияндар", + "list_tryagain": "Қайталау", + "wallets": "Әмияндар", + "add_bitcoin_explain": "Қарапайым әрі қуатты Bitcoin әмияны", + "total_balance": "Жалпы баланс", + "add_entropy_reset_title": "Энтропияны қалпына келтіру", + "add_entropy_reset_message": "Әмиян түрін өзгерту ағымдағы энтропияны қалпына келтіреді. Жалғастырғыңыз келе ме?", + "add_entropy": "Энтропия", + "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_type": "Түрі", + "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": "Импорттау кезінде алмасу буферін тазалау", + "details_advanced": "Қосымша", + "details_are_you_sure": "Сенімдісіз бе?", + "details_connected_to": "Қосылған", + "details_del_wb_err": "Енгізілген баланс сомасы осы әмиянның балансымен сәйкес келмейді. Қайталап көріңіз.", + "details_del_wb_q": "Бұл әмиянның балансы бар. Жалғастыру алдында, осы әмиянның сид тіркесінсіз қаражатты қалпына келтіре алмайтыныңызды ескеріңіз. Кездейсоқ жоюды болдырмау үшін әмиянның {balance} сатоши балансын енгізіңіз.", + "details_delete": "Жою", + "details_delete_wallet": "Әмиянды жою", + "details_display": "Бастапқы экранда көрсету", + "details_export_backup": "Экспорт/Резервтік көшірме", + "details_export_history": "Журналды CSV-ке экспорттау", + "details_multisig_type": "multisig", + "details_show_xpub": "Әмиянның xpub-ын көрсету", + "details_show_addresses": "Мекенжайларды көрсету", + "swipe_balance_hide": "Жасыру", + "swipe_balance_show": "Көрсету", + "drag_to_reorder": "Ретін өзгерту үшін сүйреңіз", + "clear_search": "Іздеуді тазалау", + "details_use_with_hardware_wallet": "Аппараттық әмиянмен пайдалану", + "details_yes_delete": "Иә, жою", + "enter_bip38_password": "Шифрды ашу үшін құпиясөз енгізіңіз", + "export_title": "Әмиянды экспорттау", + "import_passphrase_message": "Құпиясөйлемді қолданған болсаңыз, енгізіңіз", + "import_error": "Импорттау сәтсіз аяқталды. Берілген деректердің жарамды екенін тексеріңіз.", + "import_explanation": "Сид сөздерін, ашық кілтті, WIF-ті немесе қолыңызда барды енгізіңіз. BlueWallet дұрыс пішімді табуға және әмияныңызды импорттауға бар күшін салады.", + "import_imported": "Импортталған", + "import_scan_qr": "Сканерлеу немесе файл импорттау", + "import_success": "Әмияныңыз сәтті импортталды.", + "import_success_watchonly": "Әмияныңыз сәтті импортталды. ЕСКЕРТУ: Бұл — тек қарау әмияны, одан жұмсауға БОЛМАЙДЫ.", + "learn_more": "Көбірек білу", + "import_discovery_title": "Табу", + "import_discovery_subtitle": "Табылған әмиянды таңдаңыз", + "import_discovery_derivation": "Қолмен шығару жолын пайдалану", + "import_discovery_no_wallets": "Әмиян табылмады.", + "import_discovery_offline": "BlueWallet қазір офлайн режимде. Бұл режимде әмияннің бар-жоғын тексере алмайды, сондықтан дұрысын қолмен таңдауыңыз керек", + "import_derivation_found": "Табылды", + "import_derivation_found_not": "Табылмады", + "import_derivation_loading": "Жүктелуде...", + "import_derivation_subtitle": "Қолмен шығару жолын енгізіңіз, біз сіздің әмияныңызды табуға тырысамыз.", + "import_derivation_title": "Шығару жолы", + "import_derivation_unknown": "Белгісіз", + "import_wrong_path": "Қате шығару жолы", + "list_create_a_button": "Қазір қосу", + "list_create_a_wallet": "Әмиян қосу", + "list_create_a_wallet_text": "Бұл тегін, әрі қалағаныңызша\nкөп әмиян жасай аласыз.", + "list_empty_txs1": "Транзакцияларыңыз осы жерде көрінеді.", + "list_empty_txs1_lightning": "Lightning әмияны күнделікті транзакциялар үшін пайдаланылуы тиіс. Комиссиялар әділдіктен тыс арзан, ал жылдамдық тым тез.", + "list_empty_txs2": "Әмияныңыздан бастаңыз.", + "list_empty_txs2_lightning": "\nПайдалануды бастау үшін «Қаражатты басқару» дегенді түртіп, балансты толтырыңыз.", + "list_latest_transaction": "Соңғы транзакция", + "list_long_choose": "Фото таңдау", + "paste_from_clipboard": "Қою", + "import_file": "Файлды импорттау", + "list_long_scan": "QR кодты сканерлеу", + "no_ln_wallet_error": "Lightning шотын төлеу алдында алдымен Lightning әмиянын қосуыңыз қажет.", + "looks_like_bip38": "Бұл құпиясөзбен қорғалған жеке кілт сияқты (BIP38).", + "manage_title": "Әмияндарды басқару", + "no_results_found": "Ештеңе табылмады.", + "please_continue_scanning": "Сканерлеуді жалғастырыңыз.", + "select_no_bitcoin": "Қазіргі уақытта қолжетімді Bitcoin әмияндары жоқ.", + "select_no_bitcoin_exp": "Lightning әмияндарын толтыру үшін Bitcoin әмияны қажет. Оны жасаңыз немесе импорттаңыз.", + "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": "Кейін әмиянды қалпына келтіру үшін осы URL-ді көшіріп, қауіпсіз сақтаңыз.", + "copy_ln_public": "Кейін әмиянды қалпына келтіру үшін осы ақпаратты көшіріп, қауіпсіз сақтаңыз.", + "add_ln_wallet_first": "Алдымен Lightning әмиянын қосуыңыз қажет.", + "identity_pubkey": "Жеке басын куәландыратын pubkey", + "xpub_title": "Әмиянның xpub-ы", + "manage_wallets_search_placeholder": "Әмияндарды, мекенжайларды, транзакцияларды және жазбаларды іздеу", + "more_info": "Қосымша ақпарат", + "details_delete_wallet_error_message": "Бұл әмиянның хабарламалардан жойылғанын растау кезінде мәселе болды — бұл желі ақаулығына немесе нашар байланысқа байланысты болуы мүмкін. Жалғастырсаңыз, әмиян жойылғаннан кейін де оған қатысты транзакциялар туралы хабарлама алуыңыз мүмкін.", + "details_delete_anyway": "Бәрібір жою" + }, + "multisig": { + "confirm": "Растау", + "header": "Жіберу", + "create": "Жасау", + "provide_signature": "Қол қою", + "quorum_header": "Кворум", + "multisig_vault": "Multisig сейфі", + "default_label": "Multisig сейфі", + "multisig_vault_explain": "Үлкен сомалар үшін ең жоғары қауіпсіздік", + "provide_signature_details": "Осы транзакцияға қол қою үшін кілт сақталған құрылғыңыз бен әмияныңызды пайдаланыңыз", + "provide_signature_details_bluewallet": "BlueWallet-те «Жіберу» экранының мәзіріне өтіп, мынаны таңдаңыз: ", + "provide_signature_next_steps": "Қол қойылған транзакцияны сканерлеу немесе импорттау", + "provide_signature_next_steps_details": "Әмияныңыз транзакцияға сәтті қол қойғаннан кейін, берілген QR кодты сканерлеңіз немесе бірге келген файлды импорттаңыз, содан кейін таратудан бұрын транзакцияның барлық мәліметтерін тексеріңіз.", + "vault_key": "Сейф кілті {number}", + "required_keys_out_of_total": "Жалпы саннан қажетті кілттер", + "fee": "Комиссия: {number}", + "fee_btc": "{number} BTC", + "share": "Бөлісу...", + "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": "Ескі", + "co_sign_transaction": "Транзакцияға қол қою", + "what_is_vault": "Сейф — бұл", + "what_is_vault_numberOfWallets": " {m}-нен-{n} multisig ", + "what_is_vault_wallet": "әмиян.", + "vault_advanced_customize": "Сейф баптаулары", + "needs": "Оған қажет", + "what_is_vault_description_number_of_vault_keys": " {m} сейф кілті ", + "what_is_vault_description_to_spend": "жұмсау үшін, ал үшіншісін\nрезервтік ретінде пайдалана аласыз.", + "what_is_vault_description_to_spend_other": "жұмсау үшін.", + "quorum": "{m}-нен-{n} кворум", + "of": "ішінен", + "wallet_type": "Әмиян түрі", + "invalid_mnemonics": "Бұл мнемоникалық тіркес жарамсыз сияқты.", + "invalid_cosigner": "Жарамсыз қос қол қоюшы деректері", + "not_a_multisignature_xpub": "Бұл multisignature әмиянының 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": "Multisig сейфтері қалай жұмыс істейді: кеңестер мен әдістер", + "ms_help_text": "Қауіпсіздікті арттыруға немесе ортақ сақтауға арналған бірнеше кілті бар әмиян", + "ms_help_title1": "Бірнеше құрылғыны пайдалану ұсынылады.", + "ms_help_1": "Сейф басқа BlueWallet қолданбаларымен және PSBT үйлесімді әмияндармен, мысалы Electrum, Specter, Coldcard, Cobo Vault және т.б. бірге жұмыс істейді.", + "ms_help_title2": "Кілттерді өңдеу", + "ms_help_2": "Барлық сейф кілттерін осы құрылғыда жасап, кейін оларды жоюға немесе өңдеуге болады. Барлық кілттер бір құрылғыда болса, қауіпсіздік деңгейі қарапайым Bitcoin әмиянына тең.", + "ms_help_title3": "Сейфтің резервтік көшірмелері", + "ms_help_3": "Әмиян параметрлерінен сейфтің және тек қарау әмиянының резервтік көшірмелерін табасыз. Бұл резервтік көшірме әмиянға арналған картаға ұқсайды. Сидтердің бірін жоғалтсаңыз, әмиянды қалпына келтіру үшін қажет.", + "ms_help_title4": "Сейфтерді импорттау", + "ms_help_4": "Multisig-ті импорттау үшін резервтік көшірме файлын және «Импорттау» функциясын пайдаланыңыз. Тек сидтер мен xpub-тарыңыз болса, сейф кілттерін жасау кезінде жеке «Импорттау» түймесін пайдалана аласыз.", + "ms_help_title5": "Кеңейтілген режим", + "ms_help_5": "Әдепкі бойынша BlueWallet 2-нен-3 сейфін жасайды. Басқа кворум жасау немесе мекенжай түрін өзгерту үшін Баптаулардағы Кеңейтілген режимді қосыңыз." + }, + "addresses": { + "addresses_title": "Мекенжайлар", + "sign_verify": "Растау", + "sign_placeholder_address": "Адрес", + "transactions": "Транзакциялар", + "type_receive": "Қабылдау", + "copy_private_key": "Жеке кілтті көшіру", + "sensitive_private_key": "Ескерту: жеке кілттер өте құпия. Жалғастыру керек пе?", + "sign_title": "Хабарламаға қол қою/растау", + "sign_help": "Мұнда Bitcoin мекенжайы негізінде криптографиялық қолтаңба жасай немесе тексере аласыз.", + "sign_sign": "Қол қою", + "sign_signature_correct": "Растау сәтті өтті!", + "sign_signature_incorrect": "Растау сәтсіз аяқталды!", + "sign_placeholder_message": "Хабарлама", + "sign_placeholder_signature": "Қолтаңба", + "type_change": "Қалдық", + "type_used": "Пайдаланылған" + }, + "bip47": { + "payment_code": "Төлем коды", + "contacts": "Контактілер", + "copy_payment_code": "Төлем кодын көшіру", + "invalid_pc": "Жарамсыз төлем коды", + "notif_tx": "Хабарлама транзакциясы", + "not_found": "Төлем коды табылмады", + "bip47_explain": "Қайта пайдаланылатын әрі бөлісуге болатын код", + "bip47_explain_subtitle": "BIP47", + "purpose": "Қайта пайдаланылатын әрі бөлісуге болатын код (BIP47)", + "pay_this_contact": "Осы контактіге төлеу", + "rename_contact": "Контактінің атын өзгерту", + "hide_contact": "Контактіні жасыру", + "rename": "Атын өзгерту", + "provide_name": "Осы контактіге жаңа атау беріңіз", + "add_contact": "Контакт қосу", + "provide_payment_code": "Төлем кодын беріңіз", + "notification_tx_unconfirmed": "Хабарлама транзакциясы әлі расталмаған, күтіңіз", + "failed_create_notif_tx": "Он-чейн транзакциясын жасау сәтсіз аяқталды", + "onchain_tx_needed": "Он-чейн транзакция қажет", + "notif_tx_sent": "Хабарлама транзакциясы жіберілді. Расталғанша күтіңіз" + }, + "units": { + "BTC": "BTC", + "sat_vbyte": "sat/vByte", + "sats": "сатоши", + "MAX": "Макс." + }, + "cc": { + "change": "Қалдық", + "header": "UTXO басқару", + "coins_selected": "Таңдалған тиындар ({number})", + "selected_summ": "{value} таңдалды", + "empty": "Бұл әмиянда қазіргі уақытта тиындар жоқ.", + "freeze": "Қатыру", + "freezeLabel": "Қатыру", + "freezeLabel_un": "Қатыруды алып тастау", + "use_coin": "Тиынды пайдалану", + "use_coins": "Тиындарды пайдалану", + "tip": "Бұл функция әмиянды жақсырақ басқару үшін тиындарды көруге, белгілеуге, қатыруға немесе таңдауға мүмкіндік береді. Түрлі-түсті дөңгелектерді түрту арқылы бірнеше тиынды таңдай аласыз.", + "sort_asc": "Өсу бойынша", + "sort_desc": "Кему бойынша", + "sort_height": "Биіктік", + "sort_value": "Құн", + "sort_label": "Белгі", + "sort_status": "Күй", + "sort_by": "Сұрыптау" + }, + "azteco": { + "codeIs": "Сіздің ваучер кодыңыз", + "errorBeforeRefeem": "Ваучерді өтеу алдында алдымен Bitcoin әмиянын қосуыңыз қажет.", + "errorSomething": "Бірдеңе дұрыс болмады. Бұл ваучер әлі жарамды ма?", + "redeem": "Әмиянға өтеу", + "redeemButton": "Өтеу", + "success": "Сәтті", + "successMessage": "Ваучер сәтті өтелді! Қаражат жақын арада Bitcoin әмияныңызға түседі.", + "title": "Azte.co ваучерін өтеу" + }, + "plausibledeniability": { + "create_fake_storage": "Шифрланған сақтау орнын жасау", + "create_password_explanation": "Жалған сақтау орнының құпиясөзі негізгі сақтау орнының құпиясөзімен сәйкес келмеуі тиіс.", + "help": "Кейбір жағдайларда сізді құпиясөзді ашуға мәжбүрлеуі мүмкін. Тиындарыңызды қорғау үшін BlueWallet басқа құпиясөзі бар тағы бір шифрланған сақтау орнын жасай алады. Қысым жағдайында осы құпиясөзді үшінші тарапқа аша аласыз. BlueWallet-те енгізілген болса, ол жаңа «жалған» сақтау орнын ашады. Бұл үшінші тарапқа шынайы көрінеді, бірақ ол негізгі сақтау орныңызды тиындарымен бірге құпия түрде сақтайды.", + "help2": "Жаңа сақтау орны толық жұмыс істейді, әрі сенімді көріну үшін онда шамалы соманы сақтай аласыз.", + "password_should_not_match": "Бұл құпиясөз қазір қолданыста. Басқа құпиясөзді көріңіз.", + "title": "Plausible Deniability" + }, + "pleasebackup": { + "ask": "Әмияныңыздың резервтік тіркесін сақтадыңыз ба? Бұл құрылғыны жоғалтсаңыз, қаражатқа қол жеткізу үшін осы резервтік тіркес қажет. Резервтік тіркессіз қаражатыңыз біржола жоғалады.", + "ask_no": "Жоқ, сақтаған жоқпын.", + "ask_yes": "Иә, сақтадым.", + "ok": "ОК, жазып алдым.", + "ok_lnd": "ОК, сақтадым.", + "text": "Осы мнемоникалық тіркесті қағазға жазып алуға уақыт бөліңіз.\nБұл сіздің резервтік көшірмеңіз, оны әмиянды қалпына келтіру үшін пайдалана аласыз.", + "text_lnd": "Бұл әмиянның резервтік көшірмесін сақтаңыз. Ол жоғалған жағдайда әмиянды қалпына келтіруге мүмкіндік береді.", + "title": "Әмияныңыз жасалды." + }, + "notifications": { + "would_you_like_to_receive_notifications": "Кіріс төлемдер келгенде хабарлама алғыңыз келе ме?", + "notifications_subtitle": "Кіріс төлемдер және транзакция расталымдары", + "no_and_dont_ask": "Жоқ, бұдан былай сұрамаңыз.", + "permission_denied_message": "Сіз хабарлама жіберуге рұқсат бермедіңіз. Хабарламаларды алғыңыз келсе, оларды құрылғы баптауларынан қосыңыз." + }, + "total_balance_view": { + "display_in_bitcoin": "Bitcoin-да көрсету", + "hide": "Жасыру", + "display_in_sats": "Сатошиде көрсету", + "display_in_fiat": "{currency} валютасында көрсету", + "title": "Жалпы баланс", + "explanation": "Шолу экранында барлық әмияндарыңыздың жалпы балансын қараңыз." + }, + "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": "Lightning әмияныңызды пайдаланып, мына жерде есептік жазба тіркегіңіз келе ме:", + "register_question_part_2": "Lightning әмияныңыз арқылы?", + "register_answer": "{hostname} жерінде есептік жазба сәтті тіркелді!", + "login_question_part_1": "Lightning әмияныңызды пайдаланып, мына жерге кіргіңіз келе ме:", + "login_question_part_2": "Lightning әмияныңыз арқылы?", + "login_answer": "Сіз {hostname}-та сәтті кірдіңіз!", + "link_question_part_1": "Есептік жазбаңызды Lightning әмиянына байланыстырғыңыз келе ме:", + "link_question_part_2": "Lightning әмияныңызбен?", + "link_answer": "Lightning әмияныңыз {hostname} жеріндегі есептік жазбаңызбен сәтті байланыстырылды!", + "auth_question_part_1": "Lightning әмияныңызды пайдаланып, мына жерде расталғыңыз келе ме:", + "auth_question_part_2": "Lightning әмияныңыз арқылы?", + "auth_answer": "Сіз {hostname}-та сәтті аутентификациядан өттіңіз!", + "could_not_auth": "Сізді {hostname}-та аутентификациялай алмадық.", + "authenticate": "Растау" + } +} diff --git a/loc/kn.json b/loc/kn.json index 66fde1ef389..403f87ad41c 100644 --- a/loc/kn.json +++ b/loc/kn.json @@ -3,25 +3,702 @@ "bad_password": "ಪಾಸ್ವರ್ಡ್ ತಪ್ಪಾಗಿದೆ. ದಯವಿಟ್ಟು ಪುನಃ ಪ್ರಯತ್ನಿಸಿ.", "cancel": "ರದ್ದುಮಾಡಿ", "continue": "ಮುಂದುವರಿಸಿ", + "clipboard": "ಕ್ಲಿಪ್‌ಬೋರ್ಡ್", + "copied": "ನಕಲಿಸಲಾಗಿದೆ!", "enter_password": "ಪಾಸ್ವರ್ಡ್ ನಮೂದಿಸಿ", "never": "ಎಂದಿಗೂ", - "of": "{number} ರಲ್ಲಿ {total}", + "of": "{total} ರಲ್ಲಿ {number}", "ok": "ಸರಿ", + "enter_url": "URL ನಮೂದಿಸಿ", "storage_is_encrypted": "ನಿಮ್ಮ ಸಂಗ್ರಹಣೆಯನ್ನು ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಅದನ್ನು ಡೀಕ್ರಿಪ್ಟ್ ಮಾಡಲು ಪಾಸ್ವರ್ಡ್ ಅಗತ್ಯವಿದೆ.", "yes": "ಹೌದು", "no": "ಇಲ್ಲ", - "save": "ಉಳಿಸಿ", + "save": "ಉಳಿಸಿ...", + "seed": "ಸೀಡ್", + "success": "ಯಶಸ್ಸು", "wallet_key": "ವ್ಯಾಲೆಟ್ ಕೀ", - "invalid_animated_qr_code_fragment": "ಅಮಾನ್ಯ ಅನಿಮೇಟೆಡ್ QR ಕೋಡ್. ದಯವಿಟ್ಟು ಪುನಃ ಪ್ರಯತ್ನಿಸಿ." + "close": "ಮುಚ್ಚಿ", + "refresh": "ರಿಫ್ರೆಶ್", + "enter_amount": "ಮೊತ್ತ ನಮೂದಿಸಿ", + "unlock": "ಅನ್‌ಲಾಕ್", + "port": "ಪೋರ್ಟ್", + "ssl_port": "SSL ಪೋರ್ಟ್", + "change_input_currency": "ಇನ್‌ಪುಟ್ ಕರೆನ್ಸಿ ಬದಲಾಯಿಸಿ", + "discard_changes": "ಬದಲಾವಣೆಗಳನ್ನು ತಿರಸ್ಕರಿಸುವುದೇ?", + "discard_changes_explain": "ನಿಮ್ಮ ಬದಲಾವಣೆಗಳನ್ನು ಉಳಿಸಲಾಗಿಲ್ಲ. ಅವುಗಳನ್ನು ತಿರಸ್ಕರಿಸಿ ಪರದೆಯಿಂದ ಹೊರಹೋಗಲು ನೀವು ಖಚಿತವಾಗಿರುವಿರಾ?", + "pick_file": "ಫೈಲ್ ಆಯ್ಕೆಮಾಡಿ", + "pick_image": "ಲೈಬ್ರರಿಯಿಂದ ಆಯ್ಕೆಮಾಡಿ", + "qr_custom_input_button": "ಕಸ್ಟಮ್ ಇನ್‌ಪುಟ್ ನಮೂದಿಸಲು 10 ಬಾರಿ ಟ್ಯಾಪ್ ಮಾಡಿ", + "suggested": "ಸೂಚಿಸಲಾಗಿದೆ" + }, + "azteco": { + "redeemButton": "ರಿಡೀಮ್ ಮಾಡಿ", + "success": "ಯಶಸ್ಸು", + "codeIs": "ನಿಮ್ಮ ವೋಚರ್ ಕೋಡ್", + "errorBeforeRefeem": "ರಿಡೀಮ್ ಮಾಡುವ ಮೊದಲು, ನೀವು Bitcoin ವ್ಯಾಲೆಟ್ ಸೇರಿಸಬೇಕು.", + "errorSomething": "ಏನೋ ತಪ್ಪಾಗಿದೆ. ಈ ವೋಚರ್ ಇನ್ನೂ ಮಾನ್ಯವಾಗಿದೆಯೇ?", + "redeem": "ವ್ಯಾಲೆಟ್‌ಗೆ ರಿಡೀಮ್ ಮಾಡಿ", + "successMessage": "ವೋಚರ್ ಯಶಸ್ವಿಯಾಗಿ ರಿಡೀಮ್ ಆಗಿದೆ! ನಿಮ್ಮ ಹಣ ಶೀಘ್ರದಲ್ಲೇ ನಿಮ್ಮ Bitcoin ವ್ಯಾಲೆಟ್‌ಗೆ ಬರಬೇಕು.", + "title": "Azte.co ವೋಚರ್ ರಿಡೀಮ್ ಮಾಡಿ" }, "entropy": { - "save": "ಉಳಿಸಿ" + "save": "ಉಳಿಸಿ", + "amountOfEntropy": "{limit} ಬಿಟ್‌ಗಳಲ್ಲಿ {bits}", + "title": "ಎಂಟ್ರೋಪಿ", + "undo": "ರದ್ದು" + }, + "errors": { + "broadcast": "ಪ್ರಸಾರ ವಿಫಲವಾಗಿದೆ.", + "error": "ದೋಷ", + "network": "ನೆಟ್‌ವರ್ಕ್ ದೋಷ" + }, + "lnd": { + "errorInvoiceExpired": "ಸರಕುಪಟ್ಟಿಯ ಅವಧಿ ಮುಗಿದಿದೆ.", + "expired": "ಅವಧಿ ಮುಗಿದಿದೆ", + "expiresIn": "{time} ನಿಮಿಷಗಳಲ್ಲಿ ಅವಧಿ ಮುಗಿಯುತ್ತದೆ", + "payButton": "ಪಾವತಿಸಿ", + "payment": "ಪಾವತಿ", + "placeholder": "ಸರಕುಪಟ್ಟಿ ಅಥವಾ ವಿಳಾಸ", + "potentialFee": "ಸಂಭಾವ್ಯ ಶುಲ್ಕ: {fee}", + "refill": "ರೀಫಿಲ್", + "refill_create": "ಮುಂದುವರಿಯಲು, ರೀಫಿಲ್ ಮಾಡಲು Bitcoin ವ್ಯಾಲೆಟ್ ರಚಿಸಿ.", + "refill_external": "ಬಾಹ್ಯ ವ್ಯಾಲೆಟ್‌ನಿಂದ ರೀಫಿಲ್ ಮಾಡಿ", + "refill_lnd_balance": "Lightning ವ್ಯಾಲೆಟ್ ಬಾಕಿಯನ್ನು ರೀಫಿಲ್ ಮಾಡಿ", + "sameWalletAsInvoiceError": "ಇನ್‌ವಾಯ್ಸ್ ರಚಿಸಲು ಬಳಸಿದ ಅದೇ ವ್ಯಾಲೆಟ್‌ನಿಂದ ನೀವು ಇನ್‌ವಾಯ್ಸ್ ಪಾವತಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ.", + "title": "ಹಣ ನಿರ್ವಹಿಸಿ" + }, + "lndViewInvoice": { + "lightning_invoice": "Lightning ಸರಕುಪಟ್ಟಿ", + "sats": "ಸ್ಯಾಟ್‌ಗಳು.", + "wasnt_paid_and_expired": "ಈ ಸರಕುಪಟ್ಟಿ ಪಾವತಿಸಲಾಗಿಲ್ಲ ಮತ್ತು ಅವಧಿ ಮುಗಿದಿದೆ.", + "additional_info": "ಹೆಚ್ಚುವರಿ ಮಾಹಿತಿ", + "date_time": "ದಿನಾಂಕ ಮತ್ತು ಸಮಯ", + "for": "ಗಾಗಿ:", + "please_pay": "ದಯವಿಟ್ಟು ಪಾವತಿಸಿ", + "please_pay_between_and": "ದಯವಿಟ್ಟು {min} ಮತ್ತು {max} ನಡುವೆ ಪಾವತಿಸಿ", + "preimage": "Pre-image" + }, + "pleasebackup": { + "ask_no": "ಇಲ್ಲ, ನಾನು ಮಾಡಿಲ್ಲ.", + "ask_yes": "ಹೌದು, ನಾನು ಮಾಡಿದ್ದೇನೆ.", + "ok": "ಸರಿ, ನಾನು ಅದನ್ನು ಬರೆದಿಟ್ಟಿದ್ದೇನೆ.", + "ok_lnd": "ಸರಿ, ನಾನು ಅದನ್ನು ಉಳಿಸಿದ್ದೇನೆ.", + "ask": "ನಿಮ್ಮ ವ್ಯಾಲೆಟ್‌ನ ಬ್ಯಾಕಪ್ ಪದಗುಚ್ಛವನ್ನು ಉಳಿಸಿದ್ದೀರಾ? ಈ ಸಾಧನವನ್ನು ಕಳೆದುಕೊಂಡರೆ ನಿಮ್ಮ ಹಣವನ್ನು ಪ್ರವೇಶಿಸಲು ಈ ಬ್ಯಾಕಪ್ ಪದಗುಚ್ಛ ಅಗತ್ಯವಿದೆ. ಬ್ಯಾಕಪ್ ಪದಗುಚ್ಛವಿಲ್ಲದೆ, ನಿಮ್ಮ ಹಣವು ಶಾಶ್ವತವಾಗಿ ಕಳೆದುಹೋಗುತ್ತದೆ.", + "text": "ದಯವಿಟ್ಟು ಈ mnemonic ಪದಗುಚ್ಛವನ್ನು ಕಾಗದದ ತುಣುಕಿನ ಮೇಲೆ ಬರೆಯಲು ಸ್ವಲ್ಪ ಸಮಯ ತೆಗೆದುಕೊಳ್ಳಿ.\nಇದು ನಿಮ್ಮ ಬ್ಯಾಕಪ್ ಮತ್ತು ವ್ಯಾಲೆಟ್ ಮರುಸ್ಥಾಪಿಸಲು ಬಳಸಬಹುದು.", + "text_lnd": "ದಯವಿಟ್ಟು ಈ ವ್ಯಾಲೆಟ್ ಬ್ಯಾಕಪ್ ಉಳಿಸಿ. ನಷ್ಟವಾದರೆ ವ್ಯಾಲೆಟ್ ಮರುಸ್ಥಾಪಿಸಲು ಇದು ಸಹಾಯ ಮಾಡುತ್ತದೆ.", + "title": "ನಿಮ್ಮ ವ್ಯಾಲೆಟ್ ರಚಿಸಲಾಗಿದೆ." + }, + "receive": { + "details_create": "ರಚಿಸಿ", + "details_label": "ವರ್ಣನೆ", + "header": "ಸ್ವೀಕರಿಸಿ", + "reset": "ಮರುಹೊಂದಿಸಿ", + "qrcode_for_the_address": "ವಿಳಾಸಕ್ಕಾಗಿ QR ಕೋಡ್", + "address_not_found": "ಸ್ವೀಕರಿಸುವ ವಿಳಾಸವನ್ನು ಸೃಷ್ಟಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ.", + "bip47_explanation": "Payment codes ಗಳು ನಿಮ್ಮ ವ್ಯಾಲೆಟ್ ವಿಳಾಸಗಳನ್ನು ಬಹಿರಂಗಪಡಿಸುವುದನ್ನು ತಪ್ಪಿಸುವ ಸಾರ್ವತ್ರಿಕ ವಿಳಾಸವಾಗಿವೆ. ಎಲ್ಲಾ ಸೇವೆಗಳು ಅವುಗಳನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ.", + "details_setAmount": "ಮೊತ್ತದೊಂದಿಗೆ ಸ್ವೀಕರಿಸಿ", + "details_share": "ಹಂಚಿಕೊಳ್ಳಿ...", + "maxSats": "ಗರಿಷ್ಠ ಮೊತ್ತ {max} sats", + "maxSatsFull": "ಗರಿಷ್ಠ ಮೊತ್ತ {max} sats ಅಥವಾ {currency}", + "minSats": "ಕನಿಷ್ಠ ಮೊತ್ತ {min} sats", + "minSatsFull": "ಕನಿಷ್ಠ ಮೊತ್ತ {min} sats ಅಥವಾ {currency}" + }, + "send": { + "broadcastButton": "ಪ್ರಸಾರ ಮಾಡಿ", + "broadcastError": "ದೋಷ", + "broadcastPending": "ಬಾಕಿ ಇದೆ", + "broadcastSuccess": "ಯಶಸ್ಸು", + "confirm_header": "ದೃಢೀಕರಿಸಿ", + "create_amount": "ಮೊತ್ತ", + "create_broadcast": "ಪ್ರಸಾರ ಮಾಡಿ", + "create_fee": "ಶುಲ್ಕ", + "create_memo": "ಮೆಮೊ", + "create_satoshi_per_vbyte": "vByte ಗೆ Satoshi", + "create_to": "ಗೆ", + "details_address": "ವಿಳಾಸ", + "details_address_field_is_not_valid": "ವಿಳಾಸ ಮಾನ್ಯವಾಗಿಲ್ಲ.", + "details_next": "ಮುಂದೆ", + "details_scan": "ಸ್ಕ್ಯಾನ್", + "dynamic_next": "ಮುಂದೆ", + "dynamic_prev": "ಹಿಂದಿನ", + "dynamic_start": "ಪ್ರಾರಂಭಿಸಿ", + "dynamic_stop": "ನಿಲ್ಲಿಸಿ", + "fee_custom": "ಕಸ್ಟಮ್", + "fee_fast": "ವೇಗ", + "fee_medium": "ಮಧ್ಯಮ", + "fee_satvbyte": "sat/vByte ನಲ್ಲಿ", + "fee_slow": "ನಿಧಾನ", + "header": "ಕಳುಹಿಸಿ", + "input_done": "ಮುಗಿಯಿತು", + "input_paste": "ಅಂಟಿಸಿ", + "open_settings": "ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಿರಿ", + "psbt_clipboard": "ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ನಕಲಿಸಿ", + "broadcastNone": "ವಹಿವಾಟು Hex ಸೇರಿಸಿ", + "cant_find_bip47_notification": "ಮೊದಲು ಈ Payment Code ಅನ್ನು ಸಂಪರ್ಕಗಳಿಗೆ ಸೇರಿಸಿ", + "cant_send_to_bip47": "ಈ ವ್ಯಾಲೆಟ್ BIP47 payment codes ಗಳಿಗೆ ಕಳುಹಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ", + "cant_send_to_silentpayment_adress": "ಈ ವ್ಯಾಲೆಟ್ SilentPayment ವಿಳಾಸಗಳಿಗೆ ಕಳುಹಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ", + "confirm_sendNow": "ಈಗ ಕಳುಹಿಸಿ", + "create_copy": "ನಕಲಿಸಿ ಮತ್ತು ನಂತರ ಬ್ರಾಡ್‌ಕಾಸ್ಟ್ ಮಾಡಿ", + "create_details": "ವಿವರಗಳು", + "create_this_is_hex": "ಇದು ನಿಮ್ಮ ವಹಿವಾಟಿನ hex—ಸಹಿ ಹಾಕಲಾಗಿದೆ ಮತ್ತು ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಬ್ರಾಡ್‌ಕಾಸ್ಟ್ ಮಾಡಲು ಸಿದ್ಧವಾಗಿದೆ.", + "create_tx_size": "ವಹಿವಾಟು ಗಾತ್ರ", + "create_verify": "coinb.in ನಲ್ಲಿ ಪರಿಶೀಲಿಸಿ", + "details_add_rec_add": "ಸ್ವೀಕರಿಸುವವರನ್ನು ಸೇರಿಸಿ", + "details_add_rec_rem": "ಸ್ವೀಕರಿಸುವವರನ್ನು ತೆಗೆದುಹಾಕಿ", + "details_add_rec_rem_all": "ಎಲ್ಲಾ ಸ್ವೀಕರಿಸುವವರನ್ನು ತೆಗೆದುಹಾಕಿ", + "details_add_recc_rem_all_alert_description": "ಎಲ್ಲಾ ಸ್ವೀಕರಿಸುವವರನ್ನು ತೆಗೆದುಹಾಕಲು ನೀವು ಖಚಿತವಾಗಿರುವಿರಾ?", + "details_adv_fee_bump": "Fee Bump ಅನುಮತಿಸಿ", + "details_adv_full": "ಪೂರ್ಣ ಬಾಕಿಯನ್ನು ಬಳಸಿ", + "details_adv_full_sure": "ಈ ವಹಿವಾಟಿಗೆ ನಿಮ್ಮ ವ್ಯಾಲೆಟ್‌ನ ಪೂರ್ಣ ಬಾಕಿಯನ್ನು ಬಳಸಲು ನೀವು ಖಚಿತವಾಗಿರುವಿರಾ?", + "details_adv_full_sure_frozen": "ಈ ವಹಿವಾಟಿಗೆ ನಿಮ್ಮ ವ್ಯಾಲೆಟ್‌ನ ಪೂರ್ಣ ಬಾಕಿಯನ್ನು ಬಳಸಲು ನೀವು ಖಚಿತವಾಗಿರುವಿರಾ? ಫ್ರೀಜ್ ಮಾಡಿದ coins ಅನ್ನು ಹೊರತುಪಡಿಸಲಾಗಿದೆ ಎಂಬುದನ್ನು ಗಮನಿಸಿ.", + "details_adv_import": "ವಹಿವಾಟು ಆಮದು ಮಾಡಿ", + "details_adv_import_qr": "ವಹಿವಾಟು ಆಮದು ಮಾಡಿ (QR)", + "details_amount_field_is_less_than_minimum_amount_sat": "ನಿರ್ದಿಷ್ಟಪಡಿಸಿದ ಮೊತ್ತ ತುಂಬಾ ಚಿಕ್ಕದಾಗಿದೆ. 500 sats ಗಿಂತ ಹೆಚ್ಚಿನ ಮೊತ್ತವನ್ನು ನಮೂದಿಸಿ.", + "details_amount_field_is_not_valid": "ಮೊತ್ತ ಮಾನ್ಯವಾಗಿಲ್ಲ.", + "details_create": "ಇನ್‌ವಾಯ್ಸ್ ರಚಿಸಿ", + "details_error_decode": "Bitcoin ವಿಳಾಸವನ್ನು ಡಿಕೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "details_fee_field_is_not_valid": "ಶುಲ್ಕ ಮಾನ್ಯವಾಗಿಲ್ಲ.", + "details_frozen": "{amount} BTC ಫ್ರೀಜ್ ಆಗಿದೆ.", + "details_insert_contact": "ಸಂಪರ್ಕವನ್ನು ಸೇರಿಸಿ", + "details_no_signed_tx": "ಆಯ್ಕೆ ಮಾಡಿದ ಫೈಲ್‌ನಲ್ಲಿ ಆಮದು ಮಾಡಬಹುದಾದ ವಹಿವಾಟು ಇಲ್ಲ.", + "details_note_placeholder": "ನಿಮಗಾಗಿ ಟಿಪ್ಪಣಿ", + "details_recipient_title": "#{total} ರಲ್ಲಿ ಸ್ವೀಕರಿಸುವವರು #{number}", + "details_recipients_title": "ಸ್ವೀಕರಿಸುವವರು", + "details_scan_error": "ಸ್ಕ್ಯಾನ್ ದೋಷ", + "details_scan_hint": "ಗಮ್ಯಸ್ಥಾನವನ್ನು ಸ್ಕ್ಯಾನ್ ಅಥವಾ ಆಮದು ಮಾಡಲು ಡಬಲ್ ಟ್ಯಾಪ್ ಮಾಡಿ", + "details_total_exceeds_balance": "ಕಳುಹಿಸುವ ಮೊತ್ತವು ಲಭ್ಯವಿರುವ ಬಾಕಿಯನ್ನು ಮೀರಿದೆ.", + "details_total_exceeds_balance_frozen": "ಕಳುಹಿಸುವ ಮೊತ್ತವು ಲಭ್ಯವಿರುವ ಬಾಕಿಯನ್ನು ಮೀರಿದೆ. ಫ್ರೀಜ್ ಮಾಡಿದ coins ಅನ್ನು ಹೊರತುಪಡಿಸಲಾಗಿದೆ ಎಂಬುದನ್ನು ಗಮನಿಸಿ.", + "details_unrecognized_file_format": "ಗುರುತಿಸಲಾಗದ ಫೈಲ್ ಫಾರ್ಮ್ಯಾಟ್", + "details_wallet_before_tx": "ವಹಿವಾಟು ರಚಿಸುವ ಮೊದಲು, ನೀವು Bitcoin ವ್ಯಾಲೆಟ್ ಸೇರಿಸಬೇಕು.", + "dynamic_init": "ಆರಂಭಿಸಲಾಗುತ್ತಿದೆ", + "fee_10m": "10ನಿ", + "fee_1d": "1ದಿ", + "fee_3h": "3ಗಂ", + "fee_replace_minvb": "ನೀವು ಪಾವತಿಸಲು ಬಯಸುವ ಒಟ್ಟು ಶುಲ್ಕ ದರ (satoshi per vByte) {min} sat/vByte ಗಿಂತ ಹೆಚ್ಚಾಗಿರಬೇಕು.", + "file_saved_at_path": "ಫೈಲ್ ({filePath}) ಉಳಿಸಲಾಗಿದೆ.", + "input_clear": "ತೆರವುಗೊಳಿಸಿ", + "input_total": "ಒಟ್ಟು:", + "insert_custom_fee": "ಶುಲ್ಕ ಸೇರಿಸಿ", + "invalid_psbt": "ಅಮಾನ್ಯ PSBT ಒದಗಿಸಲಾಗಿದೆ.", + "no_tx_signing_in_progress": "ಯಾವುದೇ ವಹಿವಾಟು ಸಹಿ ಪ್ರಗತಿಯಲ್ಲಿಲ್ಲ.", + "outdated_rate": "ದರ ಕೊನೆಯ ಬಾರಿ ನವೀಕರಿಸಿದ್ದು: {date}", + "permission_camera_message": "ನಿಮ್ಮ ಕ್ಯಾಮೆರಾ ಬಳಸಲು ನಮಗೆ ಅನುಮತಿ ಬೇಕು.", + "permission_storage_denied_message": "BlueWallet ಈ ಫೈಲ್ ಅನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ನಿಮ್ಮ ಸಾಧನ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಿರಿ ಮತ್ತು ಸಂಗ್ರಹಣೆ ಅನುಮತಿಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ.", + "permission_storage_title": "ಸಂಗ್ರಹಣೆ ಪ್ರವೇಶ ಅನುಮತಿ", + "please_complete_recipient_details": "ಹೊಸ ಸ್ವೀಕರಿಸುವವರನ್ನು ಸೇರಿಸುವ ಮೊದಲು ಸ್ವೀಕರಿಸುವವರು #{number} ರ ವಿವರಗಳನ್ನು ಪೂರ್ಣಗೊಳಿಸಿ.", + "please_complete_recipient_title": "ಅಪೂರ್ಣ ಸ್ವೀಕರಿಸುವವರು", + "problem_with_psbt": "PSBT ನಲ್ಲಿ ಸಮಸ್ಯೆ", + "provided_address_is_invoice": "ಈ ವಿಳಾಸವು Lightning ಇನ್‌ವಾಯ್ಸ್‌ಗಾಗಿ ಕಾಣಿಸುತ್ತದೆ. ಈ ಇನ್‌ವಾಯ್ಸ್‌ಗಾಗಿ ಪಾವತಿ ಮಾಡಲು ದಯವಿಟ್ಟು ನಿಮ್ಮ Lightning ವ್ಯಾಲೆಟ್‌ಗೆ ಹೋಗಿ.", + "psbt_sign": "ವಹಿವಾಟಿಗೆ ಸಹಿ ಹಾಕಿ", + "psbt_this_is_psbt": "ಇದು Partially Signed Bitcoin Transaction (PSBT) ಆಗಿದೆ. ದಯವಿಟ್ಟು ನಿಮ್ಮ ಹಾರ್ಡ್‌ವೇರ್ ವ್ಯಾಲೆಟ್‌ನೊಂದಿಗೆ ಸಹಿ ಹಾಕುವುದನ್ನು ಪೂರ್ಣಗೊಳಿಸಿ.", + "psbt_tx_export": "ಫೈಲ್‌ಗೆ ರಫ್ತು ಮಾಡಿ", + "psbt_tx_open": "ಸಹಿ ಮಾಡಿದ ವಹಿವಾಟು ತೆರೆಯಿರಿ", + "psbt_tx_scan": "ಸಹಿ ಮಾಡಿದ ವಹಿವಾಟು ಸ್ಕ್ಯಾನ್ ಮಾಡಿ", + "qr_error_no_qrcode": "ಆಯ್ಕೆ ಮಾಡಿದ ಚಿತ್ರದಲ್ಲಿ ನಮಗೆ ಮಾನ್ಯ QR ಕೋಡ್ ಸಿಗಲಿಲ್ಲ. ಚಿತ್ರವು ಪಠ್ಯ ಅಥವಾ ಬಟನ್‌ಗಳಂತಹ ಹೆಚ್ಚುವರಿ ವಿಷಯವಿಲ್ಲದೆ ಕೇವಲ QR ಕೋಡ್ ಹೊಂದಿದೆ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ.", + "reset_amount": "ಮೊತ್ತ ಮರುಹೊಂದಿಸಿ", + "reset_amount_confirm": "ಮೊತ್ತವನ್ನು ಮರುಹೊಂದಿಸಲು ಬಯಸುತ್ತೀರಾ?", + "success_done": "ಮುಗಿಯಿತು", + "txSaved": "ವಹಿವಾಟು ಫೈಲ್ ({filePath}) ಉಳಿಸಲಾಗಿದೆ." }, "settings": { - "electrum_clear_alert_cancel": "ರದ್ದುಮಾಡಿ", - "save": "ಉಳಿಸಿ" + "biometrics": "ಬಯೋಮೆಟ್ರಿಕ್ಸ್", + "currency": "ಕರೆನ್ಸಿ", + "electrum_connected": "ಸಂಪರ್ಕಗೊಂಡಿದೆ", + "electrum_connected_not": "ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ", + "electrum_settings_server": "Electrum ಸರ್ವರ್", + "electrum_status": "ಸ್ಥಿತಿ", + "electrum_history": "ಇತಿಹಾಸ", + "block_explorer": "ಬ್ಲಾಕ್ ಎಕ್ಸ್‌ಪ್ಲೋರರ್", + "encrypt_title": "ಭದ್ರತೆ", + "encrypt_tstorage": "ಸಂಗ್ರಹಣೆ", + "header": "ಸೆಟ್ಟಿಂಗ್‌ಗಳು", + "language": "ಭಾಷೆ", + "last_updated": "ಕೊನೆಯದಾಗಿ ನವೀಕರಿಸಲಾಗಿದೆ", + "license": "ಪರವಾನಗಿ", + "network": "ನೆಟ್‌ವರ್ಕ್", + "network_electrum": "Electrum ಸರ್ವರ್", + "notifications": "ಅಧಿಸೂಚನೆಗಳು", + "password": "ಪಾಸ್ವರ್ಡ್", + "privacy": "ಗೌಪ್ಯತೆ", + "rate": "ದರ", + "save": "ಉಳಿಸಿ", + "saved": "ಉಳಿಸಲಾಗಿದೆ", + "about": "ಬಗ್ಗೆ", + "about_awesome": "ಅದ್ಭುತದೊಂದಿಗೆ ನಿರ್ಮಿಸಲಾಗಿದೆ", + "about_backup": "ಯಾವಾಗಲೂ ನಿಮ್ಮ ಕೀಗಳನ್ನು ಬ್ಯಾಕಪ್ ಮಾಡಿ!", + "about_free": "BlueWallet ಒಂದು ಉಚಿತ ಮತ್ತು ಓಪನ್-ಸೋರ್ಸ್ ಯೋಜನೆ. Bitcoin ಬಳಕೆದಾರರಿಂದ ರಚಿಸಲ್ಪಟ್ಟಿದೆ.", + "about_license": "MIT ಪರವಾನಗಿ", + "about_release_notes": "ಬಿಡುಗಡೆ ಟಿಪ್ಪಣಿಗಳು", + "about_review": "ನಮಗೆ ರಿವ್ಯೂ ಬರೆಯಿರಿ", + "about_selftest": "ಸ್ವಯಂ ಪರೀಕ್ಷೆ ರನ್ ಮಾಡಿ", + "about_selftest_electrum_disabled": "Electrum Offline ಮೋಡ್‌ನೊಂದಿಗೆ ಸ್ವಯಂ ಪರೀಕ್ಷೆ ಲಭ್ಯವಿಲ್ಲ. ಆಫ್‌ಲೈನ್ ಮೋಡ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ ಮತ್ತು ಪುನಃ ಪ್ರಯತ್ನಿಸಿ.", + "about_selftest_ok": "ಎಲ್ಲಾ ಆಂತರಿಕ ಪರೀಕ್ಷೆಗಳು ಯಶಸ್ವಿಯಾಗಿವೆ. ವ್ಯಾಲೆಟ್ ಚೆನ್ನಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತಿದೆ.", + "about_sm_github": "GitHub", + "about_sm_telegram": "Telegram ಚಾನೆಲ್", + "biom_10times": "ನೀವು ನಿಮ್ಮ ಪಾಸ್ವರ್ಡ್ ಅನ್ನು 10 ಬಾರಿ ನಮೂದಿಸಲು ಪ್ರಯತ್ನಿಸಿದ್ದೀರಿ. ನಿಮ್ಮ ಸಂಗ್ರಹಣೆಯನ್ನು ಮರುಹೊಂದಿಸಲು ಬಯಸುತ್ತೀರಾ? ಇದು ಎಲ್ಲಾ ವ್ಯಾಲೆಟ್‌ಗಳನ್ನು ತೆಗೆದುಹಾಕುತ್ತದೆ ಮತ್ತು ನಿಮ್ಮ ಸಂಗ್ರಹಣೆಯನ್ನು ಡೀಕ್ರಿಪ್ಟ್ ಮಾಡುತ್ತದೆ.", + "biom_conf_identity": "ದಯವಿಟ್ಟು ನಿಮ್ಮ ಗುರುತನ್ನು ದೃಢೀಕರಿಸಿ.", + "biom_no_passcode": "ನಿಮ್ಮ ಸಾಧನವು passcode ಅಥವಾ ಬಯೋಮೆಟ್ರಿಕ್ಸ್ ಸಕ್ರಿಯಗೊಂಡಿಲ್ಲ. ಮುಂದುವರಿಯಲು, Settings ಆ್ಯಪ್‌ನಲ್ಲಿ passcode ಅಥವಾ ಬಯೋಮೆಟ್ರಿಕ್ ಅನ್ನು ಕಾನ್ಫಿಗರ್ ಮಾಡಿ.", + "biom_remove_decrypt": "ನಿಮ್ಮ ಎಲ್ಲಾ ವ್ಯಾಲೆಟ್‌ಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗುತ್ತದೆ ಮತ್ತು ನಿಮ್ಮ ಸಂಗ್ರಹಣೆಯನ್ನು ಡೀಕ್ರಿಪ್ಟ್ ಮಾಡಲಾಗುತ್ತದೆ. ಮುಂದುವರಿಯಲು ನೀವು ಖಚಿತವಾಗಿರುವಿರಾ?", + "biometrics_fail": "{type} ಸಕ್ರಿಯವಾಗಿಲ್ಲದಿದ್ದರೆ ಅಥವಾ ಅನ್‌ಲಾಕ್ ಮಾಡಲು ವಿಫಲವಾದರೆ, ನೀವು ಪರ್ಯಾಯವಾಗಿ ನಿಮ್ಮ ಸಾಧನ passcode ಅನ್ನು ಬಳಸಬಹುದು.", + "biometrics_no_longer_available": "ನಿಮ್ಮ ಸಾಧನ ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಬದಲಾಗಿವೆ ಮತ್ತು ಆ್ಯಪ್‌ನಲ್ಲಿ ಆಯ್ಕೆ ಮಾಡಿದ ಭದ್ರತಾ ಸೆಟ್ಟಿಂಗ್‌ಗಳಿಗೆ ಹೊಂದಿಕೆಯಾಗುತ್ತಿಲ್ಲ. ದಯವಿಟ್ಟು ಬಯೋಮೆಟ್ರಿಕ್ಸ್ ಅಥವಾ passcode ಅನ್ನು ಮರು-ಸಕ್ರಿಯಗೊಳಿಸಿ, ನಂತರ ಬದಲಾವಣೆಗಳನ್ನು ಅನ್ವಯಿಸಲು ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಿ.", + "block_explorer_error_saving_custom": "ಆದ್ಯತೆಯ block explorer ಉಳಿಸುವಲ್ಲಿ ದೋಷ", + "block_explorer_invalid_custom_url": "ಒದಗಿಸಿದ URL ಅಮಾನ್ಯವಾಗಿದೆ. http:// ಅಥವಾ https:// ನಿಂದ ಆರಂಭವಾಗುವ ಮಾನ್ಯ URL ನಮೂದಿಸಿ.", + "block_explorer_preferred": "ಆದ್ಯತೆಯ block explorer ಬಳಸಿ", + "currency_fetch_error": "ಆಯ್ಕೆ ಮಾಡಿದ ಕರೆನ್ಸಿಗೆ ದರವನ್ನು ಪಡೆಯುವಾಗ ದೋಷ ಸಂಭವಿಸಿದೆ.", + "currency_source": "ದರವನ್ನು ಪಡೆಯಲಾಗಿದೆ ಇಲ್ಲಿಂದ", + "default_title": "ಆರಂಭದಲ್ಲಿ", + "donate": "ದಾನ ಮಾಡಿ", + "donate_description": "Blue ಅನ್ನು ಉಚಿತವಾಗಿ ಇರಿಸಲು ಸಹಾಯ ಮಾಡಿ!", + "electrum_error_connect": "ಒದಗಿಸಿದ Electrum ಸರ್ವರ್‌ಗೆ ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "electrum_error_connect_tor": "ಒದಗಿಸಿದ Electrum ಸರ್ವರ್‌ಗೆ ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. Orbot ಆ್ಯಪ್ ಸಂಪರ್ಕಿತವಾಗಿದೆಯೇ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಂಡು ಪುನಃ ಪ್ರಯತ್ನಿಸಿ.", + "electrum_host": "ಉದಾ., {example}", + "electrum_offline_description": "ಸಕ್ರಿಯಗೊಳಿಸಿದಾಗ, ನಿಮ್ಮ Bitcoin ವ್ಯಾಲೆಟ್‌ಗಳು ಬಾಕಿಗಳನ್ನು ಅಥವಾ ವಹಿವಾಟುಗಳನ್ನು ಪಡೆಯಲು ಪ್ರಯತ್ನಿಸುವುದಿಲ್ಲ.", + "electrum_offline_mode": "ಆಫ್‌ಲೈನ್ ಮೋಡ್", + "electrum_port": "Port, ಸಾಮಾನ್ಯವಾಗಿ {example}", + "electrum_preferred_server": "ಆದ್ಯತೆಯ ಸರ್ವರ್", + "electrum_preferred_server_description": "ಎಲ್ಲಾ Bitcoin ಚಟುವಟಿಕೆಗಳಿಗೆ ನಿಮ್ಮ ವ್ಯಾಲೆಟ್ ಬಳಸಲು ನೀವು ಬಯಸುವ ಸರ್ವರ್ ನಮೂದಿಸಿ. ಒಮ್ಮೆ ಹೊಂದಿಸಿದ ನಂತರ, ಬಾಕಿಗಳನ್ನು ಪರಿಶೀಲಿಸಲು, ವಹಿವಾಟುಗಳನ್ನು ಕಳುಹಿಸಲು ಮತ್ತು ನೆಟ್‌ವರ್ಕ್ ಡೇಟಾವನ್ನು ಪಡೆಯಲು ನಿಮ್ಮ ವ್ಯಾಲೆಟ್ ವಿಶೇಷವಾಗಿ ಈ ಸರ್ವರ್ ಅನ್ನು ಬಳಸುತ್ತದೆ. ಹೊಂದಿಸುವ ಮೊದಲು ಈ ಸರ್ವರ್ ಅನ್ನು ನಂಬುತ್ತೀರಿ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ.", + "electrum_reset": "ಡೀಫಾಲ್ಟ್‌ಗೆ ಮರುಹೊಂದಿಸಿ", + "electrum_reset_to_default": "ಇದು BlueWallet ಗೆ ಸರ್ವರ್ ಪಟ್ಟಿಯಿಂದ ಯಾದೃಚ್ಛಿಕವಾಗಿ ಸರ್ವರ್ ಆಯ್ಕೆ ಮಾಡಲು ಅವಕಾಶ ನೀಡುತ್ತದೆ.", + "electrum_reset_to_default_and_clear_history": "ಡೀಫಾಲ್ಟ್‌ಗೆ ಮರುಹೊಂದಿಸಿ ಮತ್ತು ಇತಿಹಾಸವನ್ನು ತೆರವುಗೊಳಿಸಿ", + "electrum_saved": "ನಿಮ್ಮ ಬದಲಾವಣೆಗಳು ಯಶಸ್ವಿಯಾಗಿ ಉಳಿಸಲಾಗಿದೆ. ಬದಲಾವಣೆಗಳು ಕಾರ್ಯಗತಗೊಳ್ಳಲು BlueWallet ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಬೇಕಾಗಬಹುದು.", + "electrum_suggested_description": "ಆದ್ಯತೆಯ ಸರ್ವರ್ ಹೊಂದಿಸದಿದ್ದಾಗ, ಯಾದೃಚ್ಛಿಕವಾಗಿ ಬಳಕೆಗೆ ಸೂಚಿಸಲಾದ ಸರ್ವರ್ ಆಯ್ಕೆ ಮಾಡಲಾಗುತ್ತದೆ.", + "electrum_unable_to_connect": "{server} ಗೆ ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ.", + "encrypt_decrypt": "ಸಂಗ್ರಹಣೆಯನ್ನು ಡೀಕ್ರಿಪ್ಟ್ ಮಾಡಿ", + "encrypt_decrypt_q": "ನಿಮ್ಮ ಸಂಗ್ರಹಣೆಯನ್ನು ಡೀಕ್ರಿಪ್ಟ್ ಮಾಡಲು ನೀವು ಖಚಿತವಾಗಿರುವಿರಾ? ಇದು ಪಾಸ್ವರ್ಡ್ ಇಲ್ಲದೆ ನಿಮ್ಮ ವ್ಯಾಲೆಟ್‌ಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸುತ್ತದೆ.", + "encrypt_enc_and_pass": "ಪಾಸ್ವರ್ಡ್ ಸಂರಕ್ಷಿತ", + "encrypt_storage_explanation_description_line1": "Storage Encryption ಸಕ್ರಿಯಗೊಳಿಸುವುದು ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ನಿಮ್ಮ ಡೇಟಾವನ್ನು ಸಂಗ್ರಹಿಸುವ ವಿಧಾನವನ್ನು ಸುರಕ್ಷಿತಗೊಳಿಸುವ ಮೂಲಕ ಆ್ಯಪ್‌ಗೆ ಹೆಚ್ಚುವರಿ ರಕ್ಷಣೆಯ ಪದರವನ್ನು ಸೇರಿಸುತ್ತದೆ. ಇದು ಯಾರಾದರೂ ಅನುಮತಿಯಿಲ್ಲದೆ ನಿಮ್ಮ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸುವುದನ್ನು ಕಷ್ಟಕರವಾಗಿಸುತ್ತದೆ.", + "encrypt_storage_explanation_description_line2": "ಆದರೆ, ಈ ಎನ್‌ಕ್ರಿಪ್ಶನ್ ಸಾಧನದ keychain ನಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ನಿಮ್ಮ ವ್ಯಾಲೆಟ್‌ಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಮಾತ್ರ ರಕ್ಷಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ತಿಳಿದುಕೊಳ್ಳುವುದು ಮುಖ್ಯ. ಇದು ವ್ಯಾಲೆಟ್‌ಗಳ ಮೇಲೆ ಪಾಸ್ವರ್ಡ್ ಅಥವಾ ಯಾವುದೇ ಹೆಚ್ಚುವರಿ ರಕ್ಷಣೆಯನ್ನು ಹಾಕುವುದಿಲ್ಲ.", + "encrypt_storage_explanation_headline": "ಸಂಗ್ರಹಣೆ ಎನ್‌ಕ್ರಿಪ್ಶನ್ ಸಕ್ರಿಯಗೊಳಿಸಿ", + "encrypt_use": "{type} ಬಳಸಿ", + "encrypt_use_expl": "ವಹಿವಾಟು ಮಾಡುವ ಮೊದಲು, ಅನ್‌ಲಾಕ್ ಮಾಡುವ ಮೊದಲು, ರಫ್ತು ಮಾಡುವ ಮೊದಲು ಅಥವಾ ವ್ಯಾಲೆಟ್ ಅಳಿಸುವ ಮೊದಲು ನಿಮ್ಮ ಗುರುತನ್ನು ದೃಢೀಕರಿಸಲು {type} ಬಳಸಲಾಗುತ್ತದೆ.", + "encrypted_feature_disabled": "ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಸಂಗ್ರಹಣೆ ಸಕ್ರಿಯವಾಗಿರುವಾಗ ಈ ವೈಶಿಷ್ಟ್ಯವನ್ನು ಬಳಸಲಾಗುವುದಿಲ್ಲ.", + "general": "ಸಾಮಾನ್ಯ", + "general_continuity": "ನಿರಂತರತೆ", + "general_continuity_e": "ಸಕ್ರಿಯಗೊಳಿಸಿದಾಗ, ನಿಮ್ಮ ಇತರ Apple iCloud ಸಂಪರ್ಕಿತ ಸಾಧನಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಆಯ್ಕೆ ಮಾಡಿದ ವ್ಯಾಲೆಟ್‌ಗಳು ಮತ್ತು ವಹಿವಾಟುಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ.", + "groundcontrol_explanation": "GroundControl ಎಂಬುದು Bitcoin ವ್ಯಾಲೆಟ್‌ಗಳಿಗಾಗಿ ಉಚಿತ, ಓಪನ್-ಸೋರ್ಸ್ push notifications ಸರ್ವರ್. BlueWallet ನ ಮೂಲಸೌಕರ್ಯವನ್ನು ಅವಲಂಬಿಸದಿರಲು ನಿಮ್ಮ ಸ್ವಂತ GroundControl ಸರ್ವರ್ ಅನ್ನು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ ಮತ್ತು ಅದರ URL ಅನ್ನು ಇಲ್ಲಿ ಹಾಕಬಹುದು. GroundControl ನ ಡೀಫಾಲ್ಟ್ ಸರ್ವರ್ ಬಳಸಲು ಖಾಲಿ ಬಿಡಿ.", + "i_understand": "ನಾನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳುತ್ತೇನೆ", + "language_isRTL": "ಭಾಷೆಯ ದಿಕ್ಕನ್ನು ಬದಲಾಯಿಸಲು BlueWallet ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಬೇಕು.", + "lightning_error_lndhub_uri": "ಅಮಾನ್ಯ LNDhub URI", + "lightning_error_lndhub_uri_tor": "ಅಮಾನ್ಯ LNDhub URI. Orbot ಆ್ಯಪ್ ಸಂಪರ್ಕಿತವಾಗಿದೆಯೇ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಂಡು ಪುನಃ ಪ್ರಯತ್ನಿಸಿ.", + "lightning_saved": "ನಿಮ್ಮ ಬದಲಾವಣೆಗಳು ಯಶಸ್ವಿಯಾಗಿ ಉಳಿಸಲಾಗಿದೆ.", + "lightning_settings": "Lightning ಸೆಟ್ಟಿಂಗ್‌ಗಳು", + "lightning_settings_explain": "ನಿಮ್ಮ ಸ್ವಂತ LND ನೋಡ್‌ಗೆ ಸಂಪರ್ಕಿಸಲು, LNDhub ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ ಮತ್ತು ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಇಲ್ಲಿ ಅದರ URL ಅನ್ನು ಹಾಕಿ. ಬದಲಾವಣೆಗಳನ್ನು ಉಳಿಸಿದ ನಂತರ ರಚಿಸಲಾದ ವ್ಯಾಲೆಟ್‌ಗಳು ಮಾತ್ರ ನಿರ್ದಿಷ್ಟಪಡಿಸಿದ LNDhub ಗೆ ಸಂಪರ್ಕಿಸುತ್ತವೆ ಎಂಬುದನ್ನು ಗಮನಿಸಿ.", + "lndhub_github": "GitHub ರೆಪೋಸಿಟರಿ", + "lndhub_uri": "ಉದಾ., {example}", + "network_broadcast": "ವಹಿವಾಟು ಬ್ರಾಡ್‌ಕಾಸ್ಟ್", + "not_a_valid_uri": "ಅಮಾನ್ಯ URI", + "open_link_in_explorer": "explorer ನಲ್ಲಿ ಲಿಂಕ್ ತೆರೆಯಿರಿ", + "password_explain": "ನಿಮ್ಮ ಸಂಗ್ರಹಣೆಯನ್ನು ಅನ್‌ಲಾಕ್ ಮಾಡಲು ಬಳಸುವ ಪಾಸ್ವರ್ಡ್ ನಮೂದಿಸಿ.", + "performance_score": "ಕಾರ್ಯಕ್ಷಮತೆ ಸ್ಕೋರ್: {num}", + "plausible_deniability": "ಸಂಭಾವ್ಯ ನಿರಾಕರಣೆ", + "privacy_clipboard_explanation": "ನಿಮ್ಮ ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ನಲ್ಲಿ ವಿಳಾಸ ಅಥವಾ ಇನ್‌ವಾಯ್ಸ್ ಸಿಕ್ಕರೆ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಒದಗಿಸಿ.", + "privacy_do_not_track": "ಅನಾಲಿಟಿಕ್ಸ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ", + "privacy_do_not_track_explanation": "ಕಾರ್ಯಕ್ಷಮತೆ ಮತ್ತು ವಿಶ್ವಸನೀಯತೆ ಮಾಹಿತಿಯನ್ನು ವಿಶ್ಲೇಷಣೆಗಾಗಿ ಸಲ್ಲಿಸಲಾಗುವುದಿಲ್ಲ.", + "privacy_quickactions": "ವ್ಯಾಲೆಟ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು", + "privacy_quickactions_explanation": "ನಿಮ್ಮ ವ್ಯಾಲೆಟ್‌ನ ಬಾಕಿಯನ್ನು ತ್ವರಿತವಾಗಿ ವೀಕ್ಷಿಸಲು BlueWallet ಆ್ಯಪ್ ಐಕಾನ್ ಅನ್ನು ಸ್ಪರ್ಶಿಸಿ ಹಿಡಿದುಕೊಳ್ಳಿ.", + "privacy_read_clipboard": "ಕ್ಲಿಪ್‌ಬೋರ್ಡ್ ಓದಿ", + "privacy_system_settings": "ಸಿಸ್ಟಮ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", + "privacy_temporary_screenshots": "ಸ್ಕ್ರೀನ್ ಕ್ಯಾಪ್ಚರ್ ಅನುಮತಿಸಿ", + "privacy_temporary_screenshots_instructions": "ಸ್ಕ್ರೀನ್ ಕ್ಯಾಪ್ಚರ್ ರಕ್ಷಣೆಯನ್ನು ತಾತ್ಕಾಲಿಕವಾಗಿ ಆಫ್ ಮಾಡಲಾಗುತ್ತದೆ, ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ಗಳು ಮತ್ತು ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗುತ್ತದೆ. BlueWallet ಮುಚ್ಚಿ ಮತ್ತೆ ತೆರೆದಾಗ ರಕ್ಷಣೆ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಮರು-ಸಕ್ರಿಯಗೊಳ್ಳುತ್ತದೆ.", + "push_notifications_explanation": "ಸೂಚನೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುವ ಮೂಲಕ, ಸೂಚನೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿದ ನಂತರ ಮಾಡಿದ ಎಲ್ಲಾ ವ್ಯಾಲೆಟ್‌ಗಳು ಮತ್ತು ವಹಿವಾಟುಗಳಿಗೆ ವ್ಯಾಲೆಟ್ ವಿಳಾಸಗಳು ಮತ್ತು ವಹಿವಾಟು ID ಗಳೊಂದಿಗೆ ನಿಮ್ಮ ಸಾಧನ ಟೋಕನ್ ಸರ್ವರ್‌ಗೆ ಕಳುಹಿಸಲಾಗುತ್ತದೆ. ಸೂಚನೆಗಳನ್ನು ಕಳುಹಿಸಲು ಸಾಧನ ಟೋಕನ್ ಅನ್ನು ಬಳಸಲಾಗುತ್ತದೆ, ಮತ್ತು ವ್ಯಾಲೆಟ್ ಮಾಹಿತಿಯು ಒಳಬರುವ Bitcoin ಅಥವಾ ವಹಿವಾಟು ದೃಢೀಕರಣಗಳ ಬಗ್ಗೆ ನಿಮಗೆ ಸೂಚಿಸಲು ಅವಕಾಶ ನೀಡುತ್ತದೆ.\n\nಸೂಚನೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿದ ನಂತರದ ಮಾಹಿತಿಯನ್ನು ಮಾತ್ರ ರವಾನಿಸಲಾಗುತ್ತದೆ—ಮೊದಲಿನ ಯಾವುದನ್ನೂ ಸಂಗ್ರಹಿಸಲಾಗುವುದಿಲ್ಲ.\n\nಸೂಚನೆಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುವುದರಿಂದ ಈ ಎಲ್ಲಾ ಮಾಹಿತಿಯನ್ನು ಸರ್ವರ್‌ನಿಂದ ತೆಗೆದುಹಾಕಲಾಗುತ್ತದೆ. ಹೆಚ್ಚುವರಿಯಾಗಿ, ಆ್ಯಪ್‌ನಿಂದ ವ್ಯಾಲೆಟ್ ಅಳಿಸುವುದರಿಂದ ಅದರ ಸಂಬಂಧಿತ ಮಾಹಿತಿಯನ್ನು ಸಹ ಸರ್ವರ್‌ನಿಂದ ತೆಗೆದುಹಾಕಲಾಗುತ್ತದೆ.", + "run_performance_test": "ಕಾರ್ಯಕ್ಷಮತೆ ಪರೀಕ್ಷೆ", + "selfTest": "ಸ್ವಯಂ ಪರೀಕ್ಷೆ", + "set_as_preferred": "ಆದ್ಯತೆಯಂತೆ ಹೊಂದಿಸಿ", + "set_as_preferred_electrum": "{host}:{port} ಅನ್ನು ಆದ್ಯತೆಯ ಸರ್ವರ್ ಆಗಿ ಹೊಂದಿಸುವುದರಿಂದ ಯಾದೃಚ್ಛಿಕವಾಗಿ ಸೂಚಿಸಲಾದ ಸರ್ವರ್‌ಗೆ ಸಂಪರ್ಕವನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗುತ್ತದೆ.", + "set_electrum_server_as_default": "{server} ಅನ್ನು ಡೀಫಾಲ್ಟ್ Electrum ಸರ್ವರ್ ಆಗಿ ಹೊಂದಿಸಬೇಕೇ?", + "set_lndhub_as_default": "{url} ಅನ್ನು ಡೀಫಾಲ್ಟ್ LNDhub ಸರ್ವರ್ ಆಗಿ ಹೊಂದಿಸಬೇಕೇ?", + "success_transaction_broadcasted": "ನಿಮ್ಮ ವಹಿವಾಟು ಯಶಸ್ವಿಯಾಗಿ ಬ್ರಾಡ್‌ಕಾಸ್ಟ್ ಆಗಿದೆ!", + "tools": "ಪರಿಕರಗಳು", + "total_balance": "ಒಟ್ಟು ಬಾಕಿ", + "total_balance_explanation": "ನಿಮ್ಮ ಮುಖಪರದೆಯ ವಿಜೆಟ್‌ಗಳಲ್ಲಿ ನಿಮ್ಮ ಎಲ್ಲಾ ವ್ಯಾಲೆಟ್‌ಗಳ ಒಟ್ಟು ಬಾಕಿಯನ್ನು ತೋರಿಸಿ.", + "use_ssl": "SSL ಬಳಸಿ", + "widgets": "ವಿಜೆಟ್‌ಗಳು" + }, + "transactions": { + "confirmations_lowercase": "{confirmations} ದೃಢೀಕರಣಗಳು", + "cpfp_create": "ರಚಿಸಿ", + "details_copy": "ನಕಲಿಸಿ", + "details_inputs": "ಇನ್‌ಪುಟ್‌ಗಳು", + "details_outputs": "ಔಟ್‌ಪುಟ್‌ಗಳು", + "date": "ದಿನಾಂಕ", + "details_received": "ಸ್ವೀಕರಿಸಲಾಗಿದೆ", + "details_title": "ವಹಿವಾಟು", + "offchain": "ಆಫ್-ಚೈನ್", + "onchain": "ಆನ್-ಚೈನ್", + "details_to": "ಔಟ್‌ಪುಟ್", + "pending": "ಬಾಕಿ ಇದೆ", + "list_title": "ವಹಿವಾಟುಗಳು", + "list_title_sent": "ಕಳುಹಿಸಲಾಗಿದೆ", + "list_title_received": "ಸ್ವೀಕರಿಸಲಾಗಿದೆ", + "transaction": "ವಹಿವಾಟು", + "status_cancel": "ರದ್ದುಮಾಡಿ", + "details_sent": "ಕಳುಹಿಸಲಾಗಿದೆ", + "details_network_fee": "ನೆಟ್‌ವರ್ಕ್ ಶುಲ್ಕ", + "details_to_address": "ಗೆ", + "details_advanced": "ಸುಧಾರಿತ", + "details_size": "ಗಾತ್ರ", + "cancel_explain": "ಈ ವಹಿವಾಟನ್ನು ನಿಮಗೆ ಪಾವತಿಸುವ ಮತ್ತು ಹೆಚ್ಚಿನ ಶುಲ್ಕವನ್ನು ಹೊಂದಿರುವ ವಹಿವಾಟಿನೊಂದಿಗೆ ನಾವು ಬದಲಾಯಿಸುತ್ತೇವೆ. ಇದು ಪರಿಣಾಮಕಾರಿಯಾಗಿ ಪ್ರಸ್ತುತ ವಹಿವಾಟನ್ನು ರದ್ದುಮಾಡುತ್ತದೆ. ಇದನ್ನು RBF—Replace by Fee ಎನ್ನುತ್ತಾರೆ.", + "cancel_no": "ಈ ವಹಿವಾಟು ಬದಲಿಸಲಾಗದು.", + "cancel_title": "ಈ ವಹಿವಾಟನ್ನು ರದ್ದುಮಾಡಿ (RBF)", + "cpfp_exp": "ನಾವು ನಿಮ್ಮ ದೃಢೀಕರಿಸದ ವಹಿವಾಟನ್ನು ಖರ್ಚು ಮಾಡುವ ಮತ್ತೊಂದು ವಹಿವಾಟನ್ನು ರಚಿಸುತ್ತೇವೆ. ಒಟ್ಟು ಶುಲ್ಕವು ಮೂಲ ವಹಿವಾಟು ಶುಲ್ಕಕ್ಕಿಂತ ಹೆಚ್ಚಾಗಿರುತ್ತದೆ, ಆದ್ದರಿಂದ ಇದು ವೇಗವಾಗಿ ಮೈನ್ ಆಗಬೇಕು. ಇದನ್ನು CPFP—Child Pays for Parent ಎನ್ನುತ್ತಾರೆ.", + "cpfp_no_bump": "ಈ ವಹಿವಾಟು bump ಮಾಡಲಾಗದು.", + "cpfp_title": "ಶುಲ್ಕ ಹೆಚ್ಚಿಸಿ (CPFP)", + "custom_fee_warning_description": "1 sat/vB ಗಿಂತ ಕಡಿಮೆ ಶುಲ್ಕಗಳು ಮಾನ್ಯವಾಗಿವೆ, ಆದರೆ ನೋಡ್ ನೀತಿಗಳಿಂದಾಗಿ ರಿಲೇ ಆಗದಿರಬಹುದು.", + "custom_fee_warning_title": "ಎಚ್ಚರಿಕೆ", + "details_balance_hide": "ಬಾಕಿಯನ್ನು ಮರೆಮಾಡಿ", + "details_balance_show": "ಬಾಕಿಯನ್ನು ತೋರಿಸಿ", + "details_copy_block_explorer_link": "Block Explorer ಲಿಂಕ್ ನಕಲಿಸಿ", + "details_copy_note": "ಟಿಪ್ಪಣಿಯನ್ನು ನಕಲಿಸಿ", + "details_copy_txid": "ವಹಿವಾಟು ID ನಕಲಿಸಿ", + "details_view_in_browser": "ಬ್ರೌಸರ್‌ನಲ್ಲಿ ವೀಕ್ಷಿಸಿ", + "enable_offline_signing": "ಈ ವ್ಯಾಲೆಟ್ ಆಫ್‌ಲೈನ್ ಸಹಿಯೊಂದಿಗೆ ಬಳಸಲಾಗುತ್ತಿಲ್ಲ. ಈಗ ಸಕ್ರಿಯಗೊಳಿಸಲು ಬಯಸುತ್ತೀರಾ?", + "eta_10m": "ETA: ~10 ನಿಮಿಷಗಳಲ್ಲಿ", + "eta_1d": "ETA: ~1 ದಿನದಲ್ಲಿ", + "eta_3h": "ETA: ~3 ಗಂಟೆಗಳಲ್ಲಿ", + "expand_note": "ಟಿಪ್ಪಣಿ ವಿಸ್ತರಿಸಿ", + "expired_transaction": "ಅವಧಿ ಮುಗಿದ ವಹಿವಾಟು", + "incoming_transaction": "ಒಳಬರುವ ವಹಿವಾಟು", + "list_conf": "ದೃಢೀಕರಣಗಳು: {number}", + "open_url_error": "ಡೀಫಾಲ್ಟ್ ಬ್ರೌಸರ್‌ನೊಂದಿಗೆ ಲಿಂಕ್ ತೆರೆಯಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ನಿಮ್ಮ ಡೀಫಾಲ್ಟ್ ಬ್ರೌಸರ್ ಬದಲಾಯಿಸಿ ಮತ್ತು ಪುನಃ ಪ್ರಯತ್ನಿಸಿ.", + "outgoing_transaction": "ಹೊರಹೋಗುವ ವಹಿವಾಟು", + "pending_transaction": "ಬಾಕಿಯಿರುವ ವಹಿವಾಟು", + "pending_with_amount": "ಬಾಕಿಯಿದೆ {amt1} ({amt2})", + "rbf_explain": "ನಾವು ಈ ವಹಿವಾಟನ್ನು ಹೆಚ್ಚಿನ ಶುಲ್ಕವಿರುವ ಒಂದರೊಂದಿಗೆ ಬದಲಾಯಿಸುತ್ತೇವೆ ಇದರಿಂದ ಅದು ವೇಗವಾಗಿ ಮೈನ್ ಆಗುತ್ತದೆ. ಇದನ್ನು RBF—Replace by Fee ಎನ್ನುತ್ತಾರೆ.", + "rbf_title": "ಶುಲ್ಕ ಹೆಚ್ಚಿಸಿ (RBF)", + "received_with_amount": "+{amt1} ({amt2})", + "status_bump": "ಶುಲ್ಕ ಹೆಚ್ಚಿಸಿ", + "transaction_loading_error": "ವಹಿವಾಟನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಮಸ್ಯೆ ಇದೆ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ.", + "transaction_not_available": "ವಹಿವಾಟು ಲಭ್ಯವಿಲ್ಲ", + "transactions_count": "ವಹಿವಾಟುಗಳ ಸಂಖ್ಯೆ", + "txid": "ವಹಿವಾಟು ID", + "updating": "ನವೀಕರಿಸಲಾಗುತ್ತಿದೆ...", + "watchOnlyWarningDescription": "ಬಳಕೆದಾರರನ್ನು ವಂಚಿಸಲು “watch-only” ವ್ಯಾಲೆಟ್‌ಗಳನ್ನು ಬಳಸುವ ವಂಚಕರ ಬಗ್ಗೆ ಎಚ್ಚರಿಕೆಯಿಂದಿರಿ. ಈ ವ್ಯಾಲೆಟ್‌ಗಳು ನಿಮಗೆ ಹಣವನ್ನು ನಿಯಂತ್ರಿಸಲು ಅಥವಾ ಕಳುಹಿಸಲು ಅವಕಾಶ ನೀಡುವುದಿಲ್ಲ; ಅವು ಕೇವಲ ಬಾಕಿಯನ್ನು ವೀಕ್ಷಿಸಲು ಅವಕಾಶ ನೀಡುತ್ತವೆ.", + "watchOnlyWarningTitle": "ಭದ್ರತಾ ಎಚ್ಚರಿಕೆ", + "details_eta_analyzing": "ವಿಶ್ಲೇಷಿಸಲಾಗುತ್ತಿದೆ...", + "details_section": "ವಿವರಗಳು", + "details_explorer": "ಎಕ್ಸ್‌ಪ್ಲೋರರ್", + "details_id": "ID", + "details_note": "ಟಿಪ್ಪಣಿ", + "details_add_note": "ಸೇರಿಸಿ", + "details_fee_rate": "ಶುಲ್ಕ ದರ", + "details_virtual_size": "ವರ್ಚುವಲ್ ಗಾತ್ರ", + "details_tx_hex": "Tx ಹೆಕ್ಸ್", + "details_inputs_count": "ಇನ್‌ಪುಟ್‌ಗಳು ({count})", + "details_outputs_count": "ಔಟ್‌ಪುಟ್‌ಗಳು ({count})" }, "wallets": { - "details_save": "ಉಳಿಸಿ" + "add_bitcoin": "Bitcoin", + "add_create": "ರಚಿಸಿ", + "add_lightning": "Lightning", + "add_wallet_name": "ಹೆಸರು", + "add_wallet_type": "ಪ್ರಕಾರ", + "add_wallet_seed_length_12": "12 ಪದಗಳು", + "add_wallet_seed_length_24": "24 ಪದಗಳು", + "details_address": "ವಿಳಾಸ", + "details_advanced": "ಸುಧಾರಿತ", + "details_delete": "ಅಳಿಸಿ", + "details_derivation_path": "ಡೆರಿವೇಶನ್ ಪಾಥ್", + "details_master_fingerprint": "ಮಾಸ್ಟರ್ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್", + "details_title": "ವ್ಯಾಲೆಟ್", + "wallets": "ವ್ಯಾಲೆಟ್‌ಗಳು", + "swipe_balance_hide": "ಮರೆಮಾಡಿ", + "swipe_balance_show": "ತೋರಿಸಿ", + "details_type": "ಪ್ರಕಾರ", + "details_use_with_hardware_wallet": "ಹಾರ್ಡ್‌ವೇರ್ ವ್ಯಾಲೆಟ್‌ನೊಂದಿಗೆ ಬಳಸಿ", + "import_do_import": "ಆಮದು", + "import_passphrase": "ಪಾಸ್‌ಫ್ರೇಸ್", + "import_passphrase_title": "ಪಾಸ್‌ಫ್ರೇಸ್", + "import_imported": "ಆಮದು ಮಾಡಲಾಗಿದೆ", + "import_title": "ಆಮದು", + "import_derivation_title": "ಡೆರಿವೇಶನ್ ಪಾಥ್", + "paste_from_clipboard": "ಅಂಟಿಸಿ", + "list_title": "ವ್ಯಾಲೆಟ್‌ಗಳು", + "xpub_title": "ವ್ಯಾಲೆಟ್ xpub", + "add_bitcoin_explain": "ಸರಳ ಮತ್ತು ಶಕ್ತಿಶಾಲಿ Bitcoin ವ್ಯಾಲೆಟ್", + "add_entropy": "ಎಂಟ್ರೋಪಿ", + "add_entropy_bytes": "{bytes} ಬೈಟ್‌ಗಳ ಎಂಟ್ರೋಪಿ", + "add_entropy_generated": "{gen} ಬೈಟ್‌ಗಳ ರಚಿಸಿದ ಎಂಟ್ರೋಪಿ", + "add_entropy_provide": "ಡೈಸ್ ರೋಲ್‌ಗಳ ಮೂಲಕ entropy ಒದಗಿಸಿ", + "add_entropy_remain": "{gen} bytes of generated entropy. ಉಳಿದ {rem} bytes ಅನ್ನು System random number generator ನಿಂದ ಪಡೆಯಲಾಗುತ್ತದೆ.", + "add_entropy_reset_message": "ವ್ಯಾಲೆಟ್ ಪ್ರಕಾರ ಬದಲಾಯಿಸುವುದರಿಂದ ಪ್ರಸ್ತುತ entropy ಮರುಹೊಂದಿಸಲಾಗುತ್ತದೆ. ಮುಂದುವರಿಯಲು ಬಯಸುತ್ತೀರಾ?", + "add_entropy_reset_title": "Entropy ಮರುಹೊಂದಿಸಿ", + "add_import_wallet": "ವ್ಯಾಲೆಟ್ ಆಮದು ಮಾಡಿ", + "add_lightning_explain": "ತತ್ಕ್ಷಣ ವಹಿವಾಟುಗಳಿಗಾಗಿ", + "add_ln_wallet_first": "ಮೊದಲು Lightning ವ್ಯಾಲೆಟ್ ಸೇರಿಸಬೇಕು.", + "add_lndhub": "ನಿಮ್ಮ LNDhub ಗೆ ಸಂಪರ್ಕಿಸಿ", + "add_lndhub_error": "ಒದಗಿಸಿದ ನೋಡ್ ವಿಳಾಸ ಅಮಾನ್ಯ LNDhub ನೋಡ್.", + "add_lndhub_placeholder": "ನಿಮ್ಮ ನೋಡ್ ವಿಳಾಸ", + "add_placeholder": "ನನ್ನ ಮೊದಲ ವ್ಯಾಲೆಟ್", + "add_title": "ವ್ಯಾಲೆಟ್ ಸೇರಿಸಿ", + "add_wallet_seed_length": "Seed ಉದ್ದ", + "clear_clipboard_on_import": "ಆಮದು ಮೇಲೆ ಕ್ಲಿಪ್‌ಬೋರ್ಡ್ ತೆರವುಗೊಳಿಸಿ", + "total_balance": "ಒಟ್ಟು ಬ್ಯಾಲೆನ್ಸ್", + "clipboard_bitcoin": "ನಿಮ್ಮ ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ನಲ್ಲಿ Bitcoin ವಿಳಾಸ ಇದೆ. ವಹಿವಾಟಿಗಾಗಿ ಅದನ್ನು ಬಳಸಲು ಬಯಸುವಿರಾ?", + "clipboard_lightning": "ನಿಮ್ಮ ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ನಲ್ಲಿ Lightning ಇನ್‌ವಾಯ್ಸ್ ಇದೆ. ವಹಿವಾಟಿಗಾಗಿ ಅದನ್ನು ಬಳಸಲು ಬಯಸುವಿರಾ?", + "details_are_you_sure": "ನೀವು ಖಚಿತವೇ?", + "details_connected_to": "ಇದಕ್ಕೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ", + "details_del_wb_err": "ಒದಗಿಸಿದ ಬ್ಯಾಲೆನ್ಸ್ ಮೊತ್ತವು ಈ ವ್ಯಾಲೆಟ್‌ನ ಬ್ಯಾಲೆನ್ಸ್‌ಗೆ ಹೊಂದಿಕೆಯಾಗುತ್ತಿಲ್ಲ. ದಯವಿಟ್ಟು ಮತ್ತೊಮ್ಮೆ ಪ್ರಯತ್ನಿಸಿ.", + "details_del_wb_q": "ಈ ವ್ಯಾಲೆಟ್‌ನಲ್ಲಿ ಬ್ಯಾಲೆನ್ಸ್ ಇದೆ. ಮುಂದುವರಿಯುವ ಮೊದಲು, ಈ ವ್ಯಾಲೆಟ್‌ನ ಸೀಡ್ ಪದಗುಚ್ಛವಿಲ್ಲದೆ ನಿಧಿಗಳನ್ನು ಮರುಪಡೆಯಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ ಎಂಬುದನ್ನು ತಿಳಿದಿರಲಿ. ಆಕಸ್ಮಿಕವಾಗಿ ತೆಗೆದುಹಾಕುವುದನ್ನು ತಪ್ಪಿಸಲು, ದಯವಿಟ್ಟು ನಿಮ್ಮ ವ್ಯಾಲೆಟ್‌ನ ಬ್ಯಾಲೆನ್ಸ್ {balance} ಸತಾಶಿಗಳನ್ನು ನಮೂದಿಸಿ.", + "details_delete_wallet": "ವ್ಯಾಲೆಟ್ ಅಳಿಸಿ", + "details_display": "ಮುಖಪುಟದಲ್ಲಿ ಪ್ರದರ್ಶಿಸಿ", + "details_export_backup": "ರಫ್ತು/ಬ್ಯಾಕಪ್", + "details_export_history": "ಇತಿಹಾಸವನ್ನು CSV ಗೆ ರಫ್ತು ಮಾಡಿ", + "details_multisig_type": "ಮಲ್ಟಿಸಿಗ್", + "details_show_xpub": "ವ್ಯಾಲೆಟ್ XPUB ತೋರಿಸಿ", + "details_show_addresses": "ವಿಳಾಸಗಳನ್ನು ತೋರಿಸಿ", + "drag_to_reorder": "ಮರುಕ್ರಮಿಸಲು ಎಳೆಯಿರಿ", + "clear_search": "ಹುಡುಕಾಟ ತೆರವುಗೊಳಿಸಿ", + "details_yes_delete": "ಹೌದು, ಅಳಿಸಿ", + "enter_bip38_password": "ಡೀಕ್ರಿಪ್ಟ್ ಮಾಡಲು ಪಾಸ್ವರ್ಡ್ ನಮೂದಿಸಿ", + "export_title": "ವ್ಯಾಲೆಟ್ ರಫ್ತು", + "import_passphrase_message": "ನೀವು ಯಾವುದನ್ನಾದರೂ ಬಳಸಿದ್ದರೆ ಪಾಸ್‌ಫ್ರೇಸ್ ನಮೂದಿಸಿ", + "import_error": "ಆಮದು ಮಾಡಲು ವಿಫಲವಾಗಿದೆ. ಒದಗಿಸಿದ ಡೇಟಾ ಮಾನ್ಯವಾಗಿದೆ ಎಂದು ದಯವಿಟ್ಟು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ.", + "import_explanation": "ದಯವಿಟ್ಟು ನಿಮ್ಮ ಸೀಡ್ ಪದಗಳು, ಸಾರ್ವಜನಿಕ ಕೀ, WIF, ಅಥವಾ ನಿಮ್ಮ ಬಳಿ ಇರುವ ಯಾವುದನ್ನಾದರೂ ನಮೂದಿಸಿ. BlueWallet ಸರಿಯಾದ ಸ್ವರೂಪವನ್ನು ಊಹಿಸಲು ಮತ್ತು ನಿಮ್ಮ ವ್ಯಾಲೆಟ್ ಆಮದು ಮಾಡಲು ತನ್ನ ಶ್ರೇಷ್ಠ ಪ್ರಯತ್ನ ಮಾಡುತ್ತದೆ.", + "import_scan_qr": "ಸ್ಕ್ಯಾನ್ ಮಾಡಿ ಅಥವಾ ಫೈಲ್ ಆಮದು ಮಾಡಿ", + "import_success": "ನಿಮ್ಮ ವ್ಯಾಲೆಟ್ ಯಶಸ್ವಿಯಾಗಿ ಆಮದು ಮಾಡಲಾಗಿದೆ.", + "import_success_watchonly": "ನಿಮ್ಮ ವ್ಯಾಲೆಟ್ ಯಶಸ್ವಿಯಾಗಿ ಆಮದು ಮಾಡಲಾಗಿದೆ. ಎಚ್ಚರಿಕೆ: ಇದು ವೀಕ್ಷಣೆ-ಮಾತ್ರ ವ್ಯಾಲೆಟ್, ನೀವು ಇದರಿಂದ ಖರ್ಚು ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ.", + "import_search_accounts": "ಖಾತೆಗಳನ್ನು ಹುಡುಕಿ", + "learn_more": "ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ", + "import_discovery_title": "ಶೋಧನೆ", + "import_discovery_subtitle": "ಶೋಧಿಸಿದ ವ್ಯಾಲೆಟ್ ಆಯ್ಕೆಮಾಡಿ", + "import_discovery_derivation": "ಕಸ್ಟಮ್ ಡೆರಿವೇಶನ್ ಪಾಥ್ ಬಳಸಿ", + "import_discovery_no_wallets": "ಯಾವುದೇ ವ್ಯಾಲೆಟ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ.", + "import_discovery_offline": "BlueWallet ಪ್ರಸ್ತುತ ಆಫ್‌ಲೈನ್ ಮೋಡ್‌ನಲ್ಲಿದೆ. ಈ ಮೋಡ್‌ನಲ್ಲಿ, ಇದು ವ್ಯಾಲೆಟ್‌ನ ಅಸ್ತಿತ್ವವನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ, ಆದ್ದರಿಂದ ನೀವು ಸರಿಯಾದದನ್ನು ಸ್ವತಃ ಆಯ್ಕೆ ಮಾಡಬೇಕಾಗುತ್ತದೆ", + "import_derivation_found": "ಕಂಡುಬಂದಿದೆ", + "import_derivation_found_not": "ಕಂಡುಬಂದಿಲ್ಲ", + "import_derivation_loading": "ಲೋಡ್ ಆಗುತ್ತಿದೆ...", + "import_derivation_subtitle": "ಕಸ್ಟಮ್ ಡೆರಿವೇಶನ್ ಪಾಥ್ ನಮೂದಿಸಿ, ಮತ್ತು ನಾವು ನಿಮ್ಮ ವ್ಯಾಲೆಟ್ ಶೋಧಿಸಲು ಪ್ರಯತ್ನಿಸುತ್ತೇವೆ.", + "import_derivation_unknown": "ಅಜ್ಞಾತ", + "import_wrong_path": "ತಪ್ಪಾದ ಡೆರಿವೇಶನ್ ಪಾಥ್", + "list_create_a_button": "ಈಗ ಸೇರಿಸಿ", + "list_create_a_wallet": "ವ್ಯಾಲೆಟ್ ಸೇರಿಸಿ", + "list_create_a_wallet_text": "ಇದು ಉಚಿತ, ಮತ್ತು ನೀವು ಬಯಸಿದಷ್ಟು \nರಚಿಸಬಹುದು.", + "list_empty_txs1": "ನಿಮ್ಮ ವಹಿವಾಟುಗಳು ಇಲ್ಲಿ ಗೋಚರಿಸುತ್ತವೆ.", + "list_empty_txs1_lightning": "ನಿಮ್ಮ ದೈನಂದಿನ ವಹಿವಾಟುಗಳಿಗಾಗಿ Lightning ವ್ಯಾಲೆಟ್ ಬಳಸಬೇಕು. ಶುಲ್ಕಗಳು ಅನ್ಯಾಯವಾಗಿ ಅಗ್ಗವಾಗಿವೆ ಮತ್ತು ವೇಗ ಮಿಂಚಿನಂತೆ ಶೀಘ್ರವಾಗಿದೆ.", + "list_empty_txs2": "ನಿಮ್ಮ ವ್ಯಾಲೆಟ್‌ನೊಂದಿಗೆ ಪ್ರಾರಂಭಿಸಿ.", + "list_empty_txs2_lightning": "\nಇದನ್ನು ಬಳಸಲು ಪ್ರಾರಂಭಿಸಲು, Manage Funds ಮೇಲೆ ಟ್ಯಾಪ್ ಮಾಡಿ ಮತ್ತು ನಿಮ್ಮ ಬ್ಯಾಲೆನ್ಸ್ ಅನ್ನು ಟಾಪ್‌ಅಪ್ ಮಾಡಿ.", + "list_latest_transaction": "ಇತ್ತೀಚಿನ ವಹಿವಾಟು", + "list_long_choose": "ಫೋಟೋ ಆಯ್ಕೆಮಾಡಿ", + "import_file": "ಫೈಲ್ ಆಮದು", + "list_long_scan": "QR ಕೋಡ್ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ", + "list_tryagain": "ಮತ್ತೊಮ್ಮೆ ಪ್ರಯತ್ನಿಸಿ", + "no_ln_wallet_error": "Lightning ಇನ್‌ವಾಯ್ಸ್ ಪಾವತಿಸುವ ಮೊದಲು, ನೀವು ಮೊದಲು Lightning ವ್ಯಾಲೆಟ್ ಸೇರಿಸಬೇಕು.", + "looks_like_bip38": "ಇದು ಪಾಸ್ವರ್ಡ್-ರಕ್ಷಿತ ಖಾಸಗಿ ಕೀ (BIP38) ಎಂದು ತೋರುತ್ತದೆ.", + "manage_title": "ವ್ಯಾಲೆಟ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ", + "no_results_found": "ಯಾವುದೇ ಫಲಿತಾಂಶಗಳು ಕಂಡುಬಂದಿಲ್ಲ.", + "please_continue_scanning": "ದಯವಿಟ್ಟು ಸ್ಕ್ಯಾನಿಂಗ್ ಮುಂದುವರಿಸಿ.", + "select_no_bitcoin": "ಪ್ರಸ್ತುತ ಯಾವುದೇ Bitcoin ವ್ಯಾಲೆಟ್‌ಗಳು ಲಭ್ಯವಿಲ್ಲ.", + "select_no_bitcoin_exp": "Lightning ವ್ಯಾಲೆಟ್‌ಗಳನ್ನು ಮರುಪೂರಣ ಮಾಡಲು Bitcoin ವ್ಯಾಲೆಟ್ ಅಗತ್ಯವಿದೆ. ದಯವಿಟ್ಟು ಒಂದನ್ನು ರಚಿಸಿ ಅಥವಾ ಆಮದು ಮಾಡಿ.", + "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": "ನಂತರ ನಿಮ್ಮ ವ್ಯಾಲೆಟ್ ಅನ್ನು ಪುನರುದ್ಧರಿಸಲು ಈ URL ಅನ್ನು ನಕಲಿಸಿ ಮತ್ತು ಭದ್ರವಾಗಿ ಸಂಗ್ರಹಿಸಿ.", + "copy_ln_public": "ನಂತರ ನಿಮ್ಮ ವ್ಯಾಲೆಟ್ ಅನ್ನು ಪುನರುದ್ಧರಿಸಲು ಈ ಮಾಹಿತಿಯನ್ನು ನಕಲಿಸಿ ಮತ್ತು ಭದ್ರವಾಗಿ ಸಂಗ್ರಹಿಸಿ.", + "identity_pubkey": "ಗುರುತಿನ Pubkey", + "manage_wallets_search_placeholder": "ವ್ಯಾಲೆಟ್‌ಗಳು, ವಿಳಾಸಗಳು, ವಹಿವಾಟುಗಳು ಮತ್ತು ಮೆಮೊಗಳನ್ನು ಹುಡುಕಿ", + "more_info": "ಹೆಚ್ಚಿನ ಮಾಹಿತಿ", + "details_delete_wallet_error_message": "ಈ ವ್ಯಾಲೆಟ್ ಅನ್ನು ಅಧಿಸೂಚನೆಗಳಿಂದ ತೆಗೆದುಹಾಕಲಾಗಿದೆಯೇ ಎಂದು ಖಚಿತಪಡಿಸುವಲ್ಲಿ ಸಮಸ್ಯೆ ಉಂಟಾಯಿತು—ಇದು ನೆಟ್‌ವರ್ಕ್ ಸಮಸ್ಯೆ ಅಥವಾ ಕಳಪೆ ಸಂಪರ್ಕದಿಂದಾಗಿರಬಹುದು. ನೀವು ಮುಂದುವರಿದರೆ, ಈ ವ್ಯಾಲೆಟ್ ಅಳಿಸಿದ ನಂತರವೂ ಅದಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ವಹಿವಾಟುಗಳಿಗಾಗಿ ನೀವು ಇನ್ನೂ ಅಧಿಸೂಚನೆಗಳನ್ನು ಸ್ವೀಕರಿಸಬಹುದು.", + "details_delete_anyway": "ಹೇಗಾದರೂ ಅಳಿಸಿ" + }, + "total_balance_view": { + "hide": "ಮರೆಮಾಡಿ", + "display_in_bitcoin": "Bitcoin ನಲ್ಲಿ ತೋರಿಸಿ", + "display_in_fiat": "{currency} ನಲ್ಲಿ ತೋರಿಸಿ", + "display_in_sats": "sats ನಲ್ಲಿ ತೋರಿಸಿ", + "explanation": "ಅವಲೋಕನ ಪರದೆಯಲ್ಲಿ ನಿಮ್ಮ ಎಲ್ಲಾ ವ್ಯಾಲೆಟ್‌ಗಳ ಒಟ್ಟು ಬಾಕಿಯನ್ನು ವೀಕ್ಷಿಸಿ.", + "title": "ಒಟ್ಟು ಬಾಕಿ" + }, + "multisig": { + "provide_signature": "ಸಹಿ ಮಾಡಿ", + "confirm": "ದೃಢೀಕರಿಸಿ", + "header": "ಕಳುಹಿಸಿ", + "view": "ವೀಕ್ಷಿಸಿ", + "create": "ರಚಿಸಿ", + "quorum_header": "ಕೋರಂ", + "of": "ರಲ್ಲಿ", + "are_you_sure_seed_will_be_lost": "ನೀವು ಖಚಿತವಾಗಿರುವಿರಾ? ಬ್ಯಾಕಪ್ ಇಲ್ಲದಿದ್ದರೆ ನಿಮ್ಮ mnemonic seed ಕಳೆದುಹೋಗುತ್ತದೆ.", + "co_sign_transaction": "ವಹಿವಾಟಿಗೆ ಸಹಿ ಹಾಕಿ", + "cosign_this_transaction": "ಈ ವಹಿವಾಟಿಗೆ ಸಹ-ಸಹಿ ಹಾಕುವುದೇ?", + "create_new_key": "ಹೊಸದನ್ನು ರಚಿಸಿ", + "default_label": "ಬಹು-ಸಹಿ ವಾಲ್ಟ್", + "export_coordination_setup": "Coordination Setup ರಫ್ತು ಮಾಡಿ", + "export_signed_psbt": "ಸಹಿ ಮಾಡಿದ PSBT ರಫ್ತು ಮಾಡಿ", + "fee": "ಶುಲ್ಕ: {number}", + "fee_btc": "{number} BTC", + "forget_this_seed": "ಈ seed ಅನ್ನು ಮರೆತುಬಿಡಿ ಮತ್ತು ಬದಲಾಗಿ XPUB ಅನ್ನು ಬಳಸಿ.", + "how_many_signatures_can_bluewallet_make": "BlueWallet ಎಷ್ಟು ಸಹಿಗಳನ್ನು ಮಾಡಬಹುದು", + "i_have_mnemonics": "ಈ ಕೀಗಾಗಿ ನನ್ನ ಬಳಿ seed ಇದೆ.", + "input_fp": "Fingerprint ನಮೂದಿಸಿ", + "input_fp_explain": "ಡೀಫಾಲ್ಟ್ ಬಳಸಲು ಬಿಟ್ಟುಬಿಡಿ (00000000)", + "input_path": "Derivation Path ಸೇರಿಸಿ", + "input_path_explain": "ಡೀಫಾಲ್ಟ್ ({default}) ಬಳಸಲು ಬಿಟ್ಟುಬಿಡಿ", + "invalid_cosigner": "ಅಮಾನ್ಯ ಸಹ-ಸಹಿಗಾರ ಡೇಟಾ", + "invalid_cosigner_format": "ತಪ್ಪಾದ ಸಹ-ಸಹಿಗಾರ: ಇದು {format} ಫಾರ್ಮ್ಯಾಟ್‌ಗಾಗಿ ಸಹ-ಸಹಿಗಾರ ಅಲ್ಲ.", + "invalid_mnemonics": "ಈ mnemonic ಪದಗುಚ್ಛ ಮಾನ್ಯವಾಗಿಲ್ಲ.", + "legacy_title": "Legacy", + "lets_start": "ಆರಂಭಿಸೋಣ", + "manage_keys": "ಕೀಗಳನ್ನು ನಿರ್ವಹಿಸಿ", + "ms_help": "ಸಹಾಯ", + "ms_help_1": "Vault Electrum, Specter, Coldcard, Cobo Vault ಮುಂತಾದ ಇತರ BlueWallet ಆ್ಯಪ್‌ಗಳು ಮತ್ತು PSBT ಹೊಂದಾಣಿಕೆಯ ವ್ಯಾಲೆಟ್‌ಗಳೊಂದಿಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ.", + "ms_help_2": "ನೀವು ಈ ಸಾಧನದಲ್ಲಿ ಎಲ್ಲಾ Vault keys ಅನ್ನು ರಚಿಸಬಹುದು ಮತ್ತು ನಂತರ ಅವುಗಳನ್ನು ತೆಗೆದುಹಾಕಬಹುದು ಅಥವಾ ಸಂಪಾದಿಸಬಹುದು. ಒಂದೇ ಸಾಧನದಲ್ಲಿ ಎಲ್ಲಾ ಕೀಗಳನ್ನು ಹೊಂದಿರುವುದು ಸಾಮಾನ್ಯ Bitcoin ವ್ಯಾಲೆಟ್‌ನ ಸಮಾನ ಭದ್ರತೆಯನ್ನು ಹೊಂದಿರುತ್ತದೆ.", + "ms_help_3": "ವ್ಯಾಲೆಟ್ ಆಯ್ಕೆಗಳಲ್ಲಿ, ನಿಮ್ಮ Vault ಬ್ಯಾಕಪ್ ಮತ್ತು watch-only ಬ್ಯಾಕಪ್ ಸಿಗುತ್ತದೆ. ಈ ಬ್ಯಾಕಪ್ ನಿಮ್ಮ ವ್ಯಾಲೆಟ್‌ಗೆ ನಕ್ಷೆಯಂತಿದೆ. ನಿಮ್ಮ ಒಂದು seed ಕಳೆದುಕೊಂಡರೆ ವ್ಯಾಲೆಟ್ ಮರುಪಡೆಯಲು ಇದು ಅತ್ಯಗತ್ಯ.", + "ms_help_4": "Multisig ಆಮದು ಮಾಡಲು, ನಿಮ್ಮ ಬ್ಯಾಕಪ್ ಫೈಲ್ ಮತ್ತು Import ವೈಶಿಷ್ಟ್ಯವನ್ನು ಬಳಸಿ. ನಿಮ್ಮ ಬಳಿ ಕೇವಲ seeds ಮತ್ತು XPUBs ಇದ್ದರೆ, Vault keys ರಚಿಸುವಾಗ ವೈಯಕ್ತಿಕ Import ಬಟನ್ ಬಳಸಬಹುದು.", + "ms_help_5": "ಡೀಫಾಲ್ಟ್ ಆಗಿ, BlueWallet 2-of-3 Vault ಸೃಷ್ಟಿಸುತ್ತದೆ. ಬೇರೆ quorum ಸೃಷ್ಟಿಸಲು ಅಥವಾ ವಿಳಾಸ ಪ್ರಕಾರ ಬದಲಾಯಿಸಲು, Settings ನಲ್ಲಿ Advanced Mode ಸಕ್ರಿಯಗೊಳಿಸಿ.", + "ms_help_text": "ಹೆಚ್ಚಿನ ಭದ್ರತೆ ಅಥವಾ ಹಂಚಿಕೊಂಡ ಕಸ್ಟಡಿಗಾಗಿ ಬಹು ಕೀಗಳಿರುವ ವ್ಯಾಲೆಟ್", + "ms_help_title": "Multisig Vaults ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತವೆ: ಸಲಹೆಗಳು ಮತ್ತು ಟ್ರಿಕ್‌ಗಳು", + "ms_help_title1": "ಬಹು ಸಾಧನಗಳನ್ನು ಸೂಚಿಸಲಾಗಿದೆ.", + "ms_help_title2": "ಕೀಗಳನ್ನು ಸಂಪಾದಿಸುವುದು", + "ms_help_title3": "Vault ಬ್ಯಾಕಪ್‌ಗಳು", + "ms_help_title4": "Vaults ಆಮದು ಮಾಡುವುದು", + "ms_help_title5": "ಸುಧಾರಿತ ಮೋಡ್", + "multisig_vault": "ಬಹು-ಸಹಿ ವಾಲ್ಟ್", + "multisig_vault_explain": "ದೊಡ್ಡ ಮೊತ್ತಗಳಿಗೆ ಉತ್ತಮ ಭದ್ರತೆ", + "native_segwit_title": "ಉತ್ತಮ ಅಭ್ಯಾಸ", + "needs": "ಇದಕ್ಕೆ ಬೇಕು", + "not_a_multisignature_xpub": "ಇದು multisignature ವ್ಯಾಲೆಟ್‌ನಿಂದ XPUB ಅಲ್ಲ!", + "provide_signature_details": "ಈ ವಹಿವಾಟಿಗೆ ಸಹಿ ಹಾಕಲು ಕೀ ಇರುವ ನಿಮ್ಮ ಸಾಧನ ಮತ್ತು ವ್ಯಾಲೆಟ್ ಅನ್ನು ಬಳಸಿ", + "provide_signature_details_bluewallet": "BlueWallet ನಲ್ಲಿ, Send ಪರದೆಯ ಮೆನುಗೆ ಹೋಗಿ ಮತ್ತು ಆಯ್ಕೆಮಾಡಿ ", + "provide_signature_next_steps": "ಸಹಿ ಮಾಡಿದ ವಹಿವಾಟನ್ನು ಸ್ಕ್ಯಾನ್ ಅಥವಾ ಆಮದು ಮಾಡಿ", + "provide_signature_next_steps_details": "ನಿಮ್ಮ ವ್ಯಾಲೆಟ್ ವಹಿವಾಟನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಸಹಿ ಹಾಕಿದ ನಂತರ, ಒದಗಿಸಿದ QR ಕೋಡ್ ಅನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಿ ಅಥವಾ ಜೊತೆಯ ಫೈಲ್ ಅನ್ನು ಆಮದು ಮಾಡಿ, ನಂತರ ಬ್ರಾಡ್‌ಕಾಸ್ಟ್ ಮಾಡುವ ಮೊದಲು ಎಲ್ಲಾ ವಹಿವಾಟು ವಿವರಗಳನ್ನು ಪರಿಶೀಲಿಸಿ.", + "quorum": "{n} ರಲ್ಲಿ {m} ಕೋರಂ", + "required_keys_out_of_total": "ಒಟ್ಟು ಪೈಕಿ ಅಗತ್ಯವಿರುವ ಕೀಗಳು", + "scan_or_import_file": "ಫೈಲ್ ಸ್ಕ್ಯಾನ್ ಅಥವಾ ಆಮದು ಮಾಡಿ", + "scan_or_open_file": "ಸ್ಕ್ಯಾನ್ ಅಥವಾ ಫೈಲ್ ತೆರೆಯಿರಿ", + "share": "ಹಂಚಿಕೊಳ್ಳಿ...", + "shared_key_detected": "ಹಂಚಿಕೊಳ್ಳಲಾದ ಸಹ-ಸಹಿಗಾರ", + "shared_key_detected_question": "ನಿಮ್ಮೊಂದಿಗೆ ಸಹ-ಸಹಿಗಾರನನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗಿದೆ, ಆಮದು ಮಾಡಲು ಬಯಸುತ್ತೀರಾ?", + "signatures_required_to_spend": "ಅಗತ್ಯವಿರುವ ಸಹಿಗಳು {number}", + "signatures_we_can_make": "{number} ಮಾಡಬಹುದು", + "this_cosigner_is_already_imported": "ಈ ಸಹ-ಸಹಿಗಾರ ಈಗಾಗಲೇ ಆಮದು ಮಾಡಲಾಗಿದೆ.", + "this_is_cosigners_xpub": "ಇದು ಸಹ-ಸಹಿಗಾರನ XPUB—ಮತ್ತೊಂದು ವ್ಯಾಲೆಟ್‌ಗೆ ಆಮದು ಮಾಡಲು ಸಿದ್ಧ. ಇದನ್ನು ಹಂಚಿಕೊಳ್ಳಲು ಸುರಕ್ಷಿತವಾಗಿದೆ.", + "this_is_cosigners_xpub_airdrop": "AirDrop ಮೂಲಕ ಹಂಚಿಕೊಳ್ಳುತ್ತಿದ್ದರೆ ಸ್ವೀಕರಿಸುವವರು coordination ಪರದೆಯಲ್ಲಿರಬೇಕು.", + "type_your_mnemonics": "ನಿಮ್ಮ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ Vault key ಆಮದು ಮಾಡಲು seed ಸೇರಿಸಿ.", + "vault_advanced_customize": "Vault ಸೆಟ್ಟಿಂಗ್‌ಗಳು", + "vault_key": "ವಾಲ್ಟ್ ಕೀ {number}", + "view_edit_cosigners": "ಸಹ-ಸಹಿಗಾರರನ್ನು ವೀಕ್ಷಿಸಿ/ಸಂಪಾದಿಸಿ", + "wallet_key_created": "ನಿಮ್ಮ Vault key ರಚಿಸಲಾಗಿದೆ. ನಿಮ್ಮ mnemonic seed ಅನ್ನು ಸುರಕ್ಷಿತವಾಗಿ ಬ್ಯಾಕಪ್ ಮಾಡಿ.", + "wallet_type": "ವ್ಯಾಲೆಟ್ ಪ್ರಕಾರ", + "what_is_vault": "Vault ಎಂಬುದು", + "what_is_vault_description_number_of_vault_keys": " {m} ವಾಲ್ಟ್ ಕೀಗಳು ", + "what_is_vault_description_to_spend": "ಖರ್ಚು ಮಾಡಲು ಮತ್ತು ಮೂರನೆಯದನ್ನು \nಬ್ಯಾಕಪ್ ಆಗಿ ಬಳಸಬಹುದು.", + "what_is_vault_description_to_spend_other": "ಖರ್ಚು ಮಾಡಲು.", + "what_is_vault_numberOfWallets": " {n}-ರಲ್ಲಿ-{m} ಬಹು-ಸಹಿ ", + "what_is_vault_wallet": "ವ್ಯಾಲೆಟ್.", + "wrapped_segwit_title": "ಉತ್ತಮ ಹೊಂದಾಣಿಕೆ" + }, + "cc": { + "change": "ಚಿಲ್ಲರೆ", + "freeze": "ಫ್ರೀಜ್ ಮಾಡಿ", + "header": "ಕಾಯಿನ್ ನಿಯಂತ್ರಣ", + "coins_selected": "Coins ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ ({number})", + "empty": "ಈ ವ್ಯಾಲೆಟ್‌ನಲ್ಲಿ ಸದ್ಯಕ್ಕೆ ಯಾವುದೇ coins ಇಲ್ಲ.", + "freezeLabel": "ಫ್ರೀಜ್", + "freezeLabel_un": "ಅನ್‌ಫ್ರೀಜ್", + "selected_summ": "{value} ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ", + "sort_asc": "ಆರೋಹಣ", + "sort_by": "ವಿಂಗಡಿಸಿ", + "sort_desc": "ಅವರೋಹಣ", + "sort_height": "ಎತ್ತರ", + "sort_label": "ಲೇಬಲ್", + "sort_status": "ಸ್ಥಿತಿ", + "sort_value": "ಮೌಲ್ಯ", + "tip": "ಈ ವೈಶಿಷ್ಟ್ಯವು coins ಅನ್ನು ನೋಡಲು, ಲೇಬಲ್ ಮಾಡಲು, ಫ್ರೀಜ್ ಮಾಡಲು ಅಥವಾ ಆಯ್ಕೆ ಮಾಡಲು ಅವಕಾಶ ನೀಡುತ್ತದೆ, ಸುಧಾರಿತ ವ್ಯಾಲೆಟ್ ನಿರ್ವಹಣೆಗಾಗಿ. ಬಣ್ಣದ ವೃತ್ತಗಳ ಮೇಲೆ ಟ್ಯಾಪ್ ಮಾಡುವ ಮೂಲಕ ನೀವು ಬಹು coins ಆಯ್ಕೆ ಮಾಡಬಹುದು.", + "use_coin": "Coin ಬಳಸಿ", + "use_coins": "Coins ಬಳಸಿ" + }, + "units": { + "BTC": "BTC", + "MAX": "ಗರಿಷ್ಠ", + "sat_vbyte": "sat/vByte", + "sats": "sats" + }, + "addresses": { + "sign_sign": "ಸಹಿ ಮಾಡಿ", + "sign_placeholder_address": "ವಿಳಾಸ", + "addresses_title": "ವಿಳಾಸಗಳು", + "type_change": "ಚಿಲ್ಲರೆ", + "type_receive": "ಸ್ವೀಕರಿಸಿ", + "transactions": "ವಹಿವಾಟುಗಳು", + "copy_private_key": "ಖಾಸಗಿ ಕೀ ನಕಲಿಸಿ", + "sensitive_private_key": "ಎಚ್ಚರಿಕೆ: ಖಾಸಗಿ ಕೀಗಳು ಅತ್ಯಂತ ಸೂಕ್ಷ್ಮವಾಗಿವೆ. ಮುಂದುವರಿಯುವುದೇ?", + "sign_help": "ಇಲ್ಲಿ ನೀವು Bitcoin ವಿಳಾಸದ ಆಧಾರದ ಮೇಲೆ ಕ್ರಿಪ್ಟೋಗ್ರಾಫಿಕ್ ಸಹಿಯನ್ನು ರಚಿಸಬಹುದು ಅಥವಾ ಪರಿಶೀಲಿಸಬಹುದು.", + "sign_placeholder_message": "ಸಂದೇಶ", + "sign_placeholder_signature": "ಸಹಿ", + "sign_signature_correct": "ಪರಿಶೀಲನೆ ಯಶಸ್ವಿ!", + "sign_signature_incorrect": "ಪರಿಶೀಲನೆ ವಿಫಲ!", + "sign_title": "ಸಂದೇಶಕ್ಕೆ ಸಹಿ/ಪರಿಶೀಲನೆ", + "sign_verify": "ಪರಿಶೀಲಿಸಿ", + "type_used": "ಬಳಸಲಾಗಿದೆ" + }, + "bip47": { + "contacts": "ಸಂಪರ್ಕಗಳು", + "bip47_explain_subtitle": "BIP47", + "rename": "ಮರುಹೆಸರಿಸಿ", + "notification_tx_unconfirmed": "ಸೂಚನಾ ವಹಿವಾಟು ಇನ್ನೂ ದೃಢೀಕರಿಸಿಲ್ಲ, ದಯವಿಟ್ಟು ಕಾಯಿರಿ", + "notif_tx": "ಸೂಚನಾ ವಹಿವಾಟು", + "add_contact": "ಸಂಪರ್ಕವನ್ನು ಸೇರಿಸಿ", + "bip47_explain": "ಮರುಬಳಸಬಹುದಾದ ಮತ್ತು ಹಂಚಿಕೊಳ್ಳಬಹುದಾದ ಕೋಡ್", + "copy_payment_code": "Payment Code ನಕಲಿಸಿ", + "failed_create_notif_tx": "ಆನ್-ಚೈನ್ ವಹಿವಾಟು ರಚಿಸಲು ವಿಫಲ", + "hide_contact": "ಸಂಪರ್ಕವನ್ನು ಮರೆಮಾಡಿ", + "invalid_pc": "ಅಮಾನ್ಯ Payment Code", + "not_found": "Payment code ಕಂಡುಬಂದಿಲ್ಲ", + "notif_tx_sent": "ಸೂಚನಾ ವಹಿವಾಟು ಕಳುಹಿಸಲಾಗಿದೆ. ದಯವಿಟ್ಟು ಅದನ್ನು ದೃಢೀಕರಿಸಲು ಕಾಯಿರಿ", + "onchain_tx_needed": "ಆನ್-ಚೈನ್ ವಹಿವಾಟು ಅಗತ್ಯವಿದೆ", + "pay_this_contact": "ಈ ಸಂಪರ್ಕಕ್ಕೆ ಪಾವತಿಸಿ", + "payment_code": "ಪಾವತಿ ಕೋಡ್", + "provide_name": "ಈ ಸಂಪರ್ಕಕ್ಕೆ ಹೊಸ ಹೆಸರನ್ನು ಒದಗಿಸಿ", + "provide_payment_code": "Payment Code ಒದಗಿಸಿ", + "purpose": "ಮರುಬಳಸಬಹುದಾದ ಮತ್ತು ಹಂಚಿಕೊಳ್ಳಬಹುದಾದ ಕೋಡ್ (BIP47)", + "rename_contact": "ಸಂಪರ್ಕವನ್ನು ಮರುಹೆಸರಿಸಿ" + }, + "autofill_word": { + "enter": "ನಿಮ್ಮ ಭಾಗಶಃ mnemonic ಪದಗುಚ್ಛವನ್ನು ನಮೂದಿಸಿ", + "error": "ಇನ್‌ಪುಟ್ 11 ಅಥವಾ 23 ಪದಗಳ ಭಾಗಶಃ mnemonic ಅಲ್ಲ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ.", + "generate_word": "ಅಂತಿಮ ಪದವನ್ನು ಸೃಷ್ಟಿಸಿ", + "title": "Seed ಅಂತಿಮ ಪದ" + }, + "is_it_my_address": { + "check_address": "ವಿಳಾಸ ಪರಿಶೀಲಿಸಿ", + "enter_address": "ವಿಳಾಸ ನಮೂದಿಸಿ", + "no_wallet_owns_address": "ಯಾವುದೇ ಲಭ್ಯವಿರುವ ವ್ಯಾಲೆಟ್‌ಗಳಿಗೆ ಒದಗಿಸಿದ ವಿಳಾಸ ಸೇರಿಲ್ಲ.", + "owns": "{label} ಗೆ {address} ಸೇರಿದೆ", + "title": "ಇದು ನನ್ನ ವಿಳಾಸವೇ?", + "view_qrcode": "QR ಕೋಡ್ ವೀಕ್ಷಿಸಿ" + }, + "lnurl_auth": { + "auth_answer": "{hostname} ನಲ್ಲಿ ನೀವು ಯಶಸ್ವಿಯಾಗಿ ದೃಢೀಕರಿಸಿದ್ದೀರಿ!", + "auth_question_part_1": "ಇಲ್ಲಿ ದೃಢೀಕರಿಸಲು ಬಯಸುತ್ತೀರಾ", + "auth_question_part_2": "ನಿಮ್ಮ Lightning ವ್ಯಾಲೆಟ್ ಬಳಸಿಕೊಂಡು?", + "authenticate": "ದೃಢೀಕರಿಸಿ", + "could_not_auth": "{hostname} ಗೆ ನಾವು ನಿಮ್ಮನ್ನು ದೃಢೀಕರಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ.", + "link_answer": "ನಿಮ್ಮ Lightning ವ್ಯಾಲೆಟ್ {hostname} ನಲ್ಲಿ ನಿಮ್ಮ ಖಾತೆಗೆ ಯಶಸ್ವಿಯಾಗಿ ಲಿಂಕ್ ಮಾಡಲಾಗಿದೆ!", + "link_question_part_1": "ನಿಮ್ಮ ಖಾತೆಯನ್ನು ಇಲ್ಲಿ ಲಿಂಕ್ ಮಾಡಲು ಬಯಸುತ್ತೀರಾ", + "link_question_part_2": "ನಿಮ್ಮ Lightning ವ್ಯಾಲೆಟ್‌ಗೆ?", + "login_answer": "{hostname} ನಲ್ಲಿ ನೀವು ಯಶಸ್ವಿಯಾಗಿ ಲಾಗ್ ಇನ್ ಆಗಿದ್ದೀರಿ!", + "login_question_part_1": "ಇಲ್ಲಿ ಲಾಗ್ ಇನ್ ಮಾಡಲು ಬಯಸುತ್ತೀರಾ", + "login_question_part_2": "ನಿಮ್ಮ Lightning ವ್ಯಾಲೆಟ್ ಬಳಸಿಕೊಂಡು?", + "register_answer": "{hostname} ನಲ್ಲಿ ನೀವು ಯಶಸ್ವಿಯಾಗಿ ಖಾತೆಯನ್ನು ನೋಂದಾಯಿಸಿದ್ದೀರಿ!", + "register_question_part_1": "ಇಲ್ಲಿ ಖಾತೆಯನ್ನು ನೋಂದಾಯಿಸಲು ಬಯಸುತ್ತೀರಾ", + "register_question_part_2": "ನಿಮ್ಮ Lightning ವ್ಯಾಲೆಟ್ ಬಳಸಿಕೊಂಡು?" + }, + "notifications": { + "no_and_dont_ask": "ಇಲ್ಲ, ಮತ್ತು ಮತ್ತೆ ಕೇಳಬೇಡಿ.", + "notifications_subtitle": "ಒಳಬರುವ ಪಾವತಿಗಳು ಮತ್ತು ವಹಿವಾಟು ದೃಢೀಕರಣಗಳು", + "permission_denied_message": "ನಿಮಗೆ ಸೂಚನೆಗಳನ್ನು ಕಳುಹಿಸುವ ಅನುಮತಿಯನ್ನು ನೀವು ನಿರಾಕರಿಸಿದ್ದೀರಿ. ಸೂಚನೆಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಬಯಸಿದರೆ, ನಿಮ್ಮ ಸಾಧನ ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಅವುಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ.", + "would_you_like_to_receive_notifications": "ನೀವು ಒಳಬರುವ ಪಾವತಿಗಳನ್ನು ಪಡೆದಾಗ ಸೂಚನೆಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಬಯಸುತ್ತೀರಾ?" + }, + "plausibledeniability": { + "create_fake_storage": "ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಸಂಗ್ರಹಣೆ ರಚಿಸಿ", + "create_password_explanation": "ನಕಲಿ ಸಂಗ್ರಹಣೆಯ ಪಾಸ್ವರ್ಡ್ ನಿಮ್ಮ ಮುಖ್ಯ ಸಂಗ್ರಹಣೆಯ ಪಾಸ್ವರ್ಡ್‌ಗೆ ಹೊಂದಿಕೆಯಾಗಬಾರದು.", + "help": "ಕೆಲವು ಸಂದರ್ಭಗಳಲ್ಲಿ, ಪಾಸ್ವರ್ಡ್ ಬಹಿರಂಗಪಡಿಸಲು ನಿಮ್ಮನ್ನು ಒತ್ತಾಯಿಸಬಹುದು. ನಿಮ್ಮ coins ಅನ್ನು ಸುರಕ್ಷಿತವಾಗಿಡಲು, BlueWallet ಬೇರೆ ಪಾಸ್ವರ್ಡ್‌ನೊಂದಿಗೆ ಮತ್ತೊಂದು ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಸಂಗ್ರಹಣೆಯನ್ನು ರಚಿಸಬಹುದು. ಒತ್ತಡದಲ್ಲಿ, ನೀವು ಈ ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ಮೂರನೇ ಪಕ್ಷಕ್ಕೆ ಬಹಿರಂಗಪಡಿಸಬಹುದು. BlueWallet ನಲ್ಲಿ ನಮೂದಿಸಿದರೆ, ಅದು ಹೊಸ “ನಕಲಿ” ಸಂಗ್ರಹಣೆಯನ್ನು ಅನ್‌ಲಾಕ್ ಮಾಡುತ್ತದೆ. ಇದು ಮೂರನೇ ಪಕ್ಷಕ್ಕೆ ಮಾನ್ಯ ಎಂದು ಕಾಣಿಸುತ್ತದೆ, ಆದರೆ coins ಇರುವ ನಿಮ್ಮ ಮುಖ್ಯ ಸಂಗ್ರಹಣೆಯನ್ನು ಗುಪ್ತವಾಗಿ ಸುರಕ್ಷಿತವಾಗಿಡುತ್ತದೆ.", + "help2": "ಹೊಸ ಸಂಗ್ರಹಣೆಯು ಪೂರ್ಣವಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ, ಮತ್ತು ಅದು ಹೆಚ್ಚು ನಂಬಲರ್ಹವಾಗಿ ಕಾಣುವಂತೆ ಕೆಲವು ಕನಿಷ್ಠ ಮೊತ್ತಗಳನ್ನು ಅಲ್ಲಿ ಸಂಗ್ರಹಿಸಬಹುದು.", + "password_should_not_match": "ಪಾಸ್ವರ್ಡ್ ಪ್ರಸ್ತುತ ಬಳಕೆಯಲ್ಲಿದೆ. ಬೇರೆ ಪಾಸ್ವರ್ಡ್ ಪ್ರಯತ್ನಿಸಿ.", + "title": "ಸಂಭಾವ್ಯ ನಿರಾಕರಣೆ" } } diff --git a/loc/ko_KR.json b/loc/ko_KR.json index 0cb7f599d75..24a4f7c1fe9 100644 --- a/loc/ko_KR.json +++ b/loc/ko_KR.json @@ -4,124 +4,117 @@ "cancel": "취소", "continue": "계속", "clipboard": "붙여넣기판", + "copied": "복사됨!", + "discard_changes": "변경 사항을 취소하시겠습니까?", + "discard_changes_explain": "저장되지 않은 변경 사항이 있습니다. 정말로 이를 취소하고 화면을 나가시겠습니까?", "enter_password": "비밀번호 입력", - "never": "불가합니다", - "disabled": "사용불가", + "never": "존재하지 않음", "of": "{total} 개중 {number}", "ok": "확인", + "enter_url": "URL 입력", "storage_is_encrypted": "저장된 데이터가 암호화되었습니다. 암호화를 해독하려면 비밀번호를 입력하십시오.", "yes": "네", "no": "아니요", - "save": "저장", + "save": "저장...", "seed": "시드", "success": "성공", "wallet_key": "지갑 키", - "invalid_animated_qr_code_fragment": "유효하지 않은 QR 코드입니다. 다시 시도하십시오.", - "file_saved": "파일이{filePath} {destination}에 저장되었습니다.", - "downloads_folder": "폴더를 다운로드합니다." - }, - "alert": { - "default": "경고" + "close": "닫기", + "change_input_currency": "입력 통화 변경", + "refresh": "새로고침", + "pick_image": "라이브러리에서 선택", + "pick_file": "파일 선택", + "enter_amount": "수량 입력", + "qr_custom_input_button": "사용자 지정 입력을 하려면 10번 탭하세요.", + "unlock": "잠금 해제", + "port": "포트", + "ssl_port": "SSL 포트", + "suggested": "제안됨" }, "azteco": { "codeIs": "프로모션 코드", - "errorBeforeRefeem": "교환하기 전에 먼저 비트 코인 지갑을 추가해야합니다.", - "errorSomething": "문제가 발생했습니다. 이 쿠폰은 여전히 ​​유효합니까?", + "errorBeforeRefeem": "교환하기 전에 먼저 비트코인 지갑을 추가해야 합니다.", + "errorSomething": "문제가 발생했습니다. 이 쿠폰이 여전히 유효합니까?", "redeem": "지갑으로 교환", "redeemButton": "교환", "success": "성공", + "successMessage": "바우처가 성공적으로 사용되었습니다! 곧 비트코인 지갑으로 자금이 입금될 것입니다.", "title": "제목" }, "entropy": { "save": "저장", "title": "제목", - "undo": "실행 취소" + "undo": "실행 취소", + "amountOfEntropy": "{limit} 비트 중 {bits} 비트" }, "errors": { - "broadcast": "브로드 캐스트 실패", + "broadcast": "브로드캐스트 실패", "error": "오류", - "network": "네크워크 오류" + "network": "네트워크 오류" }, "lnd": { - "active": "활성", - "inactive": "비활성", - "channels": "챈널", - "no_channels": "챈널 없음", - "claim_balance": "현 잔고{balance} 찾아가기", - "close_channel": "챈널 닫기", - "new_channel": "새 챈널", - "errorInvoiceExpired": "청구서가 만료되었습니다.", - "force_close_channel": "챈널을 강제로 닫을까요?", + "errorInvoiceExpired": "인보이스가 만료되었습니다.", "expired": "만료되었습니다", - "node_alias": "노드 별칭", "expiresIn": "{time}분 뒤 무효화됩니다", "payButton": "지불하기", - "placeholder": "청구서", - "open_channel": "챈널 열기", - "funding_amount_placeholder": "자금규모, 예를 들면 0.001", - "opening_channnel_for_from": "{fromWalletLabel} 지갑의 기금으로 {forWalletLabel} 지갑에 챈널을 개설중", - "are_you_sure_open_channel": "정말 이 챈널을 개설하기 원하십니까?", - "potentialFee": "잠재적 수수료:{fee}", - "remote_host": "원격 호스트", + "payment": "결제", + "placeholder": "인보이스 또는 주소", + "potentialFee": "예상 수수료: {fee}", "refill": "재충전", - "reconnect_peer": "피어 재연결", "refill_create": "진행을 위해서 재충전할 수있는 비트코인 지갑을 만들어주세요.", "refill_external": "외부 지갑으로 재충전합니다.", - "refill_lnd_balance": "라이트닝 월렛잔액 재충전하기", - "sameWalletAsInvoiceError": "청구서를 만든 지갑으로는 해당 청구서를 지불할 수 없습니다.", - "title": "자금 관리하기", - "can_send": "보내기 가능", - "can_receive": "받기가능", - "view_logs": "내역 보기" + "refill_lnd_balance": "라이트닝 지갑 잔고 재충전하기", + "sameWalletAsInvoiceError": "해당 인보이스를 생성한 지갑으로는 결제할 수 없습니다.", + "title": "자금 관리하기" }, "lndViewInvoice": { "additional_info": "추가적인 정보", - "for": "For:", + "for": "용도:", "lightning_invoice": "라이트닝 청구서", - "open_direct_channel": "이 노드로 직접 채널 열기:", "please_pay_between_and": "{min}와 {max} 사이의 금액으로 지불하세요", "please_pay": "지불해주세요.", - "preimage": "사전 이미지/ 역상 이미지", + "preimage": "프리이미지", "sats": "사토시", + "date_time": "날짜와 시간", "wasnt_paid_and_expired": "이 청구서는 미결제되었으며 유효기간이 경과되었습니다." }, "plausibledeniability": { "create_fake_storage": "암호화된 스토리지 만들기", - "create_password": "비밀번호 만들기
", - "create_password_explanation": "fake storage(페이크 저장소)를 위한 패스워드는 메인 저장소의 패스워드와 일치하지 않아야합니다.", - "help": "특정 상황에서 비밀번호를 강제로 밝혀야 할 수 있습니다. 코인의 안전을 위해 블루월렛에서는 다른 암호화 스토리지를 다른 비밀번호로 만들어 놓을 수있습니다. 제 3자에게 비밀번호를 노출해도 이는 페이크 스토리지를 열것입니다. 이는 제 3자들에게는 진짜처럼 보이겠지만 코인은 메인 스토리지에 안전하게 보관되고 있을 것입니다. ", - "help2": "새로운 스토리지가 활성화됩니다. 최소 금액을 넣으면 더 신뢰성있어보입니다. ", + "create_password_explanation": "fake storage(페이크 저장소)를 위한 패스워드는 메인 저장소의 패스워드와 일치하지 않아야 합니다.", + "help": "특정 상황에서 비밀번호를 강제로 밝혀야 할 수 있습니다. 코인의 안전을 위해 블루월렛에서는 다른 암호화 스토리지를 다른 비밀번호로 만들어 놓을 수 있습니다. 제3자에게 비밀번호를 노출해도 이는 페이크 스토리지를 열 것입니다. 이는 제3자들에게는 진짜처럼 보이겠지만 코인은 메인 스토리지에 안전하게 보관되고 있을 것입니다. ", + "help2": "새로운 스토리지가 활성화됩니다. 최소 금액을 넣으면 더 신뢰성 있어 보입니다. ", "password_should_not_match": "이 비밀 번호는 현재 사용중입니다. 다른 비밀번호를 입력해주세요.", - "passwords_do_not_match": "비밀번호가 일치하지 않습니다. 다시 시도해주세요.
", - "retype_password": "패스워드를 다시 넣어세요", - "success": "성공했습니다.", "title": "당위적 거부" }, "pleasebackup": { - "ask": "backup phrase 를 저장해뒀나요? 이 backup phrase는 디바이스를 잃어버렸을 때 자금에 접근하는데 필요합니다. backup phrase가 없다면 영구적으로 자금을 찾을 수 없게 됩니다. ", - "ask_no": "아니요. 없습니다.", - "ask_yes": "예. 있습니다.", - "ok": "네. 적었습니다.", - "ok_lnd": "네 저장했습니다.", + "ask": "backup phrase 를 저장해뒀나요? 이 backup phrase는 디바이스를 잃어버렸을 때 자금에 접근하는데 필요합니다. backup phrase가 없다면 영구적으로 자금을 찾을 수 없게 됩니다. ", + "ask_no": "아니요, 안했어요.", + "ask_yes": "네, 했습니다.", + "ok": "네, 기록했습니다.", + "ok_lnd": "네, 저장했습니다.", "text": "잠시 멈추고 이 mnemonic phrase를 적을 종이를 준비해주세요.\n이건 월렛을 되찾을 때 쓸 예비 백업입니다.", "text_lnd": "이 지갑 백업을 저장하세요. 분실시 지갑을 복구할 수 있게 합니다.", - "title": "월렛이 생성되었습니다." + "title": "지갑이 생성되었습니다." }, "receive": { "details_create": "생성하기", "details_label": "형태", "details_setAmount": "금액명시 후 받기", - "details_share": "나누기", + "details_share": "공유...", + "address_not_found": "입금 주소를 생성할 수 없습니다.", "header": "받기", + "reset": "초기화", "maxSats": "최대금액은 {max} 사토시 입니다", "maxSatsFull": "최대금액은 {max} 사토시 또는 {currency} 입니다", "minSats": "최소금액은 {min} 사토시 입니다", - "minSatsFull": "최소금액은 {min} 사토시 또는 {currency} 입니다" + "minSatsFull": "최소금액은 {min} 사토시 또는 {currency} 입니다", + "qrcode_for_the_address": "주소용 QR코드", + "bip47_explanation": "결제 코드는 지갑 주소를 공개하지 않고도 사용할 수 있는 범용 주소입니다. 모든 서비스에서 지원되는 것은 아닙니다." }, "send": { "provided_address_is_invoice": "이 주소는 라이트닝 청구서로 보입니다. 지불하기 위해 라이트닝 지갑으로 가십시요.", "broadcastButton": "송신", - "broadcastError": "Error", + "broadcastError": "오류", "broadcastNone": "거래 헥스 넣기", "broadcastPending": "보류중", "broadcastSuccess": "성공", @@ -138,8 +131,15 @@ "create_to": "수신", "create_tx_size": "트랜잭션 사이즈", "create_verify": "coinb.in에서 검증", + "details_insert_contact": "연락처 삽입", "details_add_rec_add": "수신자 추가", "details_add_rec_rem": "수신자 빼기", + "details_add_recc_rem_all_alert_description": "모든 수신자를 제거하시겠습니까?", + "details_add_rec_rem_all": "모든 수신자 삭제", + "details_recipients_title": "수신자", + "details_recipient_title": "전체 #{total} 명 중 #{number} 번째 받는 사람", + "please_complete_recipient_title": "미완성된 수신자", + "please_complete_recipient_details": "새로운 받는 사람을 추가하기 전에 받는 사람 #{number} 의 정보를 먼저 입력해 주세요.", "details_address": "주소", "details_address_field_is_not_valid": "유효하지 않은 주소입니다", "details_adv_fee_bump": "수수료 인상 허락하기", @@ -153,25 +153,27 @@ "details_create": "청구서 만들기", "details_error_decode": "비트코인 주소를 해석할 수 없습니다", "details_fee_field_is_not_valid": "유효한 수수료가 아닙니다", - "details_frozen": "{amount} BTC가 동결되어 있습니다", + "details_frozen": "{amount} BTC가 동결되어 있습니다.", "details_next": "다음", - "details_no_signed_tx": "선택된 화일에는 들여오기 가능한 거래내역이 없습니다.", + "details_no_signed_tx": "선택된 파일에는 불러올 수 있는 트랜잭션 내역이 없습니다.", "details_note_placeholder": "자기보관용 노트", "details_scan": "스캔", "details_scan_hint": "목적지를 스캔하거나 들여오기 하기위해서 더블 탭하세요", - "details_total_exceeds_balance": "보내실 금액이 잔고를 초과합니다.", + "details_scan_error": "스캔 에러", + "details_total_exceeds_balance": "전송될 금액이 사용 가능한 잔고를 초과합니다.", "details_total_exceeds_balance_frozen": "보내시는 금액이 잔고를 초월합니다. 동결된 코인은 제외됨에 주의하시기 바랍니다.", - "details_unrecognized_file_format": "화일형식 인식불가", + "details_unrecognized_file_format": "인식할 수 없는 파일 형식", "details_wallet_before_tx": "트랜잭션을 생성하기 전 비트코인 월렛을 먼저 추가해주세요.", "dynamic_init": "초기화 중", "dynamic_next": "다음", "dynamic_prev": "이전", - "dynamic_start": "Start", - "dynamic_stop": "Stop", + "dynamic_start": "시작", + "dynamic_stop": "정지", "fee_10m": "10분", "fee_1d": "1일", "fee_3h": "3시간", "fee_custom": "맞춤형", + "insert_custom_fee": "수수료 입력", "fee_fast": "빠름", "fee_medium": "중간", "fee_replace_minvb": "지불하기 원하시는 총 수수료율 (가상바이트 당 사토시)은 반드시 {min} 가상바이트당 사토시보다 높아야 합니다", @@ -179,61 +181,67 @@ "fee_slow": "느림", "header": "보내기", "input_clear": "해소", - "input_done": "Done", + "input_done": "완료", "input_paste": "붙여넣기", "input_total": "총합계:", - "permission_camera_message": "카메라 사용 허가가 필요합니다.", - "permission_camera_title": "카메라사용 허가", + "permission_camera_message": "카메라 사용 권한이 필요합니다.", "psbt_sign": "거래를 사인하세요", + "invalid_psbt": "유효하지 않은 PSBT입니다.", "open_settings": "설정 열기", - "permission_storage_later": "나중에 물어보기", - "permission_storage_message": "블루월렛이 이 파일을 스토리지에 저장하기 위해서는 사용자의 허가가 필요합니다.", - "permission_storage_denied_message": "BlueWallet이 이 화일을 저장할 수 없습니다. 설정에 가셔서 저장관련 허가설정을 허락가능으로 설정하세요", + "permission_storage_denied_message": "BlueWallet이 이 파일을 저장할 수 없습니다. 기기 설정에서 저장소 권한을 허용해주십시오.", "permission_storage_title": "스토리지 접근 허가", "psbt_clipboard": "클립보드에 복사하기", "psbt_this_is_psbt": "PSBT(서명 비트코인거래) 입니다. 하드웨어월렛으로 서명을 마무리해주세요.", - "psbt_tx_export": "화일로 내보내기", + "psbt_tx_export": "파일로 내보내기", "no_tx_signing_in_progress": "현재 진행중인 트랜잭션 서명이 없습니다. ", "outdated_rate": "최근 요율 갱신: {date}", - "psbt_tx_open": "사인된 거래 열기", - "psbt_tx_scan": "사인된 거래 스캔하기", - "qr_error_no_qrcode": "선택된 이미지에서 QR코드를 인식할 수 없습니다. 이미지에 QR코드를 제외한 다른 내용이 포함되어 있는 지 확인해주세요. ", + "psbt_tx_open": "서명된 거래 열기", + "psbt_tx_scan": "서명된 거래 스캔하기", + "qr_error_no_qrcode": "선택한 이미지에서 유효한 QR 코드를 찾을 수 없습니다. 이미지에 텍스트나 버튼 등 추가 내용 없이 QR 코드만 포함되어 있는지 확인해 주세요.", "reset_amount": "금액 재설정", "reset_amount_confirm": "금액을 재설정하기 원하십니까?", - "success_done": "끝났습니다.", - "txSaved": "트랜잭션 파일이 ({filePath})가 다운로드 폴더에 저장되었습니다.", + "success_done": "완료되었습니다", + "txSaved": "트랜잭션 파일({filePath}) 이 저장되었습니다.", + "file_saved_at_path": "파일({filePath}) 이 저장되었습니다.", + "cant_send_to_silentpayment_adress": "이 지갑은 SilentPayment 주소로 송금할 수 없습니다.", + "cant_send_to_bip47": "이 지갑은 BIP47 결제 코드로 송금할 수 없습니다.", + "cant_find_bip47_notification": "이 결제 코드를 먼저 연락처에 추가해 주세요.", "problem_with_psbt": "PSBT에 문제가 있습니다. " }, "settings": { "about": "더 알아보기", "about_awesome": "제대로 만든", "about_backup": "항상 키를 백업하세요!", - "about_free": "블루 월렛은 비트코인 유저들이 만든 오픈 소스 프로젝트입니다. ", - "about_license": "MIT 면허", - "about_release_notes": "발행 후기", + "about_free": "BlueWallet은 비트코인 이용자들이 만든 오픈 소스 프로젝트입니다. ", + "about_license": "MIT 라이선스", + "about_release_notes": "릴리즈 노트", "about_review": "리뷰를 남겨주세요.", + "performance_score": "성능 점수: {num}", + "run_performance_test": "성능 테스트", "about_selftest": "자가 테스트", + "block_explorer_invalid_custom_url": "입력한 URL이 올바르지 않습니다. http:// 또는 https:// 로 시작하는 URL을 입력해 주세요.", "about_selftest_electrum_disabled": "일렉트럼 오프라인 모드에서는 자가 테스트 기능이 없습니다. 오프라인 모드가 작동하지 않도록 하고 다시 시도해 보세요.", "about_selftest_ok": "모든 내부 테스트를 성공적으로 마쳤습니다. 월렛은 정상적으로 기능합니다.", - "about_sm_github": "기트허브", - "about_sm_discord": "디스코드 서버", - "about_sm_telegram": "텔리그램 챈널", - "about_sm_twitter": "트위터에서 팔로우해주세요.", - "advanced_options": "상급 선택사항", + "about_sm_github": "GitHub", + "about_sm_telegram": "텔레그램 채널", + "privacy_temporary_screenshots": "화면 캡처 허용", + "privacy_temporary_screenshots_instructions": "화면 캡처 보호 기능이 일시적으로 비활성화되어, 스크린샷 및 화면 녹화가 가능해집니다. BlueWallet을 닫았다가 다시 열면 보호 기능이 자동으로 다시 활성화됩니다.", "biometrics": "바이오메트릭", + "biometrics_no_longer_available": "기기의 설정이 변경되어 앱에서 선택한 보안 설정과 일치하지 않습니다. 지문 또는 암호 잠금을 다시 활성화한 후, 앱을 재시작하여 변경 사항을 적용해 주세요.", "biom_10times": "비밀번호를 10번 실패했습니다. 스토리지를 초기화시키겠습니까? 이는 모든 월렛을 지우고 스토리지를 암호화합니다. ", "biom_conf_identity": "개인정보를 확인하세요", - "biom_no_passcode": "디바이스에 패스코드가 없습니다. 진행을 위해 환경 설정 어플에서 패스코드를 설정해주세요.", + "biom_no_passcode": "기기에 암호나 생체 인식이 설정되어 있지 않습니다. 계속하려면 설정 앱에서 암호 또는 생체 인식을 구성해 주세요.", "biom_remove_decrypt": "모든 월렛이 지워지고 스토리지는 암호화됩니다. 진행하시겠습니까?", "currency": "통화", + "currency_source": "환율 정보 제공처:", "currency_fetch_error": "선택된 통화로 된 환율을 가져오는 중 에러발생", - "default_desc": "사용불가 선택시 블루월렛이 실행되면서 선택된 지갑을 바로열기 합니다. ", - "default_info": "기본 정보", "default_title": "실행 시", - "default_wallets": "모든 지갑 보기", + "donate": "기부하기", + "donate_description": "Blue를 무료로 유지할 수 있도록 도와주세요!", "electrum_connected": "연결되었습니다.", "electrum_connected_not": "연결되지 않았습니다.", "electrum_error_connect": "제공된 일렉트럼 서버에 연결할 수 없습니다.", + "electrum_error_connect_tor": "제공된 일렉트럼 서버에 연결할 수 없습니다. Orbot 앱이 연결되어 있는지 확인한 후 다시 시도해 주세요.", "lndhub_uri": "예, {example}", "electrum_host": "예, {example}", "electrum_offline_mode": "오프라인 모드", @@ -242,32 +250,35 @@ "use_ssl": "SSL 사용", "electrum_saved": "변경된 내용이 성공적으로 저장되었습니다. 변경사항이 적용되기 위해선 블루월렛의 재시동이 필요할 수도 있습니다.", "set_electrum_server_as_default": "{server}를 기본 일렉트럼 서버로 설정하시겠습니까?", - "set_lndhub_as_default": "{url}을 기본 LNDHub 서버로 설정하시겠습니까?", + "set_lndhub_as_default": "기본 LNDhub 서버로 {url} 을 설정할까요?", "electrum_settings_server": "일렉트럼 서버", - "electrum_settings_explain": "기본값을 사용하려면 공란으로 남겨주세요", "electrum_status": "상태", - "electrum_clear_alert_title": "내역을 지울까요?", - "electrum_clear_alert_message": "일렉트럼 서버 내역을 지우길 원하십니까?", - "electrum_clear_alert_cancel": "최소하기", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "선택하세요", - "electrum_reset": "기본값으로 환원", + "electrum_preferred_server": "선호 서버", + "electrum_preferred_server_description": "지갑에서 모든 비트코인 활동에 사용할 서버를 입력하세요. 설정 후에는 이 서버만을 통해 잔액 확인, 트랜잭션 전송, 네트워크 데이터 조회가 이루어집니다. 설정 전에 해당 서버를 신뢰할 수 있는지 반드시 확인하세요.", "electrum_unable_to_connect": "{server}에 접속할 수 없음", - "electrum_history": "서버 이력", - "electrum_reset_to_default": "일렉트럼 설정을 기본값으로 환원하길 원하십니까?", - "electrum_clear": "지우기", - "tor_supported": "토르 지원", - "tor_unsupported": "토르 연결이 지원되지 않습니다", - "encrypt_decrypt": "해독 저장", + "electrum_history": "이력", + "electrum_reset_to_default": "서버 목록에서 BlueWallet이 무작위로 서버를 선택하도록 설정됩니다.", + "electrum_reset": "기본값으로 환원", + "electrum_reset_to_default_and_clear_history": "기본값으로 재설정하고 기록을 삭제합니다.", + "encrypt_decrypt": "스토리지 복호화", "encrypt_decrypt_q": "스토리지를 암호화하시겠습니까? 설정 후에는 패스워드 없이 월렛에 접근할 수 없습니다. ", - "encrypt_enc_and_pass": "암호화되고 패스워드로 보호됨", + "encrypt_enc_and_pass": "비밀번호로 보호됨", + "encrypt_storage_explanation_headline": "저장소 암호화 활성화", + "encrypt_storage_explanation_description_line1": "저장소 암호화를 활성화하면 기기에 저장된 데이터를 보다 안전하게 보호하여 앱의 보안 수준이 한층 강화됩니다. 이를 통해 허가받지 않은 접근으로부터 정보를 더욱 안전하게 지킬 수 있습니다.", + "encrypt_storage_explanation_description_line2": "다만, 이 암호화는 기기의 키체인에 저장된 지갑에 대한 접근만을 보호한다는 점을 알아두세요. 지갑 자체에 비밀번호나 추가적인 보호 기능이 적용되는 것은 아닙니다.", + "i_understand": "이해했습니다", + "block_explorer": "블록 탐색기", + "block_explorer_preferred": "선호하는 블록 탐색기 사용", + "block_explorer_error_saving_custom": "선호 블록 탐색기 저장 오류", "encrypt_title": "보안", "encrypt_tstorage": "스토리지", "encrypt_use": "{type} 사용", - "encrypt_use_expl": "거래를 하거나 지갑을 해제, 내보내기, 또는 지우기 전에 신분확인을 위해 {type}이 사용될 것입니다. 암호화된 저장장치를 해제하는되는 사용되지 않을 것 입니다.", + "set_as_preferred": "선호 서버로 설정", + "set_as_preferred_electrum": "{host}:{port} 를 선호 서버로 설정하면, 무작위로 추천 서버에 연결하는 기능이 비활성화됩니다.", + "encrypted_feature_disabled": "이 기능은 저장소 암호화가 활성화된 상태에서는 사용할 수 없습니다.", + "encrypt_use_expl": "거래를 생성하거나, 잠금 해제, 내보내기, 지갑 삭제를 하기 전에 본인 확인을 위해 {type}이(가) 사용됩니다.", + "biometrics_fail": "{type} 이 활성화되지 않았거나 잠금 해제에 실패한 경우, 기기의 암호를 대안으로 사용할 수 있습니다.", "general": "일반", - "general_adv_mode": "고급 모드", - "general_adv_mode_e": "사용가능으로 설정시, 다른 지갑 형태, 연결하고픈 LNDHub 인스턴스를 지정할 수 있는 능력, 그리고 지갑생성시 맞춤형 엔트로피등과 같은 고급 선택사항을 볼 수 있습니다.", "general_continuity": "연속성", "general_continuity_e": "사용가능으로 설정시, 애플 아이클라우드에 연결된 다른 기기을 사용하여 선택된 지갑들과 거래내역을 보실수 있습니다.", "groundcontrol_explanation": "그라운드콘트롤은 비트코인 지갑을 위한 무상, 원본공개형 알림장치 서버 입니다.", @@ -275,36 +286,37 @@ "language": "언어", "last_updated": "마지막 갱신", "language_isRTL": "선택된 언어가 나타나기 위해선 블루월렛의 재시동이 요구 됩니다.", - "lightning_error_lndhub_uri": "무효 LNDhub URI", + "license": "라이선스", + "lightning_error_lndhub_uri": "잘못된 LNDhub URI", + "lightning_error_lndhub_uri_tor": "잘못된 LNDhub URI입니다. Orbot 앱이 연결되어 있는지 확인한 후 다시 시도해 주세요.", "lightning_saved": "변경된 사항이 성공적으로 저장되었습니다.", "lightning_settings": "라이트닝 설정", - "tor_settings": "토르 설정", - "lightning_settings_explain": "자신이 직접운영하는 LND 노드에 연결하기위해선 LNDHub를 설치하고 그 URL을 여기 설정에 넣어십시요. 블루월렛에서 제공하는 LNDHub를 사용하실 경우는 공란으로 남겨주세요. 변경사항이 저장된 후 생선된 지갑들 만이 지정된 LNDHub에 연결됨을 주의하시기 바랍니다.", + "lightning_settings_explain": "자신의 LND 노드에 연결하려면 LNDhub를 설치한 후 해당 URL을 설정에 입력하세요. 이때, 변경 사항을 저장한 이후에 생성된 지갑만 지정한 LNDhub에 연결됩니다.", + "lndhub_github": "Github 리포지토리", "network": "네트워크", "network_broadcast": "거래내역 송출", "network_electrum": "일렉트럼 서버", + "electrum_suggested_description": "선호 서버가 설정되지 않은 경우, 추천 서버 중 하나가 무작위로 선택되어 사용됩니다.", "not_a_valid_uri": "무효 URI", "notifications": "공지사항", - "open_link_in_explorer": "익스플로어세서 링크 열기", + "open_link_in_explorer": "익스플로러에서 링크 열기", "password": "패스워드", - "password_explain": "저장장치를 해독할 때 사용할 패스워드 만들기", - "passwords_do_not_match": "비밀번호가 일치하지 않습니다.", + "password_explain": "저장소 잠금을 해제하는 데 사용할 비밀번호를 입력하세요.", "plausible_deniability": "당위적 부인", "privacy": "프라이버시", "privacy_read_clipboard": "읽기용 클립보드", "privacy_system_settings": "시스템 설정", "privacy_quickactions": "지갑 단축경로", - "privacy_quickactions_explanation": "지갑의 잔고를 신속 확인하기 위해서 바탕화면에 있는 블루월렛 어플 아이콘에 한동안 눌러주기를 하세요", + "privacy_quickactions_explanation": "BlueWallet 앱 아이콘을 길게 눌러 지갑의 잔액을 빠르게 확인하세요.", "privacy_clipboard_explanation": "클립보드에 주소나 청구서가 보이면 단축경로를 제공하세요", "privacy_do_not_track": "분석을 사용불가로", "privacy_do_not_track_explanation": "성능과 신뢰성 정보는 분석하는 데 제출되지 않을 것 입니다.", - "push_notifications": "푸시 알림", "rate": "환율", - "retype_password": "패스워드를 다시 넣어주세요", + "push_notifications_explanation": "알림을 활성화하면, 지갑 주소와 트랜잭션 ID를 포함한 기기 토큰이 서버로 전송됩니다. 이는 비트코인 수신이나 트랜잭션 확정과 같은 알림을 보내기 위함입니다.\n\n알림을 활성화한 이후의 정보만 전송되며, 이전 데이터는 수집되지 않습니다.\n\n알림을 비활성화하면 이 정보는 서버에서 삭제됩니다. 또한, 앱에서 지갑을 삭제하면 해당 지갑과 관련된 정보도 함께 서버에서 제거됩니다.", "selfTest": "자가 테스트", "save": "저장", "saved": "저장됨", - "success_transaction_broadcasted": "성공! 거래내역이 송출되었습니다.", + "success_transaction_broadcasted": "트랜잭션이 성공적으로 브로드캐스트되었습니다!", "total_balance": "총 잔고", "total_balance_explanation": "홈 화면 위젯에 총 잔액을 보여줍니다. ", "widgets": "위젯", @@ -312,15 +324,17 @@ }, "notifications": { "would_you_like_to_receive_notifications": "입금이 있을 때 알림경고를 받으시겠습니까?", - "no_and_dont_ask": "아니오, 다시 묻기 없기", - "ask_me_later": "나중에 묻기" + "notifications_subtitle": "수신 결제 및 트랜잭션 확정 알림", + "no_and_dont_ask": "아니요, 다시 묻지 마세요.", + "permission_denied_message": "알림 권한이 거부되었습니다. 알림을 받으려면 기기 설정에서 알림을 활성화해 주세요." }, "transactions": { "cancel_explain": "더 높은 수수료율로 사용자님께 지불되도록 하는 거래내역으로 당 거래내역을 교체하려고 합니다. 결과적으로 현 거래내역을 취소하는 것과 같습니다. 이런 방법을 RBF라고 합니다. - 수수료를 통한 교체", "cancel_no": "이 거래내역은 교체불가 입니다.", "cancel_title": "해당 트랜잭션 취소하기 (RBF)", + "transaction_loading_error": "트랜잭션을 불러오는 데 문제가 발생했습니다. 나중에 다시 시도해 주세요.", + "transaction_not_available": "이 트랜잭션을 사용할 수 없습니다.", "confirmations_lowercase": "{confirmations}번 확인", - "copy_link": "링크 복사", "expand_note": "노트 확장하기", "cpfp_create": "생성하기", "cpfp_exp": "확정전 거래를 지출할 수 있도록 또다른 거래내역을 만듭니다. 더 빨리 채굴되도록 총 수수료가 원래거래의 수수료로 보다 높게 책정될 것입니다.", @@ -328,22 +342,25 @@ "cpfp_title": "급행 수수료(CPFP)", "details_balance_hide": "잔고 감추기", "details_balance_show": "잔고 보여주기", - "details_block": "블록 높이", "details_copy": "복사", - "details_copy_amount": "복사 금액", "details_copy_block_explorer_link": "블록 익스플로러 링크 복사하기", "details_copy_note": "노트 복사하기", "details_copy_txid": "거래 아이디 복사하기", - "details_from": "입력", "details_inputs": "입력", "details_outputs": "출력", + "date": "날짜", "details_received": "받기 완료", - "transaction_note_saved": "거래 노트가 성공적으로 저장되었습니다.", - "details_show_in_block_explorer": "블록 익스플로러에서 보기", + "details_view_in_browser": "브라우저에서 보기", "details_title": "트랜잭션", + "incoming_transaction": "수신 트랜잭션", + "outgoing_transaction": "전송 트랜잭션", + "expired_transaction": "만료된 트랜잭션", + "pending_transaction": "대기 중인 트랜잭션", + "offchain": "오프체인", + "onchain": "온체인", "details_to": "출력", "enable_offline_signing": "이 지갑은 오프라인 서명과 함께 쓸수 없습니다. 지금 가능하도록 할까요?", - "list_conf": "확정획수: {number}", + "list_conf": "확정 횟수: {number}", "pending": "보류중", "pending_with_amount": "보류중 {amt1} ({amt2})", "received_with_amount": "+{amt1} ({amt2})", @@ -351,123 +368,184 @@ "eta_3h": "예상경과시간: 약 3시간", "eta_1d": "예상경과시간: 약 1일", "list_title": "트랜잭션", + "list_title_sent": "보냄", + "list_title_received": "받기 완료", + "transaction": "트랜잭션", "open_url_error": "기본 브라우저로는 링크를 열수 없습니다. 기존 브라우저를 바꾼다음에 다시 시도해 보시기 바랍니다.", - "rbf_explain": "빨이 채굴될 수 있도록 당 거래내역을 더 높은 수수료율을 내는 거래내역으로 교체하겠습니다. 이런 방법을 RBF라고 합니다 -수수료를 통한 교체", - "rbf_title": "급행 수수료(CPFP)", + "rbf_explain": "빨리 채굴될 수 있도록 당 거래내역을 더 높은 수수료율을 내는 거래내역으로 교체하겠습니다. 이런 방법을 RBF라고 합니다 - 수수료를 통한 교체", + "rbf_title": "급행 수수료(RBF)", "status_bump": "급행 수수료", "status_cancel": "트랜잭션 취소", "transactions_count": "거래 건수", "txid": "트랜잭션 아이디", - "updating": "갱신중..." + "updating": "갱신중...", + "watchOnlyWarningTitle": "보안 경고", + "watchOnlyWarningDescription": "사기꾼들은 종종 ‘조회 전용’ 지갑을 사용해 사용자를 속입니다. 이러한 지갑은 자금을 송금하거나 제어할 수 없고 잔액 조회만 가능합니다.", + "custom_fee_warning_title": "경고", + "custom_fee_warning_description": "1 sat/vB 이하의 수수료도 유효하지만, 노드의 정책에 따라 트랜잭션이 릴레이되지 않을 수 있습니다.", + "details_eta_analyzing": "분석 중...", + "details_sent": "보냄", + "details_section": "세부 정보", + "details_explorer": "탐색기", + "details_network_fee": "네트워크 수수료", + "details_to_address": "받는 주소", + "details_id": "ID", + "details_note": "노트", + "details_add_note": "추가", + "details_advanced": "고급", + "details_fee_rate": "수수료율", + "details_size": "크기", + "details_virtual_size": "가상 크기", + "details_tx_hex": "트랜잭션 Hex", + "details_inputs_count": "입력 ({count})", + "details_outputs_count": "출력 ({count})" }, "wallets": { "add_bitcoin": "비트코인", "add_bitcoin_explain": "단순하고 강력한 비트코인 지갑", "add_create": "생성하기", + "total_balance": "총 잔액", + "add_entropy_reset_title": "엔트로피 초기화", + "add_entropy_reset_message": "지갑 유형을 변경하면 현재 엔트로피가 초기화됩니다. 계속하시겠습니까?", + "add_entropy": "엔트로피", + "add_entropy_bytes": "{bytes} 바이트의 엔트로피", "add_entropy_generated": "{gen} 바이트 엔트로피", "add_entropy_provide": "주사위 굴림식 엔트로피 부여", "add_entropy_remain": "{gen} 바이트 엔트로피. 남은 {rem} 바이트는 시스템 난수발생기를 통해 얻어질 것 입니다.", "add_import_wallet": "지갑 들여오기", "add_lightning": "라이트닝", "add_lightning_explain": "즉시 거래를 통한 지출", - "add_lndhub": "사용자 LNDHub 연결", - "add_lndhub_error": "제공된 노드 주소는 유효한 LNDHub 노드가 아닙니다.", + "add_lndhub": "LNDhub에 연결", + "add_lndhub_error": "제공된 노드 주소가 유효한 LNDhub 노드가 아닙니다.", "add_lndhub_placeholder": "사용자 노드 주소", "add_placeholder": "내 첫 지갑", "add_title": "지갑 추가", "add_wallet_name": "이름", "add_wallet_type": "형식", - "balance": "잔액", + "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_address": "주소", "details_advanced": "고급", "details_are_you_sure": "정말 맞습니까?", "details_connected_to": "연결됨", - "details_del_wb_err": "제공된 잔고가 이 지갑의 잔고와 일치하지 않습니다. 다시 시도해보세요.", + "details_del_wb_err": "입력한 잔액이 이 지갑의 잔액과 일치하지 않습니다. 다시 시도해 주세요.", "details_del_wb_q": "이 지갑에는 아직 잔고가 남아 있습니다. 계속하시기 전에 지갑의 종자 단어들이 없으면 남아있는 기금을 다시 찾을 수 없음을 인지하시기 바랍니다. 원치않게 제거되는 것을 피하기 위해 {balance} 사토시로 표시된 지갑 잔고를 입력하세요.", "details_delete": "지움", "details_delete_wallet": "지갑 지우기", "details_derivation_path": "유도 경로", - "details_display": "지갑 목록에 표시", + "details_display": "홈 화면에 표시", "details_export_backup": "보내기/백업", + "details_export_history": "기록을 CSV로 내보내기", "details_master_fingerprint": "마스터 지문", "details_multisig_type": "다중서명", - "details_no_cancel": "아니요. 취소하겠습니다", - "details_save": "저장하기", "details_show_xpub": "지갑의 XPUB 보이기", "details_show_addresses": "주소 보이기", "details_title": "지갑", + "wallets": "지갑", + "swipe_balance_hide": "숨기기", + "swipe_balance_show": "보이기", + "drag_to_reorder": "끌어서 재정렬", + "clear_search": "검색 지우기", "details_type": "형태", "details_use_with_hardware_wallet": "하드웨어 지갑 사용하기", - "details_wallet_updated": "지갑 갱신완료", "details_yes_delete": "네 삭제합니다.", "enter_bip38_password": "해독하기 위해 패스워드 입력", "export_title": "지갑 내보내기", "import_do_import": "들여오기", "import_passphrase": "암호", "import_passphrase_title": "암호", - "import_passphrase_message": "사용하신적 있으시면 암호를 넣어세요", + "import_passphrase_message": "사용하신 적 있으시면 암호를 넣으세요", "import_error": "들여오기 실패. 제공한 데이타가 유효한지 확인하세요.", "import_explanation": "종자단어, 공용 키, WIF, 아니면 소지하신 어떤거라도 입력해 주세요. 블루월렛이 맞는 형식을 추정해서 지갑을 들여오기 할 수 있도록 최선을 다할 것입니다.", "import_imported": "들여오기 완료", "import_scan_qr": "스캔 또는 화일 들여오기", "import_success": "사용자 지갑의 성공적인 들여오기가 완료되었습니다.", + "import_success_watchonly": "지갑이 성공적으로 가져와졌습니다. 경고: 이 지갑은 조회 전용 지갑이며, 송금할 수 없습니다.", "import_search_accounts": "계좌 검색", "import_title": "들여오기", + "learn_more": "더 알아보기", "import_discovery_title": "발견", "import_discovery_subtitle": "발견된 지갑 선택", "import_discovery_derivation": "맞춤형 유도경로 사용", "import_discovery_no_wallets": "지갑이 없습니다.", - "import_derivation_found": "발견", - "import_derivation_found_not": "미발견", - "import_derivation_loading": "실어오는 중...", - "import_derivation_subtitle": "맞춤형 유도경로를 입력하시면 사용자의 지갑을 찾아보도록 하겠습니다.", + "import_discovery_offline": "BlueWallet이 현재 오프라인 모드입니다. 이 모드에서는 지갑 존재 여부를 확인할 수 없으므로, 수동으로 올바른 지갑을 선택해야 합니다.", + "import_derivation_found": "찾음", + "import_derivation_found_not": "찾을 수 없음", + "import_derivation_loading": "불러오는 중...", + "import_derivation_subtitle": "사용자 정의 파생 경로를 입력하면 지갑을 탐색해봅니다.", "import_derivation_title": "유도 경로", - "import_derivation_unknown": "미상", - "import_wrong_path": "잘못된 유도경로", + "import_derivation_unknown": "알 수 없음", + "import_wrong_path": "잘못된 파생 경로", "list_create_a_button": "지금 추가하기", "list_create_a_wallet": "지갑 추가하기", - "list_create_a_wallet_text": "무료이고 원하시는 만큼 만드실 수 있습니다.", + "list_create_a_wallet_text": "무료이며\n원하는 만큼 만들 수 있습니다.", "list_empty_txs1": "사용자 거래내역은 여기에 표시됩니다.", "list_empty_txs1_lightning": "라이트닝 월렛은 데일리 트랜잭션에 사용해야합니다. 수수료는 매우 저렴하고 속도는 바람처럼 빠릅니다.", "list_empty_txs2": "사용자 지갑으로 시작", "list_empty_txs2_lightning": "\n사용하시려면 기금관리를 탭하신다음 잔고를 더해 주세요.", "list_latest_transaction": "최근 트랜잭션", - "list_ln_browser": "랩 브라우저", "list_long_choose": "사진 선택하기", - "list_long_clipboard": "클립보드에서 복사", + "paste_from_clipboard": "붙여넣기", + "import_file": "파일 들여오기", "list_long_scan": "QR 코드 스캔", "list_title": "지갑", "list_tryagain": "다시 시도하기", "no_ln_wallet_error": "라이트닝 청구서를 지불하기 전 먼저 라이트닝 지갑을 추가해야 합니다.", "looks_like_bip38": "이것은 패스워드 보호된 비밀키(BIP38)로 보입니다.", - "reorder_title": "지갑 재정렬", - "reorder_instructions": "리스트를 넘겨 끌어가려면 지갑에 계속 누르기를 하세요", + "manage_title": "지갑 관리", + "no_results_found": "검색 결과가 없습니다.", "please_continue_scanning": "계속 스캔하세요.", "select_no_bitcoin": "현재 사용 가능한 비트코인 월렛이 없습니다.", "select_no_bitcoin_exp": "라이트닝 지갑을 재충전하시려면 비트코인 지갑이 필요합니다. 새로 만들기 또는 들여오기를 하시기바랍니다.", "select_wallet": "지갑 선택", - "xpub_copiedToClipboard": "클립보드에 복사완료", "pull_to_refresh": "갱신하려면 당기세요", - "warning_do_not_disclose": "경고! 공개하지 마십시오.", + "warning_do_not_disclose": "아래 정보를 절대로 공유하지 마세요", + "scan_import": "다른 앱에서 이 지갑을 가져오려면 이 QR 코드를 스캔하세요.", + "write_down_header": "수동 백업 생성", + "write_down": "이 단어들을 적어두고 안전하게 보관하세요. 나중에 지갑을 복원할 때 사용합니다.", + "wallet_type_this": "이 지갑의 유형은 {type} 입니다.", + "share_number": "{number} 공유", + "copy_ln_url": "이 URL을 복사해 안전하게 보관하세요. 나중에 지갑을 복원할 때 사용됩니다.", + "copy_ln_public": "이 정보를 복사해 안전하게 보관하세요. 나중에 지갑을 복원할 때 사용됩니다.", "add_ln_wallet_first": "먼저 라이트닝 월렛을 추가해야합니다.", "identity_pubkey": "아이덴티티 퍼브키", - "xpub_title": "지갑 XPUB" + "xpub_title": "지갑 XPUB", + "manage_wallets_search_placeholder": "지갑, 주소, 트랜잭션 및 메모 검색", + "more_info": "자세히 보기", + "details_delete_wallet_error_message": "이 지갑이 알림에서 제거되었는지 확인하는 데 문제가 발생했습니다. 네트워크 문제나 연결 불안정이 원인일 수 있습니다. 계속 진행하면, 이 지갑을 삭제한 이후에도 관련 트랜잭션 알림을 받을 수 있습니다.", + "details_delete_anyway": "그래도 삭제" + }, + "total_balance_view": { + "display_in_bitcoin": "비트코인으로 표시", + "hide": "숨기기", + "display_in_sats": "sats 단위로 표시", + "display_in_fiat": "{currency} 로 표시", + "title": "총 잔액", + "explanation": "모든 지갑의 총 잔액을 개요 화면에서 확인하세요." }, "multisig": { - "multisig_vault": "금고", + "multisig_vault": "다중서명 금고", "default_label": "다중서명 금고", "multisig_vault_explain": "대규모 금액을 위한 최고의 보안", "provide_signature": "서명 제공하기", + "provide_signature_details": "이 트랜잭션에 서명하려면 키가 저장된 기기와 지갑을 사용하세요.", + "provide_signature_details_bluewallet": "BlueWallet에서 보내기 화면 메뉴로 이동한 후 선택하세요", + "provide_signature_next_steps": "서명된 트랜잭션 스캔 또는 가져오기", + "provide_signature_next_steps_details": "지갑에서 트랜잭션에 성공적으로 서명한 후, 제공된 QR 코드를 스캔하거나 첨부된 파일을 가져와 모든 트랜잭션 내역을 확인한 후 브로드캐스트하세요.", "vault_key": "금고 키 {number}", "required_keys_out_of_total": "총 갯수중 요구되는 키의 수", "fee": "수수료: {number}", "fee_btc": "{number} BTC", "confirm": "컨펌", "header": "보내기", - "share": "공유하기", + "share": "공유...", "view": "보기", + "shared_key_detected": "공동 서명자 공유됨", + "shared_key_detected_question": "공동 서명자가 공유되었습니다. 가져오시겠습니까?", "manage_keys": "키 관리", "how_many_signatures_can_bluewallet_make": "블루월렛은 몇 개의 서명을 만들 수 있습니까", "signatures_required_to_spend": "서명이 필요합니다 {number}", @@ -478,7 +556,7 @@ "lets_start": "시작", "create": "생성하기", "native_segwit_title": "최선책", - "wrapped_segwit_title": "최고 화환성", + "wrapped_segwit_title": "최고 호환성", "legacy_title": "레거시", "co_sign_transaction": "거래 서명하기", "what_is_vault": "금고는", @@ -493,24 +571,25 @@ "quorum_header": "정족수", "of": "의", "wallet_type": "지갑 형태", - "invalid_mnemonics": "이 연상문구는 유효하지 않아보입니다.", - "invalid_cosigner": "무효한 공동서명 데이터", + "invalid_mnemonics": "이 니모닉 문구는 유효하지 않은 것 같습니다.", + "invalid_cosigner": "유효하지 않은 공동 서명자 데이터", "not_a_multisignature_xpub": "다중 서명 지갑에서 나온 XPUB이 아닙니다.", - "invalid_cosigner_format": "틀린 공동서명자: {format} 형식의 공동서명이 아닙니다.", + "invalid_cosigner_format": "잘못된 공동 서명자: {format} 의 공동 서명자가 아닙니다.", "create_new_key": "새로 생성", "scan_or_open_file": "스캔하거나 화일 들여오기", "i_have_mnemonics": "이 키에 맞는 종자단어를 가지고 있습니다.", - "type_your_mnemonics": "이미 있는 금고키를 들여오기 위해 종자단어를 넣어세요.", - "this_is_cosigners_xpub": "공동서명자의 XPUB입니다-다른 지갑으로 들여오기할 수 있는 상태. 공유하기를 하셔도 안전합니다.", + "type_your_mnemonics": "이미 있는 금고키를 들여오기 위해 종자단어를 넣으세요.", + "this_is_cosigners_xpub": "이것은 공동 서명자의 XPUB이며, 다른 지갑에 가져올 준비가 되어 있습니다. 공유해도 안전합니다.", + "this_is_cosigners_xpub_airdrop": "AirDrop으로 공유하려면 받는 사람이 조율 화면에 있어야 합니다.", "wallet_key_created": "금고 키가 생성되었습니다. 시간을 내서 사용자의 종자단어를 백업하세요.", "are_you_sure_seed_will_be_lost": "이대로 진행하시겠습니까? 백업하지 않을 시 니모닉 시드(menonic seed)를 잃을 수 있습니다. ", "forget_this_seed": "이 시드를 잊어버리고 대신 공개 키를 사용하십시오.", "view_edit_cosigners": "공동 서명자 보기/편집", - "this_cosigner_is_already_imported": "이 공동 서명자가 이미 있습니다.", + "this_cosigner_is_already_imported": "이 공동 서명자는 이미 가져와졌습니다.", "export_signed_psbt": "서명 된 PSBT 내보내기", "input_fp": "지문 입력", "input_fp_explain": "기본값 (00000000) 사용 건너뛰기", - "input_path": "유도경료를 넣어세요", + "input_path": "유도 경로를 넣으세요", "input_path_explain": "기본값 ({default})을 사용 건너뛰기", "ms_help": "도움말", "ms_help_title": "다중 서명 저장소 작동 방식 : 팁과 요령", @@ -532,20 +611,33 @@ "enter_address": "주소 입력", "check_address": "주소 확인", "no_wallet_owns_address": "제공된 주소를 가지고 있는 지갑이 없습니다.", - "view_qrcode": "QR코드 보기" + "view_qrcode": "QR 코드 보기" + }, + "autofill_word": { + "title": "시드 마지막 단어", + "enter": "부분 니모닉 문구 입력", + "generate_word": "마지막 단어 생성", + "error": "입력값이 11개 또는 23개의 니모닉 단어가 아닙니다. 다시 시도해 주세요." }, "cc": { "change": "변경", "coins_selected": "선택 된 코인 ({number})", "selected_summ": "{value} 선택됨", - "empty": "이 지갑에는 현재 코인이 없습니다.", + "empty": "이 지갑에는 현재 보유 중인 코인이 없습니다.", "freeze": "동결", "freezeLabel": "동결", "freezeLabel_un": "동결해제", "header": "코인 관리", "use_coin": "코인 사용", "use_coins": "코인 사용", - "tip": "이 기능을 사용하면 코인을 확인, 표시, 동결 또는 선택하여 지갑 관리를 개선 할 수 있습니다. 색칠 된 원을 선택하여 여러 코인을 선택할 수 있습니다." + "tip": "이 기능을 사용하면 코인을 확인, 표시, 동결 또는 선택하여 지갑 관리를 개선 할 수 있습니다. 색칠 된 원을 선택하여 여러 코인을 선택할 수 있습니다.", + "sort_asc": "오름차순", + "sort_desc": "내림차순", + "sort_height": "높이", + "sort_value": "값", + "sort_label": "라벨", + "sort_status": "상태", + "sort_by": "정렬" }, "units": { "BTC": "비트코인", @@ -554,6 +646,8 @@ "sats": "사토시" }, "addresses": { + "copy_private_key": "개인 키 복사", + "sensitive_private_key": "경고: 개인 키는 매우 민감한 정보입니다. 계속하시겠습니까?", "sign_title": "메시지 사인/검증", "sign_help": "여기서 비트코인 주소에 기반한 암호화 서명을 만들거나 검증할 수 있습니다.", "sign_sign": "서명", @@ -575,7 +669,7 @@ "register_answer": "{hostname}에 계정을 성공적으로 등록했습니다.", "login_question_part_1": "로그인 하시겠습니까", "login_question_part_2": "라이트닝 지갑을 사용할까요?", - "login_answer": "{hostname}에 계정을 성공적으로 등록했습니다.", + "login_answer": "{hostname}에 성공적으로 로그인했습니다.", "link_question_part_1": "계정을 연결하시겠습니까", "link_question_part_2": "라이트닝 지갑으로", "link_answer": "라이트닝 지갑이 성공적으로 {hostname}에 있는 계정으로 연결되었습니다.", @@ -584,5 +678,27 @@ "auth_answer": "{hostname}에 성공적으로 사용자 확인이 되었습니다.", "could_not_auth": "{hostname}에 사용자 확인을 할 수 없었습니다.", "authenticate": "사용자 확인" + }, + "bip47": { + "payment_code": "결제 코드", + "contacts": "연락처", + "bip47_explain": "재사용 및 공유 가능한 코드", + "bip47_explain_subtitle": "BIP47", + "purpose": "재사용 및 공유 가능한 코드 (BIP47)", + "pay_this_contact": "이 연락처에게 결제하기", + "rename_contact": "연락처 이름 변경", + "copy_payment_code": "결제 코드 복사", + "hide_contact": "연락처 숨기기", + "rename": "이름 변경", + "provide_name": "이 연락처의 새 이름을 입력하세요", + "add_contact": "연락처 추가", + "provide_payment_code": "결제 코드를 입력하세요", + "invalid_pc": "유효하지 않은 결제 코드", + "notification_tx_unconfirmed": "알림 트랜잭션이 아직 확정되지 않았습니다. 잠시만 기다려 주세요.", + "failed_create_notif_tx": "온체인 트랜잭션 생성에 실패했습니다", + "onchain_tx_needed": "온체인 트랜잭션이 필요합니다", + "notif_tx_sent": "알림 트랜잭션이 전송되었습니다. 확정될 때까지 기다려 주세요", + "notif_tx": "알림 트랜잭션", + "not_found": "결제코드를 찾을 수 없습니다" } } diff --git a/loc/languages.ts b/loc/languages.ts index 1aa87e8af50..e1bb3ccc81c 100644 --- a/loc/languages.ts +++ b/loc/languages.ts @@ -1,4 +1,11 @@ -export const AvailableLanguages = Object.freeze([ +export type TLanguage = { + label: string; + value: string; + isRTL?: boolean; +}; + +// Literal-typed tuple so `LangCode` is a literal union; widened on re-export. +const _availableLanguages = Object.freeze([ { label: 'English', value: 'en' }, { label: 'Afrikaans (AFR)', value: 'zar_afr' }, { label: 'العربية (AR)', value: 'ar', isRTL: true }, @@ -17,16 +24,21 @@ export const AvailableLanguages = Object.freeze([ { label: 'Eesti (ET)', value: 'et' }, { label: 'Ελληνικά (EL)', value: 'el' }, { label: 'فارسی (FA)', value: 'fa', isRTL: true }, + { label: 'لۊری بختیاری (BQI)', value: 'bqi', isRTL: true }, + { label: 'لٛۏری شومالی (LRC)', value: 'lrc', isRTL: true }, { label: 'Français (FR)', value: 'fr_fr' }, + { label: 'Føroyskt (FO)', value: 'fo' }, { label: 'עִברִית (HE)', value: 'he', isRTL: true }, { label: 'Italiano (IT)', value: 'it' }, { label: 'Indonesia (ID)', value: 'id_id' }, + { label: 'Қазақ (KK)', value: 'kk@Cyrl' }, { label: 'Magyar (HU)', value: 'hu_hu' }, { label: '日本語 (JP)', value: 'jp_jp' }, { label: '한국어 (KO)', value: 'ko_kr' }, { label: 'ಕನ್ನಡ (KN)', value: 'kn' }, { label: 'Bahasa Melayu (MS)', value: 'ms' }, { label: 'Nederlands (NL)', value: 'nl_nl' }, + { label: 'Nigerian Pidgin (NG)', value: 'pcm' }, { label: 'नेपाली (NE)', value: 'ne' }, { label: 'Norsk (NB)', value: 'nb_no' }, { label: 'Polski (PL)', value: 'pl' }, @@ -38,6 +50,7 @@ export const AvailableLanguages = Object.freeze([ { label: 'Српски (SR)', value: 'sr_rs' }, { label: 'Slovenský (SK)', value: 'sk_sk' }, { label: 'Slovenščina (SL)', value: 'sl_si' }, + { label: 'Shqip (SQ)', value: 'sq_AL' }, { label: 'Suomi (FI)', value: 'fi_fi' }, { label: 'Svenska (SE)', value: 'sv_se' }, { label: 'Thai (TH)', value: 'th_th' }, @@ -45,4 +58,9 @@ export const AvailableLanguages = Object.freeze([ { label: 'Українська (UA)', value: 'ua' }, { label: 'Türkçe (TR)', value: 'tr_tr' }, { label: 'Xhosa (XHO)', value: 'zar_xho' }, -]); +] as const) satisfies readonly TLanguage[]; + +export const AvailableLanguages: readonly TLanguage[] = _availableLanguages; + +// Drives the typed loader Record in loc/index.ts so drift becomes a TS error. +export type LangCode = (typeof _availableLanguages)[number]['value']; diff --git a/loc/lrc.json b/loc/lrc.json new file mode 100644 index 00000000000..ac1b2d272c1 --- /dev/null +++ b/loc/lrc.json @@ -0,0 +1,704 @@ +{ + "_": { + "cancel": "لقو", + "continue": "ادامه", + "clipboard": "ویرگه", + "enter_password": "رزمن بزݩ", + "of": "{number} د {total}", + "ok": "خو", + "enter_url": "نشونی اینترنتی ن بزݩ", + "yes": "ٱ", + "no": "ن", + "seed": "سید", + "success": "مووفق بی", + "wallet_key": "کلٛیل کیف پیلٛ", + "close": "بستن", + "refresh": "وانۊ کردن", + "pick_image": "د کتاو هونه پسند بکؽتوݩ", + "pick_file": "پسند فایل", + "enter_amount": "مقدارن بزݩ", + "qr_custom_input_button": "سی زؽن وۊرۊڌی سفارشی، 10 گلٛ ریش بزݩ", + "unlock": "واز کردن چفت", + "port": "پورت", + "ssl_port": "پورت SSL", + "save": "زخیره...", + "bad_password": "رزم نادرس. لطفا دۏورته تفره بکوݩ.", + "copied": "لٛف گری بی!", + "discard_changes": "آلشتیا ن وردارؽم؟", + "discard_changes_explain": "آلشتیا زخیره نبیه ای داری. ٱطمیون داری مۊخوای ٱنانه وردارؽ ۉ ز اؽ بلگه د بائ؟", + "never": "هرگز", + "storage_is_encrypted": "جاگه زفت کردنیا تو زفت بیه. سی رزم گوشایی شی رزم وا ئه.", + "change_input_currency": "آلشت واهد پیلٛ وۊرۊڌی", + "suggested": "پیشنهاد بیه" + }, + "azteco": { + "codeIs": "کود تخفیف شما", + "errorSomething": "موشکلؽ پؽش اوما. اؽ کود تخفیف هنی قوۊل هؽ؟", + "redeem": "ازاف کردن و کیف پیلٛ", + "redeemButton": "فعال کردن", + "success": "مووفق بی", + "title": "فعال کردن کود تخفیف Azte.co", + "errorBeforeRefeem": "پؽش ز فعال کردن، ٱول وا ی کیف پیلٛ بیت کوین ازاف بکوݩ.", + "successMessage": "کود تخفیف وا مووفقیت فعال بی! پیلٛ تو وا تری د کیف پیلٛ بیت کوین تو مۊرسه." + }, + "entropy": { + "title": "آنتروپی", + "save": "زخیره", + "undo": "وورگشت و هالت پؽشی", + "amountOfEntropy": "{bits} د {limit} بیت" + }, + "errors": { + "error": "ختا", + "network": "ختا شبکه", + "broadcast": "انتشار نا مووفق بی." + }, + "lnd": { + "errorInvoiceExpired": "سۊرت هساو مونقزی بیه.", + "expired": "مونقزی بیه", + "payButton": "پرداخت", + "payment": "پرداخت", + "placeholder": "سۊرت هساو یا آدرس", + "potentialFee": "کارمزد ائتمالی: {fee}", + "refill": "پور کردن", + "refill_external": "پور کردن وا کیف پیلٛ خارجی", + "refill_lnd_balance": "پور کردن مۉجۊدی کیف پیلٛ لایتنینگ", + "expiresIn": "د {time} دیقه مونقزی مۊئه", + "refill_create": "سی ادامه دیئن، لطفا ی کیف پیلٛ بیت کوین سی پور کردن بسات.", + "sameWalletAsInvoiceError": "نمؽتری ی سۊرت هساو ن وا هموݩ کیف پیلٛی که شن ساته پرداخت بکوݩ.", + "title": "دؽونداری مۉجۊدی" + }, + "lndViewInvoice": { + "additional_info": "دونسمنیا بؽشتر", + "for": "سی:", + "lightning_invoice": "سۊرت هساو لایتنینگ", + "sats": "ساتۊشی پرداخت بکو.", + "wasnt_paid_and_expired": "اؽ سۊرت هساو پرداخت نبیه ۉ مونقزی بیه.", + "preimage": "Pre-image", + "please_pay_between_and": "لطفا د {min} تا {max} پرداخت بکوݩ", + "please_pay": "لطفا پرداخت بکوݩ", + "date_time": "ویرگار ۉ ساعت" + }, + "plausibledeniability": { + "password_should_not_match": "رزم ها و کار مؽره. ی رزم هنی ن و کار بیر.", + "create_fake_storage": "ساتن جاگه زفت کردنی زفت بیه", + "create_password_explanation": "رزم سی جاگه زفت کردنی دروقین نوا وا رزم جاگه زفت کردنی ٱسلی تو ی جۊر بۊئه.", + "help": "د چن وزیتی، ایمکا داره مجبور بۊی رزمیتن فاش بکؽ. سی ٱنکه کوینیا تو ٱمن بمؽنن، BlueWallet مۊتاره ی جاگه زفت کردنی زفت بیه هنی وا ی رزم هنی بسازه. د فشار، مۊتاری اؽ رزم ن و ی نفر سؽمی فاش بکؽ. ٱگه د BlueWallet زنیه بۊئه، ی جاگه زفت کردنی “دروقین” نۊ ن وامؽکونه. اؽ سی نفر سؽمی واقعی نشوݩ مۊئه، ولؽ یواشکی جاگه زفت کردنی ٱسلی تو ن وا کوینیا ٱمن نگه مؽداره.", + "help2": "جاگه زفت کردنی نۊ کامل کار مؽکونه، ۉ مۊتاری مقادیر کموتری ر دش زخیره بکؽ تا واقعی تر نشوݩ بئه.", + "title": "انکار موجه" + }, + "pleasebackup": { + "ask_no": "ن، م نارم.", + "ask_yes": "ٱ، مه دارم.", + "ok": "خو، نۊشتمش.", + "ok_lnd": "خو، زخیرش هردم.", + "ask": "ٱیا عبارت بازیابی کیف پیلٛت ن زخیره هردیه؟ اؽ عبارت بازیابی سی دسرسی و پیلٛت اگه اؽ دسگا ن د دس بئی، لازم ئه. بی عبارت بازیابی، پیلٛت سی هؽمیشه د دس مؽره.", + "text": "لطفا چن دیقه ای وقت بزار سی نۊشتن اؽ عبارت بازیابی سی ی تاکه کاقذ.\nاؽ نۏسخه لادرار توئه ۉ مۊتاری وا اون کیف پیلٛت ن بازیابی بکؽ.", + "text_lnd": "لطفا اؽ نۏسخه لادرار کیف پیلٛ ن زخیره بکوݩ. سی بازیابی کیف پیلٛ د هالت د دس دیئن کار مۊئه.", + "title": "کیف پیلٛ تو ساته بی." + }, + "receive": { + "details_setAmount": "گرؽتن وا مقدار", + "details_share": "یک رسونی...", + "header": "گرتن", + "details_create": "ساتن", + "details_label": "توضیحات", + "qrcode_for_the_address": "کود QR سی آدرس", + "address_not_found": "نا مووفق د ساتن آدرس گرتنی.", + "reset": "آلشت و ٱول", + "maxSats": "بیشترین مقدار {max} ساتۊشی ئه", + "maxSatsFull": "بیشترین مقدار {max} ساتۊشی یا {currency} ئه", + "minSats": "کموترین مقدار {min} ساتۊشی ئه", + "minSatsFull": "کموترین مقدار {min} ساتۊشی یا {currency} ئه", + "bip47_explanation": "کودیا پرداخت ی آدرس همگانی ٱن که آدرسیا کیف پیلٛت ن فاش نمؽکونن. گرد سرویسیا اونانه پشتیوانی نمؽکونن." + }, + "send": { + "broadcastError": "ختا", + "broadcastSuccess": "مووفق بی", + "confirm_header": "تایید", + "confirm_sendNow": "هه ایسه کلٛ بکو", + "create_amount": "مقدار", + "create_copy": "لٛف گری ۉ دماتر مونتشر بکو", + "create_fee": "کارمزد", + "create_memo": "ویرداشت", + "create_satoshi_per_vbyte": "ساتۊشی سی هر بایت مجازی", + "create_to": "و", + "details_address": "آدرس", + "details_note_placeholder": "ویرداشت و خوت", + "details_scan": "اسکن", + "details_unrecognized_file_format": "قالو فایل نشناخته", + "dynamic_init": "ها ر موفته", + "fee_10m": "10 دیقه", + "fee_1d": "1 رۊز", + "fee_3h": "3 ساعت", + "fee_fast": "زلٛ", + "fee_medium": "مؽنجا", + "header": "کلٛ کردن", + "input_paste": "جا ونن", + "psbt_sign": "امزا کردن تراکونش", + "broadcastButton": "مونتشر کردن", + "broadcastPending": "د انتظار", + "create_broadcast": "مونتشر کردن", + "details_address_field_is_not_valid": "آدرس مۊعتبر نؽ.", + "details_amount_field_is_not_valid": "مقدار مۊعتبر نؽ.", + "details_fee_field_is_not_valid": "کارمزد مۊعتبر نؽ.", + "details_next": "هنی", + "details_scan_error": "ختا د اسکن", + "fee_custom": "دلخا", + "fee_slow": "اروم", + "input_clear": "پاک کردن", + "input_done": "تموم", + "input_total": "گرد:", + "dynamic_next": "هنی", + "dynamic_prev": "ویری", + "dynamic_start": "شرۊع", + "dynamic_stop": "نگه داشتن", + "create_details": "جزویات", + "create_tx_size": "اندازه تراکونش", + "details_recipients_title": "گیرنده ها", + "details_recipient_title": "گیرنده #{number} د #{total}", + "details_add_rec_add": "ازاف کردن گیرنده", + "details_add_rec_rem": "وردارئن گیرنده", + "details_add_rec_rem_all": "وردارئن گرد گیرنده ها", + "details_insert_contact": "ازاف کردن هومدنگ", + "please_complete_recipient_title": "گیرنده ناکامل", + "details_adv_fee_bump": "موجاز کردن افزایش کارمزد", + "details_adv_full": "و کار گرتن گرد مۉجۊدی", + "details_adv_import": "و مؽن اووردن تراکونش", + "details_adv_import_qr": "و مؽن اووردن تراکونش (QR)", + "details_create": "ساتن سۊرت هساو", + "details_frozen": "{amount} BTC مسدۊد بیه.", + "details_error_decode": "نا مووفق د رزم گوشایی آدرس بیت کوین", + "fee_satvbyte": "د ساتۊشی/بایت مجازی", + "insert_custom_fee": "کارمزدن بزݩ", + "open_settings": "واز کردن سامونیا", + "psbt_clipboard": "لٛف گری د ویرگه", + "psbt_tx_export": "و در کشیئن د فایل", + "psbt_tx_open": "واز کردن تراکونش امزا بیه", + "psbt_tx_scan": "اسکن تراکونش امزا بیه", + "invalid_psbt": "PSBT داده بیه مۊعتبر نؽ.", + "reset_amount": "آلشت و ٱول مقدار", + "success_done": "تموم", + "outdated_rate": "نرخ سی آخرین بار وانۊ بی: {date}", + "problem_with_psbt": "موشکل وا PSBT", + "provided_address_is_invoice": "اؽ آدرس و نظر مؽرسه سی ی سۊرت هساو لایتنینگ ئه. لطفا و کیف پیلٛ لایتنینگ خوت برۏ سی پرداخت اؽ سۊرت هساو.", + "broadcastNone": "هگزادسیمال تراکونش ن بزݩ", + "create_this_is_hex": "اؽ هگزادسیمال تراکونش توئه—امزا بیه ۉ آماده سی انتشار د شبکه.", + "create_verify": "د coinb.in دۏرۏسی بسنج", + "details_add_recc_rem_all_alert_description": "ٱطمیون داری مۊخوای گرد گیرنده ها ن وردارؽ؟", + "please_complete_recipient_details": "لطفا جزویات گیرنده #{number} ن کامل بکوݩ پؽش ز ازاف کردن گیرنده نۊ.", + "details_adv_full_sure": "ٱطمیون داری مۊخوای گرد مۉجۊدی کیف پیلٛت ن سی اؽ تراکونش و کار بیری؟", + "details_adv_full_sure_frozen": "ٱطمیون داری مۊخوای گرد مۉجۊدی کیف پیلٛت ن سی اؽ تراکونش و کار بیری؟ لطفا توجه بکوݩ که کوینیا مسدۊد بیه استثنا ٱن.", + "details_amount_field_is_less_than_minimum_amount_sat": "مقدار مۊشخس بیه خله کۏچک ئه. لطفا مقداری بیشتر ز 500 ساتۊشی بزݩ.", + "details_no_signed_tx": "فایل پسند بیه تراکونشی که بۊئه و مؽن اوورد ن دش ندیره.", + "details_scan_hint": "دۊ بار بزݩ سی اسکن یا و مؽن اووردن مقصد", + "details_total_exceeds_balance": "مقدار کلٛ کردنی ز مۉجۊدی د دسرس بیشتر ئه.", + "details_total_exceeds_balance_frozen": "مقدار کلٛ کردنی ز مۉجۊدی د دسرس بیشتر ئه. لطفا توجه بکوݩ که کوینیا مسدۊد بیه استثنا ٱن.", + "details_wallet_before_tx": "پؽش ز ساتن تراکونش، ٱول وا ی کیف پیلٛ بیت کوین ازاف بکوݩ.", + "fee_replace_minvb": "نرخ کلٛ کارمزد (ساتۊشی سی هر بایت مجازی) که مۊخوای پرداخت بکؽ وا ز {min} ساتۊشی/بایت مجازی بیشتر بۊئه.", + "permission_camera_message": "ما و اجازه تو سی و کار گرتن دۊروینت لازم دارؽم.", + "permission_storage_denied_message": "BlueWallet نمؽتاره اؽ فایل ن زخیره بکونه. لطفا سامونیا دسگاتن واز بکوݩ ۉ اجازه دسرسی و جاگه زفت کردنی ن فعال بکوݩ.", + "permission_storage_title": "اجازه دسرسی و جاگه زفت کردنی", + "psbt_this_is_psbt": "اؽ ی تراکونش بیت کوین نیمه امزا بیه (PSBT) ئه. لطفا امزایی شن وا کیف پیلٛ سخت ٱفزاری خوت تموم بکوݩ.", + "no_tx_signing_in_progress": "هؽچ امزایی تراکونشی د حال انجام نؽ.", + "qr_error_no_qrcode": "ما نتارسیم ی کود QR مۊعتبر د تصویر پسند بیه پیدا بکونؽم. مطمئن بۊ که تصویر فقط کود QR داره ۉ هؽچ موحتوای اوزافه ای جۊر متن یا دوگمه دش نؽ.", + "reset_amount_confirm": "مۊخوای مقدار ن و ٱول آلشت بکؽ؟", + "txSaved": "فایل تراکونش ({filePath}) زخیره بی.", + "file_saved_at_path": "فایل ({filePath}) زخیره بی.", + "cant_send_to_silentpayment_adress": "اؽ کیف پیلٛ نمؽتاره و آدرسیا Silent Payments کلٛ بکونه", + "cant_send_to_bip47": "اؽ کیف پیلٛ نمؽتاره و کودیا پرداخت BIP47 کلٛ بکونه", + "cant_find_bip47_notification": "ٱول اؽ کود پرداخت ن و هومدنگیا ازاف بکوݩ" + }, + "settings": { + "about": "دبار", + "about_selftest": "روونن خۊ ٱزمایی", + "about_selftest_electrum_disabled": "خوش آزمایی د هالت آفلاین د دسرس نؽ. هالت آفلاینن قیر فعال بکو ۉ دۏورته تفره بکو.", + "about_sm_github": "گیت هاب", + "about_sm_telegram": "تورگه تلگرام", + "biometrics": "بیومتریک", + "biom_conf_identity": "هوویت خوتونه تایید بکؽت.", + "currency": "واهد پیلٛ", + "default_title": "مجال روونن", + "electrum_offline_mode": "هالت آفلاین", + "use_ssl": "SSL ن و کار بییر", + "set_electrum_server_as_default": "{server} سی سرور پؽش فرز الکترام ساموݩ بۊئه؟", + "electrum_settings_server": "سرور الکترام", + "electrum_status": "وزیت", + "electrum_history": "ویرگار", + "encrypt_title": "ٱمنیت", + "encrypt_use": "{type} ن و کار بییر", + "general": "گرد ولاتی", + "header": "سامونیا", + "language": "زوݩ", + "license": "موجوز", + "lightning_settings": "سامونیا لایتنینگ", + "network": "شبکه", + "network_electrum": "سرور الکترام", + "notifications": "وارسونیا", + "password": "رزم", + "privacy": "سی خومی", + "privacy_read_clipboard": "ونن ویرگه", + "privacy_system_settings": "سامونیا دسگا", + "rate": "نرخ", + "widgets": "اوزارکیا", + "tools": "اوزاریا", + "plausible_deniability": "انکار موجه", + "save": "زخیره", + "saved": "زخیره بی", + "block_explorer": "گشت گر بلاک", + "lightning_settings_explain": "سی وصل بیؽن و گرهٛ LND خوتوݩ، لطفا LNDhub ن نصب کوݩ ۉ نشونی اینترنتی شن د سامونیا بزݩ. لطفا توجه کوݩ که فقط کیف پیلٛیی که دما زخیره آلشتیا ساته بۊن، و LNDhub مۊشخس وصل مۊوݩ.", + "lndhub_github": "گنجه گیت هاب", + "donate": "اهدا", + "electrum_connected": "وصل بیه", + "electrum_connected_not": "وصل نبیه", + "selfTest": "خوش آزمایی", + "last_updated": "آخرین وانۊ کردن", + "about_license": "موجوز MIT", + "about_release_notes": "ویرداشتیا انتشار", + "about_review": "ی بازبینی سی ما بنیر", + "about_backup": "هؽمیشه ز کلٛیلیا خوت نوسخه لادرار بگیر!", + "network_broadcast": "مونتشر کردن تراکونش", + "open_link_in_explorer": "واز کردن لینگ د گشت گر", + "not_a_valid_uri": "URI مۊعتبر نؽ", + "lightning_error_lndhub_uri": "URI LNDhub مۊعتبر نؽ", + "i_understand": "م فمیدم", + "total_balance": "گرد مۉجۊدی", + "currency_source": "نرخ د اؽ جا گرته مۊئه", + "block_explorer_preferred": "و کار گرتن گشت گر بلاک پسندیده", + "lightning_saved": "آلشتیا تو وا مووفقیت زخیره بین.", + "electrum_reset": "آلشت و پؽش فرز", + "encrypt_tstorage": "زخیره آلشتیا", + "encrypt_decrypt": "رزم گوشایی جاگه زفت کردنی", + "performance_score": "نمره عملکرد: {num}", + "run_performance_test": "آزمایش عملکرد", + "about_awesome": "ساته بیه وا اؽ ٱفکار شگفت انگیز", + "about_free": "BlueWallet ی پروژه ٱزاد ۉ مۊتن واز ئه. ساته بیه و دس و کار گریا بیت کوین.", + "block_explorer_invalid_custom_url": "نشونی اینترنتی داده بیه مۊعتبر نؽ. لطفا ی نشونی اینترنتی مۊعتبر بزݩ که وا http:// یا https:// شرۊع بۊئه.", + "about_selftest_ok": "گرد آزمایشیا داخلی وا مووفقیت رد بین. کیف پیلٛ خۊ کار مۊکونه.", + "privacy_temporary_screenshots": "اجازه گرتن تصویر د سفه", + "privacy_temporary_screenshots_instructions": "محافظت ز گرتن تصویر د سفه موقت قیر فعال مۊئه، که اجازه گرتن تصویر ۉ ضبط ز سفه ن مۊئه. محافظت خود کار وقتی BlueWallet ن بستی ۉ دۏورته واز هردی، فعال مۊئه.", + "biometrics_no_longer_available": "سامونیا دسگات آلشت بیه ۉ هنی وا سامونیا ٱمنیتی پسند بیه د بنومه ی جۊر نؽ. لطفا بیومتریک یا کد دسترسی ن دۏورته فعال بکوݩ، سپس بنومه ن سی اعمال اؽ آلشتیا د نۊ شرۊع بکوݩ.", + "biom_10times": "تو 10 بار سعی هردی رزم ن بزنی. مۊخوای جاگه زفت کردنی خوت ن و ٱول آلشت بکؽ؟ اؽ کار گرد کیف پیلٛیا ن وردیره ۉ جاگه زفت کردنی تن رزم گوشایی مۊکونه.", + "biom_no_passcode": "د دسگاتو کد دسترسی یا بیومتریک فعال نؽ. سی ادامه، لطفا ی کد دسترسی یا بیومتریک د بنومه سامونیا تنظیم بکوݩ.", + "biom_remove_decrypt": "گرد کیف پیلٛیا تو وردارئن مۊوݩ ۉ جاگه زفت کردنی تو رزم گوشایی مۊئه. ٱطمیون داری مۊخوای ادامه بئی؟", + "currency_fetch_error": "د گرتن نرخ سی واهد پیلٛ پسند بیه ختا پؽش اوما.", + "donate_description": "کۊمک ز ما بکوݩ تا Blue ٱزاد بمؽنه!", + "electrum_error_connect": "نمؽئه و سرور Electrum داده بیه وصل بۊی", + "electrum_error_connect_tor": "نمؽئه و سرور Electrum داده بیه وصل بۊی. لطفا مطمئن بۊ که بنومه Orbot وصل ئه ۉ دۏورته تفره بکوݩ.", + "lndhub_uri": "نمونه: {example}", + "electrum_host": "نمونه: {example}", + "electrum_offline_description": "وقتی فعال بۊئه، کیف پیلٛیا بیت کوین تو سعی نمؽکونن مۉجۊدی یا تراکونشیا ن بگرن.", + "electrum_port": "پورت، معمولا {example}", + "electrum_saved": "آلشتیا تو وا مووفقیت زخیره بین. ایمکا داره سی اعمال آلشتیا BlueWallet ن د نۊ شرۊع بکوݩ.", + "set_lndhub_as_default": "{url} ن سی سرور پؽش فرز LNDhub ساموݩ بۊئه؟", + "electrum_preferred_server": "سرور پسندیده", + "electrum_preferred_server_description": "سروری ن که مۊخوای کیف پیلٛت سی گرد فعالیتیا بیت کوین و کار بیره ن بزݩ. اگه ساموݩ بۊئه، کیف پیلٛت فقط ز اؽ سرور سی چک کردن مۉجۊدی، کلٛ کردن تراکونشیا، ۉ گرتن دونسمنیا شبکه و کار مۊگیره. مطمئن بۊ که و اؽ سرور اؽتمات داری پؽش ز ساموݩ کردنش.", + "electrum_unable_to_connect": "نمؽئه و {server} وصل بۊی.", + "electrum_reset_to_default": "اؽ کار اجازه مؽئه که BlueWallet ی سرور ن د تصادفی ز لیست سرورا انتخاب بکونه.", + "electrum_reset_to_default_and_clear_history": "آلشت و پؽش فرز ۉ پاک کردن ویرگار", + "encrypt_decrypt_q": "ٱطمیون داری مۊخوای جاگه زفت کردنی خوت ن رزم گوشایی بکؽ؟ اؽ کار اجازه مؽئه کیف پیلٛیا تو بی رزم د دسرس بۊن.", + "encrypt_enc_and_pass": "ز رزم محافظت بیه", + "encrypt_storage_explanation_headline": "فعال کردن زفت کردن جاگه زفت کردنی", + "encrypt_storage_explanation_description_line1": "فعال کردن زفت کردن جاگه زفت کردنی ی لایه محافظت اوزافه و بنومه ت ازاف مۊکونه، وا ٱمن کردن جۊر زخیره ساتن دونسمنیا تو د دسگات. اؽ کار سی هرکس که بخوائه بی اجازه و دونسمنیا تو دسرسی پیدا بکونه، سخت تر مۊکونه.", + "encrypt_storage_explanation_description_line2": "ولؽ مۊهیمه که بدونی اؽ زفت کردن فقط ز دسرسی و کیف پیلٛیا زخیره بیه د کیچین دسگا محافظت مۊکونه. هؽچ رزم یا محافظتی اوزافه ای ر روی خود کیف پیلٛیا نمؽزاره.", + "block_explorer_error_saving_custom": "ختا د زخیره گشت گر بلاک پسندیده", + "set_as_preferred": "ساموݩ و عونوان پسندیده", + "set_as_preferred_electrum": "ساموݩ {host}:{port} و عونوان سرور پسندیده، اتصال د تصادفی و سرور پیشنهاد بیه ن قیر فعال مؽکونه.", + "encrypted_feature_disabled": "اؽ قابلیت نمؽتاره وا جاگه زفت کردنی زفت بیه فعال بۊئه و کار بیره.", + "encrypt_use_expl": "{type} سی تایید هوویت تو پؽش ز ساتن تراکونش، واز کردن چفت، و در کشیئن، یا پاک کردن کیف پیلٛ و کار مۊره.", + "biometrics_fail": "اگه {type} فعال نبۊئه، یا واز کردن چفت نا مووفق بۊئه، مۊتاری ز کد دسترسی دسگات و عونوان جایگزین و کار بیری.", + "general_continuity": "تداوم", + "general_continuity_e": "وقتی فعال بۊئه، مۊتاری کیف پیلٛیا پسند بیه ۉ تراکونشیا ن وا دسگایا هنی اپل آی کلاد وصل بیه ت دؽی.", + "groundcontrol_explanation": "GroundControl ی سرور وارسونی پوش ٱزاد ۉ مۊتن واز سی کیف پیلٛیا بیت کوین ئه. مۊتاری سرور GroundControl خوت ن نصب بکؽ ۉ نشونی اینترنتی شن د اؽ جا بزنؽ تا و زیرساخت BlueWallet متکی نبۊئی. خالی بزار سی و کار گرتن سرور پؽش فرز GroundControl.", + "language_isRTL": "سی اعمال جؽت زوݩ، BlueWallet ن د نۊ شرۊع بکوݩ.", + "lightning_error_lndhub_uri_tor": "URI LNDhub مۊعتبر نؽ. لطفا مطمئن بۊ که بنومه Orbot وصل ئه ۉ دۏورته تفره بکوݩ.", + "electrum_suggested_description": "وقتی سرور پسندیده ساموݩ نبۊئه، ی سرور پیشنهاد بیه د تصادفی سی و کار گرتن پسند مۊئه.", + "password_explain": "رزمی ن که سی واز کردن چفت جاگه زفت کردنی تو و کار مۊری ن بزݩ.", + "privacy_quickactions": "میونبر کیف پیلٛ", + "privacy_quickactions_explanation": "نشونه بنومه BlueWallet ن لمس بکوݩ ۉ نگه دار سی دؽن سریع مۉجۊدی کیف پیلٛت.", + "privacy_clipboard_explanation": "اگه آدرس یا سۊرت هساوی د ویرگه تو پیدا بۊئه، میونبر دار.", + "privacy_do_not_track": "قیر فعال کردن تحلیل گری", + "privacy_do_not_track_explanation": "دونسمنیا عملکرد ۉ ٱطمیون پزیری سی تحلیل کلٛ نمؽۊن.", + "push_notifications_explanation": "وا فعال کردن وارسونیا، توکن دسگاتو و سرور کلٛ مۊۊ، هومرا وا آدرسیا کیف پیلٛ ۉ شناسه تراکونشیا سی گرد کیف پیلٛیا ۉ تراکونشیایی که دما فعال کردن وارسونیا ساته بۊن. توکن دسگا سی کلٛ کردن وارسونیا و کار مۊره، ۉ دونسمنیا کیف پیلٛ و ما اجازه مؽئه که د بار بیت کوین ورودی یا تایید تراکونشیا و تو خبر بدؽم.\n\nفقط دونسمنیا دما فعال کردن وارسونیا کلٛ مۊۊ—هؽچی ز پؽش تش جمع نمؽۊ.\n\nقیر فعال کردن وارسونیا گرد اؽ دونسمنیا ن ز سرور وردیره. هومچنین، پاک کردن ی کیف پیلٛ د بنومه دونسمنیا مربوط ش ن ز سرور هم وردیره.", + "success_transaction_broadcasted": "تراکونش تو وا مووفقیت مونتشر بی!", + "total_balance_explanation": "نشوݩ دیئن گرد مۉجۊدی گرد کیف پیلٛیا تو د اوزارکیا سفه ٱسلی." + }, + "transactions": { + "details_copy": "لٛف گری", + "date": "ویرگار", + "details_received": "گرته بیه", + "details_title": "تراکونش", + "list_title": "تراکونشیا", + "list_title_received": "گرته بیه", + "transaction": "تراکونش", + "open_url_error": "نا مووفق د واز کردن لینگ وا گشت گر پؽش فرز. گشت گر پؽش فرز خوته آلشت کوݩ ۉ د نۊ تفره بکوݩ.", + "details_inputs": "ورودیا", + "details_outputs": "خروجیا", + "details_inputs_count": "ورودیا ({count})", + "details_outputs_count": "خروجیا ({count})", + "details_to": "خروجی", + "details_to_address": "و", + "details_id": "شناسه", + "details_note": "ویرداشت", + "details_advanced": "پؽش رته", + "details_size": "اندازه", + "details_virtual_size": "اندازه مجازی", + "details_fee_rate": "نرخ کارمزد", + "details_network_fee": "کارمزد شبکه", + "details_sent": "کلٛ بی", + "details_section": "جزویات", + "details_view_in_browser": "د مرورگر دؽن", + "details_copy_txid": "لٛف گری شناسه تراکونش", + "details_copy_note": "لٛف گری ویرداشت", + "details_copy_block_explorer_link": "لٛف گری لینگ گشت گر بلاک", + "details_balance_hide": "قایم کردن مۉجۊدی", + "details_balance_show": "نشوݩ دؽن مۉجۊدی", + "details_add_note": "ازافه", + "pending": "د انتظار", + "offchain": "آفچین", + "onchain": "آنچین", + "list_title_sent": "کلٛ بی", + "status_bump": "تسریع", + "status_cancel": "لقو", + "txid": "شناسه تراکونش", + "updating": "ها ر و نی...", + "rbf_title": "تسریع (RBF)", + "cpfp_title": "افزایش کارمزد (CPFP)", + "cpfp_create": "ساتن", + "transactions_count": "تعداد تراکونشیا", + "eta_10m": "تخمین: تقریبا 10 دیقه", + "eta_3h": "تخمین: تقریبا 3 ساعت", + "eta_1d": "تخمین: تقریبا 1 رۊز", + "list_conf": "تایید: {number}", + "confirmations_lowercase": "{confirmations} تایید", + "incoming_transaction": "تراکونش ورودی", + "outgoing_transaction": "تراکونش خروجی", + "pending_transaction": "تراکونش د انتظار", + "expired_transaction": "تراکونش مونقزی بیه", + "expand_note": "واز کردن ویرداشت", + "transaction_not_available": "تراکونش د دسرس نؽ", + "cancel_explain": "ما اؽ تراکونش ن وا ی تراکونش هنی که و تو پرداخت مؽکونه ۉ کارمزد بیشتری داره جایگزین مؽکونؽم. اؽ کار د واقع تراکونش فعلی ن لقو مؽکونه. اؽ ن RBF—جایگزینی وا کارمزد ای نامن.", + "cancel_no": "اؽ تراکونش قابل جایگزینی نؽ.", + "cancel_title": "لقو اؽ تراکونش (RBF)", + "transaction_loading_error": "موشکلؽ د بار ونی تراکونش پؽش اوما. لطفا دما تر دۏورته تفره بکوݩ.", + "cpfp_exp": "ما ی تراکونش هنی مۊسازؽم که تراکونش تایید نبیه تو ن خرج بکونه. کلٛ کارمزد ز کارمزد تراکونش ٱسلی بیشتر مۊئه، بنابراین وا تری ایستخراج مۊئه. اؽ ن CPFP—فرزن سی والد پرداخت مؽکونه ای نامن.", + "cpfp_no_bump": "اؽ تراکونش قابل افزایش کارمزد نؽ.", + "enable_offline_signing": "اؽ کیف پیلٛ هومرا وا امزا کردن آفلاین و کار نمؽره. مۊخوای ایسه فعال شه؟", + "pending_with_amount": "د انتظار {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "rbf_explain": "ما اؽ تراکونش ن وا ی تراکونش هنی که کارمزد بیشتری داره جایگزین مؽکونؽم تا وا تری ایستخراج بۊئه. اؽ ن RBF—جایگزینی وا کارمزد ای نامن.", + "watchOnlyWarningTitle": "هۊشدار ٱمنیتی", + "watchOnlyWarningDescription": "موواظب کلاهبردارایی باش که اقلب ز کیف پیلٛیا «ناظر» سی فریب دیئن کاربرا و کار مؽگرن. اؽ کیف پیلٛیا اجازه دؽونداری یا کلٛ کردن پیلٛ ن نمؽئن؛ فقط اجازه دؽن مۉجۊدی ن مؽئن.", + "custom_fee_warning_title": "هۊشدار", + "custom_fee_warning_description": "کارمزدا کموتر ز 1 ساتۊشی/بایت مجازی مۊعتبر ٱن، ولؽ ایمکا داره سی سیاستیا گرهٛ، انتشار پیدا نکونن.", + "details_eta_analyzing": "ها د تحلیل...", + "details_explorer": "گشت گر", + "details_tx_hex": "هگزادسیمال تراکونش" + }, + "wallets": { + "add_bitcoin": "بیت کوین", + "add_entropy": "آنتروپی", + "add_wallet_type": "نوع", + "details_address": "آدرس", + "details_advanced": "پؽش رته", + "details_delete": "پاک کردن", + "details_delete_wallet": "پاک کردن کیف پیلٛ", + "details_derivation_path": "تۏر موشتق بیؽن", + "details_display": "نشوݩ دؽن مؽن بلگه ٱسلی", + "details_export_backup": "و در کردن نۏسخه لادرار", + "details_export_history": "گرتن ۉ و در کشیئن ویرگار و فورمت CSV", + "details_multisig_type": "چند امزایی", + "details_show_xpub": "نشوݩ دؽن XPUB کیف پیلٛ", + "details_show_addresses": "نشوݩ دؽن آدرسیا", + "details_title": "کیف پیلٛ", + "wallets": "کیف پیلٛیا", + "details_type": "نوع", + "details_yes_delete": "ٱ پاک کوݩ", + "enter_bip38_password": "رزمن سی رزم گوشایی بزنؽت", + "export_title": "و در کشیئن کیف پیلٛ", + "import_do_import": "و مؽن اووردن", + "import_passphrase": "پس فریز (Passphrase)", + "import_passphrase_title": "پس فریز (Passphrase)", + "import_imported": "و مؽن اوما", + "import_scan_qr": "اسکن یا و مؽن اووردن فایل", + "import_success": "کیف پیلٛ شما وا مووفقیت و مؽن اوما.", + "import_title": "و مؽن اووردن", + "learn_more": "بیشتر دونسۊیت", + "import_discovery_title": "جۊرسن", + "import_discovery_subtitle": "کیف پیلٛ جۊرسه بیه ن پسند بکؽت", + "import_discovery_derivation": "و کار گرتن تۏر موشتق دلخا", + "import_discovery_no_wallets": "کیف پیلٛی نجۊرس", + "import_derivation_found": "جۊرس", + "import_derivation_found_not": "نجۊرس", + "import_derivation_loading": "ها بار ونی مۊئه...", + "paste_from_clipboard": "جا ونن", + "import_file": "و مؽن اووردن فایل", + "list_long_scan": "اسکن کود QR", + "list_title": "کیف پیلٛیا", + "list_tryagain": "دۏورته تفره بکوݩ", + "add_create": "ساتن", + "add_lightning": "لایتنینگ", + "add_title": "ازاف کردن کیف پیلٛ", + "add_wallet_name": "نوم", + "add_wallet_seed_length": "درازی سید", + "add_wallet_seed_length_12": "12 ریشه", + "add_wallet_seed_length_24": "24 ریشه", + "total_balance": "گرد مۉجۊدی", + "details_are_you_sure": "ٱطمیون داری؟", + "details_connected_to": "وصل بیه و", + "swipe_balance_hide": "قایم کردن", + "swipe_balance_show": "نشوݩ دؽن", + "clear_search": "پاک کردن جۊرسن", + "manage_title": "دؽونداری کردن کیف پیلٛیا", + "no_results_found": "هؽچی نجۊرست.", + "select_wallet": "پسند کیف پیلٛ", + "pull_to_refresh": "بکش سی وانۊ کردن", + "xpub_title": "XPUB کیف پیلٛ", + "more_info": "دونسمنیا بؽشتر", + "add_bitcoin_explain": "کیف پیلٛ بیت کوین ساده ۉ پۊر زۊر", + "add_lightning_explain": "سی خرج کردن وا تراکونشیا فۊری", + "add_lndhub": "وصل بیؽن و LNDhub خوت", + "add_lndhub_placeholder": "نشونی گرهٛ خوت", + "add_placeholder": "اولین کیف پیلٛ مه", + "add_import_wallet": "و مؽن اووردن کیف پیلٛ", + "add_entropy_reset_title": "آلشت و ٱول آنتروپی", + "drag_to_reorder": "بکش سی آلشت ترتیب", + "details_use_with_hardware_wallet": "و کار گرتن وا کیف پیلٛ سخت ٱفزاری", + "import_passphrase_message": "اگه پس فریزی ن و کار گرته بی، شن بزݩ", + "import_search_accounts": "جۊرسن هساویا", + "import_derivation_title": "تۏر موشتق بیؽن", + "import_derivation_unknown": "نا معلوم", + "import_wrong_path": "تۏر موشتق بیؽن نادرس", + "list_create_a_button": "ایسه ازاف بکو", + "list_create_a_wallet": "ازاف کردن کیف پیلٛ", + "list_latest_transaction": "آخرین تراکونش", + "list_long_choose": "پسند تصویر", + "select_no_bitcoin": "ایسه هؽچ کیف پیلٛ بیت کوینی د دسرس نؽ.", + "scan_import": "اؽ کود QR ن اسکن کوݩ سی و مؽن اووردن کیف پیلٛ خوت د بنومه هنی.", + "write_down_header": "ساتن نۏسخه لادرار دسی", + "wallet_type_this": "نوع اؽ کیف پیلٛ {type} ئه.", + "share_number": "یک رسونی {number}", + "identity_pubkey": "کلٛیل عمومی هوویتی", + "add_ln_wallet_first": "ٱول وا ی کیف پیلٛ لایتنینگ ازاف بکوݩ.", + "looks_like_bip38": "اؽ شبیه ی کلٛیل خصوصی رزم دار (BIP38) ئه.", + "details_delete_anyway": "د هر هال پاک بکو", + "add_entropy_reset_message": "آلشت نوع کیف پیلٛ آنتروپی فعلی ن و ٱول آلشت مۊکونه. مۊخوای ادامه بئی؟", + "add_entropy_bytes": "{bytes} بایت آنتروپی", + "add_entropy_generated": "{gen} بایت آنتروپی ساته بیه", + "add_entropy_provide": "آنتروپی ن وا تاس انداختن و دس بیار", + "add_entropy_remain": "{gen} بایت آنتروپی ساته بیه. {rem} بایت ساری ز ساته بنومه ٱدادا تصادفی دسگا گرته مۊئه.", + "add_lndhub_error": "نشونی گرهٛ داده بیه ی گرهٛ LNDhub مۊعتبر نؽ.", + "clipboard_bitcoin": "ی آدرس بیت کوین د ویرگه تو دارؽ. مۊخوای سی ی تراکونش ز ش و کار بیری؟", + "clipboard_lightning": "ی سۊرت هساو لایتنینگ د ویرگه تو دارؽ. مۊخوای سی ی تراکونش ز ش و کار بیری؟", + "clear_clipboard_on_import": "پاک کردن ویرگه د و مؽن اووردن", + "details_del_wb_err": "مقدار مۉجۊدی داده بیه وا مۉجۊدی اؽ کیف پیلٛ ی جۊر نؽ. لطفا دۏورته تفره بکوݩ.", + "details_del_wb_q": "اؽ کیف پیلٛ مۉجۊدی داره. پؽش ز ادامه دیئن، لطفا توجه بکوݩ که بی عبارت بازیابی اؽ کیف پیلٛ نمؽتاری پیلٛ ن بازیابی بکؽ. سی پیش گیری ز پاک کردن تصادفی، لطفا مۉجۊدی کیف پیلٛت ن که {balance} ساتۊشی ئه، بزݩ.", + "details_master_fingerprint": "اثر انگوشت ٱسلی", + "import_error": "نا مووفق د و مؽن اووردن. لطفا مطمئن بۊ که دونسمنیا داده بیه مۊعتبر ٱن.", + "import_explanation": "لطفا ریشه یا سید، کلٛیل عمومی، WIF، یا هرچی داری ن بزݩ. BlueWallet هرچه بتاره سعی مۊکونه قالو دۏرۏس ن حدس بزنه ۉ کیف پیلٛت ن و مؽن بیاره.", + "import_success_watchonly": "کیف پیلٛ تو وا مووفقیت و مؽن اوما. هۊشدار: اؽ ی کیف پیلٛ ناظر ئه، نمؽتاری ز ش کلٛ بکؽ.", + "import_discovery_offline": "BlueWallet ایسه د هالت آفلاین ئه. د اؽ هالت، نمؽتاره وجود کیف پیلٛ ن دۏرۏسی بسنجه، بنابراین وا کیف پیلٛ دۏرۏس ن دسی پسند بکؽ", + "import_derivation_subtitle": "تۏر موشتق بیؽن دلخا ن بزݩ، ۉ ما سعی مۊکونؽم کیف پیلٛت ن جۊرسؽم.", + "list_create_a_wallet_text": "ٱزاد ئه، ۉ مۊتاری هرچن دلخوات \nبسازی.", + "list_empty_txs1": "تراکونشیا تو د اؽ جا نشوݩ مؽئن.", + "list_empty_txs1_lightning": "کیف پیلٛ لایتنینگ وا تراکونشیا رۊز مرت تو و کار مۊره. کارمزدا و هؽوا ارزون ٱن ۉ سرعت بسؽار زلٛ ئه.", + "list_empty_txs2": "وا کیف پیلٛت شرۊع بکوݩ.", + "list_empty_txs2_lightning": "\nسی شرۊع و کار گرتن، ر دؽونداری مۉجۊدی بزݩ ۉ مۉجۊدی تن پور بکوݩ.", + "no_ln_wallet_error": "پؽش ز پرداخت ی سۊرت هساو لایتنینگ، ٱول وا ی کیف پیلٛ لایتنینگ ازاف بکوݩ.", + "please_continue_scanning": "لطفا اسکن کردن ن ادامه بئی.", + "select_no_bitcoin_exp": "سی پور کردن کیف پیلٛیا لایتنینگ ی کیف پیلٛ بیت کوین لازم ئه. لطفا یکی بسات یا و مؽن بیار.", + "warning_do_not_disclose": "هؽچ وخت دونسمنیا زیر ن و یکی نکوݩ", + "write_down": "اؽ ریشه ها ن بنویس ۉ ٱمن زخیره بکوݩ. ز اونا سی بازیابی کیف پیلٛت دما تر و کار بیر.", + "copy_ln_url": "اؽ نشونی اینترنتی ن لٛف گری بکوݩ ۉ ٱمن زخیره بکوݩ سی بازیابی کیف پیلٛت دما تر.", + "copy_ln_public": "اؽ دونسمنیا ن لٛف گری بکوݩ ۉ ٱمن زخیره بکوݩ سی بازیابی کیف پیلٛت دما تر.", + "manage_wallets_search_placeholder": "جۊرسن کیف پیلٛیا، آدرسیا، تراکونشیا ۉ ویرداشتیا", + "details_delete_wallet_error_message": "موشکلؽ د تایید پاک کردن اؽ کیف پیلٛ ز وارسونیا پؽش اوما—ایمکا داره سی موشکل شبکه یا اتصال زاف بۊئه. اگه ادامه بئی، ایمکا داره هنوز وارسونیا ر تراکونشیا مربوط و اؽ کیف پیلٛ بگری، حتا دما ز پاک کردنش." + }, + "multisig": { + "provide_signature": "دین امزا", + "confirm": "تایید", + "header": "کلٛ کردن", + "share": "یک رسونی...", + "view": "سلٛ", + "shared_key_detected": "امزا کوݩ هومبهر", + "scan_or_import_file": "اسکن یا و مؽن اووردن فایل", + "export_coordination_setup": "و در کشیئن ساموݩ هماهنگی", + "cosign_this_transaction": "اؽ تراکونشن و جۊر هومبهر امزا مؽکؽتوݩ؟", + "legacy_title": "Legacy", + "co_sign_transaction": "امزا کردن تراکونش", + "what_is_vault_numberOfWallets": "چند امزایی {m} د {n} ", + "what_is_vault_wallet": "کیف پیلٛ", + "of": "د", + "wallet_type": "نوع کیف پیلٛ", + "not_a_multisignature_xpub": "ی XPUB د ی کیف پیلٛ چند امزایی نؽ!", + "scan_or_open_file": "اسکن یا واز کردن فایل", + "forget_this_seed": "اؽ سید د ویرت روئه ۉ د جالش XPUB ن و کار بیر.", + "ms_help_title5": "هالت پؽش رته", + "create": "ساتن", + "manage_keys": "دؽونداری کلٛیلیا", + "lets_start": "بشروع", + "quorum_header": "حد نصاب", + "ms_help": "کۊمک", + "export_signed_psbt": "و در کشیئن PSBT امزا بیه", + "fee": "کارمزد: {number}", + "fee_btc": "{number} BTC", + "multisig_vault": "گاوصندوق چند امزایی", + "default_label": "گاوصندوق چند امزایی", + "multisig_vault_explain": "بهترین ٱمنیت سی مقادیر زۊر", + "vault_key": "کلٛیل گاوصندوق {number}", + "vault_advanced_customize": "سامونیا گاوصندوق", + "create_new_key": "ساتن نۊ", + "quorum": "{m} د {n} حد نصاب", + "invalid_cosigner": "دانگیا امزا کوݩ هومبهر مۊعتبر نؽ", + "i_have_mnemonics": "م ی سید سی اؽ کلٛیل دارم.", + "input_fp": "اثر انگوشتن بزݩ", + "input_path": "تۏر موشتق بیؽنن بزݩ", + "signatures_required_to_spend": "امزایا لازم {number}", + "signatures_we_can_make": "ساتن {number}", + "what_is_vault": "ی گاوصندوق یعنی", + "provide_signature_details": "ز دسگا ۉ کیف پیلٛی که کلٛیل دش ئه سی امزا کردن اؽ تراکونش و کار بیر", + "provide_signature_details_bluewallet": "د BlueWallet، ر منوی سفه کلٛ کردن برۏ ۉ بزݩ ", + "provide_signature_next_steps": "اسکن یا و مؽن اووردن تراکونش امزا بیه", + "provide_signature_next_steps_details": "وقتی کیف پیلٛت تراکونش ن وا مووفقیت امزا کرد، کود QR داده بیه ن اسکن بکوݩ یا فایل هومرا ن و مؽن بیار، سپس گرد جزویات تراکونش ن پؽش ز انتشار، بازبینی بکوݩ.", + "required_keys_out_of_total": "کلٛیلیا لازم ز کلٛ", + "shared_key_detected_question": "ی امزا کوݩ هومبهر وا تو یک رسونی بیه، مۊخوای و مؽن بیاری شن؟", + "how_many_signatures_can_bluewallet_make": "چن گل امزا BlueWallet مۊتاره بکونه", + "native_segwit_title": "بهترین روش", + "wrapped_segwit_title": "بهترین سازگاری", + "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_format": "امزا کوݩ هومبهر نادرس: اؽ ی امزا کوݩ هومبهر سی قالو {format} نؽ.", + "type_your_mnemonics": "ی سید سی و مؽن اووردن کلٛیل گاوصندوق مۏجۊدت بزݩ.", + "this_is_cosigners_xpub": "اؽ XPUB امزا کوݩ هومبهر ئه—آماده سی و مؽن اووردن د ی کیف پیلٛ هنی. یک رسونی شن ٱمنه.", + "this_is_cosigners_xpub_airdrop": "اگه ز ریق AirDrop یک رسونی بکونی گیرنده ها وا ر سفه هماهنگی بۊن.", + "wallet_key_created": "کلٛیل گاوصندوق تو ساته بی. چن دیقه ای وقت بزار سی نۏسخه لادرار ٱمن ز سید عبارت بازیابی ت.", + "are_you_sure_seed_will_be_lost": "ٱطمیون داری؟ سید عبارت بازیابی ت د دس مؽره اگه نۏسخه لادرار نداشتیه.", + "view_edit_cosigners": "دؽن/ویرایش امزا کوݩ هومبهرا", + "this_cosigner_is_already_imported": "اؽ امزا کوݩ هومبهر دما تر و مؽن اوومده.", + "input_fp_explain": "رد بکوݩ سی و کار گرتن پؽش فرز (00000000)", + "input_path_explain": "رد بکوݩ سی و کار گرتن پؽش فرز ({default})", + "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 مۊسازه. سی ساتن حد نصاب هنی یا آلشت نوع آدرس، هالت پؽش رته ن د سامونیا فعال بکوݩ." + }, + "is_it_my_address": { + "title": "ٱیا اؽ آدرس مه؟", + "enter_address": "آدرس ن بزݩ", + "check_address": "آدرس ن چک کوݩ", + "no_wallet_owns_address": "هؽچ کیف پیلٛی مالک اؽ آدرس نؽ.", + "view_qrcode": "دؽن کود QR", + "owns": "{label} مالک {address} ئه" + }, + "total_balance_view": { + "display_in_bitcoin": "نشوݩ دؽن وا بیت کوین", + "display_in_sats": "نشوݩ دؽن وا ساتۊشی", + "display_in_fiat": "نشوݩ دؽن وا {currency}", + "title": "گرد مۉجۊدی", + "hide": "قایم کردن", + "explanation": "دؽن گرد مۉجۊدی گرد کیف پیلٛیا تو د سفه نمای کلٛی." + }, + "notifications": { + "no_and_dont_ask": "ن، ۉ د نۊ ا م مپرس.", + "would_you_like_to_receive_notifications": "مۊخوای وقتی پرداخت ورودی داری وارسونی بگری؟", + "notifications_subtitle": "پرداختیا ورودی ۉ تایید تراکونشیا", + "permission_denied_message": "تو اجازه کلٛ کردن وارسونیا ن نداده ای. اگه مۊخوای وارسونی بگری، لطفا ز سامونیا دسگات اونانه فعال بکوݩ." + }, + "cc": { + "change": "باقی منه", + "header": "دؽونداری کردن", + "use_coin": "و کار گرتن کوین", + "use_coins": "و کار گرتن کوینیا", + "sort_value": "مقدار", + "sort_status": "وزیت", + "sort_by": "ترتیب و ری", + "freeze": "مسدۊد کردن", + "freezeLabel": "مسدۊد کردن", + "freezeLabel_un": "واز کردن", + "coins_selected": "کوینیا پسند بیه ({number})", + "selected_summ": "{value} پسند بی", + "sort_label": "برچسب", + "sort_height": "بلندی", + "empty": "اؽ کیف پیلٛ ایسه هؽچ کوینی نداره.", + "tip": "اؽ قابلیت اجازه مؽئه که کوینیا ن سی دؽونداری بهتر کیف پیلٛ بدؽی، برچسب بزنی، مسدۊد بکونی، یا پسند بکونی. مۊتاری چند کوین ن وا زیئن ر دایره ها رنگی پسند بکونی.", + "sort_asc": "صعودی", + "sort_desc": "نزولی" + }, + "units": { + "BTC": "بیت کوین", + "MAX": "گرد مۉجۊدی", + "sat_vbyte": "ساتۊشی/بایت مجازی", + "sats": "ساتۊشی" + }, + "addresses": { + "sign_sign": "امزا کردن", + "sign_verify": "دۏرۏسی سنجی", + "sign_signature_correct": "دۏرۏسی سنجی مووفق بی!", + "sign_signature_incorrect": "دۏرۏسی سنجی شکست خوارد!", + "sign_placeholder_address": "آدرس", + "sign_placeholder_message": "پیوم", + "sign_placeholder_signature": "امزا", + "addresses_title": "آدرسیا", + "type_change": "باقی منه", + "type_receive": "گرتن", + "type_used": "و کار گرته بیه", + "transactions": "تراکونشیا", + "copy_private_key": "لٛف گری کلٛیل خصوصی", + "sensitive_private_key": "هۊشدار: کلٛیلیا خصوصی خله هساس ٱن. ادامه بئم؟", + "sign_title": "امزا/دۏرۏسی سنجی پیوم", + "sign_help": "د اؽ جا مۊتاری ی امزا رمزنگاری ر اساس ی آدرس بیت کوین بسازی یا دۏرۏسی سنجی بکونی." + }, + "lnurl_auth": { + "register_question_part_1": "مؽهایت هساوی مؽن", + "login_question_part_1": "مؽهایت مؽن", + "auth_question_part_1": "مؽهایت مؽن", + "authenticate": "ائراز", + "register_question_part_2": "وا کیف پیلٛ لایتنینگ خوت ساته بۊئه؟", + "login_question_part_2": "وا کیف پیلٛ لایتنینگ خوت ورود کوݩ؟", + "link_question_part_2": "و کیف پیلٛ لایتنینگ خوت پیوند بزݩ؟", + "auth_question_part_2": "وا کیف پیلٛ لایتنینگ خوت تایید بۊئه؟", + "link_question_part_1": "مؽهایت هساوی خوت ن", + "register_answer": "تو وا مووفقیت ی هساو د {hostname} ساتی!", + "login_answer": "تو وا مووفقیت د {hostname} ورود کردی!", + "link_answer": "کیف پیلٛ لایتنینگ تو وا مووفقیت و هساو تو د {hostname} پیوند بیه!", + "auth_answer": "تو وا مووفقیت د {hostname} تایید بیی!", + "could_not_auth": "ما نتارسؽم تو ن د {hostname} تایید بکونؽم." + }, + "bip47": { + "payment_code": "کود پرداخت", + "contacts": "هومدنگیا", + "add_contact": "اوردن هومدنگ", + "not_found": "کود پرداخت نجۊرست", + "copy_payment_code": "لٛف گری کود پرداخت", + "hide_contact": "قایم کردن هومدنگ", + "rename_contact": "آلشت نوم هومدنگ", + "rename": "آلشت نوم", + "pay_this_contact": "پرداخت و اؽ هومدنگ", + "invalid_pc": "کود پرداخت مۊعتبر نؽ", + "provide_payment_code": "کود پرداخت ن بزݩ", + "provide_name": "نوم جدیدن سی اؽ هومدنگ بزݩ", + "notif_tx": "تراکونش اعلان", + "bip47_explain_subtitle": "BIP47", + "bip47_explain": "کود قابل و کار گرتن دۏورته ۉ یک رسونی", + "purpose": "کود قابل و کار گرتن دۏورته ۉ یک رسونی (BIP47)", + "notification_tx_unconfirmed": "تراکونش اعلان هنی تایید نبیه، لطفا چش بدار", + "failed_create_notif_tx": "نا مووفق د ساتن تراکونش آنچین", + "onchain_tx_needed": "تراکونش آنچین لازم ئه", + "notif_tx_sent": "تراکونش اعلان کلٛ بی. لطفا چش بدار تا تایید بۊئه" + }, + "autofill_word": { + "title": "ریشه آخر سید", + "enter": "عبارت بازیابی نا کامل خوت ن بزݩ", + "generate_word": "ساتن ریشه آخر", + "error": "ورودی ی عبارت بازیابی نا کامل 11 یا 23 ریشه ای نؽ. لطفا دۏورته تفره بکوݩ." + } +} diff --git a/loc/ms.json b/loc/ms.json index fcd8b90fba2..b1fbf4e6cf7 100644 --- a/loc/ms.json +++ b/loc/ms.json @@ -4,21 +4,32 @@ "cancel": "Batalkan", "continue": "Teruskan", "clipboard": "Papan Sepit", + "copied": "Disalin!", + "discard_changes": "Buangkan perubahan?", + "discard_changes_explain": "Anda mempunyai perubahan yang belum disimpan. Adakah anda pasti mahu membuangnya dan meninggalkan skrin ini?", "enter_password": "Masukkan kata laluan", "never": "Tiada", - "disabled": "Dilumpuhkan", "of": "{number} daripada {total}", "ok": "OK", + "enter_url": "Masukkan URL", "storage_is_encrypted": "Simpanan anda disulitkan. Kata laluan diperlukan untuk nyahsulit.", "yes": "Ya", "no": "Tidak", - "save": "Simpan", + "save": "Simpan...", "seed": "Benih", "success": "Berjaya", "wallet_key": "Anak kunci dompet", - "invalid_animated_qr_code_fragment": "Serpihan QRCode teranimasi tidak sah. Sila cuba lagi.", - "file_saved": "Fail {filePath} sudah disimpan di dalam {destination} anda.", - "downloads_folder": "Folder Muat Turun" + "close": "Tutup", + "change_input_currency": "Tukar mata wang input", + "refresh": "Segar semula", + "pick_image": "Pilih dari pustaka", + "pick_file": "Pilih fail", + "enter_amount": "Masukkan jumlah", + "qr_custom_input_button": "Ketik 10 kali untuk masukkan input tersendiri", + "unlock": "Nyahkunci", + "port": "Liang", + "ssl_port": "Liang SSL", + "suggested": "Dicadangkan" }, "azteco": { "codeIs": "Kod baucar anda ialah", @@ -27,12 +38,14 @@ "redeem": "Tebus kepada dompet", "redeemButton": "Tebus", "success": "Berjaya", + "successMessage": "Baucar berjaya ditebus! Wang anda akan tiba di dompet Bitcoin anda sebentar lagi.", "title": "Tebus baucar Azte.co" }, "entropy": { "save": "Simpan", "title": "Entropi", - "undo": "Semula" + "undo": "Semula", + "amountOfEntropy": "{bits} daripada {limit} bit" }, "errors": { "broadcast": "Penyiaran gagal.", @@ -40,78 +53,66 @@ "network": "Ralat Rangkaian" }, "lnd": { - "active": "Giat", - "inactive": "Tidak Giat", - "channels": "Saluran", - "no_channels": "Tiada saluran", - "claim_balance": "Tebus baki {balance}", - "close_channel": "Tutup saluran", - "new_channel": "Saluran baru", - "errorInvoiceExpired": "Invois tamat tempoh", - "force_close_channel": "Tutup saluran secara paksa?", + "errorInvoiceExpired": "Invois sudah tamat tempoh.", "expired": "Tamat tempoh", - "node_alias": "Surihan nod", "expiresIn": "Tamat tempoh dalam {time} minit", "payButton": "Bayar", - "placeholder": "Invois", - "open_channel": "Saluran Terbuka", - "funding_amount_placeholder": "Jumlah wang, contohnya 0.001", - "opening_channnel_for_from": "Membuka saluran untuk dompet {forWalletLabel}, atas pembiayaan dari {fromWalletLabel}", - "are_you_sure_open_channel": "Adakah anda pasti anda mahu membuka saluran ini?", - "potentialFee": "Agakan Yuran: {fee}", - "remote_host": "Host Jauh", + "payment": "Bayaran", + "placeholder": "Invois atau alamat", + "potentialFee": "Yuran berpotensi: {fee}", "refill": "Isi semula", - "reconnect_peer": "Sambung semula dengan rakan", "refill_create": "Untuk meneruskan, sila buat dompet Bitcoin untuk diisi semula.", "refill_external": "Isi Semula dengan Dompet Luaran", "refill_lnd_balance": "Isi Semula Baki Dompet Lightning", - "sameWalletAsInvoiceError": "Anda tidak boleh membayar invois dengan dompet yang mencipta invois tersebut.", - "title": "Uruskan Wang", - "can_send": "Boleh Menghantar", - "can_receive": "Boleh Menerima", - "view_logs": "Lihat Log" + "sameWalletAsInvoiceError": "Anda tidak boleh membayar invois dengan dompet yang sama yang digunakan untuk menciptanya.", + "title": "Uruskan Wang" }, "lndViewInvoice": { "additional_info": "Maklumat Tambahan", "for": "Untuk:", "lightning_invoice": "Invois Lightning", - "open_direct_channel": "Buka saluran terus dengan nod ini:", "please_pay_between_and": "Sila bayar di antara {min} dan {max}", "please_pay": "Sila bayar", - "preimage": "Praimej", + "preimage": "Pra-imej", "sats": "sat.", + "date_time": "Tarikh dan Masa", "wasnt_paid_and_expired": "Invois ini tidak dibayar dan sudah luput." }, "plausibledeniability": { "create_fake_storage": "Cipta Simpanan Sulit.", - "create_password": "Cipta kata laluan", "create_password_explanation": "Kata laluan untuk simpanan palsu tidak boleh sepadan dengan kata laluan untuk simpanan utama anda.", - "help": "Dalam keadaan tertentu, anda mungkin dipaksa untuk mendedahkan kata laluan. Supaya duit anda kekal selamat, BlueWallet boleh menghasilkan simpanan sulit lain dengan kata laluan berbeza. Apabila dipaksa. anda dapat mendedahkan kata laluan ini kepada pihak ketiga. Jika dimasukkan ke dalam BlueWallet, satu simpanan \"palsu\" akan dinyahkunci. Simpanan ini akan kelihatan tulen kepada pihak ketiga, tetapi secara rahsia mengekalkan keselamatan duit anda.", + "help": "Dalam keadaan tertentu, anda mungkin dipaksa untuk mendedahkan kata laluan. Supaya duit anda kekal selamat, BlueWallet boleh menghasilkan simpanan sulit lain dengan kata laluan berbeza. Apabila dipaksa, anda boleh mendedahkan kata laluan ini kepada pihak ketiga. Jika dimasukkan ke dalam BlueWallet, satu simpanan \"palsu\" akan dinyahkunci. Simpanan ini akan kelihatan tulen kepada pihak ketiga, tetapi secara rahsia mengekalkan keselamatan duit anda.", "help2": "Simpanan baharu itu akan berfungsi secara penuh, dan anda boleh menyimpan sedikit jumlah minimum di sana agar ia kelihatan tulen.", "password_should_not_match": "Kata laluan sudah dalam penggunaan. Sila cuba kata laluan lain.", - "passwords_do_not_match": "Kata laluan tidak sepadan. Sila cuba lagi.", - "retype_password": "Ulang taip kata laluan.", - "success": "Berjaya", "title": "Penafian Munasabah" }, "pleasebackup": { "ask": "Sudahkah anda simpan frasa sandaran dompet anda? Frasa sandaran ini diperlukan untuk mencapai wang anda andai anda kehilangan peranti ini. Tanpa frasa sandaran ini, wang anda akan hilang selamanya.", - "ask_no": "Tidak, belum lagi", - "ask_yes": "Ya, sudah", - "ok": "OK, sudah disalin", - "ok_lnd": "OK, sudah disimpan", - "text": "Sila ambil sedikit waktu untuk menyalin frasa nemonik ini di atas sehelai kertas.\nSalinan ini adalah sandaran anda dan anda boleh menggunakan salinan ini untuk mendapatkan semula dompet anda. ", + "ask_no": "Tidak, belum lagi.", + "ask_yes": "Ya, sudah.", + "ok": "OK, sudah disalin.", + "ok_lnd": "OK, sudah disimpan.", + "text": "Sila ambil sedikit waktu untuk menyalin frasa mnemonik ini di atas sehelai kertas.\nSalinan ini adalah sandaran anda dan anda boleh menggunakan salinan ini untuk mendapatkan semula dompet anda. ", "text_lnd": "Sila simpan sandaran dompet ini. Sandaran ini membolehkan anda untuk mengembalikan dompet ini dalam kes kehilangan.", - "title": "Dompet anda sudah dicipta" + "title": "Dompet anda sudah dicipta." }, "receive": { "details_create": "Cipta", "details_label": "Penerangan", "details_setAmount": "Terima dengan jumlah", - "details_share": "Kongsi", - "header": "Terima" + "details_share": "Kongsi...", + "address_not_found": "Tidak dapat menjana alamat penerima.", + "header": "Terima", + "reset": "Tetapkan semula", + "maxSats": "Jumlah maksimum ialah {max} sat", + "maxSatsFull": "Jumlah maksimum ialah {max} sat atau {currency}", + "minSats": "Jumlah minimum ialah {min} sat", + "minSatsFull": "Jumlah minimum ialah {min} sat atau {currency}", + "qrcode_for_the_address": "Kod QR untuk alamat", + "bip47_explanation": "Kod pembayaran ialah alamat universal yang mengelakkan pendedahan alamat dompet anda. Tidak semua perkhidmatan akan menyokongnya." }, "send": { + "provided_address_is_invoice": "Alamat ini kelihatan untuk invois Lightning. Sila pergi ke dompet Lightning anda untuk membuat bayaran bagi invois ini.", "broadcastButton": "Siar", "broadcastError": "Ralat", "broadcastNone": "Masukkan heks urus niaga", @@ -125,17 +126,26 @@ "create_details": "Perincian", "create_fee": "Yuran", "create_memo": "Memo", + "create_satoshi_per_vbyte": "Satoshi per vByte", "create_this_is_hex": "Ini adalah heks urus niaga anda—ditandatangan dan sedia untuk disiarkan kepada rangkaian.", "create_to": "Kepada", "create_tx_size": "Ukuran urus niaga", "create_verify": "Absahkan di coinb.in", + "details_insert_contact": "Masukkan Kenalan", "details_add_rec_add": "Tambah Penerima", "details_add_rec_rem": "Buang Penerima", + "details_add_recc_rem_all_alert_description": "Adakah anda pasti mahu membuang semua penerima?", + "details_add_rec_rem_all": "Buang Semua Penerima", + "details_recipients_title": "Penerima", + "details_recipient_title": "Penerima #{number} daripada #{total}", + "please_complete_recipient_title": "Penerima Tidak Lengkap", + "please_complete_recipient_details": "Sila lengkapkan butiran penerima #{number} sebelum menambah penerima baharu.", "details_address": "Alamat", "details_address_field_is_not_valid": "Alamat tidak sah", "details_adv_fee_bump": "Benarkan Penambahan Yuran", "details_adv_full": "Guna Kesemua Baki", "details_adv_full_sure": "Adakah anda pasti anda mahu menggunakan kesemua baki dompet anda untuk urus niaga ini? ", + "details_adv_full_sure_frozen": "Adakah anda pasti anda mahu menggunakan kesemua baki dompet anda untuk urus niaga ini? Sila ambil maklum bahawa duit yang dibekukan dikecualikan.", "details_adv_import": "Pindah Masuk Urus Niaga", "details_adv_import_qr": "Pindah Masuk Urus Niaga (QR)", "details_amount_field_is_not_valid": "Jumlah tidak sah", @@ -143,12 +153,15 @@ "details_create": "Cipta Invois", "details_error_decode": "Tidak dapat menyahkod alamat Bitcoin", "details_fee_field_is_not_valid": "Yuran tidak sah.", + "details_frozen": "{amount} BTC dibekukan.", "details_next": "Seterusnya", "details_no_signed_tx": "Fail yang dipilih tidak mengandungi urus niaga yang boleh dipindah masuk.", "details_note_placeholder": "Nota Kendiri", "details_scan": "Imbas", "details_scan_hint": "Ketik dua kali untuk mengimbas atau memindah masuk destinasi", + "details_scan_error": "Ralat imbasan", "details_total_exceeds_balance": "Jumlah dihantar melebihi baki yang ada.", + "details_total_exceeds_balance_frozen": "Jumlah dihantar melebihi baki yang ada. Sila ambil maklum bahawa duit yang dibekukan dikecualikan.", "details_unrecognized_file_format": "Susun atur fail tidak dikenali", "details_wallet_before_tx": "Sebelum mencipta urus niaga, anda perlu menambah dompet Bitcoin terlebih dahulu.", "dynamic_init": "Memulakan", @@ -156,12 +169,14 @@ "dynamic_prev": "Sebelumnya", "dynamic_start": "Mulakan", "dynamic_stop": "Hentikan", - "fee_10m": "10m", + "fee_10m": "10min", "fee_1d": "1h", "fee_3h": "3j", "fee_custom": "Tersendiri", + "insert_custom_fee": "Masukkan yuran", "fee_fast": "Laju", "fee_medium": "Sederhana", + "fee_replace_minvb": "Jumlah kadar yuran (satoshi per vByte) yang anda mahu bayar perlu lebih tinggi daripada {min} sat/vByte.", "fee_satvbyte": "dalam sat/vBait", "fee_slow": "Perlahan", "header": "Hantar", @@ -170,24 +185,27 @@ "input_paste": "Tampal", "input_total": "Jumlah:", "permission_camera_message": "Kami memerlukan izin anda untuk menggunakan kamera anda.", - "permission_camera_title": "Keizinan untuk menggunakan kamera.", "psbt_sign": "Tandatangan urus niaga", + "invalid_psbt": "PSBT yang diberikan tidak sah.", "open_settings": "Buka Tetapan", - "permission_storage_later": "Tanya semula kemudian", - "permission_storage_message": "BlueWallet memerlukan keizinan anda untuk mencapai simpanan anda untuk menyimpan fail ini.", "permission_storage_denied_message": "BlueWallet tidak dapat menyimpan fail ini. Sila buka tetapan peranti anda dan benarkan Keizinan Simpanan.", "permission_storage_title": "Keizinan Capaian Simpanan", "psbt_clipboard": "Salin ke Papan Sepit", "psbt_this_is_psbt": "Ini ialah Urus Niaga Bitcoin Bertandatangan Separa (PSBT). Sila tandatangan penuh dengan menggunakan dompet perkakas.", "psbt_tx_export": "Pindah keluar ke fail", "no_tx_signing_in_progress": "Tiada penandatanganan urus niaga sedang berjalan.", + "outdated_rate": "Kadar terakhir dikemas kini: {date}", "psbt_tx_open": "Buka Urus Niaga Bertandatangan", "psbt_tx_scan": "Imbas Urus Niaga Bertandatangan", - "qr_error_no_qrcode": "Kami tidak menjumpai Kod QR di dalam gambar yang dipilih. Sila pastikan gambar itu hanya mengandungi Kod QR dan tiada isi kandungan lain seperti teks atau butang.", + "qr_error_no_qrcode": "Kami tidak dapat menemui Kod QR yang sah dalam imej yang dipilih. Pastikan imej hanya mengandungi Kod QR dan tiada kandungan tambahan seperti teks atau butang.", "reset_amount": "Tetapkan Semula Jumlah", "reset_amount_confirm": "Adakah anda ingin menetapkan semula jumlah?", "success_done": "Selesai", - "txSaved": "Fail urus niaga ({filePath}) sudah disimpan di dalam folder Muat Turun.", + "txSaved": "Fail urus niaga ({filePath}) sudah disimpan.", + "file_saved_at_path": "Fail ({filePath}) sudah disimpan.", + "cant_send_to_silentpayment_adress": "Dompet ini tidak boleh menghantar ke alamat SilentPayment", + "cant_send_to_bip47": "Dompet ini tidak boleh menghantar ke kod pembayaran BIP47", + "cant_find_bip47_notification": "Tambah Kod Pembayaran ini ke kenalan terlebih dahulu", "problem_with_psbt": "Masalah dengan PSBT" }, "settings": { @@ -198,27 +216,32 @@ "about_license": "Lesen MIT", "about_release_notes": "Nota pelepasan", "about_review": "Berikan kami ulasan anda", + "performance_score": "Skor prestasi: {num}", + "run_performance_test": "Uji prestasi", "about_selftest": "Jalankan swaujian", + "block_explorer_invalid_custom_url": "URL yang diberikan tidak sah. Sila masukkan URL yang sah bermula dengan http:// atau https://.", "about_selftest_electrum_disabled": "Swaujian tidak hadir dengan Mod Luar Talian Electrum. Sila matikan mod luar talian dan cuba lagi.", "about_selftest_ok": "Semua ujian dalaman berjaya. Dompet ini berfungsi dengan baik.", "about_sm_github": "GitHub", - "about_sm_discord": "Pelayan Discord", "about_sm_telegram": "Saluran Telegram", - "about_sm_twitter": "Ikuti kami di Twitter", - "advanced_options": "Pilihan Lanjut", + "privacy_temporary_screenshots": "Benarkan Tangkapan Skrin", + "privacy_temporary_screenshots_instructions": "Perlindungan tangkapan skrin akan dimatikan buat sementara waktu, membolehkan tangkapan skrin dan rakaman skrin. Perlindungan akan dihidupkan semula secara automatik apabila anda menutup dan membuka semula BlueWallet.", "biometrics": "Biometrik", + "biometrics_no_longer_available": "Tetapan peranti anda sudah berubah dan tidak lagi sepadan dengan tetapan keselamatan yang dipilih dalam aplikasi. Sila bolehkan semula biometrik atau kod laluan, kemudian mulakan semula aplikasi untuk menggunakan perubahan ini.", "biom_10times": "Anda telah cuba memasukkan kata laluan sebanyak 10 kali. Adakah anda mahu menetapkan semula simpanan anda? Ini akan membuang semua dompet dan menyahsulit simpanan anda.", "biom_conf_identity": "Sila pastikan keperibadian anda", - "biom_no_passcode": "Peranti anda tidak mempunyai kod laluan. Untuk meneruskan, sila susun kata laluan di Persediaan aplikasi.", + "biom_no_passcode": "Peranti anda tidak mempunyai kod laluan atau biometrik yang dibolehkan. Untuk meneruskan, sila konfigurasi kod laluan atau biometrik dalam aplikasi Tetapan.", "biom_remove_decrypt": "Semua dompet anda akan dibuang dan simpanan anda akan dinyahsulitkan. Adakah anda pasti untuk meneruskannya?", "currency": "Mata Wang", - "default_desc": "Apabila dilumpuhkan, BlueWallet akan membuka dompet terpilih dengan serta-merta ketika pelancaran.", - "default_info": "Maklumat lalai", + "currency_source": "Kadar diperoleh daripada", + "currency_fetch_error": "Terdapat ralat semasa memperoleh kadar untuk mata wang yang dipilih.", "default_title": "Ketika Pelancaran", - "default_wallets": "Paparkan Semua Dompet", + "donate": "Derma", + "donate_description": "Bantu kami pastikan Blue percuma!", "electrum_connected": "Terhubung", "electrum_connected_not": "Tidak Terhubung", - "electrum_error_connect": "Tidak dapat menghubungi pelayan Electrum yang disediakan", + "electrum_error_connect": "Tidak dapat menghubungi pelayan Electrum yang diberikan", + "electrum_error_connect_tor": "Tidak dapat menghubungi pelayan Electrum yang diberikan. Sila pastikan aplikasi Orbot terhubung dan cuba lagi.", "lndhub_uri": "cth., {example}", "electrum_host": "cth., {example}", "electrum_offline_mode": "Mod Luar Talian", @@ -227,67 +250,73 @@ "use_ssl": "Guna SSL", "electrum_saved": "Pengubahan oleh anda berjaya disimpan. Pemulaan semula BlueWallet mungkin diperlukan untuk perubahan itu berlaku. ", "set_electrum_server_as_default": "Tetapkan {server} sebagai pelayan lalai Electrum?", - "set_lndhub_as_default": "Tetapkan {url} sebagai pelayan lalai LNDHub?", + "set_lndhub_as_default": "Tetapkan {url} sebagai pelayan LNDhub lalai?", "electrum_settings_server": "Pelayan Electrum", - "electrum_settings_explain": "Kosongkan untuk mengguna nilai lalai.", "electrum_status": "Keadaan", - "electrum_clear_alert_title": "Padam sejarah?", - "electrum_clear_alert_message": "Adakah anda mahu memadam sejarah pelayan Electrum?", - "electrum_clear_alert_cancel": "Batal", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Pilih", - "electrum_reset": "Tetapkan semula kepada lalai", + "electrum_preferred_server": "Pelayan Pilihan", + "electrum_preferred_server_description": "Masukkan pelayan yang anda mahu dompet anda gunakan untuk semua aktiviti Bitcoin. Setelah ditetapkan, dompet anda akan menggunakan pelayan ini secara eksklusif untuk menyemak baki, menghantar urus niaga, dan mendapatkan data rangkaian. Pastikan anda mempercayai pelayan ini sebelum menetapkannya.", "electrum_unable_to_connect": "Tidak dapat dihubungkan ke {server}.", - "electrum_history": "Sejarah pelayan", - "electrum_reset_to_default": "Adakah anda pasti anda mahu tetapkan semula Electrum kepada tetapan lalai?", - "electrum_clear": "Padam", - "tor_supported": "Menyokong Tor", - "tor_unsupported": "Hubungan Tor tidak disokong.", + "electrum_history": "Sejarah", + "electrum_reset_to_default": "Ini akan membenarkan BlueWallet memilih pelayan secara rawak daripada senarai pelayan.", + "electrum_reset": "Tetapkan semula kepada lalai", + "electrum_reset_to_default_and_clear_history": "Tetapkan semula kepada lalai dan kosongkan sejarah", "encrypt_decrypt": "Nyahsulit Simpanan", "encrypt_decrypt_q": "Adakah anda pasti anda mahu nyahkunci simpanan anda? Ini akan membolehkan dompet anda dicapai tanpa kata laluan.", - "encrypt_enc_and_pass": "Tersulitkan dan Dilindungi dengan Kata Laluan", + "encrypt_enc_and_pass": "Dilindungi Kata Laluan", + "encrypt_storage_explanation_headline": "Bolehkan Penyulitan Simpanan", + "encrypt_storage_explanation_description_line1": "Membolehkan Penyulitan Simpanan menambah lapisan perlindungan tambahan kepada aplikasi anda dengan menjamin cara data anda disimpan pada peranti anda. Ini menjadikannya lebih sukar untuk sesiapa mencapai maklumat anda tanpa kebenaran.", + "encrypt_storage_explanation_description_line2": "Walau bagaimanapun, penting untuk diketahui bahawa penyulitan ini hanya melindungi capaian ke dompet anda yang disimpan dalam keychain peranti. Ia tidak meletakkan kata laluan atau perlindungan tambahan pada dompet itu sendiri.", + "i_understand": "Saya faham", + "block_explorer": "Penjelajah Blok", + "block_explorer_preferred": "Guna penjelajah blok pilihan", + "block_explorer_error_saving_custom": "Ralat menyimpan penjelajah blok pilihan", "encrypt_title": "Keselamatan", "encrypt_tstorage": "Simpanan", "encrypt_use": "Gunakan {type}", - "encrypt_use_expl": "{type} akan digunakan untuk memperakukan keperibadian anda sebelum melakukan urus niaga, penyahkuncian, pemindahan keluar, atau pemadaman sesuatu dompet. {type} tidak akan digunakan untuk menyahkunci simpanan sulit.", + "set_as_preferred": "Tetapkan sebagai pilihan", + "set_as_preferred_electrum": "Menetapkan {host}:{port} sebagai pelayan pilihan akan melumpuhkan sambungan ke pelayan yang dicadangkan secara rawak.", + "encrypted_feature_disabled": "Ciri ini tidak boleh digunakan apabila penyulitan simpanan dibolehkan.", + "encrypt_use_expl": "{type} akan digunakan untuk mengesahkan keperibadian anda sebelum membuat urus niaga, menyahkunci, memindah keluar, atau memadamkan dompet.", + "biometrics_fail": "Jika {type} tidak dibolehkan, atau gagal untuk menyahkunci, anda boleh menggunakan kod laluan peranti anda sebagai alternatif.", "general": "Umum", - "general_adv_mode": "Mod Lanjut", - "general_adv_mode_e": "Apabila dibolehkan, anda akan nampak pilihan lanjut seperti jenis dompet yang lain, kebolehan untuk memperincikan LNDHub yang anda mahu hubungkan, dan entropi tersendiri ketika penciptaan dompet.", "general_continuity": "Kesinambungan", "general_continuity_e": "Apabila dibolehkan, anda akan dapat melihat dompet terpilih dan urus niaga, menggunakan peranti lain yang terhubung dengan iCloud Apple.", - "groundcontrol_explanation": "GroundControl ialah satu pelayan pemberitahuan percuma dan sumber terbuka untuk dompet Bitcoin. Anda boleh memasang pelayan GroundControl anda sendiri dan letakkan URL GrounControl di sini untuk tidak bergantung pada prasarana BlueWallet. Kosongkan untuk menggunakan pelayan lalai GroundControl.", + "groundcontrol_explanation": "GroundControl ialah satu pelayan pemberitahuan percuma dan sumber terbuka untuk dompet Bitcoin. Anda boleh memasang pelayan GroundControl anda sendiri dan letakkan URL GroundControl di sini untuk tidak bergantung pada prasarana BlueWallet. Kosongkan untuk menggunakan pelayan lalai GroundControl.", "header": "Tetapan", "language": "Bahasa", + "last_updated": "Terakhir Dikemas Kini", "language_isRTL": "Pemulaan semula BlueWallet diperlukan untuk pengalihan bahasa berlaku.", - "lightning_error_lndhub_uri": "URL LNDHub tidak sah", + "license": "Lesen", + "lightning_error_lndhub_uri": "URI LNDhub tidak sah", + "lightning_error_lndhub_uri_tor": "URI LNDhub tidak sah. Sila pastikan aplikasi Orbot terhubung dan cuba lagi.", "lightning_saved": "Pengubahan oleh anda berjaya disimpan.", "lightning_settings": "Tetapan Lightning", - "tor_settings": "Tetapan Tor", - "lightning_settings_explain": "Untuk menghubungkan nod LND anda sendiri, sila pasang LNDHub dan letakkan URL LNDHub dalam tetapan ini. Kosongkan untuk menggunakan LNDHub BlueWallet . Sila ambil perhatian hanya dompet yang dicipta setelah perubahan disimpan akan dihubungkan kepada LNDHub tersebut.", + "lightning_settings_explain": "Untuk menghubungi nod LND anda sendiri, sila pasang LNDhub dan letakkan URLnya di sini dalam tetapan. Sila ambil maklum bahawa hanya dompet yang dicipta selepas perubahan disimpan akan menghubungi LNDhub yang dinyatakan.", + "lndhub_github": "Repositori GitHub", "network": "Rangkaian", "network_broadcast": "Siarkan Urus Niaga", "network_electrum": "Pelayan Electrum", + "electrum_suggested_description": "Apabila pelayan pilihan tidak ditetapkan, pelayan yang dicadangkan akan dipilih untuk digunakan secara rawak.", "not_a_valid_uri": "URL tidak sah", "notifications": "Pemberitahuan", "open_link_in_explorer": "Buka pautan di dalam penjelajah", "password": "Kata Laluan", - "password_explain": "Cipta kata laluan yang anda akan gunakan untuk menyahsulit simpanan ini.", - "passwords_do_not_match": "Kata laluan tidak sepadan.", + "password_explain": "Masukkan kata laluan yang akan anda gunakan untuk menyahkunci simpanan anda.", "plausible_deniability": "Penafian Munasabah", "privacy": "Persendirian", "privacy_read_clipboard": "Baca Papan Sepit", "privacy_system_settings": "Tetapan Sistem", "privacy_quickactions": "Pintasan Dompet", - "privacy_quickactions_explanation": "Sentuh dan jeda pada ikon aplikasi BlueWallet di layar beranda anda untuk melihat baki dompet anda secara pantas.", + "privacy_quickactions_explanation": "Sentuh dan tahan ikon aplikasi BlueWallet untuk melihat baki dompet anda dengan pantas.", "privacy_clipboard_explanation": "Memberikan pintasan jika alamat atau invois dijumpai pada papan sepit anda.", "privacy_do_not_track": "Lumpuhkan Penyelidik", "privacy_do_not_track_explanation": "Maklumat pencapaian dan keandalan tidak akan dihantar untuk kaji selidik.", - "push_notifications": "Pemberitahuan Pacu", - "retype_password": "Ulang taip kata laluan", + "rate": "Kadar", + "push_notifications_explanation": "Dengan membolehkan pemberitahuan, token peranti anda akan dihantar kepada pelayan, bersama dengan alamat dompet dan ID urus niaga untuk semua dompet dan urus niaga yang dibuat selepas membolehkan pemberitahuan. Token peranti digunakan untuk menghantar pemberitahuan, dan maklumat dompet membenarkan kami memaklumkan anda tentang Bitcoin masuk atau pengesahan urus niaga.\n\nHanya maklumat selepas anda membolehkan pemberitahuan dihantar—tiada yang dikumpulkan sebelum itu.\n\nMelumpuhkan pemberitahuan akan membuang semua maklumat ini daripada pelayan. Selain itu, memadamkan dompet daripada aplikasi juga akan membuang maklumat berkaitan daripada pelayan.", "selfTest": "Swaujian", "save": "Simpan", "saved": "Disimpan", - "success_transaction_broadcasted": "Berjaya! Urus niaga anda sudah disiarkan!", + "success_transaction_broadcasted": "Urus niaga anda berjaya disiarkan!", "total_balance": "Jumlah Baki", "total_balance_explanation": "Paparkan jumlah baki kesemua dompet anda di widget layar beranda anda.", "widgets": "Widget", @@ -295,14 +324,17 @@ }, "notifications": { "would_you_like_to_receive_notifications": "Adakah anda mahu menerima pemberitahuan apabila anda mendapat bayaran sedang masuk?", - "no_and_dont_ask": "Tidak, dan jangan ulang tanya saya.", - "ask_me_later": "Tanya semula kemudian" + "notifications_subtitle": "Bayaran masuk dan pengesahan urus niaga", + "no_and_dont_ask": "Tidak, dan jangan tanya lagi.", + "permission_denied_message": "Anda telah menolak keizinan untuk menghantar pemberitahuan kepada anda. Jika anda ingin menerima pemberitahuan, sila bolehkan dalam tetapan peranti anda." }, "transactions": { + "cancel_explain": "Kami akan menggantikan urus niaga ini dengan satu yang membayar kepada anda dan mempunyai yuran lebih tinggi. Ini secara berkesan membatalkan urus niaga semasa. Hal ini dipanggil RBF—Replace by Fee.", "cancel_no": "Urus niaga ini tidak boleh diganti.", "cancel_title": "Batalkan urus niaga ini (RBF)", + "transaction_loading_error": "Terdapat masalah memuatkan urus niaga. Sila cuba lagi kemudian.", + "transaction_not_available": "Urus niaga tidak tersedia", "confirmations_lowercase": "{confirmations} perakuan", - "copy_link": "Salin Pautan", "expand_note": "Bentangkan Nota", "cpfp_create": "Ciptakan", "cpfp_exp": "Kami akan cipta urus niaga lain yang akan membelanjakan wang dari urus niaga tak terperaku anda. Jumlah yuran akan lebih tinggi dari urus niaga asal, supaya ia dilombong lebih pantas. Hal ini dipanggil CPFP—Cabang Pembayar Fi Pokok.", @@ -310,15 +342,22 @@ "cpfp_title": "Tambah Yuran (CPFP)", "details_balance_hide": "Sorok Baki", "details_balance_show": "Papar Baki", - "details_block": "Ketinggian Bongkah", "details_copy": "Salin", - "details_from": "Masukan", + "details_copy_block_explorer_link": "Salin Pautan Penjelajah Blok", + "details_copy_note": "Salin Nota", + "details_copy_txid": "Salin ID Urus Niaga", "details_inputs": "Masukan", "details_outputs": "Keluaran", + "date": "Tarikh", "details_received": "Diterima", - "transaction_note_saved": "Nota urus niaga berjaya disimpan.", - "details_show_in_block_explorer": "Lihat di Penjelajah Bongkah.", + "details_view_in_browser": "Lihat dalam Pelayar", "details_title": "Urus niaga", + "incoming_transaction": "Urus Niaga Masuk", + "outgoing_transaction": "Urus Niaga Keluar", + "expired_transaction": "Urus Niaga Tamat Tempoh", + "pending_transaction": "Urus Niaga Tergantung", + "offchain": "Off-chain", + "onchain": "On-chain", "details_to": "Keluaran", "enable_offline_signing": "Dompet ini tidak digunakan bersama dengan penandatanganan luar talian. Adakah anda mahu membolehkan ciri ini sekarang?", "list_conf": "Akuan: {number}", @@ -329,52 +368,90 @@ "eta_3h": "ETA: Dalam ~3 jam", "eta_1d": "ETA: Dalam ~1 hari", "list_title": "Urus niaga", + "list_title_sent": "Dihantar", + "list_title_received": "Diterima", + "transaction": "Urus niaga", + "open_url_error": "Tidak dapat membuka pautan dengan pelayar lalai. Sila ubah pelayar lalai anda dan cuba lagi.", + "rbf_explain": "Kami akan menggantikan urus niaga ini dengan satu yang mempunyai yuran lebih tinggi supaya ia dilombong dengan lebih pantas. Hal ini dipanggil RBF—Replace by Fee.", "rbf_title": "Tambah Yuran (RBF)", "status_bump": "Tambah Yuran", "status_cancel": "Batalkan Urus Niaga", "transactions_count": "Bilangan Urus Niaga", "txid": "KP Urus Niaga", - "updating": "Mengemas kini..." + "updating": "Mengemas kini...", + "watchOnlyWarningTitle": "Amaran keselamatan", + "watchOnlyWarningDescription": "Berhati-hati dengan penipu yang sering menggunakan dompet “lihat-saja” untuk memperdaya pengguna. Dompet ini tidak membenarkan anda mengawal atau menghantar wang; ia hanya membenarkan anda melihat baki.", + "custom_fee_warning_title": "Amaran", + "custom_fee_warning_description": "Yuran di bawah 1 sat/vB adalah sah, tetapi mungkin tidak disiarkan disebabkan polisi nod.", + "details_eta_analyzing": "Menganalisis...", + "details_sent": "Dihantar", + "details_section": "Perincian", + "details_explorer": "penjelajah", + "details_network_fee": "Yuran Rangkaian", + "details_to_address": "Kepada", + "details_id": "ID", + "details_note": "Nota", + "details_add_note": "tambah", + "details_advanced": "Lanjut", + "details_fee_rate": "Kadar yuran", + "details_size": "Saiz", + "details_virtual_size": "Saiz maya", + "details_tx_hex": "Heks Urus Niaga", + "details_inputs_count": "Masukan ({count})", + "details_outputs_count": "Keluaran ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Dompet Bitcoin yang mudah dan berkuasa", "add_create": "Cipta", + "total_balance": "Jumlah Baki", + "add_entropy_reset_title": "Tetapkan Semula Entropi", + "add_entropy_reset_message": "Mengubah jenis dompet akan menetapkan semula entropi semasa. Adakah anda mahu meneruskan?", + "add_entropy": "Entropi", + "add_entropy_bytes": "{bytes} bait entropi", "add_entropy_generated": "{gen} bait entropi telah terjana", "add_entropy_provide": "Berikan entropi melalui lemparan dadu", "add_entropy_remain": "{gen} bait entropi telah terjana. Lebihan {rem} bait akan diperoleh dari penjana nombor rawak milik Sistem.", "add_import_wallet": "Pindah masuk dompet", "add_lightning": "Lightning", "add_lightning_explain": "Untuk perbelanjaan dengan urusan sepantas kilat", - "add_lndhub": "Hubungkan ke LNDHub anda", - "add_lndhub_error": "Alamat nod diberi ialah nod LNDHub yang tidak sah.", + "add_lndhub": "Hubung ke LNDhub anda", + "add_lndhub_error": "Alamat nod yang diberikan ialah nod LNDhub yang tidak sah.", "add_lndhub_placeholder": "Alamat Nod Anda", + "add_placeholder": "dompet pertama saya", "add_title": "Tambah Dompet", "add_wallet_name": "Nama", "add_wallet_type": "Jenis", - "balance": "Baki", + "add_wallet_seed_length": "Panjang Benih", + "add_wallet_seed_length_12": "12 perkataan", + "add_wallet_seed_length_24": "24 perkataan", "clipboard_bitcoin": "Ada alamat Bitcoin pada papan sepit anda. Adakah anda mahu menggunakan alamat itu untuk urus niaga?", "clipboard_lightning": "Ada alamat Lightning pada papan sepit anda. Adakah anda mahu menggunakan alamat itu untuk urus niaga?", + "clear_clipboard_on_import": "Kosongkan papan sepit selepas pindah masuk", "details_address": "Alamat", "details_advanced": "Lanjut", "details_are_you_sure": "Adakah anda pasti?", - "details_connected_to": "Terhububg kepada", - "details_del_wb_err": "Jumlah baki yang diberi tidak sepadan dengan baki dompet ini. Sila cuba lagi.", + "details_connected_to": "Terhubung kepada", + "details_del_wb_err": "Jumlah baki yang diberikan tidak sepadan dengan baki dompet ini. Sila cuba lagi.", + "details_del_wb_q": "Dompet ini mempunyai baki. Sebelum meneruskan, sila ambil maklum bahawa anda tidak akan dapat mengembalikan wang tanpa frasa benih dompet ini. Untuk mengelakkan pembuangan secara tidak sengaja, sila masukkan baki dompet anda sebanyak {balance} satoshi.", "details_delete": "Padam", "details_delete_wallet": "Padamkan Dompet", "details_derivation_path": "laluan terbitan", - "details_display": "Paparkan di dalam Senarai Dompet", + "details_display": "Papar di Skrin Beranda", "details_export_backup": "Pindahkan Keluar/Sandarkan", + "details_export_history": "Pindah Keluar Sejarah ke CSV", "details_master_fingerprint": "Cap Jari Induk", "details_multisig_type": "tandamai", - "details_no_cancel": "Tidak, batalkan", - "details_save": "Simpan", "details_show_xpub": "Paparkan Dompet XPUB", "details_show_addresses": "Paparkan alamat", "details_title": "Dompet", + "wallets": "Dompet", + "swipe_balance_hide": "Sembunyikan", + "swipe_balance_show": "Tunjukkan", + "drag_to_reorder": "Seret untuk susun semula", + "clear_search": "Kosongkan carian", "details_type": "Jenis", "details_use_with_hardware_wallet": "Guna dengan Dompet Perkakas", - "details_wallet_updated": "Dompet sudah dikemas kini", "details_yes_delete": "Ya, padamkan", "enter_bip38_password": "Masukkan kata laluan untuk menyahsulit", "export_title": "Pindah Keluar Dompet", @@ -387,48 +464,88 @@ "import_imported": "Dipindah Masuk", "import_scan_qr": "Imbas atau pindah masuk fail", "import_success": "Dompet anda sudah berjaya dipindah masuk.", + "import_success_watchonly": "Dompet anda sudah berjaya dipindah masuk. AMARAN: Ini ialah dompet lihat-saja, anda TIDAK BOLEH membelanjakan daripadanya.", + "import_search_accounts": "Cari akaun", "import_title": "Pindah Masuk", + "learn_more": "Ketahui lebih lanjut", + "import_discovery_title": "Penemuan", + "import_discovery_subtitle": "Pilih dompet yang ditemui", + "import_discovery_derivation": "Guna laluan terbitan tersendiri", + "import_discovery_no_wallets": "Tiada dompet ditemui.", + "import_discovery_offline": "BlueWallet kini berada dalam mod luar talian. Dalam mod ini, ia tidak dapat mengesahkan kewujudan dompet, jadi anda perlu memilih yang betul secara manual", + "import_derivation_found": "Ditemui", + "import_derivation_found_not": "Tidak ditemui", + "import_derivation_loading": "Memuatkan...", + "import_derivation_subtitle": "Masukkan laluan terbitan tersendiri, dan kami akan cuba menemui dompet anda.", + "import_derivation_title": "Laluan terbitan", + "import_derivation_unknown": "Tidak diketahui", + "import_wrong_path": "Laluan terbitan salah", "list_create_a_button": "Tambah sekarang", "list_create_a_wallet": "Tambah dompet", - "list_create_a_wallet_text": "Percuma dan anda boleh mencipta\nsebanyak mana yang anda mahu.", + "list_create_a_wallet_text": "Ia percuma, dan anda boleh cipta \nsebanyak yang anda mahu.", "list_empty_txs1": "Urus niaga anda akan terpapar di sini.", "list_empty_txs1_lightning": "Dompet Lightning sebaiknya digunakan untuk urus niaga harian anda. Yurannya rendah dan kelajuannya sepantas kilat.", "list_empty_txs2": "Mulakan dengan dompet anda.", "list_empty_txs2_lightning": "\nUntuk mula menggunakan, ketik pada Uruskan Wang dan tambah nilai pada baki anda.", "list_latest_transaction": "Urus Niaga Terkini", - "list_ln_browser": "Pelayar LApp", "list_long_choose": "Pilih Gambar", - "list_long_clipboard": "Salin dari Papan Sepit", + "paste_from_clipboard": "Tampal", + "import_file": "Pindah Masuk Fail", "list_long_scan": "Imbas Kod QR", "list_title": "Dompet", "list_tryagain": "Cuba lagi", "no_ln_wallet_error": "Sebelum membayar invois Lightning, anda perlu menambah dompet Lightning terlebih dahulu.", "looks_like_bip38": "Ini nampak seperti kunci persendirian yang dilindungi kata laluan (BIP38)", - "reorder_title": "Ubah Aturan Dompet", + "manage_title": "Uruskan Dompet", + "no_results_found": "Tiada hasil ditemui.", "please_continue_scanning": "Sila teruskan mengimbas.", "select_no_bitcoin": "Tiada dompet Bitcoin ketika ini.", "select_no_bitcoin_exp": "Dompet Bitcoin diperlukan untuk mengisi dompet Lightning. Sila cipta atau pindah masuk sebuah dompet.", "select_wallet": "Pilih Dompet", - "xpub_copiedToClipboard": "Disalin ke papan sepit.", "pull_to_refresh": "Tarik untuk Segar Semula", - "warning_do_not_disclose": "Amaran! Jangan dedahkan.", + "warning_do_not_disclose": "Jangan sekali-kali kongsi maklumat di bawah", + "scan_import": "Imbas kod QR ini untuk memindah masuk dompet anda ke aplikasi lain.", + "write_down_header": "Cipta sandaran manual", + "write_down": "Tuliskan dan simpan perkataan ini dengan selamat. Gunakannya untuk mengembalikan dompet anda di kemudian hari.", + "wallet_type_this": "Jenis dompet ini ialah {type}.", + "share_number": "Kongsi {number}", + "copy_ln_url": "Salin dan simpan URL ini dengan selamat untuk mengembalikan dompet anda di kemudian hari.", + "copy_ln_public": "Salin dan simpan maklumat ini dengan selamat untuk mengembalikan dompet anda di kemudian hari.", "add_ln_wallet_first": "Anda perlu menambah dompet Lightning terlebih dahulu.", "identity_pubkey": "Kunci Umum Keperibadian", - "xpub_title": "Dompet XPUB" + "xpub_title": "Dompet XPUB", + "manage_wallets_search_placeholder": "Cari dompet, alamat, urus niaga dan memo", + "more_info": "Maklumat Lanjut", + "details_delete_wallet_error_message": "Terdapat masalah mengesahkan jika dompet ini telah dibuang daripada pemberitahuan—ini mungkin disebabkan oleh isu rangkaian atau sambungan lemah. Jika anda meneruskan, anda mungkin masih menerima pemberitahuan untuk urus niaga berkaitan dompet ini, walaupun ia dipadamkan.", + "details_delete_anyway": "Padam juga" + }, + "total_balance_view": { + "display_in_bitcoin": "Paparkan dalam Bitcoin", + "hide": "Sembunyikan", + "display_in_sats": "Paparkan dalam sat", + "display_in_fiat": "Paparkan dalam {currency}", + "title": "Jumlah Baki", + "explanation": "Lihat jumlah baki kesemua dompet anda dalam skrin gambaran keseluruhan." }, "multisig": { - "multisig_vault": "Bilik Kebal", + "multisig_vault": "Bilik Kebal Tandamai", "default_label": "Bilik Kebal Tandamai", "multisig_vault_explain": "Keselamatan terbaik untuk jumlah wang yang besar", "provide_signature": "Berikan tandatangan", + "provide_signature_details": "Gunakan peranti dan dompet di mana kunci berada untuk menandatangan urus niaga ini", + "provide_signature_details_bluewallet": "Dalam BlueWallet, pergi ke menu skrin Hantar dan pilih ", + "provide_signature_next_steps": "Imbas atau Pindah Masuk Urus Niaga Bertandatangan", + "provide_signature_next_steps_details": "Sebaik sahaja dompet anda berjaya menandatangan urus niaga, imbas kod QR yang diberikan atau pindah masuk fail yang disertakan, kemudian semak semua butiran urus niaga sebelum menyiarkannya.", "vault_key": "Kunci Bilik Kebal {number}", "required_keys_out_of_total": "Anak kunci diperlukan daripada jumlah keseluruhan", "fee": "Yuran: {number}", "fee_btc": "{number} BTC", "confirm": "Pasti", "header": "Hantar", - "share": "Kongsi", + "share": "Kongsi...", "view": "Lihat", + "shared_key_detected": "Penandatangan bersama dikongsi", + "shared_key_detected_question": "Seorang penandatangan bersama dikongsi dengan anda, adakah anda mahu memindah masuknya?", "manage_keys": "Uruskan Anak Kunci", "how_many_signatures_can_bluewallet_make": "berapa banyak tandatangan BlueWallet boleh buat", "signatures_required_to_spend": "Tandatangan diperlukan {number}", @@ -444,7 +561,9 @@ "co_sign_transaction": "Tandatangani urus niaga", "what_is_vault": "Bilik Kebal ini adalah sebuah dompet", "what_is_vault_numberOfWallets": " {m}-daripada-{n} tandamai.", + "what_is_vault_wallet": "dompet.", "vault_advanced_customize": "Tetapan Bilik Kebal", + "needs": "Ia perlu", "what_is_vault_description_number_of_vault_keys": " {m} anak kunci bilik kebal diperlukan ", "what_is_vault_description_to_spend": "untuk perbelanjaan \ndan kunci ketiga boleh digunakan sebagai sandaran.", "what_is_vault_description_to_spend_other": "untuk perbelanjaan.", @@ -452,20 +571,21 @@ "quorum_header": "Kuorum", "of": "daripada", "wallet_type": "Jenis Dompet", - "invalid_mnemonics": "Frasa nemonik ini nampaknya tidak sah.", - "invalid_cosigner": "Butiran penanda tangan bersama tidak sah", + "invalid_mnemonics": "Frasa mnemonik ini tidak kelihatan sah.", + "invalid_cosigner": "Data penandatangan bersama tidak sah", "not_a_multisignature_xpub": "Ini bukanlah sebentuk XPUB dari dompet tandamai!", - "invalid_cosigner_format": "Penandamai yang salah: Ini bukanlah seorang penandamai untuk susun atur {format}. ", + "invalid_cosigner_format": "Penandatangan bersama tidak betul: Ini bukan penandatangan bersama untuk format {format}.", "create_new_key": "Cipta Baharu", "scan_or_open_file": "Imbas atau buka fail", "i_have_mnemonics": "Saya ada benih untuk kunci ini.", "type_your_mnemonics": "Masukkan benih untuk memindah masuk kunci Bilik Kebal yang anda punyai.", - "this_is_cosigners_xpub": "Ini adalah XPUB milik penandamai—sedia untuk dipindah masuk ke dalam dompet lain. Selamat untuk dikongsikan.", - "wallet_key_created": "Kunci Bilik Kebal anda sudah dicipta. Sila ambil sedikit waktu untuk membuat sandaran benih nemonik anda.", - "are_you_sure_seed_will_be_lost": "Adakah anda pasti? Benih nemonik anda akan hilang jika anda tidak membuat sandaran.", + "this_is_cosigners_xpub": "Ini ialah XPUB penandatangan bersama—sedia untuk dipindah masuk ke dompet lain. Selamat untuk dikongsi.", + "this_is_cosigners_xpub_airdrop": "Jika anda berkongsi melalui AirDrop, penerima perlu berada di skrin penyelarasan.", + "wallet_key_created": "Kunci Bilik Kebal anda sudah dicipta. Sila ambil sedikit waktu untuk membuat sandaran frasa mnemonik anda.", + "are_you_sure_seed_will_be_lost": "Adakah anda pasti? Frasa mnemonik anda akan hilang jika anda tidak membuat sandaran.", "forget_this_seed": "Lupakan benih ini dan gunakan XPUB sebagai ganti.", - "view_edit_cosigners": "Lihat/Ubah Penandamai", - "this_cosigner_is_already_imported": "Penandamai ini sudah dipindah masuk.", + "view_edit_cosigners": "Lihat/Sunting Penandatangan Bersama", + "this_cosigner_is_already_imported": "Penandatangan bersama ini sudah dipindah masuk.", "export_signed_psbt": "Pindah Keluar PSBT Bertandatangan Penuh", "input_fp": "Masukkan cap jari", "input_fp_explain": "Langkau untuk gunakan nilai lalai (00000000)", @@ -483,26 +603,41 @@ "ms_help_title4": "Memindah Masuk Bilik Kebal", "ms_help_4": "Untuk memindah masuk dompet tandamai, gunakan fail sandaran anda dan ciri Pindah Masuk. Jika anda hanya ada benih dan XPUB, anda boleh menggunakan butang Pindah Masuk tersendiri ketika mencipta anak kunci Bilik Kebal.", "ms_help_title5": "Mod Lanjut", - "ms_help_5": "Secara lalai, BlueWallet akan menjana sebuah Bilik Kebal 2-daripada-3. Untuk mencipta kuorom berbeza atau mengubah jenis alamat, giatkan Mod Lanjut di dalam Tetapan." + "ms_help_5": "Secara lalai, BlueWallet akan menjana sebuah Bilik Kebal 2-daripada-3. Untuk mencipta kuorum berbeza atau mengubah jenis alamat, giatkan Mod Lanjut di dalam Tetapan." }, "is_it_my_address": { "title": "Adakah ini alamat saya?", "owns": "{label} memiliki alamat {address}", "enter_address": "Masukkan alamat", "check_address": "Periksa alamat", - "no_wallet_owns_address": "Tiada dompet yang anda ada memiliki alamat yang diberikan." + "no_wallet_owns_address": "Tiada dompet yang anda ada memiliki alamat yang diberikan.", + "view_qrcode": "Lihat Kod QR" + }, + "autofill_word": { + "title": "Perkataan terakhir benih", + "enter": "Masukkan frasa mnemonik separa anda", + "generate_word": "Jana perkataan terakhir", + "error": "Input bukanlah mnemonik separa 11 atau 23 perkataan. Sila cuba lagi." }, "cc": { "change": "Ubah", "coins_selected": "Duit yang Dipilih ({number})", - "empty": "Dompet ini tidak mempunyai sebarang duit ketika ini.", + "selected_summ": "{value} dipilih", + "empty": "Dompet ini tidak mempunyai sebarang duit pada masa ini.", "freeze": "Bekukan", "freezeLabel": "Bekukan", "freezeLabel_un": "Cairkan", "header": "Kawalan Duit", "use_coin": "Gunakan Duit", "use_coins": "Gunakan Duit", - "tip": "Ciri ini membolehkan anda untuk melihat, melabel, membeku, atau memilih duit bagi menambah baik pengurusan dompet. Anda boleh pilih pelbagai duit dengan mengetik pada bulatan berwarna." + "tip": "Ciri ini membolehkan anda untuk melihat, melabel, membeku, atau memilih duit bagi menambah baik pengurusan dompet. Anda boleh pilih pelbagai duit dengan mengetik pada bulatan berwarna.", + "sort_asc": "Menaik", + "sort_desc": "Menurun", + "sort_height": "Ketinggian", + "sort_value": "Nilai", + "sort_label": "Label", + "sort_status": "Keadaan", + "sort_by": "Susun mengikut" }, "units": { "BTC": "BTC", @@ -511,8 +646,14 @@ "sats": "sat" }, "addresses": { + "copy_private_key": "Salin kunci persendirian", + "sensitive_private_key": "Amaran: kunci persendirian amat sensitif. Teruskan?", + "sign_title": "Tandatangan/Absahkan Pesanan", + "sign_help": "Di sini anda boleh mencipta atau mengesahkan tandatangan kriptografi berdasarkan alamat Bitcoin.", "sign_sign": "Tandatangan", "sign_verify": "Absahkan", + "sign_signature_correct": "Pengesahan berjaya!", + "sign_signature_incorrect": "Pengesahan gagal!", "sign_placeholder_address": "Alamat", "sign_placeholder_message": "Pesanan", "sign_placeholder_signature": "Tandatangan", @@ -521,5 +662,43 @@ "type_receive": "Terima", "type_used": "Digunakan", "transactions": "Urus Niaga" + }, + "lnurl_auth": { + "register_question_part_1": "Adakah anda mahu mendaftar akaun di", + "register_question_part_2": "menggunakan dompet Lightning anda?", + "register_answer": "Anda berjaya mendaftar akaun di {hostname}!", + "login_question_part_1": "Adakah anda mahu log masuk di", + "login_question_part_2": "menggunakan dompet Lightning anda?", + "login_answer": "Anda berjaya log masuk di {hostname}!", + "link_question_part_1": "Adakah anda mahu memautkan akaun anda di", + "link_question_part_2": "ke dompet Lightning anda?", + "link_answer": "Dompet Lightning anda berjaya dipautkan ke akaun anda di {hostname}!", + "auth_question_part_1": "Adakah anda mahu disahkan di", + "auth_question_part_2": "menggunakan dompet Lightning anda?", + "auth_answer": "Anda berjaya disahkan di {hostname}!", + "could_not_auth": "Kami tidak dapat mengesahkan anda di {hostname}.", + "authenticate": "Sahkan" + }, + "bip47": { + "payment_code": "Kod Pembayaran", + "contacts": "Kenalan", + "bip47_explain": "Kod yang boleh digunakan semula dan dikongsi", + "bip47_explain_subtitle": "BIP47", + "purpose": "Kod yang boleh digunakan semula dan dikongsi (BIP47)", + "pay_this_contact": "Bayar kenalan ini", + "rename_contact": "Namakan semula kenalan", + "copy_payment_code": "Salin Kod Pembayaran", + "hide_contact": "Sembunyikan kenalan", + "rename": "Namakan semula", + "provide_name": "Berikan nama baharu untuk kenalan ini", + "add_contact": "Tambah Kenalan", + "provide_payment_code": "Berikan Kod Pembayaran", + "invalid_pc": "Kod Pembayaran tidak sah", + "notification_tx_unconfirmed": "Urus niaga pemberitahuan belum disahkan, sila tunggu", + "failed_create_notif_tx": "Gagal mencipta urus niaga on-chain", + "onchain_tx_needed": "Urus niaga on-chain diperlukan", + "notif_tx_sent": "Urus niaga pemberitahuan dihantar. Sila tunggu untuk pengesahan", + "notif_tx": "Urus niaga pemberitahuan", + "not_found": "Kod pembayaran tidak ditemui" } } diff --git a/loc/nb_no.json b/loc/nb_no.json index ac4b203a3ca..4dd3467e664 100644 --- a/loc/nb_no.json +++ b/loc/nb_no.json @@ -4,24 +4,32 @@ "cancel": "Avbryt", "continue": "Fortsett", "clipboard": "Utklippstavle", + "copied": "Kopiert!", + "discard_changes": "Forkaste endringer?", + "discard_changes_explain": "Du har ulagrede endringer. Er du sikker på at du vil forkaste dem og forlate skjermen?", "enter_password": "Oppgi passord", "never": "Aldri", - "disabled": "Deaktivert", "of": "{number} av {total}", "ok": "OK", - "storage_is_encrypted": "Din lagring er kryptert. Passord er nødvendig for å dekryptere det.", + "enter_url": "Oppgi URL", + "storage_is_encrypted": "Lagringen din er kryptert. Passord er nødvendig for å dekryptere den.", "yes": "Ja", "no": "Nei", - "save": "Lagre", + "save": "Lagre...", "seed": "Seed", "success": "Vellykket", "wallet_key": "Lommebok-nøkkel", - "invalid_animated_qr_code_fragment" : "Ugyldig animert QRCode-fragment. Vennligst prøv på nytt.", - "file_saved": "Filen {filePath} er lagret i {destination}.", - "downloads_folder": "Nedlastingsmappe" - }, - "alert": { - "default": "Varsling" + "close": "Lukk", + "change_input_currency": "Bytt valuta", + "refresh": "Oppdater", + "pick_image": "Velg fra biblioteket", + "pick_file": "Velg fil", + "enter_amount": "Oppgi beløp", + "qr_custom_input_button": "Trykk 10 ganger for å oppgi egendefinert inndata", + "unlock": "Lås opp", + "ssl_port": "SSL-port", + "suggested": "Foreslått", + "port": "Port" }, "azteco": { "codeIs": "Din kupongkode er", @@ -30,12 +38,14 @@ "redeem": "Løs inn til lommebok", "redeemButton": "Løs inn", "success": "Vellykket", + "successMessage": "Kupongen ble innløst! Midlene dine skal ankomme Bitcoin-lommeboken din om kort tid.", "title": "Løs inn Azte.co-kupong" }, "entropy": { "save": "Lagre", "title": "Entropi", - "undo": "Angre" + "undo": "Angre", + "amountOfEntropy": "{bits} av {limit} bits" }, "errors": { "broadcast": "Sendingen mislyktes.", @@ -43,80 +53,63 @@ "network": "Nettverksfeil" }, "lnd": { - "active":"Aktiv", - "inactive":"Inaktiv", - "channels": "Kanaler", - "no_channels": "Ingen kanaler", - "claim_balance": "Få saldo {balance}", - "close_channel": "Lukk kanal", - "new_channel" : "Ny kanal", - "errorInvoiceExpired": "Faktura utløpt", - "force_close_channel": "Tvinge kanal til å lukke?", + "errorInvoiceExpired": "Fakturaen er utløpt.", "expired": "Utløpt", - "node_alias": "Node alias", "expiresIn": "Utløper om {time} minutter", "payButton": "Betal", - "placeholder": "Faktura", - "open_channel": "Åpne kanal", - "funding_amount_placeholder": "Finansieringsbeløp, for eksempel 0,001", - "opening_channnel_for_from":"Åpner kanal for lommebok {forWalletLabel}, med finansiering fra {fromWalletLabel}", - "are_you_sure_open_channel": "Er du sikker på at du vil åpne denne kanalen?", - "potentialFee": "Potensiell avgift: {fee}", - "remote_host": "Remote host", + "payment": "Betaling", + "placeholder": "Faktura eller adresse", + "potentialFee": "Potensielt gebyr: {fee}", "refill": "Fyll på", - "reconnect_peer": "Koble til peer på nytt", "refill_create": "For å fortsette må du lage en Bitcoin-lommebok du kan fylle på med.", "refill_external": "Fyll på med Ekstern Lommebok", "refill_lnd_balance": "Fyll på Lightning Wallet-balanse", - "sameWalletAsInvoiceError": "Du kan ikke betale en faktura med samme lommebok som ble brukt til å opprette den.", - "title": "Administrer Midler", - "can_send": "Kan Sende", - "can_receive": "Kan Motta", - "view_logs": "Vis Logg" + "sameWalletAsInvoiceError": "Du kan ikke betale en faktura med samme lommebok som brukes til å lage den.", + "title": "Administrer Midler" }, "lndViewInvoice": { "additional_info": "Tilleggsinformasjon", "for": "Til:", "lightning_invoice": "Lightning-faktura", - "open_direct_channel": "Åpne direkte kanal med denne noden:", "please_pay_between_and": "Vennligst betal mellom {min} og {max}", "please_pay": "Vennligst betal", - "preimage": "Preimage", - "sats": "sats.", - "wasnt_paid_and_expired": "Denne fakturaen ble ikke betalt og har utløpt." + "preimage": "Forhåndsbilde", + "date_time": "Dato og klokkeslett", + "wasnt_paid_and_expired": "Denne fakturaen ble ikke betalt og har utløpt.", + "sats": "sats." }, "plausibledeniability": { "create_fake_storage": "Opprett Kryptert Lagring", - "create_password": "Lag et passord", "create_password_explanation": "Passordet for den falske lagringen skal ikke samsvare med passordet for hovedlagringen din.", - "help": "Under visse omstendigheter kan du bli tvunget til å oppgi et passord. For å holde myntene dine trygge, kan BlueWallet opprette en annen kryptert lagring med et annet passord. Under press kan du avsløre dette passordet til en tredjepart. Hvis den legges inn i BlueWallet, vil den låse opp en ny \"falsk\" lagring. Dette vil virke legitimt for tredjeparten, men det vil i hemmelighet holde hovedlagringen din trygt.", + "help": "Under visse omstendigheter kan du bli tvunget til å oppgi et passord. For å holde myntene dine trygge, kan BlueWallet opprette en annen kryptert lagring med et annet passord. Under press kan du avsløre dette passordet til en tredjepart. Hvis det legges inn i BlueWallet, vil det låse opp en ny \"falsk\" lagring. Dette vil virke legitimt for tredjeparten, men det vil i hemmelighet holde hovedlagringen din trygg.", "help2": "Den nye lagringen vil være fullt funksjonell, og du kan lagre noen minimumsbeløp der slik at den ser mer troverdig ut.", "password_should_not_match": "Passordet er i bruk. Prøv et annet passord.", - "passwords_do_not_match": "Passordene stemmer ikke overens. Vennligst prøv igjen.", - "retype_password": "Skriv inn passordet på nytt", - "success": "Vellykket", "title": "Plausibel Fornektelse" }, "pleasebackup": { "ask": "Har du lagret lommebokens sikkerhetskopifrase? Denne sikkerhetskopifrasen er nødvendig for å få tilgang til pengene dine i tilfelle du mister denne enheten. Uten sikkerhetskopifrasen vil pengene dine gå tapt permanent.", - "ask_no": "Nei, det jeg har ikke", - "ask_yes": "Ja, det har jeg", - "ok": "Greit, jeg skrev den ned", - "ok_lnd": "Greit, jeg har lagret den", + "ask_no": "Nei, det har jeg ikke.", + "ask_yes": "Ja, det har jeg.", + "ok": "Greit, jeg skrev det ned.", + "ok_lnd": "OK, jeg har lagret den.", "text": "Vennligst bruk et øyeblikk til å skrive ned denne mnemoniske frasen på et stykke papir.\nDet er sikkerhetskopien din, og du kan bruke den til å gjenopprette lommeboken.", "text_lnd": "Vennligst lagre denne sikkerhetskopien. Den lar deg gjenopprette lommeboken i tilfelle tap.", - "title": "Lommeboken din er opprettet" + "title": "Lommeboken din er opprettet." }, "receive": { - "details_create": "Oprett", + "details_create": "Opprett", "details_label": "Beskrivelse", "details_setAmount": "Motta med beløp", - "details_share": "Del", + "details_share": "Del...", + "address_not_found": "Kan ikke generere mottaksadresse.", "header": "Motta", + "reset": "Tilbakestill", "maxSats": "Maksimalt beløp er {max} sats", "maxSatsFull": "Maksimalt beløp er {max} sats eller {currency}", "minSats": "Minimumsbeløpet er {min} sats", - "minSatsFull": "Minimumsbeløpet er {min} sats eller {currency}" + "minSatsFull": "Minimumsbeløpet er {min} sats eller {currency}", + "qrcode_for_the_address": "QR-kode for adressen", + "bip47_explanation": "Betalingskoder er en universell adresse som unngår å avsløre lommebokadressene dine. Ikke alle tjenester vil støtte dem." }, "send": { "provided_address_is_invoice": "Denne adressen ser ut til å være for en Lightning-faktura. Vennligst gå til Lightning-lommeboken din for å foreta en betaling for denne fakturaen.", @@ -131,15 +124,20 @@ "create_broadcast": "Kringkast", "create_copy": "Kopier og kringkast senere", "create_details": "Detaljer", - "create_fee": "Avgift", - "create_memo": "Memo", - "create_satoshi_per_vbyte": "Satoshi per vByte", - "create_this_is_hex": "Dette er din transaksjonsheks - signert og klar til å kringkastes til nettverket.", + "create_fee": "Gebyr", + "create_this_is_hex": "Dette er transaksjonens hex – signert og klar til å kringkastes til nettverket.", "create_to": "Til", "create_tx_size": "Transaksjonsstørrelse", "create_verify": "Bekreft på coinb.in", + "details_insert_contact": "Sett inn kontakt", "details_add_rec_add": "Legg til mottaker", "details_add_rec_rem": "Fjern mottaker", + "details_add_recc_rem_all_alert_description": "Er du sikker på at du vil fjerne alle mottakere?", + "details_add_rec_rem_all": "Fjern alle mottakere", + "details_recipients_title": "Mottakere", + "details_recipient_title": "Mottaker #{number} av #{total}", + "please_complete_recipient_title": "Ufullstendig mottaker", + "please_complete_recipient_details": "Vennligst fyll ut detaljene for mottaker #{number} før du legger til en ny mottaker.", "details_address": "Adresse", "details_address_field_is_not_valid": "Adressen er ikke gyldig.", "details_adv_fee_bump": "Tillat \"Fee Bump\"", @@ -152,13 +150,14 @@ "details_amount_field_is_less_than_minimum_amount_sat": "Det angitte beløpet er for lite. Vennligst skriv inn et beløp som er større enn 500 sats.", "details_create": "Opprett Faktura", "details_error_decode": "Kan ikke dekode Bitcoin-adressen", - "details_fee_field_is_not_valid": "Avgiftsfeltet er ikke gyldig", - "details_frozen": "{amount} BTC er frosset", + "details_fee_field_is_not_valid": "Gebyret er ikke gyldig.", + "details_frozen": "{amount} BTC er fryst.", "details_next": "Neste", "details_no_signed_tx": "Den valgte filen inneholder ikke en transaksjon som kan importeres.", "details_note_placeholder": "Notat til meg selv", "details_scan": "Skanne", "details_scan_hint": "Dobbelttrykk for å skanne eller importere en destinasjon", + "details_scan_error": "Skannefeil", "details_total_exceeds_balance": "Sendingsbeløpet overstiger den tilgjengelige saldoen.", "details_total_exceeds_balance_frozen": "Sendingsbeløpet overstiger den tilgjengelige saldoen. Vær oppmerksom på at frosne mynter er ekskludert.", "details_unrecognized_file_format": "Ukjent filformat", @@ -166,15 +165,13 @@ "dynamic_init": "Initialiserer", "dynamic_next": "Neste", "dynamic_prev": "Forrige", - "dynamic_start": "Start", "dynamic_stop": "Stopp", - "fee_10m": "10m", - "fee_1d": "1d", "fee_3h": "3t", "fee_custom": "Tilpass", + "insert_custom_fee": "Sett inn gebyr", "fee_fast": "Rask", "fee_medium": "Middels", - "fee_replace_minvb": "Den totale avgiften (satoshi per vByte) du vil betale bør være høyere enn {min} sat/vByte.", + "fee_replace_minvb": "Det totale gebyret (satoshi per vByte) du vil betale bør være høyere enn {min} sat/vByte.", "fee_satvbyte": "i sat/vByte", "fee_slow": "Tregt", "header": "Sende", @@ -184,9 +181,8 @@ "input_total": "Totalt:", "permission_camera_message": "Vi trenger din tillatelse for å bruke kameraet ditt.", "psbt_sign": "Signer en transaksjon", + "invalid_psbt": "Ugyldig PSBT oppgitt.", "open_settings": "Åpne Innstillinger", - "permission_storage_later": "Spør meg senere", - "permission_storage_message": "BlueWallet trenger din tillatelse for å få tilgang til lagringsplassen din for å lagre denne filen.", "permission_storage_denied_message": "BlueWallet kan ikke lagre denne filen. Åpne innstillinger og aktiver lagringstillatelse.", "permission_storage_title": "Lagringstilgangstillatelse", "psbt_clipboard": "Kopier til utklippstavle", @@ -196,78 +192,89 @@ "outdated_rate": "Prisen ble sist oppdatert: {date}", "psbt_tx_open": "Åpne Signert Transaksjon", "psbt_tx_scan": "Skann Signert Transaksjon", - "qr_error_no_qrcode": "Vi kunne ikke finne en QR-kode i det valgte bildet. Sørg for at bildet bare inneholder en QR-kode og ikke noe tilleggsinnhold som tekst eller knapper.", + "qr_error_no_qrcode": "Vi kunne ikke finne en gyldig QR-kode i det valgte bildet. Sørg for at bildet kun inneholder en QR-kode og ikke noe annet innhold som tekst eller knapper.", "reset_amount": "Tilbakestill Beløp", "reset_amount_confirm": "Vil du tilbakestille beløpet?", "success_done": "Ferdig", - "txSaved": "Transaksjonsfilen ({filePath}) er lagret i nedlastingsmappen din.", - "problem_with_psbt": "Problem med PSBT" + "txSaved": "Transaksjonsfilen ({filePath}) er lagret.", + "file_saved_at_path": "Filen ({filePath}) er lagret.", + "cant_send_to_silentpayment_adress": "Denne lommeboken kan ikke sende til SilentPayment-adresser", + "cant_send_to_bip47": "Denne lommeboken kan ikke sende til BIP47-betalingskoder", + "cant_find_bip47_notification": "Legg først til denne betalingskoden i kontakter", + "problem_with_psbt": "Problem med PSBT", + "create_memo": "Notat", + "create_satoshi_per_vbyte": "Satoshi per vByte", + "dynamic_start": "Start", + "fee_10m": "10 min", + "fee_1d": "1 d" }, "settings": { "about": "Om", "about_awesome": "Bygget med det fantastiske", "about_backup": "Sikkerhetskopier alltid nøklene dine!", "about_free": "BlueWallet er et gratis og åpent kildekode-prosjekt. Laget av Bitcoin-brukere.", - "about_license": "MIT License", "about_release_notes": "Versjonsmerknader", "about_review": "Gi oss en anmeldelse", + "performance_score": "Ytelsesresultat: {num}", + "run_performance_test": "Test ytelse", "about_selftest": "Kjør selvtest", + "block_explorer_invalid_custom_url": "URL-en som er oppgitt, er ugyldig. Vennligst oppgi en gyldig URL som begynner med http:// eller https://.", "about_selftest_electrum_disabled": "Selvtesting er ikke tilgjengelig med Electrum Offline Mode. Deaktiver offline-modus og prøv igjen.", "about_selftest_ok": "Alle interne tester har bestått. Lommeboken fungerer bra.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord Server", "about_sm_telegram": "Telegram-kanal", - "about_sm_twitter": "Følg oss på Twitter", - "advanced_options": "Avanserte Instillinger", - "biometrics": "Biometrics", + "privacy_temporary_screenshots": "Tillat skjermopptak", + "privacy_temporary_screenshots_instructions": "Beskyttelse mot skjermopptak vil bli midlertidig slått av, slik at skjermbilder og skjermopptak blir mulig. Beskyttelsen vil automatisk aktiveres igjen når du lukker og åpner BlueWallet på nytt.", + "biometrics_no_longer_available": "Enhetsinnstillingene dine har endret seg og samsvarer ikke lenger med de valgte sikkerhetsinnstillingene i appen. Aktiver biometri eller tilgangskode på nytt, og start deretter appen på nytt for å bruke endringene.", "biom_10times": "Du har forsøkt å skrive inn passordet ditt 10 ganger. Vil du tilbakestille lagringen din? Dette vil fjerne alle lommebøker og dekryptere lagringen din.", "biom_conf_identity": "Vennligst bekreft din identitet.", - "biom_no_passcode": "Enheten din har ikke et passord. For å fortsette, vennligst konfigurer et passord i Innstillinger-appen.", + "biom_no_passcode": "Enheten din har ikke en tilgangskode eller biometri aktivert. For å fortsette, vennligst konfigurer en tilgangskode eller biometri i Innstillinger-appen.", "biom_remove_decrypt": "Alle lommebøker vil bli fjernet og lagringen din vil bli dekryptert. Er du sikker på at du vil fortsette?", "currency": "Valuta", - "currency_source": "Pris er hentet fra", + "currency_source": "Kurs hentes fra", "currency_fetch_error": "Det oppsto en feil under henting av kursen for den valgte valutaen.", - "default_desc": "Når den er deaktivert, vil BlueWallet umiddelbart åpne den valgte lommeboken ved åpning.", - "default_info": "Standardinformasjon", "default_title": "Ved åpning", - "default_wallets": "Se Alle Lommebøker", + "donate": "Doner", + "donate_description": "Hjelp oss å holde Blue gratis!", "electrum_connected": "Tilkoblet", "electrum_connected_not": "Ikke Tilkoblet", - "electrum_error_connect": "Kan ikke koble til den Electrum-serveren", - "lndhub_uri": "F.eks. {eksempel}", - "electrum_host": "F.eks. {eksempel}", + "electrum_error_connect": "Kan ikke koble til den oppgitte Electrum-serveren", + "electrum_error_connect_tor": "Kan ikke koble til den oppgitte Electrum-serveren. Sørg for at Orbot-appen er tilkoblet og prøv igjen.", + "lndhub_uri": "F.eks. {example}", + "electrum_host": "F.eks. {example}", "electrum_offline_mode": "Offline-modus", "electrum_offline_description": "Når den er aktivert, vil ikke Bitcoin-lommebøkene forsøke å hente saldoer eller transaksjoner.", - "electrum_port": "Port, vanligvis {eksempel}", + "electrum_port": "Port, vanligvis {example}", "use_ssl": "Bruk SSL", "electrum_saved": "Endringene dine er lagret. Det kan være nødvendig å starte BlueWallet på nytt for at endringene skal tre i kraft.", "set_electrum_server_as_default": "Vil du angi {server} som standard Electrum-server?", - "set_lndhub_as_default": "Vil du angi {url} som standard LNDHub-server?", - "electrum_settings_server": "Electrum Server", - "electrum_settings_explain": "La feltet stå tomt for å bruke standard.", - "electrum_status": "Status", - "electrum_clear_alert_title": "Slett logg?", - "electrum_clear_alert_message": "Vil du slette electrum-serverhistorikken?", - "electrum_clear_alert_cancel": "Avbryt", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Velg", - "electrum_reset": "Tilbakestill til standard", + "set_lndhub_as_default": "Vil du angi {url} som standard LNDhub-server?", + "electrum_preferred_server": "Foretrukket server", + "electrum_preferred_server_description": "Oppgi serveren du vil at lommeboken din skal bruke for alle Bitcoin-aktiviteter. Når den er angitt, vil lommeboken din utelukkende bruke denne serveren til å sjekke saldoer, sende transaksjoner og hente nettverksdata. Sørg for at du stoler på denne serveren før du angir den.", "electrum_unable_to_connect": "Kan ikke koble til {server}.", - "electrum_history": "Serverhistorikk", - "electrum_reset_to_default": "Er du sikker på at du vil tilbakestille Electrum-innstillingene dine til standard?", - "electrum_clear": "Rydd", - "tor_supported": "Tor støttet", - "tor_unsupported": "Tor-tilkoblinger støttes ikke.", + "electrum_history": "Historikk", + "electrum_reset_to_default": "Dette vil la BlueWallet velge en server tilfeldig fra serverlisten.", + "electrum_reset": "Tilbakestill til standard", + "electrum_reset_to_default_and_clear_history": "Tilbakestill til standard og slett historikk", "encrypt_decrypt": "Dekrypter Lagring", "encrypt_decrypt_q": "Er du sikker på at du vil dekryptere lagringen din? Dette vil tillate tilgang til lommeboken din uten passord.", - "encrypt_enc_and_pass": "Kryptert og passordbeskyttet", + "encrypt_enc_and_pass": "Passordbeskyttet", + "encrypt_storage_explanation_headline": "Aktiver kryptert lagring", + "encrypt_storage_explanation_description_line1": "Aktivering av kryptert lagring legger til et ekstra beskyttelseslag i appen ved å sikre måten dataene dine lagres på enheten. Dette gjør det vanskeligere for noen å få tilgang til informasjonen din uten tillatelse.", + "encrypt_storage_explanation_description_line2": "Det er imidlertid viktig å vite at denne krypteringen kun beskytter tilgangen til lommebøkene som er lagret i enhetens nøkkelring. Den setter ikke et passord eller noen ekstra beskyttelse på lommebøkene selv.", + "i_understand": "Jeg forstår", + "block_explorer": "Block Explorer", + "block_explorer_preferred": "Bruk foretrukket block explorer", + "block_explorer_error_saving_custom": "Feil ved lagring av foretrukket block explorer", "encrypt_title": "Sikkerhet", "encrypt_tstorage": "Lagring", - "encrypt_use": "Bruk {type}", - "encrypt_use_expl": "{type} vil bli brukt til å bekrefte identiteten din før du foretar en transaksjon, låser opp, eksporterer eller sletter en lommebok. {type} vil ikke bli brukt til å låse opp kryptert lagring.", + "encrypt_use": "Bruk {type}", + "set_as_preferred": "Angi som foretrukket", + "set_as_preferred_electrum": "Å angi {host}:{port} som foretrukket server vil deaktivere tilkobling til en foreslått server tilfeldig.", + "encrypted_feature_disabled": "Denne funksjonen kan ikke brukes når kryptert lagring er aktivert.", + "encrypt_use_expl": "{type} vil bli brukt til å bekrefte identiteten din før du gjør en transaksjon, låser opp, eksporterer eller sletter en lommebok.", + "biometrics_fail": "Hvis {type} ikke er aktivert eller mislykkes i å låse opp, kan du bruke enhetens tilgangskode som et alternativ.", "general": "Generelt", - "general_adv_mode": "Avansert Modus", - "general_adv_mode_e": "Når den er aktivert, vil du se avanserte alternativer som forskjellige lommeboktyper, muligheten til å spesifisere hvilken LNDHub-instance du ønsker å koble til, og tilpasset entropi under oppretting av lommebok.", "general_continuity": "Kontinuitet", "general_continuity_e": "Når den er aktivert, vil du kunne se utvalgte lommebøker og transaksjoner ved å bruke de andre Apple iCloud-tilkoblede enhetene dine.", "groundcontrol_explanation": "GroundControl er en gratis, åpen kildekode push-varslingsserver for Bitcoin-lommebøker. Du kan installere din egen GroundControl-server og legge dens URL her for ikke å stole på BlueWallet sin infrastruktur. La feltet stå tomt for å bruke GroundControl sin standardserver.", @@ -275,72 +282,82 @@ "language": "Språk", "last_updated": "Sist Oppdatert", "language_isRTL": "Å starte BlueWallet på nytt er nødvendig for at språkorienteringen skal tre i kraft.", - "lightning_error_lndhub_uri": "Ugyldig LNDHub URI", + "license": "Lisens", + "lightning_error_lndhub_uri": "Ugyldig LNDhub-URI", + "lightning_error_lndhub_uri_tor": "Ugyldig LNDhub-URI. Sørg for at Orbot-appen er tilkoblet og prøv igjen.", "lightning_saved": "Endringene dine er lagret.", "lightning_settings": "Lightning Innstillinger", - "tor_settings": "Tor Innstillinger", - "lightning_settings_explain": "For å koble til din egen LND-node, installer LNDHub og legg inn URL-adressen her i innstillingene. La feltet stå tomt for å bruke BlueWallet sin LNDHub . Vær oppmerksom på at bare lommebøker opprettet etter lagring av endringer vil koble til den angitte LNDHub.", + "lightning_settings_explain": "For å koble til din egen LND-node, vennligst installer LNDhub og legg dens URL her i innstillingene. Vær oppmerksom på at bare lommebøker som er opprettet etter at endringer er lagret, vil koble til den angitte LNDhub-en.", + "lndhub_github": "GitHub-repositorium", "network": "Nettverk", "network_broadcast": "Kringkast Transaksjon", - "network_electrum": "Electrum Server", + "electrum_suggested_description": "Når en foretrukket server ikke er angitt, vil en foreslått server bli valgt for bruk tilfeldig.", "not_a_valid_uri": "Ugyldig URI", "notifications": "Varslinger", - "open_link_in_explorer" : "Åpne lenken i Utforsker", + "open_link_in_explorer": "Åpne lenken i Utforsker", "password": "Passord", - "password_explain": "Opprett passordet du vil bruke til å dekryptere lagringen", - "passwords_do_not_match": "Passordene er ikke like.", + "password_explain": "Oppgi passordet du vil bruke for å låse opp lagringen din.", "plausible_deniability": "Plausibel Fornektelse", "privacy": "Personvern", "privacy_read_clipboard": "Les Utklippstavlen", "privacy_system_settings": "Systeminnstillinger", "privacy_quickactions": "Lommebok-snarveier", - "privacy_quickactions_explanation": "Berør og hold BlueWallet-appikonet inne på startskjermen for raskt å se lommebokens saldo.", + "privacy_quickactions_explanation": "Trykk og hold på BlueWallet-appens ikon for raskt å se saldoen for lommeboken din.", "privacy_clipboard_explanation": "Oppgi snarveier hvis en adresse eller faktura er funnet i utklippstavlen.", "privacy_do_not_track": "Deaktiver Analyse", "privacy_do_not_track_explanation": "Informasjon om ytelse og pålitelighet vil ikke bli sendt inn for analyse.", - "push_notifications": "Varslinger", - "rate": "Rate", - "retype_password": "Skriv inn passordet på nytt", + "push_notifications_explanation": "Ved å aktivere varslinger vil enhetstokenet ditt bli sendt til serveren, sammen med lommebokadresser og transaksjons-ID-er for alle lommebøker og transaksjoner som er gjort etter at varslinger ble aktivert. Enhetstokenet brukes til å sende varslinger, og lommebokinformasjonen lar oss varsle deg om innkommende Bitcoin eller transaksjonsbekreftelser.\n\nKun informasjon fra etter at du aktiverer varslinger overføres – ingenting fra før blir samlet inn.\n\nÅ deaktivere varslinger vil fjerne all denne informasjonen fra serveren. I tillegg vil sletting av en lommebok fra appen også fjerne tilhørende informasjon fra serveren.", "selfTest": "Selvtest", "save": "Lagre", "saved": "Lagret", - "success_transaction_broadcasted" : "Suksess! Transaksjonen din har blitt kringkastet!", + "success_transaction_broadcasted": "Transaksjonen din er kringkastet!", "total_balance": "Total balanse", "total_balance_explanation": "Vis den totale saldoen til alle lommebokene dine på widgetene på startskjermen.", - "widgets": "Widgets", - "tools": "Verktøy" + "tools": "Verktøy", + "about_license": "MIT-lisens", + "biometrics": "Biometri", + "electrum_settings_server": "Electrum-server", + "electrum_status": "Status", + "network_electrum": "Electrum-server", + "rate": "Kurs", + "widgets": "Widgeter" }, "notifications": { "would_you_like_to_receive_notifications": "Vil du motta varsler når du mottar innbetalinger?", - "no_and_dont_ask": "Nei, og ikke spør meg igjen", - "ask_me_later": "Spør meg senere" + "notifications_subtitle": "Innkommende betalinger og transaksjonsbekreftelser", + "no_and_dont_ask": "Nei, og ikke spør meg igjen.", + "permission_denied_message": "Du har nektet tillatelse til å sende deg varslinger. Hvis du vil motta varslinger, vennligst aktiver dem i enhetens innstillinger." }, "transactions": { "cancel_explain": "Vi vil erstatte denne transaksjonen med en som betaler deg og har høyere gebyrer. Dette kansellerer gjeldende transaksjon. Dette kalles RBF - Replace by Fee.", "cancel_no": "Denne transaksjonen kan ikke erstattes", "cancel_title": "Kanseller denne transaksjonen (RBF)", + "transaction_loading_error": "Det oppsto et problem under lasting av transaksjonen. Prøv igjen senere.", + "transaction_not_available": "Transaksjon ikke tilgjengelig", "confirmations_lowercase": "{confirmations} bekreftelser", - "copy_link": "Kopier link", "expand_note": "Utvid Merknad", "cpfp_create": "Opprett", "cpfp_exp": "Vi vil opprette en annen transaksjon som bruker den ubekreftede transaksjonen din. Det totale gebyret vil være høyere enn det opprinnelige transaksjonsgebyret, så den bør utvinnes raskere. Dette kalles CPFP - Child Pays for Parent.", "cpfp_no_bump": "Denne transaksjonen kan ikke sendes raskere.", - "cpfp_title": "Betal høyere avgift (CPFP)", + "cpfp_title": "Betal høyere gebyr (CPFP)", "details_balance_hide": "Gjem Balanse", "details_balance_show": "Vis Balanse", - "details_block": "Blokkhøyde", "details_copy": "Kopier", - "details_copy_amount": "Kopier Beløp", "details_copy_block_explorer_link": "Kopier Block Explorer-lenken", "details_copy_note": "Kopier Merknad", "details_copy_txid": "Kopier Transaksjons-ID", - "details_from": "Inndata", "details_inputs": "Inndata", "details_outputs": "Utdata", + "date": "Dato", "details_received": "Mottatt", - "transaction_note_saved": "Transaksjonsnotatet er lagret.", - "details_show_in_block_explorer": "Vis i Block Explorer", + "details_view_in_browser": "Vis i nettleser", "details_title": "Transaksjon", + "incoming_transaction": "Innkommende transaksjon", + "outgoing_transaction": "Utgående transaksjon", + "expired_transaction": "Utløpt transaksjon", + "pending_transaction": "Ventende transaksjon", + "offchain": "Utenfor kjeden", + "onchain": "På kjeden", "details_to": "Utdata", "enable_offline_signing": "Denne lommeboken brukes ikke i forbindelse med en offline-signering. Vil du aktivere det nå?", "list_conf": "Bekreftelser: {number}", @@ -350,58 +367,89 @@ "eta_10m": "ETA: Om ~10 minutter", "eta_3h": "ETA: Om ~3 timer", "eta_1d": "ETA: Om ~1 dag", - "view_wallet": "Se {walletLabel}", "list_title": "Transaksjoner", + "list_title_sent": "Sendt", + "list_title_received": "Mottatt", + "transaction": "Transaksjon", "open_url_error": "Kan ikke åpne lenken med standardnettleseren. Vennligst endre standardnettleser og prøv igjen.", - "rbf_explain": "Vi vil erstatte denne transaksjonen med en med en høyere avgift, slik at den blir utvunnet raskere. Dette kalles RBF - Replace by Fee.", - "rbf_title": "Betal en høyere avgift (RBF)", - "status_bump": "Betal en høyere avgift", + "rbf_explain": "Vi vil erstatte denne transaksjonen med en med et høyere gebyr, slik at den blir utvunnet raskere. Dette kalles RBF - Replace by Fee.", + "rbf_title": "Betal et høyere gebyr (RBF)", + "status_bump": "Betal et høyere gebyr", "status_cancel": "Avbryt transaksjon", "transactions_count": "Antall Transaksjoner", "txid": "Transaksjons-ID", - "updating": "Oppdaterer..." + "updating": "Oppdaterer...", + "watchOnlyWarningTitle": "Sikkerhetsadvarsel", + "watchOnlyWarningDescription": "Vær forsiktig med svindlere som ofte bruker \"kun observasjon\"-lommebøker for å lure brukere. Disse lommebøkene lar deg ikke kontrollere eller sende midler; de lar deg bare se saldoen.", + "custom_fee_warning_title": "Advarsel", + "custom_fee_warning_description": "Gebyrer under 1 sat/vB er gyldige, men kan kanskje ikke videresendes på grunn av noderegler.", + "details_eta_analyzing": "Analyserer...", + "details_sent": "Sendt", + "details_section": "Detaljer", + "details_explorer": "utforsker", + "details_network_fee": "Nettverksgebyr", + "details_to_address": "Til", + "details_note": "Notat", + "details_add_note": "legg til", + "details_advanced": "Avansert", + "details_fee_rate": "Gebyrsats", + "details_size": "Størrelse", + "details_virtual_size": "Virtuell størrelse", + "details_tx_hex": "Tx-hex", + "details_inputs_count": "Inndata ({count})", + "details_outputs_count": "Utdata ({count})", + "details_id": "ID" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Enkel og kraftig Bitcoin-lommebok", "add_create": "Opprett", + "total_balance": "Total balanse", + "add_entropy_reset_title": "Tilbakestill entropi", + "add_entropy_reset_message": "Endring av lommeboktype vil tilbakestille gjeldende entropi. Vil du fortsette?", + "add_entropy": "Entropi", + "add_entropy_bytes": "{bytes} bytes med entropi", "add_entropy_generated": "{gen} bytes med generert entropi", "add_entropy_provide": "Gi entropi via terningkast", "add_entropy_remain": "{gen} bytes med generert entropi. Gjenværende {rem} bytes vil bli hentet fra systemet sin tilfeldige tallgenerator.", "add_import_wallet": "Importer lommebok", "add_lightning": "Lightning", "add_lightning_explain": "For betaling med umiddelbare transaksjoner", - "add_lndhub": "Koble til din LNDHub", - "add_lndhub_error": "Den oppgitte nodeadressen er en ugyldig LNDHub-node.", + "add_lndhub": "Koble til din LNDhub", + "add_lndhub_error": "Den oppgitte nodeadressen er en ugyldig LNDhub-node.", "add_lndhub_placeholder": "Din nodeadresse", "add_placeholder": "min første lommebok", "add_title": "Legg til lommebok", "add_wallet_name": "Navn", - "add_wallet_type": "Type", - "balance": "Balanse", + "add_wallet_seed_length": "Seed-lengde", + "add_wallet_seed_length_12": "12 ord", + "add_wallet_seed_length_24": "24 ord", "clipboard_bitcoin": "Du har en Bitcoin-adresse på utklippstavlen. Vil du bruke den til en transaksjon?", "clipboard_lightning": "Du har en Lightning-faktura på utklippstavlen. Vil du bruke den til en transaksjon?", + "clear_clipboard_on_import": "Tøm utklippstavle ved import", "details_address": "Adresse", "details_advanced": "Avansert", "details_are_you_sure": "Er du sikker?", "details_connected_to": "Koblet til", - "details_del_wb_err": "Det oppgitte saldobeløpet samsvarer ikke med denne lommeboksaldoen. Vennligst prøv på nytt.", - "details_del_wb_q": "Denne lommeboken har en balanse. Før du fortsetter, vær oppmerksom på at du ikke vil være i stand til å få tilbake pengene dine uten lommeboken sin seed phrase. For å unngå utilsiktet fjerning, skriv inn lommeboken sin saldo på {balance} satoshier.", + "details_del_wb_err": "Det oppgitte saldobeløpet samsvarer ikke med denne lommebokens saldo. Prøv igjen.", + "details_del_wb_q": "Denne lommeboken har en saldo. Før du fortsetter, vær oppmerksom på at du ikke vil være i stand til å få tilbake pengene dine uten lommebokens seed phrase. For å unngå utilsiktet fjerning, skriv inn lommebokens saldo på {balance} satoshier.", "details_delete": "Slett", "details_delete_wallet": "Slett Lommebok", "details_derivation_path": "derivation path", - "details_display": "Vis i lommebokliste", + "details_display": "Vis på startskjermen", "details_export_backup": "Eksporter / backup", + "details_export_history": "Eksporter historikk til CSV", "details_master_fingerprint": "Master Fingerprint", "details_multisig_type": "multisig", - "details_no_cancel": "Nei, avbryt", - "details_save": "Lagre", "details_show_xpub": "Vis lommebok XPUB", "details_show_addresses": "Vis adresser", "details_title": "Lommebok", - "details_type": "Type", + "wallets": "Lommebøker", + "swipe_balance_hide": "Skjul", + "swipe_balance_show": "Vis", + "drag_to_reorder": "Dra for å endre rekkefølge", + "clear_search": "Tøm søk", "details_use_with_hardware_wallet": "Bruk med maskinvarelommebok", - "details_wallet_updated": "Lommebok oppdatert", "details_yes_delete": "Ja, slett", "enter_bip38_password": "Skriv inn passord for å dekryptere", "export_title": "Eksporter Lommebok", @@ -414,61 +462,87 @@ "import_imported": "Importert", "import_scan_qr": "Skann eller importer en fil", "import_success": "Lommeboken din har blitt importert.", + "import_success_watchonly": "Lommeboken din har blitt importert. ADVARSEL: Dette er en kun observasjon-lommebok, du kan IKKE bruke den til å sende fra.", "import_search_accounts": "Søk kontoer", "import_title": "Importer", + "learn_more": "Lær mer", "import_discovery_title": "Oppdag", "import_discovery_subtitle": "Velg en oppdaget lommebok", "import_discovery_derivation": "Bruk egendefinert derivation path", "import_discovery_no_wallets": "Ingen lommebøker ble funnet.", - "import_derivation_found": "funnet", - "import_derivation_found_not": "ikke funnet", - "import_derivation_loading": "laster...", - "import_derivation_subtitle": "Angi egendefinert derivation path, og vi vil prøve å finne lommeboken din", + "import_discovery_offline": "BlueWallet er for øyeblikket i offline-modus. I denne modusen kan den ikke verifisere eksistensen av lommeboken, så du må velge riktig en manuelt", + "import_derivation_found": "Funnet", + "import_derivation_found_not": "Ikke funnet", + "import_derivation_loading": "Laster...", + "import_derivation_subtitle": "Oppgi egendefinert derivation path, så vil vi prøve å oppdage lommeboken din.", "import_derivation_title": "Derivation path", - "import_derivation_unknown": "ukjent", - "import_wrong_path": "feil derivation path", + "import_derivation_unknown": "Ukjent", + "import_wrong_path": "Feil derivation path", "list_create_a_button": "Legg til nå", "list_create_a_wallet": "Legg til en lommebok", - "list_create_a_wallet_text": "Det er gratis og du kan lage\nså mange du vil.", + "list_create_a_wallet_text": "Det er gratis, og du kan opprette \nså mange du vil.", "list_empty_txs1": "Dine transaksjoner vil vises her.", - "list_empty_txs1_lightning": "Lightning-lommeboken bør brukes til dine daglige transaksjoner. Avgiftene er urettferdig billige og hastigheten er lynrask.", + "list_empty_txs1_lightning": "Lightning-lommeboken bør brukes til dine daglige transaksjoner. Gebyrene er urettferdig billige og hastigheten er lynrask.", "list_empty_txs2": "Start med lommeboken din.", "list_empty_txs2_lightning": "\nFor å begynne å bruke den, trykk på Administrer Midler og fyll på saldoen.", "list_latest_transaction": "Siste Transaksjon", - "list_ln_browser": "LApp-nettleser", "list_long_choose": "Velg Bilde", - "list_long_clipboard": "Kopier fra utklippstavlen", + "paste_from_clipboard": "Lim inn", + "import_file": "Importer Fil", "list_long_scan": "Skann QR-kode", "list_title": "Lommebøker", "list_tryagain": "Prøv igjen", "no_ln_wallet_error": "Før du betaler en Lightning-faktura, må du først legge til en Lightning-lommebok.", "looks_like_bip38": "Dette ser ut som en passordbeskyttet privat nøkkel (BIP38).", - "reorder_title": "Omorganisere Lommebøker", - "reorder_instructions": "Trykk og hold en lommebok for å dra den rundt på listen.", + "manage_title": "Administrer lommebøker", + "no_results_found": "Ingen resultater funnet.", "please_continue_scanning": "Vennligst fortsett å skanne.", "select_no_bitcoin": "Det er for øyeblikket ingen tilgjengelige Bitcoin-lommebøker.", "select_no_bitcoin_exp": "En Bitcoin-lommebok kreves for å fylle Lightning-lommebøker. Opprett eller importer en.", "select_wallet": "Velg Lommebok", - "xpub_copiedToClipboard": "Kopiert til utklippstavlen.", "pull_to_refresh": "Dra for å oppdatere", - "warning_do_not_disclose": "Advarsel! Ikke avslør.", + "warning_do_not_disclose": "Del aldri informasjonen nedenfor", + "scan_import": "Skann denne QR-koden for å importere lommeboken din i en annen applikasjon.", + "write_down_header": "Opprett en manuell sikkerhetskopi", + "write_down": "Skriv ned og lagre disse ordene på en sikker måte. Bruk dem til å gjenopprette lommeboken din senere.", + "wallet_type_this": "Denne lommeboktypen er {type}.", + "share_number": "Del {number}", + "copy_ln_url": "Kopier og lagre denne URL-en på en sikker måte for å gjenopprette lommeboken din senere.", + "copy_ln_public": "Kopier og lagre denne informasjonen på en sikker måte for å gjenopprette lommeboken din senere.", "add_ln_wallet_first": "Du må først legge til en Lightning-lommebok.", "identity_pubkey": "Identity Pubkey", - "xpub_title": "Wallet XPUB" + "manage_wallets_search_placeholder": "Søk i lommebøker, adresser, transaksjoner og notater", + "more_info": "Mer info", + "details_delete_wallet_error_message": "Det oppsto et problem med å bekrefte om denne lommeboken ble fjernet fra varslinger – dette kan skyldes et nettverksproblem eller dårlig tilkobling. Hvis du fortsetter, kan du fortsatt motta varslinger for transaksjoner relatert til denne lommeboken, selv etter at den er slettet.", + "details_delete_anyway": "Slett likevel", + "add_wallet_type": "Type", + "details_type": "Type", + "xpub_title": "Lommebok XPUB" + }, + "total_balance_view": { + "display_in_bitcoin": "Vis i Bitcoin", + "hide": "Skjul", + "display_in_sats": "Vis i sats", + "display_in_fiat": "Vis i {currency}", + "title": "Total balanse", + "explanation": "Vis den totale saldoen til alle lommebøkene dine på oversiktsskjermen." }, "multisig": { - "multisig_vault": "Vault", - "default_label": "Multisig Vault", "multisig_vault_explain": "Beste sikkerhet for store beløp", - "provide_signature": "Skriv signatur", + "provide_signature": "Oppgi signatur", + "provide_signature_details": "Bruk enheten og lommeboken der nøkkelen befinner seg for å signere denne transaksjonen", + "provide_signature_details_bluewallet": "I BlueWallet går du til Send-skjermmenyen og velger ", + "provide_signature_next_steps": "Skann eller importer signert transaksjon", + "provide_signature_next_steps_details": "Når lommeboken din har signert transaksjonen, skanner du den oppgitte QR-koden eller importerer den medfølgende filen, og deretter gjennomgår du alle transaksjonsdetaljene før du kringkaster den.", "vault_key": "Vault-nøkkel {number}", "required_keys_out_of_total": "Nødvendige nøkler av totalen", - "fee": "Avgift: {number}", + "fee": "Gebyr: {number}", "fee_btc": "{number} BTC", "confirm": "Bekreft", - "header": "Send", - "share": "Del", + "share": "Del...", "view": "Vis", + "shared_key_detected": "Delt medsignerer", + "shared_key_detected_question": "En medsignerer ble delt med deg, vil du importere den?", "manage_keys": "Administrer Nøkler", "how_many_signatures_can_bluewallet_make": "hvor mange signaturer kan BlueWallet lage", "signatures_required_to_spend": "Signaturer kreves {number}", @@ -488,26 +562,26 @@ "vault_advanced_customize": "Vault Innstillinger", "needs": "Den trenger", "what_is_vault_description_number_of_vault_keys": " {m} vault-nøkler", - "what_is_vault_description_to_spend": "å bruke og en tredje du\nkan brukes som backup.", + "what_is_vault_description_to_spend": "å bruke og en tredje du\nkan bruke som sikkerhetskopi.", "what_is_vault_description_to_spend_other": "å bruke.", "quorum": "{m} av {n} quorum", - "quorum_header": "Quorum", "of": "av", "wallet_type": "Lommebok Type", "invalid_mnemonics": "Denne mnemoniske frasen ser ikke ut til å være gyldig.", - "invalid_cosigner": "Ugyldig medunderskriver-data", + "invalid_cosigner": "Ugyldige medsignerer-data", "not_a_multisignature_xpub": "Dette er ikke en XPUB fra en multisignatur-lommebok!", - "invalid_cosigner_format": "Feil medunderskriver: Dette er ikke en medunderskriver for {format}-format.", + "invalid_cosigner_format": "Feil medsignerer: Dette er ikke en medsignerer for {format}-format.", "create_new_key": "Opprett Ny", "scan_or_open_file": "Skann eller åpne fil", "i_have_mnemonics": "Jeg har en seed til denne nøkkelen.", "type_your_mnemonics": "Sett inn en seed for å importere din eksisterende Vault-nøkkel.", - "this_is_cosigners_xpub": "Dette er medunderskriveren sin XPUB – klar til å importeres til en annen lommebok. Det er trygt å dele det.", - "wallet_key_created": "Vault-nøkkelen din ble opprettet. Ta deg tid til å ta sikkerhetskopi din mnemoniske seed.", + "this_is_cosigners_xpub": "Dette er medsignererens XPUB – klar til å bli importert til en annen lommebok. Det er trygt å dele den.", + "this_is_cosigners_xpub_airdrop": "Hvis du deler via AirDrop, må mottakerne være på koordineringsskjermen.", + "wallet_key_created": "Vault-nøkkelen din ble opprettet. Ta deg tid til å ta sikkerhetskopi av din mnemoniske seed.", "are_you_sure_seed_will_be_lost": "Er du sikker? Din mnemoniske seed vil gå tapt hvis du ikke har en sikkerhetskopi.", "forget_this_seed": "Glem den seeden og bruk XPUB i stedet.", - "view_edit_cosigners": "Se/rediger Medunderskrivere", - "this_cosigner_is_already_imported": "Denne medunderskriveren er allerede importert.", + "view_edit_cosigners": "Vis/rediger medsignerere", + "this_cosigner_is_already_imported": "Denne medsignereren er allerede importert.", "export_signed_psbt": "Eksporter Signert PSBT", "input_fp": "Skriv inn fingeravtrykk", "input_fp_explain": "Hopp over for å bruke standard (00000000)", @@ -525,7 +599,11 @@ "ms_help_title4": "Importerer Vault", "ms_help_4": "For å importere en multisig, bruk sikkerhetskopifilen og importfunksjonen. Hvis du bare har seeds og XPUB-er, kan du bruke den individuelle Import-knappen når du oppretter Vault-nøkler.", "ms_help_title5": "Enable advanced mode", - "ms_help_5": "Som standard vil BlueWallet generere et 2-av-3 Vault. For å opprette et annet quorum eller endre adressetypen, aktiver Avansert-modus i innstillingene." + "ms_help_5": "Som standard vil BlueWallet generere et 2-av-3 Vault. For å opprette et annet quorum eller endre adressetypen, aktiver Avansert-modus i innstillingene.", + "multisig_vault": "Multisig Vault", + "default_label": "Multisig Vault", + "header": "Send", + "quorum_header": "Quorum" }, "is_it_my_address": { "title": "Er det adressen min?", @@ -533,7 +611,13 @@ "enter_address": "Skriv inn adresse", "check_address": "Sjekk adresse", "no_wallet_owns_address": "Ingen av de tilgjengelige lommebøkene eier den oppgitte adressen.", - "view_qrcode": "Se QR-kode" + "view_qrcode": "Vis QR-kode" + }, + "autofill_word": { + "title": "Seedens siste ord", + "enter": "Oppgi din delvise mnemoniske frase", + "generate_word": "Generer det siste ordet", + "error": "Inndataene er ikke en 11- eller 23-ords delvis mnemonisk frase. Prøv igjen." }, "cc": { "change": "Endre", @@ -544,9 +628,16 @@ "freezeLabel": "Frys", "freezeLabel_un": "Fjern frys", "header": "Myntkontroll", - "use_coin": "Bryk Mynt", + "use_coin": "Bruk Mynt", "use_coins": "Bruk Myntene", - "tip": "Denne funksjonen lar deg se, merke, fryse eller velge mynter for forbedret lommebokadministrasjon. Du kan velge flere mynter ved å trykke på de fargede sirklene." + "tip": "Denne funksjonen lar deg se, merke, fryse eller velge mynter for forbedret lommebokadministrasjon. Du kan velge flere mynter ved å trykke på de fargede sirklene.", + "sort_asc": "Stigende", + "sort_desc": "Synkende", + "sort_height": "Høyde", + "sort_value": "Verdi", + "sort_label": "Merkelapp", + "sort_by": "Sorter etter", + "sort_status": "Status" }, "units": { "BTC": "BTC", @@ -555,6 +646,8 @@ "sats": "sats" }, "addresses": { + "copy_private_key": "Kopier privat nøkkel", + "sensitive_private_key": "Advarsel: private nøkler er ekstremt sensitive. Fortsette?", "sign_title": "Signer/Bekreft Melding", "sign_help": "Her kan du opprette eller verifisere en kryptografisk signatur basert på en Bitcoin-adresse.", "sign_sign": "Signer", @@ -585,5 +678,27 @@ "auth_answer": "Du har autentisert på {hostname}!", "could_not_auth": "Vi kunne ikke autentisere deg til {hostname}.", "authenticate": "Autentiser" + }, + "bip47": { + "payment_code": "Betalingskode", + "contacts": "Kontakter", + "bip47_explain": "Gjenbrukbar og delbar kode", + "bip47_explain_subtitle": "BIP47", + "purpose": "Gjenbrukbar og delbar kode (BIP47)", + "pay_this_contact": "Betal denne kontakten", + "rename_contact": "Gi nytt navn til kontakt", + "copy_payment_code": "Kopier betalingskode", + "hide_contact": "Skjul kontakt", + "rename": "Gi nytt navn", + "provide_name": "Oppgi nytt navn for denne kontakten", + "add_contact": "Legg til kontakt", + "provide_payment_code": "Oppgi betalingskode", + "invalid_pc": "Ugyldig betalingskode", + "notification_tx_unconfirmed": "Varslingstransaksjonen er ikke bekreftet ennå, vennligst vent", + "failed_create_notif_tx": "Kunne ikke opprette on-chain-transaksjon", + "onchain_tx_needed": "On-chain-transaksjon kreves", + "notif_tx_sent": "Varslingstransaksjon sendt. Vent på at den skal bekreftes", + "notif_tx": "Varslingstransaksjon", + "not_found": "Betalingskode ikke funnet" } } diff --git a/loc/ne.json b/loc/ne.json index 060f9be83ed..0bfdcd1fbd6 100644 --- a/loc/ne.json +++ b/loc/ne.json @@ -4,31 +4,32 @@ "cancel": "क्यान्सेल गर्नुहोस्", "continue": "जारी गर्नुहोस्", "clipboard": "क्लिपबोर्ड", + "copied": "कापी गरियो!", + "discard_changes": "परिवर्तनहरू खारेज गर्ने हो?", + "discard_changes_explain": "तपाईंसँग सुरक्षित नगरिएका परिवर्तनहरू छन्। के तपाईं तिनीहरूलाई खारेज गरेर यो स्क्रिनबाट बाहिर निस्कन निश्चित हुनुहुन्छ?", "enter_password": "पासवर्ड राख्नुहोस्", "never": "कहिल्यै", - "disabled": "अक्षम", "of": "{number} मध्ये {total}", "ok": "ठिक छ", + "enter_url": "URL राख्नुहोस्", "storage_is_encrypted": "तपाईंको स्टोरेज इन्क्रिप्टेड छ। डिक्रिप्ट गर्न पासवर्ड को आवश्यक छ।", "yes": "हो", "no": "होइन", - "save": "सेव", + "save": "सेव गर्नुहोस्...", "seed": "सीड", "success": "सफल", "wallet_key": "वालेट को सांचो", - "invalid_animated_qr_code_fragment": "अवैध एनिमेटेड QRCode को टुक्रा। फेरि प्रयास गर्नुहोस।", - "file_saved": "तपाईंको फाइल - {filePath}, गन्तव्यमा - {destination} मा सेव गरिएको छ।", - "downloads_folder": "डाउनलोड फोल्डर", "close": "बन्द", "change_input_currency": "इनपुट मुद्रा परिवर्तन गर्नुहोस्", "refresh": "रिफ्रेस गर्नुहोस", - "more": "अरु", - "pick_file": "एउटा फाइल छान्नुहोस्", + "pick_image": "लाइब्रेरीबाट छान्नुहोस्", + "pick_file": "फाइल छान्नुहोस्", "enter_amount": "रकम राख्नुहोस्", - "qr_custom_input_button": "अनुकूल इनपुट प्रविष्ट गर्न 10 पटक ट्याप गर्नुहोस्" - }, - "alert": { - "default": "सचेत" + "qr_custom_input_button": "अनुकूल इनपुट प्रविष्ट गर्न 10 पटक ट्याप गर्नुहोस्", + "unlock": "अनलक गर्नुहोस्", + "port": "पोर्ट", + "ssl_port": "SSL पोर्ट", + "suggested": "सुझाव गरिएको" }, "azteco": { "codeIs": "तपाईंको भाउचर कोड", @@ -37,12 +38,14 @@ "redeem": "वालेटमा रिडिम गर्नुहोस्", "redeemButton": "रिडिम गर्नुहोस्", "success": "सफल", + "successMessage": "भाउचर सफलतापूर्वक रिडिम गरियो! तपाईंको कोष चाँडै तपाईंको बिटकोइन वालेटमा आइपुग्नेछ।", "title": "Azte.co भाउचर रिडिम गर्नुहोस्" }, "entropy": { "save": "सेव", "title": "एन्ट्रोपी", - "undo": "पूर्ववत गर्नुहोस्" + "undo": "पूर्ववत गर्नुहोस्", + "amountOfEntropy": "{limit} मध्ये {bits} बिट" }, "errors": { "broadcast": "प्रसारण असफल भयो।", @@ -50,64 +53,45 @@ "network": "नेटवर्क एर्रोर" }, "lnd": { - "active": "सक्रिय", - "inactive": "निष्क्रिय", - "channels": "च्यानलहरू", - "no_channels": "च्यानलहरू छैनन्", - "claim_balance": "दावी ब्यालेन्स {balance}", - "close_channel": "च्यानलहरू बन्द गर्नुहोस्", - "new_channel": "नयाँ च्यानल", - "errorInvoiceExpired": "इनभ्वाइसको म्याद सकियो", - "force_close_channel": "जबरजस्ती च्यानल बन्द गर्ने हो?", + "errorInvoiceExpired": "इनभ्वाइसको म्याद सकियो।", "expired": "म्याद सकियो", - "node_alias": "नोड को उपनाम", "expiresIn": "{time} मिनेटमा म्याद सकिन्छ", "payButton": "तिर्नुहोस्", - "open_channel": "च्यानल खोल्नुहोस्", - "funding_amount_placeholder": "कोष रकम, उदाहरण को लागी 0.001", - "opening_channnel_for_from": "वालेट {forWalletLabel} को लागि वालेट {fromWalletLabel} को कोषद्वारा बाट च्यानल खोल्दै", - "are_you_sure_open_channel": "के तपाइँ यो च्यानल खोल्न निश्चित हुनुहुन्छ?", + "payment": "भुक्तानी", + "placeholder": "इनभ्वाइस वा ठेगाना", "potentialFee": "सम्भावित शुल्क: {fee}", - "remote_host": "रिमोट होस्ट", "refill": "फेरि भर्नु", - "reconnect_peer": "पियर पुन: जडान गर्नुहोस्", "refill_create": "अगाडि बढ्नको लागि, कृपया बिटकोइन वालेट सिर्जना गर्नुहोस्।", "refill_external": "बाहिरको वालेटको साथ रिफिल गर्नुहोस्", "refill_lnd_balance": "लाइटनिङ वालेट ब्यालेन्स रिफिल गर्नुहोस्", - "title": "कोष प्रबन्ध गर्नुहोस्", - "can_send": "पठाउन सकिन्छ", - "can_receive": "प्राप्त गर्न सक्छ", - "view_logs": "लगहरू हेर्नुहोस्" + "sameWalletAsInvoiceError": "तपाईंले इनभ्वाइस सिर्जना गर्न प्रयोग गरिएको उही वालेटले इनभ्वाइसको भुक्तानी गर्न सक्नुहुन्न।", + "title": "कोष प्रबन्ध गर्नुहोस्" }, "lndViewInvoice": { "additional_info": "थप जानकारी", "for": "के को लागि:", "lightning_invoice": "लाइटनिङ इनभ्वाइस", - "open_direct_channel": "यो नोडको साथ प्रत्यक्ष च्यानल खोल्नुहोस्:", "please_pay_between_and": "कृपया {min} र {max} को बीचमा भुक्तानी गर्नुहोस्", "please_pay": "कृपया तिर्नुहोस्", - "preimage": "प्रिमेज", - "sats": "सातस।", + "preimage": "प्रीइमेज", + "sats": "sats।", + "date_time": "मिति र समय", "wasnt_paid_and_expired": "यो इनभ्वाइस भुक्तान गरिएको थिएन र म्याद सकिएको छ।" }, "plausibledeniability": { "create_fake_storage": "ईन्क्रिप्टेड स्टोरेज सिर्जना गर्नुहोस्", - "create_password": "पासवर्ड बनाउनुहोस्", "create_password_explanation": "नक्कली स्टोरेजको पासवर्ड तपाईको मुख्य स्टोरेजको पासवर्डसँग मेल खानु हुँदैन।", "help": "केहि परिस्थितिहरूमा, तपाइँ पासवर्ड खुलासा गर्न बाध्य हुन सक्छ। आफ्नो कोइन सुरक्षित राख्नको लागि, BlueWallet फरक पासवर्डको साथ अर्को इन्क्रिप्टेड स्टोरेज सिर्जना गर्न सक्छ। दबाबमा, तपाईंले तेस्रो पक्षलाई यो पासवर्ड खुलासा गर्न सक्नुहुन्छ। यदि BlueWallet मा प्रविष्ट गरियो भने, यसले नयाँ \"नक्कली\" स्टोरेज अनलक गर्नेछ। यो तेस्रो पक्षलाई वैध देखिन्छ, तर यसले गोप्य रूपमा सिक्काको साथ तपाईंको मुख्य स्टोरेज सुरक्षित राख्नेछ।", "help2": "नयाँ स्टोरेज पूर्ण रूपमा कार्यात्मक हुनेछ, र तपाईंले त्यहाँ केही न्यूनतम रकमहरू स्टोर गर्न सक्नुहुन्छ ताकि यो अझ विश्वासयोग्य देखिन्छ।", "password_should_not_match": "पासवर्ड हाल प्रयोगमा छ। कृपया फरक पासवर्ड प्रयास गर्नुहोस्।", - "passwords_do_not_match": "पासवर्डहरू मेल खाएन। फेरि प्रयास गर्नुहोस।", - "retype_password": "पासवर्ड पुन: लेख्नुहोस", - "success": "सफल", "title": "व्यावहारिक अस्वीकार्यता" }, "pleasebackup": { "ask": "के तपाईंले आफ्नो वालेटको ब्याकअप पासफ्रेज सेव गर्नुभयो? यदि तपाईँले यो यन्त्र गुमाउनु भयो भने, यो ब्याकअप पासफ्रेज तपाईँको कोष पहुँच गर्न आवश्यक हुनेछ। ब्याकअप पासफ्रेज बिना, तपाईंको कोष स्थायी रूपमा हराउनेछ।", - "ask_no": "होइन", - "ask_yes": "हो", - "ok": "ठीक छ, मैले यो लेखे", - "ok_lnd": "ठीक छ, मैले यसलाई सुरक्षित गरेको छु", + "ask_no": "होइन, मैले गरेको छैन।", + "ask_yes": "हो, मैले गरेको छु।", + "ok": "ठिक छ, मैले लेखिसकेँ।", + "ok_lnd": "ठिक छ, मैले सेव गरिसकेँ।", "text": "कृपया कागजको टुक्रामा यो निमोनिक फ्रेज लेख्न एक क्षण लिनुहोस्।\nयो तपाइँको ब्याकअप हो र तपाइँ वालेट रिकभर गर्न प्रयोग गर्न सक्नुहुन्छ।", "text_lnd": "कृपया यो वालेट ब्याकअप सेव गर्नुहोस्। यसले वालेट हराएको अवस्थामा पुनर्स्थापना गर्न मद्दत गर्नेछ।", "title": "तपाईंको वालेट सिर्जना गरिएको छ।" @@ -116,19 +100,23 @@ "details_create": "बनाउनुहोस", "details_label": "विवरण", "details_setAmount": "रकम सहित प्राप्त गर्नुहोस्", - "details_share": "सेयर गर्नुहोस्", + "details_share": "सेयर गर्नुहोस्...", + "address_not_found": "प्राप्त गर्ने ठेगाना बनाउन असमर्थ।", "header": "प्राप्त गर्नुहोस्", + "reset": "रिसेट गर्नुहोस्", "maxSats": "अधिकतम रकम {max} sats हो", "maxSatsFull": "अधिकतम रकम {max} sats वा {currency} हो", "minSats": "न्यूनतम रकम {min} sats हो", - "minSatsFull": "न्यूनतम रकम {min} sats वा {currency} हो" + "minSatsFull": "न्यूनतम रकम {min} sats वा {currency} हो", + "qrcode_for_the_address": "ठेगानाको लागि QR कोड", + "bip47_explanation": "भुक्तानी कोडहरू एक विश्वव्यापी ठेगाना हुन् जसले तपाईंको वालेट ठेगानाहरू खुलासा गर्नबाट जोगाउँछ। सबै सेवाहरूले यसलाई समर्थन गर्दैनन्।" }, "send": { "provided_address_is_invoice": "यो ठेगाना लाइटनिङ इनभ्वाइसको लागि हो जस्तो देखिन्छ। कृपया, यो इनभ्वाइसको लागि भुक्तानी गर्न आफ्नो लाइटनिङ वालेटमा जानुहोस्।", "broadcastButton": "प्रसारण", "broadcastError": "एर्रोर", "broadcastNone": "लेनदेन हेक्स राख्नुहोस्", - "broadcastPending": "पेनदिंग", + "broadcastPending": "विचाराधीन", "broadcastSuccess": "सफल", "confirm_header": "पक्का", "confirm_sendNow": "अहिले पठाउनुहोस्", @@ -143,8 +131,15 @@ "create_to": "को लागी", "create_tx_size": "लेनदेन आकार", "create_verify": "coinb.in मा प्रमाणित गर्नुहोस्", + "details_insert_contact": "सम्पर्क प्रविष्ट गर्नुहोस्", "details_add_rec_add": "प्रापक थप्नुहोस्", "details_add_rec_rem": "प्रापक हटाउनुहोस्", + "details_add_recc_rem_all_alert_description": "के तपाईं सबै प्रापकहरू हटाउन निश्चित हुनुहुन्छ?", + "details_add_rec_rem_all": "सबै प्रापकहरू हटाउनुहोस्", + "details_recipients_title": "प्रापकहरू", + "details_recipient_title": "प्रापक #{number} मध्ये #{total}", + "please_complete_recipient_title": "अपूर्ण प्रापक", + "please_complete_recipient_details": "नयाँ प्रापक थप्नु अघि कृपया प्रापक #{number} को विवरणहरू पूरा गर्नुहोस्।", "details_address": "ठेगाना", "details_address_field_is_not_valid": "ठेगाना मान्य छैन।", "details_adv_fee_bump": "शुल्क बम्प गर्न अनुमति दिनुहोस्", @@ -158,15 +153,16 @@ "details_create": "इनभ्वाइस सिर्जना गर्नुहोस्", "details_error_decode": "बिटकोइन ठेगाना डिकोड गर्न असमर्थ भयो", "details_fee_field_is_not_valid": "शुल्क मान्य छैन।", - "details_frozen": "{amount} BTC स्थिर छ", + "details_frozen": "{amount} BTC फ्रिज गरिएको छ।", "details_next": "अर्को", - "details_no_signed_tx": "चयन गरिएको फाइलमा आयात गर्न सकिने लेनदेन समावेश छैन।", + "details_no_signed_tx": "चयन गरिएको फाइलमा आयात गर्न सकिने लेनदेन छैन।", "details_note_placeholder": "आफैलाई नोट", "details_scan": "स्क्यान", "details_scan_hint": "स्क्यान गर्न वा गन्तव्य आयात गर्न डबल ट्याप गर्नुहोस्", + "details_scan_error": "स्क्यान त्रुटि", "details_total_exceeds_balance": "पठाउने रकम उपलब्ध ब्यालेन्स भन्दा बढी छ।", "details_total_exceeds_balance_frozen": "पठाउने रकम उपलब्ध ब्यालेन्स भन्दा बढी छ। कृपया ध्यान दिनुहोस् कि फ्रोजन भएका कोइनहरू बहिष्कृत छन्।", - "details_unrecognized_file_format": "अपरिचित फाइल फरमात", + "details_unrecognized_file_format": "अपरिचित फाइल ढाँचा", "details_wallet_before_tx": "लेनदेन गर्नु अघि, तपाईंले पहिले बिटकोइन वालेट थप्नु पर्छ।", "dynamic_init": "प्रारम्भ गर्दै", "dynamic_next": "अर्को", @@ -177,6 +173,7 @@ "fee_1d": "1 दिन", "fee_3h": "3 घण्टा", "fee_custom": "अनुकूलन", + "insert_custom_fee": "शुल्क राख्नुहोस्", "fee_fast": "छिटो", "fee_medium": "मध्यम", "fee_replace_minvb": "तपाईंले तिर्न चाहनुभएको कुल शुल्क दर (सतोशी प्रति vByte) {min} sat/vByte भन्दा बढी हुनुपर्छ।", @@ -185,13 +182,12 @@ "header": "पठाउनुहोस्", "input_clear": "सफा", "input_done": "पूरा भयो", - "input_paste": "पेस्त", + "input_paste": "पेस्ट", "input_total": "कुल:", "permission_camera_message": "हामीलाई तपाईंको क्यामेरा प्रयोग गर्न तपाईंको अनुमति चाहिन्छ।", "psbt_sign": "लेनदेनमा हस्ताक्षर गर्नुहोस्", + "invalid_psbt": "अमान्य PSBT प्रदान गरियो।", "open_settings": "सेटिङ्हरू खोल्नुहोस्", - "permission_storage_later": "पछि सोध्नुहोस्", - "permission_storage_message": "यो फाइल सेव गर्नको लागि BlueWallet लाई तपाइँको अनुमति चाहिन्छ।", "permission_storage_denied_message": "BlueWallet यो फाइल सेव गर्न असमर्थ भयो। कृपया आफ्नो यन्त्र सेटिङहरू खोल्नुहोस् र Storage Permission गर्न अनुमति गर्नुहोस्।", "permission_storage_title": "Storage Access Permission ", "psbt_clipboard": "क्लिपबोर्डमा प्रतिलिपि गर्नुहोस्", @@ -201,205 +197,457 @@ "outdated_rate": "दर पछिल्लो पटक अद्यावधिक गरिएको समय: {date}", "psbt_tx_open": "हस्ताक्षरित लेनदेन खोल्नुहोस्", "psbt_tx_scan": "हस्ताक्षरित लेनदेन स्क्यान गर्नुहोस्", - "qr_error_no_qrcode": "हामीले चयन गरिएको छविमा QR कोड फेला पार्न सकेनौं। छविमा QR कोड मात्र समावेश छ र बटनहरू जस्ता अतिरिक्त सामग्रीहरू छैनन् भनी सुनिश्चित गर्नुहोस्।", + "qr_error_no_qrcode": "हामीले चयन गरिएको तस्बिरमा मान्य QR कोड भेट्न सकेनौं। कृपया तस्बिरमा QR कोड बाहेक टेक्स्ट वा बटनहरू जस्ता थप सामग्री नभएको सुनिश्चित गर्नुहोस्।", "reset_amount": "रकम रिसेट गर्नुहोस्", "reset_amount_confirm": "के तपाइँ रकम रिसेट गर्न चाहनुहुन्छ?", "success_done": "सकियो", - "txSaved": "लेनदेन फाइल ({filePath}) तपाईंको डाउनलोड फोल्डरमा सुरक्षित गरिएको छ।", + "txSaved": "लेनदेन फाइल ({filePath}) सेव गरिएको छ।", + "file_saved_at_path": "फाइल ({filePath}) सेव गरिएको छ।", + "cant_send_to_silentpayment_adress": "यो वालेटले Silent Payments ठेगानाहरूमा पठाउन सक्दैन", + "cant_send_to_bip47": "यो वालेटले BIP47 भुक्तानी कोडहरूमा पठाउन सक्दैन", + "cant_find_bip47_notification": "पहिले यो भुक्तानी कोडलाई सम्पर्कहरूमा थप्नुहोस्", "problem_with_psbt": "PSBT मा समस्या" }, "settings": { "about": "बारेमा", + "about_awesome": "उत्कृष्टसँग बनाइएको", "about_backup": "सधैं आफ्नो सांचो सेव गर्नुहोस !", "about_free": "BlueWallet एक बिटकोइन प्रयोगकर्ताहरू द्वारा बनाईएको नि:शुल्क र खुला स्रोत परियोजना हो।", - "about_release_notes": "नोटहरू जारी गर्नुहोस्", - "about_review": "हामीलाई एक विश्लेषण छोड्नुहोस्", + "about_release_notes": "रिलिज नोटहरू", + "about_review": "हामीलाई एक समीक्षा छोड्नुहोस्", + "about_license": "MIT लाइसेन्स", "performance_score": "प्रदर्शन स्कोर: {num}", - "run_performance_test": "परीक्षा प्रदर्शन", + "run_performance_test": "प्रदर्शन परीक्षण", "about_selftest": "आत्म परीक्षण गर्नुहोस्", + "block_explorer_invalid_custom_url": "प्रदान गरिएको URL अमान्य छ। कृपया http:// वा https:// बाट सुरु हुने मान्य URL प्रविष्ट गर्नुहोस्।", + "about_selftest_electrum_disabled": "Electrum अफलाइन मोडसँग आत्म-परीक्षण उपलब्ध छैन। कृपया अफलाइन मोड बन्द गरेर पुन: प्रयास गर्नुहोस्।", "about_selftest_ok": "सबै आन्तरिक परीक्षाहरू सफलतापूर्वक उत्तीर्ण भएका छन्। वालेट राम्रोसँग काम गर्दछ।", - "about_sm_discord": "विवाद सर्भर", + "about_sm_github": "GitHub", "about_sm_telegram": "टेलिग्राम च्यानल", - "about_sm_twitter": "हामीलाई twitter मा फलोगर्नुहोस्", - "advanced_options": "विकास विकल्प", + "privacy_temporary_screenshots": "स्क्रिन क्याप्चरलाई अनुमति दिनुहोस्", + "privacy_temporary_screenshots_instructions": "स्क्रिन क्याप्चर सुरक्षा अस्थायी रूपमा बन्द हुनेछ, जसले स्क्रिनसट र स्क्रिन रेकर्डिङलाई सक्षम पार्छ। तपाईंले BlueWallet बन्द गरेर फेरि खोल्दा सुरक्षा स्वचालित रूपमा पुन: सक्रिय हुनेछ।", "biometrics": "बायोमेट्रिक्स", + "biometrics_no_longer_available": "तपाईंको यन्त्र सेटिङहरू परिवर्तन भएका छन् र अब एपमा चयन गरिएको सुरक्षा सेटिङहरूसँग मेल खाँदैनन्। कृपया बायोमेट्रिक्स वा पासकोड पुन: सक्षम गर्नुहोस्, त्यसपछि यी परिवर्तनहरू लागू गर्न एप पुन: सुरु गर्नुहोस्।", "biom_10times": "तपाईंले आफ्नो पासवर्ड १० पटक प्रविष्ट गर्ने प्रयास गर्नुभएको छ। तपाईं आफ्नो भण्डारण रिसेट गर्न चाहनुहुन्छ? यसले सबै वालेटहरू हटाउनेछ र तपाईंको भण्डारण डिक्रिप्ट गर्नेछ।", "biom_conf_identity": "आफ्नो पहिचान पुष्टि गर्नुहोस्।", - "biom_no_passcode": "तपाईंको यन्त्रमा पासकोड छैन। अगाडि बढ्नको लागि, कृपया सेटिङ एपमा पासकोड कन्फिगर गर्नुहोस्।", + "biom_no_passcode": "तपाईंको यन्त्रमा पासकोड वा बायोमेट्रिक्स सक्षम छैन। अगाडि बढ्नको लागि, कृपया सेटिङहरू एपमा पासकोड वा बायोमेट्रिक कन्फिगर गर्नुहोस्।", "biom_remove_decrypt": "तपाईंको सबै वालेटहरू हटाइनेछ र तपाईंको भण्डारण डिक्रिप्ट गरिनेछ। के तपाई निश्चित रूपमा अगाडि बढ्न चाहनुहुन्छ?", "currency": "मुद्रा", - "currency_source": "मूल्य प्राप्त हुने ठाउँ", + "currency_source": "दर यहाँबाट प्राप्त गरिएको छ", "currency_fetch_error": "चयन गरिएको मुद्राको लागि दर प्राप्त गर्दा एर्रोर भयो।", - "default_info": "पूर्वनिर्धारित जानकारी", - "default_wallets": "सबै वालेटहरू हेर्नुहोस्", + "default_title": "सुरुवातमा", + "donate": "दान गर्नुहोस्", + "donate_description": "Blue लाई निःशुल्क राख्न हामीलाई मद्दत गर्नुहोस्!", "electrum_connected": "जोडिएको", "electrum_connected_not": "जोडिएको छैन", + "electrum_error_connect": "प्रदान गरिएको Electrum सर्भरमा जडान गर्न सकिएन", + "electrum_error_connect_tor": "प्रदान गरिएको Electrum सर्भरमा जडान गर्न सकिएन। कृपया Orbot एप जडान भएको सुनिश्चित गर्नुहोस् र पुन: प्रयास गर्नुहोस्।", + "lndhub_uri": "उदाहरण: {example}", + "electrum_host": "उदाहरण: {example}", "electrum_offline_mode": "अफलाइन मोड", - "electrum_saved": "तपाईंको परिवर्तनहरू सफलतापूर्वक सुरक्षित गरिएको छ। परिवर्तनहरू प्रभाव पार्नको लागि बुलुवालेट पुन: सुरु गर्न आवश्यक हुन सक्छ।", + "electrum_offline_description": "सक्षम भएमा, तपाईंको बिटकोइन वालेटहरूले ब्यालेन्स वा लेनदेनहरू ल्याउने प्रयास गर्ने छैनन्।", + "electrum_port": "पोर्ट, सामान्यतया {example}", + "use_ssl": "SSL प्रयोग गर्नुहोस्", + "electrum_saved": "तपाईंको परिवर्तनहरू सफलतापूर्वक सुरक्षित गरिएको छ। परिवर्तनहरू प्रभाव पार्नको लागि BlueWallet पुन: सुरु गर्न आवश्यक हुन सक्छ।", + "set_electrum_server_as_default": "{server} लाई पूर्वनिर्धारित Electrum सर्भरको रूपमा सेट गर्ने हो?", + "set_lndhub_as_default": "{url} लाई पूर्वनिर्धारित LNDhub सर्भरको रूपमा सेट गर्ने हो?", + "electrum_settings_server": "Electrum सर्भर", "electrum_status": "स्थिति", - "electrum_clear_alert_title": "स्पष्ट इतिहास?", - "electrum_clear_alert_message": "के तपाई इलेक्ट्रम सेभर्स इतिहास खाली गर्न चाहनुहुन्छ?", - "electrum_clear_alert_cancel": "रद्द गर्नुहोस्", - "electrum_clear_alert_ok": "ठिक छ", - "electrum_select": "छान्नुहोस्", - "electrum_reset": "चूकेमा रिसेट गर्नुहोस्", - "electrum_unable_to_connect": "जडान गर्न असमर्थ {सर्भर}.", - "electrum_history": "सर्भर इतिहास", - "electrum_reset_to_default": "के तपाइँ तपाइँको इलेक्ट्रम सेटिङहरू पूर्वनिर्धारितमा रिसेट गर्न निश्चित हुनुहुन्छ?", - "electrum_clear": "सफा", + "electrum_preferred_server": "रुचाइएको सर्भर", + "electrum_preferred_server_description": "तपाईंको वालेटले सबै बिटकोइन गतिविधिहरूको लागि प्रयोग गर्न चाहेको सर्भर प्रविष्ट गर्नुहोस्। एक पटक सेट गरेपछि, तपाईंको वालेटले ब्यालेन्स जाँच गर्न, लेनदेन पठाउन र नेटवर्क डाटा ल्याउनको लागि यो सर्भर मात्र प्रयोग गर्नेछ। सेट गर्नु अघि यो सर्भरमा भरोसा गर्ने सुनिश्चित गर्नुहोस्।", + "electrum_unable_to_connect": "जडान गर्न असमर्थ {server}.", + "electrum_history": "इतिहास", + "electrum_reset_to_default": "यसले BlueWallet लाई सर्भर सूचीबाट अनियमित रूपमा सर्भर छनोट गर्न दिनेछ।", + "electrum_reset": "पूर्वनिर्धारितमा रिसेट गर्नुहोस्", + "electrum_reset_to_default_and_clear_history": "पूर्वनिर्धारितमा रिसेट गर्नुहोस् र इतिहास खाली गर्नुहोस्", "encrypt_decrypt": "डिक्रिप्ट स्टोरेज", + "encrypt_decrypt_q": "के तपाईं आफ्नो स्टोरेज डिक्रिप्ट गर्न निश्चित हुनुहुन्छ? यसले तपाईंको वालेटहरूलाई पासवर्ड बिना नै पहुँच गर्न अनुमति दिनेछ।", + "encrypt_enc_and_pass": "पासवर्ड संरक्षित", + "encrypt_storage_explanation_headline": "स्टोरेज इन्क्रिप्सन सक्षम गर्नुहोस्", + "encrypt_storage_explanation_description_line1": "स्टोरेज इन्क्रिप्सन सक्षम गरेर तपाईंको डाटा यन्त्रमा स्टोर गर्ने तरिकालाई सुरक्षित बनाएर तपाईंको एपमा अतिरिक्त सुरक्षा थपिन्छ। यसले अनुमति बिना तपाईंको जानकारी पहुँच गर्न अरूलाई गाह्रो बनाउँछ।", + "encrypt_storage_explanation_description_line2": "तथापि, यो जान्नु महत्त्वपूर्ण छ कि यो इन्क्रिप्सनले यन्त्रको किचेनमा भण्डारण गरिएका तपाईंका वालेटहरूको पहुँचलाई मात्र सुरक्षित गर्छ। यसले वालेट आफैमा पासवर्ड वा कुनै अतिरिक्त सुरक्षा थप्दैन।", + "i_understand": "मैले बुझेँ", + "block_explorer": "ब्लक एक्सप्लोरर", + "block_explorer_preferred": "रुचाइएको ब्लक एक्सप्लोरर प्रयोग गर्नुहोस्", + "block_explorer_error_saving_custom": "रुचाइएको ब्लक एक्सप्लोरर सेव गर्न त्रुटि", "encrypt_title": "सुरक्षा", "encrypt_tstorage": "स्टोरेज", "encrypt_use": "प्रयोग गर्नुहोस् {type}", - "encrypt_use_expl": "{type} लेनदेन, अनलक, निर्यात, वा वालेट मेटाउनु अघि तपाइँको पहिचान पुष्टि गर्न प्रयोग गरिनेछ। इन्क्रिप्टेड स्टोरेज अनलक गर्न {type} प्रयोग गरिने छैन।", - "general_adv_mode": "अग्रिम मोड", + "set_as_preferred": "रुचाइएकोको रूपमा सेट गर्नुहोस्", + "set_as_preferred_electrum": "{host}:{port} लाई रुचाइएको सर्भरको रूपमा सेट गर्दा अनियमित रूपमा सुझाव गरिएको सर्भरमा जडान हुने अक्षम हुनेछ।", + "encrypted_feature_disabled": "इन्क्रिप्टेड स्टोरेज सक्षम भएमा यो सुविधा प्रयोग गर्न सकिँदैन।", + "encrypt_use_expl": "लेनदेन गर्नु, अनलक गर्नु, निर्यात गर्नु, वा वालेट हटाउनु अघि तपाईंको पहिचान पुष्टि गर्न {type} प्रयोग गरिनेछ।", + "biometrics_fail": "यदि {type} सक्षम छैन, वा अनलक गर्न असफल हुन्छ भने, तपाईंले विकल्पको रूपमा आफ्नो यन्त्रको पासकोड प्रयोग गर्न सक्नुहुन्छ।", + "general": "सामान्य", "general_continuity": "निरन्तरता", + "general_continuity_e": "सक्षम भएमा, तपाईंले आफ्ना अन्य Apple iCloud जोडिएका यन्त्रहरू प्रयोग गरेर चयन गरिएका वालेटहरू र लेनदेनहरू हेर्न सक्नुहुनेछ।", + "groundcontrol_explanation": "GroundControl बिटकोइन वालेटहरूको लागि एक निःशुल्क, खुला-स्रोत पुश सूचना सर्भर हो। तपाईंले आफ्नै GroundControl सर्भर स्थापना गरेर BlueWallet को पूर्वाधारमा भर पर्न नपर्ने गरी यसको URL यहाँ राख्न सक्नुहुन्छ। GroundControl को पूर्वनिर्धारित सर्भर प्रयोग गर्न खाली छोड्नुहोस्।", "header": "सेटिङहरू", "language": "भाषा", "last_updated": "पछिल्लो अपडेट", + "language_isRTL": "भाषा अभिमुखीकरण प्रभाव पार्नको लागि BlueWallet पुन: सुरु गर्न आवश्यक छ।", + "license": "लाइसेन्स", + "lightning_error_lndhub_uri": "अमान्य LNDhub URI", + "lightning_error_lndhub_uri_tor": "अमान्य LNDhub URI। कृपया Orbot एप जडान भएको सुनिश्चित गर्नुहोस् र पुन: प्रयास गर्नुहोस्।", "lightning_saved": "तपाईंको परिवर्तनहरू सफलतापूर्वक सुरक्षित गरिएको छ।", "lightning_settings": "लाइटनिङ सेटिङहरू", + "lightning_settings_explain": "आफ्नै LND नोडमा जडान गर्न, कृपया LNDhub स्थापना गर्नुहोस् र यसको URL यहाँ सेटिङहरूमा राख्नुहोस्। कृपया ध्यान दिनुहोस् कि परिवर्तनहरू सेव गरेपछि सिर्जना गरिएका वालेटहरू मात्र निर्दिष्ट LNDhub मा जोडिनेछन्।", + "lndhub_github": "GitHub भण्डार", "network": "नेटवर्क", + "network_broadcast": "लेनदेन प्रसारण गर्नुहोस्", + "network_electrum": "Electrum सर्भर", + "electrum_suggested_description": "जब रुचाइएको सर्भर सेट गरिएको हुँदैन, सुझाव गरिएको सर्भर अनियमित रूपमा प्रयोगको लागि चयन गरिनेछ।", + "not_a_valid_uri": "अमान्य URI", "notifications": "सूचनाहरू", + "open_link_in_explorer": "एक्सप्लोररमा लिङ्क खोल्नुहोस्", "password": "पासवर्ड", - "passwords_do_not_match": "पासवर्ड मिल्दैन।", + "password_explain": "तपाईंले आफ्नो स्टोरेज अनलक गर्न प्रयोग गर्ने पासवर्ड प्रविष्ट गर्नुहोस्।", "plausible_deniability": "व्यावहारिक अस्वीकार्यता", "privacy": "गोपनीयता", + "privacy_read_clipboard": "क्लिपबोर्ड पढ्नुहोस्", + "privacy_system_settings": "प्रणाली सेटिङहरू", "privacy_quickactions": "वालेट सर्टकटहरू", + "privacy_quickactions_explanation": "आफ्नो वालेटको ब्यालेन्स छिटो हेर्नको लागि BlueWallet एप आइकनलाई छोएर समातिराख्नुहोस्।", + "privacy_clipboard_explanation": "यदि तपाईंको क्लिपबोर्डमा ठेगाना वा इनभ्वाइस फेला परेमा सर्टकटहरू उपलब्ध गराउनुहोस्।", + "privacy_do_not_track": "एनालिटिक्स अक्षम गर्नुहोस्", + "privacy_do_not_track_explanation": "प्रदर्शन र विश्वसनीयता जानकारी विश्लेषणको लागि पेस गरिने छैन।", "rate": "दर", - "retype_password": "पासवर्ड पुन: लेख्नुहोस", + "push_notifications_explanation": "सूचनाहरू सक्षम गरेर, तपाईंको यन्त्र टोकन सर्भरमा पठाइनेछ, सूचनाहरू सक्षम गरेपछिका सबै वालेटहरू र लेनदेनहरूको वालेट ठेगाना र लेनदेन IDs सहित। यन्त्र टोकन सूचनाहरू पठाउन प्रयोग गरिन्छ, र वालेट जानकारीले हामीलाई आगमन बिटकोइन वा लेनदेन पुष्टिकरणहरूको बारेमा तपाईंलाई सूचित गर्न अनुमति दिन्छ।\n\nतपाईंले सूचनाहरू सक्षम गरेपछिको जानकारी मात्र प्रसारित हुन्छ—पहिलेको कुनै पनि कुरा सङ्कलन गरिँदैन।\n\nसूचनाहरू अक्षम गर्दा यो सबै जानकारी सर्भरबाट हटाइनेछ। थप रूपमा, एपबाट वालेट मेटाउँदा सर्भरबाट यसको सम्बन्धित जानकारी पनि हटाइनेछ।", + "selfTest": "आत्म-परीक्षण", "save": "सेव", "saved": "बचत गरियो", - "success_transaction_broadcasted": "सफलता! तपाईंको लेनदेन प्रसारण गरिएको छ!", - "total_balance": "पूरा रकम" + "success_transaction_broadcasted": "तपाईंको लेनदेन सफलतापूर्वक प्रसारण गरियो!", + "total_balance": "पूरा रकम", + "total_balance_explanation": "तपाईंको होम स्क्रिन विजेटहरूमा सबै वालेटहरूको पूरा रकम देखाउनुहोस्।", + "widgets": "विजेटहरू", + "tools": "उपकरणहरू" }, "notifications": { "would_you_like_to_receive_notifications": "तपाईंले आगमन भुक्तानीहरू प्राप्त गर्दा सूचनाहरू प्राप्त गर्न चाहनुहुन्छ?", - "no_and_dont_ask": "होइन, र मलाई फेरि नसोध्नुहोस्", - "ask_me_later": "पछि सोध्नुहोस्" + "notifications_subtitle": "आगमन भुक्तानीहरू र लेनदेन पुष्टिकरणहरू", + "no_and_dont_ask": "होइन, र मलाई फेरि नसोध्नुहोस्।", + "permission_denied_message": "तपाईंले तपाईंलाई सूचनाहरू पठाउने अनुमति अस्वीकार गर्नुभएको छ। यदि तपाईं सूचनाहरू प्राप्त गर्न चाहनुहुन्छ भने, कृपया तिनीहरूलाई आफ्नो यन्त्र सेटिङहरूमा सक्षम गर्नुहोस्।" }, "transactions": { + "cancel_explain": "हामी यो लेनदेनलाई तपाईंलाई तिर्ने र उच्च शुल्क भएको अर्को लेनदेनसँग प्रतिस्थापन गर्नेछौं। यसले प्रभावकारी रूपमा हालको लेनदेन रद्द गर्छ। यसलाई RBF भनिन्छ—Replace by Fee।", "cancel_no": "यो लेनदेन बदल्न सकिने योग्य छैन", "cancel_title": "यो लेनदेन रद्द गर्नुहोस् (RBF)", + "transaction_loading_error": "लेनदेन लोड गर्दा समस्या आयो। कृपया पछि पुन: प्रयास गर्नुहोस्।", + "transaction_not_available": "लेनदेन उपलब्ध छैन", "confirmations_lowercase": "{confirmations} पुष्टिकरणहरू", - "copy_link": "लिङ्क कापी गर्नुहोस्", "expand_note": "नोट बढाउनुहोस्", "cpfp_create": "सिर्जना गर्नुहोस्", "cpfp_exp": "हामी तपाईंको अपुष्ट लेनदेन खर्च गर्ने अर्को लेनदेन सिर्जना गर्नेछौं। कुल शुल्क मूल लेनदेन शुल्क भन्दा बढी हुनेछ, त्यसैले यसलाई छिटो खनन गर्नुपर्छ। यसलाई CPFP भनिन्छ—Child Pays for Parent।", + "cpfp_no_bump": "यो लेनदेनको शुल्क बढाउन सकिँदैन।", + "cpfp_title": "शुल्क वृद्धि (CPFP)", "details_balance_hide": "रकम लुकाउनुहोस्", "details_balance_show": "रकम देखाउनुहोस्", "details_copy": "कापी", - "details_copy_amount": "कापी रकम", + "details_copy_block_explorer_link": "ब्लक एक्सप्लोरर लिङ्क कापी गर्नुहोस्", "details_copy_note": "नोट कापी", + "details_copy_txid": "लेनदेन ID कापी गर्नुहोस्", + "details_inputs": "इनपुटहरू", + "details_outputs": "आउटपुटहरू", "date": "मिति", "details_received": "प्राप्त भयो", + "details_view_in_browser": "ब्राउजरमा हेर्नुहोस्", "details_title": "लेनदेन", - "pending": "पेनदिंग", + "incoming_transaction": "आगमन लेनदेन", + "outgoing_transaction": "बहिर्गमन लेनदेन", + "expired_transaction": "म्याद सकिएको लेनदेन", + "pending_transaction": "विचाराधीन लेनदेन", + "offchain": "अफ-चेन", + "onchain": "अन-चेन", + "details_to": "आउटपुट", + "enable_offline_signing": "यो वालेट अफलाइन हस्ताक्षरसँग संयोजनमा प्रयोग भइरहेको छैन। के तपाईं यसलाई अहिले सक्षम गर्न चाहनुहुन्छ?", + "list_conf": "पुष्टि: {number}", + "pending": "विचाराधीन", + "pending_with_amount": "विचाराधीन {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: लगभग १० मिनेटमा", + "eta_3h": "ETA: लगभग ३ घण्टामा", + "eta_1d": "ETA: लगभग १ दिनमा", "list_title": "लेनदेन", - "status_cancel": "लेनदेन क्यान्सिल " + "list_title_sent": "पठाइयो", + "list_title_received": "प्राप्त भयो", + "transaction": "लेनदेन", + "open_url_error": "पूर्वनिर्धारित ब्राउजरबाट लिङ्क खोल्न असमर्थ। कृपया आफ्नो पूर्वनिर्धारित ब्राउजर परिवर्तन गरेर पुन: प्रयास गर्नुहोस्।", + "rbf_explain": "हामी यो लेनदेनलाई उच्च शुल्क भएको अर्कोसँग प्रतिस्थापन गर्नेछौं ताकि यसलाई छिटो खनन गर्न सकियोस्। यसलाई RBF भनिन्छ—Replace by Fee।", + "rbf_title": "छिटो बनाउनुहोस् (RBF)", + "status_bump": "छिटो बनाउनुहोस्", + "status_cancel": "लेनदेन क्यान्सिल", + "transactions_count": "लेनदेन गणना", + "txid": "लेनदेन ID", + "updating": "अद्यावधिक गर्दै...", + "watchOnlyWarningTitle": "सुरक्षा चेतावनी", + "watchOnlyWarningDescription": "स्क्यामरहरूबाट सावधान रहनुहोस् जसले प्रायः प्रयोगकर्ताहरूलाई धोका दिन “केवल हेर्ने” वालेटहरू प्रयोग गर्छन्। यी वालेटहरूले तपाईंलाई कोष नियन्त्रण वा पठाउन अनुमति दिँदैनन्; तिनीहरूले तपाईंलाई ब्यालेन्स मात्र हेर्न दिन्छन्।", + "custom_fee_warning_title": "चेतावनी", + "custom_fee_warning_description": "१ sat/vB भन्दा कम शुल्क मान्य छन्, तर नोड नीतिहरूका कारण रिले नहुन सक्छन्।", + "details_eta_analyzing": "विश्लेषण गर्दै...", + "details_sent": "पठाइयो", + "details_section": "विवरणहरू", + "details_explorer": "एक्सप्लोरर", + "details_network_fee": "नेटवर्क शुल्क", + "details_to_address": "लाई", + "details_id": "ID", + "details_note": "नोट", + "details_add_note": "थप्नुहोस्", + "details_advanced": "अग्रिम", + "details_fee_rate": "शुल्क दर", + "details_size": "आकार", + "details_virtual_size": "भर्चुअल आकार", + "details_tx_hex": "Tx हेक्स", + "details_inputs_count": "इनपुटहरू ({count})", + "details_outputs_count": "आउटपुटहरू ({count})" }, "wallets": { "add_bitcoin": "बिटकोइन", "add_bitcoin_explain": "सजिलो र शक्तिशाली बिटकोइन वालेट", "add_create": "सिर्जना गर्नुहोस्", + "total_balance": "पूरा रकम", + "add_entropy_reset_title": "एन्ट्रोपी रिसेट गर्नुहोस्", + "add_entropy_reset_message": "वालेटको प्रकार परिवर्तन गर्दा हालको एन्ट्रोपी रिसेट हुनेछ। के तपाईं अगाडि बढ्न चाहनुहुन्छ?", + "add_entropy": "एन्ट्रोपी", + "add_entropy_bytes": "एन्ट्रोपीको {bytes} बाइट", + "add_entropy_generated": "उत्पन्न एन्ट्रोपीको {gen} बाइट", + "add_entropy_provide": "पासाको रोलमार्फत एन्ट्रोपी उपलब्ध गराउनुहोस्", + "add_entropy_remain": "उत्पन्न एन्ट्रोपीको {gen} बाइट। बाँकी {rem} बाइट प्रणालीको अनियमित संख्या जेनेरेटरबाट प्राप्त गरिनेछ।", "add_import_wallet": "वालेट आयात गर्नुहोस्", "add_lightning": "लाइटनिङ", + "add_lightning_explain": "तत्काल लेनदेनसँग खर्च गर्नको लागि", + "add_lndhub": "तपाईंको LNDhub मा जडान गर्नुहोस्", + "add_lndhub_error": "प्रदान गरिएको नोड ठेगाना अमान्य LNDhub नोड हो।", + "add_lndhub_placeholder": "तपाईंको नोड ठेगाना", "add_placeholder": "मेरो पहिलो वालेट", "add_title": "वालेट थप्नुहोस्", "add_wallet_name": "नाम", - "add_wallet_type": "टाइप गर्नुहोस्", - "balance": "ब्यालेन्स", + "add_wallet_type": "प्रकार", + "add_wallet_seed_length": "सीडको लम्बाइ", + "add_wallet_seed_length_12": "१२ शब्द", + "add_wallet_seed_length_24": "२४ शब्द", "clipboard_bitcoin": "तपाईको क्लिपबोर्डमा बिटकोइन ठेगाना छ। के तपाइँ यसलाई लेनदेनको लागि प्रयोग गर्न चाहनुहुन्छ?", "clipboard_lightning": "तपाईंको क्लिपबोर्डमा लाइटनिङ इनभ्वाइस छ। के तपाइँ यसलाई लेनदेनको लागि प्रयोग गर्न चाहनुहुन्छ?", + "clear_clipboard_on_import": "आयात गर्दा क्लिपबोर्ड खाली गर्नुहोस्", "details_address": "ठेगाना", + "details_advanced": "अग्रिम", "details_are_you_sure": "के तपाईँ निश्चित हुनुहुन्छ?", "details_connected_to": "जोडिएको", + "details_del_wb_err": "प्रदान गरिएको ब्यालेन्स रकम यो वालेटको ब्यालेन्ससँग मेल खाँदैन। कृपया पुन: प्रयास गर्नुहोस्।", + "details_del_wb_q": "यो वालेटमा ब्यालेन्स छ। अगाडि बढ्नु अघि, कृपया सावधान रहनुहोस् कि तपाईंले यो वालेटको सीड फ्रेज बिना कोष पुनः प्राप्त गर्न सक्नुहुन्न। आकस्मिक हटाउनबाट बच्न, कृपया तपाईंको वालेटको {balance} सातोशीको ब्यालेन्स प्रविष्ट गर्नुहोस्।", "details_delete": "हटाउनुहोस्", "details_delete_wallet": "वालेट हटाउनुहोस्", - "details_display": "वालेट सूचीमा देखाउनु होस्", + "details_derivation_path": "डेरिभेसन पथ", + "details_display": "होम स्क्रिनमा देखाउनुहोस्", + "details_export_backup": "निर्यात/ब्याकअप", + "details_export_history": "इतिहास CSV मा निर्यात गर्नुहोस्", "details_master_fingerprint": "मास्टर फिंगरप्रिन्ट", - "details_no_cancel": "होइन, रद्द गर्नुहोस्", - "details_save": "सेव", + "details_multisig_type": "मल्टिसिग", "details_show_xpub": "वालेटको XPUB देखाउनुहोस्", "details_show_addresses": "ठेगानाहरू देखाउनुहोस्", "details_title": "वालेट", - "details_type": "टाइप गर्नुहोस्", + "wallets": "वालेटहरू", + "swipe_balance_hide": "लुकाउनुहोस्", + "swipe_balance_show": "देखाउनुहोस्", + "drag_to_reorder": "पुनः क्रमबद्ध गर्न तान्नुहोस्", + "clear_search": "खोजी खाली गर्नुहोस्", + "details_type": "प्रकार", "details_use_with_hardware_wallet": "हार्डवेयर वालेटको साथ प्रयोग गर्नुहोस्", - "details_wallet_updated": "वालेट अपडेट ", "details_yes_delete": "हो, मेटाउनुहोस्", + "enter_bip38_password": "डिक्रिप्ट गर्न पासवर्ड प्रविष्ट गर्नुहोस्", "export_title": "वालेट निर्यात", + "import_do_import": "आयात गर्नुहोस्", + "import_passphrase": "पासफ्रेज", + "import_passphrase_title": "पासफ्रेज", + "import_passphrase_message": "यदि तपाईंले कुनै पासफ्रेज प्रयोग गर्नुभएको छ भने प्रविष्ट गर्नुहोस्", + "import_error": "आयात गर्न असफल। कृपया प्रदान गरिएको डाटा मान्य भएको सुनिश्चित गर्नुहोस्।", + "import_explanation": "कृपया आफ्नो सीड शब्दहरू, सार्वजनिक कुञ्जी, WIF, वा तपाईंसँग भएको जे पनि प्रविष्ट गर्नुहोस्। BlueWallet ले सही ढाँचा अनुमान लगाएर तपाईंको वालेट आयात गर्न सक्दो प्रयास गर्नेछ।", + "import_imported": "आयात गरियो", + "import_scan_qr": "स्क्यान गर्नुहोस् वा फाइल आयात गर्नुहोस्", + "import_success": "तपाईंको वालेट सफलतापूर्वक आयात गरिएको छ।", + "import_success_watchonly": "तपाईंको वालेट सफलतापूर्वक आयात गरिएको छ। चेतावनी: यो केवल हेर्ने वालेट हो, तपाईंले यसबाट खर्च गर्न सक्नुहुन्न।", + "import_search_accounts": "खाताहरू खोज्नुहोस्", + "import_title": "आयात गर्नुहोस्", + "learn_more": "थप जान्नुहोस्", "import_discovery_title": "आविष्कार", "import_discovery_subtitle": "पत्ता लागेको वालेट छान्नुहोस्", + "import_discovery_derivation": "अनुकूल डेरिभेसन पथ प्रयोग गर्नुहोस्", "import_discovery_no_wallets": "कुनै वालेट फेला परेन।", - "import_derivation_found": "भेटियो", + "import_discovery_offline": "BlueWallet हाल अफलाइन मोडमा छ। यस मोडमा, यसले वालेटको अस्तित्व प्रमाणित गर्न सक्दैन, त्यसैले तपाईंले सही एउटा म्यानुअली चयन गर्नुपर्नेछ", + "import_derivation_found": "फेला परेको", "import_derivation_found_not": "फेला परेन", - "import_derivation_loading": "लोड गर्दै...", - "import_derivation_unknown": "नजानिएको", + "import_derivation_loading": "लोड हुँदै...", + "import_derivation_subtitle": "अनुकूल डेरिभेसन पथ प्रविष्ट गर्नुहोस्, र हामी तपाईंको वालेट पत्ता लगाउने प्रयास गर्नेछौं।", + "import_derivation_title": "डेरिभेसन पथ", + "import_derivation_unknown": "अज्ञात", + "import_wrong_path": "गलत डेरिभेसन पथ", "list_create_a_button": "अहिले थप्नुहोस्", "list_create_a_wallet": "वालेट थप्नुहोस्", - "list_create_a_wallet_text": "यो नि: शुल्क छ र तपाईं सिर्जना गर्न सक्नुहुन्छ तपाईलाई मन पर्ने जति।", + "list_create_a_wallet_text": "यो निःशुल्क छ, र तपाईंले \nजति चाहनुहुन्छ त्यति सिर्जना गर्न सक्नुहुन्छ।", "list_empty_txs1": "तपाईंको लेनदेन यहाँ देखा पर्नेछ।", "list_empty_txs1_lightning": "तपाईंको दैनिक लेनदेनको लागि लाइटनिङ वालेट प्रयोग गर्नुपर्छ। शुल्कहरू अनुचित रूपमा सस्तो र छिटो छ।", "list_empty_txs2": "आफ्नो वालेटबाट सुरु गर्नुहोस्।", + "list_empty_txs2_lightning": "\nयसलाई प्रयोग गर्न सुरु गर्न, कोष प्रबन्ध गर्नुहोस् ट्याप गर्नुहोस् र आफ्नो ब्यालेन्स टपअप गर्नुहोस्।", "list_latest_transaction": "पछिल्लो लेनदेन", + "list_long_choose": "तस्बिर छान्नुहोस्", + "paste_from_clipboard": "पेस्ट", + "import_file": "फाइल आयात गर्नुहोस्", + "list_long_scan": "QR कोड स्क्यान गर्नुहोस्", "list_title": "वालेटहरू", "list_tryagain": "पुन: प्रयास गर्नुहोस्", "no_ln_wallet_error": "लाइटनिङ इनभ्वाइस भुक्तान गर्नु अघि, तपाईंले पहिले लाइटनिङ वालेट थप्नुपर्छ।", "looks_like_bip38": "यो पासवर्ड सुरक्षित निजी कुञ्जी जस्तो देखिन्छ (BIP38)।", - "reorder_title": "वालेटहरू पुन: अर्डर गर्नुहोस्", + "manage_title": "वालेटहरू प्रबन्ध गर्नुहोस्", + "no_results_found": "कुनै नतिजा फेला परेन।", "please_continue_scanning": "कृपया स्क्यान जारी राख्नुहोस्।", "select_no_bitcoin": "हाल कुनै बिटकोइन वालेटहरू उपलब्ध छैनन्।", "select_no_bitcoin_exp": "लाइटनिङ वालेटहरू पुन: भर्नको लागि बिटकोइन वालेट आवश्यक छ। कृपया बिटकोइन वालेट सिर्जना वा आयात गर्नुहोस्।", "select_wallet": "वालेट छान्नुहोस्", "pull_to_refresh": "नवीकरण गर्न तान्नुहोस्", - "add_ln_wallet_first": "तपाईंले पहिले लाइटनिङ वालेट थप्नुपर्छ।" + "warning_do_not_disclose": "तलको जानकारी कहिल्यै सेयर नगर्नुहोस्", + "scan_import": "अर्को एप्लिकेसनमा आफ्नो वालेट आयात गर्न यो QR कोड स्क्यान गर्नुहोस्।", + "write_down_header": "म्यानुअल ब्याकअप सिर्जना गर्नुहोस्", + "write_down": "यी शब्दहरू लेखेर सुरक्षित रूपमा भण्डारण गर्नुहोस्। पछि आफ्नो वालेट पुनर्स्थापना गर्न तिनीहरूलाई प्रयोग गर्नुहोस्।", + "wallet_type_this": "यो वालेटको प्रकार {type} हो।", + "share_number": "सेयर {number}", + "copy_ln_url": "पछि आफ्नो वालेट पुनर्स्थापना गर्न यो URL कापी गरेर सुरक्षित रूपमा भण्डारण गर्नुहोस्।", + "copy_ln_public": "पछि आफ्नो वालेट पुनर्स्थापना गर्न यो जानकारी कापी गरेर सुरक्षित रूपमा भण्डारण गर्नुहोस्।", + "add_ln_wallet_first": "तपाईंले पहिले लाइटनिङ वालेट थप्नुपर्छ।", + "identity_pubkey": "पहिचान सार्वजनिक कुञ्जी", + "xpub_title": "वालेट XPUB", + "manage_wallets_search_placeholder": "वालेट, ठेगाना, लेनदेन र मेमोहरू खोज्नुहोस्", + "more_info": "थप जानकारी", + "details_delete_wallet_error_message": "यो वालेट सूचनाहरूबाट हटाइएको हो भनेर पुष्टि गर्न समस्या आयो—यो नेटवर्क समस्या वा कमजोर जडानको कारणले हुन सक्छ। यदि तपाईं जारी राख्नुहुन्छ भने, यो वालेट मेटाइएपछि पनि यससँग सम्बन्धित लेनदेनहरूको लागि तपाईंले सूचनाहरू प्राप्त गर्न सक्नुहुन्छ।", + "details_delete_anyway": "जे होस् मेटाउनुहोस्" + }, + "total_balance_view": { + "display_in_bitcoin": "बिटकोइनमा देखाउनुहोस्", + "hide": "लुकाउनुहोस्", + "display_in_sats": "sats मा देखाउनुहोस्", + "display_in_fiat": "{currency} मा देखाउनुहोस्", + "title": "पूरा रकम", + "explanation": "अवलोकन स्क्रिनमा सबै वालेटहरूको पूरा रकम हेर्नुहोस्।" }, "multisig": { - "fee_btc": "{संख्या} BTC", - "confirm": "पक्का ", + "multisig_vault": "मल्टिसिग भल्ट", + "default_label": "मल्टिसिग भल्ट", + "multisig_vault_explain": "ठूलो रकमको लागि उत्कृष्ट सुरक्षा", + "provide_signature": "हस्ताक्षर गर्नुहोस्", + "provide_signature_details": "यो लेनदेनमा हस्ताक्षर गर्न कुञ्जी रहेको आफ्नो यन्त्र र वालेट प्रयोग गर्नुहोस्", + "provide_signature_details_bluewallet": "BlueWallet मा, पठाउनुहोस् स्क्रिन मेनुमा जानुहोस् र छान्नुहोस् ", + "provide_signature_next_steps": "हस्ताक्षरित लेनदेन स्क्यान वा आयात गर्नुहोस्", + "provide_signature_next_steps_details": "एक पटक तपाईंको वालेटले लेनदेनमा सफलतापूर्वक हस्ताक्षर गरेपछि, प्रदान गरिएको QR कोड स्क्यान गर्नुहोस् वा सँगै रहेको फाइल आयात गर्नुहोस्, र त्यसपछि प्रसारण गर्नु अघि सबै लेनदेन विवरणहरू समीक्षा गर्नुहोस्।", + "vault_key": "भल्ट कुञ्जी {number}", + "required_keys_out_of_total": "कुल मध्ये आवश्यक कुञ्जीहरू", + "fee": "शुल्क: {number}", + "fee_btc": "{number} BTC", + "confirm": "पक्का", "header": "पठाउनुहोस्", - "share": "सेयर गर्नुहोस्", + "share": "सेयर गर्नुहोस्...", "view": "हेर्नुहोस्", - "how_many_signatures_can_bluewallet_make": "ब्लू वालेटले कति हस्ताक्षर गर्न सक्छ", + "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": "सुरु गरौँ", "create": "सिर्जना गर्नुहोस्", + "native_segwit_title": "उत्तम अभ्यास", + "wrapped_segwit_title": "उत्तम सुसंगति", + "legacy_title": "लिगेसी", "co_sign_transaction": "लेनदेनमा हस्ताक्षर गर्नुहोस्", + "what_is_vault": "भल्ट एक", + "what_is_vault_numberOfWallets": " {m}-को-{n} मल्टिसिग ", "what_is_vault_wallet": "वालेट।", "vault_advanced_customize": "भल्ट सेटिङहरू", "needs": "यसको आवश्यकता", + "what_is_vault_description_number_of_vault_keys": " {m} भल्ट कुञ्जीहरू ", + "what_is_vault_description_to_spend": "खर्च गर्न र तेस्रो एउटा तपाईंले \nब्याकअपको रूपमा प्रयोग गर्न सक्नुहुन्छ।", "what_is_vault_description_to_spend_other": "खर्च गर्नु।", + "quorum": "{n} को {m} कोरम", + "quorum_header": "कोरम", + "of": "को", "wallet_type": "वालेट को प्रकार", + "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_title4": "वाल्टहरू आयात गर्दै ", - "ms_help_title5": "अग्रिम मोड" + "ms_help_3": "वालेट विकल्पहरूमा, तपाईंले आफ्नो भल्ट ब्याकअप र केवल हेर्ने ब्याकअप फेला पार्नुहुनेछ। यो ब्याकअप तपाईंको वालेटको लागि नक्सा जस्तै हो। यदि तपाईंले आफ्नो एउटा सीड गुमाउनुभयो भने वालेट रिकभरीको लागि यो आवश्यक छ।", + "ms_help_title4": "भल्टहरू आयात गर्दै", + "ms_help_4": "मल्टिसिग आयात गर्न, आफ्नो ब्याकअप फाइल र आयात सुविधा प्रयोग गर्नुहोस्। यदि तपाईंसँग सीड र XPUBs मात्र छन् भने, भल्ट कुञ्जीहरू सिर्जना गर्दा तपाईंले व्यक्तिगत आयात बटन प्रयोग गर्न सक्नुहुन्छ।", + "ms_help_title5": "अग्रिम मोड", + "ms_help_5": "पूर्वनिर्धारित रूपमा, BlueWallet ले २-को-३ भल्ट उत्पन्न गर्नेछ। फरक कोरम सिर्जना गर्न वा ठेगाना प्रकार परिवर्तन गर्न, सेटिङहरूमा अग्रिम मोड सक्रिय गर्नुहोस्।" }, "is_it_my_address": { "title": "यो मेरो ठेगाना हो?", - "enter_address": " ठेगाना लेख्नुहोस्", + "owns": "{label} सँग {address} छ", + "enter_address": "ठेगाना लेख्नुहोस्", "check_address": "ठेगाना जाँच गर्नुहोस्", "no_wallet_owns_address": "उपलब्ध वालेटहरू मध्ये कुनै पनि प्रदान गरिएको ठेगानाको स्वामित्व छैन।", - "view_qrcode": "QRCode हेर्नुहोस्" + "view_qrcode": "QR कोड हेर्नुहोस्" + }, + "autofill_word": { + "title": "सीडको अन्तिम शब्द", + "enter": "तपाईंको आंशिक निमोनिक फ्रेज प्रविष्ट गर्नुहोस्", + "generate_word": "अन्तिम शब्द उत्पन्न गर्नुहोस्", + "error": "इनपुट ११- वा २३-शब्दको आंशिक निमोनिक होइन। कृपया पुन: प्रयास गर्नुहोस्।" }, "cc": { "change": "परिवर्तन", "coins_selected": "कोइन छानिएको ({number})", "selected_summ": "{value} छानिएको", - "empty": "यो वालेटमा अहिले कुनै पनि कोइनहरू छैन।", + "empty": "यो वालेटमा अहिले कुनै कोइन छैन।", "freeze": "फ्रिज", "freezeLabel": "फ्रिज", "freezeLabel_un": "अनफ्रिज गर्नुहोस्", "header": "सिक्का नियन्त्रण", "use_coin": "कोइन प्रयोग गर्नुहोस्", - "use_coins": "कोइनहरू प्रयोग गर्नुहोस्" + "use_coins": "कोइनहरू प्रयोग गर्नुहोस्", + "tip": "यो सुविधाले तपाईंलाई वालेट व्यवस्थापन सुधारको लागि कोइनहरू हेर्न, लेबल गर्न, फ्रिज गर्न वा छनोट गर्न दिन्छ। तपाईंले रंगीन वृत्तहरूमा ट्याप गरेर धेरै कोइनहरू छनोट गर्न सक्नुहुन्छ।", + "sort_asc": "आरोही", + "sort_desc": "अवरोही", + "sort_height": "उचाइ", + "sort_value": "मूल्य", + "sort_label": "लेबल", + "sort_status": "स्थिति", + "sort_by": "यसद्वारा क्रमबद्ध गर्नुहोस्" + }, + "units": { + "BTC": "BTC", + "MAX": "अधिकतम", + "sat_vbyte": "sat/vByte", + "sats": "sats" }, "addresses": { + "copy_private_key": "निजी कुञ्जी कापी गर्नुहोस्", + "sensitive_private_key": "चेतावनी: निजी कुञ्जीहरू अत्यन्तै संवेदनशील हुन्छन्। जारी राख्ने हो?", "sign_title": "हस्ताक्षर/प्रमाणित सन्देश", "sign_help": "यहाँ तपाईले बिटकोइन ठेगानामा आधारित क्रिप्टोग्राफिक हस्ताक्षर सिर्जना वा प्रमाणित गर्न सक्नुहुन्छ।", "sign_sign": "हस्ताक्षर", @@ -433,8 +681,24 @@ }, "bip47": { "payment_code": "भुक्तानी कोड", - "payment_codes_list": "भुक्तानी कोड सूची", - "who_can_pay_me": "कसले मलाई तिर्न सक्छ:", + "contacts": "सम्पर्कहरू", + "bip47_explain": "पुन: प्रयोग्य र सेयर गर्न मिल्ने कोड", + "bip47_explain_subtitle": "BIP47", + "purpose": "पुन: प्रयोग्य र सेयर गर्न मिल्ने कोड (BIP47)", + "pay_this_contact": "यो सम्पर्कलाई तिर्नुहोस्", + "rename_contact": "सम्पर्क पुन: नामकरण गर्नुहोस्", + "copy_payment_code": "भुक्तानी कोड कापी गर्नुहोस्", + "hide_contact": "सम्पर्क लुकाउनुहोस्", + "rename": "पुन: नामकरण गर्नुहोस्", + "provide_name": "यो सम्पर्कको लागि नयाँ नाम प्रदान गर्नुहोस्", + "add_contact": "सम्पर्क थप्नुहोस्", + "provide_payment_code": "भुक्तानी कोड प्रदान गर्नुहोस्", + "invalid_pc": "अमान्य भुक्तानी कोड", + "notification_tx_unconfirmed": "सूचना लेनदेन अझै पुष्टि भएको छैन, कृपया पर्खनुहोस्", + "failed_create_notif_tx": "अन-चेन लेनदेन सिर्जना गर्न असफल", + "onchain_tx_needed": "अन-चेन लेनदेन आवश्यक", + "notif_tx_sent": "सूचना लेनदेन पठाइयो। कृपया यो पुष्टि हुनको लागि पर्खनुहोस्", + "notif_tx": "सूचना लेनदेन", "not_found": "भुक्तानी कोड फेला परेन" } } diff --git a/loc/nl_nl.json b/loc/nl_nl.json index a49dd352de8..385414dc344 100644 --- a/loc/nl_nl.json +++ b/loc/nl_nl.json @@ -4,38 +4,48 @@ "cancel": "Annuleren", "continue": "Doorgaan", "clipboard": "Klembord", + "discard_changes": "Veranderingen ongedaan maken?", "enter_password": "Voer wachtwoord in", "never": "Nooit", - "disabled": "Uitgeschakeld", "of": "{number} van {total}", "ok": "Oké", - "storage_is_encrypted": "Uw opslag is versleuteld. Wachtwoord is vereist om het te ontcijferen", + "storage_is_encrypted": "Uw opslag is versleuteld. Wachtwoord is vereist om te ontcijferen.", "yes": "Ja", "no": "Nee", - "save": "Opslaan", "seed": "Seed", "success": "Succes", "wallet_key": "Wallet sleutel", - "invalid_animated_qr_code_fragment": "Ongeldig geanimeerde QRCode, probeer het opnieuw", - "file_saved": "Bestand {filePath} is opgeslagen in {destination}", - "downloads_folder": "Downloads map" - }, - "alert": { - "default": "Melding" + "close": "Sluit", + "change_input_currency": "Wijzig invoervaluta", + "refresh": "Vernieuw", + "enter_amount": "Voer bedrag in", + "qr_custom_input_button": "Tik 10 keer om aangepaste invoer in te geven", + "copied": "Gekopieerd!", + "discard_changes_explain": "Je hebt niet-opgeslagen wijzigingen. Weet je zeker dat je deze wilt verwerpen en het scherm wilt verlaten?", + "enter_url": "Voer URL in", + "save": "Opslaan...", + "pick_image": "Kies uit bibliotheek", + "pick_file": "Bestand kiezen", + "unlock": "Ontgrendelen", + "port": "Poort", + "ssl_port": "SSL-poort", + "suggested": "Voorgesteld" }, "azteco": { "codeIs": "De code van uw tegoedbon is", "errorBeforeRefeem": "Voor het inlossen moet u eerst Bitcoin-wallet toevoegen.", - "errorSomething": "Er ging is verkeerd. Is deze tegoedbon nog geldig?", + "errorSomething": "Er is iets misgegaan. Is deze tegoedbon nog geldig?", "redeem": "Inwisselen naar wallet.", "redeemButton": "Inwisselen", "success": "Succes", - "title": "Los uw Atze.co voucher in" + "title": "Verzilver uw Azte.co voucher", + "successMessage": "Voucher succesvol ingewisseld! Je tegoeden zouden binnenkort in je Bitcoin-wallet moeten verschijnen." }, "entropy": { "save": "Opslaan", - "title": "Entropy", - "undo": "Ongedaan maken" + "title": "Entropie", + "undo": "Ongedaan maken", + "amountOfEntropy": "{bits} van {limit} bits" }, "errors": { "broadcast": "Verzenden mislukt", @@ -43,92 +53,76 @@ "network": "Netwerkfout" }, "lnd": { - "active": "Actief", - "inactive": "Inactief", - "channels": "Kanalen", - "no_channels": "Geen kanalen", - "claim_balance": "Saldo claimen {balance}", - "close_channel": "Sluit kanaal", - "new_channel": "Nieuw kanaal", - "errorInvoiceExpired": "Factuur verlopen", - "force_close_channel": "Het sluiten van een kanaal forceren?", + "errorInvoiceExpired": "Factuur verlopen.", "expired": "Verlopen", - "node_alias": "Node alias", "expiresIn": "Verloopt in {time} minuten", "payButton": "Betalen", - "placeholder": "Factuur", - "open_channel": "Open kanaal", - "funding_amount_placeholder": "Stortingsbedrag, bijvoorbeeld 0.001", - "opening_channnel_for_from": "Open kanaal voor wallet {forWalletLabel}, door financiering vanuit {fromWalletLabel}", - "are_you_sure_open_channel": "Weet je het zeker dat je dit kanaal wilt openen?", + "payment": "Betaling", + "placeholder": "Factuur of adres", "potentialFee": "Mogelijke fee: {fee}", - "remote_host": "Externe host", "refill": "Bijvullen", - "reconnect_peer": "Verbind opnieuw", "refill_create": "Om verder te gaan, moet u een Bitcoin-wallet maken om mee te vullen.", "refill_external": "Aanvullen met externe wallet", "refill_lnd_balance": "Vul Lightning-walletsaldo bij", "sameWalletAsInvoiceError": "U kunt geen factuur betalen met dezelfde wallet die is gebruikt om de factuur te maken.", - "title": "tegoeden beheren", - "can_send": "Kan verzenden", - "can_receive": "Kan verzenden", - "view_logs": "Bekijk logs" + "title": "tegoeden beheren" }, "lndViewInvoice": { "additional_info": "Extra informatie", "for": "Voor:", "lightning_invoice": "Lightning-factuur", - "open_direct_channel": "Open een rechtstreeks kanaal met deze node:", "please_pay_between_and": "Betaal alsjeblieft tussen de {min} en {max}", "please_pay": "Betaal alstublieft", - "preimage": "Préimage", "sats": "sats", - "wasnt_paid_and_expired": "Deze factuur is niet betaald en is verlopen" + "wasnt_paid_and_expired": "Deze factuur is niet betaald en is verlopen", + "preimage": "Pre-image", + "date_time": "Datum en tijd" }, "plausibledeniability": { "create_fake_storage": "Creëer versleutelde opslag", - "create_password": "Wachtwoord aanmaken", "create_password_explanation": "Wachtwoord voor nep-opslag hoort niet overeen te komen met wachtwoord voor uw hoofdopslag", - "help": "Onder bepaalde omstandigheden kunt u worden gedwongen om uw wachtwoord te onthullen. Om uw coins veilig te houden, kan BlueWallet nog een versleutelde opslag aanmaken, met een ander wachtwoord. Onder druk kunt u dit wachtwoord bekendmaken aan de derde partij. Indien ingevoerd in BlueWallet, zal het nieuwe nep'-opslagruimte worden ontgrendeld. Dit lijkt legitiem voor de derde partij, maar zal uw hoofdopslag met coins niet bekend maken aan de derde partij.", + "help": "Onder bepaalde omstandigheden kunt u worden gedwongen om uw wachtwoord te onthullen. Om uw coins veilig te houden, kan BlueWallet nog een versleutelde opslag aanmaken, met een ander wachtwoord. Onder druk kunt u dit wachtwoord bekendmaken aan de derde partij. Indien ingevoerd in BlueWallet, zal een nieuwe \"nep\"-opslagruimte worden ontgrendeld. Dit lijkt legitiem voor de derde partij, maar zal uw hoofdopslag met coins niet bekend maken aan de derde partij.", "help2": "De nieuwe opslag zal volledig functioneel zijn en u kunt er een minimum aantal munten opslaan zodat het geloofwaardig lijkt.", "password_should_not_match": "Wachtwoord is momenteel in gebruik. Probeer een ander wachtwoord.", - "passwords_do_not_match": "Wachtwoorden komen niet overeen, probeer het opnieuw", - "retype_password": "Herhaal wachtwoord", - "success": "Succes", "title": "Plausibele ontkenning" }, "pleasebackup": { "ask": "Heeft u uw back-up zin opgeslagen? Deze back-up zin is nodig om toegang te krijgen tot uw tegoeden wanneer u dit apparaat verliest. Zonder de back-up zin zijn je gelden voor altijd verloren.", - "ask_no": "Nee, dat heb ik niet", - "ask_yes": "Ja, dat heb ik.", - "ok": "Oké, ik heb het opgeschreven", - "ok_lnd": "Oké, ik heb het opgeslagen", + "ok": "Ok, ik heb het opgeschreven", + "ok_lnd": "Oké, ik heb het bewaard.", "text": "Neem alstublieft een moment om deze mnemonische zin te noteren op een stukje papier.\nHet is uw backup en kan gebruikt worden om de wallet te herstellen.", "text_lnd": "Sla deze wallet backup op. Zo kun je de wallet herstellen in geval van verlies.", - "title": "Uw wallet is gecreëerd" + "title": "Je wallet is aangemaakt", + "ask_no": "Nee, dat heb ik niet gedaan.", + "ask_yes": "Ja, dat heb ik gedaan." }, "receive": { "details_create": "Maken", "details_label": "Omschrijving", "details_setAmount": "Ontvang met bedrag", - "details_share": "delen", - "header": "Ontvang" + "header": "Ontvang", + "details_share": "Delen...", + "address_not_found": "Kan geen ontvangstadres genereren.", + "maxSats": "Maximaal bedrag is {max} sats", + "maxSatsFull": "Maximaal bedrag is {max} sats of {currency}", + "minSats": "Minimaal bedrag is {min} sats", + "minSatsFull": "Minimaal bedrag is {min} sats of {currency}", + "qrcode_for_the_address": "QR-code voor het adres", + "bip47_explanation": "Payment codes zijn een universeel adres dat voorkomt dat je wallet-adressen worden onthuld. Niet alle diensten ondersteunen ze.", + "reset": "Reset" }, "send": { "provided_address_is_invoice": "Dit adres lijkt te zijn voor een Lightning factuur, Ga naar je Lightning wallet om een betaling voor deze factuur te maken.", "broadcastButton": "Verzenden", "broadcastError": "fout", - "broadcastNone": "Voer transactie-hash in", + "broadcastNone": "Voer transactie-hex in", "broadcastPending": "in afwachting", "broadcastSuccess": "succes", "confirm_header": "Bevestig", "confirm_sendNow": "Nu verzenden", "create_amount": "Bedrag", "create_broadcast": "Verzenden", - "create_copy": "Kopieer en verzend lader", - "create_details": "Details", - "create_fee": "Fee", - "create_memo": "Memo", + "create_copy": "Kopieer en verzend later", "create_this_is_hex": "Dit is uw transactie-hex, ondertekend en klaar om op het netwerk te worden uitgezonden.", "create_to": "Naar", "create_tx_size": "TX grootte", @@ -137,10 +131,10 @@ "details_add_rec_rem": "Verwijder ontvanger", "details_address": "adres", "details_address_field_is_not_valid": "Adres is niet geldig", - "details_adv_fee_bump": "Sta Fee Bumb toe", + "details_adv_fee_bump": "Sta Fee Bump toe", "details_adv_full": "Gebruik volledige saldo", "details_adv_full_sure": "Weet u zeker dat u het volledige saldo van uw wallet wilt gebruiken voor deze transactie?", - "details_adv_full_sure_frozen": "Weet je zeker dat je de volledige balans van je wallet voor deze transactie? Let op: frozen coins worden niet meegenomen", + "details_adv_full_sure_frozen": "Weet je zeker dat je de volledige balans van je wallet wilt gebruiken voor deze transactie? Let op: bevroren coins worden niet meegenomen.", "details_adv_import": "Importeer transactie", "details_adv_import_qr": "Importeer transactie (QR)", "details_amount_field_is_not_valid": "Bedrag is niet geldig", @@ -151,7 +145,6 @@ "details_next": "Volgende", "details_no_signed_tx": "Het geselecteerde bestand bevat geen transactie die kan worden geïmporteerd.", "details_note_placeholder": "notitie voor mezelf", - "details_scan": "Scan", "details_scan_hint": "Dubbelklik om een bestemming te scannen of te importeren", "details_total_exceeds_balance": "Het verzendingsbedrag overschrijdt het beschikbare saldo.", "details_unrecognized_file_format": "Onherkenbaar bestandsformaat", @@ -159,15 +152,8 @@ "dynamic_init": "Initialiseren", "dynamic_next": "Volgende", "dynamic_prev": "Vorige", - "dynamic_start": "Start", - "dynamic_stop": "Stop", - "fee_10m": "10m", - "fee_1d": "1d", "fee_3h": "3u", - "fee_custom": "Custom", "fee_fast": "Snel", - "fee_medium": "Medium", - "fee_satvbyte": "in sat/vByte", "fee_slow": "Langzaam", "header": "Verstuur", "input_clear": "Leegmaken", @@ -177,27 +163,54 @@ "permission_camera_message": "We hebben toestemming nodig om uw camera te gebruiken", "psbt_sign": "Onderteken een transactie", "open_settings": "Open instellingen", - "permission_storage_later": "Vraag mij later", - "permission_storage_message": "BlueWallet heeft u toestemming nodig om toegang te krijgen tot uw opslag om deze transactie op te slaan.", - "permission_storage_denied_message": "Bluewallet kan dit bestand niet opslaan. Ga naar apparaatinstellingen en zet Opslag Toegestaan aan,", + "permission_storage_denied_message": "BlueWallet kan dit bestand niet opslaan. Ga naar apparaatinstellingen en zet Opslag Toegestaan aan.", "permission_storage_title": "BlueWallet opslag toegang toestemming", - "psbt_clipboard": "Gekopieerd naar Plakbord", + "psbt_clipboard": "Gekopieerd naar klembord", "psbt_this_is_psbt": "Dit is een gedeeltelijk ondertekende bitcoin-transactie (PSBT). Voltooi het door te ondertekenen met uw hardware-wallet. ", "psbt_tx_export": "Exporteer naar bestand", "no_tx_signing_in_progress": "Er is geen ondertekening van een transactie bezig", - "outdated_rate": "De Rate is voor het laatst geüpdate op: {date} ", + "outdated_rate": "De koers is voor het laatst bijgewerkt op: {date} ", "psbt_tx_open": "Open ondertekende transactie", "psbt_tx_scan": "Scan ondertekende transactie", - "qr_error_no_qrcode": "We hebben geen QR Code kunnen vinden in de geselecteerde afbeelding. Zorg ervoor dat de afbeelding enkel een QR Code bevat en geen extra gegevens zoals tekst of knoppen.", "reset_amount": "Bedrag resetten", "reset_amount_confirm": "Weet je het zeker dat je het bedrag wilt resetten?", "success_done": "Klaar", - "txSaved": "Het transactiebestand ({filePath}) is opgeslagen in uw map Downloads.", - "problem_with_psbt": "Probleem met PSBT" + "problem_with_psbt": "Probleem met PSBT", + "details_insert_contact": "Contact invoegen", + "details_add_recc_rem_all_alert_description": "Weet je zeker dat je alle ontvangers wilt verwijderen?", + "details_add_rec_rem_all": "Verwijder alle ontvangers", + "details_recipients_title": "Ontvangers", + "details_recipient_title": "Ontvanger #{number} van #{total}", + "please_complete_recipient_title": "Onvolledige ontvanger", + "please_complete_recipient_details": "Vul de gegevens van ontvanger #{number} in voordat je een nieuwe toevoegt.", + "details_frozen": "{amount} BTC is bevroren.", + "details_scan_error": "Scanfout", + "details_total_exceeds_balance_frozen": "Het verzendbedrag overschrijdt het beschikbare saldo. Let op: bevroren coins zijn uitgesloten.", + "insert_custom_fee": "Voer fee in", + "fee_replace_minvb": "De totale fee rate (satoshi per vByte) die je wilt betalen moet hoger zijn dan {min} sat/vByte.", + "invalid_psbt": "Ongeldige PSBT opgegeven.", + "qr_error_no_qrcode": "We konden geen geldige QR-code vinden in de geselecteerde afbeelding. Zorg ervoor dat de afbeelding alleen een QR-code bevat en geen andere inhoud zoals tekst of knoppen.", + "txSaved": "Het transactiebestand ({filePath}) is opgeslagen.", + "file_saved_at_path": "Het bestand ({filePath}) is opgeslagen.", + "cant_send_to_silentpayment_adress": "Deze wallet kan niet naar SilentPayment-adressen verzenden", + "cant_send_to_bip47": "Deze wallet kan niet naar BIP47 payment codes verzenden", + "cant_find_bip47_notification": "Voeg deze payment code eerst toe aan contacten", + "create_details": "Details", + "create_fee": "Fee", + "create_memo": "Memo", + "create_satoshi_per_vbyte": "Satoshi per vByte", + "details_scan": "Scannen", + "dynamic_start": "Start", + "dynamic_stop": "Stop", + "fee_10m": "10m", + "fee_1d": "1d", + "fee_custom": "Aangepast", + "fee_medium": "Gemiddeld", + "fee_satvbyte": "in sat/vByte" }, "settings": { "about": "Over", - "about_awesome": "Gebouwt met de geweldige", + "about_awesome": "Gebouwd met de geweldige", "about_backup": "Maak altijd een backup van uw sleutels!", "about_free": "BlueWallet is een gratis en open-source project. Gemaakt door Bitcoin gebruikers.", "about_license": "MIT Licentie", @@ -207,70 +220,41 @@ "about_selftest_electrum_disabled": "Het zelf testen is niet beschikbaar met Electrum offline modus. Zet alsjeblieft de offline modus uit en probeer het opnieuw.", "about_selftest_ok": "Alle interne tests zijn geslaagd. De wallet werkt.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord server", "about_sm_telegram": "Telegram kanaal", - "about_sm_twitter": "Volg ons op Twitter", - "advanced_options": "Geavanceerde opties", "biometrics": "Biometrische beveiliging", "biom_10times": "Je hebt 10 wachtwoordpogingen gedaan. Wil je je opslagruimte resetten? Al je wallets worden verwijderd en je opslag wordt ontsleuteld.", "biom_conf_identity": "Bevestig je identiteit.", - "biom_no_passcode": "Er is geen wachtwoord ingesteld op je apparaat. Ga naar het instellingenmenu om een wachtwoord in te stellen, om door te gaan.", "biom_remove_decrypt": "Al je wallets worden verwijderd en je opslag zal worden ontsleuteld. Weet je zeker dat je door wil gaan?", "currency": "Valuta", - "currency_fetch_error": "Er is een fout opgetreden bij het ophalen van de Rate voor de geselecteerde muntsoort", - "default_desc": "Indien uitgeschakeld zal BlueWallet meteen de geselecteerde wallet openen bij het opstarten.", - "default_info": "Standaard info", + "currency_fetch_error": "Er is een fout opgetreden bij het ophalen van de koers voor de geselecteerde muntsoort.", "default_title": "Bij opstarten", - "default_wallets": "Bekijk alle wallets", "electrum_connected": "Verbonden", "electrum_connected_not": "Niet verbonden", - "electrum_error_connect": "Kan niet verbinden met aangeleverde Electrum server", "lndhub_uri": "Bijv., {example}", "electrum_host": "Bijv., {example}", "electrum_offline_mode": "Offline Modus", - "electrum_offline_description": "Indien ingeschakeld, zullen uw Bitcoin wallets geen poging doen om balansen of transacties op te halen.", - "electrum_port": "Port, gebruikelijk {example}", + "electrum_offline_description": "Indien ingeschakeld, zullen uw Bitcoin-wallets geen poging doen om balansen of transacties op te halen.", + "electrum_port": "Poort, gebruikelijk {example}", "use_ssl": "Gebruik SSL", "electrum_saved": "Uw veranderingen zijn succesvol opgeslagen. Opnieuw opstarten kan nodig zijn om de wijzigingen door te voeren.", "set_electrum_server_as_default": "{server} instellen als de standaard electrum server?", - "set_lndhub_as_default": "{url} instellen als de standaard LNDHub server?", - "electrum_settings_server": "Electrum Server", - "electrum_settings_explain": "Laat leeg om de standaard te gebuiken.", - "electrum_status": "Status", - "electrum_clear_alert_title": "Geschiedenis verwijderen?", - "electrum_clear_alert_message": "Wil je de geschiedenis van de electrum servers wissen?", - "electrum_clear_alert_cancel": "Annuleren", - "electrum_clear_alert_ok": "Oké", - "electrum_select": "Selecteren", - "electrum_reset": "Naar standaardinstellingen resetten", "electrum_unable_to_connect": "Niet in staat te verbinden met {server}.", - "electrum_history": "Server geschiedenis", - "electrum_reset_to_default": "Weet u zeker dat u uw Electrum instellingen wil herstellen naar de standaardinstellingen?", - "electrum_clear": "Wissen", - "tor_supported": "Tor ondersteund", - "tor_unsupported": "Tor verbindingen worden niet ondersteund.", + "electrum_reset": "Naar standaardinstellingen resetten", "encrypt_decrypt": "Versleutel opslag", "encrypt_decrypt_q": "Weet u zeker dat u uw opslag wilt ontsleutelen? Hierdoor kunnen uw wallets zonder wachtwoord worden geopend.", - "encrypt_enc_and_pass": "Versleuteld en wachtwoord beveiligd", "encrypt_title": "Beveiliging", "encrypt_tstorage": "opslag", "encrypt_use": "Gebruik {type}", - "encrypt_use_expl": "{type} wordt gebruikt om jouw identiteit te bevestigen voordat je een transactie uitvoert, een wallet ontgrendelt, exporteert of verwijdert. {type} wordt niet gebruikt om versleutelde opslag te ontgrendelen.", "general": "Algemeen", - "general_adv_mode": "Geavanceerde modus", - "general_adv_mode_e": "Indien ingeschakeld ziet u geavanceerde opties zoals verschillende wallettypes, de mogelijkheid om de LNDHub-instantie te specificeren waarmee u verbinding wilt maken en aangepaste entropie tijdens het aanmaken van een wallet.", "general_continuity": "Continuïteit", "general_continuity_e": "Indien ingeschakeld, kunt u geselecteerde wallets en transacties bekijken met uw andere Apple iCloud-apparaten.", "groundcontrol_explanation": "GroundControl is een gratis opensource-server voor pushmeldingen voor bitcoin-wallets. U kunt uw eigen GroundControl-server installeren en de URL hier plaatsen om niet te vertrouwen op de infrastructuur van BlueWallet. Laat leeg om standaard te gebruiken", "header": "Instellingen", "language": "Taal", - "last_updated": "Voor het laatst ge-update op", - "language_isRTL": "Herstarten van BlueWallet is vereist voordat de taal oriëntatie van kracht wordt.", - "lightning_error_lndhub_uri": "Ongeldige LNDHub URI", + "last_updated": "Voor het laatst bijgewerkt op", + "language_isRTL": "Herstarten van BlueWallet is vereist voordat de taaloriëntatie van kracht wordt.", "lightning_saved": "Uw wijzigingen zijn succesvol opgeslagen", "lightning_settings": "Lightning-instellingen", - "tor_settings": "Tor instellingen", - "lightning_settings_explain": "Om uw eigen LND node te verbinden, installeer LNDHub en voer de URL hier in bij instellingen. Laat open om de BlueWallet LNDHub te gebruiken. Let op dat enkel wallets die gecreëerd zijn na het opslaan van wijzigingen zullen verbinden met de aangegeven LNDHub.", "network": "Netwerk", "network_broadcast": "Verzend transactie", "network_electrum": "Electrum server", @@ -278,58 +262,88 @@ "notifications": "Meldingen", "open_link_in_explorer": "Open link in verkenner", "password": "Wachtwoord", - "password_explain": "Maak een wachtwoord aan dat u wilt gebruiken om de opslag te versleutelen", - "passwords_do_not_match": "Wachtwoorden komen niet overeen", "plausible_deniability": "Plausibele ontkenning", - "privacy": "Privacy", "privacy_read_clipboard": "Lees klembord", "privacy_system_settings": "Systeeminstellingen", "privacy_quickactions": "Wallet snelkoppelingen", - "privacy_quickactions_explanation": "Houd het BlueWallet-app-pictogram op uw startscherm ingedrukt om snel het saldo van uw wallet te bekijken.", "privacy_clipboard_explanation": "Bied snelkoppelingen aan als een adres of factuur op uw klembord staat.", "privacy_do_not_track": "Analyses Uitschakelen", "privacy_do_not_track_explanation": "Informatie over de prestatie en betrouwbaarheid zal niet worden opgegeven voor analyse.", - "push_notifications": "Push notificaties", - "rate": "Rate", - "retype_password": "Geef nogmaals het wachtwoord op", "selfTest": "Zelf-Test", "save": "Opslaan", "saved": "Opgeslagen", - "success_transaction_broadcasted": "Gelukt! Jouw transactie is uitgezonden!", "total_balance": "Totaalbalans", "total_balance_explanation": "Laat de totaalbalans van al je wallets zien op de widgets op je thuisscherm.", - "widgets": "Widgets", - "tools": "Hulpmiddelen" + "tools": "Hulpmiddelen", + "performance_score": "Prestatiescore: {num}", + "run_performance_test": "Prestatie testen", + "block_explorer_invalid_custom_url": "De opgegeven URL is ongeldig. Voer een geldige URL in die begint met http:// of https://.", + "privacy_temporary_screenshots": "Schermopname toestaan", + "privacy_temporary_screenshots_instructions": "Schermopname-bescherming wordt tijdelijk uitgeschakeld, waardoor screenshots en schermopnames mogelijk worden. De bescherming wordt automatisch opnieuw geactiveerd wanneer je BlueWallet sluit en opnieuw opent.", + "biometrics_no_longer_available": "Je apparaatinstellingen zijn gewijzigd en komen niet meer overeen met de geselecteerde beveiligingsinstellingen in de app. Schakel biometrische beveiliging of toegangscode opnieuw in en start de app opnieuw op om deze wijzigingen toe te passen.", + "biom_no_passcode": "Je apparaat heeft geen toegangscode of biometrische beveiliging ingeschakeld. Om verder te gaan, configureer een toegangscode of biometrische beveiliging in de Instellingen-app.", + "currency_source": "Rate wordt opgehaald van", + "donate": "Doneren", + "donate_description": "Help ons om Blue gratis te houden!", + "electrum_error_connect": "Kan geen verbinding maken met de opgegeven Electrum-server", + "electrum_error_connect_tor": "Kan geen verbinding maken met de opgegeven Electrum-server. Zorg ervoor dat de Orbot-app is verbonden en probeer het opnieuw.", + "set_lndhub_as_default": "{url} instellen als de standaard LNDhub-server?", + "electrum_preferred_server": "Voorkeursserver", + "electrum_preferred_server_description": "Voer de server in die je wallet wil gebruiken voor alle Bitcoin-activiteiten. Eenmaal ingesteld, zal je wallet exclusief deze server gebruiken om saldi te controleren, transacties te verzenden en netwerkgegevens op te halen. Zorg ervoor dat je deze server vertrouwt voordat je deze instelt.", + "electrum_history": "Geschiedenis", + "electrum_reset_to_default": "Hierdoor zal BlueWallet willekeurig een server kiezen uit de serverlijst.", + "electrum_reset_to_default_and_clear_history": "Naar standaard resetten en geschiedenis wissen", + "encrypt_enc_and_pass": "Wachtwoordbeveiligd", + "encrypt_storage_explanation_headline": "Opslagversleuteling inschakelen", + "encrypt_storage_explanation_description_line1": "Het inschakelen van opslagversleuteling voegt een extra beschermingslaag toe aan je app door de manier waarop je gegevens op je apparaat worden opgeslagen te beveiligen. Dit maakt het moeilijker voor anderen om zonder toestemming toegang te krijgen tot je informatie.", + "encrypt_storage_explanation_description_line2": "Het is echter belangrijk om te weten dat deze versleuteling alleen de toegang beschermt tot je wallets die zijn opgeslagen in de keychain van het apparaat. Het plaatst geen wachtwoord of extra bescherming op de wallets zelf.", + "i_understand": "Ik begrijp het", + "block_explorer": "Blokverkenner", + "block_explorer_preferred": "Gebruik voorkeursblokverkenner", + "block_explorer_error_saving_custom": "Fout bij het opslaan van voorkeursblokverkenner", + "set_as_preferred": "Instellen als voorkeur", + "set_as_preferred_electrum": "Door {host}:{port} als voorkeursserver in te stellen wordt willekeurig verbinden met een voorgestelde server uitgeschakeld.", + "encrypted_feature_disabled": "Deze functie kan niet worden gebruikt met versleutelde opslag ingeschakeld.", + "encrypt_use_expl": "{type} wordt gebruikt om je identiteit te bevestigen voordat je een transactie maakt, ontgrendelt, exporteert of een wallet verwijdert.", + "biometrics_fail": "Als {type} niet is ingeschakeld of niet kan ontgrendelen, kun je de toegangscode van je apparaat als alternatief gebruiken.", + "license": "Licentie", + "lightning_error_lndhub_uri": "Ongeldige LNDhub-URI", + "lightning_error_lndhub_uri_tor": "Ongeldige LNDhub-URI. Zorg ervoor dat de Orbot-app is verbonden en probeer het opnieuw.", + "lightning_settings_explain": "Om verbinding te maken met je eigen LND-node, installeer LNDhub en plaats de URL hier in de instellingen. Houd er rekening mee dat alleen wallets die na het opslaan van wijzigingen worden aangemaakt verbinding zullen maken met de opgegeven LNDhub.", + "lndhub_github": "GitHub-repository", + "electrum_suggested_description": "Wanneer er geen voorkeursserver is ingesteld, wordt willekeurig een voorgestelde server geselecteerd voor gebruik.", + "password_explain": "Voer het wachtwoord in dat je gaat gebruiken om je opslag te ontgrendelen.", + "privacy_quickactions_explanation": "Houd het BlueWallet-app-icoon ingedrukt om snel het saldo van je wallet te bekijken.", + "push_notifications_explanation": "Door meldingen in te schakelen, wordt je apparaattoken naar de server verzonden, samen met wallet-adressen en transactie-ID's voor alle wallets en transacties die zijn gemaakt na het inschakelen van meldingen. Het apparaattoken wordt gebruikt om meldingen te verzenden, en de wallet-informatie stelt ons in staat om je te informeren over inkomende Bitcoin of transactiebevestigingen.\n\nAlleen informatie van na het inschakelen van meldingen wordt verzonden — niets van daarvoor wordt verzameld.\n\nHet uitschakelen van meldingen verwijdert al deze informatie van de server. Bovendien zal het verwijderen van een wallet uit de app ook de bijbehorende informatie van de server verwijderen.", + "success_transaction_broadcasted": "Je transactie is succesvol verzonden!", + "electrum_settings_server": "Electrum-server", + "electrum_status": "Status", + "privacy": "Privacy", + "rate": "Koers", + "widgets": "Widgets" }, "notifications": { "would_you_like_to_receive_notifications": "Wil je meldingen ontvangen als je binnenkomende betalingen ontvangt?", - "no_and_dont_ask": "Nee, en vraag het mij niet meer", - "ask_me_later": "Vraag het mij later" + "notifications_subtitle": "Inkomende betalingen en transactiebevestigingen", + "no_and_dont_ask": "Nee, en vraag het me niet meer.", + "permission_denied_message": "Je hebt geweigerd om meldingen te ontvangen. Als je meldingen wilt ontvangen, schakel deze dan in via de instellingen van je apparaat." }, "transactions": { "cancel_no": "Deze transactie is niet vervangbaar", - "cancel_title": "Annuleer deze transcatie (RBF)", + "cancel_title": "Annuleer deze transactie (RBF)", "confirmations_lowercase": "{confirmations} bevestigingen", - "copy_link": "Kopieer link", "expand_note": "Notitie Uitbreiden", "cpfp_create": "Creëer", "cpfp_exp": "We zullen een andere transactie creëren die uw niet-bevestigde transactie besteedt. De totale vergoeding is hoger dan de oorspronkelijke transactiekosten, dus deze moet sneller worden gemined. Dit heet CPFP - Child Pays For Parent.", - "cpfp_no_bump": "Deze transactie is niet bumpable", - "cpfp_title": "Bumb fee (CPFP)", + "cpfp_no_bump": "Deze transactie is niet bumpable.", + "cpfp_title": "Bump fee (CPFP)", "details_balance_hide": "Verberg saldo", "details_balance_show": "Toon saldo", - "details_block": "Blokhoogte", "details_copy": "Kopiëren", - "details_copy_amount": "Kopieer aantal", "details_copy_block_explorer_link": "Kopieer blok explorer Link", "details_copy_note": "Kopieer Note", "details_copy_txid": "Kopieer Transactie ID", - "details_from": "Invoer", - "details_inputs": "Inputs", - "details_outputs": "Outputs", "details_received": "Ontvangen", - "transaction_note_saved": "Transactie notitie is succesvol opgeslagen.", - "details_show_in_block_explorer": "Weergeven in block explorer", "details_title": "Transacties", "details_to": "Uitvoer", "enable_offline_signing": "Deze wallet wordt niet gebruikt in combinatie met een hardware wallet. Wilt u het gebruik inschakelen?", @@ -341,59 +355,87 @@ "eta_3h": "ETA: In ~3 uur", "eta_1d": "ETA: In ~1 dag", "list_title": "Transacties", - "rbf_title": "Bumb fee (RBF)", - "status_bump": "Bumb fee", + "list_title_received": "Ontvangen", + "transaction": "Transacties", + "rbf_title": "Bump fee (RBF)", + "status_bump": "Bump fee", "status_cancel": "Annuleer transactie", "transactions_count": "Transactieteller", "txid": "Transactie ID", - "updating": "Updaten..." + "updating": "Updaten...", + "cancel_explain": "We zullen deze transactie vervangen door een transactie die aan jezelf betaalt en hogere fees heeft. Dit annuleert effectief de huidige transactie. Dit heet RBF — Replace by Fee.", + "transaction_loading_error": "Er was een probleem bij het laden van de transactie. Probeer het later opnieuw.", + "transaction_not_available": "Transactie niet beschikbaar", + "date": "Datum", + "details_view_in_browser": "Bekijken in browser", + "incoming_transaction": "Inkomende transactie", + "outgoing_transaction": "Uitgaande transactie", + "expired_transaction": "Verlopen transactie", + "pending_transaction": "Transactie in afwachting", + "offchain": "Off-chain", + "onchain": "On-chain", + "list_title_sent": "Verzonden", + "open_url_error": "Kan de link niet openen met de standaardbrowser. Wijzig je standaardbrowser en probeer het opnieuw.", + "rbf_explain": "We zullen deze transactie vervangen door een transactie met een hogere fee zodat deze sneller gemined wordt. Dit heet RBF — Replace by Fee.", + "watchOnlyWarningTitle": "Beveiligingswaarschuwing", + "watchOnlyWarningDescription": "Pas op voor oplichters die vaak \"watch-only\" wallets gebruiken om gebruikers te misleiden. Deze wallets staan je niet toe om tegoeden te beheren of te verzenden; ze laten je alleen het saldo bekijken.", + "custom_fee_warning_title": "Waarschuwing", + "custom_fee_warning_description": "Fees onder 1 sat/vB zijn geldig, maar worden mogelijk niet doorgegeven vanwege node-beleid.", + "details_eta_analyzing": "Bezig met analyseren...", + "details_sent": "Verzonden", + "details_explorer": "verkenner", + "details_network_fee": "Netwerk-fee", + "details_to_address": "Naar", + "details_note": "Notitie", + "details_add_note": "toevoegen", + "details_advanced": "Geavanceerd", + "details_size": "Grootte", + "details_virtual_size": "Virtuele grootte", + "details_tx_hex": "Tx hex", + "details_inputs": "Inputs", + "details_outputs": "Outputs", + "details_section": "Details", + "details_id": "ID", + "details_fee_rate": "Fee-tarief", + "details_inputs_count": "Inputs ({count})", + "details_outputs_count": "Outputs ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Eenvoudige en krachtige Bitcoin-wallet", "add_create": "Aanmaken", + "total_balance": "Totaalbalans", "add_entropy_generated": "{gen} bytes gegenereerde entropie", "add_entropy_provide": "Zorg voor entropie via dobbelstenen", "add_entropy_remain": "{gen} bytes gegenereerde entropie. Resterende {rem} bytes zullen worden verkregen uit het systeem voor willekeurige getallen.", "add_import_wallet": "Wallet importeren", "add_lightning": "Lightning", "add_lightning_explain": "Voor uitgaven met directe transacties", - "add_lndhub": "Verbind met uw LNDHub", - "add_lndhub_error": "399\nHet ingevoerde node adres is een ongeldige LNDHub node.", "add_lndhub_placeholder": "Uw node adres", "add_placeholder": "Mijn eerste wallet", "add_title": "Wallet toevoegen", "add_wallet_name": "Naam", - "add_wallet_type": "Type", - "balance": "Balans", "clipboard_bitcoin": "U heeft een Bitcoin-adres op uw klembord. Wilt u deze gebruiken voor een transactie?", "clipboard_lightning": "Je hebt een Lightning-factuur op je klembord. Wilt u deze gebruiken voor een transactie?", "details_address": "Adres", - "details_advanced": "Geavenceerd", + "details_advanced": "Geavanceerd", "details_are_you_sure": "Weet u het zeker?", "details_connected_to": "Verbonden met", - "details_del_wb_err": "Het opgegeven saldo komt niet overeen met het saldo van deze wallet. Probeer het a.u.b. opnieuw", "details_del_wb_q": "Deze wallet heeft een balans. Voor je verder gaat, wees er van bewust dat je het bedrag in deze wallet niet kan herstellen zonder de seed phrase van deze wallet. Om onbedoelde verwijdering te voorkomen, voer alsjeblieft hier de balans {balance} van de wallet in Satoshis in. ", "details_delete": "Verwijderen", "details_delete_wallet": "Verwijder wallet", "details_derivation_path": "Derivatiepad", - "details_display": "Toon in wallet-lijst", "details_export_backup": "Exporteren / back-up maken", "details_master_fingerprint": "Master vingerafdruk", "details_multisig_type": "multisig", - "details_no_cancel": "Nee, annuleren", - "details_save": "Opslaan", "details_show_xpub": "Toon wallet XPUB", "details_show_addresses": "Toon adressen", - "details_title": "Wallet", - "details_type": "Type", "details_use_with_hardware_wallet": "Gebruik met hardware-wallet", - "details_wallet_updated": "Wallet bijgewerkt", "details_yes_delete": "Ja, verwijder", "enter_bip38_password": "Voer wachtwoord in om te ontgrendelen", "export_title": "Wallet exporteren", "import_do_import": "Importeren", - "import_passphrase": "Passphrase", + "import_passphrase": "Wachtwoordzin", "import_passphrase_title": "Wachtwoord", "import_passphrase_message": "Voer wachtwoord in als u één heeft gebruikt", "import_error": "Importeren mislukt. Zorg ervoor dat de verstrekte gegevens geldig zijn.", @@ -407,64 +449,105 @@ "import_discovery_subtitle": "Kies een ontdekte wallet", "import_discovery_derivation": "Gebruik een handmatig derivation path", "import_discovery_no_wallets": "Er zijn geen wallets gevonden", - "import_derivation_found": "gevonden", - "import_derivation_found_not": "niet gevonden", - "import_derivation_loading": "aan het laden...", - "import_derivation_subtitle": "Vul hier het handmatige derivation path in en wel zullen proberen te helpen met je wallet te vinden", "import_derivation_title": "Derivation path", - "import_derivation_unknown": "Onbekend", - "import_wrong_path": "verkeerde Derivation Path", "list_create_a_button": "Voeg nu toe", "list_create_a_wallet": "Wallet aanmaken", - "list_create_a_wallet_text": "Het is gratis en u kunt \ner zoveel maken als u wilt", "list_empty_txs1": "Uw transacties verschijnen hier", "list_empty_txs1_lightning": "Lightning-wallet moet worden gebruikt voor uw dagelijkse transacties. De fees zijn oneerlijk goedkoop en het is razendsnel.", "list_empty_txs2": "Begin met uw wallet.", "list_empty_txs2_lightning": "\nOm het te gebruiken, tikt u op \"tegoeden beheren\" en laadt u uw saldo op.", "list_latest_transaction": "Laatste transactie", - "list_ln_browser": "LApp Browser", "list_long_choose": "Kies foto", - "list_long_clipboard": "Kopiëren van klembord", + "paste_from_clipboard": "Plak", + "import_file": "Importeer bestand", "list_long_scan": "Scan QR-code", - "list_title": "Wallets", "list_tryagain": "Probeer opnieuw", "no_ln_wallet_error": "Voordat u een Lightning-factuur betaalt, moet u eerst een Lightning-wallet toevoegen.", "looks_like_bip38": "Dit lijkt op een met een wachtwoord beveiligde private key (BIP38)", - "reorder_title": "Wallets opnieuw ordenen", "please_continue_scanning": "Ga door met scannen", "select_no_bitcoin": "Er is momenteel geen Bitcoin-wallet beschikbaar", "select_no_bitcoin_exp": "Een Bitcoin-wallet is vereist om Lightning-wallets opnieuw te vullen. Maak of importeer er een.", "select_wallet": "Selecteer wallet", - "xpub_copiedToClipboard": "Gekopieerd naar het klembord.", "pull_to_refresh": "Pull om te refreshen.", - "warning_do_not_disclose": "Waarschuwing! Niet bekendmaken", "add_ln_wallet_first": "U moet eerst een Lightning-wallet toevoegen.", "identity_pubkey": "Identiteit PubKey", - "xpub_title": "Wallet XPUB" + "add_entropy_reset_title": "Entropie resetten", + "add_entropy_reset_message": "Het wijzigen van het wallet-type reset de huidige entropie. Wil je doorgaan?", + "add_entropy_bytes": "{bytes} bytes entropie", + "add_lndhub": "Verbind met je LNDhub", + "add_lndhub_error": "Het opgegeven node-adres is een ongeldige LNDhub-node.", + "add_wallet_seed_length": "Seed-lengte", + "add_wallet_seed_length_12": "12 woorden", + "add_wallet_seed_length_24": "24 woorden", + "clear_clipboard_on_import": "Klembord wissen bij importeren", + "details_del_wb_err": "Het opgegeven saldobedrag komt niet overeen met het saldo van deze wallet. Probeer het opnieuw.", + "details_display": "Weergeven op startscherm", + "details_export_history": "Geschiedenis exporteren naar CSV", + "swipe_balance_hide": "Verbergen", + "swipe_balance_show": "Tonen", + "drag_to_reorder": "Sleep om volgorde te wijzigen", + "clear_search": "Zoekopdracht wissen", + "import_success_watchonly": "Je wallet is succesvol geïmporteerd. WAARSCHUWING: Dit is een watch-only-wallet, je kunt er NIET vanaf uitgeven.", + "learn_more": "Meer informatie", + "import_discovery_offline": "BlueWallet is momenteel in offline-modus. In deze modus kan het bestaan van de wallet niet worden geverifieerd, dus moet je de juiste handmatig selecteren", + "import_derivation_found": "Gevonden", + "import_derivation_found_not": "Niet gevonden", + "import_derivation_loading": "Bezig met laden...", + "import_derivation_subtitle": "Voer een aangepast derivatiepad in, en we zullen proberen je wallet te ontdekken.", + "import_derivation_unknown": "Onbekend", + "import_wrong_path": "Verkeerd derivatiepad", + "list_create_a_wallet_text": "Het is gratis, en je kunt er \nzoveel aanmaken als je wilt.", + "manage_title": "Wallets beheren", + "no_results_found": "Geen resultaten gevonden.", + "warning_do_not_disclose": "Deel onderstaande informatie nooit", + "scan_import": "Scan deze QR-code om je wallet in een andere applicatie te importeren.", + "write_down_header": "Maak een handmatige back-up", + "write_down": "Schrijf deze woorden op en bewaar ze veilig. Gebruik ze om je wallet later te herstellen.", + "wallet_type_this": "Dit wallet-type is {type}.", + "share_number": "Deel {number}", + "copy_ln_url": "Kopieer en bewaar deze URL veilig om je wallet later te herstellen.", + "copy_ln_public": "Kopieer en bewaar deze informatie veilig om je wallet later te herstellen.", + "manage_wallets_search_placeholder": "Zoek wallets, adressen, transacties en memo's", + "more_info": "Meer info", + "details_delete_wallet_error_message": "Er was een probleem bij het bevestigen of deze wallet uit de meldingen is verwijderd — dit kan komen door een netwerkprobleem of slechte verbinding. Als je doorgaat, ontvang je mogelijk nog steeds meldingen voor transacties met betrekking tot deze wallet, zelfs nadat deze is verwijderd.", + "details_delete_anyway": "Toch verwijderen", + "add_entropy": "Entropie", + "add_wallet_type": "Type", + "details_title": "Wallet", + "wallets": "Wallets", + "details_type": "Type", + "list_title": "Wallets", + "xpub_title": "Wallet xpub" + }, + "total_balance_view": { + "title": "Totaalbalans", + "display_in_bitcoin": "Weergeven in Bitcoin", + "hide": "Verbergen", + "display_in_sats": "Weergeven in sats", + "display_in_fiat": "Weergeven in {currency}", + "explanation": "Bekijk het totaalsaldo van al je wallets in het overzichtsscherm." }, "multisig": { - "multisig_vault": "Kluis", + "multisig_vault": "Multisig Kluis", "default_label": "Multisig Kluis", "multisig_vault_explain": "Beste beveiliging voor grote bedragen", "provide_signature": "Geef een handtekening", "vault_key": "Kluissleutel {number}", "required_keys_out_of_total": "Vereiste sleutels uit het totaal", - "fee": "Fee: {number}", "fee_btc": "{number} BTC", "confirm": "Bevestig", "header": "Verzenden", - "share": "Delen", "view": "Bekijken", "manage_keys": "Beheer sleutels", "how_many_signatures_can_bluewallet_make": "hoeveel handtekeningen kan BlueWallet maken", - "signatures_required_to_spend": "Signatures vereist {number}", + "signatures_required_to_spend": "Handtekeningen vereist {number}", "signatures_we_can_make": "kan {number} aanmaken", "scan_or_import_file": "Scan of importeer bestand", "export_coordination_setup": "Export coördinatie setup", "cosign_this_transaction": "Deze transactie medeondertekenen?", "lets_start": "Laten we beginnen", "create": "Maken", - "native_segwit_title": "Beste oefening", + "native_segwit_title": "Beste werkwijze", "wrapped_segwit_title": "Beste compatibiliteit", "legacy_title": "Legacy", "co_sign_transaction": "Signeer een transactie", @@ -477,33 +560,25 @@ "what_is_vault_description_to_spend": "te besteden en een 3e die u \nals back-up kunt gebruiken.", "what_is_vault_description_to_spend_other": "om uit te geven.", "quorum": "{m} van {n} quorum", - "quorum_header": "Quorum", "of": "van", "wallet_type": "Wallettype", - "invalid_mnemonics": "Deze mnemonic phrase lijkt niet te kloppen", - "invalid_cosigner": "Ongeldige medeondertekenaargegevens", "not_a_multisignature_xpub": "Dit is geen xpub van een multisignature wallet!", - "invalid_cosigner_format": "Onjuiste mede-ondertekenaar: dit is geen mede-ondertekenaar voor het {format} formaat", "create_new_key": "Maak een nieuwe", "scan_or_open_file": "Scan of open bestand", "i_have_mnemonics": "Ik heb een seed voor deze key...", "type_your_mnemonics": "Voeg een seed in om uw bestaande kluissleutel te importeren", - "this_is_cosigners_xpub": "Dit is de XPUB van mede-ondertekenaar, klaar om in een andere wallet te worden geïmporteerd. Het is veilig om het te delen.", "wallet_key_created": "Uw kluissleutel is gemaakt. Neem even de tijd om een ​​veilige back-up van uw mnemonic seed te maken", "are_you_sure_seed_will_be_lost": "Weet u het zeker? Uw mnemonic seed zal verloren gaan als u geen back-up heeft", "forget_this_seed": "Vergeet deze seed en gebruik XPUB", - "view_edit_cosigners": "Bekijk/bewerk mede-ondertekenaars", - "this_cosigner_is_already_imported": "Deze mede-ondertekenaar is al geïmporteerd", "export_signed_psbt": "Ondertekende PSBT exporteren", "input_fp": "Vingerafdruk invoeren", "input_fp_explain": "Overslaan om de standaard te gebruiken (00000000)", "input_path": "Voer het derivation path in", "input_path_explain": "Sla over om de standaard te gebruiken ({default})", - "ms_help": "Help", "ms_help_title": "Hoe Multisig kluizen werken. Tips en trucs", "ms_help_text": "Een wallet met meerdere sleutels, om de veiligheid exponentieel te verhogen of voor gedeelde bewaring.", "ms_help_title1": "Meerdere apparaten worden geadviseerd", - "ms_help_1": "De kluis werkt met andere BlueWallet-apps en PSBT-compatibele wallets, zoals Electrum, Spectre, Coldcard, Cobo-kluis, enz.", + "ms_help_1": "De kluis werkt met andere BlueWallet-apps en PSBT-compatibele wallets, zoals Electrum, Spectre, Coldcard, Cobo Vault, enz.", "ms_help_title2": "Sleutels bewerken", "ms_help_2": "Je kan alle Kluissleutels op dit apparaat opslaan en ze later aanpassen of verwijderen", "ms_help_title3": "Kluis Back-ups", @@ -511,27 +586,52 @@ "ms_help_title4": "Kluizen importeren", "ms_help_4": "Om een ​​Multisig te importeren, gebruikt u uw multisig back-upbestand en de importfunctie. Als u alleen uitgebreide sleutels en seeds heeft, kunt u de afzonderlijke importvelden in het Kluis Toevoegen schema gebruiken.", "ms_help_title5": "Geavanceerde opties", - "ms_help_5": "BlueWallet genereert standaard een 2-van-3 Kluis. Activeer de geavanceerde opties in de Instellingen om een ​​ander quorum te creëren of om het adrestype te wijzigen." + "ms_help_5": "BlueWallet genereert standaard een 2-van-3 Kluis. Activeer de geavanceerde opties in de Instellingen om een ​​ander quorum te creëren of om het adrestype te wijzigen.", + "provide_signature_details": "Gebruik je apparaat en wallet waar de sleutel zich bevindt om deze transactie te ondertekenen", + "provide_signature_details_bluewallet": "Ga in BlueWallet naar het menu van het Verzenden-scherm en selecteer ", + "provide_signature_next_steps": "Scan of importeer ondertekende transactie", + "provide_signature_next_steps_details": "Zodra je wallet de transactie met succes heeft ondertekend, scan je de gegeven QR-code of importeer je het bijbehorende bestand, en controleer vervolgens alle transactiedetails voordat je deze verzendt.", + "share": "Delen...", + "shared_key_detected": "Gedeelde medeondertekenaar", + "shared_key_detected_question": "Er is een medeondertekenaar met je gedeeld, wil je deze importeren?", + "invalid_mnemonics": "Deze mnemonische zin lijkt niet geldig te zijn.", + "invalid_cosigner": "Ongeldige medeondertekenaar-gegevens", + "invalid_cosigner_format": "Onjuiste medeondertekenaar: Dit is geen medeondertekenaar voor {format}-formaat.", + "this_is_cosigners_xpub": "Dit is de xpub van de medeondertekenaar — klaar om in een andere wallet te worden geïmporteerd. Het is veilig om deze te delen.", + "this_is_cosigners_xpub_airdrop": "Als je deelt via AirDrop, moeten de ontvangers zich in het coördinatiescherm bevinden.", + "view_edit_cosigners": "Medeondertekenaars bekijken/bewerken", + "this_cosigner_is_already_imported": "Deze medeondertekenaar is al geïmporteerd.", + "fee": "Fee: {number}", + "quorum_header": "Quorum", + "ms_help": "Help" }, "is_it_my_address": { "title": "Is het mijn adres?", - "owns": "{label} eigen {address}", + "owns": "{label} bezit {address}", "enter_address": "Adres invoeren", "check_address": "Adres checken", "no_wallet_owns_address": "Geen van de beschikbare wallets bezit het opgegeven adres.", - "view_qrcode": "Bekijk QR code" + "view_qrcode": "QR-code bekijken" }, "cc": { "change": "Veranderen", "coins_selected": "Geselecteerde coins ({number})", - "empty": "Deze wallet heeft momenteel geen coins", "freeze": "Bevriezen", "freezeLabel": "Bevriezen", "freezeLabel_un": "Ontvriezen", - "header": "Coin Control", "use_coin": "Gebruik Coin", "use_coins": "Gebruik Coins", - "tip": "Hiermee kunt u coins zien, labelen, bevriezen of selecteren voor verbeterd walletbeheer." + "tip": "Hiermee kunt u coins zien, labelen, bevriezen of selecteren voor verbeterd walletbeheer.", + "selected_summ": "{value} geselecteerd", + "empty": "Deze wallet heeft op dit moment geen coins.", + "sort_asc": "Oplopend", + "sort_desc": "Aflopend", + "sort_height": "Hoogte", + "sort_value": "Waarde", + "sort_by": "Sorteren op", + "header": "Coin Control", + "sort_label": "Label", + "sort_status": "Status" }, "units": { "BTC": "BTC", @@ -549,6 +649,56 @@ "type_change": "Wisselgeld", "type_receive": "Ontvang", "type_used": "Gebruikt", - "transactions": "Transacties" + "transactions": "Transacties", + "copy_private_key": "Privésleutel kopiëren", + "sensitive_private_key": "Waarschuwing: privésleutels zijn extreem gevoelig. Doorgaan?", + "sign_title": "Bericht ondertekenen/verifiëren", + "sign_help": "Hier kun je een cryptografische handtekening maken of verifiëren op basis van een Bitcoin-adres.", + "sign_signature_correct": "Verificatie geslaagd!", + "sign_signature_incorrect": "Verificatie mislukt!" + }, + "autofill_word": { + "title": "Laatste seed-woord", + "enter": "Voer je gedeeltelijke mnemonische zin in", + "generate_word": "Genereer het laatste woord", + "error": "De invoer is geen gedeeltelijke mnemonische zin van 11 of 23 woorden. Probeer het opnieuw." + }, + "lnurl_auth": { + "register_question_part_1": "Wil je een account registreren bij", + "register_question_part_2": "met je Lightning-wallet?", + "register_answer": "Je hebt met succes een account geregistreerd bij {hostname}!", + "login_question_part_1": "Wil je inloggen bij", + "login_question_part_2": "met je Lightning-wallet?", + "login_answer": "Je bent met succes ingelogd bij {hostname}!", + "link_question_part_1": "Wil je je account koppelen bij", + "link_question_part_2": "aan je Lightning-wallet?", + "link_answer": "Je Lightning-wallet is met succes gekoppeld aan je account bij {hostname}!", + "auth_question_part_1": "Wil je geauthenticeerd worden bij", + "auth_question_part_2": "met je Lightning-wallet?", + "auth_answer": "Je bent met succes geauthenticeerd bij {hostname}!", + "could_not_auth": "We konden je niet authenticeren bij {hostname}.", + "authenticate": "Authenticeren" + }, + "bip47": { + "payment_code": "Payment code", + "contacts": "Contacten", + "bip47_explain": "Herbruikbare en deelbare code", + "bip47_explain_subtitle": "BIP47", + "purpose": "Herbruikbare en deelbare code (BIP47)", + "pay_this_contact": "Betaal dit contact", + "rename_contact": "Contact hernoemen", + "copy_payment_code": "Payment code kopiëren", + "hide_contact": "Contact verbergen", + "rename": "Hernoemen", + "provide_name": "Geef een nieuwe naam voor dit contact op", + "add_contact": "Contact toevoegen", + "provide_payment_code": "Geef payment code op", + "invalid_pc": "Ongeldige payment code", + "notification_tx_unconfirmed": "Notificatietransactie is nog niet bevestigd, even wachten", + "failed_create_notif_tx": "Aanmaken van on-chain transactie mislukt", + "onchain_tx_needed": "On-chain transactie nodig", + "notif_tx_sent": "Notificatietransactie verzonden. Wacht tot deze is bevestigd", + "notif_tx": "Notificatietransactie", + "not_found": "Payment code niet gevonden" } } diff --git a/loc/pcm.json b/loc/pcm.json new file mode 100644 index 00000000000..120f97bfc1a --- /dev/null +++ b/loc/pcm.json @@ -0,0 +1,704 @@ +{ + "_": { + "bad_password": "Password wey you enter no correct, run am again", + "cancel": "Comot am", + "continue": "Continue am", + "clipboard": "Clipboard", + "copied": "Don copy!", + "discard_changes": "Throway changes?", + "discard_changes_explain": "You get changes wey you neva save. You sure say make we throway dem comot for this screen?", + "enter_password": "Put password", + "never": "lai lai", + "of": "{number} of {total}", + "ok": "OK", + "enter_url": "Put URL", + "storage_is_encrypted": "Your storage don encrypt. We need password to decrypt am.", + "yes": "Yes", + "no": "No", + "save": "Save am...", + "seed": "seed", + "success": "You correct", + "wallet_key": "Wallet key wey dey", + "close": "Close am", + "change_input_currency": "Change the currency wey you dey put", + "refresh": "Refresh am", + "pick_image": "Pick from library", + "pick_file": "Pick file", + "enter_amount": "Put amount", + "qr_custom_input_button": "Tap 10 times make you enter custom input", + "unlock": "Open am", + "port": "Port", + "ssl_port": "SSL Port", + "suggested": "We suggest" + }, + "azteco": { + "codeIs": "Your voucher code na", + "errorBeforeRefeem": "Before you cash am, you go first add Bitcoin wallet.", + "errorSomething": "Something do anyhow. This voucher still dey valid?", + "redeem": "Cash am to wallet", + "redeemButton": "Cash am", + "success": "You correct", + "successMessage": "Voucher don cash finish! Your money go land for your Bitcoin wallet small time.", + "title": "Cash Azte.co voucher" + }, + "entropy": { + "save": "Save am", + "title": "Entropy", + "undo": "Undo am", + "amountOfEntropy": "{bits} for {limit} bits" + }, + "errors": { + "broadcast": "Broadcast no work.", + "error": "Wahala", + "network": "Network Wahala" + }, + "lnd": { + "errorInvoiceExpired": "Invoice don kalas.", + "expired": "Don kalas", + "expiresIn": "E go kalas for {time} minutes", + "payButton": "Pay am", + "payment": "Payment wey dey", + "placeholder": "Invoice or address abeg", + "potentialFee": "Possible fee: {fee}", + "refill": "Refill am", + "refill_create": "To proceed like this so, abeg create Bitcoin wallet wey you go refill am with", + "refill_external": "Refill am with External Wallet", + "refill_lnd_balance": "Refill your Lightning Wallet balance", + "sameWalletAsInvoiceError": "You no fit pay invoice with the same wallet wey you use create am.", + "title": "Manage Money" + }, + "lndViewInvoice": { + "additional_info": "Extra Info", + "for": "Na for:", + "lightning_invoice": "Lightning Invoice", + "please_pay_between_and": "Abeg pay between {min} and {max}", + "please_pay": "Abeg pay", + "preimage": "Preimage", + "sats": "sats.", + "date_time": "Date and time wey", + "wasnt_paid_and_expired": "You nor quick pay this invoice and e don kalas" + }, + "plausibledeniability": { + "create_fake_storage": "Make we create Encrypted Storage", + "create_password_explanation": "Password for the fake storage no suppose match password for your main storage.", + "help": "Some time, dem fit force you make you talk your password. To make sure your coins dey safe, BlueWallet fit create another encrypted storage with different password. If pressure come, you fit give that password to person. If dem enter am for BlueWallet, e go open one ‘fake’ storage. The thing go look real to that person, but your main storage with your coins go still dey safe.", + "help2": "The new storage go work proper, and you fit keep small money for there so e go look believable.", + "password_should_not_match": "You dey use this password, run another password wey you never use.", + "title": "Plausible Deniability" + }, + "pleasebackup": { + "ask": "You don save your wallet backup phrase? You need this backup phrase to enter your money if you lose this device. Without the backup phrase, your money go lost for life.", + "ask_no": "No, I never save am.", + "ask_yes": "Yes, I don save am.", + "ok": "OK, I don write am down.", + "ok_lnd": "OK, I don save am.", + "text": "Abeg take small time write down this mnemonic phrase for paper.\nNa your backup, and you fit use am recover the wallet.", + "text_lnd": "Abeg save this wallet backup. E go let you restore the wallet if e lost.", + "title": "Your wallet don create." + }, + "receive": { + "details_create": "Create am", + "details_label": "Tell us small story", + "details_setAmount": "Receive am with amount", + "details_share": "Share am...", + "address_not_found": "We no fit generate receiving address.", + "header": "Receive am", + "reset": "Reset am", + "maxSats": "Maximum amount na {max} sats", + "maxSatsFull": "Maximum amount na {max} sats or {currency}", + "minSats": "Minimal amount na {min} sats", + "minSatsFull": "Minimal amount na {min} sats or {currency}", + "qrcode_for_the_address": "QR Code wey be for the address", + "bip47_explanation": "Payment codes na universal address wey no go show your wallet addresses. No be every service go support am." + }, + "send": { + "provided_address_is_invoice": "This address look like say na Lightning invoice. Abeg, go your Lightning wallet make you pay this invoice.", + "broadcastButton": "Broadcast am", + "broadcastError": "Wahala", + "broadcastNone": "Put Transaction Hex", + "broadcastPending": "Pend", + "broadcastSuccess": "You correct", + "confirm_header": "Confam", + "confirm_sendNow": "Push am", + "create_amount": "How much", + "create_broadcast": "Broadcast am", + "create_copy": "Copy make you broadcast am later", + "create_details": "The full gist", + "create_fee": "Fee wey dey", + "create_memo": "Small note", + "create_satoshi_per_vbyte": "Satoshi for one vByte", + "create_this_is_hex": "Na your transaction hex be this—dem don sign am and e ready to broadcast to the network.", + "create_to": "Send go", + "create_tx_size": "Transaction size na", + "create_verify": "Verify for coinb.in", + "details_insert_contact": "Put Contact", + "details_add_rec_add": "Add another person", + "details_add_rec_rem": "Comot Recipient", + "details_add_recc_rem_all_alert_description": "You sure say make we comot all recipients?", + "details_add_rec_rem_all": "Comot All Recipients", + "details_recipients_title": "People wey go receive", + "details_recipient_title": "Person wey go receive #{number} for #{total}", + "please_complete_recipient_title": "Recipient no complete", + "please_complete_recipient_details": "Abeg finish the details of recipient #{number} before you add another recipient.", + "details_address": "Address dey", + "details_address_field_is_not_valid": "The address no correct.", + "details_adv_fee_bump": "Make fee bump dey allowed", + "details_adv_full": "Use the full balance", + "details_adv_full_sure": "You sure say you wan use your wallet full balance for this transaction?", + "details_adv_full_sure_frozen": "You sure say you wan use your wallet full balance for this transaction? Note say frozen coins no dey enter.", + "details_adv_import": "Bring transaction", + "details_adv_import_qr": "Bring transaction (QR)", + "details_amount_field_is_not_valid": "The amount no correct.", + "details_amount_field_is_less_than_minimum_amount_sat": "The amount too small. Abeg enter amount wey pass 500 sats.", + "details_create": "Create Invoice", + "details_error_decode": "We no fit decode this Bitcoin address", + "details_fee_field_is_not_valid": "The fee no correct.", + "details_frozen": "Dem freeze {amount} BTC.", + "details_next": "Next one", + "details_no_signed_tx": "The file wey you select no get transaction wey we fit import.", + "details_note_placeholder": "Note for yourself", + "details_scan": "Scan am", + "details_scan_hint": "Double tap make you scan or import destination", + "details_scan_error": "Scan wahala", + "details_total_exceeds_balance": "The amount you dey try send pass wetin you get for balance", + "details_total_exceeds_balance_frozen": "The amount you dey try send pass wetin you get for balance. Note say frozen coins no dey enter.", + "details_unrecognized_file_format": "We no sabi this file format", + "details_wallet_before_tx": "Before you create transaction, you go first add Bitcoin wallet.", + "dynamic_init": "E dey load", + "dynamic_next": "Next one", + "dynamic_prev": "Wetin dey before", + "dynamic_start": "Begin", + "dynamic_stop": "Stop am", + "fee_10m": "10m", + "fee_1d": "1d", + "fee_3h": "3h", + "fee_custom": "Your own one", + "insert_custom_fee": "Put fee", + "fee_fast": "Sharp", + "fee_medium": "Middle one", + "fee_replace_minvb": "The total fee rate (satoshi per vByte) wey you wan pay suppose pass {min} sat/vByte.", + "fee_satvbyte": "for sat/vByte", + "fee_slow": "Slow slow", + "header": "Send am", + "input_clear": "wipe am", + "input_done": "Don finish", + "input_paste": "Paste am", + "input_total": "Total na:", + "permission_camera_message": "We need permission to use your camera.", + "psbt_sign": "Sign one transaction", + "invalid_psbt": "PSBT no correct.", + "open_settings": "Open Settings", + "permission_storage_denied_message": "BlueWallet no fit save this file. Abeg open your device settings and enable Storage Permission.", + "permission_storage_title": "Storage Access wey we need", + "psbt_clipboard": "Copy go Clipboard", + "psbt_this_is_psbt": "Na Partially Signed Bitcoin Transaction (PSBT) be this. Abeg finish sign am with your hardware wallet.", + "psbt_tx_export": "Export go file", + "no_tx_signing_in_progress": "No transaction signing dey go on now.", + "outdated_rate": "Last time dem update the rate: {date}", + "psbt_tx_open": "Open the transaction wey don sign", + "psbt_tx_scan": "Scan the transaction wey don sign", + "qr_error_no_qrcode": "We no fit find valid QR Code for the image wey you select. Make sure say the image get only QR Code, no text or button.", + "reset_amount": "Reset the amount", + "reset_amount_confirm": "You go like reset the bar?", + "success_done": "Don finish", + "txSaved": "We don save the transaction file ({filePath}).", + "file_saved_at_path": "We don save the file ({filePath}).", + "cant_send_to_silentpayment_adress": "This wallet no fit send to SilentPayment addresses", + "cant_send_to_bip47": "This wallet no fit send to BIP47 payment codes", + "cant_find_bip47_notification": "Add this Payment Code go contacts first", + "problem_with_psbt": "Wahala with PSBT" + }, + "settings": { + "about": "About us", + "about_awesome": "We build am with awesome", + "about_backup": "Always backup your keys!", + "about_free": "BlueWallet na free and open-source project. Bitcoin users craft am.", + "about_license": "MIT License", + "about_release_notes": "Notes for release", + "about_review": "Leave us review", + "performance_score": "Performance score na: {num}", + "run_performance_test": "Test the performance", + "about_selftest": "Run self-test", + "block_explorer_invalid_custom_url": "The URL no correct. Abeg enter valid URL wey start with http:// or https://.", + "about_selftest_electrum_disabled": "Self-testing no dey work with Electrum Offline Mode. Abeg disable offline mode try again.", + "about_selftest_ok": "All internal tests don pass well well. The wallet dey work fine.", + "about_sm_github": "GitHub", + "about_sm_telegram": "Telegram channel wey dey", + "privacy_temporary_screenshots": "Allow Screen Capture", + "privacy_temporary_screenshots_instructions": "Screen capture protection go off small time, so you fit take screenshot and screen recording. The protection go come back on when you close and open BlueWallet again.", + "biometrics": "Biometrics", + "biometrics_no_longer_available": "Your device settings don change and e no match the security settings wey you select for app. Abeg enable biometrics or passcode again, then restart the app to make the changes work.", + "biom_10times": "Na like 10 times you don enter password, You go like reset your storage? This one go comot all your wallets and e go decrypt your storage.", + "biom_conf_identity": "Abeg confirm who you be.", + "biom_no_passcode": "Your device no get passcode or biometrics. Make you proceed, abeg configure passcode or biometric for Settings app.", + "biom_remove_decrypt": "All your wallets go comot and your storage go decrypt. You sure say make we go on?", + "currency": "Currency wey dey", + "currency_source": "Rate dey come from", + "currency_fetch_error": "Error dey as we dey find rate for the currency you select. ", + "default_title": "As you launch", + "donate": "Donate something", + "donate_description": "Helep us make Blue dey free!", + "electrum_connected": "Don Connect", + "electrum_connected_not": "No Connect", + "electrum_error_connect": "We no fit connect to the Electrum server", + "electrum_error_connect_tor": "We no fit connect to the Electrum server. Abeg make sure Orbot app dey connected and try again.", + "lndhub_uri": "E.g., {example}", + "electrum_host": "E.g., {example}", + "electrum_offline_mode": "Offline Mode", + "electrum_offline_description": "If e enabled, your Bitcoin wallets no go try fetch balances or transactions.", + "electrum_port": "Port, usually na {example}", + "use_ssl": "Use SSL", + "electrum_saved": "Your changes don save well well. You fit need to restart BlueWallet make the changes take effect.", + "set_electrum_server_as_default": "Set {server} as default Electrum server?", + "set_lndhub_as_default": "Set {url} as default LNDhub server?", + "electrum_settings_server": "Electrum server wey dey", + "electrum_status": "How e dey", + "electrum_preferred_server": "Server wey you like", + "electrum_preferred_server_description": "Enter the server wey you wan make your wallet use for all Bitcoin work. Once you set am, your wallet go use this server only to check balances, send transactions, and fetch network data. Make sure say you trust this server before you set am.", + "electrum_unable_to_connect": "We nor fit connect to {server}.", + "electrum_history": "History wey dey", + "electrum_reset_to_default": "This go let BlueWallet pick server anyhow from the server list.", + "electrum_reset": "Reset go default", + "electrum_reset_to_default_and_clear_history": "Reset go default and clear history", + "encrypt_decrypt": "Decrypt the storage", + "encrypt_decrypt_q": "You sure say you wan decrypt your storage? This go let person enter your wallets without password.", + "encrypt_enc_and_pass": "Password Don Protect Am", + "encrypt_storage_explanation_headline": "Turn On Storage Encryption", + "encrypt_storage_explanation_description_line1": "Enabling Storage Encryption add extra protection to your app, e make the way your data dey stored for your device more secure. E hard for person to enter your information without permission.", + "encrypt_storage_explanation_description_line2": "But know say, this encryption only protect access to your wallets wey dey for the device keychain. E no put password or extra protection for the wallets themselves.", + "i_understand": "I sabi", + "block_explorer": "Block Explorer", + "block_explorer_preferred": "Use block explorer wey you like", + "block_explorer_error_saving_custom": "Error don happen as we dey save preferred block explorer", + "encrypt_title": "Security wey dey", + "encrypt_tstorage": "Storage wey dey", + "encrypt_use": "Use {type}", + "set_as_preferred": "Set am as the one you like", + "set_as_preferred_electrum": "Setting {host}:{port} as preferred server go disable connecting to suggested server anyhow.", + "encrypted_feature_disabled": "This feature no fit work with encrypted storage enabled.", + "encrypt_use_expl": "We go use {type} to confirm who you be before transaction, unlocking, exporting, or deleting wallet.", + "biometrics_fail": "If {type} no enabled, or e no unlock, you fit use your device passcode instead.", + "general": "General matter", + "general_continuity": "Continuity", + "general_continuity_e": "If e enabled, you go fit view selected wallets, and transactions, with your other Apple iCloud connected devices.", + "groundcontrol_explanation": "GroundControl na free, open-source push notifications server for Bitcoin wallets. You fit install your own GroundControl server and put im URL here so you no go depend on BlueWallet infrastructure. Leave am empty to use GroundControl default server.", + "header": "Settings dey", + "language": "Language wey", + "last_updated": "Last Update", + "language_isRTL": "You need to restart BlueWallet make the language direction take effect.", + "license": "License", + "lightning_error_lndhub_uri": "LNDhub URI no correct", + "lightning_error_lndhub_uri_tor": "LNDhub URI no correct. Abeg make sure Orbot app dey connected and try again.", + "lightning_saved": "Your changes don save well well.", + "lightning_settings": "Lightning Settings", + "lightning_settings_explain": "To connect to your own LND node, abeg install LNDhub and put im URL here for settings. Note say na only wallets wey you create after you save changes go connect to the LNDhub.", + "lndhub_github": "GitHub Repository wey dey", + "network": "Network wey", + "network_broadcast": "Broadcast the transaction", + "network_electrum": "Electrum Server", + "electrum_suggested_description": "When preferred server no dey set, suggested server go select anyhow.", + "not_a_valid_uri": "URI no correct", + "notifications": "Notifications dey", + "open_link_in_explorer": "Open link for explorer", + "password": "Password wey", + "password_explain": "Enter the password wey you go use unlock your storage.", + "plausible_deniability": "Plausible Deniability", + "privacy": "Privacy matter", + "privacy_read_clipboard": "Read the clipboard", + "privacy_system_settings": "System Settings", + "privacy_quickactions": "Wallet shortcuts wey dey", + "privacy_quickactions_explanation": "Touch and hold the BlueWallet app icon make you quick see your wallet balance.", + "privacy_clipboard_explanation": "Provide shortcuts if address or invoice dey for your clipboard.", + "privacy_do_not_track": "Off Analytics", + "privacy_do_not_track_explanation": "Performance and reliability information no go send for analysis.", + "rate": "Rate am", + "push_notifications_explanation": "If you enable notifications, your device token go go to the server, with wallet addresses and transaction IDs for all wallets and transactions wey happen after you enable notifications. We use the device token to send notifications, and the wallet information dey let us notify you about incoming Bitcoin or transaction confirmations.\n\nNa only information from after you enable notifications dey transmit—nothing from before dey collect.\n\nIf you disable notifications, all this information go comot from the server. Also, if you delete wallet from the app, im information go comot from the server.", + "selfTest": "Self-Test", + "save": "Save am", + "saved": "Don Save", + "success_transaction_broadcasted": "Your transaction don broadcast well well!", + "total_balance": "Total balance wey dey", + "total_balance_explanation": "Show the total balance of all your wallets for your home screen widgets.", + "widgets": "Widgets dey", + "tools": "Tools wey dey" + }, + "notifications": { + "would_you_like_to_receive_notifications": "You go like receive notifications when payment dey come?", + "notifications_subtitle": "Payments wey dey come in and transaction confirmations", + "no_and_dont_ask": "No, and no ask me again.", + "permission_denied_message": "You don deny permission to send you notifications. If you wan receive notifications, abeg enable dem for your device settings." + }, + "transactions": { + "cancel_explain": "We go replace this transaction with one wey pay you and get higher fees. This one go cancel the current transaction. Na RBF—Replace by Fee dem dey call am.", + "cancel_no": "This transaction no fit replace.", + "cancel_title": "Comot this transaction (RBF)", + "transaction_loading_error": "Wahala dey load the transaction. Abeg try again later.", + "transaction_not_available": "Transaction no dey", + "confirmations_lowercase": "{confirmations} confirmations don dey", + "expand_note": "Open Note", + "cpfp_create": "Create am", + "cpfp_exp": "We go create another transaction wey spend your unconfirmed transaction. The total fee go pass the original transaction fee, so dem go mine am faster. Na CPFP—Child Pays for Parent dem dey call am.", + "cpfp_no_bump": "This transaction no fit bump.", + "cpfp_title": "Bump the fee (CPFP)", + "details_balance_hide": "Hide the balance", + "details_balance_show": "Show the balance", + "details_copy": "Copy am", + "details_copy_block_explorer_link": "Copy the block explorer link", + "details_copy_note": "Copy the note", + "details_copy_txid": "Copy the Transaction ID", + "details_inputs": "Inputs", + "details_outputs": "Outputs", + "date": "Date wey", + "details_received": "Don Receive", + "details_view_in_browser": "View for Browser", + "details_title": "Transaction wey dey", + "incoming_transaction": "Transaction wey dey come", + "outgoing_transaction": "Transaction wey dey go", + "expired_transaction": "Don kalas Transaction", + "pending_transaction": "Pend Transaction", + "offchain": "Offchain", + "onchain": "Onchain", + "details_to": "Output", + "enable_offline_signing": "This wallet no dey use with offline signing. You go like enable am now?", + "list_conf": "Conf na: {number}", + "pending": "Pend", + "pending_with_amount": "Pend {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: For ~10 minutes", + "eta_3h": "ETA: For ~3 hours", + "eta_1d": "ETA: For ~1 day", + "list_title": "Transactions wey don happen", + "list_title_sent": "Don Send", + "list_title_received": "Don Receive", + "transaction": "Transaction wey", + "open_url_error": "We no fit open the link with the default browser. Abeg change your default browser and try again.", + "rbf_explain": "We go replace this transaction with one wey get higher fee, so dem go mine am faster. Na RBF—Replace by Fee dem dey call am.", + "rbf_title": "Sharp am up (RBF)", + "status_bump": "Sharp am up", + "status_cancel": "Comot transaction", + "transactions_count": "How many transactions", + "txid": "Transaction ID na", + "updating": "Dey update...", + "watchOnlyWarningTitle": "Security wahala", + "watchOnlyWarningDescription": "Watch out for scammers wey dey use “watch-only” wallets to deceive people. These wallets no dey let you control or send money; dem only let you view the balance.", + "custom_fee_warning_title": "Wahala dey", + "custom_fee_warning_description": "Fees below 1 sat/vB dey valid, but dem fit no relay am because of node policies.", + "details_eta_analyzing": "Analyzing...", + "details_sent": "Sent", + "details_section": "Details", + "details_explorer": "explorer", + "details_network_fee": "Network Fee", + "details_to_address": "To", + "details_id": "ID", + "details_note": "Note", + "details_add_note": "add", + "details_advanced": "Advanced", + "details_fee_rate": "Fee rate", + "details_size": "Size", + "details_virtual_size": "Virtual size", + "details_tx_hex": "Tx Hex", + "details_inputs_count": "Inputs ({count})", + "details_outputs_count": "Outputs ({count})" + }, + "wallets": { + "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Simple Bitcoin wallet wey strong well well", + "add_create": "Create am", + "total_balance": "Total balance wey dey", + "add_entropy_reset_title": "Reset the entropy", + "add_entropy_reset_message": "If you change the wallet type, the current entropy go reset. You wan proceed?", + "add_entropy": "Entropy", + "add_entropy_bytes": "{bytes} bytes of entropy don dey", + "add_entropy_generated": "{gen} bytes of entropy wey we don generate", + "add_entropy_provide": "Provide entropy by rolling dice", + "add_entropy_remain": "{gen} bytes of generated entropy. The remaining {rem} bytes go come from the System random number generator.", + "add_import_wallet": "Bring wallet enter", + "add_lightning": "Lightning", + "add_lightning_explain": "For instant transactions wey you go spend", + "add_lndhub": "Connect go your LNDhub", + "add_lndhub_error": "The node address wey you give no be valid LNDhub node.", + "add_lndhub_placeholder": "Your node address abeg", + "add_placeholder": "my first wallet wey I get", + "add_title": "Add Wallet", + "add_wallet_name": "Wetin you go call am", + "add_wallet_type": "Wetin kind", + "add_wallet_seed_length": "How long the seed go be", + "add_wallet_seed_length_12": "12 words wey go", + "add_wallet_seed_length_24": "24 words wey go", + "clipboard_bitcoin": "Bitcoin address dey for your clipboard. You go like use am for transaction?", + "clipboard_lightning": "Lightning invoice dey for your clipboard. You go like use am for transaction?", + "clear_clipboard_on_import": "Clear clipboard when import", + "details_address": "Address wey", + "details_advanced": "Advanced matter", + "details_are_you_sure": "You sure?", + "details_connected_to": "Don Connect to", + "details_del_wb_err": "The balance amount no match this wallet balance. Abeg try again.", + "details_del_wb_q": "This wallet get balance. Before you proceed, know say you no go fit recover the money without this wallet seed phrase. To prevent mistake removal, abeg enter the wallet balance of {balance} satoshis.", + "details_delete": "Delete am", + "details_delete_wallet": "Comot wallet", + "details_derivation_path": "derivation path wey", + "details_display": "Show for Home Screen", + "details_export_backup": "Export/Backup am", + "details_export_history": "Export History go CSV", + "details_master_fingerprint": "Master fingerprint wey", + "details_multisig_type": "multisig", + "details_show_xpub": "Show the wallet XPUB", + "details_show_addresses": "Show the addresses", + "details_title": "Wallet wey", + "wallets": "Wallets wey you get", + "swipe_balance_hide": "Hide am", + "swipe_balance_show": "Show am", + "drag_to_reorder": "Drag make you reorder", + "clear_search": "Clear the search", + "details_type": "Wetin kind", + "details_use_with_hardware_wallet": "Use with Hardware Wallet", + "details_yes_delete": "Yes, delete am", + "enter_bip38_password": "Put password to decrypt", + "export_title": "Export the wallet", + "import_do_import": "Import am", + "import_passphrase": "Passphrase wey", + "import_passphrase_title": "Passphrase wey dey", + "import_passphrase_message": "Enter passphrase if you don use any", + "import_error": "Import no work. Abeg make sure say the data wey you provide dey valid.", + "import_explanation": "Abeg enter your seed words, public key, WIF, or anything wey you get. BlueWallet go try im best to guess the correct format and import your wallet.", + "import_imported": "Don Import", + "import_scan_qr": "Scan or import file", + "import_success": "Your wallet don import well well.", + "import_success_watchonly": "Your wallet don import well well. WARNING: Na watch-only wallet be this, you NO fit spend from am.", + "import_search_accounts": "Find accounts", + "import_title": "Bring am", + "learn_more": "Sabi more", + "import_discovery_title": "Discovery wey", + "import_discovery_subtitle": "Choose discovered wallet", + "import_discovery_derivation": "Use your own derivation path", + "import_discovery_no_wallets": "We no find any wallet.", + "import_discovery_offline": "BlueWallet dey for offline mode now. For this mode, e no fit verify say wallet dey exist, so you go need select the correct one by yourself", + "import_derivation_found": "Don Find", + "import_derivation_found_not": "No Find", + "import_derivation_loading": "Dey load...", + "import_derivation_subtitle": "Enter custom derivation path, and we go try find your wallet.", + "import_derivation_title": "Derivation path wey", + "import_derivation_unknown": "We no sabi", + "import_wrong_path": "Derivation path no correct", + "list_create_a_button": "Add am now now", + "list_create_a_wallet": "Add wallet", + "list_create_a_wallet_text": "E free, and you fit create \nas many as you like.", + "list_empty_txs1": "Your transactions go show here.", + "list_empty_txs1_lightning": "You suppose use Lightning wallet for your daily transactions. The fees too cheap, and the speed sharp well well.", + "list_empty_txs2": "Start with your wallet abeg.", + "list_empty_txs2_lightning": "\nTo start use am, tap on Manage Funds and topup your balance.", + "list_latest_transaction": "Last Transaction", + "list_long_choose": "Pick Photo", + "paste_from_clipboard": "Paste am", + "import_file": "Bring file enter", + "list_long_scan": "Scan the QR code", + "list_title": "Wallets wey dey", + "list_tryagain": "Try am again", + "no_ln_wallet_error": "Before you pay Lightning invoice, you go first add Lightning wallet.", + "looks_like_bip38": "This one look like password-protected private key (BIP38).", + "manage_title": "Manage your wallets", + "no_results_found": "No result.", + "please_continue_scanning": "Abeg continue scanning.", + "select_no_bitcoin": "No Bitcoin wallet dey available now.", + "select_no_bitcoin_exp": "You need Bitcoin wallet to refill Lightning wallets. Abeg create or import one.", + "select_wallet": "Pick Wallet", + "pull_to_refresh": "Pull am to refresh", + "warning_do_not_disclose": "No share the information below for anybody", + "scan_import": "Scan this QR code to import your wallet for another app.", + "write_down_header": "Create manual backup", + "write_down": "Write down and store these words well well. Use dem to restore your wallet later.", + "wallet_type_this": "This wallet type na {type}.", + "share_number": "Share {number}", + "copy_ln_url": "Copy and store this URL well well so you fit restore your wallet later.", + "copy_ln_public": "Copy and store this information well well so you fit restore your wallet later.", + "add_ln_wallet_first": "You go first add Lightning wallet.", + "identity_pubkey": "Identity pubkey wey", + "xpub_title": "Wallet XPUB wey dey", + "manage_wallets_search_placeholder": "Find wallets, addresses, transactions and memos", + "more_info": "More gist", + "details_delete_wallet_error_message": "Wahala dey confirm say we don comot this wallet from notifications—e fit be network issue or poor connection. If you continue, you fit still dey receive notifications for transactions wey concern this wallet, even after dem delete am.", + "details_delete_anyway": "Delete am anyhow" + }, + "total_balance_view": { + "display_in_bitcoin": "Show for Bitcoin", + "hide": "Hide am", + "display_in_sats": "Show for sats", + "display_in_fiat": "Show for {currency}", + "title": "Total balance wey dey", + "explanation": "View the total balance of all your wallets for the overview screen." + }, + "multisig": { + "multisig_vault": "Multisig safe", + "default_label": "Multisig safe", + "multisig_vault_explain": "Best security for plenty money", + "provide_signature": "Sign am", + "provide_signature_details": "Use your device and wallet where the key dey to sign this transaction", + "provide_signature_details_bluewallet": "For BlueWallet, go the Send screen menu and select ", + "provide_signature_next_steps": "Scan or bring signed transaction enter", + "provide_signature_next_steps_details": "Once your wallet don sign the transaction, scan the QR code dem give or import the file, then check all the transaction details well well before broadcast.", + "vault_key": "Safe Key {number}", + "required_keys_out_of_total": "Keys wey we need out of total", + "fee": "Fee na: {number}", + "fee_btc": "{number} BTC", + "confirm": "Confam", + "header": "Send am", + "share": "Share am...", + "view": "View am", + "shared_key_detected": "Co-signer wey dem dey share", + "shared_key_detected_question": "Person share co-signer with you, you wan import am?", + "manage_keys": "Manage the keys", + "how_many_signatures_can_bluewallet_make": "how many signatures BlueWallet fit make", + "signatures_required_to_spend": "Signatures wey we need {number}", + "signatures_we_can_make": "fit make {number}", + "scan_or_import_file": "Scan am or bring file", + "export_coordination_setup": "Export the coordination setup", + "cosign_this_transaction": "You go co-sign this transaction?", + "lets_start": "Make we start", + "create": "Create am", + "native_segwit_title": "The way wey good pass", + "wrapped_segwit_title": "Best wey go fit work everywhere", + "legacy_title": "Legacy", + "co_sign_transaction": "Sign one transaction", + "what_is_vault": "Safe na", + "what_is_vault_numberOfWallets": " {m}-of-{n} multisig ", + "what_is_vault_wallet": "wallet wey.", + "vault_advanced_customize": "Safe Settings", + "needs": "E need", + "what_is_vault_description_number_of_vault_keys": " {m} safe keys ", + "what_is_vault_description_to_spend": "to spend, and one more wey you fit use as backup.", + "what_is_vault_description_to_spend_other": "make you spend.", + "quorum": "{m} for {n} quorum", + "quorum_header": "Quorum wey", + "of": "for", + "wallet_type": "Wetin kind wallet", + "invalid_mnemonics": "This mnemonic phrase no look valid.", + "invalid_cosigner": "Co-signer data no correct", + "not_a_multisignature_xpub": "This no be XPUB from multisignature wallet!", + "invalid_cosigner_format": "Co-signer no correct: This no be co-signer for {format} format.", + "create_new_key": "Create new one", + "scan_or_open_file": "Scan am or open file", + "i_have_mnemonics": "I get seed for this key.", + "type_your_mnemonics": "Insert seed to import your existing Safe key.", + "this_is_cosigners_xpub": "Na the co-signer XPUB be this—e ready to import to another wallet. E safe to share am.", + "this_is_cosigners_xpub_airdrop": "If you share via AirDrop, the receivers must dey for the coordination screen.", + "wallet_key_created": "Your Safe key don create. Take small time backup your mnemonic seed safely.", + "are_you_sure_seed_will_be_lost": "You sure? Your mnemonic seed go lost if you no get backup.", + "forget_this_seed": "Forget this seed and use XPUB instead.", + "view_edit_cosigners": "See/Edit co-signers", + "this_cosigner_is_already_imported": "Dem don import this co-signer already.", + "export_signed_psbt": "Export the signed PSBT", + "input_fp": "Put fingerprint", + "input_fp_explain": "Skip make you use the default one (00000000)", + "input_path": "Put Derivation Path", + "input_path_explain": "Skip make you use the default one ({default})", + "ms_help": "Helep dey", + "ms_help_title": "How Multisig Safes Work: Tips and Tricks", + "ms_help_text": "Wallet with plenty keys, for better security or shared custody", + "ms_help_title1": "Plenty devices dey advised.", + "ms_help_1": "The Safe go work with other BlueWallet apps and PSBT compatible wallets, like Electrum, Specter, Coldcard, Cobo Vault, etc.", + "ms_help_title2": "Editing the keys", + "ms_help_2": "You fit create all Safe keys for this device and comot or edit dem later. To get all keys for the same device get the same security as regular Bitcoin wallet.", + "ms_help_title3": "Safe Backups", + "ms_help_3": "For the wallet options, you go find your Safe backup and watch-only backup. This backup be like map to your wallet. E important for wallet recovery if you lose one of your seeds.", + "ms_help_title4": "Importing Safes", + "ms_help_4": "To import multisig, use your backup file and the Import feature. If na only seeds and XPUBs you get, you fit use the individual Import button when you dey create Safe keys.", + "ms_help_title5": "Advanced Mode", + "ms_help_5": "By default, BlueWallet go generate 2-of-3 Safe. To create different quorum or change the address type, activate Advanced Mode for Settings." + }, + "is_it_my_address": { + "title": "Na my address?", + "owns": "{label} get {address}", + "enter_address": "Put address", + "check_address": "Check the address", + "no_wallet_owns_address": "No wallet wey dey available get the address.", + "view_qrcode": "See the QR Code" + }, + "autofill_word": { + "title": "Last seed word", + "enter": "Enter your part mnemonic phrase", + "generate_word": "Generate the last word", + "error": "The input no be 11- or 23-word partial mnemonic. Abeg try again." + }, + "cc": { + "change": "Change wey remain", + "coins_selected": "Coins Wey You Select ({number})", + "selected_summ": "You don pick {value}", + "empty": "This wallet no get any coin for now.", + "freeze": "Freeze am", + "freezeLabel": "Freeze am", + "freezeLabel_un": "Unfreeze am", + "header": "coin control", + "use_coin": "Use the coin", + "use_coins": "Use the coins", + "tip": "This feature dey let you see, label, freeze or select coins for better wallet management. You fit select plenty coins by tap on the colored circles.", + "sort_asc": "From small to big", + "sort_desc": "From big to small", + "sort_height": "Height wey", + "sort_value": "Value wey", + "sort_label": "Label wey", + "sort_status": "How e dey", + "sort_by": "Sort am by" + }, + "units": { + "BTC": "BTC", + "MAX": "All of am", + "sat_vbyte": "sat/vByte", + "sats": "sats" + }, + "addresses": { + "copy_private_key": "Copy the private key", + "sensitive_private_key": "Warning: private keys too sensitive. Continue?", + "sign_title": "Sign/Verify the message", + "sign_help": "For here, you fit create or verify cryptographic signature with Bitcoin address.", + "sign_sign": "Sign am", + "sign_verify": "Verify am", + "sign_signature_correct": "Verification work!", + "sign_signature_incorrect": "Verification no work!", + "sign_placeholder_address": "Address abeg", + "sign_placeholder_message": "Message abeg", + "sign_placeholder_signature": "Signature abeg", + "addresses_title": "Addresses wey dey", + "type_change": "Change wey remain", + "type_receive": "Receive am", + "type_used": "Don Use", + "transactions": "Transactions wey" + }, + "lnurl_auth": { + "register_question_part_1": "You go like register account for", + "register_question_part_2": "with your Lightning wallet?", + "register_answer": "You don register account for {hostname} well well!", + "login_question_part_1": "You go like log in for", + "login_question_part_2": "with your Lightning wallet?", + "login_answer": "You don log in for {hostname} well well!", + "link_question_part_1": "You go like link your account for", + "link_question_part_2": "to your Lightning wallet?", + "link_answer": "Your Lightning wallet don link to your account for {hostname} well well!", + "auth_question_part_1": "You go like make dem authenticate you for", + "auth_question_part_2": "with your Lightning wallet?", + "auth_answer": "You don authenticate for {hostname} well well!", + "could_not_auth": "We no fit authenticate you for {hostname}.", + "authenticate": "Authenticate am" + }, + "bip47": { + "payment_code": "Payment code wey", + "contacts": "Contacts wey", + "bip47_explain": "Code wey you fit reuse and share", + "bip47_explain_subtitle": "BIP47", + "purpose": "Code wey you fit reuse and share (BIP47)", + "pay_this_contact": "Pay this contact", + "rename_contact": "Rename the contact", + "copy_payment_code": "Copy the payment code", + "hide_contact": "Hide the contact", + "rename": "Rename am", + "provide_name": "Give new name for this contact", + "add_contact": "Add Contact", + "provide_payment_code": "Give Payment Code", + "invalid_pc": "Payment Code no correct", + "notification_tx_unconfirmed": "Notification transaction never confam yet, abeg wait", + "failed_create_notif_tx": "No fit create on-chain transaction", + "onchain_tx_needed": "On-chain transaction dey needed", + "notif_tx_sent": "Don send notification transaction. Abeg wait make e confam", + "notif_tx": "Notification transaction wey", + "not_found": "Payment code no dey" + } +} diff --git a/loc/pl.json b/loc/pl.json index a33a0e25b31..47be8535864 100644 --- a/loc/pl.json +++ b/loc/pl.json @@ -4,46 +4,48 @@ "cancel": "Anuluj", "continue": "Kontynuuj", "clipboard": "Schowek", + "discard_changes": "Odrzucić zmiany?", + "discard_changes_explain": "Masz niezapisane zmiany. Czy na pewno chcesz je porzucić i opuścić ten ekran?", "enter_password": "Wprowadź hasło", "never": "Nigdy", - "disabled": "Wyłączone", "of": "{number} z {total}", "ok": "OK", + "enter_url": "Wprowadź URL", "storage_is_encrypted": "Twoje dane są zaszyfrowane. Hasło jest wymagane do ich rozszyfrowania", "yes": "Tak", "no": "Nie", - "save": "Zapisz", - "seed": "Ziarno", + "save": "Zapisz...", + "seed": "Seed", "success": "Sukces", "wallet_key": "Klucz Portfela", - "invalid_animated_qr_code_fragment": "Nieprawidłowy fragment animowanego kodu QR, spróbuj ponownie", - "file_saved": "Plik {filePath} został zapisany w lokalizacji {destination}", - "downloads_folder": "Folder Pobrane", "close": "Zamknij", "change_input_currency": "Zmień walutę", "refresh": "Odśwież", - "more": "Więcej", - "pick_image": "Wybierz obrazek z biblioteki", + "pick_image": "Wybierz z biblioteki", "pick_file": "Wybierz plik", "enter_amount": "Wprowadź kwotę", - "qr_custom_input_button": "Tapnij 10 razy aby wprowadzić własne dane" - }, - "alert": { - "default": "Powiadomienie" + "qr_custom_input_button": "Stuknij 10 razy, aby wprowadzić niestandardowe dane", + "unlock": "Odblokuj", + "ssl_port": "Port SSL", + "port": "Port", + "suggested": "Sugerowane", + "copied": "Skopiowano!" }, "azteco": { - "codeIs": "Twój kod vouchera to", + "codeIs": "Kod twojego vouchera to", "errorBeforeRefeem": "Zanim wykorzystasz kod rabatowy, musisz dodać najpierw portfel Bitcoinowy", "errorSomething": "Coś poszło nie tak. Czy kod vouchera jest ciągle ważny?", - "redeem": "Odbirze do portfela", + "redeem": "Odbierz do portfela", "redeemButton": "Odbierz", "success": "Sukces", + "successMessage": "Kupon zrealizowany pomyślnie! Twoje środki powinny wkrótce trafić do twojego portfela.", "title": "Odbierz voucher Azte.co" }, "entropy": { "save": "Zapisz", "title": "Entropia", - "undo": "Cofnij" + "undo": "Cofnij", + "amountOfEntropy": "{bits} z {limit} bitów" }, "errors": { "broadcast": "Rozgłoszenie nie powiodło się", @@ -51,122 +53,113 @@ "network": "Błąd sieciowy" }, "lnd": { - "active": "Aktywny", - "inactive": "Nieaktywny", - "channels": "Kanały", - "no_channels": "Brak kanałów", - "claim_balance": "Zażądaj pozostałej kwoty {balance}", - "close_channel": "Zamknij kanał", - "new_channel": "Nowy kanał", - "errorInvoiceExpired": "Faktura utraciła ważność", - "force_close_channel": "Wymusić zamknięcie kanału?", + "errorInvoiceExpired": "Faktura wygasła.", "expired": "Przeterminowana", - "node_alias": "Alias węzła", "expiresIn": "Traci ważność w ciągu {time} minut", "payButton": "Zapłać", + "payment": "Płatność", "placeholder": "Faktura lub adres", - "open_channel": "Otwórz kanał", - "funding_amount_placeholder": "Kwota fundująca, np. 0.001", - "opening_channnel_for_from": "Otwieram kanał dla portfela {forWalletLabel} fundując z {fromWalletLabel}", - "are_you_sure_open_channel": "Na pewno otworzyć ten kanał?", "potentialFee": "Potencjalna opłata transakcyjna: {fee}", - "remote_host": "Zdalny host", "refill": "Doładuj", - "reconnect_peer": "Połącz ponownie", "refill_create": "Aby kontynuować, utwórz portfel Bitcoinowy który potem doładujesz.", "refill_external": "Doładowanie z zewnętrznego portfela", "refill_lnd_balance": "Doładowanie stanu portfela Lightning", - "sameWalletAsInvoiceError": "Nie możesz zapłacić faktury tym samym portfelem, który ją utworzył.", - "title": "Zarządzaj środkami", - "can_send": "Może wysłać", - "can_receive": "Może otrzymać", - "view_logs": "Pokaż dziennik zdarzeń" + "sameWalletAsInvoiceError": "Nie możesz zapłacić faktury z tego samego portfela, w którym ją utworzyłeś.", + "title": "Zarządzaj środkami" }, "lndViewInvoice": { "additional_info": "Dodatkowe informacje", "for": "Do:", "lightning_invoice": "Faktura Lightning", - "open_direct_channel": "Otwórz kanał bezpośredni z tym nodem:", "please_pay_between_and": "Proszę zapłać między {min} a {max}", "please_pay": "Proszę zapłać", - "preimage": "Faktura wstępna (Preimage)", + "preimage": "Obraz pierwotny", "sats": "satoshi.", + "date_time": "Data i czas", "wasnt_paid_and_expired": "Ta faktura nie została opłacona i przeterminowała się." }, "plausibledeniability": { - "create_fake_storage": "Utwórz szyfrowany schowek", - "create_password": "Utwórz hasło", - "create_password_explanation": "Hasło portfela widmo, powinno różnić się od głównego hasła.", - "help": "W pewnych okolicznościach możesz być zmuszony do podania hasła. Aby chronić swoje środki w portfelu głównym, BlueWallet może stworzyć dodatkową szyfrowaną przestrzeń z innym hasłem. Będąc do tego zmuszonym przez trzecią stronę, możesz je ujawnić. Po jego wpisaniu BlueWallet odblokuje nowy portfel \"widmo\". Będzie on wyglądał na prawdziwy i pozwoli nie ujawniać zawartości głównego.", - "help2": "Nowa przestrzeń będzie w pełni funkcjonalna i możesz przechowywać w niej minimalne ilości, aby wyglądało to wiarygodnie.", + "create_fake_storage": "Utwórz zaszyfrowany magazyn danych", + "create_password_explanation": "Hasło fałszywego magazynu danych, powinno różnić się od głównego.", + "help": "W pewnych okolicznościach możesz być zmuszony do podania hasła. Aby chronić swoje środki w portfelu głównym, BlueWallet może stworzyć dodatkowy, zaszyfrowany magazyn danych z innym hasłem. W przypadku nacisku ze strony trzeciej możesz ujawnić to alternatywne hasło. Po jego wpisaniu BlueWallet odblokuje nowy, fałszywy portfel. Będzie on wyglądał na prawdziwy i pozwoli ukryć zawartość portfela głównego.", + "help2": "Nowy magazyn danych będzie w pełni funkcjonalny i możesz przechowywać w niej minimalne ilości, aby wyglądało to wiarygodnie.", "password_should_not_match": "Hasło jest aktualnie w użyciu. Spróbuj z innym hasłem.", - "passwords_do_not_match": "Hasła do siebie nie pasują, spróbuj ponownie.", - "retype_password": "Wpisz ponownie hasło", - "success": "Sukces", "title": "Wiarygodna zaprzeczalność" }, "pleasebackup": { - "ask": "Czy zapisałeś już słowa klucze? Są one niezbędne, by dysponować środkami, w przypadku zgubienia tego urządzenia. Bez słów kluczy, twoje środki przepadną na zawsze,", - "ask_no": "Nie mam", - "ask_yes": "Mam", - "ok": "OK, zapisałem", - "ok_lnd": "OK, zapisałem ", - "text": "Poświęć chwilę by zapisać tę frazę mnemoniczną na kartce papieru\nTo Twoja kopia zapasowa, której możesz użyć później by odtworzyć portfel.", + "ask": "Czy zapisałeś już słowa klucze? Są one niezbędne, by dysponować środkami, w przypadku zgubienia tego urządzenia. Bez słów kluczy, twoje środki przepadną na zawsze.", + "ask_no": "Nie, nie mam.", + "ask_yes": "Tak, mam.", + "ok": "OK, zapisałem.", + "ok_lnd": "OK, zachowałem.", + "text": "Poświęć chwilę by zapisać tę frazę mnemoniczną na kartce papieru\nTo twoja kopia zapasowa, której możesz użyć później by odtworzyć portfel.", "text_lnd": "Zachowaj tę kopię zapasową portfela. Umożliwi Ci odtworzenie portfela w przypadku utraty.", - "title": "Twój portfel został utworzony" + "title": "Twój portfel został utworzony." }, "receive": { "details_create": "Stwórz", "details_label": "Opis", "details_setAmount": "Otrzymaj kwotę", - "details_share": "Udostępnij", + "details_share": "Udostępnij...", + "address_not_found": "Nie można wygenerować adresu do odbioru.", "header": "Otrzymaj", "maxSats": "Kwota maksymalna to {max} satoshi", "maxSatsFull": "Maksymalna kwota to {max} satoshi lub {currency}", "minSats": "Kwota minimalna to {min} satoshi", - "minSatsFull": "Kwota minimalna to {min} satoshi lub {currency}" + "minSatsFull": "Kwota minimalna to {min} satoshi lub {currency}", + "qrcode_for_the_address": "Kod QR dla adresu", + "bip47_explanation": "Kody płatności to uniwersalne adresy, które chronią prywatność Twojego portfela. Nie wszystkie usługi je obsługują.", + "reset": "Resetuj" }, "send": { "provided_address_is_invoice": "Ten adres wygląda na fakturę Lightning. Przejdź do swojego portfela Lightning aby ją opłacić.", "broadcastButton": "Rozgłoś", "broadcastError": "Błąd", - "broadcastNone": "Wprowadź transakcję w postaci szestnastkowej", + "broadcastNone": "Wprowadź transakcję w postaci szesnastkowej", "broadcastPending": "Oczekująca", "broadcastSuccess": "Sukces", "confirm_header": "Potwierdź", "confirm_sendNow": "Wyślij teraz", "create_amount": "Kwota", "create_broadcast": "Rozgłoś", - "create_copy": "Skopiuj i rozgłoś później", + "create_copy": "Kopiuj i rozgłoś później", "create_details": "Szczegóły", "create_fee": "Opłata", "create_memo": "Notatka", - "create_satoshi_per_vbyte": "Satoshi za vBajt", - "create_this_is_hex": "To jest Twoja transakcja w postaci szestnastkowej, podpisana i gotowa żeby rozgłosić ją w sieci", + "create_satoshi_per_vbyte": "Satoshi za vByte", + "create_this_is_hex": "To jest twoja transakcja w postaci szesnastkowej, podpisana i gotowa żeby rozgłosić ją w sieci", "create_to": "Do", "create_tx_size": "Rozmiar transakcji", "create_verify": "Zweryfikuj na coinb.in", - "details_add_rec_add": "Dodaj Adresata", - "details_add_rec_rem": "Usuń Adresata", + "details_insert_contact": "Wstaw kontakt", + "details_add_rec_add": "Dodaj odbiorcę", + "details_add_rec_rem": "Usuń odbiorcę", + "details_add_recc_rem_all_alert_description": "Na pewno usunąć wszystkich odbiorców?", + "details_add_rec_rem_all": "Usuń wszystkich odbiorców", + "details_recipients_title": "Odbiorcy", + "details_recipient_title": "Odbiorca #{number} z #{total}", + "please_complete_recipient_title": "Niekompletny odbiorca", + "please_complete_recipient_details": "Uzupełnij dane odbiorcy #{number} przed dodaniem nowego odbiorcy.", "details_address": "Adres", - "details_address_field_is_not_valid": "Adres niepoprawny", - "details_adv_fee_bump": "Pozwól na zwiększanie opłaty", + "details_address_field_is_not_valid": "Adres jest nieprawidłowy.", + "details_adv_fee_bump": "Zezwól na zwiększanie opłat", "details_adv_full": "Użyj wszystkich środków", - "details_adv_full_sure": "Czy jesteś pewien/-a, że chcesz użyć wszystkich środków z Twojego portfela w tej transakcji? ", + "details_adv_full_sure": "Czy jesteś pewien/-a, że chcesz użyć wszystkich środków z twojego portfela w tej transakcji? ", "details_adv_full_sure_frozen": "Na pewno wydać całe saldo w tej transakcji? Pamiętaj, że zamrożone monety są wyłączone z użytku.", "details_adv_import": "Importuj transakcję", "details_adv_import_qr": "Importuj transakcję (QR)", - "details_amount_field_is_not_valid": "Kwota w polu jest niepoprawna", + "details_amount_field_is_not_valid": "Kwota jest nieprawidłowa.", "details_amount_field_is_less_than_minimum_amount_sat": "Podana ilość jest za mała. Proszę podaj ilość większą niż 500 sat.", "details_create": "Stwórz fakturę", "details_error_decode": "Nie można zdekodować adresu Bitcoin", - "details_fee_field_is_not_valid": "Błędnie wypełnione pole z opłatą", - "details_frozen": "{amount} BTC jest zamrożona", + "details_fee_field_is_not_valid": "Opłata jest nieprawidłowa.", + "details_frozen": "{amount} BTC zamrożono.", "details_next": "Dalej", "details_no_signed_tx": "Wskazany plik nie zawiera transakcji, która może zostać zaimportowana.", "details_note_placeholder": "Własny opis transakcji", "details_scan": "Skanuj", "details_scan_hint": "Kliknij dwukrotnie, aby zeskanować lub zaimportować miejsce docelowe", + "details_scan_error": "Błąd skanowania", "details_total_exceeds_balance": "Wysłanie tej ilości przekracza dostępne saldo.", "details_total_exceeds_balance_frozen": "Wysyłana kwota przekracza dostępne saldo. Zwróć uwagę, że zamrożone monety są wyłączone.", "details_unrecognized_file_format": "Format nierozpoznany", @@ -180,11 +173,12 @@ "fee_1d": "1 dzień", "fee_3h": "3 godz.", "fee_custom": "Niestandardowe", - "fee_fast": "Szybkie", - "fee_medium": "Średnie", - "fee_replace_minvb": "Opłata całkowita (satoshi za vByte), którą chcesz ponieść powinna być wyższa niż {min} satoshi/vBajt", - "fee_satvbyte": "w sat/vBajt", - "fee_slow": "Wolne", + "insert_custom_fee": "Wprowadź opłatę", + "fee_fast": "Szybko", + "fee_medium": "Średnio", + "fee_replace_minvb": "Całkowita stawka opłaty (satoshi za vByte), którą chcesz zapłacić, powinna być wyższa niż {min} sat/vByte.", + "fee_satvbyte": "w sat/vByte", + "fee_slow": "Wolno", "header": "Wyślij", "input_clear": "Wyczyść", "input_done": "Gotowe", @@ -192,23 +186,26 @@ "input_total": "Łącznie:", "permission_camera_message": "Potrzebujemy twojej zgody na wykorzystanie kamery", "psbt_sign": "Podpisz transakcję", + "invalid_psbt": "Podano nieprawidłową PSBT.", "open_settings": "Otwórz ustawienia", - "permission_storage_later": "Zapytaj mnie później", - "permission_storage_message": "BlueWallet potrzebuje twojej zgody żeby zachować ten plik.", "permission_storage_denied_message": "BlueWallet nie jest w stanie zapisać tego pliku. Otwórz proszę ustawienia swojego urządzenia i włącz uprawnienia do Pamięci Masowej.", "permission_storage_title": "Dostęp do Pamięci Masowej", - "psbt_clipboard": "Skopiuj do schowka", + "psbt_clipboard": "Kopiuj do schowka", "psbt_this_is_psbt": "To jest częściowo podpisana transakcja Bitcoin (PSBT). Podpisz ją w swoim portfelu sprzętowym.", "psbt_tx_export": "Eksportuj do pliku.", "no_tx_signing_in_progress": "Żadna transakcja nie jest obecnie podpisywana.", "outdated_rate": "Ostatnia aktualizacja kursu: {date}", "psbt_tx_open": "Otwórz podpisaną transakcję", "psbt_tx_scan": "Skanuj Podpisane Transakcje", - "qr_error_no_qrcode": "Nie znaleziono kodu QR w wybranym obrazku. Upewnij się, że obrazek zawiera tylko kod QR i nie zawiera dodatkowej treści takiej jak tekst czy przyciski.", + "qr_error_no_qrcode": "Nie udało nam się znaleźć prawidłowego kodu QR w wybranym obrazie. Upewnij się, że obraz zawiera tylko kod QR i nie ma dodatkowych elementów, takich jak tekst lub przyciski.", "reset_amount": "Resetuj ilość", "reset_amount_confirm": "Czy chcesz zresetować ilość?", "success_done": "Zrobione", - "txSaved": "Ścieżka pliku ({filePath}) została zapisana w twoim folderze Pobrane.", + "txSaved": "Plik transakcji ({filePath}) został zapisany.", + "file_saved_at_path": "Plik ({filePath}) został zapisany.", + "cant_send_to_silentpayment_adress": "Ten portfel nie może wysyłać na adresy SilentPayment", + "cant_send_to_bip47": "Ten portfel nie może wysyłać na kody płatności BIP47", + "cant_find_bip47_notification": "Najpierw dodaj ten kod płatności do kontaktów", "problem_with_psbt": "Problem z PSBT" }, "settings": { @@ -222,62 +219,66 @@ "performance_score": "Wynik wydajności: {num}", "run_performance_test": "Test wydajności", "about_selftest": "Wykonaj autotest", + "block_explorer_invalid_custom_url": "Podany adres URL jest nieprawidłowy. Wprowadź poprawny adres zaczynający się od http:// lub https://.", "about_selftest_electrum_disabled": "Autotest nie jest dostępny z Electrum w trybie Offline. Wyłącz tryb offline i spróbuj ponownie.", "about_selftest_ok": "Wszystkie testy wewnętrzne przebiegły pomyślnie. Portfel działa dobrze.", "about_sm_github": "GitHub", - "about_sm_discord": "Serwer Discord", "about_sm_telegram": "Chat na Telegramie", - "about_sm_twitter": "Obserwuj nas na Twitterze", - "advanced_options": "Opcje Zaawansowane", + "privacy_temporary_screenshots": "Zezwól na przechwytywanie ekranu", + "privacy_temporary_screenshots_instructions": "Ochrona przed przechwytywaniem ekranu zostanie tymczasowo wyłączona, umożliwiając wykonywanie zrzutów ekranu i nagrań ekranu. Ochrona automatycznie włączy się ponownie po zamknięciu i ponownym uruchomieniu BlueWallet.", "biometrics": "Biometria", + "biometrics_no_longer_available": "Ustawienia twojego urządzenia zostały zmienione i nie są już zgodne z wybranymi ustawieniami bezpieczeństwa w aplikacji. Proszę ponownie włączyć biometrię lub kod dostępu, a następnie uruchomić ponownie aplikację, aby zastosować te zmiany.", "biom_10times": "Próbowałeś podać hasło 10 razy. Czy chcesz zresetować magazyn danych? To usunie wszystkie portfele i odszyfruje dane.", "biom_conf_identity": "Proszę potwierdź swoją tożsamość", - "biom_no_passcode": "Twoje urządzenie nie ma hasła. Aby przejść dalej, skonfiguruj hasło w Ustawieniach aplikacji.", - "biom_remove_decrypt": "Wszystkie Twoje portfele zostaną skasowane a magazyn danych odszyfrowany. Czy jesteś pewien?", + "biom_no_passcode": "Twoje urządzenie nie ma włączonego kodu dostępu ani biometrii. Aby kontynuować, skonfiguruj kod dostępu lub biometrię w Ustawieniach aplikacji.", + "biom_remove_decrypt": "Wszystkie twoje portfele zostaną skasowane a magazyn danych odszyfrowany. Czy jesteś pewien?", "currency": "Waluta", - "currency_source": "Cena jest pobierana z", + "currency_source": "Kurs pobierany z", "currency_fetch_error": "Wystąpił błąd podczas pobierania kursu dla wybranej waluty.", - "default_desc": "Gdy jest wyłączone, BlueWallet natychmiast otworzy wybrany domyślny portfel.", - "default_info": "Domyślny portfel", "default_title": "Po uruchomieniu", - "default_wallets": "Wyświetl wszystkie portfele", + "donate": "Wesprzyj darowizną", + "donate_description": "Pomóż nam utrzymać Blue bez opłat!", "electrum_connected": "Połączony", "electrum_connected_not": "Nie Podłączony", - "electrum_error_connect": "Nie można się połączyć z wybranym serwerem Electrum", + "electrum_error_connect": "Nie można połączyć się z podanym serwerem Electrum.", + "electrum_error_connect_tor": "Nie można połączyć się z podanym serwerem Electrum. Upewnij się, że aplikacja Orbot jest połączona i spróbuj ponownie.", "lndhub_uri": "np. {example}", "electrum_host": "np. {example}", "electrum_offline_mode": "Tryb Offline", - "electrum_offline_description": "W trybie offline, Twoje portfele Bitcoinowe nie będą usiłowały pobierać sald lub transakcji.", + "electrum_offline_description": "W trybie offline, twoje portfele bitcoinowe nie będą usiłowały pobierać sald lub transakcji.", "electrum_port": "Port, zazwyczaj {example}", "use_ssl": "Użyj SSL", "electrum_saved": "Zmiany zostały zachowane pomyślnie. Żeby je zobaczyć zrestartuj aplikację.", + "electrum_status": "Status", "set_electrum_server_as_default": "Ustawić {server} jako domyślny serwer Electrum?", - "set_lndhub_as_default": "Ustawić {url} jako domyślny serwer LNDHub?", + "set_lndhub_as_default": "Ustawić {url} jako domyślny serwer LNDhub?", "electrum_settings_server": "Serwer Electrum", - "electrum_settings_explain": "Pozostaw puste aby użyć wartości domyślnej", - "electrum_status": "Status", - "electrum_clear_alert_title": "Wyczyścić historię?", - "electrum_clear_alert_message": "Czy chcesz wyczyścić historię serwerów Electrum?", - "electrum_clear_alert_cancel": "Anuluj", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Wybierz", - "electrum_reset": "Ustaw wartości domyślne", + "electrum_preferred_server": "Preferowany serwer", + "electrum_preferred_server_description": "Wprowadź serwer, którego ma używać twój portfel do wszystkich operacji związanych z Bitcoinem. Po zapisaniu ustawień, portfel będzie korzystał wyłącznie z tego serwera do sprawdzania sald, wysyłania transakcji oraz pobierania danych z sieci. Upewnij się, że masz zaufanie do tego serwera przed jego wyborem.", "electrum_unable_to_connect": "Nie można się połączyć z {server}.", - "electrum_history": "Historia serwerów", - "electrum_reset_to_default": "Czy na pewno ustawić domyślne ustawienia Electrum?", - "electrum_clear": "Wyczyść", - "tor_supported": "Sieć Tor obsługiwana", - "tor_unsupported": "Połączenia Tor nie są wspierane.", - "encrypt_decrypt": "Odszyfruj Schowek", - "encrypt_decrypt_q": "Czy jesteś pewien, że chcesz odszyfrować schowek? To pozwoli na dostęp do twoich portfeli bez hasła.", - "encrypt_enc_and_pass": "Szyfrowany i chroniony hasłem", + "electrum_history": "Historia", + "electrum_reset_to_default": "To pozwoli BlueWallet losowo wybrać serwer z listy.", + "electrum_reset": "Ustaw wartości domyślne", + "electrum_reset_to_default_and_clear_history": "Przywróć domyślne ustawienia i wyczyść historię", + "encrypt_decrypt": "Odszyfruj Magazyn Danych", + "encrypt_decrypt_q": "Czy jesteś pewien, że chcesz odszyfrować magazyn danych? To pozwoli na dostęp do twoich portfeli bez hasła.", + "encrypt_enc_and_pass": "Chroniony hasłem", + "encrypt_storage_explanation_headline": "Włącz szyfrowanie danych", + "encrypt_storage_explanation_description_line1": "Włączenie szyfrowania danych dodaje dodatkową warstwę ochrony do aplikacji, zabezpieczając sposób, w jaki dane są przechowywane na urządzeniu. Utrudnia to dostęp do Twoich informacji bez odpowiedniego upoważnienia.", + "encrypt_storage_explanation_description_line2": "Jednak warto wiedzieć, że to szyfrowanie chroni jedynie dostęp do portfeli przechowywanych w pęku kluczy urządzenia. Nie zabezpiecza ono samych portfeli hasłem ani żadną dodatkową ochroną.", + "i_understand": "Rozumiem", + "block_explorer": "Eksplorator bloków", + "block_explorer_preferred": "Użyj preferowanego eksploratora bloków", + "block_explorer_error_saving_custom": "Błąd podczas zapisywania preferowanego eksploratora bloków", "encrypt_title": "Zabezpieczenia", - "encrypt_tstorage": "Schowek", + "encrypt_tstorage": "Dane", "encrypt_use": "Użyj {type}", - "encrypt_use_expl": "{type} będzie użyty w celu potwierdzenia Twojej tożsamości przed wykonaniem transakcji, odblokowaniem, eksportem lub usunięciem portfela. {type} nie będzie użyty do odblokowanie danych zaszyfrowanych.", + "set_as_preferred": "Ustaw jako preferowany", + "set_as_preferred_electrum": "Ustawienie {host}:{port} jako preferowanego serwera wyłączy losowe łączenie się z sugerowanym serwerem.", + "encrypted_feature_disabled": "Ta funkcja nie może być używana z włączonym szyfrowaniem pamięci.", + "encrypt_use_expl": "{type} zostanie użyte do potwierdzenia twojej tożsamości przed wykonaniem transakcji, odblokowaniem, wyeksportowaniem lub usunięciem portfela.", + "biometrics_fail": "Jeśli {type} nie jest włączony lub nie udaje się odblokować, możesz alternatywnie użyć kodu dostępu swojego urządzenia.", "general": "Ogólne", - "general_adv_mode": "Tryb zaawansowany", - "general_adv_mode_e": "Gdy włączone, zobaczysz zaawansowane ustawienia takie jak np. różne typy portfeli, zdolność do określenia instancji LNDHub, z którą chcesz się połączyć oraz niestandardowej entropii w trakcie tworzenia portfela.", "general_continuity": "Funkcja Continuity", "general_continuity_e": "Gdy włączone, będziesz miał podgląd do wybranych portfeli i transakcji przy użyciu swoich urządzeń zalogowanych do Apple iCloud. ", "groundcontrol_explanation": "GroundControl jest darmowym, open source'owym serwerem powiadomień push dla portfeli bitcoin. Możesz zainstalować swój własny serwer GroundControl i podać jego URL tutaj aby nie polegać na infrastrukturze BlueWallet. Zostaw puste by użyć domyślnej wartości.", @@ -285,36 +286,37 @@ "language": "Język", "last_updated": "Ostatnia aktualizacja", "language_isRTL": "Aby ustawienia dotyczące kierunku pisma wybranego języka zaczęły obowiązywać, BlueWallet musi być zrestartowany.", - "lightning_error_lndhub_uri": "Nieprawidłowy adres LNDHub", + "license": "Licencja", + "lightning_error_lndhub_uri": "Adres LNDhub nieprawidłowy", + "lightning_error_lndhub_uri_tor": "Nieprawidłowy adres LNDhub. Upewnij się, że aplikacja Orbot jest połączona i spróbuj ponownie.", "lightning_saved": "Wprowadzone przez ciebie zmiany zostały pomyślnie zachowane.", "lightning_settings": "Ustawienia Lightning", - "tor_settings": "Ustawienia sieci Tor", - "lightning_settings_explain": "Aby połączyć się z własnym węzłem LND, zainstaluj LNDHub i wpisz jego adres URL w ustawieniach. Pamiętaj, że portfele utworzone po tej zmianie będą łączyć się z podanym serwerem LNDHub.", + "lightning_settings_explain": "Aby połączyć się z własnym węzłem LND, zainstaluj LNDhub i wprowadź jego URL tutaj w ustawieniach. Pamiętaj, że tylko portfele utworzone po zapisaniu zmian będą połączone z określonym LNDhubem.", + "lndhub_github": "Repozytorium GitHub", "network": "Sieć", "network_broadcast": "Rozgłoś transakcję", "network_electrum": "Serwer Electrum", - "not_a_valid_uri": "Nieprawidłowy adres", + "electrum_suggested_description": "Gdy preferowany serwer nie jest ustawiony, sugerowany serwer zostanie wybrany losowo do użycia.", + "not_a_valid_uri": "Adres nieprawidłowy", "notifications": "Powiadomienia", "open_link_in_explorer": "Otwórz link w eksploratorze bloków", "password": "Hasło", - "password_explain": "Stwórz hasło które odszyfruje schowek", - "passwords_do_not_match": "Hasła się nie zgadzają", + "password_explain": "Podaj hasło, do odblokowania pamięci telefonu.", "plausible_deniability": "Wiarygodna zaprzeczalność", "privacy": "Prywatność", "privacy_read_clipboard": "Czytaj Schowek", "privacy_system_settings": "Ustawienia Systemowe", "privacy_quickactions": "Skróty Portfeli", - "privacy_quickactions_explanation": "Dotknij i przytrzymaj ikonę aplikacji BlueWallet na swoim ekranie głównym aby szybko wyświetlić stan portfela.", + "privacy_quickactions_explanation": "Dotknij i przytrzymaj ikonę aplikacji BlueWallet, aby szybko sprawdzić saldo swojego portfela.", "privacy_clipboard_explanation": "Włącz skróty jeżeli adres lub faktura są znalezione w schowku.", "privacy_do_not_track": "Wyłącz analitykę", "privacy_do_not_track_explanation": "Informacje dotyczące wydajności i niezawodności nie będą przesyłane do analizy.", - "push_notifications": "Powiadomienia Push", "rate": "Kurs", - "retype_password": "Wprowadź Ponownie hasło", + "push_notifications_explanation": "Włączając powiadomienia, token urządzenia zostanie wysłany do serwera, wraz z adresami portfela oraz identyfikatorami transakcji dla wszystkich portfeli i transakcji dokonanych po włączeniu powiadomień. Token ten jest używany do wysyłania powiadomień, a informacje o portfelu pozwalają nam powiadamiać cię o przychodzących Bitcoinach lub potwierdzeniach transakcji.\n\nPrzesyłane są wyłącznie informacje dotyczące okresu po włączeniu powiadomień — żadne dane sprzed tego momentu nie są zbierane.\n\nWyłączenie powiadomień spowoduje usunięcie wszystkich tych informacji z serwera. Dodatkowo, usunięcie portfela z aplikacji również spowoduje usunięcie powiązanych z nim danych z serwera.", "selfTest": "Autotest", "save": "Zapisz", "saved": "Zapisano", - "success_transaction_broadcasted": "Sukces! Twoja transakcja została rozgłoszona!", + "success_transaction_broadcasted": "Twoja transakcja została rozgłoszona pomyślnie!", "total_balance": "Saldo całkowite", "total_balance_explanation": "Wyświetlaj saldo całkowite wszystkich Twoich portfeli wśród widżetów na ekranie domowym", "widgets": "Widżety", @@ -322,15 +324,17 @@ }, "notifications": { "would_you_like_to_receive_notifications": "Czy chcesz otrzymywać powiadomienia o nadchodzących płatnościach?", - "no_and_dont_ask": "Nie. I nie pytaj ponownie.", - "ask_me_later": "Zapytaj później" + "notifications_subtitle": "Płatności przychodzące i potwierdzenia transakcji", + "no_and_dont_ask": "Nie, i nie pytaj mnie ponownie.", + "permission_denied_message": "Odmówiłeś zgody na wysyłanie powiadomień. Jeśli chcesz otrzymywać powiadomienia, włącz je w ustawieniach swojego urządzenia." }, "transactions": { "cancel_explain": "Zastąpimy tę transakcję taką, która płaci Tobie i ma wyższe opłaty. To anuluje bieżącą transakcję. To się nazywa RBF—Replace by Fee.", "cancel_no": "Ta transakcja jest nie do zastąpienia", "cancel_title": "Anuluj tę transakcję (RBF)", + "transaction_loading_error": "Wystąpił problem z wczytaniem transakcji. Proszę spróbować ponownie później.", + "transaction_not_available": "Transakcja niedostępna", "confirmations_lowercase": "Potwierdzenia: {confirmations}", - "copy_link": "Kopiuj link", "expand_note": "Rozwiń notatkę", "cpfp_create": "Utwórz", "cpfp_exp": "Stworzymy kolejną transakcję, która wydaje twoją niepotwierdzoną transakcję. Całkowita opłata będzie wyższa niż z transakcji pierwotnej, więc powinna zostać wykopana szybciej. To się nazywa CPFP - Child Pays For Parent, Dziecko Płaci Za Rodzica.", @@ -338,20 +342,22 @@ "cpfp_title": "Zwiększ opłatę (CPFP)", "details_balance_hide": "Ukryj Saldo", "details_balance_show": "Pokaż Saldo", - "details_block": "Number bloku", "details_copy": "Kopiuj", - "details_copy_amount": "Kopiuj kwotę", "details_copy_block_explorer_link": "Kopiuj link do eksploratora bloków", "details_copy_note": "Kopiuj notatkę", "details_copy_txid": "Kopiuj ID transakcji", - "details_from": "Wejście", "details_inputs": "Wejścia", "details_outputs": "Wyjścia", "date": "Data", "details_received": "Otrzymano", - "transaction_note_saved": "Transakcja została pomyślnie zapisana.", - "details_show_in_block_explorer": "Zobacz w eksploratorze bloków", + "details_view_in_browser": "Pokaż w przeglądarce", "details_title": "Transakcja", + "incoming_transaction": "Transakcja przychodząca", + "outgoing_transaction": "Transakcja wychodząca", + "expired_transaction": "Transakcja przeterminowana", + "pending_transaction": "Transakcja w toku", + "offchain": "Offchain", + "onchain": "Onchain", "details_to": "Wyjście", "enable_offline_signing": "Ten portfel nie jest używany w połączeniu z podpisem offline. Czy chcesz to teraz włączyć?", "list_conf": "potwierdzenia: {number}", @@ -361,127 +367,185 @@ "eta_10m": "Szacowany czas: ~10 minut", "eta_3h": "Szacowany czas: ~3 godziny", "eta_1d": "Szacowany czas: ~1 dzień", - "view_wallet": "Pokaż {walletLabel}", "list_title": "Transakcje", - "open_url_error": "Nie udało się otworzyć linku za pomocą domyślnej przeglądarki. Zmień swoją domyślną przeglądarkę i spróbój ponownie.", + "list_title_sent": "Wysłano", + "list_title_received": "Otrzymano", + "transaction": "Transakcja", + "open_url_error": "Nie udało się otworzyć linku za pomocą domyślnej przeglądarki. Zmień swoją domyślną przeglądarkę i spróbuj ponownie.", "rbf_explain": "Zastąpimy tę transakcję taką, która ma wyższą opłatę, aby została wykopana szybciej. To się nazywa RBF—Replace by Fee.", "rbf_title": "Zwiększ opłatę (RBF)", "status_bump": "Zwiększ opłatę", "status_cancel": "Anuluj transakcję", "transactions_count": "Ilość transakcji", "txid": "ID Transakcji", - "updating": "Aktualizuję..." + "updating": "Aktualizuję...", + "watchOnlyWarningTitle": "Ostrzeżenie bezpieczeństwa", + "watchOnlyWarningDescription": "Zachowaj ostrożność wobec oszustów, którzy często używają portfeli „tylko do odczytu” do wprowadzania użytkowników w błąd. Te portfele nie pozwalają na kontrolowanie ani wysyłanie środków; umożliwiają jedynie przeglądanie salda.", + "custom_fee_warning_title": "Ostrzeżenie", + "custom_fee_warning_description": "Opłaty poniżej 1 sat/vB są prawidłowe, ale mogą nie zostać przekazane dalej z powodu konfiguracji zasad (policy) węzłów.", + "details_eta_analyzing": "Analizuję...", + "details_sent": "Wysłano", + "details_section": "Szczegóły", + "details_explorer": "eksplorator", + "details_id": "ID", + "details_network_fee": "Opłata sieciowa", + "details_to_address": "Do", + "details_note": "Notatka", + "details_add_note": "dodaj", + "details_advanced": "Zaawansowane", + "details_fee_rate": "Stawka opłaty", + "details_size": "Rozmiar", + "details_virtual_size": "Rozmiar wirtualny", + "details_tx_hex": "Hex transakcji", + "details_inputs_count": "Wejścia ({count})", + "details_outputs_count": "Wyjścia ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Prosty i potężny portfel Bitcoin", "add_create": "Utwórz", + "total_balance": "Saldo całkowite", + "add_entropy_reset_title": "Zresetuj entropię", + "add_entropy_reset_message": "Zmiana typu portfela spowoduje zresetowanie obecnej entropii. Czy chcesz kontynuować?", + "add_entropy": "Entropia", + "add_entropy_bytes": "{bytes} bajtów entropii", "add_entropy_generated": "{gen} bajtów wygenerowanej entropii", "add_entropy_provide": "Dostarcz entropii przy użyciu rzutów kością", "add_entropy_remain": "{gen} bajtów wygenerowanej entropii. Pozostałe {rem} bajty zostaną pozyskane z systemowego generatora liczb losowych.", "add_import_wallet": "Import portfela", "add_lightning": "Lightning", "add_lightning_explain": "W celu wydawania za pomocą natychmiastowych transakcji", - "add_lndhub": "Podłącz do własnego LNDHuba", - "add_lndhub_error": "Podany adres nie jest prawidłowym węzłem LNDHub.", - "add_lndhub_placeholder": "Adres Twojego węzła", + "add_lndhub": "Podłącz do swojego LNDhuba", + "add_lndhub_error": "Podany adres węzła jest nieprawidłowym węzłem LNDhub.", + "add_lndhub_placeholder": "Adres twojego węzła", "add_placeholder": "mój pierwszy portfel", "add_title": "Dodaj portfel", "add_wallet_name": "Nazwa", "add_wallet_type": "Typ", - "balance": "Saldo", + "add_wallet_seed_length": "Długość frazy seed", + "add_wallet_seed_length_12": "12 słów", + "add_wallet_seed_length_24": "24 słowa", "clipboard_bitcoin": "Masz w schowku adres Bitcoin. Czy chcesz go użyć do transakcji?", "clipboard_lightning": "Masz w schowku fakturę Lightning. Czy chcesz jej użyć do transakcji?", + "clear_clipboard_on_import": "Wyczyść schowek po zaimportowaniu", "details_address": "Adres", "details_advanced": "Zaawansowane", "details_are_you_sure": "Na pewno?", "details_connected_to": "Podłączono do", - "details_del_wb_err": "Podane saldo nie zgadza się z saldem portfela. Spróbuj ponownie.", - "details_del_wb_q": "Ten portfel ma dodatnie saldo. Zanim przejdziesz dalej, miej na uwadze, że nie będzie można odzyskać tych środków bez ziarna tego portfela. Aby uniknąć przypadkowego usunięcia, podaj saldo tego portfela w wysokości {balance} satoshi.", + "details_del_wb_err": "Podana kwota nie zgadza się z saldem tego portfela. Spróbuj ponownie.", + "details_del_wb_q": "Ten portfel ma dodatnie saldo. Zanim przejdziesz dalej, miej na uwadze, że nie będzie można odzyskać tych środków bez frazy seed tego portfela. Aby uniknąć przypadkowego usunięcia, podaj saldo tego portfela w wysokości {balance} satoshi.", "details_delete": "Skasuj", "details_delete_wallet": "Skasuj portfel", "details_derivation_path": "ścieżka derywacji", - "details_display": "Pokaż na liście portfeli", + "details_display": "Wyświetlaj na ekranie początkowym", "details_export_backup": "Eksport/Kopia zapasowa", "details_export_history": "Eksportuj historię do pliku CSV", "details_master_fingerprint": "Główny odcisk palca", "details_multisig_type": "multisig", - "details_no_cancel": "Nie, anuluj", - "details_save": "Zapisz", "details_show_xpub": "Pokaż XPUB portfela", "details_show_addresses": "Pokaż adresy", "details_title": "Portfel", + "wallets": "Portfele", "details_type": "Typ", "details_use_with_hardware_wallet": "Użyj z portfelem sprzętowym", - "details_wallet_updated": "Portfel zaktualizowany", "details_yes_delete": "Tak, skasuj", "enter_bip38_password": "Podaj hasło by zdeszyfrować", "export_title": "Eksport portfela", "import_do_import": "Importuj", "import_passphrase": "Hasło", "import_passphrase_title": "Hasło", - "import_passphrase_message": "Podaj hasło jeśli jakieś użyto", + "import_passphrase_message": "Wprowadź hasło, jeśli użyłeś jakiegokolwiek", "import_error": "Import nieudany. Upewnij się proszę, że dane są prawidłowe.", - "import_explanation": "Podaj poniżej twoje ziarno prywatny klucz, WIF lub cokolwiek co masz. BlueWallet postara się odgadnąć prawidłowy format i zaimportować Twój portfel. Jeśli zostanie podany klucz publiczny, dodamy go jako portfel tylko do obserwacji.", + "import_explanation": "Proszę wprowadź swoją frazę seed, klucz publiczny, WIF lub cokolwiek innego, co posiadasz. BlueWallet zrobi wszystko, co w jej mocy, aby odgadnąć poprawny format i zaimportować twój portfel.", "import_imported": "Zaimportowano", "import_scan_qr": "Skanuj lub importuj plik", "import_success": "Twój portfel został pomyślnie zaimportowany.", "import_success_watchonly": "Twój portfel został zaimportowany. UWAGA: To jest portfel tylko do odczytu, nie możesz z niego wydawać.", "import_search_accounts": "Szukaj kont", "import_title": "Importuj", + "learn_more": "Dowiedz się więcej", "import_discovery_title": "Wyszukiwanie", "import_discovery_subtitle": "Wybierz odnaleziony portfel", "import_discovery_derivation": "Użyj niestandardowej ścieżki derywacji", "import_discovery_no_wallets": "Nie odnaleziono portfeli.", - "import_derivation_found": "znaleziono", - "import_derivation_found_not": "nie znaleziono", - "import_derivation_loading": "ładowanie...", - "import_derivation_subtitle": "Wprowadź niestandardową ścieżkę derywacji a my spróbujemy odnaleźć Twój portfel", + "import_discovery_offline": "BlueWallet jest obecnie w trybie offline. W tym trybie nie można zweryfikować istnienia portfela, więc musisz wybrać odpowiedni ręcznie", + "import_derivation_found": "Znaleziono", + "import_derivation_found_not": "Nie znaleziono", + "import_derivation_loading": "Ładowanie...", + "import_derivation_subtitle": "Wprowadź niestandardową ścieżkę pochodną, a spróbujemy odnaleźć twój portfel.", "import_derivation_title": "Ścieżka derywacji", - "import_derivation_unknown": "nieznane", - "import_wrong_path": "zła ścieżka derywacji", + "import_derivation_unknown": "Nieznane", + "import_wrong_path": "Niewłaściwa ścieżka derywacji", "list_create_a_button": "Dodaj teraz", "list_create_a_wallet": "Dodaj portfel", - "list_create_a_wallet_text": "Jest za darmo i możesz utworzyć\nich ile Ci się podoba.", + "list_create_a_wallet_text": "To jest darmowe i możesz \nutworzyć tyle, ile chcesz.", "list_empty_txs1": "Twoje transakcje pojawią się tutaj.", "list_empty_txs1_lightning": "Portfel Lightning powinien być używany do codziennych transakcji. Opłaty są szalenie niskie a prędkość piorunująca.", "list_empty_txs2": "Rozpocznij z Twoim portfelem.", "list_empty_txs2_lightning": "\nAby zacząć używać, dotknij Zarządzaj Środkami i doładuj swoje saldo.", "list_latest_transaction": "Ostatnia transakcja", - "list_ln_browser": "Przeglądarka LApp", "list_long_choose": "Wybierz zdjęcie", - "list_long_clipboard": "Kopiuj ze schowka", + "paste_from_clipboard": "Wklej", + "import_file": "Importuj plik", "list_long_scan": "Zeskanuj kod QR", "list_title": "Portfele", "list_tryagain": "Spróbuj ponownie", "no_ln_wallet_error": "Musisz najpierw dodać portfel Lightning, zanim zapłacisz fakturę.", "looks_like_bip38": "To wygląda na klucz prywatny chroniony hasłem (BIP38).", - "reorder_title": "Zmień kolejność portfeli", - "reorder_instructions": "Stuknij i przytrzmaj portfel aby go przeciągnąć na liście.", + "manage_title": "Zarządzaj portfelami", + "no_results_found": "Nie znaleziono wyników.", "please_continue_scanning": "Proszę skanuj dalej.", "select_no_bitcoin": "Nie ma dostępnych portfeli Bitcoin.", "select_no_bitcoin_exp": "Portfel Bitcoin jest wymagany by uzupełnić portfel Lightning. Proszę utwórz lub zaimportuj.", "select_wallet": "Wybierz portfel", - "xpub_copiedToClipboard": "Skopiowano do schowka.", "pull_to_refresh": "Pociągnij by odświeżyć", - "warning_do_not_disclose": "Uwaga! Nie ujawniać.", + "warning_do_not_disclose": "Nigdy nie ujawniaj poniższych informacji", + "scan_import": "Zeskanuj ten kod QR, aby zaimportować swój portfel do innej aplikacji.", + "write_down_header": "Utwórz ręczną kopię zapasową", + "write_down": "Zapisz i bezpiecznie przechowaj te słowa. Użyj ich, aby odtworzyć swój portfel w późniejszym czasie.", + "wallet_type_this": "Ten typ portfela to {type}.", + "share_number": "Udostępnij {number}", + "copy_ln_url": "Skopiuj i bezpiecznie przechowaj ten URL, aby odtworzyć swój portfel w późniejszym czasie.", + "copy_ln_public": "Skopiuj i bezpiecznie przechowaj te informacje, aby odtworzyć swój portfel w późniejszym czasie.", "add_ln_wallet_first": "Najpierw musisz dodać portfel Lightning.", "identity_pubkey": "Klucz publiczny tożsamości", - "xpub_title": "XPUB portfela" + "xpub_title": "XPUB portfela", + "manage_wallets_search_placeholder": "Szukaj portfeli, adresów, transakcji i notatek", + "more_info": "Więcej informacji", + "details_delete_wallet_error_message": "Nie udało się potwierdzić usunięcia tego portfela z powiadomień – możliwe, że przyczyną jest problem z siecią lub słabe połączenie. Jeśli kontynuujesz, możesz nadal otrzymywać powiadomienia o transakcjach związanych z tym portfelem, nawet po jego usunięciu.", + "details_delete_anyway": "Usuń mimo to", + "swipe_balance_hide": "Ukryj", + "swipe_balance_show": "Pokaż", + "drag_to_reorder": "Przeciągnij, aby zmienić kolejność", + "clear_search": "Wyczyść wyszukiwanie" + }, + "total_balance_view": { + "display_in_bitcoin": "Wyświetlaj w Bitcoinie", + "hide": "Ukryj", + "display_in_sats": "Wyświetlaj w sats", + "display_in_fiat": "Wyświetlaj w {currency}", + "title": "Saldo całkowite", + "explanation": "Wyświetl saldo całkowite wszystkich swoich portfeli na ekranie podglądu." }, "multisig": { - "multisig_vault": "Skarbiec", + "multisig_vault": "Skarbiec wielopodpisowy", "default_label": "Skarbiec wielopodpisowy", "multisig_vault_explain": "Najlepsze bezpieczeństwo dla dużych kwot", "provide_signature": "Podaj podpis", + "provide_signature_details": "Użyj urządzenia i portfela, w którym znajduje się klucz, aby podpisać tę transakcję.", + "provide_signature_details_bluewallet": "W BlueWallet przejdź do menu ekranu wysyłania i wybierz ", + "provide_signature_next_steps": "Skanuj lub importuj podpisaną transakcję", + "provide_signature_next_steps_details": "Gdy Twój portfel pomyślnie podpisze transakcję, zeskanuj podany kod QR lub zaimportuj dołączony plik, a następnie zweryfikuj wszystkie szczegóły przed wysłaniem jej.", "vault_key": "Klucz Skarbca {number}", "required_keys_out_of_total": "Wymagane klucze spośród wszystkich", "fee": "Opłata: {number}", "fee_btc": "{number} BTC", "confirm": "Potwierdź", "header": "Wyślij", - "share": "Udostępnij", + "share": "Udostępnij...", "view": "Widok", + "shared_key_detected": "Wspólny współsygnatariusz", + "shared_key_detected_question": "Współsygnatariusz został Tobie udostępniony, czy chcesz go zaimportować?", "manage_keys": "Zarządzaj kluczami", "how_many_signatures_can_bluewallet_make": "ile podpisów BlueWallet może zrobić", "signatures_required_to_spend": "Podpisy wymagane {number}", @@ -507,20 +571,21 @@ "quorum_header": "Kworum", "of": "z", "wallet_type": "Typ portfela", - "invalid_mnemonics": "To wyrażenie mnemoniczne nie wydaje się prawidłowe.", + "invalid_mnemonics": "Ta fraza mnemoniczna nie wygląda na prawidłową.", "invalid_cosigner": "Nieprawidłowe dane współsygnatariusza", "not_a_multisignature_xpub": "To nie jest XPUB portfela wielopodpisowego", - "invalid_cosigner_format": "Niepoprawny współsygnatariusz: to nie jest współsygnatariusz formatu {format}.", + "invalid_cosigner_format": "Nieprawidłowy współsygnatariusz: Jest niezgodny z formatem {format}.", "create_new_key": "Otwórz nowy", "scan_or_open_file": "Skanuj lub otwórz plik", - "i_have_mnemonics": "Mam ziarno dla tego klucza", - "type_your_mnemonics": "Wprowadź ziarno by zaimportować Twój istniejący klucz Skarbca.", - "this_is_cosigners_xpub": "To jest XPUB współsygnatariusza—gotowy do zaimportowania w innym portfelu. Można go udostępniać.", + "i_have_mnemonics": "Mam seed dla tego klucza.", + "type_your_mnemonics": "Wprowadź seed, aby zaimportować istniejący klucz do Twojego Skarbca.", + "this_is_cosigners_xpub": "To jest XPUB współsygnatariusza — gotowy do zaimportowania do innego portfela. Udostępnianie go jest bezpieczne.", + "this_is_cosigners_xpub_airdrop": "Jeśli udostępniasz za pomocą AirDrop, odbiorcy muszą znajdować się na ekranie koordynacji.", "wallet_key_created": "Twój klucz Skarbca został utworzony. Poświęć chwilę by zrobić kopię bezpieczeństwa Twojego wyrażenia mnemonicznego.", - "are_you_sure_seed_will_be_lost": "Czy jesteś pewien? Twoje wyrażenie mnemoniczne zostanie utracone jeśli nie masz kopii bezpieczeństwa.", - "forget_this_seed": "Zapomnij to ziarno i w zamian użyj XPUB.", - "view_edit_cosigners": "Wyświetł/edytuj współsygnatariuszy", - "this_cosigner_is_already_imported": "Ten współsygnatariusz już został zaimportowany.", + "are_you_sure_seed_will_be_lost": "Czy na pewno? Twoje wyrażenie mnemoniczne zostanie utracone jeśli nie masz kopii bezpieczeństwa.", + "forget_this_seed": "Zapomnij ten seed i w zamian użyj XPUB.", + "view_edit_cosigners": "Pokaż/Edytuj współsygnatariuszy", + "this_cosigner_is_already_imported": "Ten współsygnatariusz jest już zaimportowany.", "export_signed_psbt": "Eksportuj podpisaną PSBT", "input_fp": "Podaj odcisk palca", "input_fp_explain": "Pomiń, aby użyć domyślnej wartości (00000000)", @@ -533,10 +598,10 @@ "ms_help_1": "Ten Skarbiec będzie funkcjonować z innymi aplikacjami BlueWallet i portfelami kompatybilnymi z PSBT, takimi jak Electrum, Specter, Coldcard, Cobo Vault, etc.", "ms_help_title2": "Edycja kluczy", "ms_help_2": "Możesz utworzyć wszystkie klucze Skarbca na tym urządzeniu i usunąć lub edytować je później. Posiadanie wszystkich kluczy na tym samym urządzeniu jest z punktu widzenia bezpieczeństwa równoważne zwykłemu portfelowi.", - "ms_help_title3": "Kopie zapasowa Skarbców", - "ms_help_3": "W ustawieniach portfela znajdziesz kopię zapasową Twojego Skarbca oraz tylko do odczytu. Ta kopia jest jak mapa do Twojego portfela. Jest niezbędna przy odzyskiwaniu portfela w razie utraty ziarna.", + "ms_help_title3": "Kopie zapasowe Skarbców", + "ms_help_3": "W ustawieniach portfela znajdziesz kopię zapasową Twojego Skarbca oraz tylko do odczytu. Ta kopia jest jak mapa do Twojego portfela. Jest niezbędna przy odzyskiwaniu portfela w razie utraty seeda.", "ms_help_title4": "Import Skarbców", - "ms_help_4": "Aby zaimportować portfel wielopodpisowy, użyj kopii zapasowej i funkcji Importuj. Jeśli masz tylko ziarna lub XPUB, możesz użyć przycisku Importuj podczas tworzenia kluczy Skarbca.", + "ms_help_4": "Aby zaimportować portfel wielopodpisowy, użyj kopii zapasowej i funkcji Importuj. Jeśli masz tylko seedy lub XPUB, możesz użyć przycisku Importuj podczas tworzenia kluczy Skarbca.", "ms_help_title5": "Tryb zaawansowany", "ms_help_5": "Domyślnie, BlueWallet wygeneruje skarbiec 2-z-3. Aby stworzyć inne kworum lub zmienić typ adresu, włącz Tryb Zaawansowany w Ustawieniach." }, @@ -548,26 +613,41 @@ "no_wallet_owns_address": "Żaden z dostępnych portfeli nie posiada podanego adresu.", "view_qrcode": "Pokaż kod QR" }, + "autofill_word": { + "title": "Ostatnie słowo seeda", + "enter": "Wprowadź swój częściowy mnemonik", + "generate_word": "Generuj ostatnie słowo", + "error": "Wprowadzone dane nie są 11- lub 23-wyrazowym częściowym mnemonikiem. Proszę spróbować ponownie." + }, "cc": { "change": "Reszta", "coins_selected": "Wybrano monet ({number})", "selected_summ": "Wybrana kwota {value}", - "empty": "Ten portfel nie ma żadnych monet w tej chwili.", + "empty": "Ten portfel nie ma obecnie żadnych monet.", "freeze": "Zamrożona", "freezeLabel": "Zamróź", "freezeLabel_un": "Odmroź", "header": "Kontrola monet", "use_coin": "Użyj tej monety", "use_coins": "Użyj tych monet", - "tip": "Dzięki tej funkcji możesz lepiej zarządzać monetami poprzez ich wybieranie, zamrażanie lub sprawdzanie i nadawanie im etykiet. Możesz zaznaczyć kilka monet naraz wybierając kolorowe kółka." + "tip": "Dzięki tej funkcji możesz lepiej zarządzać monetami poprzez ich wybieranie, zamrażanie lub sprawdzanie i nadawanie im etykiet. Możesz zaznaczyć kilka monet naraz wybierając kolorowe kółka.", + "sort_asc": "Rosnąco", + "sort_desc": "Malejąco", + "sort_height": "Wysokość", + "sort_value": "Wartość", + "sort_label": "Etykieta", + "sort_status": "Status", + "sort_by": "Sortuj wg" }, "units": { "BTC": "BTC", "MAX": "Max", - "sat_vbyte": "sat/vBajt", + "sat_vbyte": "sat/vByte", "sats": "satoshi" }, "addresses": { + "copy_private_key": "Kopiuj klucz prywatny", + "sensitive_private_key": "Uwaga: klucze prywatne są skrajnie poufne. Kontynuować?", "sign_title": "Podpisz/Weryfikuj wiadomość", "sign_help": "Tutaj możesz stworzyć lub zweryfikować podpis kryptograficzny oparty o adres Bitcoin.", "sign_sign": "Podpisz", @@ -601,9 +681,24 @@ }, "bip47": { "payment_code": "Kod płatności", - "payment_codes_list": "Lista kodów płatności", - "who_can_pay_me": "Kto może mi płacić:", + "contacts": "Kontakty", + "bip47_explain": "Kod wielokrotnego użytku i do udostępnienia.", + "bip47_explain_subtitle": "BIP47", "purpose": "Kod wielokrotnego użytku możliwy do udostępnienia (BIP47)", + "pay_this_contact": "Zapłać temu kontaktowi", + "rename_contact": "Zmień nazwę kontaktu", + "copy_payment_code": "Kopiuj kod płatności", + "hide_contact": "Ukryj kontakt", + "rename": "Zmień nazwę", + "provide_name": "Podaj nową nazwę dla tego kontaktu", + "add_contact": "Dodaj kontakt", + "provide_payment_code": "Podaj kod płatności", + "invalid_pc": "Nieprawidłowy kod płatności", + "notification_tx_unconfirmed": "Transakcja powiadomienia nie została jeszcze potwierdzona, proszę czekać", + "failed_create_notif_tx": "Nie udało się utworzyć transakcji on-chain", + "onchain_tx_needed": "Wymagana transakcja on-chain", + "notif_tx_sent": "Transakcja powiadomienia wysłana. Proszę czekać na jej potwierdzenie", + "notif_tx": "Transakcja powiadomienia", "not_found": "Kod płatności nie znaleziony" } } diff --git a/loc/pt_br.json b/loc/pt_br.json index 74e3a740046..198a17c7f79 100644 --- a/loc/pt_br.json +++ b/loc/pt_br.json @@ -4,32 +4,32 @@ "cancel": "Cancelar", "continue": "Continuar", "clipboard": "Área de transferência", + "discard_changes": "Descartar alterações?", + "discard_changes_explain": "Você tem alterações não salvas. Tem certeza de que deseja descartá-las e sair da tela?", "enter_password": "Insira a senha", "never": "Nunca", - "disabled": "Desativado", "of": "{number} de {total}", "ok": "OK", + "enter_url": "Insira URL", "storage_is_encrypted": "Os arquivos estão criptografados, uma senha é necessária para descriptografá-los.", "yes": "Sim", "no": "Não", - "save": "Salvar", + "save": "Salvar...", "seed": "Seed", "success": "Sucesso", "wallet_key": "Chave da Carteira", - "invalid_animated_qr_code_fragment": "Código QR animado inválido, por favor tente novamente.", - "file_saved": "Arquivo {filePath} foi salvo em {destination}.", - "downloads_folder": "Pasta de Downloads", "close": "Fechar", "change_input_currency": "Alterar moeda de entrada", "refresh": "Atualizar", - "more": "Mais", - "pick_image": "Escolher imagem da biblioteca", + "pick_image": "Escolher da biblioteca", "pick_file": "Escolher arquivo", "enter_amount": "Insira o valor", - "qr_custom_input_button": "Toque 10 vezes para inserir uma entrada personalizada" - }, - "alert": { - "default": "Alerta" + "qr_custom_input_button": "Toque 10 vezes para inserir uma entrada personalizada", + "unlock": "Desbloquear", + "port": "Porta", + "ssl_port": "Porta SSL", + "suggested": "Sugerido", + "copied": "Copiado!" }, "azteco": { "codeIs": "Seu código voucher é", @@ -38,12 +38,14 @@ "redeem": "Resgatar para carteira", "redeemButton": "Resgatar", "success": "Sucesso", - "title": "Resgatar voucher Azte.co" + "title": "Resgatar voucher Azte.co", + "successMessage": "Voucher resgatado com sucesso! Seus fundos devem chegar à sua carteira Bitcoin em breve." }, "entropy": { "save": "Salvar", "title": "Entropia", - "undo": "Desfazer" + "undo": "Desfazer", + "amountOfEntropy": "{bits} de {limit} bits" }, "errors": { "broadcast": "Falha na transmissão.", @@ -51,86 +53,69 @@ "network": "Erro na rede" }, "lnd": { - "active": "Ativo", - "inactive": "Inativo", - "channels": "Canais", - "no_channels": "Nenhum canal", - "claim_balance": "Reinvindicar saldo {balance}", - "close_channel": "Fechar canal", - "new_channel": "Novo canal", "errorInvoiceExpired": "Fatura expirada", - "force_close_channel": "Forçar fechamento do canal?", "expired": "Expirada", - "node_alias": "Apelido do nó", "expiresIn": "Expira em {time} minutos", "payButton": "Pagar", + "payment": "Pagamento", "placeholder": "Fatura ou endereço", - "open_channel": "Abrir canal", - "funding_amount_placeholder": "Quantidade de financiamento, por exemplo 0.001", - "opening_channnel_for_from": "Abrindo canal para carteira {forWalletLabel}, com financiamento de {fromWalletLabel}", - "are_you_sure_open_channel": "Tem certeza que deseja abrir esse canal?", "potentialFee": "Taxa potencial: {fee}", - "remote_host": "Hospedeiro remoto", "refill": "Recarregar", - "reconnect_peer": "Reconectar par", "refill_create": "Para continuar, por favor, crie uma carteira Bitcoin para recarregar.", "refill_external": "Recarregar com uma carteira externa", "refill_lnd_balance": "Recarregar a carteira Lightning", "sameWalletAsInvoiceError": "Você não pode pagar uma fatura com a mesma carteira que a criou.", - "title": "Administrar fundos", - "can_send": "Pode enviar", - "can_receive": "Pode receber", - "view_logs": "Ver logs" + "title": "Administrar fundos" }, "lndViewInvoice": { "additional_info": "Informação adicional", "for": "Para:", "lightning_invoice": "Fatura Lightning", - "open_direct_channel": "Abrir canal direto com este nó:", "please_pay_between_and": "Por favor, pague entre {min} e {max}", "please_pay": "Por favor, pague", - "preimage": "Preimage", + "preimage": "Pré-imagem", "sats": "sats.", + "date_time": "Data e Horário", "wasnt_paid_and_expired": "Esta fatura não foi paga e expirou." }, "plausibledeniability": { "create_fake_storage": "Criar armazenamento criptografado", - "create_password": "Adicionar uma senha", "create_password_explanation": "A senha para o armazenamento falso não deve ser igual a do armazenamento principal.", - "help": "Em algumas circunstâncias, você pode ser forçado a revelar uma senha. Para manter seus bitcoins seguros, a BlueWallet pode criar uma senha alternativa. Sob pressão, você pode revelar essa senha ao invés da senha principal. Quando inserida na BlueWallet, esta abrirá uma interface falsa, que parecerá legítima a um terceiro, enquanto suas carteiras originais continuarão à salvo em segredo.", + "help": "Em algumas circunstâncias, você pode ser forçado a revelar uma senha. Para manter seus bitcoins seguros, a BlueWallet pode criar uma senha alternativa. Sob pressão, você pode revelar essa senha ao invés da senha principal. Quando inserida na BlueWallet, esta abrirá uma interface falsa, que parecerá legítima a um terceiro, enquanto suas carteiras originais continuarão a salvo em segredo.", "help2": "Essa nova interface é completamente funcional e você pode inclusive manter nela um valor mínimo para que pareça mais real.", "password_should_not_match": "Esta senha já está sendo usada. Por favor, tente uma senha diferente.", - "passwords_do_not_match": "As senhas não coincidem. Por favor, tente outra vez.", - "retype_password": "Inserir senha novamente", - "success": "Sucesso", "title": "Negação plausível" }, "pleasebackup": { "ask": "Você salvou a frase de backup da sua carteira? Esta frase de backup é necessária para acessar seus fundos, caso você perca este dispositivo. Sem a frase de backup, seus fundos serão perdidos permanentemente.", - "ask_no": "Não fiz", - "ask_yes": "Sim, já fiz", - "ok": "OK, eu anotei", - "ok_lnd": "OK, eu salvei", - "text": "Reserve um tempo para anotar esta sequência de palavras em um pedaço de papel.\nÉ o seu backup e você pode usá-la para recuperar a sua carteira.", + "ask_no": "Não, eu não salvei.", + "ask_yes": "Sim, eu salvei", + "ok": "Ok, eu anotei.", + "ok_lnd": "OK, eu salvei.", + "text": "Reserve um tempo para anotar esta sequência de palavras em um pedaço de papel.\nÉ o seu backup e você pode usá-lo para recuperar a sua carteira.", "text_lnd": "Por favor, salve este backup. É ele que permite a restauração da carteira em caso de perda.", - "title": "Sua carteira foi criada" + "title": "Sua carteira foi criada." }, "receive": { "details_create": "Criar", "details_label": "Descrição", "details_setAmount": "Quantia a receber", - "details_share": "Compartilhar", + "details_share": "Compartilhar...", "header": "Receber", + "reset": "Resetar", "maxSats": "O Valor máximo é de {max} sats", "maxSatsFull": "O valor máximo é de {max} sats ou {currency}", "minSats": "O valor mínimo é de {min} sats", - "minSatsFull": "O valor mínimo é de {min} sats ou {currency}" + "minSatsFull": "O valor mínimo é de {min} sats ou {currency}", + "qrcode_for_the_address": "QR Code para o Endereço", + "bip47_explanation": "Os Payment Codes são um tipo de endereço universal que evita a divulgação dos endereços da sua carteira. Nem todos os serviços irão suportar envio.", + "address_not_found": "Não foi possível gerar o endereço de recebimento." }, "send": { "provided_address_is_invoice": "Esse tipo de endereço se parece com uma fatura da Lightning. Por favor, acesse a sua carteira Lightning para realizar o pagamento dessa fatura.", "broadcastButton": "Transmitir", "broadcastError": "Erro", - "broadcastNone": "Insira o Hash da Transação", + "broadcastNone": "Insira o Hex da Transação", "broadcastPending": "Pendente", "broadcastSuccess": "Sucesso", "confirm_header": "Confirmar", @@ -142,12 +127,16 @@ "create_fee": "Taxa", "create_memo": "Nota", "create_satoshi_per_vbyte": "Satoshi por vByte", - "create_this_is_hex": "Este é o hash da sua transação, assinada e pronta para ser transmitida para o mundo.", + "create_this_is_hex": "Este é o hex da sua transação, assinada e pronta para ser transmitida para o mundo.", "create_to": "Para", "create_tx_size": "Tamanho da Transação", "create_verify": "Verificar no coinb.in", + "details_insert_contact": "Inserir Contato", "details_add_rec_add": "Adicionar Destinatário", "details_add_rec_rem": "Remover Destinatário", + "details_add_recc_rem_all_alert_description": "Você tem certeza que deseja remover estes destinatários?", + "details_add_rec_rem_all": "Remover todos os destinatários", + "details_recipients_title": "Destinatários", "details_address": "Endereço", "details_address_field_is_not_valid": "O endereço não é válido.", "details_adv_fee_bump": "Permitir aumento de taxa", @@ -161,7 +150,7 @@ "details_create": "Criar fatura", "details_error_decode": "Não foi possível decodificar o endereço Bitcoin", "details_fee_field_is_not_valid": "A taxa não é válida.", - "details_frozen": "{amount} BTC estão congelados", + "details_frozen": "{amount} BTC está congelado.", "details_next": "Próximo", "details_no_signed_tx": "O arquivo selecionado não contém uma transação que possa ser importada.", "details_note_placeholder": "Nota pessoal", @@ -182,7 +171,7 @@ "fee_custom": "Personalizada", "fee_fast": "Rápida", "fee_medium": "Normal", - "fee_replace_minvb": "A taxa total (satoshi per vByte) que você deseja pagar deve ser maior que {min} sat/vByte.", + "fee_replace_minvb": "A taxa total (satoshi por vByte) que você deseja pagar deve ser maior que {min} sat/vByte.", "fee_satvbyte": "em sat/vByte", "fee_slow": "Lenta", "header": "Enviar", @@ -193,8 +182,6 @@ "permission_camera_message": "Precisamos de permissão para usar a câmera.", "psbt_sign": "Assinar uma transação", "open_settings": "Abrir Configurações", - "permission_storage_later": "Me pergunte mais tarde", - "permission_storage_message": "A BlueWallet precisa da sua permissão para acessar seu armazenamento e salvar o arquivo.", "permission_storage_denied_message": "BlueWallet não pode salvar este arquivo. Por favor abra as configurações do seu dispositivo e habilite as permissões de armazenamento.", "permission_storage_title": "Permissão de acesso ao armazenamento", "psbt_clipboard": "Copiar para área de transferência", @@ -204,12 +191,22 @@ "outdated_rate": "A taxa foi atualizada pela última vez em: {date}", "psbt_tx_open": "Abrir transação assinada", "psbt_tx_scan": "Ler transação assinada", - "qr_error_no_qrcode": "Não foi possível encontrar um código QR na imagem selecionada. Certifique-se de que a imagem contém apenas um código QR e nenhum conteúdo adicional, como texto ou botões.", + "qr_error_no_qrcode": "Não foi possível encontrar um QR Code na imagem selecionada. Certifique-se de que a imagem contenha apenas um QR Code e nenhum conteúdo adicional, como texto ou botões.", "reset_amount": "Redefinir quantia", "reset_amount_confirm": "Gostaria de redefinir a quantia?", "success_done": "Enviado", - "txSaved": "O arquivo de transação ({filePath}) foi salvo na pasta Downloads.", - "problem_with_psbt": "Problema com a PSBT" + "txSaved": "O arquivo de transação ({filePath}) foi salvo.", + "file_saved_at_path": "O arquivo ({filePath}) foi salvo.", + "cant_send_to_silentpayment_adress": "Esta carteira não pode enviar para endereços SilentPayment", + "cant_send_to_bip47": "Esta carteira não pode enviar para Payment Codes BIP47", + "cant_find_bip47_notification": "Adicione este Payment Code aos contatos primeiro", + "problem_with_psbt": "Problema com a PSBT", + "details_recipient_title": "Destinatário #{number} de #{total}", + "please_complete_recipient_title": "Destinatário incompleto", + "please_complete_recipient_details": "Por favor, complete os detalhes do destinatário #{number} antes de adicionar um novo destinatário.", + "details_scan_error": "Erro na leitura", + "insert_custom_fee": "Insira a taxa", + "invalid_psbt": "PSBT inválido fornecido." }, "settings": { "about": "Sobre", @@ -222,28 +219,24 @@ "performance_score": "Pontuação de performance: {num}", "run_performance_test": "Teste de performance", "about_selftest": "Executar autoteste", + "block_explorer_invalid_custom_url": "A URL é inválida. Insira uma URL válida começando com http:// ou https://.", "about_selftest_electrum_disabled": "Autoteste indisponível no modo Offline da Electrum. Desative o modo Offline e tente novamente.", "about_selftest_ok": "Todos os testes internos passaram com sucesso. A carteira funciona bem.", "about_sm_github": "GitHub", - "about_sm_discord": "Servidor Discord", "about_sm_telegram": "Chat do Telegram", - "about_sm_twitter": "Siga-nos no Twitter", - "advanced_options": "Opções avançadas", "biometrics": "Biometria", + "biometrics_no_longer_available": "As configurações do seu dispositivo foram alteradas e não correspondem mais às configurações de segurança selecionadas no aplicativo. Reative a biometria ou a senha e reinicie o aplicativo para aplicar essas alterações.", "biom_10times": "Você tentou digitar sua senha 10 vezes. Você gostaria de resetar seu armazenamento? Isso irá remover todas suas carteiras e descriptografar seu armazenamento.", "biom_conf_identity": "Por favor, confirme sua identidade.", - "biom_no_passcode": "Seu dispositivo não possui uma senha. Para proceder, por favor configure uma senha no app de Configurações.", - "biom_remove_decrypt": "Todas suas carteiras serão removidas e seu armazenamento será descritptografado. Você tem certeza que deseja proceder?", + "biom_no_passcode": "Seu dispositivo não possui senha ou biometria ativada. Para prosseguir, configure uma senha ou biometria nas configurações do aplicativo.", + "biom_remove_decrypt": "Todas suas carteiras serão removidas e seu armazenamento será descriptografado. Você tem certeza que deseja proceder?", "currency": "Moeda", - "currency_source": "Os preços são obtidos a partir da", + "currency_source": "A taxa é obtida de", "currency_fetch_error": "Ocorreu um erro ao tentar obter o índice da moeda selecionada.", - "default_desc": "Quando desativado, a BlueWallet abrirá imediatamente a carteira selecionada ao ser iniciada.", - "default_info": "Informação Padrão", "default_title": "Ao abrir", - "default_wallets": "Ver todas as carteiras", "electrum_connected": "Conectado", "electrum_connected_not": "Não conectado", - "electrum_error_connect": "Não é possível conectar ao servidor Electrum fornecido", + "electrum_error_connect": "Não foi possível conectar ao servidor Electrum fornecido", "lndhub_uri": "Por ex. {example}", "electrum_host": "Por ex. {example}", "electrum_offline_mode": "Modo offline", @@ -252,32 +245,33 @@ "use_ssl": "Usar SSL", "electrum_saved": "Suas alterações foram salvas com sucesso. Pode ser necessário reiniciar para que as alterações tenham efeito.", "set_electrum_server_as_default": "Definir {server} como servidor Electrum padrão?", - "set_lndhub_as_default": "Definir {server} como servidor LNDHub padrão?", + "set_lndhub_as_default": "Definir {url} como servidor LNDHub padrão?", "electrum_settings_server": "Servidor Electrum", - "electrum_settings_explain": "Deixe em branco para usar o padrão.", "electrum_status": "Status", - "electrum_clear_alert_title": "Limpar histórico?", - "electrum_clear_alert_message": "Você deseja limpar o histórico de servidores Electrum?", - "electrum_clear_alert_cancel": "Cancelar", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Selecionar", - "electrum_reset": "Resetar para o padrão.", + "electrum_preferred_server": "Servidor Preferencial", + "electrum_preferred_server_description": "Insira o servidor que você quer que sua carteira use para todas as atividades de Bitcoin. Uma vez definido, sua carteira usará exclusivamente este servidor para verificar saldos, enviar transações e buscar dados de rede. Certifique-se de confiar neste servidor antes de defini-lo.", "electrum_unable_to_connect": "Não foi possível conectar com {server}.", - "electrum_history": "Histórico do servidor", - "electrum_reset_to_default": "Tem certeza de que deseja redefinir suas configurações Electrum para o padrão?", - "electrum_clear": "Limpar", - "tor_supported": "Compatível com Tor", - "tor_unsupported": "Conexões Tor não são suportadas.", + "electrum_history": "Histórico", + "electrum_reset_to_default": "Isso permitirá que o BlueWallet escolha aleatoriamente um servidor da lista de servidores.", + "electrum_reset": "Resetar para o padrão.", + "electrum_reset_to_default_and_clear_history": "Redefinir para o padrão e limpar histórico", "encrypt_decrypt": "Descriptografar armazenamento", "encrypt_decrypt_q": "Tem certeza de que deseja descriptografar seu armazenamento? Isso permitirá que suas carteiras sejam acessadas sem uma senha.", - "encrypt_enc_and_pass": "Encriptado e protegido por senha", + "encrypt_storage_explanation_headline": "Habilitar criptografia de armazenamento", + "encrypt_storage_explanation_description_line1": "A ativação da criptografia de armazenamento adiciona uma camada extra de proteção ao seu aplicativo, protegendo a forma como os dados são armazenados no dispositivo. Isso torna mais difícil para qualquer pessoa acessar suas informações sem a sua permissão.", + "encrypt_storage_explanation_description_line2": "Porém, é importante entender que essa criptografia protege apenas o acesso às suas carteiras armazenadas nas chaves do dispositivo. Não coloca senha ou qualquer proteção extra especificamente nas carteiras.", + "i_understand": "Eu entendo", + "block_explorer": "Explorador de Blocos", + "block_explorer_preferred": "Usar explorador de blocos preferido", + "block_explorer_error_saving_custom": "Erro ao salvar o explorador de blocos favorito", "encrypt_title": "Segurança", "encrypt_tstorage": "Armazenamento", "encrypt_use": "Usar {type}", - "encrypt_use_expl": "{type} será usado para confirmar sua identidade antes de fazer uma transação, desbloquear, exportar ou deletar uma carteira. {type} não será usado para desbloquear armazenamento encriptado.", + "set_as_preferred": "Definir como preferido", + "set_as_preferred_electrum": "Definir {host}:{port} como servidor preferencial desabilitará a conexão com um servidor sugerido aleatoriamente.", + "encrypted_feature_disabled": "Este recurso não pode ser usado com o armazenamento criptografado ativado.", + "biometrics_fail": "Se {type} não estiver habilitado ou falhar ao desbloquear, você pode usar o código de acesso do seu dispositivo como alternativa.", "general": "Geral", - "general_adv_mode": "Modo Avançado", - "general_adv_mode_e": "Quando ativado, você verá opções avançadas, como diferentes tipos de carteira, a capacidade de especificar a instância do LNDHub à qual deseja se conectar e a entropia personalizada durante a criação da carteira.", "general_continuity": "Continuidade", "general_continuity_e": "Quando ativado, você poderá visualizar carteiras selecionadas e transações, usando seus outros dispositivos conectados ao Apple iCloud.", "groundcontrol_explanation": "GroundControl é um servidor de notificações push de código aberto gratuito para carteiras Bitcoin. Você pode instalar seu próprio servidor GroundControl e colocar sua URL aqui para não depender da infraestrutura da BlueWallet. Deixe em branco para usar o padrão", @@ -285,52 +279,62 @@ "language": "Idioma", "last_updated": "Última Atualização", "language_isRTL": "É necessário reiniciar a BlueWallet para que a nova orientação linguística seja ativada.", + "license": "Licença", "lightning_error_lndhub_uri": "URI LNDHub inválida", "lightning_saved": "Suas alterações foram salvas com sucesso.", "lightning_settings": "Configurações Lightning", - "tor_settings": "Configurações Tor", - "lightning_settings_explain": "Para se conectar ao seu próprio node LND, instale LNDHub e copie seu URL para cá. Somente carteiras criadas após salvar as alterações se conectarão com a instância LNDHub especificada.", + "lightning_settings_explain": "Para se conectar ao seu próprio node LND, instale LNDHub e cole seu URL em configurações. Somente carteiras criadas após salvar as alterações se conectarão com a instância LNDHub especificada.", "network": "Rede", "network_broadcast": "Transmitir Transação", "network_electrum": "Servidor Electrum", + "electrum_suggested_description": "Quando um servidor preferencial não for definido, um servidor sugerido será selecionado para uso aleatoriamente.", "not_a_valid_uri": "URI inválida", "notifications": "Notificações", "open_link_in_explorer": "Abrir link no navegador", "password": "Senha", - "password_explain": "Defina a senha para descriptografar o armazenamento.", - "passwords_do_not_match": "As senhas não conferem.", + "password_explain": "Insira a senha que você vai usar para desbloquear o seu armazenamento.", "plausible_deniability": "Negação plausível", "privacy": "Privacidade", "privacy_read_clipboard": "Leia a área de transferência", "privacy_system_settings": "Configurações do sistema", "privacy_quickactions": "Atalhos da carteira", - "privacy_quickactions_explanation": "Toque e segure o ícone do aplicativo BlueWallet em sua tela inicial para visualizar rapidamente o saldo de sua carteira.", + "privacy_quickactions_explanation": "Toque e segure o ícone do aplicativo BlueWallet para visualizar rapidamente o saldo da sua carteira.", "privacy_clipboard_explanation": "Fornece atalhos se endereços ou faturas são encontrados na área de transferência.", "privacy_do_not_track": "Desativar analítica", "privacy_do_not_track_explanation": "Informações de confiabilidade e desempenho não serão enviadas para análise.", - "push_notifications": "Notificações push", "rate": "Taxa", - "retype_password": "Inserir senha novamente", + "push_notifications_explanation": "Ao habilitar as notificações, o token do seu dispositivo será enviado ao servidor, junto com endereços de carteira e IDs de transação para todas as carteiras e transações feitas após habilitar as notificações. O token do dispositivo é usado para enviar notificações, e as informações da carteira nos permitem notificá-lo sobre Bitcoins recebidos ou confirmações de transações.\n\nSomente informações após você habilitar as notificações são transmitidas — nada anterior é coletado.\n\nDesabilitar notificações removerá todas essas informações do servidor. Além disso, excluir uma carteira do aplicativo também removerá suas informações associadas do servidor.", "selfTest": "Autoteste", "save": "Salvar", "saved": "Salvo", - "success_transaction_broadcasted": "Sucesso! Sua transação foi transmitida!", + "success_transaction_broadcasted": "Sua transação foi transmitida com sucesso!", "total_balance": "Saldo total", "total_balance_explanation": "Exibir o saldo total de todas suas carteiras nos seus widgets da tela inicial.", "widgets": "Widgets", - "tools": "Ferramentas" + "tools": "Ferramentas", + "privacy_temporary_screenshots": "Permitir captura de tela", + "privacy_temporary_screenshots_instructions": "A proteção contra captura de tela será temporariamente desativada, permitindo capturas de tela e gravações de tela. A proteção será reativada automaticamente quando você fechar e reabrir o BlueWallet.", + "donate": "Doar", + "donate_description": "Ajude-nos a manter o Blue gratuito!", + "electrum_error_connect_tor": "Não foi possível conectar ao servidor Electrum fornecido. Por favor, certifique-se de que o aplicativo Orbot está conectado e tente novamente.", + "encrypt_enc_and_pass": "Protegido por senha", + "encrypt_use_expl": "{type} será usada para confirmar sua identidade antes de fazer uma transação, desbloquear, exportar ou apagar uma carteira.", + "lightning_error_lndhub_uri_tor": "URI LNDhub inválido. Verifique se o Orbot está conectado e tente novamente.", + "lndhub_github": "Repositório no GitHub" }, "notifications": { "would_you_like_to_receive_notifications": "Gostaria de receber notificações quando receber pagamentos?", - "no_and_dont_ask": "Não e não me pergunte de novo", - "ask_me_later": "Me pergunte mais tarde" + "notifications_subtitle": "Pagamentos recebidos e confirmações de transação", + "no_and_dont_ask": "Não, e não me perguntar novamente.", + "permission_denied_message": "Você negou permissão para enviar notificações. Se você desejar de receber notificações, habilite-as nas configurações do seu dispositivo." }, "transactions": { "cancel_explain": "Nós vamos substituir esta transação por uma que envia os fundos para você com taxas mais altas. Isto, efetivamente, cancela a transação atual. Este ato é denominado RBF—Replace by Fee.", "cancel_no": "Esta transação não é substituível", "cancel_title": "Cancelar esta transação (RBF)", + "transaction_loading_error": "Houve um problema ao carregar a transação. Tente novamente mais tarde.", + "transaction_not_available": "Transação indisponível", "confirmations_lowercase": "{confirmations} confirmações", - "copy_link": "Copiar link", "expand_note": "Expandir anotação", "cpfp_create": "Criar", "cpfp_exp": "Criaremos outra transação que gasta sua transação não confirmada. A taxa total será maior do que a taxa de transação original, portanto, deve ser confirmada mais rapidamente. Este ato é denominado CPFP - Child Pays For Parent.", @@ -338,20 +342,22 @@ "cpfp_title": "Aumento de taxa (CPFP)", "details_balance_hide": "Esconder Saldo", "details_balance_show": "Mostrar Saldo", - "details_block": "Altura dos Blocos", "details_copy": "Copiar", - "details_copy_amount": "Copiar Quantia", "details_copy_block_explorer_link": "Copiar Link do Explorador de Blocos", "details_copy_note": "Copiar Nota", "details_copy_txid": "Copiar ID da transação", - "details_from": "Entrada", "details_inputs": "Entradas", "details_outputs": "Saídas", "date": "Data", "details_received": "Recebido", - "transaction_note_saved": "A anotação da transação foi salva com sucesso.", - "details_show_in_block_explorer": "Ver no Explorador de Blocos", + "details_view_in_browser": "Ver num navegador", "details_title": "Transação", + "incoming_transaction": "Transação de Entrada", + "outgoing_transaction": "Transação de Saída", + "expired_transaction": "Transação Expirada", + "pending_transaction": "Transação Pendente", + "offchain": "Offchain", + "onchain": "Onchain", "details_to": "Saída", "enable_offline_signing": "Esta carteira não está sendo usada em conjunto com uma para assinar offline. Deseja ativar agora?", "list_conf": "conf: {number}", @@ -361,8 +367,9 @@ "eta_10m": "Tempo estimado de chegada: Em ~10 minutos", "eta_3h": "Tempo estimado de chegada: Em ~3 horas", "eta_1d": "Tempo estimado de chegada: Em ~1 dia", - "view_wallet": "Ver {walletLabel}", "list_title": "Transações", + "list_title_received": "Recebido", + "transaction": "Transação", "open_url_error": "Incapaz de abrir o link com o navegador padrão. Por favor, mude o seu navegador padrão e tente novamente.", "rbf_explain": "Substituiremos essa transação por uma com uma taxa maior, assim ela será alocada mais rapidamente em um bloco. Isso é chamado de RBF—Replace by Fee.", "rbf_title": "Aumentar Taxa (RBF)", @@ -370,12 +377,38 @@ "status_cancel": "Cancelar Transação", "transactions_count": "Contagem das Transações", "txid": "ID da transação", - "updating": "Atualizando..." + "updating": "Atualizando...", + "watchOnlyWarningTitle": "Alerta de segurança", + "watchOnlyWarningDescription": "Tenha cuidado com golpistas que costumam usar carteiras “watch-only (somente para assistir)” para enganar os usuários. Essas carteiras não permitem controlar ou enviar fundos; elas apenas permitem que você visualize o saldo.", + "list_title_sent": "Enviado", + "custom_fee_warning_title": "Aviso", + "custom_fee_warning_description": "Taxas abaixo de 1 sat/vB são válidas, mas podem não ser propagadas devido às políticas dos nós.", + "details_eta_analyzing": "Analisando...", + "details_sent": "Enviado", + "details_section": "Detalhes", + "details_explorer": "explorador", + "details_network_fee": "Taxa de rede", + "details_to_address": "Para", + "details_id": "ID", + "details_note": "Nota", + "details_add_note": "adicionar", + "details_advanced": "Avançado", + "details_fee_rate": "Taxa", + "details_size": "Tamanho", + "details_virtual_size": "Tamanho virtual", + "details_tx_hex": "Hex da transação", + "details_inputs_count": "Entradas ({count})", + "details_outputs_count": "Saídas ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Carteira Bitcoin simples e poderosa", "add_create": "Criar", + "total_balance": "Saldo total", + "add_entropy_reset_title": "Resetar Entropia", + "add_entropy_reset_message": "Alterar o tipo de carteira irá reiniciar a entropia atual. Você quer prosseguir?", + "add_entropy": "Entropia", + "add_entropy_bytes": "{bytes} bytes de entropia", "add_entropy_generated": "{gen} bytes de entropia gerada", "add_entropy_provide": "Fornecer entropia por meio da rolagem de dados", "add_entropy_remain": "{gen} bytes de entropia gerada. Os bytes {rem} restantes serão obtidos do gerador de números aleatórios do Sistema.", @@ -389,31 +422,32 @@ "add_title": "Adicionar Carteira", "add_wallet_name": "Nome", "add_wallet_type": "Tipo", - "balance": "Saldo", + "add_wallet_seed_length": "Comprimento da Seed Phrase", + "add_wallet_seed_length_12": "12 Palavras", + "add_wallet_seed_length_24": "24 Palavras", "clipboard_bitcoin": "Você tem um endereço Bitcoin na área de transferência. Deseja utilizá-lo para uma transação?", "clipboard_lightning": "Você tem uma fatura Lightning na área de transferência. Deseja utilizá-la para uma transação?", + "clear_clipboard_on_import": "Limpar a área de transferência ao importar", "details_address": "Endereço", "details_advanced": "Avançado", "details_are_you_sure": "Tem certeza?", "details_connected_to": "Conectado a", - "details_del_wb_err": "O valor do saldo fornecido não corresponde ao saldo desta carteira. Tente novamente.", + "details_del_wb_err": "O valor do saldo fornecido não corresponde ao saldo desta carteira. Por favor, tente novamente.", "details_del_wb_q": "Esta carteira tem um saldo. Antes de continuar, esteja ciente de que você não será capaz de recuperar os seus fundos sem a seed desta carteira. Para evitar a remoção acidental, insira o saldo de sua carteira de {balance} satoshis.", "details_delete": "Apagar", "details_delete_wallet": "Apagar Carteira", "details_derivation_path": "caminho de derivação", - "details_display": "Exibir na Lista de Carteiras", + "details_display": "Exibir na tela inicial", "details_export_backup": "Exportar/Backup", "details_export_history": "Exportar Histórico para CSV", - "details_master_fingerprint": "Fingerprint Soberana", + "details_master_fingerprint": "Fingerprint mestra", "details_multisig_type": "multisig", - "details_no_cancel": "Não, cancelar", - "details_save": "Salvar", "details_show_xpub": "Exibir XPUB da Carteira", "details_show_addresses": "Mostrar endereços", "details_title": "Carteira", + "wallets": "Carteiras", "details_type": "Tipo", "details_use_with_hardware_wallet": "Usar com Carteira Hardware", - "details_wallet_updated": "Carteira atualizada", "details_yes_delete": "Sim, apagar", "enter_bip38_password": "Digite a senha para descriptografar", "export_title": "Exportar carteira", @@ -426,62 +460,92 @@ "import_imported": "Importada", "import_scan_qr": "Ler código ou importar arquivo", "import_success": "Sua carteira foi importada com sucesso.", - "import_success_watchonly": "A carteira foi importada.\nAVISO: Esta é uma carteira de consulta e você NÃO pode mover fundos com ela.", + "import_success_watchonly": "A carteira foi importada. AVISO: Esta é uma carteira de consulta e você NÃO pode mover fundos com ela.", "import_search_accounts": "Procurar contas", "import_title": "Importar", + "learn_more": "Saber mais", "import_discovery_title": "Descoberta", "import_discovery_subtitle": "Escolha uma carteira encontrada", "import_discovery_derivation": "Use um caminho de derivação personalizado", "import_discovery_no_wallets": "Nenhuma carteira foi encontrada.", - "import_derivation_found": "encontrado", - "import_derivation_found_not": "não encontrado", - "import_derivation_loading": "carregando...", - "import_derivation_subtitle": "Insira um caminho de derivação personalizado e tentaremos descobrir sua carteira", + "import_discovery_offline": "BlueWallet está atualmente no modo offline. Neste modo, ele não pode verificar a existência da carteira, então você precisará selecionar a carteira correta manualmente", + "import_derivation_found": "Encontrado", + "import_derivation_found_not": "Não encontrado", + "import_derivation_loading": "Carregando...", + "import_derivation_subtitle": "Forneça o caminho de derivação personalizado, e vamos tentar encontrar sua carteira.", "import_derivation_title": "Caminho de derivação", - "import_derivation_unknown": "desconhecido", - "import_wrong_path": "caminho de derivação errado", + "import_derivation_unknown": "Desconhecido", + "import_wrong_path": "Caminho de derivação errado", "list_create_a_button": "Adicionar agora", "list_create_a_wallet": "Adicionar uma carteira", - "list_create_a_wallet_text": "É grátis e você pode criar\nquantas quiser.", + "list_create_a_wallet_text": "É gratuito, e você pode criar \nquantas você quiser.", "list_empty_txs1": "Suas transações aparecerão aqui,", "list_empty_txs1_lightning": "A carteira Lightning faz transações super-rápidas e tem taxas ridiculamente baratas, ideal para transações diárias e de baixo valor.", "list_empty_txs2": "Comece por sua carteira.", "list_empty_txs2_lightning": "\nPara começar a usar clique em \"administrar fundos\" e recarregue o seu saldo.", "list_latest_transaction": "Transação mais recente", - "list_ln_browser": "Navegador LApp", "list_long_choose": "Escolher foto", - "list_long_clipboard": "Copiar da área de transferência", + "paste_from_clipboard": "Colar", + "import_file": "Importar arquivo", "list_long_scan": "Leia o código QR", "list_title": "Carteiras", "list_tryagain": "Tente novamente", "no_ln_wallet_error": "Antes de pagar uma fatura Lightning, você deve primeiro adicionar uma carteira Lightning.", "looks_like_bip38": "Parece que esta é uma chave privada protegida por senha (BIP38)", - "reorder_title": "Reordenar Carteiras", - "reorder_instructions": "Aperte e segure uma carteira para arrastá-la pela lista.", + "manage_title": "Gerenciamento de Carteiras", + "no_results_found": "Nenhum resultado encontrado.", "please_continue_scanning": "Por favor, continue a leitura.", "select_no_bitcoin": "Não há carteiras Bitcoin disponíveis no momento.", "select_no_bitcoin_exp": "É necessário ter uma carteira Bitcoin para recarregar as carteiras Lightning. Por favor, crie ou importe uma.", "select_wallet": "Escolher carteira", - "xpub_copiedToClipboard": "Copiado para a área de transferência", "pull_to_refresh": "Puxe para atualizar", - "warning_do_not_disclose": "Cuidado! Não divulgue.", + "warning_do_not_disclose": "Nunca compartilhe a informação abaixo.", + "scan_import": "Leia este código QR para importar sua carteira em outro aplicativo.", + "write_down_header": "Crie um backup manual", + "write_down": "Anote e armazene essas palavras com segurança. Use-as para restaurar sua carteira mais tarde.", + "wallet_type_this": "O tipo desta carteira é {type}.", + "share_number": "Compartilhar {number}", + "copy_ln_url": "Copie e armazene com segurança esta URL para restaurar sua carteira em um momento posterior.", + "copy_ln_public": "Copie e armazene essas informações com segurança para restaurar sua carteira mais tarde.", "add_ln_wallet_first": "Primeiro você deve adicionar uma carteira Lightning.", "identity_pubkey": "Identificar chave pública", - "xpub_title": "XPUB da Carteira" + "xpub_title": "XPUB da Carteira", + "more_info": "Mais informações", + "details_delete_wallet_error_message": "Houve um problema ao confirmar se esta carteira foi removida das notificações — isso pode ser devido a um problema de rede ou conexão ruim. Se você continuar, ainda poderá receber notificações de transações relacionadas a esta carteira, mesmo depois que ela for excluída.", + "details_delete_anyway": "Apagar mesmo assim", + "swipe_balance_hide": "Ocultar", + "swipe_balance_show": "Mostrar", + "drag_to_reorder": "Arraste para reordenar", + "clear_search": "Limpar busca", + "manage_wallets_search_placeholder": "Buscar carteiras, endereços, transações e notas" + }, + "total_balance_view": { + "display_in_bitcoin": "Mostrar em Bitcoin", + "hide": "Ocultar", + "display_in_sats": "Mostrar em sats", + "display_in_fiat": "Mostrar em {currency}", + "title": "Saldo total", + "explanation": "Veja o saldo total de todas as suas carteiras na tela principal." }, "multisig": { - "multisig_vault": "Cofre", + "multisig_vault": "Cofre Multisig", "default_label": "Cofre Multisig", "multisig_vault_explain": "Melhor segurança para grandes valores", "provide_signature": "Fornecer assinatura", + "provide_signature_details": "Use seu dispositivo e carteira onde a chave está localizada para assinar esta transação", + "provide_signature_details_bluewallet": "Na BlueWallet, vá à tela de Enviar e selecione", + "provide_signature_next_steps": "Ler ou Importar transação assinada", + "provide_signature_next_steps_details": "Depois que sua carteira tiver assinado a transação com sucesso, escaneie o código QR fornecido ou importe o arquivo assinado e revise todos os detalhes da transação antes de transmitir", "vault_key": "Chave {number} do Cofre", "required_keys_out_of_total": "Chaves obrigatórias do total", "fee": "Taxa: {number}", "fee_btc": "{number} BTC", "confirm": "Confirmar", "header": "Enviar", - "share": "Compartilhar", + "share": "Compartilhar...", "view": "Ver", + "shared_key_detected": "Coassinatura compartilhada", + "shared_key_detected_question": "Uma coassinatura foi compartilhada com você, deseja importá-la?", "manage_keys": "Gerenciar chaves", "how_many_signatures_can_bluewallet_make": "quantas assinaturas a BlueWallet pode fazer", "signatures_required_to_spend": "Assinaturas necessárias {number}", @@ -495,9 +559,9 @@ "wrapped_segwit_title": "Melhor compatibilidade", "legacy_title": "Antigo", "co_sign_transaction": "Assinar uma transação", - "what_is_vault": "Um Cofre é uma", - "what_is_vault_numberOfWallets": "carteira", - "what_is_vault_wallet": "multisig {m}-de-{n}.", + "what_is_vault": "Um Vault é um", + "what_is_vault_numberOfWallets": " {m}-de-{n} multisig ", + "what_is_vault_wallet": "carteira.", "vault_advanced_customize": "Configurações do Cofre", "needs": "Necessita", "what_is_vault_description_number_of_vault_keys": "{m} chaves do cofre", @@ -507,20 +571,21 @@ "quorum_header": "Quantidade", "of": "de", "wallet_type": "Tipo da Carteira", - "invalid_mnemonics": "Esta frase mnemônica não parece ser válida.", - "invalid_cosigner": "Dados de cossignatário inválidos", + "invalid_mnemonics": "Esta seed mnemônica não parece ser válida. ", + "invalid_cosigner": "Coassinatura inválida", "not_a_multisignature_xpub": "Esta não é uma XPUB de uma carteira multisig!", - "invalid_cosigner_format": "Cossignatário incorreto: não é um cossignatário para o formato {format}.", + "invalid_cosigner_format": "Coassinatura errada: Esta não é uma coassinatura para o formato {format}.", "create_new_key": "Criar nova", "scan_or_open_file": "Ler código ou abrir arquivo", "i_have_mnemonics": "Eu tenho uma seed para esta chave...", "type_your_mnemonics": "Insira uma seed para importar a sua chave do Cofre", - "this_is_cosigners_xpub": "Esta é a XPUB do cossignatário, pronta para ser importada para outra carteira. É seguro compartilhá-la.", + "this_is_cosigners_xpub": "Este é o XPUB da coassinatura—pronto para ser importado para outra carteira. É seguro compartilhá-lo.", + "this_is_cosigners_xpub_airdrop": "Se você compartilhar via AirDrop, os recebedores devem estar na tela de coordenação.", "wallet_key_created": "Sua chave do Cofre foi criada. Reserve um momento para fazer backup com segurança de sua seed.", "are_you_sure_seed_will_be_lost": "Você tem certeza? Sua seed mnemônica será perdida se você não tiver um backup", "forget_this_seed": "Esquecer esta seed e usar a XPUB no lugar.", - "view_edit_cosigners": "Ver/Editar cossignatários", - "this_cosigner_is_already_imported": "Este cossignatário já foi importado.", + "view_edit_cosigners": "Ver/Editar Coassinaturas", + "this_cosigner_is_already_imported": "Esta coassinatura já foi importada antes.", "export_signed_psbt": "Exportar PSBT Assinado", "input_fp": "Inserir fingerprint", "input_fp_explain": "Pule para usar a padrão (00000000)", @@ -536,7 +601,7 @@ "ms_help_title3": "Backups do Cofre", "ms_help_3": "Nas opções da carteira, você encontrará o backup do seu Cofre e o backup somente-leitura. O backup serve como um mapa para sua carteira. É essencial para a recuperação da carteira caso você perca alguma seed.", "ms_help_title4": "Importando Cofres", - "ms_help_4": "Para importar uma Multisig, use o arquivo de backup da sua multisig e o recurso de importação. Se você só tem seeds e XPUBs, você pode usar os campos de importação individuais quando estiver criando as chaves do Cofre.", + "ms_help_4": "Para importar uma Multisig, use o arquivo de backup da sua multisig e o recurso de importação. Se você só tem seeds e XPUBs, você pode usar os campos de importação individuais quando estiver criando as chaves do Cofre.", "ms_help_title5": "Modo Avançado", "ms_help_5": "Por padrão, a BlueWallet vai gerar um Cofre 2 de 3. Para alterar a quantidade mínima ou máxima do número de assinaturas ou alterar o tipo de endereço, ative o Modo Avançado nas Configurações." }, @@ -546,20 +611,33 @@ "enter_address": "Insira o endereço", "check_address": "Verificar endereço", "no_wallet_owns_address": "Nenhuma das carteiras disponíveis possui o endereço fornecido.", - "view_qrcode": "Ver o QRCode" + "view_qrcode": "Ver QR Code" + }, + "autofill_word": { + "enter": "Forneça sua seed mnemônica parcial", + "generate_word": "Gerar última palavra", + "error": "Não foi fornecida seed mnemônica parcial no formato de 11 OU 23 palavras. Por favor, tente novamente.", + "title": "Última palavra da seed" }, "cc": { "change": "Troco", "coins_selected": "Moedas selecionadas ({number})", "selected_summ": "{value} selecionado", - "empty": "Esta carteira ainda não tem nenhuma moeda", + "empty": "Esta carteira não tem fundos no momento.", "freeze": "Congelar", "freezeLabel": "Congelar", "freezeLabel_un": "Descongelar", "header": "Controle de moedas", "use_coin": "Usar moeda", "use_coins": "Usar moedas", - "tip": "Permite que você veja, marque, congele ou selecione moedas para gerenciar melhor sua carteira." + "tip": "Permite que você veja, marque, congele ou selecione moedas para gerenciar melhor sua carteira.", + "sort_asc": "Crescente", + "sort_desc": "Decrescente", + "sort_height": "Altura", + "sort_value": "Valor", + "sort_label": "Nome", + "sort_status": "Status", + "sort_by": "Ordenar por" }, "units": { "BTC": "BTC", @@ -568,6 +646,8 @@ "sats": "sats" }, "addresses": { + "copy_private_key": "Copiar chave privada", + "sensitive_private_key": "Aviso: as chaves privadas são extremamente sensíveis. Continuar?", "sign_title": "Assinar/Verificar Mensagem", "sign_help": "Aqui você pode criar ou verificar uma assinatura realizada por um endereço Bitcoin", "sign_sign": "Assinar", @@ -601,9 +681,24 @@ }, "bip47": { "payment_code": "Código de pagamento", - "payment_codes_list": "Lista de códigos de pagamento", - "who_can_pay_me": "Quem pode me pagar:", + "contacts": "Contatos", + "bip47_explain": "Código reutilizável e compartilhável", + "bip47_explain_subtitle": "BIP47", "purpose": "Código para compartilhar (BIP47)", + "pay_this_contact": "Pague este contato", + "rename_contact": "Renomear contato", + "copy_payment_code": "Copiar Payment Code", + "hide_contact": "Esconder Contato", + "rename": "Renomear", + "provide_name": "Forneça um novo nome para este contato", + "add_contact": "Adicionar contato", + "provide_payment_code": "Forneça Payment Code", + "invalid_pc": "Payment Code inválido", + "notification_tx_unconfirmed": "A transação de notificação ainda não foi confirmada, aguarde.", + "failed_create_notif_tx": "Falha para criar transação on-chain", + "onchain_tx_needed": "Necessária transação on-chain", + "notif_tx_sent": "Transação de notificação enviada. Por favor espere a confirmação", + "notif_tx": "Transação de notificação", "not_found": "Código de pagamento não encontrado" } } diff --git a/loc/pt_pt.json b/loc/pt_pt.json index 586ddd24111..af710f77052 100644 --- a/loc/pt_pt.json +++ b/loc/pt_pt.json @@ -1,33 +1,51 @@ { "_": { - "bad_password": "Pasword errada. Por favor, tente novamente.", + "bad_password": "Palavra-passe incorreta. Por favor, tente novamente.", "cancel": "Cancelar", + "copied": "Copiado!", "continue": "Continuar", - "enter_password": "Inserir password", - "never": "nunca", - "of": "6\n{number} de {total}", + "clipboard": "Área de Transferência", + "discard_changes": "Descartar alterações?", + "discard_changes_explain": "Tem alterações não guardadas. Tem a certeza de que deseja descartá-las e sair do ecrã?", + "enter_password": "Inserir palavra-passe", + "never": "Nunca", + "of": "{number} de {total}", "ok": "OK", - "storage_is_encrypted": "O armazenamento está encriptado. Uma password é necessária para desencriptar", + "enter_url": "Inserir URL", + "storage_is_encrypted": "O armazenamento está encriptado. É necessária uma palavra-passe para desencriptar.", "yes": "Sim", "no": "Não", - "save": "Salvar", + "save": "Guardar...", "seed": "Seed", "success": "Sucesso", - "wallet_key": "Chave da carteira" + "wallet_key": "Chave da carteira", + "close": "Fechar", + "change_input_currency": "Alterar moeda da entrada", + "refresh": "Atualizar", + "pick_image": "Escolher da biblioteca", + "pick_file": "Escolher ficheiro", + "enter_amount": "Inserir montante", + "qr_custom_input_button": "Toque 10 vezes para inserir uma entrada personalizada", + "unlock": "Desbloquear", + "port": "Porta", + "ssl_port": "Porta SSL", + "suggested": "Sugerido" }, "azteco": { "codeIs": "O seu código é", - "errorBeforeRefeem": "Antes de obter necessita de adicionar uma carteira Bitcoin", + "errorBeforeRefeem": "Antes de efetuar o resgate, é necessário adicionar uma carteira Bitcoin.", "errorSomething": "Ocorreu um erro. Este código ainda é válido?", "redeem": "Enviar para a carteira", "redeemButton": "Obter", "success": "Sucesso", + "successMessage": "Código resgatado com sucesso! Os fundos devem chegar à carteira Bitcoin em breve.", "title": "Obter voucher azte.co" }, "entropy": { "save": "Guardar", "title": "Entropia", - "undo": "Desfazer" + "undo": "Desfazer", + "amountOfEntropy": "{bits} de {limit} bits" }, "errors": { "broadcast": "Transmissão falhou", @@ -35,56 +53,69 @@ "network": "Erro da rede" }, "lnd": { - "errorInvoiceExpired": "Factura expirada", + "errorInvoiceExpired": "A fatura expirou.", "expired": "Expirado", + "expiresIn": "Expira em {time} minutos", "payButton": "Paga", - "placeholder": "Factura", + "payment": "Pagamento", + "placeholder": "Fatura ou endereço", "potentialFee": "Taxa provável: {fee}", "refill": "Carregar", "refill_create": "Para continuar, crie uma carteira Bitcoin para recarregar.", "refill_external": "Recarregar com carteira externa", - "refill_lnd_balance": "Carregar o saldo da Lightning wallet", - "sameWalletAsInvoiceError": "Não pode pagar uma factura com a mesma wallet usada para a criar.", - "title": "gerir saldo" + "refill_lnd_balance": "Recarregar o saldo da carteira Lightning", + "sameWalletAsInvoiceError": "Não pode pagar uma fatura com a mesma carteira usada para a criar.", + "title": "Gerir saldo" }, "lndViewInvoice": { "additional_info": "Informação adicional", "for": "Para:", - "open_direct_channel": "Abrir canal directo com este node:", + "lightning_invoice": "Fatura Lightning", + "please_pay_between_and": "Por favor pague entre {min} e {max}", "please_pay": "Por favor pague", - "preimage": "Preimage", + "preimage": "Pré-imagem", "sats": "sats", - "wasnt_paid_and_expired": "Esta factura não foi paga e expirou" + "date_time": "Data e Hora", + "wasnt_paid_and_expired": "Esta fatura não foi paga e expirou" }, "plausibledeniability": { - "create_fake_storage": "Criar armazenamento encriptado FALSO", - "create_password": "Criar password", - "create_password_explanation": "Password para armazenamento FALSO não deve coincidir com a password principal", - "help": "Em algumas circunstâncias, pode ser forçado a relevar uma password. Para manter as suas moedas seguras, A BlueWallet pode criar outro armazenamento encriptado, com uma password diferente. Sobre pressão, pode revelar esta password a um terceiro. Se inserida na BlueWallet, esta vai abrir um armazenamento \"falso\". Que vai parecer legítimo a um terceiro, mas que secretamente vai manter o seu armazenamento principal com as moedas em segurança.", - "help2": "Este novo armazenamento é completamente funcional, e pode guardar um valor minímo para parecer mais real.", - "password_should_not_match": "Password para armazenamento FALSO não deve coincidir com a password principal", - "passwords_do_not_match": "Passwords não coincidem, tente novamente", - "retype_password": "Inserir password novamente", - "success": "Sucesso", + "create_fake_storage": "Criar armazenamento encriptado", + "create_password_explanation": "A palavra-passe para armazenamento FALSO não deve coincidir com a palavra-passe principal.", + "help": "Em algumas circunstâncias, pode ser forçado a revelar uma palavra-passe. Para manter as suas moedas seguras, a BlueWallet pode criar outro armazenamento encriptado, com uma palavra-passe diferente. Sob pressão, pode revelar esta palavra-passe a um terceiro. Se inserida na BlueWallet, esta vai abrir um armazenamento \"falso\". Que vai parecer legítimo a um terceiro, mas que secretamente vai manter o seu armazenamento principal com as moedas em segurança.", + "help2": "Este novo armazenamento é completamente funcional, e pode guardar um valor mínimo para parecer mais real.", + "password_should_not_match": "A palavra-passe está atualmente em uso. Por favor, tente uma palavra-passe diferente.", "title": "Negação plausível" }, "pleasebackup": { - "ask": "Salvou a frase de backup da sua carteira? Esta frase de backup é necessária para pode ter acesso aos fundos em caso de perder este dispositivo. Sem a frase de backup, os fundos serão perdidos permanentemente.", - "ask_no": "Não, eu não tenho", - "ask_yes": "Sim, eu fiz", - "text_lnd": "Por favor, reserve um momento para guardar este backup. É o seu backup que lhe permite restaurar a carteira em outro dispositivo." + "ask": "Guardou a frase de backup da sua carteira? Esta frase de backup é necessária para poder ter acesso aos fundos caso perca este dispositivo. Sem a frase de backup, os fundos serão perdidos permanentemente.", + "ask_no": "Não, não fiz isso.", + "ask_yes": "Sim, fiz isso.", + "ok": "OK, já anotei.", + "ok_lnd": "Ok, eu guardei.", + "text": "Por favor, reserve um momento para anotar esta frase mnemónica num pedaço de papel.\nÉ o seu backup e pode usá-la para recuperar a carteira.", + "text_lnd": "Por favor, reserve um momento para guardar este backup. É o seu backup que lhe permite restaurar a carteira em outro dispositivo.", + "title": "A sua carteira foi criada..." }, "receive": { "details_create": "Criar", "details_label": "Descrição", "details_setAmount": "Quantia a receber", - "details_share": "partilhar", - "header": "receber" + "details_share": "Partilhar...", + "address_not_found": "Não é possível gerar o endereço de receção.", + "header": "Receber", + "reset": "Reiniciar", + "maxSats": "O montante máximo é {max} sats", + "maxSatsFull": "O montante máximo é {max} sats ou {currency}", + "minSats": "O montante mínimo é {min} sats", + "minSatsFull": "O montante mínimo é {min} sats ou {currency}", + "qrcode_for_the_address": "Código QR para o endereço", + "bip47_explanation": "Os códigos de pagamento são um endereço universal que evita a divulgação dos endereços da sua carteira. Nem todos os serviços os suportam." }, "send": { - "broadcastButton": "Transmitir", + "provided_address_is_invoice": "Este endereço parece ser para uma fatura Lightning. Por favor, aceda à sua carteira Lightning para efetuar o pagamento desta fatura.", + "broadcastButton": "Difundir", "broadcastError": "Erro", - "broadcastNone": "Input transaction hash", + "broadcastNone": "Inserir Hex da Transação", "broadcastPending": "Pendente ", "broadcastSuccess": "Sucesso", "confirm_header": "Confirmar", @@ -95,240 +126,579 @@ "create_details": "Detalhes", "create_fee": "Taxa", "create_memo": "Nota pessoal", - "create_this_is_hex": "Este é o hex da transacção, assinado e pronto para ser difundido para a network. Continuar?", + "create_satoshi_per_vbyte": "Satoshi por vByte", + "create_this_is_hex": "Este é o hex da transação, assinado e pronto para ser difundido para a rede. Continuar?", "create_to": "Para", - "create_tx_size": "Tamanho TX", + "create_tx_size": "Tamanho da Transação", "create_verify": "Verificar no coinb.in", + "details_insert_contact": "Inserir Contacto", "details_add_rec_add": "Adicionar destinatário", "details_add_rec_rem": "Remover destinatário", + "details_add_recc_rem_all_alert_description": "Tem a certeza de que deseja remover todos os destinatários?", + "details_add_rec_rem_all": "Remover Todos os Destinatários", + "details_recipients_title": "Destinatários", + "details_recipient_title": "Destinatário #{number} de #{total}", + "please_complete_recipient_title": "Destinatário Incompleto", + "please_complete_recipient_details": "Preencha os dados do destinatário #{number} antes de adicionar um novo destinatário.", "details_address": "Endereço", - "details_address_field_is_not_valid": "Campo de endereço não é válido", + "details_address_field_is_not_valid": "Este endereço não é válido", "details_adv_fee_bump": "Permitir aumentar taxa", "details_adv_full": "Use o saldo total", - "details_adv_full_sure": "Tem a certeza de que deseja usar o saldo total da sua carteira para esta transacção?", + "details_adv_full_sure": "Tem a certeza de que deseja usar o saldo total da sua carteira para esta transação?", + "details_adv_full_sure_frozen": "Tem a certeza de que deseja usar o saldo total da sua carteira para esta transação? Tenha em atenção que as moedas congeladas estão excluídas.", "details_adv_import": "Importar transação", - "details_amount_field_is_not_valid": "Campo de quantia não é válido", - "details_amount_field_is_less_than_minimum_amount_sat": "A quantidade indicada é muito pequena. Por favor, indique uma quantidade superior a 500 sats.", + "details_adv_import_qr": "Importar Transação (QR)", + "details_amount_field_is_not_valid": "O montante não é válido", + "details_amount_field_is_less_than_minimum_amount_sat": "O montante indicado é muito pequeno. Por favor, indique um montante superior a 500 sats.", "details_create": "Criar", - "details_fee_field_is_not_valid": "Campo de taxa não é válido", + "details_error_decode": "Não é possível descodificar o endereço Bitcoin", + "details_fee_field_is_not_valid": "A taxa não é válida.", + "details_frozen": "{amount} BTC está congelado.", "details_next": "Próximo", - "details_no_signed_tx": "O ficheiro seleccionado não contém uma transacção que possa ser importada.", - "details_note_placeholder": "Nota pessoal", - "details_scan": "Scan", + "details_no_signed_tx": "O ficheiro selecionado não contém uma transação que possa ser importada.", + "details_note_placeholder": "Nota Pessoal", + "details_scan": "Digitalizar", + "details_scan_hint": "Toque duas vezes para fazer scan ou importar um destino", + "details_scan_error": "Erro de scan", "details_total_exceeds_balance": "O valor total excede o saldo disponível.", - "details_wallet_before_tx": "Antes de criar uma transacção, deve primeiro adicionar uma carteira Bitcoin.", + "details_total_exceeds_balance_frozen": "O valor do envio excede o saldo disponível. Tenha em atenção que as moedas congeladas estão excluídas.", + "details_unrecognized_file_format": "Formato de ficheiro não reconhecido", + "details_wallet_before_tx": "Antes de criar uma transação, deve primeiro adicionar uma carteira Bitcoin.", "dynamic_init": "Inicializar", "dynamic_next": "Próximo", "dynamic_prev": "Anterior", "dynamic_start": "Começar", "dynamic_stop": "Parar", + "fee_custom": "Escolher", + "insert_custom_fee": "Inserir taxa", "fee_10m": "10m", "fee_1d": "1d", "fee_3h": "3h", - "fee_custom": "Escolher", "fee_fast": "Rápido", "fee_medium": "Médio", + "fee_replace_minvb": "A taxa total (satoshi por vByte) que pretende pagar deve ser superior a {min} sat/vByte.", + "fee_satvbyte": "em sat/vByte", "fee_slow": "Lento", "header": "Enviar", "input_clear": "Limpar", "input_done": "Feito", "input_paste": "Colar", "input_total": "Total:", - "permission_camera_message": "Precisamos da sua permissão para usar sua câmera", - "permission_camera_title": "Permissão para usar a câmera", + "permission_camera_message": "Precisamos da sua permissão para utilizar a sua câmera", "psbt_sign": "Assinar transação", + "invalid_psbt": "O PSBT fornecido é inválido.", "open_settings": "Abrir configurações", - "permission_storage_later": "Perguntar mais tarde", - "permission_storage_message": "A BlueWallet precisa da sua permissão para aceder ao seu armazenamento para guardar esta transação.", - "permission_storage_denied_message": "A BlueWallet não conseguiu gravar este ficheiro. Por favor aceda às definições do seu dispositivo e habilite as permissões de armazenamento.", + "permission_storage_denied_message": "A BlueWallet não conseguiu gravar este ficheiro. Por favor aceda às definições do seu dispositivo e ative as permissões de armazenamento.", "permission_storage_title": "Permissão de acesso ao armazenamento", "psbt_clipboard": "Copiar para área de transferência", "psbt_this_is_psbt": "Esta é uma transação bitcoin parcialmente assinada (PSBT). Conclua a assinatura com a sua carteira de hardware.", "psbt_tx_export": "Exportar para ficheiro", "no_tx_signing_in_progress": "Nenhuma assinatura de transação em progresso.", "outdated_rate": "A taxa foi atualizada pela última vez em: {date}", - "psbt_tx_open": "Abrir transacção assinada", - "psbt_tx_scan": "Scan transacção assinada", + "psbt_tx_open": "Abrir transação assinada", + "psbt_tx_scan": "Ler transação assinada", + "qr_error_no_qrcode": "Não foi possível encontrar um código QR válido na imagem selecionada. Certifique-se de que a imagem contém apenas um código QR e nenhum outro conteúdo, como texto ou botões.", + "reset_amount": "Redefinir valor", + "reset_amount_confirm": "Deseja redefinir o valor?", "success_done": "Feito", - "txSaved": "O ficheiro de transacção ({filePath}) foi guardado na pasta Downloads.", + "txSaved": "O ficheiro da transação ({filePath}) foi guardado.", + "file_saved_at_path": "O ficheiro ({filePath}) foi guardado.", + "cant_send_to_silentpayment_adress": "Esta carteira não pode enviar para endereços SilentPayment", + "cant_send_to_bip47": "Esta carteira não pode enviar para códigos de pagamento BIP47", + "cant_find_bip47_notification": "Adicione primeiro este Código de Pagamento aos contactos", "problem_with_psbt": "Problema com PSBT" }, "settings": { "about": "Sobre", - "about_awesome": "Construído com o incríveis", + "about_awesome": "Construído com os incríveis", "about_backup": "Faça sempre backup das suas chaves!", "about_free": "BlueWallet é um projeto gratuito e de código aberto. Criado por utilizadores de Bitcoin.", "about_license": "Licença MIT", - "about_release_notes": "Release notes", - "about_review": "Deixa-nos uma review", - "about_selftest": "Run self test", + "about_release_notes": "Notas de versão", + "about_review": "Deixe-nos uma avaliação", + "performance_score": "Pontuação de desempenho: {num}", + "run_performance_test": "Testar desempenho", + "about_selftest": "Executar autoteste", + "block_explorer_invalid_custom_url": "O URL fornecido não é válido. Introduza um URL válido que comece por http:// ou https://.", + "about_selftest_electrum_disabled": "O autoteste não está disponível no modo offline do Electrum. Desative o modo offline e tente novamente.", + "about_selftest_ok": "Todos os testes internos foram aprovados com sucesso. A carteira funciona bem.", "about_sm_github": "GitHub", - "about_sm_discord": "Servidor Discord", - "about_sm_telegram": "Chat Telegram", - "about_sm_twitter": "Segue-nos no Twitter", - "advanced_options": "Opções Avançadas", - "biom_10times": "Você tentou digitar sua senha 10 vezes. Você gostaria de resetar seu armazenamento? Isso irá remover todas suas carteiras e descriptografar seu armazenamento.", - "biom_conf_identity": "Por favor, confirme sua identidade.", - "biom_no_passcode": "Seu dispositivo não possui uma palavra-passe. Para proceder, por favor configure uma palavra-passe no app de Configurações.", - "biom_remove_decrypt": "Todas suas carteiras serão removidas e seu armazenamento será descriptografado. Tem certeza que deseja proceder?", + "about_sm_telegram": "Canal Telegram", + "privacy_temporary_screenshots": "Permitir captura de ecrã", + "privacy_temporary_screenshots_instructions": "A proteção contra captura de ecrã será temporariamente desativada, permitindo capturas e gravações de ecrã. A proteção será reativada automaticamente quando fechar e reabrir o BlueWallet.", + "biometrics": "Biometria", + "biometrics_no_longer_available": "As configurações do seu dispositivo foram alteradas e já não correspondem às configurações de segurança selecionadas na aplicação. Reative a biometria ou a senha e reinicie a aplicação para aplicar essas alterações.", + "biom_10times": "Tentou introduzir a sua palavra-passe 10 vezes. Deseja redefinir o seu armazenamento? Isto irá remover todas as carteiras e desencriptar o seu armazenamento.", + "biom_conf_identity": "Por favor, confirme a sua identidade.", + "biom_no_passcode": "O seu dispositivo não tem uma senha ou biometria ativada. Para continuar, configure uma senha ou biometria na aplicação Definições.", + "biom_remove_decrypt": "Todas as suas carteiras serão removidas e o seu armazenamento será desencriptado. Tem a certeza de que deseja continuar?", "currency": "Moeda", - "default_desc": "Quando desactivado, a BlueWallet abrirá imediatamente a carteira seleccionada no lançamento.", - "default_info": "Carteira padrão", + "currency_source": "A taxa é obtida a partir de", + "currency_fetch_error": "Ocorreu um erro ao obter a taxa para a moeda selecionada.", "default_title": "A abrir", - "default_wallets": "Ver todas as carteiras", + "donate": "Doar", + "donate_description": "Ajude-nos a manter a Blue gratuita!", + "lndhub_github": "Repositório GitHub", "electrum_connected": "Conectado", "electrum_connected_not": "Desconectado", - "electrum_error_connect": "Não é possível conectar ao servidor Electrum fornecido", + "electrum_error_connect": "Não é possível ligar-se ao servidor Electrum fornecido", + "electrum_error_connect_tor": "Não é possível ligar ao servidor Electrum fornecido. Certifique-se de que a aplicação Orbot está ligada e tente novamente.", + "lndhub_uri": "E.x., {example}", + "electrum_host": "E.x., {example}", + "electrum_offline_mode": "Modo Offline", + "electrum_offline_description": "Quando ativado, as suas carteiras Bitcoin não tentarão obter saldos ou transações.", + "electrum_port": "Port, normalmente {example}", + "use_ssl": "Usar SSL", "electrum_saved": "As alterações foram guardadas com sucesso. Pode ser necessário reiniciar para que as alterações tenham efeito.", - "electrum_settings_server": "Electrum server", + "set_electrum_server_as_default": "Definir {server} como servidor Electrum predefinido?", + "set_lndhub_as_default": "Definir {url} como servidor LNDhub predefinido?", + "electrum_settings_server": "Servidor Electrum", "electrum_status": "Estado", - "electrum_clear_alert_title": "Limpar histórico?", - "electrum_clear_alert_message": "Você realmente deseja limpar o histórico de servidores electrum?", - "electrum_clear_alert_cancel": "Cancelar", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Selecionar", - "electrum_reset": "Resetar para o padrão", - "electrum_history": "Histórico do servidor", - "electrum_clear": "Limpar", + "electrum_preferred_server": "Servidor Predefinido", + "electrum_preferred_server_description": "Insira o servidor que deseja que a sua carteira utilize para todas as atividades relacionadas com Bitcoin. Depois de definido, a sua carteira utilizará exclusivamente este servidor para verificar saldos, enviar transações e obter dados da rede. Certifique-se de que confia neste servidor antes de o definir.", + "electrum_unable_to_connect": "Não é possível ligar a {server}.", + "electrum_history": "Histórico", + "electrum_reset_to_default": "Isso permitirá que o BlueWallet escolha aleatoriamente um servidor da lista de servidores.", + "electrum_reset": "Restaurar para predefinição", + "electrum_reset_to_default_and_clear_history": "Restaurar para predefinição e apagar histórico", "encrypt_decrypt": "Desencriptar armazenamento", - "encrypt_decrypt_q": "Tem certeza de que deseja desencriptar o seu armazenamento? Isso permitirá que suas carteiras sejam acessadas sem uma senha.", - "encrypt_enc_and_pass": "Encriptado e protegido por password", + "encrypt_decrypt_q": "Tem a certeza de que deseja desencriptar o seu armazenamento? Isto permitirá que as suas carteiras sejam acedidas sem uma palavra-passe.", + "encrypt_enc_and_pass": "Palavra-passe Protegida", + "encrypt_storage_explanation_headline": "Ativar Encriptação de Armazenamento", + "encrypt_storage_explanation_description_line1": "A ativação da Encriptação de Armazenamento adiciona uma camada extra de proteção à sua aplicação, protegendo a forma como os seus dados são armazenados no seu dispositivo. Isto torna mais difícil para qualquer pessoa aceder às suas informações sem permissão.", + "encrypt_storage_explanation_description_line2": "No entanto, é importante saber que esta encriptação apenas protege o acesso às suas carteiras armazenadas no dispositivo. Não coloca uma palavra-passe ou qualquer proteção extra nas próprias carteiras.", + "i_understand": "Eu compreendo", + "block_explorer": "Explorador de Blocos", + "block_explorer_preferred": "Utilizar explorador de blocos predefinido", + "block_explorer_error_saving_custom": "Erro ao guardar explorador de blocos predefinido", "encrypt_title": "Segurança", "encrypt_tstorage": "Armazenamento", "encrypt_use": "Usar {type}", + "set_as_preferred": "Definir como predefinido", + "set_as_preferred_electrum": "Definir {host}:{port} como servidor predefinido desativará a ligação a um servidor sugerido aleatoriamente.", + "encrypted_feature_disabled": "Esta funcionalidade não pode ser utilizada com a encriptação de armazenamento ativada.", + "encrypt_use_expl": "{type} será usado para confirmar a sua identidade antes de fazer uma transação, desbloquear, exportar ou eliminar uma carteira.", + "biometrics_fail": "Se {type} não estiver ativado ou não conseguir desbloquear, pode usar a senha do seu dispositivo como alternativa.", "general": "Geral", - "general_adv_mode": "Ligar modo avançado", - "general_adv_mode_e": "Quando activado, verá opções avançadas, como diferentes tipos de carteira, a capacidade de especificar a instância do LNDHub à qual se deseja conectar e a entropia personalizada durante a criação da carteira.", "general_continuity": "Continuidade", - "general_continuity_e": "Quando activado, poderá visualizar carteiras seleccionadas e transacções, usando os seus outros dispositivos conectados ao Apple iCloud.", - "groundcontrol_explanation": "GroundControl é um servidor de notificações push de código aberto gratuito para carteiras bitcoin. Pode instalar seu próprio servidor GroundControl e colocar sua URL aqui para não depender da infraestrutura da BlueWallet. Deixe em branco para usar o padrão", - "header": "definições", + "general_continuity_e": "Quando ativado, poderá visualizar carteiras selecionadas e transações, usando os seus outros dispositivos conectados ao Apple iCloud.", + "groundcontrol_explanation": "GroundControl é um servidor de notificações push gratuito e de código aberto para carteiras Bitcoin. Pode instalar o seu próprio servidor GroundControl e colocar o seu URL aqui para não depender da infraestrutura da BlueWallet. Deixe em branco para usar o servidor predefinido do GroundControl.", + "header": "Definições", "language": "Idioma", + "last_updated": "Última Atualização", + "language_isRTL": "É necessário reiniciar a BlueWallet para que a alteração de idioma tenha efeito.", + "license": "Licença", + "lightning_error_lndhub_uri": "URI LNDhub Inválido", + "lightning_error_lndhub_uri_tor": "URI LNDhub Inválido. Certifique-se de que a aplicação Orbot está conectada e tente novamente.", "lightning_saved": "As alterações foram guardadas com sucesso", - "lightning_settings": "Definições do Lightning", + "lightning_settings": "Definições de Lightning", + "lightning_settings_explain": "Para se ligar ao seu próprio nó LND, instale o LNDhub e insira o URL aqui nas definições. Apenas as carteiras criadas após guardar as alterações se ligarão ao LNDhub especificado.", "network": "Rede", - "network_broadcast": "Transmitir transacção", - "network_electrum": "Electrum server", + "network_broadcast": "Transmitir transação", + "network_electrum": "Servidor Electrum", + "electrum_suggested_description": "Quando um servidor predefinido não estiver configurado, um servidor sugerido será selecionado aleatoriamente para uso.", + "not_a_valid_uri": "URI inválido", "notifications": "Notificações", "open_link_in_explorer": "Abrir link no explorador", - "password": "Password", - "password_explain": "Definir a password para desencriptar o armazenamento", - "passwords_do_not_match": "Passwords não coincidem", - "plausible_deniability": "Negação plausível...", + "password": "Palavra-passe", + "password_explain": "Introduza a palavra-passe que usará para desbloquear o seu armazenamento.", + "plausible_deniability": "Negação Plausível", "privacy": "Privacidade", - "privacy_system_settings": "Configurações do Sistema", + "privacy_read_clipboard": "Copiar da Área de Transferência", + "privacy_system_settings": "Definições do Sistema", "privacy_quickactions": "Atalhos da Carteira", - "push_notifications": "Notificações via push", - "retype_password": "Inserir password novamente", + "privacy_quickactions_explanation": "Toque e mantenha pressionado o ícone da aplicação BlueWallet para visualizar rapidamente o saldo da sua carteira.", + "privacy_clipboard_explanation": "Fornecer atalhos se um endereço ou fatura for encontrado na sua área de transferência.", + "privacy_do_not_track": "Desativar Ferramentas Analíticas", + "privacy_do_not_track_explanation": "As informações sobre desempenho e fiabilidade não serão enviadas para análise.", + "rate": "Taxa", + "push_notifications_explanation": "Ao ativar as notificações, o token do seu dispositivo será enviado ao servidor, juntamente com os endereços das carteiras e os IDs das transações para todas as carteiras e transações realizadas após a ativação das notificações. O token do dispositivo é usado para enviar notificações, e as informações da carteira permitem-nos notificá-lo sobre Bitcoin a receber ou confirmações de transações.\n\nApenas as informações após a ativação das notificações são transmitidas — nada anterior a isso é recolhido.\n\nDesativar as notificações removerá todas estas informações do servidor. Além disso, eliminar uma carteira da aplicação também removerá as informações associadas à mesma do servidor.", + "selfTest": "Autoteste", "save": "Guardar", "saved": "Guardado", + "success_transaction_broadcasted": "A sua transação foi difundida com sucesso!", "total_balance": "Saldo Total", - "widgets": "Widgets" + "total_balance_explanation": "Mostrar o saldo total de todas as suas carteiras nos widgets do ecrã inicial.", + "widgets": "Widgets", + "tools": "Ferramentas" }, "notifications": { - "would_you_like_to_receive_notifications": "Você gostaria de receber notificações quando você receber pagamentos?", - "ask_me_later": "Pergunte-me depois" + "would_you_like_to_receive_notifications": "Gostaria de receber notificações quando receber pagamentos?", + "notifications_subtitle": "Pagamentos a receber e confirmações de transação", + "no_and_dont_ask": "Não, e não pergunte novamente.", + "permission_denied_message": "Negou a permissão para receber notificações. Se desejar receber notificações, ative-as nas definições do seu dispositivo." }, "transactions": { - "cancel_no": "Esta transacção não é substituível", - "cancel_title": "Cancelar esta transacção (RBF)", + "cancel_explain": "Iremos substituir esta transação por outra que lhe pague e tenha taxas mais elevadas. Isto cancela efetivamente a transação atual. Isto é chamado de Substituição da Taxa ou Replace by Fee — RBF.", + "cancel_no": "Esta transação não é substituível", + "cancel_title": "Cancelar esta transação (RBF)", + "transaction_loading_error": "Ocorreu um problema ao carregar a transação. Por favor, tente novamente mais tarde.", + "transaction_not_available": "Transação indisponível", "confirmations_lowercase": "{confirmations} confirmações", + "expand_note": "Expandir Nota", "cpfp_create": "Criar", - "cpfp_exp": "Criaremos outra transacção que gasta esta transação não confirmada. A taxa total será maior do que a taxa de transacção original, portanto, deve ser confirmada mais rapidamente. Isto é chamado de CPFP - Child Pays For Parent.", - "cpfp_no_bump": "A taxa desta transacção não pode ser aumentada", - "cpfp_title": "Aumento de taxa (CPFP)", + "cpfp_exp": "Criaremos outra transação que gasta esta transação não confirmada. A taxa total será maior do que a taxa de transação original, portanto, deve ser confirmada mais rapidamente. Isto é chamado de CPFP – Child Pays For Parent.", + "cpfp_no_bump": "A taxa desta transação não pode ser aumentada", + "cpfp_title": "Aumento de Taxa (CPFP)", "details_balance_hide": "Esconder Saldo", "details_balance_show": "Mostrar Saldo", - "details_block": "Block Height", "details_copy": "Copiar", - "details_from": "De", - "details_inputs": "Inputs", - "details_outputs": "Outputs", + "details_copy_block_explorer_link": "Copiar Link do Explorador de Blocos", + "details_copy_note": "Copiar Nota", + "details_copy_txid": "Copiar ID da Transação", + "details_id": "ID", + "details_inputs": "Entradas", + "details_outputs": "Saídas", + "date": "Data", "details_received": "Recebido", - "transaction_note_saved": "Nota de transação salva com sucesso.", - "details_show_in_block_explorer": "Mostrar no block explorer", - "details_title": "detalhes", + "details_view_in_browser": "Ver no Browser", + "details_title": "Transação", + "incoming_transaction": "Transação a Receber", + "outgoing_transaction": "Transação a Enviar", + "expired_transaction": "Transação Expirada", + "pending_transaction": "Transação Pendente", + "offchain": "Offchain", + "onchain": "Onchain", "details_to": "Para", + "enable_offline_signing": "Esta carteira não está a ser utilizada em conjunto com uma assinatura offline. Deseja ativá-la agora?", "pending": "Pendente", - "list_title": "transacções", - "rbf_title": "Aumento de taxa (RBF)", + "pending_with_amount": "Pendente {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: Em ~10 minutos", + "eta_3h": "ETA: Em ~3 horas", + "eta_1d": "ETA: Em ~1 dia", + "list_conf": "Conf: {number}", + "list_title": "Transações", + "list_title_received": "Recebido", + "transaction": "Transação", + "open_url_error": "Não foi possível abrir a ligação com o browser predefinido. Altere o seu navegador predefinido e tente novamente.", + "rbf_explain": "Substituiremos esta transação por outra com uma taxa mais elevada para que seja minerada mais rapidamente. A isto chama-se Substituição por Taxa ou Replace by Fee — RBF.", + "rbf_title": "Aumentar taxa (RBF)", "status_bump": "Aumento de taxa", - "status_cancel": "Cancelar transacção", - "transactions_count": "contagem de transações", - "updating": "A atualizar..." + "status_cancel": "Cancelar transação", + "transactions_count": "Número de transações", + "txid": "ID da transação", + "updating": "A atualizar...", + "watchOnlyWarningTitle": "Aviso de segurança", + "watchOnlyWarningDescription": "Tenha cuidado com os burlões que costumam usar carteiras apenas de visualização. Estas carteiras não permitem controlar ou enviar fundos; apenas permitem visualizar o saldo.", + "custom_fee_warning_title": "Aviso", + "custom_fee_warning_description": "Taxas abaixo de 1 sat/vB são válidas, mas podem não ser retransmitidas devido às políticas dos nós.", + "list_title_sent": "Enviado", + "details_eta_analyzing": "A analisar...", + "details_sent": "Enviado", + "details_section": "Detalhes", + "details_explorer": "explorador", + "details_network_fee": "Taxa de Rede", + "details_to_address": "Para", + "details_note": "Nota", + "details_add_note": "adicionar", + "details_advanced": "Avançado", + "details_fee_rate": "Taxa", + "details_size": "Tamanho", + "details_virtual_size": "Tamanho virtual", + "details_tx_hex": "Hex da Transação", + "details_inputs_count": "Entradas ({count})", + "details_outputs_count": "Saídas ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Carteira Bitcoin simples e poderosa", "add_create": "Adicionar", + "total_balance": "Saldo Total", + "add_entropy_reset_title": "Redefinir Entropia", + "add_entropy_reset_message": "Alterar o tipo de carteira irá redefinir a entropia atual. Deseja continuar?", + "add_entropy": "Entropia", + "add_entropy_bytes": "{bytes} bytes de entropia", "add_entropy_generated": "{gen} bytes de entropia gerada", "add_entropy_provide": "Entropia através de dados", "add_entropy_remain": "{gen} bytes de entropia gerada. Os bytes {rem} restantes serão obtidos do gerador de números aleatórios do sistema.", - "add_import_wallet": "Importar wallet", + "add_import_wallet": "Importar carteira", "add_lightning": "Lightning", "add_lightning_explain": "Para gastar com transações instantâneas", - "add_lndhub": "Conecte-se ao seu LNDHub", - "add_lndhub_placeholder": "seu endereço de nó", - "add_title": "adicionar wallet", - "add_wallet_name": "nome", - "add_wallet_type": "tipo", + "add_lndhub": "Ligar o seu LNDhub", + "add_lndhub_error": "O endereço fornecido é um nó LNDhub inválido.", + "add_lndhub_placeholder": "O Endereço do Seu Nó", + "add_placeholder": "a minha primeira carteira", + "add_title": "Adicionar Carteira", + "add_wallet_name": "Nome", + "add_wallet_type": "Tipo", + "add_wallet_seed_length": "Tamanho da Seed", + "add_wallet_seed_length_12": "12 palavras", + "add_wallet_seed_length_24": "24 palavras", + "clipboard_bitcoin": "Tem um endereço Bitcoin na área de transferência. Gostaria de o utilizar para uma transação?", + "clipboard_lightning": "Tem uma fatura de Lightning na área de transferência. Gostaria de a utilizar para uma transação?", + "clear_clipboard_on_import": "Limpar área de transferência ao importar", "details_address": "Endereço", "details_advanced": "Avançado", "details_are_you_sure": "Tem a certeza?", "details_connected_to": "Conectado a", - "details_del_wb_err": "O valor do saldo fornecido não corresponde ao saldo desta carteira. Por favor, tente novamente", + "details_del_wb_err": "O valor do saldo fornecido não corresponde ao saldo desta carteira. Por favor, tente novamente.", + "details_del_wb_q": "Esta carteira tem saldo. Antes de continuar, tenha em atenção que não poderá recuperar os fundos sem as palavras-semente desta carteira. Para evitar uma remoção acidental, introduza o saldo da sua carteira de {balance} satoshis.", "details_delete": "Eliminar", - "details_delete_wallet": "Remover carteira", - "details_display": "mostrar na lista de carteiras", - "details_export_backup": "Exportar / backup", - "details_master_fingerprint": "Master fingerprint", - "details_no_cancel": "Não, cancelar", - "details_save": "Guardar", - "details_show_xpub": "Mostrar XPUB da wallet", - "details_title": "wallet", + "details_delete_wallet": "Remover Carteira", + "details_derivation_path": "caminho de derivação", + "details_display": "Exibir no Ecrã Inicial", + "details_export_backup": "Exportar/Backup", + "details_export_history": "Exportar Histórico para CSV", + "details_master_fingerprint": "Master Fingerprint", + "details_multisig_type": "multiassinatura", + "details_show_xpub": "Mostrar XPUB da Carteira", + "details_show_addresses": "Mostrar endereços", + "details_title": "Carteira", + "wallets": "Carteiras", "details_type": "Tipo", - "details_use_with_hardware_wallet": "Use com carteira de hardware", - "details_wallet_updated": "Carteira actualizada", + "details_use_with_hardware_wallet": "Use com Carteira de Hardware", "details_yes_delete": "Sim, eliminar", - "export_title": "Exportar Wallet", + "enter_bip38_password": "Introduzir a palavra-passe para desencriptar", + "export_title": "Exportar Carteira", "import_do_import": "Importar", - "import_error": "Falhou. É um dado válido?", + "import_passphrase": "Passphrase", + "import_passphrase_title": "Passphrase", + "import_passphrase_message": "Insira a passphrase, caso tenha utilizado alguma", + "import_error": "Falha na importação. Certifique-se de que os dados fornecidos são válidos.", + "import_explanation": "Introduza as suas palavras-semente, chave pública, WIF, ou qualquer outra coisa que tenha. A BlueWallet fará o seu melhor para adivinhar o formato correto e importar a sua carteira.", "import_imported": "Importada", - "import_scan_qr": "ou scan o QR code?", + "import_scan_qr": "Ler código QR ou importar ficheiro", "import_success": "Sucesso", - "import_title": "importar", + "import_success_watchonly": "A sua carteira foi importada com sucesso.\nAVISO: Esta é uma carteira apenas de consulta e não poderá gastar a partir dela.", + "import_search_accounts": "Procurar contas", + "import_title": "Importar", + "learn_more": "Saber mais", + "import_discovery_title": "Descobrimento", + "import_discovery_subtitle": "Escolha uma carteira descoberta", + "import_discovery_derivation": "Usar caminho de derivação predefinido", + "import_discovery_no_wallets": "Nenhuma carteira foi encontrada.", + "import_discovery_offline": "A BlueWallet está atualmente no modo offline. Neste modo, não é possível verificar a existência da carteira, pelo que terá de selecionar manualmente a carteira correta.", + "import_derivation_found": "Encontrada", + "import_derivation_found_not": "Não encontrada", + "import_derivation_loading": "Carregando...", + "import_derivation_subtitle": "Insira o caminho de derivação predefinido e tentaremos encontrar a sua carteira.", + "import_derivation_title": "Caminho de derivação", + "import_derivation_unknown": "Desconhecido", + "import_wrong_path": "Caminho de derivação incorreto", "list_create_a_button": "Adicionar agora", - "list_create_a_wallet": "Adicionar uma wallet", - "list_empty_txs1": "As suas transacções aparecerão aqui,", - "list_empty_txs1_lightning": "A wallet Lightning deve ser usada para as suas transações diárias. As taxas são muito baixas e a velocidade muito elevada", - "list_empty_txs2_lightning": "\nPara começar a usar toque em \"gerir fundos\" e recarregue o seu saldo.", - "list_latest_transaction": "últimas transacções", + "list_create_a_wallet": "Adicionar uma carteira", + "list_create_a_wallet_text": "É gratuito e pode criar\ntantos quantos desejar.", + "list_empty_txs1": "As suas transações aparecerão aqui.", + "list_empty_txs1_lightning": "A carteira Lightning deve ser usada para as suas transações diárias. As taxas são baixíssimas e a velocidade é extremamente rápida.", + "list_empty_txs2": "Comece pela sua carteira.", + "list_empty_txs2_lightning": "\nPara começar a usar toque em Gerir Fundos e recarregue o seu saldo.", + "list_latest_transaction": "Últimas Transações", "list_long_choose": "Escolher Foto", - "list_long_clipboard": "Copiar da área de transferência", + "paste_from_clipboard": "Colar", + "import_file": "Importar Ficheiro", "list_long_scan": "Leia o código QR", - "list_title": "carteiras", + "list_title": "Carteiras", "list_tryagain": "Tente novamente", - "reorder_title": "Reordenar Wallets", + "no_ln_wallet_error": "Antes de pagar uma fatura Lightning, deve adicionar primeiro uma carteira Lightning.", + "looks_like_bip38": "Isto parece ser uma chave privada protegida por palavra-passe (BIP38).", + "manage_title": "Gerir Carteiras", + "no_results_found": "Nenhum resultado encontrado.", + "please_continue_scanning": "Continue a digitalização.", "select_no_bitcoin": "No momento, não há carteiras Bitcoin disponíveis.", "select_no_bitcoin_exp": "Uma carteira Bitcoin é necessária para recarregar as carteiras Lightning. Por favor, crie ou importe uma.", - "select_wallet": "Seleccione uma Wallet", - "xpub_copiedToClipboard": "copiado para o clipboard", - "xpub_title": "XPUB da wallet" + "select_wallet": "Selecione uma Carteira", + "pull_to_refresh": "Puxe para atualizar", + "warning_do_not_disclose": "Nunca partilhe as informações abaixo", + "scan_import": "Digitalize este código QR para importar a sua carteira noutra aplicação.", + "write_down_header": "Criar um backup manual", + "write_down": "Escreva e guarde estas palavras num local seguro. Use-as para restaurar a sua carteira posteriormente.", + "wallet_type_this": "O tipo desta carteira é {type}.", + "share_number": "Partilhar {number}", + "copy_ln_url": "Copie e guarde num local seguro este URL para restaurar a sua carteira posteriormente.", + "copy_ln_public": "Copie e guarde num local seguro esta informação para restaurar a sua carteira posteriormente.", + "add_ln_wallet_first": "Primeiro, deve adicionar uma carteira Lightning.", + "identity_pubkey": "Chave pública de Identidade", + "xpub_title": "XPUB da Carteira", + "manage_wallets_search_placeholder": "Pesquisar carteiras, endereços, transações e memorandos", + "more_info": "Saber Mais", + "details_delete_wallet_error_message": "Houve um problema ao confirmar se esta carteira foi removida das notificações — isto pode ser devido a um problema de rede ou ligação fraca. Se continuar, poderá ainda receber notificações de transações relacionadas com esta carteira, mesmo depois desta ter sido eliminada.", + "details_delete_anyway": "Apagar de qualquer forma", + "swipe_balance_hide": "Esconder", + "swipe_balance_show": "Mostrar", + "drag_to_reorder": "Arrastar para reordenar", + "clear_search": "Limpar pesquisa" + }, + "total_balance_view": { + "display_in_bitcoin": "Exibir em Bitcoin", + "hide": "Esconder", + "display_in_sats": "Exibir em sats", + "display_in_fiat": "Exibir em {currency}", + "title": "Saldo Total", + "explanation": "Veja o saldo total de todas as suas carteiras no ecrã de visão geral." }, "multisig": { + "multisig_vault": "Cofre Multiassinatura", + "default_label": "Cofre Multiassinatura", + "multisig_vault_explain": "A melhor segurança para montantes elevados", + "provide_signature": "Fornecer assinatura", + "provide_signature_details": "Use o seu dispositivo e carteira onde a chave reside para assinar esta transação.", + "provide_signature_details_bluewallet": "No BlueWallet, aceda ao ecrã Enviar e selecione", + "provide_signature_next_steps": "Digitalizar ou importar transação assinada", + "provide_signature_next_steps_details": "Depois da sua carteira ter assinado a transação com sucesso, digitalize o código QR fornecido ou importe o ficheiro anexo e, em seguida, reveja todos os detalhes da transação antes de difundi-la.", + "vault_key": "Código do Cofre {number}", + "required_keys_out_of_total": "Chaves necessárias do total", + "fee": "Taxa: {number}", + "fee_btc": "{number} BTC", "confirm": "Confirmar", "header": "Enviar", - "share": "partilhar", + "share": "Partilhar...", + "view": "Ver", + "shared_key_detected": "Co-signatário partilhado", + "shared_key_detected_question": "Um co-signatário foi partilhado consigo. Deseja importá-lo?", + "manage_keys": "Gerir Chaves", + "how_many_signatures_can_bluewallet_make": "quantas assinaturas a BlueWallet consegue fazer", + "signatures_required_to_spend": "Assinaturas necessárias {number}", + "signatures_we_can_make": "consegue fazer {number}", + "scan_or_import_file": "Digitalize ou importe ficheiro", + "export_coordination_setup": "Exportar Configuração de Coordenação", + "cosign_this_transaction": "Co-assinar esta transação?", + "lets_start": "Vamos começar", "create": "Criar", + "native_segwit_title": "Melhor prática", + "wrapped_segwit_title": "Melhor compatibilidade", + "legacy_title": "Antigo", "co_sign_transaction": "Assinar transação", - "ms_help_title5": "Ligar modo avançado" + "what_is_vault": "Um Cofre é", + "what_is_vault_numberOfWallets": "{m}-de-{n} multiassinatura", + "what_is_vault_wallet": "carteira.", + "vault_advanced_customize": "Definições de Cofre", + "needs": "Necessita", + "what_is_vault_description_number_of_vault_keys": "{m} chaves de cofre", + "what_is_vault_description_to_spend": "para gastar e um terceiro que\npode usar como reserva.", + "what_is_vault_description_to_spend_other": "para gastar.", + "quorum": "{m} de {n} quórum", + "quorum_header": "Quórum", + "of": "de", + "wallet_type": "Tipo de Carteira", + "invalid_mnemonics": "Esta frase mnemónica não parece ser válida.", + "invalid_cosigner": "Dados do co-signatário inválidos", + "not_a_multisignature_xpub": "Isto não é um XPUB de uma carteira multiassinatura!", + "invalid_cosigner_format": "Co-signatário incorreto: Este não é um co-signatário para o formato {format}.", + "create_new_key": "Criar Novo", + "scan_or_open_file": "Digitalizar ou abrir ficheiro", + "i_have_mnemonics": "Tenho uma seed para esta chave.", + "type_your_mnemonics": "Insira uma seed para importar a sua chave do Cofre existente.", + "ms_help_title5": "Modo Avançado", + "this_is_cosigners_xpub": "Este é o XPUB do co-signatário — pronto para ser importado noutra carteira. É seguro partilhá-lo.", + "this_is_cosigners_xpub_airdrop": "Se partilhar via AirDrop, os destinatários têm de estar no ecrã de coordenação.", + "wallet_key_created": "A sua chave do Cofre foi criada. Reserve um momento para fazer cópia de segurança da sua seed mnemónica em segurança.", + "are_you_sure_seed_will_be_lost": "Tem a certeza? A seed será perdida se não tiver cópia de segurança.", + "forget_this_seed": "Esquecer esta seed e usar o XPUB em vez disso.", + "view_edit_cosigners": "Ver/Editar co-signatários", + "this_cosigner_is_already_imported": "Este co-signatário já foi importado.", + "export_signed_psbt": "Exportar PSBT assinado", + "input_fp": "Inserir impressão digital", + "input_fp_explain": "Ignore para usar o padrão (00000000)", + "input_path": "Inserir caminho de derivação", + "input_path_explain": "Ignore para usar o padrão ({default})", + "ms_help": "Ajuda", + "ms_help_title": "Como Funcionam os Cofres Multiassinatura: Dicas e Truques", + "ms_help_text": "Uma carteira com várias chaves, para maior segurança ou custódia partilhada", + "ms_help_title1": "Recomenda-se usar vários dispositivos.", + "ms_help_1": "O Cofre funciona com outras aplicações BlueWallet e carteiras compatíveis com PSBT, tais como Electrum, Specter, Coldcard, Cobo Vault, etc.", + "ms_help_title2": "Editar chaves", + "ms_help_2": "Pode criar todas as chaves do Cofre neste dispositivo e removê-las ou editá-las mais tarde. Ter todas as chaves no mesmo dispositivo equivale, em segurança, a uma carteira Bitcoin normal.", + "ms_help_title3": "Cópias de Segurança do Cofre", + "ms_help_3": "Nas opções da carteira, irá encontrar a cópia de segurança do Cofre e a cópia de segurança só de observação. Esta cópia de segurança é como um mapa para a sua carteira. É essencial para recuperar a carteira caso perca uma das suas seeds.", + "ms_help_title4": "Importar Cofres", + "ms_help_4": "Para importar uma carteira multiassinatura, utilize o ficheiro de cópia de segurança e a funcionalidade Importar. Se só tiver seeds e XPUBs, pode usar o botão individual Importar ao criar as chaves do Cofre.", + "ms_help_5": "Por predefinição, a BlueWallet irá gerar um Cofre de 2-de-3. Para criar um quórum diferente ou alterar o tipo de endereço, ative o Modo Avançado nas Definições." + }, + "is_it_my_address": { + "title": "É o meu endereço?", + "owns": "{label} é dono de {address}", + "enter_address": "Inserir endereço", + "check_address": "Verificar endereço", + "no_wallet_owns_address": "Nenhuma das carteiras disponíveis é dona do endereço fornecido.", + "view_qrcode": "Ver código QR" + }, + "autofill_word": { + "title": "Última palavra da seed", + "enter": "Introduza a sua frase mnemónica parcial", + "generate_word": "Gerar a palavra final", + "error": "A frase introduzida não tem 11 ou 23 palavras. Tente novamente." + }, + "cc": { + "sort_label": "Nome", + "sort_status": "Estado", + "change": "Troco", + "coins_selected": "Moedas selecionadas ({number})", + "selected_summ": "{value} selecionado", + "empty": "Esta carteira não tem moedas no momento.", + "freeze": "Congelar", + "freezeLabel": "Congelar", + "freezeLabel_un": "Descongelar", + "header": "Controlo de moedas", + "use_coin": "Usar moeda", + "use_coins": "Usar moedas", + "tip": "Esta funcionalidade permite ver, etiquetar, congelar ou selecionar moedas para melhor gestão da carteira. Pode selecionar várias moedas tocando nos círculos coloridos.", + "sort_asc": "Crescente", + "sort_desc": "Decrescente", + "sort_height": "Altura", + "sort_value": "Valor", + "sort_by": "Ordenar por" }, "units": { + "BTC": "BTC", + "MAX": "Máx", + "sat_vbyte": "sat/vByte", "sats": "sats" }, "addresses": { "sign_placeholder_address": "Endereço", "type_receive": "receber", - "transactions": "transacções" + "transactions": "transações", + "copy_private_key": "Copiar chave privada", + "sensitive_private_key": "Aviso: chaves privadas são extremamente sensíveis. Continuar?", + "sign_title": "Assinar/Verificar mensagem", + "sign_help": "Aqui pode criar ou verificar uma assinatura criptográfica baseada num endereço Bitcoin.", + "sign_sign": "Assinar", + "sign_verify": "Verificar", + "sign_signature_correct": "Verificação bem-sucedida!", + "sign_signature_incorrect": "Verificação falhou!", + "sign_placeholder_message": "Mensagem", + "sign_placeholder_signature": "Assinatura", + "addresses_title": "Endereços", + "type_change": "Troco", + "type_used": "Usado" + }, + "lnurl_auth": { + "register_question_part_1": "Deseja registar uma conta em", + "register_question_part_2": "usando a sua carteira Lightning?", + "register_answer": "Conta registada com sucesso em {hostname}!", + "login_question_part_1": "Deseja iniciar sessão em", + "login_question_part_2": "usando a sua carteira Lightning?", + "login_answer": "Sessão iniciada com sucesso em {hostname}!", + "link_question_part_1": "Deseja associar a sua conta em", + "link_question_part_2": "à sua carteira Lightning?", + "link_answer": "A sua carteira Lightning foi associada com sucesso à sua conta em {hostname}!", + "auth_question_part_1": "Deseja autenticar-se em", + "auth_question_part_2": "usando a sua carteira Lightning?", + "auth_answer": "Autenticou-se com sucesso em {hostname}!", + "could_not_auth": "Não foi possível autenticá-lo em {hostname}.", + "authenticate": "Autenticar" + }, + "bip47": { + "payment_code": "Código de pagamento", + "contacts": "Contactos", + "bip47_explain": "Código reutilizável e partilhável", + "bip47_explain_subtitle": "BIP47", + "purpose": "Código reutilizável e partilhável (BIP47)", + "pay_this_contact": "Pagar a este contacto", + "rename_contact": "Renomear contacto", + "copy_payment_code": "Copiar código de pagamento", + "hide_contact": "Ocultar contacto", + "rename": "Renomear", + "provide_name": "Indique um novo nome para este contacto", + "add_contact": "Adicionar contacto", + "provide_payment_code": "Indique o código de pagamento", + "invalid_pc": "Código de pagamento inválido", + "notification_tx_unconfirmed": "A transação de notificação ainda não foi confirmada, aguarde", + "failed_create_notif_tx": "Falha ao criar transação on-chain", + "onchain_tx_needed": "É necessária uma transação on-chain", + "notif_tx_sent": "Transação de notificação enviada. Aguarde a confirmação", + "notif_tx": "Transação de notificação", + "not_found": "Código de pagamento não encontrado" } } diff --git a/loc/ro.json b/loc/ro.json index f76a9769bac..65c7c8eecf5 100644 --- a/loc/ro.json +++ b/loc/ro.json @@ -3,39 +3,49 @@ "bad_password": "Parolă incorectă. Încearcă din nou. ", "cancel": "Anulează", "continue": "Continuă", - "clipboard": "Clipboard", + "discard_changes": "Renunți la modificări?", + "discard_changes_explain": "Ai modificări nesalvate. Sigur vrei să renunți la ele și să părăsești ecranul?", "enter_password": "Introdu parola", + "enter_url": "Introdu URL", + "save": "Salvează...", + "close": "Închide", + "change_input_currency": "Schimbă moneda de intrare", + "refresh": "Reîncarcă", + "pick_image": "Alege din bibliotecă", + "pick_file": "Alege fișier", + "enter_amount": "Introdu suma", + "qr_custom_input_button": "Apasă de 10 ori pentru a introduce date personalizate", + "clipboard": "Clipboard", + "port": "Port", + "unlock": "Deblochează", + "ssl_port": "Port SSL", + "suggested": "Sugerat", + "copied": "Copiat!", "never": "Niciodată", - "disabled": "Dezactivat", "of": "{number} din {total}", "ok": "OK", "storage_is_encrypted": "Spațiul tău de stocare este criptat. E necesară parola pentru decriptare.", "yes": "Da", "no": "Nu", - "save": "Salvează", "seed": "Seed", "success": "Succes", - "wallet_key": "Cheia portofelului", - "invalid_animated_qr_code_fragment": "Fragment animat de QRCode invalid. Încearcă din nou.", - "file_saved": "Fișierul {filePath} a fost salvat în {destination}.", - "downloads_folder": "Folderul de descărcări" - }, - "alert": { - "default": "Alertă" + "wallet_key": "Cheia portofelului" }, "azteco": { - "codeIs": "Voucher codul tău este", - "errorBeforeRefeem": "Înainte de revendicare, trebuie mai întîi să adaugi un portofel Bitcoin.", + "codeIs": "Codul tău de voucher este", + "errorBeforeRefeem": "Înainte de revendicare, trebuie mai întâi să adaugi un portofel Bitcoin.", "errorSomething": "Ceva nu a mers. Este acest voucher încă valid?", "redeem": "Revendică în portofel", "redeemButton": "Revendică", "success": "Succes", + "successMessage": "Voucher revendicat cu succes! Fondurile tale ar trebui să ajungă în scurt timp în portofelul tău Bitcoin.", "title": "Revendică voucher Azte.co" }, "entropy": { "save": "Salvează", "title": "Entropie", - "undo": "Anulează" + "undo": "Anulează", + "amountOfEntropy": "{bits} din {limit} biți" }, "errors": { "broadcast": "Difuzare eșuată.", @@ -43,64 +53,46 @@ "network": "Eroare de rețea" }, "lnd": { - "active": "Activ", - "inactive": "Inactiv", - "channels": "Canale", - "no_channels": "Niciun canal", - "claim_balance": "Revendică sold {balance}", - "close_channel": "Închide canal", - "new_channel": "Canal nou", - "errorInvoiceExpired": "Factură expirată", - "force_close_channel": "Forțează închiderea canalului?", + "errorInvoiceExpired": "Factură expirată.", "expired": "Expirat", - "node_alias": "Alias nod", "expiresIn": "Expiră în {time} minute", "payButton": "Plătește", - "placeholder": "Factură", - "open_channel": "Deschide canal", - "funding_amount_placeholder": "Valoarea finanțării, de exemplu 0.001", - "are_you_sure_open_channel": "Sigur vrei să deschizi acest canal?", - "potentialFee": "Comision Potențial: {fee}", + "payment": "Plată", + "placeholder": "Factură sau adresă", + "potentialFee": "Comision potențial: {fee}", "refill": "Reumple", "refill_create": "Pentru a continua, creează un portofel Bitcoin cu care să reumpli.", "refill_external": "Reumple cu Portofel Extern", "refill_lnd_balance": "Reumple balanța portofelului Lightning", - "sameWalletAsInvoiceError": "Nu poți plăti o factură cu același portofel folosit să o creeze.", - "title": "Administrează fondurile", - "can_send": "Poate trimite", - "can_receive": "Poate primi", - "view_logs": "Vezi jurnalele" + "sameWalletAsInvoiceError": "Nu poți plăti o factură cu același portofel folosit pentru a o crea.", + "title": "Administrează fondurile" }, "lndViewInvoice": { "additional_info": "Informații suplimentare", "for": "Pentru:", "lightning_invoice": "Factură Lightning", - "open_direct_channel": "Deschide canal direct cu acest nod:", "please_pay_between_and": "Plătește între {min} și {max}", "please_pay": "Plătește", - "preimage": "Pre-imagine", + "preimage": "Preimagine", + "date_time": "Data și ora", "sats": "sats.", "wasnt_paid_and_expired": "Această factură nu a fost plătită și a expirat." }, "plausibledeniability": { "create_fake_storage": "Creează Spațiu de Stocare Criptat", - "create_password": "Creează o parolă", - "create_password_explanation": "Parola pentru stocarea falsă nu trebuie să fie acceași cu parola pentru stocarea principală.", - "help": "În anumite circumstanțe, ai putea fi forțat să spui parola. Pentru a-ți păstra monedele în siguranță, BlueWallet poate crea un alt spațiu de stocare cu o parolă diferită. Sub presiune, poți oferi această parolă unei entități terțe. Dacă e introdusă în BlueWallet, va debloca un nou spațiu de stocare \"fals\". Acest lucru va părea în regulă unei entități terțe, dar îți va păstra în siguranță spațiul de stocare secret cu monedele tale.", + "create_password_explanation": "Parola pentru stocarea falsă nu trebuie să fie aceeași cu parola pentru stocarea principală.", + "help": "În anumite circumstanțe, ai putea fi forțat să spui parola. Pentru a-ți păstra monedele în siguranță, BlueWallet poate crea un alt spațiu de stocare cu o parolă diferită. Sub presiune, poți oferi această parolă unei entități terțe. Dacă e introdusă în BlueWallet, va debloca un nou spațiu de stocare \"fals\". Acest lucru va părea în regulă unei entități terțe, dar îți va păstra în siguranță spațiul de stocare secret cu monedele tale.", "help2": "Noul spațiu de stocare va fi complet funcțional, și poți păstra o cantitate minimă pentru a părea mai credibil. ", "password_should_not_match": "Parola este deja în folosință. Încearcă o parolă diferită.", - "passwords_do_not_match": "Parolele nu sunt identice. Încearcă din nou.", - "retype_password": "Re-introdu parola", - "success": "Succes", "title": "Negare plauzibilă" }, "pleasebackup": { "ask": "Ai salvat cuvintele de rezervă ale portofelului tău? Aceste cuvinte de rezervă sunt necesare pentru a-ți accesa fondurile în cazul în care îți pierzi dispozitivul. Fără cuvintele de rezervă, fondurile tale vor fi permanent pierdute.", - "ask_no": "Nu, nu am făcut asta", - "ask_yes": "Da, am făcut asta", - "ok": "Ok, am notat acestea", - "ok_lnd": "Ok, am salvat acestea", - "text": "Notează această frază mnemonică pe o hîrtie. Este copia de rezervă pe care o poți folosi să restabilești portofelul pe alt dispozitiv. ", + "ask_no": "Nu, nu am salvat.", + "ask_yes": "Da, am salvat.", + "ok": "OK, o scriu pe foaie.", + "ok_lnd": "OK, am salvat.", + "text": "Notează această frază mnemonică pe o hârtie.\nEste copia de rezervă pe care o poți folosi să restabilești portofelul pe alt dispozitiv.", "text_lnd": "Salvează această copie de rezervă a portofelului. Îți permite să restabilești portofelul în cazul unei pierderi.", "title": "Portofelul tău a fost creat." }, @@ -108,7 +100,15 @@ "details_create": "Creează", "details_label": "Descriere", "details_setAmount": "Primește sumă determinată", - "details_share": "Distribuie", + "details_share": "Partajează...", + "address_not_found": "Nu s-a putut genera adresa de primire.", + "reset": "Resetează", + "maxSats": "Suma maximă este {max} sats", + "maxSatsFull": "Suma maximă este {max} sats sau {currency}", + "minSats": "Suma minimă este {min} sats", + "minSatsFull": "Suma minimă este {min} sats sau {currency}", + "qrcode_for_the_address": "Cod QR pentru adresă", + "bip47_explanation": "Codurile de plată sunt o adresă universală care evită dezvăluirea adreselor portofelului tău. Nu toate serviciile le suportă.", "header": "Primește" }, "send": { @@ -121,7 +121,7 @@ "confirm_sendNow": "Trimite acum", "create_amount": "Cantitate", "create_broadcast": "Difuzează", - "create_copy": "Copiază și difuzează mai tîrziu", + "create_copy": "Copiază și difuzează mai târziu", "create_details": "Detalii", "create_fee": "Comision", "create_memo": "Memo", @@ -146,24 +146,23 @@ "details_error_decode": "Nu s-a putut decoda adresa Bitcoin", "details_fee_field_is_not_valid": "Comisionul nu este valid.", "details_next": "Înainte", - "details_no_signed_tx": "Fișierul ales nu conține o tranzacție ce poate fi importată.", "details_note_placeholder": "Notă pentru sine", "details_scan": "Scanează", "details_total_exceeds_balance": "Suma de trimis depășește balanța disponibilă.", "details_unrecognized_file_format": "Format de fișier nerecunoscut", - "details_wallet_before_tx": "Înainte de a crea o tranzacție, trebuie mai întîi să adaugi un portofel Bitcoin.", + "details_wallet_before_tx": "Înainte de a crea o tranzacție, trebuie mai întâi să adaugi un portofel Bitcoin.", "dynamic_init": "Inițializare", "dynamic_next": "Înainte", "dynamic_prev": "Înapoi", - "dynamic_start": "Start", - "dynamic_stop": "Stop", + "dynamic_start": "Pornește", + "dynamic_stop": "Oprește", "fee_10m": "10m", "fee_1d": "1z", "fee_3h": "3h", "fee_custom": "Personalizat", "fee_fast": "Rapid", "fee_medium": "Mediu", - "fee_replace_minvb": "Rata totală a comisionului (satoshi per vByte) pe care vrei să o plătești ar trebui să fie mai mare decît {min} sat/vByte.", + "fee_replace_minvb": "Rata totală a comisionului (satoshi per vByte) pe care vrei să o plătești ar trebui să fie mai mare decât {min} sat/vByte.", "fee_satvbyte": "în sat/vByte", "fee_slow": "Lent", "header": "Trimite", @@ -172,15 +171,12 @@ "input_paste": "Lipește", "input_total": "Total:", "permission_camera_message": "E nevoie de permisiune de folosire a camerei foto", - "permission_camera_title": "Permisiune de folosire a camerei foto", "psbt_sign": "Semnează o tranzacție", "open_settings": "Deschide Setări", - "permission_storage_later": "Întreabă-mă mai tîrziu", - "permission_storage_message": "BlueWallet necesită permisiune de accesare a spațiului tău de stocare pentru a salva acest fișier.", "permission_storage_denied_message": "BlueWallet nu poate salva acest fișier. Deschide setările dispozitivului și permite permisiunea de stocare.", "permission_storage_title": "Permisiune de acces a spațiului de stocare", "psbt_clipboard": "Copiază în Clipboard", - "psbt_this_is_psbt": "Aceasta este o tranzacție Bitcoin parțial semnată (PBST). Finalizează semnarea folosind portofelul hardware.", + "psbt_this_is_psbt": "Aceasta este o tranzacție Bitcoin parțial semnată (PSBT). Finalizează semnarea folosind portofelul hardware.", "psbt_tx_export": "Exportă în fișier", "no_tx_signing_in_progress": "Nu e nicio semnare de tranzacție în derulare. ", "psbt_tx_open": "Deschide Tranzacția Semnată", @@ -188,8 +184,29 @@ "reset_amount": "Resetează suma", "reset_amount_confirm": "Vrei să resetezi suma?", "success_done": "Gata", - "txSaved": "Fișierul de tranzacție ({filePath}) a fost salvat în folderul Downloads", - "problem_with_psbt": "Problemă cu PSBT" + "problem_with_psbt": "Problemă cu PSBT", + "provided_address_is_invoice": "Această adresă pare să fie pentru o factură Lightning. Te rugăm să mergi la portofelul tău Lightning pentru a efectua o plată pentru această factură.", + "details_insert_contact": "Inserează contact", + "details_add_recc_rem_all_alert_description": "Sigur vrei să elimini toți destinatarii?", + "details_add_rec_rem_all": "Elimină toți destinatarii", + "details_recipients_title": "Destinatari", + "details_recipient_title": "Destinatar #{number} din #{total}", + "please_complete_recipient_title": "Destinatar incomplet", + "please_complete_recipient_details": "Te rugăm să completezi detaliile destinatarului #{number} înainte de a adăuga un nou destinatar.", + "details_frozen": "{amount} BTC sunt înghețați.", + "details_no_signed_tx": "Fișierul selectat nu conține o tranzacție care poate fi importată.", + "details_scan_hint": "Apasă de două ori pentru a scana sau importa o destinație", + "details_scan_error": "Eroare de scanare", + "details_total_exceeds_balance_frozen": "Suma de trimis depășește balanța disponibilă. Ai în vedere că monedele înghețate sunt excluse.", + "insert_custom_fee": "Introdu comisionul", + "invalid_psbt": "PSBT furnizat este invalid.", + "outdated_rate": "Rata a fost actualizată ultima dată: {date}", + "qr_error_no_qrcode": "Nu am putut găsi un cod QR valid în imaginea selectată. Asigură-te că imaginea conține doar un cod QR și niciun alt conținut, precum text sau butoane.", + "txSaved": "Fișierul tranzacției ({filePath}) a fost salvat.", + "file_saved_at_path": "Fișierul ({filePath}) a fost salvat.", + "cant_send_to_silentpayment_adress": "Acest portofel nu poate trimite către adrese SilentPayment", + "cant_send_to_bip47": "Acest portofel nu poate trimite către coduri de plată BIP47", + "cant_find_bip47_notification": "Adaugă mai întâi acest cod de plată la contacte" }, "settings": { "about": "Despre", @@ -202,23 +219,15 @@ "about_selftest": "Rulează auto-test", "about_selftest_ok": "Toate testele interne au trecut cu succes. Portofelul funcționează bine.", "about_sm_github": "GitHub", - "about_sm_discord": "Server Discord", "about_sm_telegram": "Canal Telegram", - "about_sm_twitter": "Urmărește-ne pe Twitter", - "advanced_options": "Opțiuni avansate", "biometrics": "Biometrici", "biom_10times": "Ai încercat să introduci parola de 10 ori. Ai vrea să resetezi spațiul de stocare? Acest lucru va înlătura toate portofelele și va decripta spațiul de stocare.", "biom_conf_identity": "Confirmă-ți identitatea.", - "biom_no_passcode": "Dispozitivul tău nu are un cod de acces. Pentru a continua, configurează un cod de acces în aplicația de Setări.", "biom_remove_decrypt": "Toate portofelele tale vor fi înlăturate și spațiul de stocare va fi decriptat. Sigur vrei să continui?", "currency": "Monedă", - "default_desc": "Cînd e dezactivat, BlueWallet va deschide imediat la pornire portofelul selectat.", - "default_info": "Informație implicită", "default_title": "La pornire", - "default_wallets": "Vezi Toate Portofelele", "electrum_connected": "Conectat", "electrum_connected_not": "Nu e conectat", - "electrum_error_connect": "Nu se poate conecta la serverul Electrum furnizat", "lndhub_uri": "Exemplu: {example}", "electrum_host": "Exemplu: {example}", "electrum_offline_mode": "Mod offline", @@ -226,43 +235,25 @@ "use_ssl": "Folosește SSL", "electrum_saved": "Modificările tale au fost salvate cu succes. E posibil să fie nevoie de restartarea BlueWallet pentru ca modificările să-și facă efectul. ", "set_electrum_server_as_default": "Setează {server} ca server Electrum implicit?", - "set_lndhub_as_default": "Setează {url} ca server LDNHub implicit?", "electrum_settings_server": "Server Electrum", - "electrum_settings_explain": "Lasă necompletat pentru a folosi setările implicite.", - "electrum_status": "Status", - "electrum_clear_alert_title": "Șterge istoricul?", - "electrum_clear_alert_message": "Vrei să ștergi istoricul serverelor Electrum?", - "electrum_clear_alert_cancel": "Anulează", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Alege", - "electrum_reset": "Resetează la setările implicite", + "electrum_status": "Stare", "electrum_unable_to_connect": "Nu s-a putut conecta la {server}.", - "electrum_history": "Istoricul serverului", - "electrum_reset_to_default": "Sigur vrei să resetezi setările Electrum?", - "electrum_clear": "Șterge", - "tor_supported": "Tor supportat", - "tor_unsupported": "Conexiunile Tor nu sunt supportate.", + "electrum_reset": "Resetează la setările implicite", "encrypt_decrypt": "Decriptează spațiul de stocare", "encrypt_decrypt_q": "Ești sigur că vrei să decriptezi spațiul de stocare? Acest lucru va permite portofelelor tale să fie accesate fără parolă.", - "encrypt_enc_and_pass": "Encriptat și protejat de parolă", "encrypt_title": "Securitate", "encrypt_tstorage": "Spațiul de stocare", "encrypt_use": "Folosește {type}", - "encrypt_use_expl": "{type} va fi folosit pentru a confirma identitatea ta înainte de a face o tranzacție, de a debloca, de a exporta, sau de a șterge un portofel. {type} nu va fi folosit să deblocheze un spațiu de stocare criptat.", "general": "General", - "general_adv_mode": "Mod avansat", - "general_adv_mode_e": "Cînd e activat, vei vedea opțiuni avansate, precum tipuri diferite de portofele, abilitatea de a specifica instanța LNDHub la care vrei să te conectezi, și entropia personalizată la crearea portofelului.", "general_continuity": "Continuitate", - "general_continuity_e": "Cînd e activat, vei vedea aici portofelele selectate, și tranzacțiile, folosind celelalte dispozitive ale tale Apple conectate la iCloud.", + "general_continuity_e": "Când e activat, vei vedea aici portofelele selectate, și tranzacțiile, folosind celelalte dispozitive ale tale Apple conectate la iCloud.", "groundcontrol_explanation": "GroundControl este un server de notificări gratuit, open-source, pentru portofele Bitcoin. Poți instala propriul server GroundControl și să-i pui URL-ul aici pentru a nu te baza pe infrastructura BlueWallet. Lasă necompletat pentru a folosi serverul implicit al GroundControl.", "header": "Setări", "language": "Limbă", "last_updated": "Actualizat ultima dată", "language_isRTL": "E nevoie de restartarea BlueWallet pentru ca orientarea lingvistică să-și facă efectul.", - "lightning_error_lndhub_uri": "URI LNDHub invalid", "lightning_saved": "Modificările tale au fost salvate cu succes.", "lightning_settings": "Setări Lightning", - "tor_settings": "Setări Tor", "network": "Rețea", "network_broadcast": "Difuzează tranzacția", "network_electrum": "Server Electrum", @@ -270,113 +261,180 @@ "notifications": "Notificări", "open_link_in_explorer": "Deschide link în explorer", "password": "Parolă", - "password_explain": "Creează parola pe care o vei folosi pentru decriptarea spațiului de stocare.", - "passwords_do_not_match": "Parolele nu se potrivesc.", "plausible_deniability": "Negare plauzibilă", "privacy": "Confidențialitate", "privacy_read_clipboard": "Citește clipboard", "privacy_system_settings": "Setări sistem", "privacy_quickactions": "Scurtături ale portofelului", - "privacy_quickactions_explanation": "Ține apăsat pe iconița aplicație BlueWallet de pe ecranul tău principal pentru a vedea rapid balanța portofelului tău.", "privacy_clipboard_explanation": "Furnizează scurtături dacă o adresă sau factură este găsită în clipboard-ul tău.", "privacy_do_not_track": "Dezactivează Analytics", - "push_notifications": "Notificări push", "rate": "Rata", - "retype_password": "Scrie parola din nou", "selfTest": "Auto-test", "save": "Salvează", "saved": "Salvat", - "success_transaction_broadcasted": "Realizat cu succes! Tranzacția ta a fost difuzată!", "total_balance": "Balanță totală", "total_balance_explanation": "Afișează balanța totală a tuturor portofelelor tale pe widget-urile ecranului tău principal.", "widgets": "Widget-uri", - "tools": "Unelte" + "tools": "Unelte", + "performance_score": "Scor de performanță: {num}", + "run_performance_test": "Testează performanța", + "block_explorer_invalid_custom_url": "URL-ul furnizat nu este valid. Te rugăm să introduci un URL valid care începe cu http:// sau https://.", + "about_selftest_electrum_disabled": "Auto-testarea nu este disponibilă în Modul Offline Electrum. Te rugăm să dezactivezi modul offline și să încerci din nou.", + "privacy_temporary_screenshots": "Permite capturi de ecran", + "privacy_temporary_screenshots_instructions": "Protecția împotriva capturilor de ecran va fi dezactivată temporar, permițând capturi de ecran și înregistrări ecran. Protecția se va reactiva automat când închizi și redeschizi BlueWallet.", + "biometrics_no_longer_available": "Setările dispozitivului tău s-au schimbat și nu mai corespund cu setările de securitate selectate în aplicație. Te rugăm să reactivezi biometria sau codul de acces, apoi repornește aplicația pentru a aplica aceste modificări.", + "biom_no_passcode": "Dispozitivul tău nu are un cod de acces sau biometrie activată. Pentru a continua, te rugăm să configurezi un cod de acces sau biometrie în aplicația Setări.", + "currency_source": "Rata este obținută de la", + "currency_fetch_error": "A apărut o eroare la obținerea ratei pentru moneda selectată.", + "donate": "Donează", + "donate_description": "Ajută-ne să păstrăm Blue gratuit!", + "electrum_error_connect": "Nu se poate conecta la serverul Electrum furnizat", + "electrum_error_connect_tor": "Nu se poate conecta la serverul Electrum furnizat. Te rugăm să te asiguri că aplicația Orbot este conectată și încearcă din nou.", + "electrum_offline_description": "Când este activat, portofelele tale Bitcoin nu vor încerca să obțină balanțe sau tranzacții.", + "set_lndhub_as_default": "Setează {url} ca server LNDhub implicit?", + "electrum_preferred_server": "Server preferat", + "electrum_preferred_server_description": "Introdu serverul pe care vrei să-l folosească portofelul tău pentru toate activitățile Bitcoin. Odată setat, portofelul tău va folosi exclusiv acest server pentru a verifica balanțele, a trimite tranzacții și a obține date de rețea. Asigură-te că ai încredere în acest server înainte de a-l seta.", + "electrum_history": "Istoric", + "electrum_reset_to_default": "Acest lucru va permite BlueWallet să aleagă aleatoriu un server din lista de servere.", + "electrum_reset_to_default_and_clear_history": "Resetează la implicit și șterge istoricul", + "encrypt_enc_and_pass": "Protejat prin parolă", + "encrypt_storage_explanation_headline": "Activează criptarea spațiului de stocare", + "encrypt_storage_explanation_description_line1": "Activarea criptării spațiului de stocare adaugă un nivel suplimentar de protecție aplicației tale, securizând modul în care datele tale sunt stocate pe dispozitiv. Acest lucru face mai dificilă accesarea informațiilor tale fără permisiune.", + "encrypt_storage_explanation_description_line2": "Totuși, este important să știi că această criptare protejează doar accesul la portofelele tale stocate în keychain-ul dispozitivului. Nu pune o parolă sau o protecție suplimentară pe portofelele propriu-zise.", + "i_understand": "Am înțeles", + "block_explorer": "Block explorer", + "block_explorer_preferred": "Folosește block explorer-ul preferat", + "block_explorer_error_saving_custom": "Eroare la salvarea block explorer-ului preferat", + "set_as_preferred": "Setează ca preferat", + "set_as_preferred_electrum": "Setarea {host}:{port} ca server preferat va dezactiva conectarea aleatorie la un server sugerat.", + "encrypted_feature_disabled": "Această funcționalitate nu poate fi folosită cu spațiul de stocare criptat activat.", + "encrypt_use_expl": "{type} va fi folosit pentru a-ți confirma identitatea înainte de a face o tranzacție, deblocare, exportare sau ștergere a unui portofel.", + "biometrics_fail": "Dacă {type} nu este activat sau nu reușește să deblocheze, poți folosi codul de acces al dispozitivului ca alternativă.", + "license": "Licență", + "lightning_error_lndhub_uri": "URI LNDhub invalid", + "lightning_error_lndhub_uri_tor": "URI LNDhub invalid. Te rugăm să te asiguri că aplicația Orbot este conectată și încearcă din nou.", + "lightning_settings_explain": "Pentru a te conecta la propriul nod LND, te rugăm să instalezi LNDhub și să-i pui URL-ul aici în setări. Reține că doar portofelele create după salvarea modificărilor se vor conecta la LNDhub-ul specificat.", + "lndhub_github": "Depozit GitHub", + "electrum_suggested_description": "Când un server preferat nu este setat, va fi selectat aleatoriu un server sugerat pentru utilizare.", + "password_explain": "Introdu parola pe care o vei folosi pentru a-ți debloca spațiul de stocare.", + "privacy_quickactions_explanation": "Atinge și ține apăsată pictograma aplicației BlueWallet pentru a vedea rapid balanța portofelului tău.", + "privacy_do_not_track_explanation": "Informațiile despre performanță și fiabilitate nu vor fi trimise spre analiză.", + "push_notifications_explanation": "Prin activarea notificărilor, tokenul dispozitivului tău va fi trimis către server, împreună cu adresele portofelului și ID-urile tranzacțiilor pentru toate portofelele și tranzacțiile efectuate după activarea notificărilor. Tokenul dispozitivului este folosit pentru a trimite notificări, iar informațiile despre portofel ne permit să te anunțăm despre Bitcoin-ul primit sau despre confirmările tranzacțiilor.\n\nDoar informațiile de după activarea notificărilor sunt transmise — nu se colectează nimic din trecut.\n\nDezactivarea notificărilor va elimina toate aceste informații de pe server. În plus, ștergerea unui portofel din aplicație va elimina și informațiile asociate de pe server.", + "success_transaction_broadcasted": "Tranzacția ta a fost difuzată cu succes!" }, "notifications": { - "would_you_like_to_receive_notifications": "Ai vrea să primești notificări atunci cînd primești plăți?", - "no_and_dont_ask": "Nu, și nu mă mai întreba", - "ask_me_later": "Întreabă-mă mai tîrziu" + "would_you_like_to_receive_notifications": "Ai vrea să primești notificări atunci când primești plăți?", + "notifications_subtitle": "Plăți primite și confirmări de tranzacții", + "no_and_dont_ask": "Nu, și nu mă întreba din nou.", + "permission_denied_message": "Ai refuzat permisiunea de a primi notificări. Dacă dorești să primești notificări, te rugăm să le activezi în setările dispozitivului." }, "transactions": { "cancel_explain": "Se va înlocui această tranzacție cu aceea care te plătește pe tine și are comisioane mai mari. Acest lucru anulează tranzacția curentă. Acest proces se numește RBF - Replace by Fee / Înlocuiește prin Comision. ", "cancel_no": "Această tranzacție nu e înlocuibilă.", "cancel_title": "Anulează această tranzacție (RBF).", "confirmations_lowercase": "{confirmations} confirmări", - "copy_link": "Copiază link", "expand_note": "Extinde notiță", "cpfp_create": "Creează", - "cpfp_exp": "Se va crea o altă tranzacție care va cheltui tranzacția ta neconfirmată. Comisionul total va fi mai mare decît comisionul original al tranzacției, așa încît ar trebui să fie minată mai repede. Acest proces se numește CPFP - Child Pays for Parent / Copilul Plătește pentru Părinte.", + "cpfp_exp": "Se va crea o altă tranzacție care va cheltui tranzacția ta neconfirmată. Comisionul total va fi mai mare decât comisionul original al tranzacției, așa încât ar trebui să fie minată mai repede. Acest proces se numește CPFP - Child Pays for Parent / Copilul Plătește pentru Părinte.", "cpfp_no_bump": "Această tranzacție nu poate fi accelerată.", "cpfp_title": "Crește comisionul (CPFP)", "details_balance_hide": "Ascunde balanța", "details_balance_show": "Afișează balanța", - "details_block": "Block Height", "details_copy": "Copiază", - "details_copy_amount": "Copiază sumă", "details_copy_block_explorer_link": "Copiază link-ul block explorer-ului.", "details_copy_txid": "Copiază ID-ul tranzacției", - "details_from": "Input", "details_inputs": "Input-uri", "details_outputs": "Output-uri", "details_received": "Primit", - "transaction_note_saved": "Notița tranzacției a fost salvată cu succes.", - "details_show_in_block_explorer": "Afișează în Block Explorer", "details_title": "Tranzacție", - "details_to": "Output", + "details_to": "Ieșire", "enable_offline_signing": "Acest portofel nu este folosit în legătură cu o semnare offline. Ai vrea să activezi asta acum?", - "list_conf": "Conf: {number}", "pending": "În așteptare", "eta_10m": "Estimat: în ~10 minute", "eta_3h": "Estimat: în ~3 ore", "eta_1d": "Estimat: în ~1 zi", - "view_wallet": "Vezi {walletLabel}", + "list_conf": "Conf: {number}", "list_title": "Tranzacții", + "list_title_received": "Primit", + "transaction": "Tranzacție", "rbf_title": "Crește comisionul (RBF)", "status_bump": "Crește comisionul", "status_cancel": "Anulează tranzacția", "transactions_count": "Numărul tranzacțiilor", "txid": "ID-ul tranzacției", - "updating": "Se actualizează..." + "updating": "Se actualizează...", + "transaction_loading_error": "A apărut o problemă la încărcarea tranzacției. Te rugăm să încerci din nou mai târziu.", + "transaction_not_available": "Tranzacție indisponibilă", + "details_copy_note": "Copiază nota", + "date": "Dată", + "details_explorer": "explorer", + "details_id": "ID", + "details_view_in_browser": "Vezi în browser", + "incoming_transaction": "Tranzacție primită", + "outgoing_transaction": "Tranzacție trimisă", + "expired_transaction": "Tranzacție expirată", + "pending_transaction": "Tranzacție în așteptare", + "offchain": "Off-chain", + "onchain": "On-chain", + "pending_with_amount": "În așteptare {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "list_title_sent": "Trimis", + "open_url_error": "Nu se poate deschide link-ul cu browser-ul implicit. Te rugăm să-ți schimbi browser-ul implicit și să încerci din nou.", + "rbf_explain": "Vom înlocui această tranzacție cu una cu un comision mai mare astfel încât să fie minată mai rapid. Acest proces se numește RBF — Replace by Fee.", + "watchOnlyWarningTitle": "Avertisment de securitate", + "watchOnlyWarningDescription": "Fii atent la escrocii care folosesc adesea portofele „doar citire” pentru a înșela utilizatorii. Aceste portofele nu îți permit să controlezi sau să trimiți fonduri; îți permit doar să vezi balanța.", + "custom_fee_warning_title": "Avertisment", + "custom_fee_warning_description": "Comisioanele sub 1 sat/vB sunt valide, dar este posibil să nu fie retransmise din cauza politicilor nodurilor.", + "details_eta_analyzing": "Se analizează...", + "details_sent": "Trimis", + "details_section": "Detalii", + "details_network_fee": "Comision de rețea", + "details_to_address": "Către", + "details_note": "Notă", + "details_add_note": "adaugă", + "details_advanced": "Avansat", + "details_fee_rate": "Rată comision", + "details_size": "Mărime", + "details_virtual_size": "Mărime virtuală", + "details_tx_hex": "Hex tranzacție", + "details_inputs_count": "Intrări ({count})", + "details_outputs_count": "Ieșiri ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Portofel Bitcoin simplu și puternic", "add_create": "Creează", + "total_balance": "Balanță totală", + "add_entropy": "Entropie", "add_entropy_generated": "{gen} bytes de entropie generată", "add_entropy_provide": "Furnizează entropie prin aruncări de zaruri", "add_entropy_remain": "{gen} bytes de entropie generată. {rem} bytes rămași vor fi obținuți din generatorul de numere aleatorii al Sistemului.", "add_import_wallet": "Import portofel", "add_lightning": "Lightning", "add_lightning_explain": "Pentru cheltuirea cu tranzacții instante", - "add_lndhub": "Conectează-te la propriul LNDHub", "add_lndhub_placeholder": "Adresa nodului tău", "add_placeholder": "primul meu portofel", "add_title": "Adaugă portofel", "add_wallet_name": "Nume", "add_wallet_type": "Tip", - "balance": "Balanță", "clipboard_bitcoin": "Ai o adresă Bitcoin în clipboard. Ai vrea să o folosești pentru o tranzacție?", "clipboard_lightning": "Ai o factură Lightning în clipboard. Ai vrea să o folosești pentru o tranzacție?", "details_address": "Adresă", "details_advanced": "Avansat", "details_are_you_sure": "Ești sigur(ă)?", "details_connected_to": "Conectat la", - "details_del_wb_err": "Balanța furnizată nu se potrivește cu balanța acestui portofel. Încearcă din nou.", "details_delete": "Șterge", "details_delete_wallet": "Șterge Portofelul", "details_derivation_path": "calea de derivare", - "details_display": "Afișează în Lista Portofelelor", "details_export_backup": "Exportă/Backup", "details_master_fingerprint": "Amprenta principală", "details_multisig_type": "multisig", - "details_no_cancel": "Nu, anulează", - "details_save": "Salvează", "details_show_xpub": "Afișează XPUB-ul portofelului", "details_show_addresses": "Arată adresele", "details_title": "Portofel", + "wallets": "Portofele", "details_type": "Tip", "details_use_with_hardware_wallet": "Folosește cu portofelul hardware", - "details_wallet_updated": "Portofel actualizat", "details_yes_delete": "Da, șterge", "enter_bip38_password": "Introdu parola pentru a decripta", "export_title": "Export portofel", @@ -391,42 +449,86 @@ "import_discovery_title": "Descoperire", "import_discovery_subtitle": "Alege un portofel descoperit", "import_discovery_no_wallets": "Niciun portofel nu a fost găsit.", - "import_derivation_found": "găsit", - "import_derivation_found_not": "nu a fost găsit", - "import_derivation_loading": "se încarcă...", "import_derivation_title": "Cale de derivare", - "import_derivation_unknown": "necunoscut", - "import_wrong_path": "Cale greșită de derivare", "list_create_a_button": "Adaugă acum", "list_create_a_wallet": "Adaugă un portofel", - "list_create_a_wallet_text": "Este gratuit și poți crea\ncît de multe vrei", "list_empty_txs1": "Tranzacțiile tale vor apărea aici.", "list_empty_txs1_lightning": "Portofelul Lightning e de folosit pentru tranzacțiile tale zilnice. Comisioanele sunt nedrept de ieftine, iar viteza este amețitor de rapidă. ", "list_empty_txs2": "Pornește cu portofelul tău.", "list_empty_txs2_lightning": "\nPentru a începe să utilizezi, apasă pe Administrează Fonduri și încarcă balanța.", "list_latest_transaction": "Ultima tranzacție", - "list_ln_browser": "Browser LApp", "list_long_choose": "Alege foto", - "list_long_clipboard": "Copiază din clipboard", - "list_long_scan": "Scaneză cod QR", + "paste_from_clipboard": "Lipește", + "import_file": "Importă fișier", + "list_long_scan": "Scanează cod QR", "list_title": "Portofele", "list_tryagain": "Încearcă din nou", - "no_ln_wallet_error": "Înainte de a plăti o factură Lightning, trebuie mai întîi să adaugi un portofel Lightning.", + "no_ln_wallet_error": "Înainte de a plăti o factură Lightning, trebuie mai întâi să adaugi un portofel Lightning.", "looks_like_bip38": "Aceasta arată ca o cheie privată protejată de o parolă (BIP38).", - "reorder_title": "Reordonează portofelele", "please_continue_scanning": "Continuă scanarea.", "select_no_bitcoin": "Nu e niciun portofel Bitcoin disponibil.", "select_no_bitcoin_exp": "E necesar un portofel Bitcoin pentru a re-umple portofelele Lightning. Creează sau importă unul.", "select_wallet": "Alege portofel", - "xpub_copiedToClipboard": "Copiat în clipboard.", "pull_to_refresh": "Trage pentru a reîncărca", - "warning_do_not_disclose": "Atenție! Nu dezvălui.", - "add_ln_wallet_first": " Trebuie mai întîi să adaugi un portofel Lightning.", + "add_ln_wallet_first": "Trebuie mai întâi să adaugi un portofel Lightning.", "identity_pubkey": "Pubkey identitate", - "xpub_title": "XPUB-ul portofelului" + "xpub_title": "XPUB-ul portofelului", + "add_entropy_reset_title": "Resetează entropia", + "add_entropy_reset_message": "Schimbarea tipului de portofel va reseta entropia curentă. Vrei să continui?", + "add_entropy_bytes": "{bytes} bytes de entropie", + "add_lndhub": "Conectează-te la LNDhub-ul tău", + "add_lndhub_error": "Adresa nodului furnizat este un nod LNDhub invalid.", + "add_wallet_seed_length": "Lungime seed", + "add_wallet_seed_length_12": "12 cuvinte", + "add_wallet_seed_length_24": "24 cuvinte", + "clear_clipboard_on_import": "Șterge clipboard-ul la import", + "details_del_wb_err": "Suma balanței furnizate nu corespunde balanței acestui portofel. Te rugăm să încerci din nou.", + "details_del_wb_q": "Acest portofel are o balanță. Înainte de a continua, te rugăm să fii conștient că nu vei putea recupera fondurile fără fraza seed a acestui portofel. Pentru a evita ștergerea accidentală, te rugăm să introduci balanța portofelului tău de {balance} satoshi.", + "details_display": "Afișează pe ecranul principal", + "details_export_history": "Exportă istoricul în CSV", + "swipe_balance_hide": "Ascunde", + "swipe_balance_show": "Afișează", + "drag_to_reorder": "Trage pentru a reordona", + "clear_search": "Șterge căutarea", + "import_passphrase": "Frază de acces", + "import_passphrase_title": "Frază de acces", + "import_passphrase_message": "Introdu fraza de acces dacă ai folosit una", + "import_success_watchonly": "Portofelul tău a fost importat cu succes. ATENȚIE: Acesta este un portofel doar citire, NU poți cheltui din el.", + "learn_more": "Află mai multe", + "import_discovery_derivation": "Folosește cale de derivare personalizată", + "import_discovery_offline": "BlueWallet este momentan în mod offline. În acest mod, nu poate verifica existența portofelului, așa că va trebui să selectezi manual portofelul corect.", + "import_derivation_found": "Găsit", + "import_derivation_found_not": "Negăsit", + "import_derivation_loading": "Se încarcă...", + "import_derivation_subtitle": "Introdu calea de derivare personalizată, și vom încerca să-ți descoperim portofelul.", + "import_derivation_unknown": "Necunoscut", + "import_wrong_path": "Cale de derivare greșită", + "list_create_a_wallet_text": "Este gratuit, și poți crea \ncâte vrei.", + "manage_title": "Administrează portofele", + "no_results_found": "Niciun rezultat găsit.", + "warning_do_not_disclose": "Nu împărtăși niciodată informațiile de mai jos", + "scan_import": "Scanează acest cod QR pentru a-ți importa portofelul în altă aplicație.", + "write_down_header": "Creează o copie de rezervă manuală", + "write_down": "Notează și păstrează în siguranță aceste cuvinte. Folosește-le pentru a-ți restaura portofelul mai târziu.", + "wallet_type_this": "Tipul acestui portofel este {type}.", + "share_number": "Partajează {number}", + "copy_ln_url": "Copiază și păstrează în siguranță acest URL pentru a-ți restaura portofelul mai târziu.", + "copy_ln_public": "Copiază și păstrează în siguranță aceste informații pentru a-ți restaura portofelul mai târziu.", + "manage_wallets_search_placeholder": "Caută portofele, adrese, tranzacții și note", + "more_info": "Mai multe informații", + "details_delete_wallet_error_message": "A apărut o problemă la confirmarea ștergerii acestui portofel din notificări — acest lucru se poate datora unei probleme de rețea sau a unei conexiuni slabe. Dacă continui, este posibil să primești în continuare notificări pentru tranzacțiile legate de acest portofel, chiar și după ștergerea sa.", + "details_delete_anyway": "Șterge oricum" + }, + "total_balance_view": { + "title": "Balanță totală", + "display_in_bitcoin": "Afișează în Bitcoin", + "hide": "Ascunde", + "display_in_sats": "Afișează în sats", + "display_in_fiat": "Afișează în {currency}", + "explanation": "Vezi balanța totală a tuturor portofelelor tale pe ecranul de prezentare generală." }, "multisig": { - "multisig_vault": "Seif", + "multisig_vault": "Seif multisig", "default_label": "Seif multisig", "multisig_vault_explain": "Cea mai bună securitate pentru sume mari. ", "provide_signature": "Furnizează semnătură", @@ -436,10 +538,9 @@ "fee_btc": "{number} BTC", "confirm": "Confirmă", "header": "Trimite", - "share": "Distribuie", "view": "Afișează", "manage_keys": "Administrează chei", - "how_many_signatures_can_bluewallet_make": "cîte semnături poate face BlueWallet", + "how_many_signatures_can_bluewallet_make": "câte semnături poate face BlueWallet", "signatures_required_to_spend": "Semnături necesare {number}", "signatures_we_can_make": "poate realiza {number}", "scan_or_import_file": "Scanează sau importă fișier", @@ -463,19 +564,14 @@ "quorum_header": "Cvorum", "of": "din", "wallet_type": "Tipul Portofelului", - "invalid_mnemonics": "Această frază mnemonică nu pare să fie validă.", "not_a_multisignature_xpub": "Acesta nu este un XPUB al unui portofel multi-semnătură!", - "invalid_cosigner_format": "Co-semnatar incorect: acesta nu este un co-semnatar pentru formatul {format}.", "create_new_key": "Creează o nouă", "scan_or_open_file": "Scanează sau deschide fișier", "i_have_mnemonics": "Am un seed pentru această cheie.", "type_your_mnemonics": "Inserează un seed pentru a importa cheia ta de Seif existentă.", - "this_is_cosigners_xpub": "Acesta este XPUB-ul co-semnatarului - gata de importat în alt portofel. Este sigur să îl distribui.", "wallet_key_created": "Seiful tău a fost creat. Ia-ți un moment să faci o copie de rezervă a seed-ului tău mnemonic.", "are_you_sure_seed_will_be_lost": "Ești sigur(ă)? Seed-ul tău mnemonic va fi pierdut dacă nu ai o copie de rezervă.", "forget_this_seed": "Uită acest seed și folosește XPUB-ul în loc.", - "view_edit_cosigners": "Vezi/Editează co-semnatari.", - "this_cosigner_is_already_imported": "Co-semnatarul este deja importat.", "export_signed_psbt": "Exportă PSBT semnată", "input_fp": "Introdu amprenta", "input_fp_explain": "Sari peste pentru a o folosi pe cea implicită (00000000)", @@ -489,30 +585,59 @@ "ms_help_title2": "Chei de editare", "ms_help_title3": "Copii de rezervă ale seifului", "ms_help_title4": "Importare seifuri", - "ms_help_4": "Pentru a importa un multisig, folosește fișierul de rezervă și funcționalitatea de Import. Dacă ai doar seed-uri și XPUB-uri, poți folosi butonul individual de Import atunci cînd creezi chei ale Seifului.", + "ms_help_4": "Pentru a importa un multisig, folosește fișierul de rezervă și funcționalitatea de Import. Dacă ai doar seed-uri și XPUB-uri, poți folosi butonul individual de Import atunci când creezi chei ale Seifului.", "ms_help_title5": "Mod avansat", - "ms_help_5": "Implicit, BlueWallet va genera un portofel 2-din-3. Pentru a crea un cvorum diferit sau pentru a schimba tipul adresei, activează Modul Avansat în Setări." + "ms_help_5": "Implicit, BlueWallet va genera un portofel 2-din-3. Pentru a crea un cvorum diferit sau pentru a schimba tipul adresei, activează Modul Avansat în Setări.", + "provide_signature_details": "Folosește dispozitivul și portofelul unde se află cheia pentru a semna această tranzacție", + "provide_signature_details_bluewallet": "În BlueWallet, mergi la meniul ecranului Trimite și selectează ", + "provide_signature_next_steps": "Scanează sau importă tranzacția semnată", + "provide_signature_next_steps_details": "Odată ce portofelul tău a semnat cu succes tranzacția, scanează codul QR furnizat sau importă fișierul însoțitor, apoi verifică toate detaliile tranzacției înainte de a o difuza.", + "share": "Partajează...", + "shared_key_detected": "Cosemnatar partajat", + "shared_key_detected_question": "Un cosemnatar a fost partajat cu tine, vrei să-l imporți?", + "invalid_mnemonics": "Această frază mnemonică nu pare să fie validă.", + "invalid_cosigner": "Date invalide pentru cosemnatar", + "invalid_cosigner_format": "Cosemnatar incorect: Acesta nu este un cosemnatar pentru formatul {format}.", + "this_is_cosigners_xpub": "Acesta este XPUB-ul cosemnatarului — gata să fie importat într-un alt portofel. Este sigur să-l partajezi.", + "this_is_cosigners_xpub_airdrop": "Dacă partajezi prin AirDrop, destinatarii trebuie să fie în ecranul de coordonare.", + "view_edit_cosigners": "Vezi/Editează cosemnatari", + "this_cosigner_is_already_imported": "Acest cosemnatar este deja importat.", + "ms_help_2": "Poți crea toate cheile Seifului pe acest dispozitiv și să le elimini sau editezi mai târziu. Având toate cheile pe același dispozitiv are securitatea echivalentă a unui portofel Bitcoin obișnuit.", + "ms_help_3": "În opțiunile portofelului, vei găsi copia de rezervă a Seifului tău și copia de rezervă doar citire. Această copie de rezervă este ca o hartă pentru portofelul tău. Este esențială pentru recuperarea portofelului în caz că pierzi unul dintre seed-urile tale." }, "is_it_my_address": { "title": "Este adresa mea?", "owns": "{label} deține {address}", "enter_address": "Introdu adresa", "check_address": "Verifică adresa", - "no_wallet_owns_address": "Niciunul dintre portofelele disponibile nu deține adresa furnizată.", - "view_qrcode": "Vezi cod QR" + "view_qrcode": "Vezi codul QR", + "no_wallet_owns_address": "Niciunul dintre portofelele disponibile nu deține adresa furnizată." + }, + "autofill_word": { + "title": "Ultimul cuvânt al seed-ului", + "enter": "Introdu fraza mnemonică parțială", + "generate_word": "Generează ultimul cuvânt", + "error": "Datele introduse nu reprezintă o frază mnemonică parțială de 11 sau 23 cuvinte. Te rugăm să încerci din nou." }, "cc": { - "change": "Schimb", + "change": "Rest", "coins_selected": "Monede selectate ({number})", "selected_summ": "{value} selectat", - "empty": "Acest portofel nu are nicio monedă în acest moment.", "freeze": "Îngheață", "freezeLabel": "Îngheață", "freezeLabel_un": "Deblochează", "header": "Controlul monedelor", "use_coin": "Folosește moneda", "use_coins": "Folosește monedele", - "tip": "Această funcționalitate îți permite să vezi, etichetezi, îngheți sau selectezi monedele pentru o administrare îmbunătățită a portofelului. Poți selecta monede multiple apăsînd cercurile colorate. " + "tip": "Această funcționalitate îți permite să vezi, etichetezi, îngheți sau selectezi monedele pentru o administrare îmbunătățită a portofelului. Poți selecta monede multiple apăsând cercurile colorate.", + "empty": "Acest portofel nu are nicio monedă în acest moment.", + "sort_asc": "Crescător", + "sort_desc": "Descrescător", + "sort_height": "Înălțime", + "sort_value": "Valoare", + "sort_label": "Etichetă", + "sort_status": "Stare", + "sort_by": "Sortează după" }, "units": { "BTC": "BTC", @@ -530,14 +655,22 @@ "sign_placeholder_message": "Mesaj", "sign_placeholder_signature": "Semnătură", "addresses_title": "Adrese", - "type_change": "Schimb", + "type_change": "Rest", "type_receive": "Primește", + "type_used": "Folosit", + "copy_private_key": "Copiază cheia privată", + "sensitive_private_key": "Avertisment: cheile private sunt extrem de sensibile. Continui?", + "sign_help": "Aici poți crea sau verifica o semnătură criptografică bazată pe o adresă Bitcoin.", "transactions": "Tranzacții" }, "lnurl_auth": { + "register_question_part_1": "Ai vrea să înregistrezi un cont la", "register_question_part_2": "folosești portofelul tău Lightning?", + "register_answer": "Ai înregistrat cu succes un cont la {hostname}!", + "login_question_part_1": "Ai vrea să te autentifici la", "login_question_part_2": "folosești portofelul tău Lightning?", "login_answer": "Te-ai autentificat cu succes la {hostname}!", + "could_not_auth": "Nu am putut să te autentificăm la {hostname}.", "link_question_part_1": "Ai vrea să legi contul tău de la", "link_question_part_2": "cu portofelul tău Lightning?", "link_answer": "Portofelul tău Lightning a fost legat cu succes cu contul tău de la {hostname}!", @@ -545,5 +678,27 @@ "auth_question_part_2": "folosești portofelul tău Lightning?", "auth_answer": "Te-ai autentificat cu succes la {hostname}!", "authenticate": "Autentifică" + }, + "bip47": { + "payment_code": "Cod de plată", + "contacts": "Contacte", + "bip47_explain": "Cod reutilizabil și partajabil", + "bip47_explain_subtitle": "BIP47", + "purpose": "Cod reutilizabil și partajabil (BIP47)", + "pay_this_contact": "Plătește acest contact", + "rename_contact": "Redenumește contactul", + "copy_payment_code": "Copiază codul de plată", + "hide_contact": "Ascunde contactul", + "rename": "Redenumește", + "provide_name": "Oferă un nume nou pentru acest contact", + "add_contact": "Adaugă contact", + "provide_payment_code": "Oferă codul de plată", + "invalid_pc": "Cod de plată invalid", + "notification_tx_unconfirmed": "Tranzacția de notificare nu este încă confirmată, te rugăm să aștepți", + "failed_create_notif_tx": "Crearea tranzacției on-chain a eșuat", + "onchain_tx_needed": "Este necesară o tranzacție on-chain", + "notif_tx_sent": "Tranzacție de notificare trimisă. Te rugăm să aștepți confirmarea ei", + "notif_tx": "Tranzacție de notificare", + "not_found": "Cod de plată negăsit" } } diff --git a/loc/ru.json b/loc/ru.json index 71ce11b64b9..fec0c7386e3 100644 --- a/loc/ru.json +++ b/loc/ru.json @@ -4,173 +4,166 @@ "cancel": "Отмена", "continue": "Продолжить", "clipboard": "Буфер обмена", + "copied": "Скопировано!", + "discard_changes": "Отменить изменения?", + "discard_changes_explain": "У вас есть несохранённые изменения. Вы уверены, что хотите отменить их и выйти?", "enter_password": "Введите пароль", "never": "Никогда", - "disabled": "Отключено", "of": "{number} из {total}", "ok": "OK", + "enter_url": "Введите URL", "storage_is_encrypted": "Ваше хранилище зашифровано. Введите пароль для расшифровки.", "yes": "Да", "no": "Нет", - "save": "Сохранить", - "seed": "Cид-фраза", + "save": "Сохранить...", + "seed": "Сид-фраза", "success": "Успешно", "wallet_key": "Ключ кошелька", - "invalid_animated_qr_code_fragment": "Ошибочный фрагмент QR-кода, попробуйте снова.", - "file_saved": "Файл {filePath} сохранён в {destination}.", - "downloads_folder": "Папка Загрузки", "close": "Закрыть", "change_input_currency": "Сменить валюту", "refresh": "Обновить", - "more": "Еще", - "pick_image": "Выбрать изображение из библиотеки", + "pick_image": "Выбрать из библиотеки", "pick_file": "Выбрать файл", - "enter_amount": "Ввести количество", - "qr_custom_input_button": "Нажмите 10 раз, чтобы ввести своё значение." - }, - "alert": { - "default": "Внимание" + "enter_amount": "Ввести сумму", + "qr_custom_input_button": "Нажмите 10 раз, чтобы ввести своё значение.", + "unlock": "Разблокировать", + "port": "Порт", + "ssl_port": "SSL-порт", + "suggested": "Рекомендуемые" }, "azteco": { - "codeIs": "Код вашего ваучера", - "errorBeforeRefeem": "Перед активацией нужно сделать Bitcoin кошелёк.", - "errorSomething": "Что-то пошло не так. Этот ваучер еще активен?", - "redeem": "Активировать в кошелёк", + "codeIs": "Ваш код ваучера:", + "errorBeforeRefeem": "Перед активацией необходимо добавить Bitcoin-кошелёк.", + "errorSomething": "Что-то пошло не так. Этот ваучер ещё действителен?", + "redeem": "Зачислить на кошелёк", "redeemButton": "Активировать", - "success": "Успех", + "success": "Успешно", + "successMessage": "Ваучер успешно активирован! Средства скоро поступят на ваш Bitcoin-кошелёк.", "title": "Активировать ваучер Azte.co" }, "entropy": { "save": "Сохранить", "title": "Энтропия", - "undo": "Отмена" + "undo": "Отмена", + "amountOfEntropy": "{bits} из {limit} бит" }, "errors": { - "broadcast": "Отправка не удалась", + "broadcast": "Отправка не удалась.", "error": "Ошибка", "network": "Ошибка сети" }, "lnd": { - "active": "Активный", - "inactive": "Неактивный", - "channels": "Каналы", - "no_channels": "Нет каналов", - "claim_balance": "Требовать баланс {balance}", - "close_channel": "Закрыть канал", - "new_channel": "Новый канал", - "errorInvoiceExpired": "Инвойс просрочен", - "force_close_channel": "Закрыть канал принудительно?", - "expired": "Истекший", - "node_alias": "Псевдоним ноды", + "errorInvoiceExpired": "Инвойс просрочен.", + "expired": "Просрочен", "expiresIn": "Истекает через {time} мин", "payButton": "Оплатить", - "placeholder": "Инвойс", - "open_channel": "Открыть Канал", - "funding_amount_placeholder": "Количество для пополнения, например 0.001", - "opening_channnel_for_from": "Открытие канала для кошелька {forWalletLabel} средствами из {fromWalletLabel}", - "are_you_sure_open_channel": "Вы уверены, что хотите открыть этот канал?", + "payment": "Платёж", + "placeholder": "Инвойс или адрес", "potentialFee": "Примерная комиссия: {fee}", - "remote_host": "Удалённый хост", "refill": "Пополнить", - "reconnect_peer": "Переподключиться", - "refill_create": "Чтобы продолжить, пожалуйста, создайте биткоин-кошелёк для пополнения.", + "refill_create": "Чтобы продолжить, создайте биткоин-кошелёк для пополнения.", "refill_external": "Пополнить с помощью внешнего кошелька", "refill_lnd_balance": "Пополнить баланс кошелька Lightning", "sameWalletAsInvoiceError": "Вы не можете оплатить инвойс тем же кошельком, который использовали для его создания.", - "title": "Мои средства", - "can_send": "Может Отправлять", - "can_receive": "Может Получать", - "view_logs": "Посмотреть Логи" + "title": "Управление средствами" }, "lndViewInvoice": { "additional_info": "Дополнительная информация", "for": "Для:", - "lightning_invoice": "Lightning инвойс", - "open_direct_channel": "Открыть канал с этой нодой:", + "lightning_invoice": "Lightning-инвойс", "please_pay_between_and": "Оплатите от {min} до {max}", "please_pay": "Пожалуйста, оплатите", - "preimage": "Предпросмотр", - "sats": "sats", - "wasnt_paid_and_expired": "Этот инвойс не был оплачен и просрочен" + "preimage": "Преимидж", + "sats": "сатоши.", + "date_time": "Дата и время", + "wasnt_paid_and_expired": "Этот инвойс не был оплачен и просрочен." }, "plausibledeniability": { - "create_fake_storage": "Создать фальшивое хранилище", - "create_password": "Придумайте пароль", - "create_password_explanation": "Пароль для фальшивого хранилища не должен совпадать с основным паролем", - "help": "В некоторых случаях вас могут вынудить раскрыть пароль. Чтобы сохранить ваши биткоины в безопасности, BlueWallet может создать еще одно зашифрованное хранилище с другим паролем. В случае шантажа вы можете раскрыть третьим лицам этот пароль. Если ввести этот пароль в BlueWallet, откроется 'фальшивое' хранилище. Это будет выглядеть правдоподобно для третьих лиц, но при этом сохранит ваше основное хранилище с биткоинами в безопасности.", - "help2": "Новое хранилище будет полностью функциональным, и вы даже можете хранить на нем небольшое количество биткоинов, чтобы это выглядело более правдоподобно.", - "password_should_not_match": "Пароль для фальшивого хранилища не должен быть таким же как основной пароль", - "passwords_do_not_match": "Пароли не совпадают, попробуйте еще раз", - "retype_password": "Повторите пароль", - "success": "Готово", + "create_fake_storage": "Создать зашифрованное хранилище", + "create_password_explanation": "Пароль для фальшивого хранилища не должен совпадать с основным паролем.", + "help": "В некоторых случаях вас могут вынудить раскрыть пароль. Чтобы сохранить ваши биткоины в безопасности, BlueWallet может создать ещё одно зашифрованное хранилище с другим паролем. Под давлением вы можете раскрыть этот пароль третьей стороне. Введя его в BlueWallet, вы откроете «фальшивое» хранилище, а основное останется скрытым.", + "help2": "Новое хранилище будет полностью функциональным, и вы можете хранить на нём небольшое количество средств для правдоподобия.", + "password_should_not_match": "Этот пароль уже используется. Попробуйте другой пароль.", "title": "Двойное дно" }, "pleasebackup": { - "ask": "Вы сохранили сид-фразу вашего кошелька? Эта резервная фраза необходима для восстановления доступа к вашим средствам, если вы потеряете это устройство. Без резервной фразы ваши средства будут потеряны навсегда.", - "ask_no": "Нет, я не сохранил", - "ask_yes": "Да, я сохранил", + "ask": "Вы сохранили сид-фразу вашего кошелька? Без неё восстановить средства будет невозможно.", + "ask_no": "Нет, я не сохранил.", + "ask_yes": "Да, я сохранил.", "ok": "Я всё записал!", "ok_lnd": "Я всё сохранил.", - "text": "Пожалуйста, запишите эту мнемоническую фразу на листе бумаги. \nЭто ваша резервная копия, которую вы можете использовать для восстановления кошелька на другом устройстве.", - "text_lnd": "Сохраните резервную копию кошелька. Это поможет восстановить его в случае потери.", - "title": "Кошелёк создан" + "text": "Запишите эту мнемоническую фразу на бумаге.\nОна понадобится для восстановления кошелька.", + "text_lnd": "Сохраните резервную копию кошелька. Она поможет восстановить его при потере устройства.", + "title": "Кошелёк создан." }, "receive": { "details_create": "Создать", "details_label": "Описание", "details_setAmount": "Указать сумму", - "details_share": "Поделиться", + "details_share": "Поделиться...", + "address_not_found": "Не удалось сгенерировать адрес для приёма.", "header": "Получить", - "maxSats": "Максимальная сумма: {max} sats", - "maxSatsFull": "Максимальная сумма: {max} sats или {currency}", - "minSats": "Минимальная сумма: {min} sats", - "minSatsFull": "Минимальная сумма: {min} sats или {currency}" + "reset": "Сбросить", + "maxSats": "Максимальная сумма: {max} сатоши", + "maxSatsFull": "Максимальная сумма: {max} сатоши или {currency}", + "minSats": "Минимальная сумма: {min} сатоши", + "minSatsFull": "Минимальная сумма: {min} сатоши или {currency}", + "qrcode_for_the_address": "QR-код для адреса", + "bip47_explanation": "Коды оплаты — это универсальные адреса, скрывающие ваши реальные. Поддерживается не всеми сервисами." }, "send": { - "provided_address_is_invoice": "Похоже, этот адрес предназначен для Лайтнинг-инвойса. Пожалуйста, перейдите в свой кошелёк Лайтнинг, чтобы оплатить этот счет.", - "broadcastButton": "ОТПРАВИТЬ", + "provided_address_is_invoice": "Похоже, этот адрес предназначен для Lightning-инвойса. Перейдите в свой Lightning-кошелёк, чтобы оплатить его.", + "broadcastButton": "Отправить", "broadcastError": "Ошибка", - "broadcastNone": "Хэш входящей транзакции", + "broadcastNone": "Вставьте HEX транзакции", "broadcastPending": "В процессе", - "broadcastSuccess": "Успех", + "broadcastSuccess": "Успешно", "confirm_header": "Подтвердить", "confirm_sendNow": "Отправить", - "create_amount": "Сколько", + "create_amount": "Сумма", "create_broadcast": "Отправить", "create_copy": "Скопировать и отправить позже", "create_details": "Детали", "create_fee": "Комиссия", "create_memo": "Примечание", - "create_satoshi_per_vbyte": "Satoshi за vByte", - "create_this_is_hex": "Это данные транзакции. Транзакция подписана и готова к трансляции в сеть. Продолжить?", - "create_to": "Куда", - "create_tx_size": "Размер", + "create_satoshi_per_vbyte": "Сатоши за vByte", + "create_this_is_hex": "Это данные вашей транзакции — подписаны и готовы к отправке в сеть.", + "create_to": "Кому", + "create_tx_size": "Размер Транзакции", "create_verify": "Проверить на coinb.in", - "details_add_rec_add": "Добавить Получателя", - "details_add_rec_rem": "Удалить Получателя", + "details_insert_contact": "Добавить контакт", + "details_add_rec_add": "Добавить получателя", + "details_add_rec_rem": "Удалить получателя", + "details_add_recc_rem_all_alert_description": "Вы уверены, что хотите удалить всех получателей?", + "details_add_rec_rem_all": "Удалить всех получателей", + "details_recipients_title": "Получатели", + "details_recipient_title": "Получатель #{number} из #{total}", + "please_complete_recipient_title": "Незаполненный получатель", + "please_complete_recipient_details": "Пожалуйста, заполните данные получателя #{number} перед добавлением нового.", "details_address": "Адрес", - "details_address_field_is_not_valid": "Введенный адрес неверный", - "details_adv_fee_bump": "Включить повышение комиссии", + "details_address_field_is_not_valid": "Введённый адрес неверен.", + "details_adv_fee_bump": "Разрешить повышение комиссии", "details_adv_full": "Использовать весь баланс", - "details_adv_full_sure": "Вы уверены, что хотите использовать весь баланс кошелька для этой транзакции?", - "details_adv_full_sure_frozen": "Вы уверены, что хотите использовать весь баланс кошелька для этой транзакции? Учтите, что замороженные монеты не учитываются.", + "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_create": "Создать", - "details_error_decode": "Нельзя декодировать Bitcoin адрес", - "details_fee_field_is_not_valid": "Введенная комиссия неверна", - "details_frozen": "{amount} BTC заморожено", + "details_amount_field_is_not_valid": "Введённая сумма неверна.", + "details_amount_field_is_less_than_minimum_amount_sat": "Сумма слишком мала. Введите больше 500 сатоши.", + "details_create": "Создать инвойс", + "details_error_decode": "Не удалось декодировать Bitcoin-адрес", + "details_fee_field_is_not_valid": "Введённая комиссия неверна.", + "details_frozen": "{amount} BTC заморожено.", "details_next": "Дальше", - "details_no_signed_tx": "В файле нет транзакций, которые можно импортировать.", - "details_note_placeholder": "примечание платежа", + "details_no_signed_tx": "Файл не содержит импортируемой транзакции.", + "details_note_placeholder": "Заметка", "details_scan": "Скан", - "details_scan_hint": "Тапните два раза, чтобы сканировать или импортировать адрес отправки", - "details_total_exceeds_balance": "Общая сумма превышает баланс.", - "details_total_exceeds_balance_frozen": "Сумма отправки превышает доступный баланс. Обратите внимание, что замороженные монеты не учитываются.", - "details_unrecognized_file_format": "Неизвестный формат", - "details_wallet_before_tx": "Перед созданием транзакции нужно создать Bitcoin кошелёк.", + "details_scan_hint": "Дважды нажмите, чтобы сканировать или импортировать адрес назначения", + "details_scan_error": "Ошибка сканирования", + "details_total_exceeds_balance": "Сумма превышает доступный баланс.", + "details_total_exceeds_balance_frozen": "Сумма превышает баланс. Замороженные монеты не учитываются.", + "details_unrecognized_file_format": "Неизвестный формат файла", + "details_wallet_before_tx": "Перед созданием транзакции добавьте Bitcoin-кошелёк.", "dynamic_init": "Инициализация", "dynamic_next": "Следующий", "dynamic_prev": "Предыдущий", @@ -180,430 +173,532 @@ "fee_1d": "1д", "fee_3h": "3ч", "fee_custom": "Другое", + "insert_custom_fee": "Ввести комиссию", "fee_fast": "Быстро", "fee_medium": "Средне", - "fee_replace_minvb": "Общая комиссия (сатоши за vByte), которую Вы хотите заплатить, должна быть выше {min} sat/vByte.", - "fee_satvbyte": "в сатоши/vByte", + "fee_replace_minvb": "Комиссия должна быть выше {min} сат/вБайт.", + "fee_satvbyte": "в сат/вБайт", "fee_slow": "Медленно", "header": "Отправить", "input_clear": "Очистить", "input_done": "Готово", "input_paste": "Вставить", "input_total": "Всего:", - "permission_camera_message": "Нужно ваше разрешение на использование камеры", + "permission_camera_message": "Нужно разрешение на использование камеры.", "psbt_sign": "Подписать транзакцию", + "invalid_psbt": "Предоставлен неверный PSBT.", "open_settings": "Открыть настройки", - "permission_storage_later": "Спросить позже", - "permission_storage_message": "BlueWallet нужно ваше разрешение для доступа к хранилищу, чтобы сохранить этот файл.", - "permission_storage_denied_message": "BlueWallet не может сохранить файл. Пожалуйста, откройте настройки устройства и разрешите использование Хранилища.", - "permission_storage_title": "Разрешение на доступ к Хранилищу", + "permission_storage_denied_message": "BlueWallet не может сохранить файл. Разрешите доступ к хранилищу в настройках устройства.", + "permission_storage_title": "Разрешение на доступ к хранилищу", "psbt_clipboard": "Скопировать в буфер обмена", - "psbt_this_is_psbt": "Это частично подписанная транзакция (PSBT). Пожалуйста, завершите подпись в вашем аппаратном кошельке.", + "psbt_this_is_psbt": "Это частично подписанная Bitcoin-транзакция (PSBT). Завершите подпись в вашем аппаратном кошельке.", "psbt_tx_export": "Экспортировать в файл", - "no_tx_signing_in_progress": "Нет транзакции в процессе подписания", + "no_tx_signing_in_progress": "Нет транзакции в процессе подписания.", "outdated_rate": "Курс был обновлён: {date}", - "psbt_tx_open": "Открыть Подписаную Транзакцию", - "psbt_tx_scan": "Сканировать Подписаную Транзакцию", - "qr_error_no_qrcode": "Нам не удалось найти QR-код на выбранном изображении. Убедитесь, что изображение содержит только QR-код и ничего лишнего, например текста или кнопок.", - "reset_amount": "Сбросить Количество", - "reset_amount_confirm": "Хотите сбросить количество?", + "psbt_tx_open": "Открыть подписанную транзакцию", + "psbt_tx_scan": "Сканировать подписанную транзакцию", + "qr_error_no_qrcode": "Не удалось найти QR-код на изображении. Убедитесь, что оно содержит только QR-код.", + "reset_amount": "Сбросить сумму", + "reset_amount_confirm": "Хотите сбросить сумму?", "success_done": "Готово", - "txSaved": "Файл с транзакцией ({filePath}) сохранён в папке Загрузки.", + "txSaved": "Файл транзакции ({filePath}) сохранён.", + "file_saved_at_path": "Файл ({filePath}) сохранён.", + "cant_send_to_silentpayment_adress": "Этот кошелёк не может отправлять на адреса SilentPayment", + "cant_send_to_bip47": "Этот кошелёк не может отправлять на коды оплаты BIP47", + "cant_find_bip47_notification": "Сначала добавьте этот Код Оплаты в контакты", "problem_with_psbt": "Проблема с PSBT" }, "settings": { "about": "О программе", "about_awesome": "Основано на офигенных", "about_backup": "Всегда делайте резервные копии ваших ключей!", - "about_free": "BlueWallet - это бесплатный проект с открытым исходным кодом. Создано Биткойн пользователями.", + "about_free": "BlueWallet — бесплатный проект с открытым исходным кодом. Создан Bitcoin-пользователями.", "about_license": "Лицензия MIT", "about_release_notes": "История изменений", "about_review": "Оставьте отзыв о нас", "performance_score": "Оценка производительности: {num}", "run_performance_test": "Тест производительности", "about_selftest": "Запустить самодиагностику", - "about_selftest_electrum_disabled": "Самотестирование недоступно в офлайн режиме. Пожалуйста, отключите офлайн режим и попробуйте еще раз.", - "about_selftest_ok": "Все внутренние тестирование прошло успешно.\nКошелёк работает отлично. ", + "block_explorer_invalid_custom_url": "Предоставленный URL недействителен. Введите URL, начинающийся с http:// или https://.", + "about_selftest_electrum_disabled": "Самотестирование недоступно в офлайн режиме. Отключите офлайн режим и попробуйте ещё раз.", + "about_selftest_ok": "Все внутренние тесты прошли успешно. Кошелёк работает отлично.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord-сервер", "about_sm_telegram": "Telegram-канал", - "about_sm_twitter": "Мы в Twitter", - "advanced_options": "Расширенные настройки", + "donate": "Поддержать", + "donate_description": "Помогите нам сохранить Blue бесплатным!", + "privacy_temporary_screenshots": "Разрешить снимки экрана", + "privacy_temporary_screenshots_instructions": "Защита от снимков экрана будет временно отключена, что позволит делать скриншоты и записи экрана. Защита автоматически включится снова после закрытия и повторного открытия BlueWallet.", "biometrics": "Биометрия", - "biom_10times": "Вы 10 раз пытались ввести пароль. Хотите очистить хранилище? Все кошельки будут удалены, а Хранилище дешифровано.", - "biom_conf_identity": "Пожалуйста, подтвердите вашу личность.", - "biom_no_passcode": "На вашем устройстве нет пароля. Чтобы продолжить, задайте пароль в Настройках устройства.", - "biom_remove_decrypt": "Все кошельки будут удалены, а Хранилище расшифровано. Продолжить?", + "biometrics_no_longer_available": "Настройки устройства изменились и больше не соответствуют выбранным в приложении. Включите биометрию или пароль и перезапустите приложение.", + "biom_10times": "Вы 10 раз ввели неверный пароль. Очистить хранилище? Это удалит все кошельки.", + "biom_conf_identity": "Подтвердите личность.", + "biom_no_passcode": "На устройстве не установлен пароль или биометрия. Включите их в настройках.", + "biom_remove_decrypt": "Все кошельки будут удалены, а хранилище расшифровано. Продолжить?", "currency": "Валюта", - "currency_source": "Курс получается из", - "currency_fetch_error": "При получении курса для выбранной валюты произошла ошибка.", - "default_desc": "Когда выключено BlueWallet сразу же откроет выбранный кошелёк при запуске.", - "default_info": "Показать", - "default_title": "При старте", - "default_wallets": "Показать все кошельки", + "currency_source": "Курс получен от", + "currency_fetch_error": "Ошибка получения курса.", + "default_title": "При запуске", "electrum_connected": "Подключено", - "electrum_connected_not": "Не Подключено", - "electrum_error_connect": "Не удается подключиться к Electrum серверу", + "electrum_connected_not": "Не подключено", + "electrum_error_connect": "Не удаётся подключиться к Electrum-серверу", + "electrum_error_connect_tor": "Не удаётся подключиться к серверу. Убедитесь, что Orbot запущен.", "lndhub_uri": "Например, {example}", "electrum_host": "Например, {example}", "electrum_offline_mode": "Офлайн режим", - "electrum_offline_description": "Когда включено, ваши биткойн-кошельки не будут пытаться обновить балансы или транзакции.", + "electrum_offline_description": "В офлайн режиме балансы и транзакции не обновляются.", "electrum_port": "Порт, обычно {example}", "use_ssl": "Использовать SSL", - "electrum_saved": "Ваши изменения были успешно сохранены. Для вступления изменений в силу может потребоваться перезагрузка.", - "set_electrum_server_as_default": "Задать {server} как сервер Electrum по умолчанию?", - "set_lndhub_as_default": "Задать {url} как сервер LNDHub по умолчанию?", - "electrum_settings_server": "Сервер Electrum", - "electrum_settings_explain": "Очистите, чтобы использовать по умолчанию.", + "electrum_saved": "Сервер сохранён. Может потребоваться перезагрузка.", + "set_electrum_server_as_default": "Использовать {server} как сервер Electrum по умолчанию?", + "set_lndhub_as_default": "Использовать {url} как сервер LNDhub по умолчанию?", + "electrum_settings_server": "Electrum-сервер", "electrum_status": "Статус", - "electrum_clear_alert_title": "Очистить историю?", - "electrum_clear_alert_message": "Удалить историю серверов Electrum?", - "electrum_clear_alert_cancel": "Отмена", - "electrum_clear_alert_ok": "Ок", - "electrum_select": "Выбрать", - "electrum_reset": "По умолчанию", + "electrum_preferred_server": "Предпочтительный сервер", + "electrum_preferred_server_description": "Введите сервер, который вы хотите использовать для всех операций Bitcoin. Кошелёк будет использовать только этот сервер.", "electrum_unable_to_connect": "Невозможно подключиться к {server}.", "electrum_history": "История", - "electrum_reset_to_default": "Вы уверены, что хотите сбросить значение сервера Electrum?", - "electrum_clear": "Очистить", - "tor_supported": "Tor поддерживается", - "tor_unsupported": "Соединения Tor не поддерживаются.", + "electrum_reset_to_default": "BlueWallet будет выбирать сервер случайно.", + "electrum_reset": "По умолчанию", + "electrum_reset_to_default_and_clear_history": "Сбросить и очистить историю", "encrypt_decrypt": "Расшифровать хранилище", - "encrypt_decrypt_q": "Вы уверены, что хотите расшифровать хранилище? Это позволит получить доступ к вашим кошелькам без пароля.", - "encrypt_enc_and_pass": "Зашифровано и защищено паролем", + "encrypt_decrypt_q": "Расшифровать хранилище и убрать пароль?", + "encrypt_storage_explanation_headline": "Включить шифрование хранилища", + "encrypt_storage_explanation_description_line1": "Шифрование хранилища добавляет дополнительный уровень защиты, надёжно храня данные.", + "encrypt_storage_explanation_description_line2": "Шифрование защищает доступ к кошелькам, но не накладывает пароль на сами кошельки.", + "i_understand": "Я понимаю", + "block_explorer": "Блокчейн-обозреватель", + "block_explorer_preferred": "Использовать предпочитаемый блокчейн-обозреватель", + "block_explorer_error_saving_custom": "Ошибка сохранения блокчейн-обозревателя", "encrypt_title": "Безопасность", "encrypt_tstorage": "Хранилище", "encrypt_use": "Использовать {type}", - "encrypt_use_expl": "{type} будет использоваться для подтверждения вашей личности перед совершением транзакции, разблокировкой, экспортом или удалением кошелька. {type} не будет использоваться для открытия зашифрованного хранилища.", + "encrypt_use_expl": "{type} будет использоваться для подтверждения вашей личности перед отправкой транзакции, разблокировкой, экспортом или удалением кошелька.", + "encrypt_enc_and_pass": "Защищено паролем", + "set_as_preferred": "Установить как предпочитаемый", + "set_as_preferred_electrum": "Установка {host}:{port} как предпочитаемого сервера отключит случайное подключение к рекомендованным серверам.", + "encrypted_feature_disabled": "Функция недоступна при зашифрованном хранилище.", + "biometrics_fail": "Если {type} не сработает, используйте пароль устройства.", "general": "Основные", - "general_adv_mode": "Включить расширенный режим", - "general_adv_mode_e": "При включении вы увидите расширенные параметры, такие как разные типы кошельков, возможность указать экземпляр LNDHub, к которому вы хотите подключиться, и пользовательскую энтропию при создании кошелька.", "general_continuity": "Непрерывность", - "general_continuity_e": "Когда эта функция включена, вы сможете просматривать выбранные кошельки и транзакции, используя другие устройства, подключенные к Apple iCloud.", - "groundcontrol_explanation": "GroundControl - это бесплатный сервер push-уведомлений с открытым исходным кодом для биткойн-кошельков. Вы можете установить свой собственный сервер GroundControl и указать здесь его URL, чтобы не полагаться на инфраструктуру BlueWallet. Оставьте пустым, чтобы использовать по умолчанию", + "general_continuity_e": "Просматривайте кошельки на других устройствах через iCloud.", + "groundcontrol_explanation": "GroundControl — открытый сервер push-уведомлений. Укажите свой URL, чтобы не использовать сервер BlueWallet.", "header": "Настройки", "language": "Язык", "last_updated": "Последнее обновление", - "language_isRTL": "Перезапустите приложение для смены направления языка.", - "lightning_error_lndhub_uri": "Неверный URI LNDHub", - "lightning_saved": "Ваши изменения были успешно сохранены", + "language_isRTL": "Перезапустите BlueWallet для смены направления языка.", + "license": "Лицензия", + "lightning_error_lndhub_uri": "Неверный LNDhub URI", + "lightning_error_lndhub_uri_tor": "Неверный LNDhub URI. Убедитесь, что Orbot подключён, и попробуйте снова.", + "lightning_saved": "Изменения сохранены.", "lightning_settings": "Настройки Lightning", - "tor_settings": "Настройки Tor", - "lightning_settings_explain": "Чтобы подключиться к собственному LND, установите LNDHub и укажите его URL в настройках. Оставьте поле пустым, чтобы использовать LNDHub BlueWallet. Обратите внимание, что только кошельки, созданные после сохранения изменений, будут подключаться к указанному LNDHub.", + "lightning_settings_explain": "Чтобы подключиться к своей LND-ноде, установите LNDhub и укажите его URL. Новые кошельки будут использовать его.", + "lndhub_github": "Репозиторий GitHub", "network": "Сеть", "network_broadcast": "Отправить транзакцию", - "network_electrum": "Electrum сервер", + "network_electrum": "Electrum-сервер", + "electrum_suggested_description": "Если сервер по умолчанию не выбран, будет использован случайный.", "not_a_valid_uri": "Неверный URI", "notifications": "Уведомления", - "open_link_in_explorer": "Посмотреть в эксплорере", + "open_link_in_explorer": "Посмотреть в обозревателе", "password": "Пароль", - "password_explain": "Придумайте пароль для расшифровки хранилища", - "passwords_do_not_match": "Пароли не совпадают", + "password_explain": "Введите пароль для разблокировки хранилища.", "plausible_deniability": "Двойное дно", "privacy": "Приватность", "privacy_read_clipboard": "Чтение буфера обмена", "privacy_system_settings": "Настройки системы", "privacy_quickactions": "Быстрые команды", - "privacy_quickactions_explanation": "Нажмите и удерживайте значок приложения BlueWallet на главном экране, чтобы быстро просмотреть баланс своего кошелька.", - "privacy_clipboard_explanation": "Показать меню с действием, если в Буфере обмена есть адрес или инвойс.", - "privacy_do_not_track": "Выключить Аналитику", - "privacy_do_not_track_explanation": "Информация о производительности и надежности не будет отправлена на анализ.", - "push_notifications": "Push-уведомления", + "privacy_quickactions_explanation": "Долгое нажатие на иконку BlueWallet показывает баланс.", + "privacy_clipboard_explanation": "Показывать действие, если в буфере адрес или инвойс.", + "privacy_do_not_track": "Выключить аналитику", + "privacy_do_not_track_explanation": "Данные производительности не будут отправляться.", "rate": "Курс", - "retype_password": "Повторите пароль", + "push_notifications_explanation": "При включении уведомлений токен вашего устройства отправляется на сервер вместе с адресами кошельков и идентификаторами транзакций всех кошельков и транзакций, созданных после включения уведомлений. Токен используется для отправки уведомлений, а данные кошельков позволяют сообщать вам о входящих Bitcoin и подтверждениях транзакций.\n\nПередаётся только информация, появившаяся после включения уведомлений — ничего из того, что было раньше, не собирается.\n\nОтключение уведомлений удалит всю эту информацию с сервера. Также при удалении кошелька из приложения связанная с ним информация удаляется с сервера.", "selfTest": "Проверка приложения", "save": "Сохранить", "saved": "Сохранено", - "success_transaction_broadcasted": "Транзакция успешно транслирована в сеть!", + "success_transaction_broadcasted": "Транзакция успешно транслирована!", "total_balance": "Общий баланс", - "total_balance_explanation": "Показывать в виджетах общий баланс кошельков", + "total_balance_explanation": "Показывать общий баланс в виджетах.", "widgets": "Виджеты", "tools": "Инструменты" }, "notifications": { "would_you_like_to_receive_notifications": "Хотите получать уведомления при входящих платежах?", - "no_and_dont_ask": "Нет, и больше не спрашивать", - "ask_me_later": "Спросить позже" + "notifications_subtitle": "Входящие платежи и подтверждения транзакций", + "no_and_dont_ask": "Нет, и больше не спрашивать.", + "permission_denied_message": "Вы запретили отправку уведомлений. Включите их в настройках устройства, если необходимо." }, "transactions": { - "cancel_explain": "Мы заменим эту транзакцию на другую, которая переведет Вам деньги и имеет более высокую комиссию. Это позволит отменить текущую транзакцию. Это называется RBF — Replace by Fee.", - "cancel_no": "Эту транзакцию нельзя отменить", + "cancel_explain": "Мы заменим эту транзакцию другой с более высокой комиссией (RBF), фактически отменив текущую.", + "cancel_no": "Эту транзакцию нельзя отменить.", "cancel_title": "Отменить транзакцию (RBF)", + "transaction_loading_error": "Произошла ошибка загрузки транзакции. Попробуйте позже.", + "transaction_not_available": "Транзакция недоступна", "confirmations_lowercase": "{confirmations} подтверждений", - "copy_link": "Копировать ссылку", "expand_note": "Развернуть заметку", "cpfp_create": "Создать", - "cpfp_exp": "Мы создадим новую транзакцию, которая потратит вашу неподтвержденную. Общая сумма комиссии будет выше, чем первоначальная комиссия, поэтому она будет добыта быстрее. Это называется CPFP - ребенок платит за родителя.", - "cpfp_no_bump": "Комиссию этой транзакции нельзя повысить", + "cpfp_exp": "Будет создана CPFP-транзакция с большей комиссией, чтобы ускорить подтверждение.", + "cpfp_no_bump": "Комиссию этой транзакции нельзя повысить.", "cpfp_title": "Повысить комиссию (CPFP)", "details_balance_hide": "Скрыть баланс", "details_balance_show": "Показать баланс", - "details_block": "Номер Блока", "details_copy": "Копировать", - "details_copy_amount": "Копировать Сумму", - "details_copy_block_explorer_link": "Копировать ссылку на обозреватель блоков", - "details_copy_note": "Копировать Заметку", - "details_copy_txid": "Копировать ID транзакции", - "details_from": "От", + "details_copy_block_explorer_link": "Копировать ссылку обозревателя", + "details_copy_note": "Копировать заметку", + "details_copy_txid": "Копировать txid", "details_inputs": "Входы", + "details_inputs_count": "Входы ({count})", "details_outputs": "Выходы", + "details_outputs_count": "Выходы ({count})", "date": "Дата", - "details_received": "Получена", - "transaction_note_saved": "Заметка о транзакции успешно сохранена.", - "details_show_in_block_explorer": "Показать транзакцию в блокчейне", - "details_title": "Детали транзакции", - "details_to": "Кому", - "enable_offline_signing": "Этот кошелёк не используется вместе с оффлайн подписью. Хотите включить это сейчас?", - "list_conf": "{number} подтв.", - "pending": "В процессе", - "pending_with_amount": "В ожидании {amt1} ({amt2})", + "details_received": "Получено", + "details_sent": "Отправлено", + "details_section": "Подробности", + "details_explorer": "обозреватель", + "details_network_fee": "Сетевая комиссия", + "details_to_address": "Кому", + "details_id": "ID", + "details_note": "Заметка", + "details_add_note": "добавить", + "details_advanced": "Дополнительно", + "details_fee_rate": "Ставка комиссии", + "details_size": "Размер", + "details_virtual_size": "Виртуальный размер", + "details_tx_hex": "Hex транзакции", + "details_eta_analyzing": "Анализ...", + "details_view_in_browser": "Открыть в браузере", + "details_title": "Транзакция", + "incoming_transaction": "Входящая", + "outgoing_transaction": "Исходящая", + "expired_transaction": "Просроченная", + "pending_transaction": "В ожидании", + "offchain": "Вне цепочки", + "onchain": "В цепочке", + "details_to": "Выход", + "enable_offline_signing": "Включить офлайн-подпись для этого кошелька?", + "list_conf": "Подтв.: {number}", + "pending": "В ожидании", + "pending_with_amount": "Ожидает {amt1} ({amt2})", "received_with_amount": "+{amt1} ({amt2})", - "eta_10m": "Примерно 10 минут", - "eta_3h": "Примерно 3 часа", - "eta_1d": "Примерно 1 день", - "view_wallet": "Просмотр {walletLabel}", - "list_title": "Мои транзакции", - "open_url_error": "Не удалось открыть ссылку в браузере по умолчанию. Пожалуйста, измените свой браузер по умолчанию и попробуйте еще раз.", - "rbf_explain": "Мы заменим эту транзакцию на другую с более высокой комиссией, чтобы она была обработана быстрее. Это называется RBF—Replace by Fee.", + "eta_10m": "≈ 10 минут", + "eta_3h": "≈ 3 часа", + "eta_1d": "≈ 1 день", + "list_title": "Транзакции", + "list_title_received": "Получено", + "list_title_sent": "Отправлено", + "transaction": "Транзакция", + "open_url_error": "Не удалось открыть ссылку. Измените браузер по умолчанию.", + "rbf_explain": "Заменим транзакцию другой с большей комиссией (RBF).", "rbf_title": "Повысить комиссию (RBF)", "status_bump": "Повысить комиссию", - "status_cancel": "Отменить транзакцию", - "transactions_count": "Количество транзакций", - "txid": "ID транзакции", - "updating": "Обновление..." + "status_cancel": "Отменить", + "transactions_count": "Всего транзакций", + "txid": "TXID", + "updating": "Обновление...", + "watchOnlyWarningTitle": "Предупреждение безопасности", + "watchOnlyWarningDescription": "Кошельки «только для чтения» не позволяют тратить средства. Будьте осторожны.", + "custom_fee_warning_title": "Внимание", + "custom_fee_warning_description": "Комиссии ниже 1 сат/вБайт действительны, но могут не ретранслироваться из-за политик узлов." }, "wallets": { "add_bitcoin": "Bitcoin", - "add_bitcoin_explain": "Простой и мощный Биткоин кошелёк", + "add_bitcoin_explain": "Простой и мощный Bitcoin-кошелёк", "add_create": "Создать", + "total_balance": "Общий баланс", + "add_entropy_reset_title": "Сброс энтропии", + "add_entropy_reset_message": "Смена типа кошелька сбросит текущую энтропию. Продолжить?", + "add_entropy": "Энтропия", + "add_entropy_bytes": "{bytes} байтов энтропии", "add_entropy_generated": "{gen} байтов сгенерированной энтропии", "add_entropy_provide": "Сгенерировать энтропию с помощью игральных костей", "add_entropy_remain": "{gen} байтов сгенерированной энтропии. Оставшиеся {rem} байтов будут получены из системного генератора случайных чисел.", "add_import_wallet": "Импортировать кошелёк", "add_lightning": "Lightning", "add_lightning_explain": "Для мгновенных переводов", - "add_lndhub": "Подключиться к своему LNDHub", - "add_lndhub_error": "Неверный адрес LNDHub.", - "add_lndhub_placeholder": "Адрес хоста", + "add_lndhub": "Подключиться к вашему LNDhub", + "add_lndhub_error": "Указанный адрес не является действительной нодой LNDhub.", + "add_lndhub_placeholder": "Адрес вашей ноды", "add_placeholder": "мой первый кошелёк", "add_title": "Добавить кошелёк", "add_wallet_name": "Имя кошелька", "add_wallet_type": "Тип кошелька", - "balance": "Баланс", - "clipboard_bitcoin": "В буфере обмена есть Биткоин адрес. Использовать его для создания транзакции?", - "clipboard_lightning": "В буфере обмена есть Lightning инвойс. Использовать его для создания транзакции?", + "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": "Очищать буфер обмена после импорта", "details_address": "Адрес", "details_advanced": "Расширенные настройки", "details_are_you_sure": "Точно удалить?", "details_connected_to": "Подключено к", - "details_del_wb_err": "Указанная сумма баланса не соответствует балансу этого кошелька. Пожалуйста, попробуйте еще раз", - "details_del_wb_q": "На этом кошельке есть средства. Прежде чем продолжить, имейте в виду, что вы не сможете вернуть средства без seed фразы этого кошелька. Чтобы избежать случайного удаления этого кошелька, введите баланс {balance} сатоши в вашем кошельке.", + "details_del_wb_err": "Указанная сумма баланса не соответствует балансу этого кошелька. Пожалуйста, попробуйте ещё раз.", + "details_del_wb_q": "На этом кошельке есть средства. Перед удалением введите баланс {balance} сатоши, чтобы подтвердить.", "details_delete": "Удалить", "details_delete_wallet": "Удалить кошелёк", - "details_derivation_path": "путь деривации", - "details_display": "Показывать в списке кошельков", + "details_derivation_path": "Путь деривации", + "details_display": "Показывать на главном экране", "details_export_backup": "Экспорт/резервное копирование", "details_export_history": "Экспортировать историю в CSV", - "details_master_fingerprint": "Master fingerprint", + "details_master_fingerprint": "Отпечаток мастер-ключа", "details_multisig_type": "мультисиг", - "details_no_cancel": "Нет, отмена", - "details_save": "Сохранить", "details_show_xpub": "Показать XPUB", "details_show_addresses": "Показать адреса", - "details_title": "Информация", + "details_title": "Кошелёк", + "wallets": "Кошельки", "details_type": "Тип", "details_use_with_hardware_wallet": "Использовать с аппаратным кошельком", - "details_wallet_updated": "Кошелёк сохранен", "details_yes_delete": "Да, удалить", "enter_bip38_password": "Введите пароль для расшифровки", - "export_title": "Экспорт Кошелька", + "export_title": "Экспорт кошелька", "import_do_import": "Импортировать", "import_passphrase": "Пароль", "import_passphrase_title": "Кодовая фраза", "import_passphrase_message": "Введите кодовую фразу, если она используется", - "import_error": "Не удалось импортировать", - "import_explanation": "Введите сюда мнемоническую фразу, публичный ключ, WIF - что угодно! BlueWallet постарается угадать верный формат.", - "import_imported": "Импорт завершен", - "import_scan_qr": "или отсканируйте QR-код?", - "import_success": "Успех", - "import_success_watchonly": "Ваш кошелек был успешно импортирован. ВНИМАНИЕ: это кошелек только для просмотра, вы НЕ можете проводить операции с него.", + "import_error": "Не удалось импортировать. Убедитесь, что данные верны.", + "import_explanation": "Введите мнемонику, публичный ключ, WIF — что угодно, BlueWallet попробует определить формат и импортировать кошелёк.", + "import_imported": "Импорт завершён", + "import_scan_qr": "Сканировать или импортировать файл", + "import_success": "Кошелёк успешно импортирован.", + "import_success_watchonly": "Кошелёк импортирован. ВНИМАНИЕ: кошелёк только для просмотра, тратить нельзя.", "import_search_accounts": "Поиск аккаунтов", "import_title": "Импорт", + "learn_more": "Узнать больше", "import_discovery_title": "Поиск", "import_discovery_subtitle": "Выберите обнаруженный кошелёк", "import_discovery_derivation": "Использовать другой путь деривации", "import_discovery_no_wallets": "Кошельков не обнаружено.", - "import_derivation_found": "найден", - "import_derivation_found_not": "не найден", - "import_derivation_loading": "загрузка...", - "import_derivation_subtitle": "Введите свой собственный путь деривации, и мы постараемся обнаружить ваш кошелёк", + "import_discovery_offline": "BlueWallet сейчас в офлайн режиме. В нём нельзя проверить существование кошелька, поэтому выберите правильный вручную.", + "import_derivation_found": "Найден", + "import_derivation_found_not": "Не найден", + "import_derivation_loading": "Загрузка...", + "import_derivation_subtitle": "Введите путь деривации, и мы попытаемся найти ваш кошелёк.", "import_derivation_title": "Путь деривации", - "import_derivation_unknown": "неизвестно", - "import_wrong_path": "неправильный путь деривации", + "import_derivation_unknown": "Неизвестно", + "import_wrong_path": "Неправильный путь деривации", "list_create_a_button": "Добавить сейчас", "list_create_a_wallet": "Добавить кошелёк", - "list_create_a_wallet_text": "Это бесплатно, и вы можете создать\nнеограниченное количество кошельков", - "list_empty_txs1": "Список транзакций пока пуст", - "list_empty_txs1_lightning": "Lightning кошелёк отлично подходит для ежедневных транзакций. Комиссия несправедливо мала, а скорость невероятно высока.", - "list_empty_txs2": "Добавьте кошелёк.", - "list_empty_txs2_lightning": "\nДля начала использования нажмите \"Мои средства\" и пополните баланс.", + "list_create_a_wallet_text": "Это бесплатно, и вы можете создать\nнеограниченное количество кошельков.", + "list_empty_txs1": "Список транзакций пуст.", + "list_empty_txs1_lightning": "Lightning-кошелёк подходит для ежедневных транзакций. Комиссия мала, а скорость высока.", + "list_empty_txs2": "Начните с вашего кошелька.", + "list_empty_txs2_lightning": "\nНажмите «Управление средствами», чтобы пополнить баланс.", "list_latest_transaction": "Последняя транзакция", - "list_ln_browser": "Лайтнинг браузер", "list_long_choose": "Выбрать фото", - "list_long_clipboard": "Вставить из буфера обмена", + "paste_from_clipboard": "Вставить", + "import_file": "Импортировать файл", "list_long_scan": "Сканировать QR-код", "list_title": "Кошельки", - "list_tryagain": "Попробовать еще раз", - "no_ln_wallet_error": "Прежде чем оплачивать Лайтнинг-инвойсы, нужно добавить Лайтнинг-кошелёк.", - "looks_like_bip38": "Это похоже на закрытый ключ, защищенный паролем (BIP38)", - "reorder_title": "Отсортировать кошельки", - "reorder_instructions": "Нажмите и удерживайте кошелек, чтобы переместить его в списке.", - "please_continue_scanning": "Продолжайте сканировать", - "select_no_bitcoin": "В настоящее время нет доступных кошельков Bitcoin.", - "select_no_bitcoin_exp": "Кошелёк Bitcoin необходим для пополнения кошельков Lightning. Пожалуйста, создайте или импортируйте его.", + "list_tryagain": "Попробовать ещё раз", + "no_ln_wallet_error": "Перед оплатой Lightning-инвойса добавьте Lightning-кошелёк.", + "looks_like_bip38": "Это закрытый ключ с паролем (BIP38).", + "manage_title": "Управление кошельками", + "no_results_found": "Результатов нет.", + "please_continue_scanning": "Продолжайте сканирование.", + "select_no_bitcoin": "Нет доступных Bitcoin-кошельков.", + "select_no_bitcoin_exp": "Bitcoin-кошелёк нужен для пополнения Lightning-кошельков.", "select_wallet": "Выбрать кошелёк", - "xpub_copiedToClipboard": "Скопировано", "pull_to_refresh": "Потяните, чтобы обновить", - "warning_do_not_disclose": "Внимание! Не разглашать", - "add_ln_wallet_first": "Сначала добавьте Лайтнинг-кошелёк.", + "warning_do_not_disclose": "Никогда не раскрывайте информацию ниже", + "scan_import": "Сканируйте этот QR-код, чтобы импортировать кошелёк в другое приложение.", + "write_down_header": "Создать резервную копию вручную", + "write_down": "Запишите и сохраните эти слова. Они нужны для восстановления кошелька.", + "wallet_type_this": "Тип этого кошелька — {type}.", + "share_number": "Поделиться {number}", + "copy_ln_url": "Скопируйте и сохраните этот URL, чтобы восстановить кошелёк позже.", + "copy_ln_public": "Скопируйте и сохраните эти данные для восстановления кошелька позже.", + "add_ln_wallet_first": "Сначала добавьте Lightning-кошелёк.", "identity_pubkey": "Identity Pubkey", - "xpub_title": "XPUB кошелька" + "xpub_title": "XPUB кошелька", + "manage_wallets_search_placeholder": "Поиск кошельков, адресов, транзакций и заметок", + "swipe_balance_hide": "Скрыть", + "swipe_balance_show": "Показать", + "drag_to_reorder": "Перетащите для изменения порядка", + "clear_search": "Очистить поиск", + "more_info": "Подробнее", + "details_delete_wallet_error_message": "Ошибка при удалении уведомлений. Возможно, проблемы с сетью.", + "details_delete_anyway": "Удалить всё равно" + }, + "total_balance_view": { + "display_in_bitcoin": "Показывать в Bitcoin", + "hide": "Скрыть", + "display_in_sats": "Показывать в сатоши", + "display_in_fiat": "Показывать в {currency}", + "title": "Общий баланс", + "explanation": "Просмотр общего баланса всех кошельков на обзорном экране." }, "multisig": { - "multisig_vault": "Хранилище", + "multisig_vault": "Мультисиг хранилище", "default_label": "Мультисиг хранилище", "multisig_vault_explain": "Лучшая безопасность для больших сумм", "provide_signature": "Предоставить подпись", + "provide_signature_details": "Используйте устройство и кошелёк, где находится ключ, чтобы подписать транзакцию", + "provide_signature_details_bluewallet": "В BlueWallet откройте меню экрана «Отправить» и выберите ", + "provide_signature_next_steps": "Сканировать или импортировать подписанную транзакцию", + "provide_signature_next_steps_details": "Сканируйте или импортируйте подписанную транзакцию и проверьте детали перед отправкой.", "vault_key": "Ключ хранилища {number}", - "required_keys_out_of_total": "Требуемое количество ключей", - "fee": "Коммисия: {number}", + "required_keys_out_of_total": "Требуемое количество ключей из общего", + "fee": "Комиссия: {number}", "fee_btc": "{number} BTC", "confirm": "Подтвердить", "header": "Отправить", - "share": "Поделиться", + "share": "Поделиться...", "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}", + "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": "Давайте начнем", + "lets_start": "Давайте начнём", "create": "Создать", "native_segwit_title": "Лучшая практика", "wrapped_segwit_title": "Лучшая совместимость", "legacy_title": "Устаревший", "co_sign_transaction": "Подписать транзакцию", - "what_is_vault": "Хранилище - это", - "what_is_vault_numberOfWallets": "{m}-из-{n} мультисиг", - "what_is_vault_wallet": "кошелёк", - "vault_advanced_customize": "Настройки Хранилища...", + "what_is_vault": "Хранилище — это", + "what_is_vault_numberOfWallets": " {m}-из-{n} мультисиг ", + "what_is_vault_wallet": "кошелёк.", + "vault_advanced_customize": "Настройки Хранилища", "needs": "Нужно", - "what_is_vault_description_number_of_vault_keys": "{m} ключа хранилища", - "what_is_vault_description_to_spend": "чтобы потратить и третий мы можете \nиспользовать как резервный", + "what_is_vault_description_number_of_vault_keys": " {m} ключа хранилища ", + "what_is_vault_description_to_spend": "чтобы потратить и третий вы можете \nиспользовать как резервный.", "what_is_vault_description_to_spend_other": "чтобы потратить.", - "quorum": "{m}-из-{n} кворум", + "quorum": "{m} из {n} кворум", "quorum_header": "Кворум", "of": "из", "wallet_type": "Тип кошелька", - "invalid_mnemonics": "Сид-фраза неверна", - "invalid_cosigner": "Неверные данные cosigner", - "not_a_multisignature_xpub": "Это xpub не мультисиг кошелька!", - "invalid_cosigner_format": "Неверный cosigner: он не подходит для {format} формата", - "create_new_key": "Создать Новый", + "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 cosigner'а готов к импорту в другой кошелёк. Делиться им безопасно.", - "wallet_key_created": "Ваш ключ Хранилища был создан. Обязательно сделайте резервную копию вашей сид-фразы.", - "are_you_sure_seed_will_be_lost": "Вы уверены? Ваша сид-фраза будет утеряна, если нет резервной копии.", + "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": "Просмотр/редактирование cosigner'ов", - "this_cosigner_is_already_imported": "Этот cosigner уже импортирован.", - "export_signed_psbt": "Экспортировать PSBT с подписью", - "input_fp": "Введите отпечаток (fingerprint)", - "input_fp_explain": "Пропустить и использовать значение по умолчанию (00000000)", + "view_edit_cosigners": "Просмотр/Редактирование со-подписантов", + "this_cosigner_is_already_imported": "Этот со-подписант уже импортирован.", + "export_signed_psbt": "Экспортировать подписанный PSBT", + "input_fp": "Введите отпечаток", + "input_fp_explain": "Пропустить и использовать 00000000", "input_path": "Введите путь деривации", - "input_path_explain": "Пропустить и использовать значение по умолчанию ({default})", + "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_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": "В свойствах кошелька можно создать полную резервную копию Хранилища или версию только для чтения. Этот бэкап как карта для BlueWallet. Он необходим для восстановления средств в случае потери одной из сид-фраз.", + "ms_help_3": "В настройках кошелька есть резервные копии Хранилища и watch-only. Они важны для восстановления.", "ms_help_title4": "Импорт Хранилища", - "ms_help_4": "Для импорта Хранилища используйте файл мультисиг бэкапа. Если у вас есть только XPUB'ы и сид-фразы, вы можете заполнить их при создании Хранилища.", - "ms_help_title5": "Расширенные настройки", - "ms_help_5": "По умолчанию BlueWallet создает Хранилище 2 из 3. Чтобы создать другой кворум или изменить тип адреса, активируйте Расширенный режим в Настройках." + "ms_help_4": "Импортируйте мультисиг через файл резервной копии или сид-фразы и XPUB.", + "ms_help_title5": "Расширенный режим", + "ms_help_5": "По умолчанию создаётся 2-из-3. Для другого кворума включите Расширенный режим." }, "is_it_my_address": { "title": "Это мой адрес?", - "owns": "{label} имеет {address}", + "owns": "{label} владеет {address}", "enter_address": "Введите адрес", "check_address": "Проверить адрес", - "no_wallet_owns_address": "Ни один из доступных кошельков не владеет этим адресом.", + "no_wallet_owns_address": "Ни один кошелёк не владеет этим адресом.", "view_qrcode": "Посмотреть QR-код" }, + "autofill_word": { + "title": "Последнее слово сид-фразы", + "enter": "Введите неполную мнемонику", + "generate_word": "Сгенерировать финальное слово", + "error": "Нужно 11 или 23 слова. Попробуйте ещё раз." + }, "cc": { "change": "Сдача", "coins_selected": "Выбрано монет: {number}", "selected_summ": "{value} выбрано", - "empty": "В этом кошельке пока нет монет", + "empty": "В этом кошельке нет монет.", "freeze": "Заморозить", "freezeLabel": "Заморозить", "freezeLabel_un": "Разморозить", - "header": "Управление Монетами", - "use_coin": "Использовать Монету", + "header": "Управление монетами", + "use_coin": "Использовать монету", "use_coins": "Использовать монеты", - "tip": "Позволяет просматривать, помечать, замораживать или использовать Монеты для гибкого управления кошельком." + "tip": "Можно помечать, замораживать и выбирать монеты для гибкого управления.", + "sort_asc": "По возрастанию", + "sort_desc": "По убыванию", + "sort_height": "Высота", + "sort_value": "Сумма", + "sort_label": "Метка", + "sort_status": "Статус", + "sort_by": "Сортировать по" }, "units": { "BTC": "BTC", "MAX": "МАКС", - "sat_vbyte": "sat/vByte", + "sat_vbyte": "сат/вБайт", "sats": "сатоши" }, "addresses": { - "sign_title": "Подписать/Проверить сообщение", - "sign_help": "Здесь вы можете создать или проверить криптографическую подпись на основе адреса Bitcoin.", + "copy_private_key": "Копировать приватный ключ", + "sensitive_private_key": "Внимание: приватные ключи чрезвычайно чувствительны. Продолжить?", + "sign_title": "Подпись/Проверка сообщения", + "sign_help": "Создать или проверить подпись по Bitcoin-адресу.", "sign_sign": "Подписать", "sign_verify": "Проверить", - "sign_signature_correct": "Проверка прошла успешно!", - "sign_signature_incorrect": "Проверка не удалась!", + "sign_signature_correct": "Подпись верна!", + "sign_signature_incorrect": "Подпись неверна!", "sign_placeholder_address": "Адрес", "sign_placeholder_message": "Сообщение", "sign_placeholder_signature": "Подпись", "addresses_title": "Адреса", "type_change": "Сдача", "type_receive": "Получение", - "type_used": "Использован", + "type_used": "Использованные", "transactions": "Транзакции" }, "lnurl_auth": { "register_question_part_1": "Хотите зарегистрировать аккаунт на", - "register_question_part_2": "с помощью вашего Lightning кошелька?", - "register_answer": "Вы успешно зарегистрировали аккаунт на {hostname}!", + "register_question_part_2": "с помощью вашего Lightning-кошелька?", + "register_answer": "Вы успешно зарегистрированы на {hostname}!", "login_question_part_1": "Хотите войти на", - "login_question_part_2": "с помощью вашего Lightning кошелька?", + "login_question_part_2": "с помощью вашего Lightning-кошелька?", "login_answer": "Вы успешно вошли на {hostname}!", - "link_question_part_1": "Хотите связать ваш аккаунт на", - "link_question_part_2": "с вашим Lightning кошельком?", - "link_answer": "Ваш Lightning кошелёк успешно связан с вашим аккаунтом на {hostname}!", - "auth_question_part_1": "Хотите произвести аутентификацию на", - "auth_question_part_2": "с помощью вашего Lightning кошелька?", - "auth_answer": "Вы успешно аутентифицировались на {hostname}!", - "could_not_auth": "Мы не смогли аутентифицировать вас на {hostname}.", + "link_question_part_1": "Хотите связать аккаунт на", + "link_question_part_2": "с Lightning-кошельком?", + "link_answer": "Lightning-кошелёк успешно связан с аккаунтом на {hostname}!", + "auth_question_part_1": "Хотите аутентифицироваться на", + "auth_question_part_2": "с помощью вашего Lightning-кошелька?", + "auth_answer": "Вы успешно аутентифицированы на {hostname}!", + "could_not_auth": "Не удалось аутентифицировать на {hostname}.", "authenticate": "Аутентифицироваться" }, "bip47": { - "payment_code": "Код Оплаты", - "payment_codes_list": "Список Кодов Оплаты", - "who_can_pay_me": "Кто может мне заплатить:", + "payment_code": "Код оплаты", + "contacts": "Контакты", + "bip47_explain": "Многоразовый и общий код", + "bip47_explain_subtitle": "BIP47", "purpose": "Многоразовый и общий код (BIP47)", + "pay_this_contact": "Оплатить этому контакту", + "rename_contact": "Переименовать контакт", + "copy_payment_code": "Копировать код оплаты", + "hide_contact": "Скрыть контакт", + "rename": "Переименовать", + "provide_name": "Укажите новое имя", + "add_contact": "Добавить контакт", + "provide_payment_code": "Введите код оплаты", + "invalid_pc": "Неверный код оплаты", + "notification_tx_unconfirmed": "Транзакция уведомления не подтверждена, подождите", + "failed_create_notif_tx": "Не удалось создать on-chain транзакцию", + "onchain_tx_needed": "Необходима on-chain транзакция", + "notif_tx_sent": "Транзакция уведомления отправлена. Ожидайте подтверждения", + "notif_tx": "Транзакция уведомления", "not_found": "Код оплаты не найден" } } diff --git a/loc/si_LK.json b/loc/si_LK.json index e52ee23ca18..02f06086b05 100644 --- a/loc/si_LK.json +++ b/loc/si_LK.json @@ -4,24 +4,32 @@ "cancel": "අවලංගු කරන්න", "continue": "ඉදිරියට යන්න", "clipboard": "පසුරු පුවරුව", + "copied": "පිටපත් කරන ලදි!", + "discard_changes": "වෙනස් කිරීම් ඉවත් කරන්නද?", + "discard_changes_explain": "ඔබට සුරැකූ නැති වෙනස්කම් ඇත. ඒවා ඉවත දමා මෙම තිරයෙන් ඉවත් වීමට ඔබට විශ්වාසද?", "enter_password": "මුරපදය ඇතුළත් කරන්න", + "enter_url": "URL ඇතුළත් කරන්න", + "save": "සුරකින්න...", + "close": "වසන්න", + "change_input_currency": "ආදාන මුදල් ඒකකය වෙනස් කරන්න", + "refresh": "නැවුම් කරන්න", + "pick_image": "පුස්තකාලයෙන් තෝරන්න", + "pick_file": "ගොනුවක් තෝරන්න", + "enter_amount": "මුදල ඇතුළත් කරන්න", + "qr_custom_input_button": "අභිරුචි ආදානයක් ඇතුළත් කිරීමට 10 වරක් තට්ටු කරන්න", + "unlock": "අගුළු හරින්න", + "port": "වරාය", + "ssl_port": "SSL වරාය", + "suggested": "යෝජිත", "never": "නැත", - "disabled": "අක්‍රීය", - "of": "{number} of {total}", + "of": "{total} න් {number}", "ok": "හරි", "storage_is_encrypted": "ඔබේ ගබඩා කිරීම සංකේතනය කර ඇත. එය විකේතනය කිරීමට මුරපදය අවශ්‍යයි.", "yes": "ඔව්", "no": "නැත", - "save": "සුරකින්න", "seed": "බීජ", "success": "සාර්ථකයි", - "wallet_key": "පසුබිම් යතුර", - "invalid_animated_qr_code_fragment": "වලංගු නොවන සජීවිකරණ QR කේත ඛණ්ඩයකි. කරුණාකර නැවත උත්සාහ කරන්න.", - "file_saved": "ගොනුව {filePath} ඔබේ සුරැකුම් ස්ථානයේ සුරැකී ඇත {destination}.", - "downloads_folder": "බාගත ෆෝල්ඩරය " - }, - "alert": { - "default": "අවදියෙන්" + "wallet_key": "පසුම්බි යතුර" }, "azteco": { "codeIs": "ඔබේ වවුචර් කේතය", @@ -30,12 +38,14 @@ "redeem": "පසුම්බියට මුදවා ගන්න.", "redeemButton": "මුදවා ගන්න", "success": "සාර්ථකයි", + "successMessage": "වවුචරය සාර්ථකව මුදවා ගන්නා ලදි! ඔබේ අරමුදල් ඉක්මනින් ඔබේ බිට්කොයින් පසුම්බියට පැමිණිය යුතුය.", "title": "azte.co වවුචරය මුදවා ගන්න" }, "entropy": { "save": "සුරකින්න", "title": "එන්ට්‍රොපි", - "undo": "අහෝසි කරන්න" + "undo": "අහෝසි කරන්න", + "amountOfEntropy": "බිට් {limit} කින් {bits}" }, "errors": { "broadcast": "විකාශනය අසමත්.", @@ -43,75 +53,58 @@ "network": "ජාලකරණ දෝෂයකි" }, "lnd": { - "active": "ක්‍රියාකාරී", - "inactive": "අක්‍රීය", - "channels": "නාලිකා", - "no_channels": "නාලිකා කිසිවක් නැත", - "claim_balance": "ශේෂය ඉල්ලන්න {balance}", - "close_channel": "නාලිකාව වසන්න", - "new_channel": "නව නාලිකාව", - "errorInvoiceExpired": "ඉන්වොයිසිය කල් ඉකුත් වී ඇත", - "force_close_channel": "කෙසේ හෝ නාලිකාව වසා දමන්නද?", + "errorInvoiceExpired": "ඉන්වොයිසිය කල් ඉකුත් වී ඇත.", "expired": "කල් ඉකුත් වී ඇත", - "node_alias": "නෝඩ් අන්වර්ථ නාමය", - "expiresIn": "මිනිත්තුවලින් {time} කල් ඉකුත් වේ", + "expiresIn": "{time} මිනිත්තුවලින් කල් ඉකුත් වේ", "payButton": "ගෙවන්න", - "placeholder": "ඉන්වොයිසිය", - "open_channel": "විවෘත නාලිකාව", - "funding_amount_placeholder": "අරමුදල් ප්‍රරමාණය, උදාහරණයක් ලෙස 0.001", - "opening_channnel_for_from": "මුදල් පසුම්බිය සඳහා {forWalletLabel}, {fromWalletLabel} අරමුදල් මඟින් නාලිකාව විවෘත කිරීම", - "are_you_sure_open_channel": "ඔබට මෙම නාලිකාව විවෘත කිරීමට අවශ්‍ය බව විශ්වාසද?", - "potentialFee": "විභව ගාස්තුව: {fee}", - "remote_host": "දුරස්ථ ධාරකයා", + "payment": "ගෙවීම", + "placeholder": "ඉන්වොයිසිය හෝ ලිපිනය", + "potentialFee": "හැකි ගාස්තුව: {fee}", + "sameWalletAsInvoiceError": "ඉන්වොයිසිය සෑදීමට භාවිතා කළ එම පසුම්බියෙන්ම එම ඉන්වොයිසිය ගෙවිය නොහැක.", "refill": "නැවත පුරවන්න", - "reconnect_peer": "මිතුරා නැවත සම්බන්ධ කරන්න", "refill_create": "ඉදිරියට යාමට, කරුණාකර නැවත පිරවීම සඳහා බිට්කොයින් පසුම්බියක් සාදන්න.", "refill_external": "බාහිර පසුම්බිය සමඟ නැවත පුරවන්න", "refill_lnd_balance": "ලයිට්නින් පසුම්බියේ ශේෂය නැවත පුරවන්න", - "sameWalletAsInvoiceError": "එය සෑදීමට භාවිතා කළ පසුම්බියෙන්ම ඔබට ඉන්වොයිසියක් ගෙවිය නොහැක.", - "title": "අරමුදල් කළමනාකරණය කරන්න", - "can_send": "යැවිය හැක", - "can_receive": "ලබා ගත හැක", - "view_logs": "ලොග් බලන්න" + "title": "අරමුදල් කළමනාකරණය කරන්න" }, "lndViewInvoice": { "additional_info": "අමතර තොරතුරු", "for": "සඳහා:", "lightning_invoice": "ලයිට්නින් ඉන්වොයිසිය", - "open_direct_channel": "මෙම නෝඩය සමග ඍජු නාලිකාව විවෘත කරන්න:", "please_pay_between_and": "කරුණාකර {min} සහ {max} අතර ගෙවන්න", "please_pay": "කරුණාකර ගෙවන්න", - "preimage": "ප්‍රාථමික", + "preimage": "පූර්ව-රූපය", + "date_time": "දිනය සහ වේලාව", "sats": "සැට්.", "wasnt_paid_and_expired": "මෙම ඉන්වොයිසිය ගෙවා නැති අතර කල් ඉකුත් වී ඇත." }, "plausibledeniability": { "create_fake_storage": "සංකේතනය කළ ගබඩාවක් සාදන්න", - "create_password": "මුරපදයක් තනන්න", "create_password_explanation": "ව්‍යාජ ගබඩාව සඳහා වූ මුරපදය ඔබේ ප්‍රධාන ගබඩාව සඳහා වූ මුරපදයට ගැලපිය යුතු නැත.", "help": "සමහර තත්වයන් යටතේ, මුරපදයක් හෙළි කිරීමට ඔබට බල කෙරෙනු ඇත. ඔබේ කාසි ආරක්‍ෂිතව තබා ගැනීම සඳහා, බ්ලූවොලට් හට වෙනත් මුරපදයකින් වෙනත් සංකේතනය කළ ගබඩාවක් සෑදිය හැකිය. පීඩනය යටතේ, ඔබට මෙම මුරපදය තුන්වන පාර්ශවයකට හෙළි කළ හැකිය. බ්ලූවොලට් තුළට ඇතුළු වුවහොත් එය නව “ව්‍යාජ” ගබඩාවක් විවෘත කරයි. මෙය තුන්වන පාර්ශවයට නීත්‍යානුකූල නොවන බව පෙනේ, නමුත් එය රහසිගතව ඔබේ ප්‍රධාන ගබඩා කාසි සමඟ ගබඩා කරයි.", "help2": "නව ගබඩාව සම්පුර්ණයෙන්ම ක්‍රියාත්මක වන අතර ඔබට විශ්වාසදායක ලෙස පෙනෙන පරිදි අවම ප්‍රමාණයක් එහි ගබඩා කළ හැකිය.", "password_should_not_match": "මුර පදය දැනටමත් භාවිතයේ ඇත. කරුණාකර වෙනත් මුර පදයක් සඳහා උත්සාහ කරන්න.", - "passwords_do_not_match": "මුරපද නොගැලපේ. කරුණාකර නැවත උත්සාහ කරන්න.", - "retype_password": "මුරපදය නැවත ඇතුළත් කරන්න", - "success": "සාර්ථකයි", "title": "පිළිගතහැකි ප්‍රතික්ෂේප කිරීම" }, "pleasebackup": { "ask": "ඔබේ පසුම්බියේ උපස්ථ වැකිය සුරැකී තිබේද? ඔබට මෙම උපකරණය නැති වුවහොත් ඔබේ අරමුදල් වෙත ප්‍රවේශ වීම සඳහා මෙම උපස්ථ වැකිය අවශ්‍ය වේ. උපස්ථ වාක්‍යය නොමැති වුවහොත් ඔබේ අරමුදල් සදහටම නැති වී යයි.", - "ask_no": "නැත. මට නැත.", - "ask_yes": "ඔව්, මට තිබේ.", - "ok": "හරි, මම ඒක සටහන් කළා", - "ok_lnd": "හරි, මම ඒක සුරැකුවා", - "text": "කරුණාකර මොහොතකට මෙම සිහිවටන වැකිය කඩදාසි කැබැල්ලක සටහන් කර ගන්න. එය ඔබේ උපස්ථය වන අතර පසුම්බිය ආපසු ලබා ගැනීමට ඔබට එය භාවිතා කළ හැකිය.", - "text_lnd": "කරුණාකර මෙම පසුම්බි උපස්ථය සුරකින්න. නැති වූ පසු මුදල් පසුම්බිය නැවත ලබා ගැනීමට එය ඔබට ඉඩ සලසයි.", - "title": "ඔබේ මුදල් පසුම්බිය සාදා ඇත" + "ask_no": "නැත, මම ලියා නැත.", + "ask_yes": "ඔව්, මම ලියා ඇත.", + "ok": "හරි, මම එය ලිව්වා.", + "ok_lnd": "හරි, මම එය සුරැකුවා.", + "title": "ඔබේ පසුම්බිය නිර්මාණය කර ඇත.", + "text": "කරුණාකර මොහොතකට මෙම සිහිවටන වැකිය කඩදාසි කැබැල්ලක සටහන් කර ගන්න.\nඑය ඔබේ උපස්ථය වන අතර පසුම්බිය ආපසු ලබා ගැනීමට ඔබට එය භාවිතා කළ හැකිය.", + "text_lnd": "කරුණාකර මෙම පසුම්බි උපස්ථය සුරකින්න. නැති වූ පසු මුදල් පසුම්බිය නැවත ලබා ගැනීමට එය ඔබට ඉඩ සලසයි." }, "receive": { "details_create": "නිර්මාණය කරන්න", "details_label": "විස්තර", "details_setAmount": "මුදල සමග ලබා ගන්න", - "details_share": "බෙදා ගන්න", + "details_share": "බෙදා ගන්න...", + "address_not_found": "ලබා ගැනීමේ ලිපිනය උත්පාදනය කිරීමට නොහැකිය.", + "reset": "යළි සකසන්න", + "qrcode_for_the_address": "ලිපිනය සඳහා QR කේතය", + "bip47_explanation": "ගෙවීම් කේත යනු ඔබේ පසුම්බියේ ලිපින හෙළි නොකරන විශ්වීය ලිපිනයකි. සියලුම සේවාවන් එයට සහාය නොදක්වයි.", "header": "ලබා ගන්න", "maxSats": "උපරිම මුදල සැට් {max} වේ", "maxSatsFull": "උපරිම මුදල {max} sats හෝ {currency} වේ", @@ -152,9 +145,7 @@ "details_create": "ඉන්වොයිසියක් සාදන්න", "details_error_decode": "බිට්කොයින් ලිපිනය විකේතනය කළ නොහැක", "details_fee_field_is_not_valid": "ගාස්තුව වලංගු නොවේ.", - "details_frozen": "{amount} BTC නිශ්චල කර ඇත", "details_next": "ඊළඟ", - "details_no_signed_tx": "තෝරාගත් ගොනුවේ ආනයනය කළ හැකි ගනුදෙනුවක් අඩංගු නොවේ.", "details_note_placeholder": "ස්වයං සටහන්", "details_scan": "ස්කෑන් කරන්න", "details_scan_hint": "ගමනාන්තයක් පරිලෝකනය කිරීමට හෝ ආනයනය කිරීමට දෙවරක් තට්ටු කරන්න", @@ -167,25 +158,22 @@ "dynamic_prev": "කලින්", "dynamic_start": "ආරම්භ කරන්න", "dynamic_stop": "නවත්වන්න", - "fee_10m": "මීටර් 10", + "fee_10m": "මිනිත්තු 10", "fee_1d": "දවස් 1", "fee_3h": "පැය 3", - "fee_custom": "චාරිත්‍රය", + "fee_custom": "අභිරුචි", "fee_fast": "ඉක්මණින්", "fee_medium": "මධ්‍යම", "fee_satvbyte": "සතොෂි/වීබයිට් හි", "fee_slow": "මන්දගාමී", "header": "යවන්න", - "input_clear": "පැහැදිලි", + "input_clear": "හිස් කරන්න", "input_done": "කළා", "input_paste": "අලවන්න", "input_total": "එකතුව:", "permission_camera_message": "ඔබේ කැමරාව භාවිතා කිරීමට අපට ඔබේ අවසරය අවශ්‍යයි.", - "permission_camera_title": "කැමරාව භාවිතා කිරීමට අවසර", "psbt_sign": "ගනුදෙනුවකට අත්සන් කරන්න", "open_settings": "සැකසුම් විවෘත කරන්න", - "permission_storage_later": "පසුව මගෙන් විමසන්න", - "permission_storage_message": "මෙම ගොනුව සුරැකීමට ඔබේ ගබඩාවට ප්‍රවේශ වීමට බ්ලූවොලට් එකට ඔබේ අවසරය අවශ්‍යයි.", "permission_storage_denied_message": "බ්ලූවොලට් හට මෙම ගොනුව සුරැකීමට නොහැකිය. කරුණාකර ඔබේ උපාංග සැකසීම් විවෘත කර ගබඩා කිරීමේ අවසරය සක්‍රීය කරන්න.", "permission_storage_title": "ගබඩා ප්‍රවේශ අවසරය", "psbt_clipboard": "පසුරු පුවරුවට පිටපත් කරන්න", @@ -195,12 +183,30 @@ "outdated_rate": "අගය අවසන් වරට යාවත්කාලීන කරන ලද්දේ: {date}", "psbt_tx_open": "අත්සන් කළ ගනුදෙනුව විවෘත කරන්න", "psbt_tx_scan": "අත්සන් කළ ගනුදෙනුව පරිලෝකනය කරන්න", - "qr_error_no_qrcode": "තෝරාගත් රූපයේ අපට QR කේතයක් සොයා ගැනීමට නොහැකි විය. රූපයේ ඇත්තේ QR කේතයක් පමණක් බවත් වචන, හෝ බොත්තම් වැනි අතිරේක අන්තර්ගතයන් නොමැති බවත් තහවුරු කර ගන්න.", "reset_amount": "මුදල නැවත සකසන්න", "reset_amount_confirm": "මුදල නැවත සැකසීමට ඔබ කැමතිද?", "success_done": "කළා", - "txSaved": "ගණුදෙනු ගොනුව ({filePath}) ඔබේ බාගැනීම් ෆෝල්ඩරයේ සුරැකී ඇත.", - "problem_with_psbt": "PSBT සමඟ ගැටළුව" + "problem_with_psbt": "PSBT සමඟ ගැටළුව", + "create_satoshi_per_vbyte": "vByte එකකට සතෝෂි", + "details_insert_contact": "සම්බන්ධතාවය ඇතුළු කරන්න", + "details_add_recc_rem_all_alert_description": "සියලුම ලබන්නන් ඉවත් කිරීමට ඔබට විශ්වාසද?", + "details_add_rec_rem_all": "සියලුම ලබන්නන් ඉවත් කරන්න", + "details_recipients_title": "ලබන්නන්", + "details_recipient_title": "#{total} න් ලබන්නා #{number}", + "please_complete_recipient_title": "අසම්පූර්ණ ලබන්නා", + "please_complete_recipient_details": "නව ලබන්නෙකු එක් කිරීමට පෙර කරුණාකර ලබන්නා #{number} හි විස්තර සම්පූර්ණ කරන්න.", + "details_frozen": "{amount} BTC කැටි කර ඇත.", + "details_no_signed_tx": "තෝරාගත් ගොනුවේ ආනයනය කළ හැකි ගනුදෙනුවක් අඩංගු නොවේ.", + "details_scan_error": "පරිලෝකන දෝෂයකි", + "insert_custom_fee": "ගාස්තුව ඇතුළු කරන්න", + "fee_replace_minvb": "ඔබට ගෙවීමට අවශ්‍ය මුළු ගාස්තු අනුපාතය (sat/vByte) {min} sat/vByte ට වඩා වැඩි විය යුතුය.", + "invalid_psbt": "සපයා ඇති PSBT වලංගු නොවේ.", + "qr_error_no_qrcode": "තෝරාගත් රූපයේ වලංගු QR කේතයක් සොයා ගැනීමට අපට නොහැකි විය. රූපයේ පෙළ හෝ බොත්තම් වැනි අමතර අන්තර්ගතයක් නොව QR කේතයක් පමණක් අඩංගු බව සහතික කරන්න.", + "txSaved": "ගනුදෙනු ගොනුව ({filePath}) සුරැකී ඇත.", + "file_saved_at_path": "ගොනුව ({filePath}) සුරැකී ඇත.", + "cant_send_to_silentpayment_adress": "මෙම පසුම්බියට Silent Payments ලිපිනවලට යැවිය නොහැක", + "cant_send_to_bip47": "මෙම පසුම්බියට BIP47 ගෙවීම් කේතවලට යැවිය නොහැක", + "cant_find_bip47_notification": "මුලින්ම මෙම ගෙවීම් කේතය සම්බන්ධතාවලට එක් කරන්න" }, "settings": { "about": "පිළිබඳව", @@ -211,27 +217,19 @@ "about_release_notes": "මුදා හැරීමේ සටහන්", "about_review": "අපට සමාලෝචනයක් තබන්න", "about_selftest": "ස්වයං පරීක්‍ෂණයක් කරන්න", - "about_selftest_electrum_disabled": "ඉලෙක්ට්‍රෝම් නොබැඳි මාදිලිය සමඟ ස්වයං පරීක්‍ෂා කිරීම නොමැත. කරුණාකර නොබැඳි ප්‍රකාරය අක්‍රිය කර නැවත උත්සාහ කරන්න.", + "about_selftest_electrum_disabled": "ඉලෙක්ට්‍රම් නොබැඳි මාදිලිය සමඟ ස්වයං පරීක්‍ෂා කිරීම නොමැත. කරුණාකර නොබැඳි ප්‍රකාරය අක්‍රිය කර නැවත උත්සාහ කරන්න.", "about_selftest_ok": "සියලුම අභ්‍යන්තර පරීක්‍ෂණ සාර්ථකව සමත් වී ඇත. පසුම්බිය හොඳින් ක්‍රියා කරයි.", "about_sm_github": "ගිට්හබ්", - "about_sm_discord": "විසංයෝජන සේවාදායකය", "about_sm_telegram": "ටෙලිග්‍රෑම් නාලිකාව", - "about_sm_twitter": "ට්විටර් හි අපව අනුගමනය කරන්න", - "advanced_options": "ඉහළ විකල්ප", "biometrics": "ජෛවමිතික", "biom_10times": "ඔබ ඔබේ මුරපදය 10 වරක් ඇතුළත් කිරීමට උත්සාහ කර ඇත. ඔබේ ගබඩාව නැවත සැකසීමට ඔබ කැමතිද? මෙය සියලුම පසුම්බි ඉවත් කර ඔබේ ආචයනය විකේතනය කරයි.", "biom_conf_identity": "කරුණාකර ඔබේ අනන්‍යතාවය තහවුරු කරන්න.", - "biom_no_passcode": "ඔබේ උපාංගයට මුර සංකේතයක් නොමැත. ඉදිරියට යාමට, කරුණාකර සැකසීම් යෙදුමේ මුර සංකේතයක් වින්‍යාස කරන්න.", "biom_remove_decrypt": "ඔබේ මුදල් පසුම්බි සියල්ලම ඉවත් කෙරෙන අතර ඔබේ ගබඩා කිරීම විකේතනය වනු ඇත. ඔබට ඉදිරියට යාමට අවශ්‍ය බව ඔබට විශ්වාසද?", "currency": "මුදල්", "currency_fetch_error": "තෝරාගත් මුදල් සඳහා ගාස්තුව ලබා ගැනීමේදී දෝෂයක් ඇති විය.", - "default_desc": "අක්‍රීය වූ විට, දියත් කිරීමේදී තෝරාගත් පසුම්බිය බ්ලූවොලට් වහාම විවෘත කරයි.", - "default_info": "පෙරනිමි තොරතුරු", "default_title": "දියත් කිරීමේදී", - "default_wallets": "සියලුම පසුම්බි බලන්න", "electrum_connected": "සම්බන්ධයි", "electrum_connected_not": "සම්බන්ධ නොවේ", - "electrum_error_connect": "සපයා ඇති ඉලෙක්ට්‍රම් සේවාදායකයට සම්බන්ධ විය නොහැක", "lndhub_uri": "උ.දා., {example}", "electrum_host": "උ.දා., {example}", "electrum_offline_mode": "නොබැඳි මාදිලිය", @@ -239,33 +237,17 @@ "electrum_port": "වරාය, සාමාන්‍යයෙන් {example}", "use_ssl": "SSL භාවිතා කරන්න", "electrum_saved": "ඔබේ වෙනස්කම් සාර්ථකව සුරැකී ඇත. වෙනස්කම් බලාත්මක වීමට බ්ලූවොලට් නැවත ආරම්භ කිරීම අවශ්‍ය විය හැකිය.", - "set_electrum_server_as_default": "පෙරනිමි ඉලෙක්ට්‍රෝම් සේවාදායකය ලෙස {server} සකසන්නද?", - "set_lndhub_as_default": "පෙරනිමි එල්.එන්.ඩී.හබ් සේවාදායකය ලෙස {url} සකසන්නද?", + "set_electrum_server_as_default": "පෙරනිමි ඉලෙක්ට්‍රම් සේවාදායකය ලෙස {server} සකසන්නද?", "electrum_settings_server": "ඉලෙක්ට්‍රම් සේවාදායකය", - "electrum_settings_explain": "පෙරනිමිය භාවිතා කිරීමට හිස්ව තබන්න.", "electrum_status": "තත්ත්වය", - "electrum_clear_alert_title": "ඉතිහාසය හිස් කරන්නද?", - "electrum_clear_alert_message": "ඔබට ඉලෙක්ට්‍රම් සේවාදායක ඉතිහාසය හිස් කිරීමට අවශ්‍යද?", - "electrum_clear_alert_cancel": "අවලංගු කරන්න", - "electrum_clear_alert_ok": "හරි", - "electrum_select": "තෝරන්න", - "electrum_reset": "පෙරනිමිය වෙත නැවත සකසන්න", "electrum_unable_to_connect": "{server} වෙත සම්බන්ධ කිරීමට නොහැකිය.", - "electrum_history": "සේවාදායක ඉතිහාසය", - "electrum_reset_to_default": "ඔබේ ඉලෙක්ට්‍රම් සැකසුම් පෙරනිමි ලෙස නැවත සැකසීමට ඔබට අවශ්‍ය බව ඔබට විශ්වාසද?", - "electrum_clear": "පැහැදිලි කරන්න", - "tor_supported": "ටෝර් සහාය දක්වයි", - "tor_unsupported": "ටෝර් සම්බන්ධතා සඳහා සහය නොදක්වයි.", + "electrum_reset": "පෙරනිමිය වෙත නැවත සකසන්න", "encrypt_decrypt": "විකේතන ගබඩාව", "encrypt_decrypt_q": "ඔබට ඔබේ ගබඩාව විකේතනය කිරීමට අවශ්‍ය බව විශ්වාසද? මුරපදයක් නොමැතිව ඔබේ මුදල් පසුම්බියට ප්‍රවේශ වීමට මෙය ඉඩ සලසයි.", - "encrypt_enc_and_pass": "සංකේතනය කර මුරපදය ආරක්ෂා කර ඇත", "encrypt_title": "ආරක්ෂාව", "encrypt_tstorage": "ගබඩාව", "encrypt_use": "{type} භාවිතා කරන්න", - "encrypt_use_expl": "මුදල් පසුම්බියක ගනුදෙනු කිරීමට, අගුළු හැරීමට, අපනයනය කිරීමට හෝ මැකීමට පෙර ඔබේ අනන්‍යතාවය තහවුරු කිරීමට {type} භාවිතා කෙරේ. සංකේතනය කළ ආචයනය අගුළු ඇරීමට {type} භාවිතා නොකරනු ඇත.", "general": "පොදු", - "general_adv_mode": "උසස් මාදිලිය", - "general_adv_mode_e": "සක්‍රීය කළ විට, විවිධ පසුම්බි වර්‍ග, ඔබට සම්බන්ධ වීමට අවශ්‍ය එල්.එන්.ඩී.හබ් අවස්ථාව නියම කිරීමේ හැකියාව සහ පසුම්බි සෑදීමේදී අභිරුචි එන්ට්‍රොපි වැනි උසස් විකල්ප ඔබට දැක ගත හැක.", "general_continuity": "අඛණ්ඩතාව", "general_continuity_e": "සක්‍රිය වූ විට, ඔබේ අනෙකුත් ඇපල් අයික්ලවුඩ් සම්බන්ධිත උපාංග භාවිතයෙන් තෝරාගත් පසුම්බි සහ ගනුදෙනු බැලීමට ඔබට හැකි වේ.", "groundcontrol_explanation": "ග්‍රවුන්ඩ්කන්ට්‍රෝල් යනු බිට්කොයින් පසුම්බි සඳහා නොමිලේ විවෘත මූලාශ්‍ර තල්ලු දැනුම්දීම් සේවාදායකයකි. බ්ලූවොලට් හි යටිතල පහසුකම් මත විශ්වාසය නොතැබීම සඳහා ඔබට ඔබේම ග්‍රවුන්ඩ්කන්ට්‍රෝල් සේවා දායකයක් ස්ථාපනය කර එහි යූආර්එල් මෙහි තැබිය හැකිය. ග්‍රවුන්ඩ්කන්ට්‍රෝල් හි පෙරනිමි සේවාදායකය භාවිතා කිරීමට හිස්ව තබන්න.", @@ -273,11 +255,8 @@ "language": "භාෂාව", "last_updated": "අවසන් වරට යාවත්කාලීන කරන ලදී", "language_isRTL": "භාෂා දිශානතිය ක්‍රියාත්මක වීමට බ්ලූවොලට් නැවත ආරම්භ කිරීම අවශ්‍ය වේ.", - "lightning_error_lndhub_uri": "වලංගු නොවන LNDHub URI", "lightning_saved": "ඔබේ වෙනස්කම් සාර්ථකව සුරැකී ඇත.", "lightning_settings": "ලයිට්නින් සැකසුම්", - "tor_settings": "ටෝර් සැකසුම්", - "lightning_settings_explain": "ඔබේම එල්එන්ඩී නෝඩයට සම්බන්ධ වීමට කරුණාකර එල්.එන්.ඩී.හබ් ස්ථාපනය කර එහි යූආර්එල් සැකසුම් තුළට දමන්න. බ්ලූවොලට් හි එල්.එන්.ඩී.හබ් භාවිතා කිරීමට හිස්ව තබන්න. වෙනස්කම් සුරැකීමෙන් පසු සාදන ලද පසුම්බි පමණක් නිශ්චිත එල්.එන්.ඩී.හබ් වෙත සම්බන්ධ වන බව කරුණාවෙන් සලකන්න.", "network": "ජාල", "network_broadcast": "විකාශන ගනුදෙනුව", "network_electrum": "ඉලෙක්ට්‍රම් සේවාදායකය", @@ -285,120 +264,178 @@ "notifications": "දැනුම්දීම්", "open_link_in_explorer": "එක්ස්ප්ලෝරර් තුළ සම්බන්ධකය විවෘත කරන්න", "password": "මුර පදය", - "password_explain": "ආචයනය විකේතනය කිරීමට ඔබ භාවිතා කරන මුරපදය සාදන්න.", - "passwords_do_not_match": "මුර පද ගැලපෙන්නේ නැත.", "plausible_deniability": "පිළිගත හැකි ප්‍රතික්ෂේප කිරීම", "privacy": "පෞද්ගලිකත්වය", "privacy_read_clipboard": "පසුරු පුවරුව කියවන්න", "privacy_system_settings": "පද්ධති සැකසීම්", "privacy_quickactions": "පසුම්බි කෙටිමං", - "privacy_quickactions_explanation": "ඔබේ මුදල් පසුම්බියේ ශේෂය ඉක්මනින් බැලීමට ඔබගේ මුල් තිරයේ ඇති බ්ලූවොලට් යෙදුම් නිරූපකය ස්පර්ශ කර අල්ලා රඳවා සිටින්න.", "privacy_clipboard_explanation": "ඔබේ පසුරු පුවරුවේ ලිපිනයක් හෝ ඉන්වොයිසියක් හමු වුවහොත් කෙටිමං ලබා දෙන්න.", "privacy_do_not_track": "විශ්ලේෂණ අක්‍රීය කරන්න", "privacy_do_not_track_explanation": "විශ්ලේෂණය සඳහා කාර්ය සාධනය සහ විශ්වසනීයත්ව තොරතුරු ඉදිරිපත් නොකෙරේ.", - "push_notifications": "තල්ලු දැනුම්දීම්", "rate": "අනුපාතය", - "retype_password": "මුරපදය යළි ඇතුළත් කරන්න", "selfTest": "ස්වයං පරීක්ෂණය", "save": "සුරකින්න", "saved": "සුරකින ලදි", - "success_transaction_broadcasted": "සාර්ථකයි! ඔබේ ගනුදෙනුව විකාශනය වී ඇත!", "total_balance": "මුළු ශේෂය", "total_balance_explanation": "ඔබේ මුදල් පසුම්බිවල මුළු ශේෂය ඔබේ මුල් තිරයේ විජට් වල පෙන්වන්න.", "widgets": "විජට්", - "tools": "මෙවලම්" + "tools": "මෙවලම්", + "performance_score": "කාර්ය සාධන ලකුණු: {num}", + "run_performance_test": "කාර්ය සාධනය පරීක්ෂා කරන්න", + "block_explorer_invalid_custom_url": "සපයා ඇති URL වලංගු නොවේ. කරුණාකර http:// හෝ https:// යනුවෙන් ආරම්භ වන වලංගු URL එකක් ඇතුළත් කරන්න.", + "privacy_temporary_screenshots": "තිර ග්‍රහණයට ඉඩ දෙන්න", + "privacy_temporary_screenshots_instructions": "තිර ග්‍රහණ ආරක්ෂාව තාවකාලිකව අක්‍රිය කරනු ලබන අතර, තිරපිටපත් සහ තිර පටිගත කිරීම් සක්‍රීය කරයි. බ්ලූවොලට් වසා නැවත විවෘත කළ විට ආරක්ෂාව ස්වයංක්‍රීයව නැවත සක්‍රිය වේ.", + "biometrics_no_longer_available": "ඔබේ උපාංග සැකසුම් වෙනස් වී ඇති අතර ඒවා තවදුරටත් යෙදුමේ තෝරාගත් ආරක්ෂක සැකසුම් සමඟ නොගැලපේ. කරුණාකර ජෛවමිතික හෝ මුර කේතය නැවත සක්‍රිය කර, මෙම වෙනස්කම් යෙදීමට යෙදුම නැවත ආරම්භ කරන්න.", + "biom_no_passcode": "ඔබේ උපාංගයේ මුර කේතයක් හෝ ජෛවමිතික සක්‍රිය කර නොමැත. ඉදිරියට යාමට, කරුණාකර සැකසුම් යෙදුමෙහි මුර කේතයක් හෝ ජෛවමිතිකයක් වින්‍යාසගත කරන්න.", + "currency_source": "අනුපාතය ලබා ගන්නා ස්ථානය", + "donate": "පරිත්‍යාග කරන්න", + "donate_description": "Blue නොමිලේ තබා ගැනීමට අපට උදව් කරන්න!", + "electrum_error_connect": "සපයා ඇති Electrum සේවාදායකය වෙත සම්බන්ධ විය නොහැක", + "electrum_error_connect_tor": "සපයා ඇති Electrum සේවාදායකය වෙත සම්බන්ධ විය නොහැක. කරුණාකර Orbot යෙදුම සම්බන්ධ බව සහතික කර නැවත උත්සාහ කරන්න.", + "set_lndhub_as_default": "{url} පෙරනිමි LNDhub සේවාදායකය ලෙස සකසන්නද?", + "electrum_preferred_server": "කැමති සේවාදායකය", + "electrum_preferred_server_description": "සියලුම බිට්කොයින් ක්‍රියාකාරකම් සඳහා ඔබේ පසුම්බියට භාවිතා කිරීමට අවශ්‍ය සේවාදායකය ඇතුළත් කරන්න. සකස් කළ පසු, ශේෂයන් පරීක්ෂා කිරීමට, ගනුදෙනු යැවීමට සහ ජාල දත්ත ලබා ගැනීමට ඔබේ පසුම්බිය මෙම සේවාදායකය පමණක් භාවිතා කරයි. සැකසීමට පෙර මෙම සේවාදායකය විශ්වාස කරන බව සහතික කරන්න.", + "electrum_history": "ඉතිහාසය", + "electrum_reset_to_default": "මෙය බ්ලූවොලට් හට සේවාදායක ලැයිස්තුවෙන් අහඹු ලෙස සේවාදායකයක් තෝරා ගැනීමට ඉඩ දෙනු ඇත.", + "electrum_reset_to_default_and_clear_history": "පෙරනිමියට යළි සකසන්න සහ ඉතිහාසය මකන්න", + "encrypt_enc_and_pass": "මුරපදයෙන් ආරක්ෂිතයි", + "encrypt_storage_explanation_headline": "ගබඩා සංකේතනය සක්‍රිය කරන්න", + "encrypt_storage_explanation_description_line1": "ගබඩා සංකේතනය සක්‍රිය කිරීම ඔබේ දත්ත ඔබේ උපාංගයේ ගබඩා කරන ආකාරය ආරක්ෂා කිරීමෙන් යෙදුමට අමතර ආරක්ෂණ ස්ථරයක් එක් කරයි. මෙය අවසරයකින් තොරව ඔබේ තොරතුරු වෙත ප්‍රවේශ වීම ඕනෑම අයෙකුට අපහසු කරයි.", + "encrypt_storage_explanation_description_line2": "කෙසේ වෙතත්, මෙම සංකේතනය ආරක්ෂා කරන්නේ උපාංගයේ යතුරුදම් මත ගබඩා කර ඇති ඔබේ පසුම්බිවලට ප්‍රවේශ වීම පමණක් බව දැන ගැනීම වැදගත් වේ. එය පසුම්බි මතම මුරපදයක් හෝ අමතර ආරක්ෂාවක් යොදන්නේ නැත.", + "i_understand": "මට වැටහෙයි", + "block_explorer": "බ්ලොක් එක්ස්ප්ලෝරර්", + "block_explorer_preferred": "කැමති බ්ලොක් එක්ස්ප්ලෝරරය භාවිතා කරන්න", + "block_explorer_error_saving_custom": "කැමති බ්ලොක් එක්ස්ප්ලෝරරය සුරැකීමේදී දෝෂයකි", + "set_as_preferred": "කැමති ලෙස සකසන්න", + "set_as_preferred_electrum": "{host}:{port} කැමති සේවාදායකය ලෙස සැකසීම, අහඹු ලෙස යෝජිත සේවාදායකයකට සම්බන්ධ වීම අක්‍රිය කරයි.", + "encrypted_feature_disabled": "ගබඩා සංකේතනය සක්‍රිය කර ඇති විට මෙම පහසුකම භාවිතා කළ නොහැක.", + "encrypt_use_expl": "ගනුදෙනුවක් කිරීමට, අගුළු හැරීමට, අපනයනය කිරීමට හෝ පසුම්බියක් මැකීමට පෙර ඔබේ අනන්‍යතාවය තහවුරු කිරීමට {type} භාවිතා කරනු ඇත.", + "biometrics_fail": "{type} සක්‍රිය නොකළහොත් හෝ අගුළු හැරීමට අසමත් වුවහොත්, විකල්පයක් ලෙස ඔබට ඔබේ උපාංගයේ මුර කේතය භාවිතා කළ හැකිය.", + "license": "බලපත්‍රය", + "lightning_error_lndhub_uri": "වලංගු නොවන LNDhub URI", + "lightning_error_lndhub_uri_tor": "වලංගු නොවන LNDhub URI. කරුණාකර Orbot යෙදුම සම්බන්ධ බව සහතික කර නැවත උත්සාහ කරන්න.", + "lightning_settings_explain": "ඔබේම LND නෝඩයකට සම්බන්ධ වීමට, කරුණාකර LNDhub ස්ථාපනය කර එහි URL මෙහි සැකසුම්වල ඇතුළත් කරන්න. වෙනස්කම් සුරැකීමෙන් පසු සාදන ලද පසුම්බි පමණක් නියමිත LNDhub වෙත සම්බන්ධ වන බව සලකන්න.", + "lndhub_github": "GitHub ගබඩාව", + "electrum_suggested_description": "කැමති සේවාදායකයක් සකසා නොමැති විට, භාවිතය සඳහා අහඹු ලෙස යෝජිත සේවාදායකයක් තෝරා ගනු ලැබේ.", + "password_explain": "ඔබේ ගබඩාව අගුළු හැරීමට භාවිතා කරන මුරපදය ඇතුළත් කරන්න.", + "privacy_quickactions_explanation": "ඔබේ පසුම්බියේ ශේෂය ඉක්මනින් බැලීමට බ්ලූවොලට් යෙදුම් අයිකනය ස්පර්ශ කර අල්ලාගෙන සිටින්න.", + "push_notifications_explanation": "දැනුම්දීම් සක්‍රිය කිරීමෙන්, ඔබේ උපාංග ටෝකනය සේවාදායකය වෙත යවනු ලැබේ, දැනුම්දීම් සක්‍රිය කිරීමෙන් පසු සියලුම පසුම්බි සහ ගනුදෙනු සඳහා පසුම්බි ලිපින සහ ගනුදෙනු හැඳුනුම්පත් සමඟ. උපාංග ටෝකනය දැනුම්දීම් යැවීමට භාවිතා වන අතර, ඇතුළට පැමිණෙන බිට්කොයින් හෝ ගනුදෙනු තහවුරු කිරීම් පිළිබඳව ඔබට දැනුම් දීමට පසුම්බි තොරතුරු ඉඩ සලසයි.\n\nදැනුම්දීම් සක්‍රිය කිරීමෙන් පසු තොරතුරු පමණක් සම්ප්‍රේෂණය වේ — ඊට පෙර කිසිවක් එකතු නොකරයි.\n\nදැනුම්දීම් අක්‍රිය කිරීමෙන් මෙම සියලු තොරතුරු සේවාදායකයෙන් ඉවත් කරයි. තවද, යෙදුමෙන් පසුම්බියක් මැකීමෙන් එයට අදාළ තොරතුරු සේවාදායකයෙන් ද ඉවත් කරයි.", + "success_transaction_broadcasted": "ඔබේ ගනුදෙනුව සාර්ථකව විකාශනය කර ඇත!" }, "notifications": { "would_you_like_to_receive_notifications": "ඔබට ගෙවීම් ලැබෙන විට දැනුම්දීම් ලැබීමට ඔබ කැමතිද?", - "no_and_dont_ask": "නැත, නැවත මගෙන් අහන්න එපා", - "ask_me_later": "පසුව මගෙන් අසන්න" + "notifications_subtitle": "ඇතුළට පැමිණෙන ගෙවීම් සහ ගනුදෙනු තහවුරු කිරීම්", + "no_and_dont_ask": "නැත, මගෙන් නැවත අහන්න එපා.", + "permission_denied_message": "ඔබට දැනුම්දීම් යැවීමට අවසරය ඔබ ප්‍රතික්ෂේප කර ඇත. ඔබට දැනුම්දීම් ලැබීමට අවශ්‍ය නම්, කරුණාකර ඔබේ උපාංග සැකසුම්වල ඒවා සක්‍රිය කරන්න." }, "transactions": { "cancel_explain": "අපි මෙම ගනුදෙනුව ඔබට ගෙවන සහ ඉහළ ගාස්තු සහිත එකක් සමඟ ප්‍රතිස්ථාපනය කරන්නෙමු. මෙය වත්මන් ගනුදෙනුව ඵලදායී ලෙස අවලංගු කරයි. මෙය RBF ලෙස හැඳින්වේ - ගාස්තුවෙන් ප්‍රතිස්ථාපනය කරන්න.", "cancel_no": "මෙම ගනුදෙනුව ප්‍රතිස්ථාපනය කළ නොහැක.", "cancel_title": "මෙම ගනුදෙනුව අවලංගු කරන්න (RBF)", "confirmations_lowercase": "තහවුරු කිරීම් {confirmations}", - "copy_link": "සම්බන්ධක පිටපත් කරන්න", "expand_note": "සටහන පුළුල් කරන්න", "cpfp_create": "සාදන්න", "cpfp_exp": "ඔබගේ තහවුරු නොකළ ගනුදෙනුව සඳහා වැය කරන තවත් ගනුදෙනුවක් අපි නිර්මාණය කරන්නෙමු. මුළු ගාස්තුව නියම ගාස්තුවට වඩා වැඩි වන බැවින් එය වේගයෙන් කැණීම් කළ යුතුය. මෙය හැඳින්වෙන්නේ CPFP - දෙමාපියන් සඳහා ළමයා ගෙවයි.", "cpfp_no_bump": "මෙම ගනුදෙනුව බම්ප් කළ නොහැක.", "cpfp_title": "බම්ප් ගාස්තුව (CPFP)", - "details_balance_hide": "Hide Balance", + "details_balance_hide": "ශේෂය සඟවන්න", "details_balance_show": "ශේෂය පෙන්වන්න", - "details_block": "උස අවහිර කරන්න", "details_copy": "පිටපත් කරන්න", - "details_copy_amount": "ප්‍රමාණය පිටපත් කරන්න", "details_copy_block_explorer_link": "බ්ලොක් එක්ස්ප්ලෝරර් සම්බන්ධකය පිටපත් කරන්න", "details_copy_note": "සටහන පිටපත් කරන්න", "details_copy_txid": "ගනුදෙනු හැඳුනුම්පත පිටපත් කරන්න", - "details_from": "ආදානය", - "details_inputs": "යෙදවුම්", + "details_id": "ID", + "details_inputs": "ආදාන", "details_outputs": "ප්‍රතිදාන", "details_received": "ලැබුණි", - "transaction_note_saved": "ගනුදෙනු සටහන සාර්ථකව සුරකින ලදි.", - "details_show_in_block_explorer": "බ්ලොක් එක්ස්ප්ලෝරර් හි බලන්න", "details_title": "ගනුදෙනුව", "details_to": "ප්‍රතිදානය", "enable_offline_signing": "මෙම පසුම්බිය නොබැඳි අත්සන් කිරීම සමඟ එක්ව භාවිතා නොකෙරේ. ඔබ දැන් එය සක්‍රීය කිරීමට කැමතිද?", - "list_conf": "Conf: {number}", + "list_conf": "තහවුරු: {number}", "pending": "අපේක්ෂිත", "pending_with_amount": "අපේක්ෂිත {amt1} ({amt2})", "received_with_amount": "+{amt1} ({amt2})", "eta_10m": "පැමිණීමට ඇස්තමේන්තු ගත කාලය: විනාඩි 10 කින්", "eta_3h": "පැමිණීමට ඇස්තමේන්තු ගත කාලය: පැය 3 කින්", "eta_1d": "පැමිණීමට ඇස්තමේන්තු ගත කාලය: දවස් 01 කින්", - "view_wallet": "{walletLabel} බලන්න", "list_title": "ගනුදෙනු", + "list_title_received": "ලැබුණි", + "transaction": "ගනුදෙනුව", "open_url_error": "පෙරනිමි බ්‍රවුසරය සමඟ සබැඳිය විවෘත කළ නොහැක. කරුණාකර ඔබගේ පෙරනිමි බ්‍රවුසරය වෙනස් කර නැවත උත්සාහ කරන්න.", "rbf_title": "බම්ප් ගාස්තුව (RBF)", "status_bump": "බම්ප් ගාස්තුව", "status_cancel": "ගනුදෙනුව අවලංගු කරන්න", "transactions_count": "ගනුදෙනු ගණන", "txid": "ගනුදෙනු හැඳුනුම්පත", - "updating": "යාවත්කාලීන කරමින් ..." + "updating": "යාවත්කාලීන කරමින් ...", + "transaction_loading_error": "ගනුදෙනුව පූරණය කිරීමේදී ගැටළුවක් ඇති විය. කරුණාකර පසුව නැවත උත්සාහ කරන්න.", + "transaction_not_available": "ගනුදෙනුව නොමැත", + "date": "දිනය", + "details_view_in_browser": "බ්‍රවුසරයේ බලන්න", + "incoming_transaction": "ඇතුළට පැමිණෙන ගනුදෙනුව", + "outgoing_transaction": "පිටතට යන ගනුදෙනුව", + "expired_transaction": "කල් ඉකුත් වූ ගනුදෙනුව", + "pending_transaction": "අපේක්ෂිත ගනුදෙනුව", + "offchain": "ඕෆ්චේන්", + "onchain": "ඔන්චේන්", + "list_title_sent": "යවන ලදී", + "rbf_explain": "අපි මෙම ගනුදෙනුව වේගයෙන් කැණීම් කිරීමට හැකි වන පරිදි ඉහළ ගාස්තුවක් සහිත එකක් සමඟ ප්‍රතිස්ථාපනය කරන්නෙමු. මෙය හැඳින්වෙන්නේ RBF - ගාස්තුවෙන් ප්‍රතිස්ථාපනය කරන්න.", + "watchOnlyWarningTitle": "ආරක්ෂක අනතුරු ඇඟවීම", + "watchOnlyWarningDescription": "පරිශීලකයින් රැවටීමට බොහෝ විට \"නැරඹීමට පමණි\" පසුම්බි භාවිතා කරන වංචාකරුවන් ගැන ප්‍රවේශම් වන්න. මෙම පසුම්බි ඔබට අරමුදල් පාලනය කිරීමට හෝ යැවීමට ඉඩ නොදේ; ඒවා ශේෂය බැලීමට පමණක් ඉඩ දෙයි.", + "custom_fee_warning_title": "අනතුරු ඇඟවීම", + "custom_fee_warning_description": "1 sat/vB ට අඩු ගාස්තු වලංගු වුවද, නෝඩ් ප්‍රතිපත්ති හේතුවෙන් ඒවා සම්ප්‍රේෂණය නොවිය හැක.", + "details_eta_analyzing": "විශ්ලේෂණය කරමින්...", + "details_sent": "යවන ලදි", + "details_section": "විස්තර", + "details_explorer": "එක්ස්ප්ලෝරර්", + "details_network_fee": "ජාල ගාස්තුව", + "details_to_address": "වෙත", + "details_note": "සටහන", + "details_add_note": "එක් කරන්න", + "details_advanced": "උසස්", + "details_fee_rate": "ගාස්තු අනුපාතය", + "details_size": "ප්‍රමාණය", + "details_virtual_size": "අතථ්‍ය ප්‍රමාණය", + "details_tx_hex": "Tx හෙක්ස්", + "details_inputs_count": "ආදාන ({count})", + "details_outputs_count": "ප්‍රතිදාන ({count})" }, "wallets": { "add_bitcoin": "බිට්කොයින්", "add_bitcoin_explain": "සරල හා බලවත් බිට්කොයින් පසුම්බිය", "add_create": "සාදන්න", + "total_balance": "මුළු ශේෂය", + "add_entropy": "එන්ට්‍රොපි", "add_entropy_generated": "උත්පාදනය කරන ලද එන්ට්‍රොපි බයිට් {gen}", - "add_entropy_provide": "Provide entropy via dice rolls", + "add_entropy_provide": "දාදු කැට ඇදීම් මගින් එන්ට්‍රොපි සපයන්න", "add_entropy_remain": "උත්පාදනය කරන ලද එන්ට්‍රොපි බයිට් {gen}. ඉතිරි {rem} බයිට් පද්ධති අහඹු අංක උත්පාදක යන්ත්‍රයෙන් ලබා ගනු ඇත.", "add_import_wallet": "මුදල් පසුම්බිය ආනයනය කරන්න", "add_lightning": "ලයිට්නින්", "add_lightning_explain": "ක්‍ෂණික ගනුදෙනු සමඟ වියදම් කිරීම සඳහා", - "add_lndhub": "ඔබේ LNDHub වෙත සම්බන්ධ වන්න", - "add_lndhub_error": "ලබා දී ඇති නෝඩ් ලිපිනය වලංගු නොවන LNDHub නෝඩයකි.", "add_lndhub_placeholder": "ඔබේ නෝඩ් ලිපිනය", "add_placeholder": "මගේ පළමු මුදල් පසුම්බිය", "add_title": "පසුම්බිය එකතු කරන්න", "add_wallet_name": "නම", - "add_wallet_type": "ටයිප් කරන්න", - "balance": "ශේෂය", + "add_wallet_type": "වර්ගය", "clipboard_bitcoin": "ඔබේ පසුරු පුවරුවේ බිට්කොයින් ලිපිනයක් තිබේ. ඔබ එය ගනුදෙනුවක් සඳහා භාවිතා කිරීමට කැමතිද?", - "clipboard_lightning": "ඔබේ පසුරු පුවරුවේ අකුණු කිරීමේ ඉන්වොයිසියක් තිබේ. ඔබ එය ගනුදෙනුවක් සඳහා භාවිතා කිරීමට කැමතිද?", + "clipboard_lightning": "ඔබේ පසුරු පුවරුවේ ලයිට්නින් ඉන්වොයිසියක් තිබේ. ඔබ එය ගනුදෙනුවක් සඳහා භාවිතා කිරීමට කැමතිද?", "details_address": "ලිපිනය", "details_advanced": "උසස්", "details_are_you_sure": "ඔබට විශ්වාසද?", "details_connected_to": "සම්බන්ධයි", - "details_del_wb_err": "සපයන ලද ඉතිරි මුදල මෙම පසුම්බියේ ශේෂයට නොගැලපේ. කරුණාකර නැවත උත්සාහ කරන්න.", "details_del_wb_q": "මෙම මුදල් පසුම්බියේ ශේෂයක් ඇත. ඉදිරියට යාමට පෙර, මෙම මුදල් පසුම්බියේ බීජ වාක්‍ය ඛණ්ඩය නොමැතිව ඔබට අරමුදල් අයකර ගැනීමට නොහැකි වනු ඇති බව කරුණාවෙන් සලකන්න. අහම්බෙන් ඉවත් කිරීම වළක්වා ගැනීම සඳහා, කරුණාකර ඔබේ මුදල් පසුම්බියේ {balance} satoshis හි ශේෂය ඇතුළත් කරන්න.", "details_delete": "මකන්න", "details_delete_wallet": "පසුම්බිය මකන්න", "details_derivation_path": "ව්යුත්පන්න මාර්ගය", - "details_display": "පසුම්බි ලැයිස්තුවේ පෙන්වන්න", "details_export_backup": "අපනයනය/උපස්ථ කිරීම", "details_master_fingerprint": "ප්‍රධාන ඇඟිලි සලකුණ", "details_multisig_type": "බහු සිග්", - "details_no_cancel": "නැහැ, අවලංගු කරන්න", - "details_save": "සුරකින්න", - "details_show_xpub": "පසුම්බිය XPUB පෙන්වන්න", + "details_show_xpub": "පසුම්බියේ XPUB පෙන්වන්න", "details_show_addresses": "ලිපිනයන් පෙන්වන්න", "details_title": "පසුම්බිය", - "details_type": "ටයිප් කරන්න", + "wallets": "පසුම්බි", + "details_type": "වර්ගය", "details_use_with_hardware_wallet": "දෘඩාංග පසුම්බිය සමඟ භාවිතා කරන්න", - "details_wallet_updated": "පසුම්බිය යාවත්කාලීන කරන ලදි", "details_yes_delete": "ඔව්, මකන්න", "enter_bip38_password": "විකේතනය කිරීමට මුරපදය ඇතුළත් කරන්න", "export_title": "පසුම්බි අපනයනය", @@ -417,44 +454,81 @@ "import_discovery_subtitle": "සොයා ගත් පසුම්බියක් තෝරන්න", "import_discovery_derivation": "අභිරුචි ව්‍යුත්පන්න මාර්ගය භාවිතා කරන්න", "import_discovery_no_wallets": "පසුම්බි කිසිවක් හමු නොවීය.", - "import_derivation_found": "හමු විය", - "import_derivation_found_not": "සොයා ගත නොහැක", - "import_derivation_loading": "පූරණය වෙමින් ...", - "import_derivation_subtitle": "අභිරුචි ව්‍යුත්පන්න මාර්ගයක් ඇතුළු කරන්න, අපි ඔබේ මුදල් පසුම්බිය සොයා ගැනීමට උත්සාහ කරමු", "import_derivation_title": "ව්‍යුත්පන්න මාර්ගය", - "import_derivation_unknown": "නොදන්නා", - "import_wrong_path": "වැරදි ව්‍යුත්පන්න මාර්ගය", "list_create_a_button": "දැන් එකතු කරන්න", "list_create_a_wallet": "පසුම්බියක් එකතු කරන්න", - "list_create_a_wallet_text": "එය නොමිලේ වන අතර ඔබට කැමති ප්‍රමාණයක් ඔබට සෑදිය හැකිය.", "list_empty_txs1": "ඔබේ ගනුදෙනු මෙහි දිස්වේ.", - "list_empty_txs1_lightning": "ඔබේ දෛනික ගනුදෙනු සඳහා අකුණු පසුම්බිය භාවිතා කළ යුතුය. ගාස්තු අසාධාරණ ලෙස ලාභදායී වන අතර වේගය වේගයෙන් දැල්වෙමින් පවතී.", + "list_empty_txs1_lightning": "ඔබේ දෛනික ගනුදෙනු සඳහා ලයිට්නින් පසුම්බිය භාවිතා කළ යුතුය. ගාස්තු අසාධාරණ ලෙස ලාභදායී වන අතර වේගය වේගයෙන් දැල්වෙමින් පවතී.", "list_empty_txs2": "ඔබේ පසුම්බිය සමඟ ආරම්භ කරන්න.", - "list_empty_txs2_lightning": "\nඑය භාවිතා කිරීම ආරම්භ කිරීම සඳහා කළමනාකරණය කළමනාකරණය කර ඔබේ ශේෂය ඉහළ නැංවීමට තට්ටු කරන්න.", + "list_empty_txs2_lightning": "\nඑය භාවිතා කිරීම ආරම්භ කිරීම සඳහා අරමුදල් කළමනාකරණය මත තට්ටු කර ඔබේ ශේෂය ඉහළ නංවන්න.", "list_latest_transaction": "නවතම ගනුදෙනුව", - "list_ln_browser": "LApp බ්‍රව්සරය", "list_long_choose": "ඡායාරූපය තෝරන්න", - "list_long_clipboard": "පසුරු පුවරුවෙන් පිටපත් කරන්න", + "paste_from_clipboard": "අලවන්න", + "import_file": "ආයාත ගොනුව", "list_long_scan": "QR කේතය පරිලෝකනය කරන්න", "list_title": "පසුම්බි", "list_tryagain": "නැවත උත්සාහ කරන්න", - "no_ln_wallet_error": "අකුණු ඉන්වොයිසියක් ගෙවීමට පෙර, ඔබ මුලින්ම අකුණු පසුම්බියක් එකතු කළ යුතුය.", + "no_ln_wallet_error": "ලයිට්නින් ඉන්වොයිසියක් ගෙවීමට පෙර, ඔබ මුලින්ම ලයිට්නින් පසුම්බියක් එකතු කළ යුතුය.", "looks_like_bip38": "මෙය මුරපදයකින් ආරක්‍ෂිත පුද්ගලික යතුරක් ලෙස පෙනේ (BIP38).", - "reorder_title": "පසුම්බි නැවත ඇණවුම් කරන්න", - "reorder_instructions": "ලැයිස්තුව හරහා ඇදගෙන යාමට පසුම්බියක් මත තට්ටු කර අල්ලාගෙන සිටින්න.", "please_continue_scanning": "කරුණාකර ස්කෑන් කිරීම දිගටම කරගෙන යන්න.", "select_no_bitcoin": "දැනට බිට්කොයින් පසුම්බි නොමැත.", "select_no_bitcoin_exp": "ලයිට්නින් පසුම්බි නැවත පිරවීම සඳහා බිට්කොයින් පසුම්බියක් අවශ්‍ය වේ. කරුණාකර එකක් සාදා හෝ ආයාත කරන්න.", "select_wallet": "පසුම්බිය තෝරන්න", - "xpub_copiedToClipboard": "පසුරු පුවරුවට පිටපත් කර ඇත.", "pull_to_refresh": "නැවුම් කිරීමට අදින්න", - "warning_do_not_disclose": "අනතුරු ඇඟවීම! හෙළි නොකරන්න.", "add_ln_wallet_first": "ඔබ මුලින්ම ලයිට්නින් පසුම්බියක් එකතු කළ යුතුයි.", "identity_pubkey": "අනන්‍යතා Pubkey", - "xpub_title": "XPUB පසුම්බිය" + "xpub_title": "පසුම්බියේ XPUB", + "add_entropy_reset_title": "එන්ට්‍රොපි යළි සකසන්න", + "add_entropy_reset_message": "පසුම්බි වර්ගය වෙනස් කිරීමෙන් වත්මන් එන්ට්‍රොපිය යළි සකසනු ලැබේ. ඔබට ඉදිරියට යාමට අවශ්‍යද?", + "add_entropy_bytes": "එන්ට්‍රොපි බයිට් {bytes}", + "add_lndhub": "ඔබේ LNDhub වෙත සම්බන්ධ වන්න", + "add_lndhub_error": "සපයා ඇති නෝඩ් ලිපිනය වලංගු නොවන LNDhub නෝඩයකි.", + "add_wallet_seed_length": "බීජ දිග", + "add_wallet_seed_length_12": "වචන 12", + "add_wallet_seed_length_24": "වචන 24", + "clear_clipboard_on_import": "ආනයනය මත පසුරු පුවරුව හිස් කරන්න", + "details_del_wb_err": "සපයා ඇති ශේෂ මුදල මෙම පසුම්බියේ ශේෂයට නොගැලපේ. කරුණාකර නැවත උත්සාහ කරන්න.", + "details_display": "මුල් තිරයේ පෙන්වන්න", + "details_export_history": "ඉතිහාසය CSV වෙත අපනයනය කරන්න", + "swipe_balance_hide": "සඟවන්න", + "swipe_balance_show": "පෙන්වන්න", + "drag_to_reorder": "නැවත අනුපිළිවෙළ කිරීමට ඇද ගන්න", + "clear_search": "සෙවුම හිස් කරන්න", + "import_success_watchonly": "ඔබේ පසුම්බිය සාර්ථකව ආනයනය කර ඇත. අවවාදයයි: මෙය නැරඹීමට පමණි පසුම්බියකි, ඔබට මෙයින් වියදම් කළ නොහැක.", + "learn_more": "තවත් දැනගන්න", + "import_discovery_offline": "බ්ලූවොලට් දැනට නොබැඳි මාදිලියේ ඇත. මෙම මාදිලියේදී, එයට පසුම්බියේ පැවැත්ම තහවුරු කළ නොහැක, එබැවින් ඔබට නිවැරදි එක අතින් තේරිය යුතුයි", + "import_derivation_found": "හමු විය", + "import_derivation_found_not": "හමු නොවීය", + "import_derivation_loading": "පූරණය වෙමින්...", + "import_derivation_subtitle": "අභිරුචි ව්‍යුත්පන්න මාර්ගය ඇතුළත් කරන්න, අපි ඔබේ පසුම්බිය සොයා ගැනීමට උත්සාහ කරන්නෙමු.", + "import_derivation_unknown": "නොදනී", + "import_wrong_path": "වැරදි ව්‍යුත්පන්න මාර්ගය", + "list_create_a_wallet_text": "එය නොමිලේ වන අතර, ඔබට කැමති තරම් සෑදිය හැක.", + "manage_title": "පසුම්බි කළමනාකරණය කරන්න", + "no_results_found": "ප්‍රතිඵල හමු නොවීය.", + "warning_do_not_disclose": "පහත තොරතුරු කිසි විටෙක බෙදා නොගන්න", + "scan_import": "වෙනත් යෙදුමක ඔබේ පසුම්බිය ආනයනය කිරීමට මෙම QR කේතය පරිලෝකනය කරන්න.", + "write_down_header": "අතින් උපස්ථයක් සාදන්න", + "write_down": "මෙම වචන ලියා ආරක්ෂිතව ගබඩා කරන්න. පසුව ඔබේ පසුම්බිය ප්‍රතිසාධනය කිරීමට ඒවා භාවිතා කරන්න.", + "wallet_type_this": "මෙම පසුම්බියේ වර්ගය {type} වේ.", + "share_number": "{number} බෙදා ගන්න", + "copy_ln_url": "පසුව ඔබේ පසුම්බිය ප්‍රතිසාධනය කිරීමට මෙම URL පිටපත් කර ආරක්ෂිතව ගබඩා කරන්න.", + "copy_ln_public": "පසුව ඔබේ පසුම්බිය ප්‍රතිසාධනය කිරීමට මෙම තොරතුරු පිටපත් කර ආරක්ෂිතව ගබඩා කරන්න.", + "manage_wallets_search_placeholder": "පසුම්බි, ලිපින, ගනුදෙනු සහ සංදේශ සොයන්න", + "more_info": "තවත් තොරතුරු", + "details_delete_wallet_error_message": "මෙම පසුම්බිය දැනුම්දීම් වලින් ඉවත් කළ බව තහවුරු කිරීමේදී ගැටළුවක් ඇති විය — මෙය ජාල ගැටළුවක් හෝ දුර්වල සම්බන්ධතාවක් නිසා විය හැක. ඔබ ඉදිරියට ගියහොත්, මෙම පසුම්බියට අදාළ ගනුදෙනු සඳහා දැනුම්දීම් තවමත් ලැබිය හැකි අතර, එය මැකූ පසුව පවා.", + "details_delete_anyway": "කෙසේ වුවද මකන්න" + }, + "total_balance_view": { + "title": "මුළු ශේෂය", + "display_in_bitcoin": "බිට්කොයින් වලින් පෙන්වන්න", + "hide": "සඟවන්න", + "display_in_sats": "සැට්ස් වලින් පෙන්වන්න", + "display_in_fiat": "{currency} වලින් පෙන්වන්න", + "explanation": "දළ විශ්ලේෂණ තිරයේ ඔබේ සියලුම පසුම්බිවල මුළු ශේෂය බලන්න." }, "multisig": { - "multisig_vault": "සුරක්ෂිතාගාරය", + "multisig_vault": "බහු සිග් සුරක්ෂිතාගාරය", "default_label": "බහු සිග් සුරක්ෂිතාගාරය", "multisig_vault_explain": "විශාල ප්‍රමාණයක් සඳහා හොඳම ආරක්‍ෂාව", "provide_signature": "අත්සන ලබා දෙන්න", @@ -464,7 +538,6 @@ "fee_btc": "{number} බී.ටී.සී", "confirm": "තහවුරු කරන්න", "header": "යවන්න", - "share": "බෙදාගන්න", "view": "දැක්ම", "manage_keys": "යතුරු කළමනාකරණය කරන්න", "how_many_signatures_can_bluewallet_make": "බ්ලූවොලට්ට කොපමණ අත්සන් කළ හැකිද?", @@ -485,26 +558,20 @@ "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": "වියදම් කිරීමට සහ තුන්වැන්න ඔබට \nඋපස්ථයක් ලෙස භාවිතා කළ හැකිය.", "what_is_vault_description_to_spend_other": "වියදම් කිරීමට.", "quorum": "ගණපූර්ණයේ {m} {n}", "quorum_header": "ගණපූරණය", "of": "වල", "wallet_type": "පසුම්බි වර්ගය", - "invalid_mnemonics": "මෙම සිහිවටන වැකිය වලංගු නොවන බව පෙනේ.", - "invalid_cosigner": "වලංගු නොවන කොසිනර් දත්ත", "not_a_multisignature_xpub": "මෙය බහු අත්සන සහිත එක්ස්පබ් පසුම්බියකින් එකක් නොවේ!", - "invalid_cosigner_format": "වැරදි කොස්නයිනර්: මෙය {format} ආකෘතිය සඳහා කොසිනර් නොවේ.", "create_new_key": "අලුතින් නිර්මාණය කරන්න", "scan_or_open_file": "ගොනුව ස්කෑන් කරන්න හෝ විවෘත කරන්න", "i_have_mnemonics": "මෙම යතුර සඳහා බීජයක් මා සතුව ඇත.", "type_your_mnemonics": "ඔබේ දැනට තිබෙන සුරක්ෂිතාගාර යතුර ආනයනය කිරීමට බීජයක් ඇතුළු කරන්න.", - "this_is_cosigners_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": "අපනයන අත්සන් කළ පී.එස්.බී.ටී", "input_fp": "ඇඟිලි සලකුණ ඇතුළත් කරන්න", "input_fp_explain": "පෙරනිමි එක භාවිතා කිරීමට මඟහැර යන්න (00000000)", @@ -522,7 +589,21 @@ "ms_help_title4": "සුරක්ෂිතාගාර ආනයනය කිරීම", "ms_help_4": "බහු සිග් ආනයනය කිරීමට, ඔබේ උපස්ථ ගොනුව සහ ආයාත කිරීමේ විශේෂාංගය භාවිතා කරන්න. ඔබට බීජ සහ එක්ස්පබ් පමණක් තිබේ නම්, වෝල්ට් යතුරු සෑදීමේදී ඔබට තනි ආයාත බොත්තම භාවිතා කළ හැකිය.", "ms_help_title5": "උසස් මාදිලිය", - "ms_help_5": "පෙරනිමියෙන්, බ්ලූවොලට් මඟින් 2-න්-3 වෝල්ට් එකක් උත්පාදනය කරනු ඇත. වෙනස් ගණපූර්‍ණයක් සෑදීමට හෝ ලිපින වර්ගය වෙනස් කිරීමට සැකසීම් තුළ උසස් ප්‍රකාරය සක්‍රිය කරන්න." + "ms_help_5": "පෙරනිමියෙන්, බ්ලූවොලට් මඟින් 2-න්-3 වෝල්ට් එකක් උත්පාදනය කරනු ඇත. වෙනස් ගණපූර්‍ණයක් සෑදීමට හෝ ලිපින වර්ගය වෙනස් කිරීමට සැකසීම් තුළ උසස් ප්‍රකාරය සක්‍රිය කරන්න.", + "provide_signature_details": "මෙම ගනුදෙනුවට අත්සන් කිරීමට යතුර පවතින ඔබේ උපාංගය සහ පසුම්බිය භාවිතා කරන්න", + "provide_signature_details_bluewallet": "බ්ලූවොලට් හි, යවන තිර මෙනුව වෙත ගොස් තෝරන්න ", + "provide_signature_next_steps": "අත්සන් කළ ගනුදෙනුව පරිලෝකනය කරන්න හෝ ආනයනය කරන්න", + "provide_signature_next_steps_details": "ඔබේ පසුම්බිය සාර්ථකව ගනුදෙනුව අත්සන් කළ පසු, සපයා ඇති QR කේතය පරිලෝකනය කරන්න හෝ ඊට ඇමුණූ ගොනුව ආනයනය කරන්න, ඉන්පසු එය විකාශනය කිරීමට පෙර සියලුම ගනුදෙනු විස්තර සමාලෝචනය කරන්න.", + "share": "බෙදා ගන්න...", + "shared_key_detected": "හවුල් සම-අත්සන්කරු", + "shared_key_detected_question": "ඔබ සමඟ සම-අත්සන්කරුවෙකු බෙදාගෙන ඇත, ඔබට එය ආනයනය කිරීමට අවශ්‍යද?", + "invalid_mnemonics": "මෙම සිහිවටන වැකිය වලංගු බව පෙනෙන්නේ නැත.", + "invalid_cosigner": "වලංගු නොවන සම-අත්සන්කරු දත්ත", + "invalid_cosigner_format": "වැරදි සම-අත්සන්කරු: මෙය {format} ආකෘතිය සඳහා සම-අත්සන්කරුවෙකු නොවේ.", + "this_is_cosigners_xpub": "මෙය සම-අත්සන්කරුගේ XPUB වේ — වෙනත් පසුම්බියකට ආනයනය කිරීමට සූදානම්. එය බෙදා ගැනීම ආරක්ෂිතය.", + "this_is_cosigners_xpub_airdrop": "ඔබ AirDrop හරහා බෙදා ගන්නේ නම් ලබන්නන් සම්බන්ධීකරණ තිරයේ සිටිය යුතුය.", + "view_edit_cosigners": "සම-අත්සන්කරුවන් බලන්න/සංස්කරණය කරන්න", + "this_cosigner_is_already_imported": "මෙම සම-අත්සන්කරු දැනටමත් ආනයනය කර ඇත." }, "is_it_my_address": { "title": "එය මගේ ලිපිනයද?", @@ -532,18 +613,31 @@ "no_wallet_owns_address": "සපයා ඇති ලිපිනයන් සතුව වලංගු මුදල් පසුම්බි කිසිවක් නැත.", "view_qrcode": "QR කේතය බලන්න" }, + "autofill_word": { + "title": "අවසන් බීජ වචනය", + "enter": "ඔබේ අර්ධ සිහිවටන වැකිය ඇතුළත් කරන්න", + "generate_word": "අවසන් වචනය උත්පාදනය කරන්න", + "error": "ආදානය වචන 11 ක් හෝ 23 ක් සහිත අර්ධ සිහිවටන වැකියක් නොවේ. කරුණාකර නැවත උත්සාහ කරන්න." + }, "cc": { "change": "වෙනස් කරන්න", "coins_selected": "තෝරා ගත් කාසි ({number})", "selected_summ": "{value} තෝරා ඇත", - "empty": "මෙම පසුම්බියේ මේ මොහොතේ කාසි නොමැත.", + "empty": "මෙම පසුම්බියේ දැනට කාසි කිසිවක් නොමැත.", "freeze": "කැටි කරන්න", "freezeLabel": "කැටි කරන්න", - "freezeLabel_un": "කැරි නොකරන්න", + "freezeLabel_un": "කැටි නොකරන්න", "header": "කාසි පාලනය", "use_coin": "කාසිය භාවිතා කරන්න", "use_coins": "කාසි භාවිතා කරන්න", - "tip": "වැඩි දියුණු කළ පසුම්බිය කළමනාකරණය සඳහා කාසි බැලීමට, ලේබල් කිරීමට, කැටි කිරීමට හෝ තෝරා ගැනීමට මෙම පහසුකම ඔබට ඉඩ සලසයි. වර්ණ කව මත තට්ටු කිරීමෙන් ඔබට කාසි කිහිපයක් තෝරා ගත හැකිය." + "tip": "වැඩි දියුණු කළ පසුම්බිය කළමනාකරණය සඳහා කාසි බැලීමට, ලේබල් කිරීමට, කැටි කිරීමට හෝ තෝරා ගැනීමට මෙම පහසුකම ඔබට ඉඩ සලසයි. වර්ණ කව මත තට්ටු කිරීමෙන් ඔබට කාසි කිහිපයක් තෝරා ගත හැකිය.", + "sort_status": "තත්ත්වය", + "sort_asc": "ආරෝහණ", + "sort_desc": "අවරෝහණ", + "sort_height": "උස", + "sort_value": "අගය", + "sort_label": "ලේබලය", + "sort_by": "අනුව වර්ග කරන්න" }, "units": { "BTC": "බී.ටී.සී", @@ -552,6 +646,8 @@ "sats": "සැට්ස්" }, "addresses": { + "copy_private_key": "පුද්ගලික යතුර පිටපත් කරන්න", + "sensitive_private_key": "අවවාදයයි: පුද්ගලික යතුරු අතිශයින් සංවේදී වේ. ඉදිරියට යන්නද?", "sign_title": "පණිවිඩය අත්සන් කරන්න/තහවුරු කරන්න", "sign_help": "මෙහිදී ඔබට බිට්කොයින් ලිපිනයක් මත පදනම්ව ගුප්තකේතන අත්සනක් නිර්මාණය කිරීමට හෝ සත්‍යාපනය කිරීමට හැකිය.", "sign_sign": "අත්සන් කරන්න", @@ -563,24 +659,46 @@ "sign_placeholder_signature": "අත්සන", "addresses_title": "ලිපිනයන්", "type_change": "වෙනස් කරන්න", - "type_receive": "පිළිගන්න", + "type_receive": "ලබා ගන්න", "type_used": "භාවිතා කර ඇත", "transactions": "ගනුදෙනු" }, "lnurl_auth": { "register_question_part_1": "ඔබ ගිණුමක් ලියාපදිංචි කිරීමට කැමතිද", - "register_question_part_2": "ඔබේ අකුණු මුදල් පසුම්බිය භාවිතා කරන්නේද?", + "register_question_part_2": "ඔබේ ලයිට්නින් මුදල් පසුම්බිය භාවිතා කරන්නේද?", "register_answer": "ඔබ {hostname} හි ගිණුමක් සාර්ථකව ලියාපදිංචි කර ඇත!", "login_question_part_1": "ඔබ ලොග් වීමට කැමතිද", - "login_question_part_2": "ඔබේ අකුණු මුදල් පසුම්බිය භාවිතා කරන්නේද?", + "login_question_part_2": "ඔබේ ලයිට්නින් මුදල් පසුම්බිය භාවිතා කරන්නේද?", "login_answer": "ඔබ {hostname} හි සාර්ථකව පුරනය වී ඇත!", "link_question_part_1": "ඔබගේ ගිණුම සම්බන්ධ කිරීමට ඔබ කැමතිද", - "link_question_part_2": "ඔබේ අකුණු මුදල් පසුම්බියට?", - "link_answer": "ඔබගේ අකුණු පසුම්බිය {hostname} හි ඔබගේ ගිණුමට සාර්ථකව සම්බන්ධ කර ඇත!", + "link_question_part_2": "ඔබේ ලයිට්නින් මුදල් පසුම්බියට?", + "link_answer": "ඔබගේ ලයිට්නින් පසුම්බිය {hostname} හි ඔබගේ ගිණුමට සාර්ථකව සම්බන්ධ කර ඇත!", "auth_question_part_1": "ඔබ සත්‍යාපනය කිරීමට කැමතිද", - "auth_question_part_2": "ඔබේ අකුණු මුදල් පසුම්බිය භාවිතා කරන්නේද?", + "auth_question_part_2": "ඔබේ ලයිට්නින් මුදල් පසුම්බිය භාවිතා කරන්නේද?", "auth_answer": "ඔබ {hostname} හිදී සාර්ථකව සත්‍යාපනය කර ඇත!", "could_not_auth": "අපට ඔබව {hostname} වෙත සත්‍යාපනය කළ නොහැකි විය.", "authenticate": "සත්‍යාපනය කරන්න" + }, + "bip47": { + "payment_code": "ගෙවීම් කේතය", + "contacts": "සම්බන්ධතා", + "bip47_explain": "නැවත භාවිතා කළ හැකි සහ බෙදා ගත හැකි කේතයකි", + "bip47_explain_subtitle": "BIP47", + "purpose": "නැවත භාවිතා කළ හැකි සහ බෙදා ගත හැකි කේතය (BIP47)", + "pay_this_contact": "මෙම සම්බන්ධතාවට ගෙවන්න", + "rename_contact": "සම්බන්ධතාවය නැවත නම් කරන්න", + "copy_payment_code": "ගෙවීම් කේතය පිටපත් කරන්න", + "hide_contact": "සම්බන්ධතාවය සඟවන්න", + "rename": "නැවත නම් කරන්න", + "provide_name": "මෙම සම්බන්ධතාවය සඳහා නව නමක් සපයන්න", + "add_contact": "සම්බන්ධතාවය එක් කරන්න", + "provide_payment_code": "ගෙවීම් කේතය සපයන්න", + "invalid_pc": "වලංගු නොවන ගෙවීම් කේතය", + "notification_tx_unconfirmed": "දැනුම්දීම් ගනුදෙනුව තවමත් තහවුරු කර නැත, කරුණාකර රැඳී සිටින්න", + "failed_create_notif_tx": "ඔන්චේන් ගනුදෙනුවක් සෑදීමට අසමත් විය", + "onchain_tx_needed": "ඔන්චේන් ගනුදෙනුවක් අවශ්‍යයි", + "notif_tx_sent": "දැනුම්දීම් ගනුදෙනුව යවන ලදී. එය තහවුරු වන තෙක් කරුණාකර රැඳී සිටින්න", + "notif_tx": "දැනුම්දීම් ගනුදෙනුව", + "not_found": "ගෙවීම් කේතය හමු නොවීය" } } diff --git a/loc/sk_sk.json b/loc/sk_sk.json index 1dbf77a4d78..848759cd1de 100644 --- a/loc/sk_sk.json +++ b/loc/sk_sk.json @@ -3,275 +3,702 @@ "bad_password": "Nesprávne heslo! Skúste to znovu.", "cancel": "Zrušiť", "continue": "Pokračovať", + "clipboard": "Schránka", + "copied": "Skopírované!", + "discard_changes": "Zahodiť zmeny?", + "discard_changes_explain": "Máte neuložené zmeny. Naozaj ich chcete zahodiť a opustiť obrazovku?", "enter_password": "Zadajte heslo", "never": "Nikdy", - "disabled": "Vypnuté", "of": "{number} z {total}", "ok": "OK", + "enter_url": "Zadajte URL", "storage_is_encrypted": "Vaše úložisko je zašifrované. Zadajte heslo k odomknutiu.", "yes": "Áno", "no": "Nie", - "save": "Uložiť", - "seed": "Semienko", + "save": "Uložiť...", + "seed": "Seed fráza", "success": "Správne", "wallet_key": "Peňaženkový kľúč", - "invalid_animated_qr_code_fragment": "Neplatný animovaný fragment QR kódu. Skúste to znovu, prosím.", - "file_saved": "Súbor {filePath} bol uložený v {destination}.", - "downloads_folder": "Priečinok so stiahnutými súbormi." - }, - "alert": { - "default": "Varovanie" + "close": "Zavrieť", + "change_input_currency": "Zmeniť vstupnú menu", + "refresh": "Obnoviť", + "pick_image": "Vybrať z knižnice", + "pick_file": "Vybrať súbor", + "enter_amount": "Zadajte čiastku", + "qr_custom_input_button": "Ťuknite 10-krát pre vlastný vstup", + "unlock": "Odomknúť", + "port": "Port", + "ssl_port": "SSL port", + "suggested": "Odporúčané" }, "azteco": { "codeIs": "Váš voucher kód je", - "success": "Správne" + "errorBeforeRefeem": "Pred uplatnením musíte najprv pridať Bitcoinovú peňaženku.", + "errorSomething": "Niečo sa pokazilo. Je tento voucher stále platný?", + "redeem": "Uplatniť do peňaženky", + "redeemButton": "Uplatniť", + "success": "Správne", + "successMessage": "Voucher bol úspešne uplatnený! Vaše prostriedky by mali čoskoro doraziť do vašej Bitcoinovej peňaženky.", + "title": "Uplatniť Azte.co voucher" }, "entropy": { "save": "Uložiť", "title": "Entropia", - "undo": "Späť" + "undo": "Späť", + "amountOfEntropy": "{bits} z {limit} bitov" }, "errors": { + "broadcast": "Odoslanie zlyhalo.", "error": "Chyba", "network": "Sieťová chyba" }, "lnd": { - "active": "Aktívne", - "inactive": "Neaktívne", - "channels": "Kanály", - "close_channel": "Zatvoriť kanál", - "new_channel": "Nový kanál", - "errorInvoiceExpired": "Faktúra je expirovaná", + "errorInvoiceExpired": "Faktúra expirovala.", "expired": "Expirovaná", "expiresIn": "Vyprší za {time} minút", "payButton": "Zaplatiť", - "placeholder": "Faktúra", - "open_channel": "Otvoriť kanál", + "payment": "Platba", + "placeholder": "Faktúra alebo adresa", + "potentialFee": "Potenciálny poplatok: {fee}", "refill": "Doplniť", "refill_create": "Pre pokračovanie, prosím, vytvorte si Bitcoinovú peňaženku", "refill_external": "Doplniť z externej peňaženky", "refill_lnd_balance": "Doplniť zostatok na Lightning peňaženke", "sameWalletAsInvoiceError": "Faktúra sa nedá uhradiť s rovnakou peňaženkou ako tá, ktorá ju vytvorila.", - "title": "spravovať zostatok", - "can_send": "Môže poslať" + "title": "spravovať zostatok" }, "lndViewInvoice": { "additional_info": "Doplňujúce informácie", "for": "Za:", - "open_direct_channel": "Otvoriť priamy kanál s týmto uzlom:", + "lightning_invoice": "Faktúra Lightning", + "please_pay_between_and": "Prosím zaplaťte medzi {min} a {max}", "please_pay": "Prosím zaplaťte", - "sats": "sats" + "preimage": "Predobraz", + "sats": "sats", + "date_time": "Dátum a čas", + "wasnt_paid_and_expired": "Táto faktúra nebola zaplatená a jej platnosť uplynula." }, "plausibledeniability": { "create_fake_storage": "Vytvoriť falošné zašifrované úložisko", - "create_password": "Vytvoriť heslo", "create_password_explanation": "Heslo k falošnému úložisku nesmie byť rovnaké ako heslo k hlavnému úložisku", - "help": "Za určitých okolností môžete byť donútení k prezradeniu hesla. K zaisteniu bezpečnosti vaších prostriedkov, BlueWallet môže vytvořiť ďalšie zašifrované úložiská s rozdielným heslom. V prípade potreby môžete toto heslo dať tretej strane. Pokiaľ bude zadané do BlueWallet, odomkne nové \"falošné\" úložisko. Toto bude vyzerať hodnoverne, ale udrží vaše pravé hlavné úložisko v bezpečí.", + "help": "Za určitých okolností môžete byť donútení k prezradeniu hesla. Na zaistenie bezpečnosti vašich prostriedkov môže BlueWallet vytvoriť ďalšie zašifrované úložiská s rozdielnym heslom. V prípade potreby môžete toto heslo dať tretej strane. Pokiaľ bude zadané do BlueWallet, odomkne nové \"falošné\" úložisko. Toto bude vyzerať hodnoverne, ale udrží vaše pravé hlavné úložisko v bezpečí.", "help2": "Nové úložisko bude plne funkčné, môžete naň uložiť minimálnu čiastku, aby vyzeralo uveriteľnejšie.", "password_should_not_match": "Heslo k falošnému úložisku nesmie byť rovnaké ako heslo k hlavnému úložisku", - "retype_password": "Heslo znovu", - "success": "Úspech", "title": "Plausible deniability..." }, "pleasebackup": { "ask": "Zapísali ste si svoju zálohovaciu frázu? Táto fráza je nutná pre prístup k vašim prostriedkom v prípade straty či poruchy prístroja. Bez zálohovacej frázy by ste trvale prišli o svoje prostriedky.", - "ask_no": "Ešte nie", - "ask_yes": "Áno", - "title": "Tvoja peňaženka bola vytvorená!" + "ask_no": "Nie, ešte nie.", + "ask_yes": "Áno, mám.", + "ok": "OK, zapísal som si to.", + "ok_lnd": "OK, uložené.", + "text": "Prosím nájdite si chvíľu a zapíšte si túto mnemonickú frázu na papier.\nJe to vaša záloha a môžete ju použiť na obnovenie peňaženky.", + "text_lnd": "Prosím uložte si túto zálohu peňaženky. Umožní vám obnoviť peňaženku v prípade straty.", + "title": "Vaša peňaženka je vytvorená..." }, "receive": { "details_create": "Vytvoriť", "details_label": "Popis", "details_setAmount": "Prijať čiastku...", - "details_share": "zdieľať", - "header": "Prijať" + "details_share": "Zdieľať...", + "address_not_found": "Nepodarilo sa vygenerovať prijímaciu adresu.", + "header": "Prijať", + "reset": "Resetovať", + "maxSats": "Maximálna čiastka je {max} sats", + "maxSatsFull": "Maximálna čiastka je {max} sats alebo {currency}", + "minSats": "Minimálna čiastka je {min} sats", + "minSatsFull": "Minimálna čiastka je {min} sats alebo {currency}", + "qrcode_for_the_address": "QR kód adresy", + "bip47_explanation": "Platobné kódy sú univerzálna adresa, ktorá neodhaľuje adresy vašej peňaženky. Nie všetky služby ich podporujú." }, "send": { + "provided_address_is_invoice": "Táto adresa vyzerá ako faktúra Lightning. Prosím prejdite do svojej peňaženky Lightning, aby ste túto faktúru zaplatili.", "broadcastButton": "Odoslať", "broadcastError": "chyba", + "broadcastNone": "Vložte hex transakcie", "broadcastPending": "Čaká...", "broadcastSuccess": "Správne", "confirm_header": "Potvrdiť", "confirm_sendNow": "Poslať teraz", "create_amount": "Čiastka", "create_broadcast": "Odoslať", + "create_copy": "Skopírovať a odoslať neskôr", "create_details": "Detaily", "create_fee": "Poplatok", "create_memo": "Popis", + "create_satoshi_per_vbyte": "Satoshi za vByte", "create_this_is_hex": "Toto je vaša transakcia, podpísaná a pripravená k odoslaniu.", "create_to": "Cieľ", "create_tx_size": "veľkosť transakcie", "create_verify": "Overiť na coinb.in", + "details_insert_contact": "Vložiť kontakt", "details_add_rec_add": "Pridať prijímateľa", "details_add_rec_rem": "Odobrať prijímateľa", + "details_add_recc_rem_all_alert_description": "Naozaj chcete odstrániť všetkých prijímateľov?", + "details_add_rec_rem_all": "Odstrániť všetkých prijímateľov", + "details_recipients_title": "Prijímatelia", + "details_recipient_title": "Prijímateľ #{number} z #{total}", + "please_complete_recipient_title": "Neúplný prijímateľ", + "please_complete_recipient_details": "Prosím doplňte údaje prijímateľa #{number} pred pridaním nového prijímateľa.", "details_address": "adresa", "details_address_field_is_not_valid": "Adresa nie je správne vyplnená", "details_adv_fee_bump": "Umožniť navýšenie poplatku", "details_adv_full": "Použiť celý zostatok", + "details_adv_full_sure": "Naozaj chcete pre túto transakciu použiť celý zostatok peňaženky?", + "details_adv_full_sure_frozen": "Naozaj chcete pre túto transakciu použiť celý zostatok peňaženky? Pamätajte, že zmrazené mince sú vylúčené.", "details_adv_import": "Importovať transakciu", + "details_adv_import_qr": "Importovať transakciu (QR)", "details_amount_field_is_not_valid": "Čiastka nie je správne vyplnená", + "details_amount_field_is_less_than_minimum_amount_sat": "Zadaná čiastka je príliš malá. Prosím zadajte čiastku väčšiu ako 500 sats.", "details_create": "Vytvoriť", + "details_error_decode": "Nepodarilo sa dekódovať Bitcoinovú adresu", "details_fee_field_is_not_valid": "Poplatok nie je správne vyplnený", + "details_frozen": "{amount} BTC je zmrazených.", "details_next": "Ďalej", + "details_no_signed_tx": "Vybraný súbor neobsahuje transakciu, ktorú možno importovať.", "details_note_placeholder": "poznámka pre seba", "details_scan": "Skenovať", + "details_scan_hint": "Dvojitým ťuknutím naskenujte alebo importujte cieľ", + "details_scan_error": "Chyba skenovania", "details_total_exceeds_balance": "Čiastka, ktorú chcete poslať, presahuje dostupný zostatok.", + "details_total_exceeds_balance_frozen": "Posielaná čiastka presahuje dostupný zostatok. Pamätajte, že zmrazené mince sú vylúčené.", + "details_unrecognized_file_format": "Nerozpoznaný formát súboru", "details_wallet_before_tx": "Pred vytvorením transakcie potrebujete najprv pridať Bitcoinovú peňaženku.", + "dynamic_init": "Inicializácia", "dynamic_next": "Ďalší", "dynamic_prev": "Späť", "dynamic_start": "Štart", - "dynamic_stop": "Stop", + "dynamic_stop": "Zastaviť", + "fee_10m": "10m", + "fee_1d": "1d", + "fee_3h": "3h", + "fee_custom": "Vlastný", + "insert_custom_fee": "Vložiť poplatok", "fee_fast": "Rýchlo", + "fee_medium": "Stredne", + "fee_replace_minvb": "Celková sadzba poplatku (satoshi za vByte), ktorú chcete zaplatiť, musí byť vyššia ako {min} sat/vByte.", + "fee_satvbyte": "v sat/vByte", + "fee_slow": "Pomaly", "header": "Poslať", + "input_clear": "Vymazať", "input_done": "Hotovo", + "input_paste": "Vložiť", + "input_total": "Spolu:", "permission_camera_message": "Potrebujeme povolenie na použitie kamery", - "permission_camera_title": "Povolenie na použitie kamery", - "permission_storage_later": "Požiadať neskôr", + "psbt_sign": "Podpísať transakciu", + "invalid_psbt": "Bol poskytnutý neplatný PSBT.", + "open_settings": "Otvoriť nastavenia", + "permission_storage_denied_message": "BlueWallet nemôže uložiť tento súbor. Prosím otvorte nastavenia zariadenia a povoľte prístup k úložisku.", + "permission_storage_title": "Povolenie prístupu k úložisku", + "psbt_clipboard": "Skopírovať do schránky", + "psbt_this_is_psbt": "Toto je čiastočne podpísaná Bitcoinová transakcia (PSBT). Prosím dokončite jej podpísanie pomocou vašej hardvérovej peňaženky.", "psbt_tx_export": "Exportovať do súboru", + "no_tx_signing_in_progress": "Neprebieha žiadne podpisovanie transakcie.", + "outdated_rate": "Kurz bol naposledy aktualizovaný: {date}", "psbt_tx_open": "Otvoriť podpísanú transakciu", "psbt_tx_scan": "Skenovať podpísanú transakciu", + "qr_error_no_qrcode": "Vo vybranom obrázku sa nepodarilo nájsť platný QR kód. Uistite sa, že obrázok obsahuje iba QR kód a žiadny ďalší obsah ako text alebo tlačidlá.", + "reset_amount": "Resetovať čiastku", + "reset_amount_confirm": "Chcete resetovať čiastku?", "success_done": "Hotovo", - "txSaved": "Transakcia bola uložená do {filePath}" + "txSaved": "Súbor transakcie ({filePath}) bol uložený.", + "file_saved_at_path": "Súbor ({filePath}) bol uložený.", + "cant_send_to_silentpayment_adress": "Z tejto peňaženky nemožno odosielať na adresy Silent Payments", + "cant_send_to_bip47": "Z tejto peňaženky nemožno odosielať na platobné kódy BIP47", + "cant_find_bip47_notification": "Najprv pridajte tento platobný kód do kontaktov", + "problem_with_psbt": "Problém s PSBT" }, "settings": { "about": "O BlueWallet", + "about_awesome": "Vytvorené pomocou skvelého", "about_backup": "Vždy si zálohujte kľúče!", "about_free": "BlueWallet je projekt, ktorý je zdarma a s otvorenými zdrojovými kódmi. Vytvorený používateľmi Bitcoinu.", + "about_license": "MIT licencia", + "about_release_notes": "Poznámky k vydaniu", "about_review": "Napíšte nám recenziu", + "performance_score": "Skóre výkonu: {num}", + "run_performance_test": "Otestovať výkon", + "about_selftest": "Spustiť samokontrolu", + "block_explorer_invalid_custom_url": "Zadaná URL je neplatná. Prosím zadajte platnú URL začínajúcu na http:// alebo https://.", + "about_selftest_electrum_disabled": "Samokontrola nie je dostupná v offline režime Electrum. Prosím vypnite offline režim a skúste znova.", + "about_selftest_ok": "Všetky interné testy prebehli úspešne. Peňaženka funguje dobre.", "about_sm_github": "GitHub", "about_sm_telegram": "Telegram chat", - "about_sm_twitter": "Sledujte nás na Twitteri", - "advanced_options": "Pokročilé nastavenia", + "privacy_temporary_screenshots": "Povoliť snímanie obrazovky", + "privacy_temporary_screenshots_instructions": "Ochrana pred snímaním obrazovky bude dočasne vypnutá, čím sa umožnia snímky obrazovky a nahrávanie obrazovky. Ochrana sa automaticky znova aktivuje po zatvorení a opätovnom otvorení BlueWallet.", + "biometrics": "Biometria", + "biometrics_no_longer_available": "Nastavenia vášho zariadenia sa zmenili a už nezodpovedajú vybraným bezpečnostným nastaveniam aplikácie. Prosím znova povoľte biometriu alebo prístupový kód a potom reštartujte aplikáciu, aby sa zmeny prejavili.", + "biom_10times": "Pokúsili ste sa zadať heslo 10-krát. Chcete resetovať svoje úložisko? Týmto sa odstránia všetky peňaženky a dešifruje úložisko.", + "biom_conf_identity": "Prosím potvrďte svoju identitu.", + "biom_no_passcode": "Vaše zariadenie nemá nastavený prístupový kód ani biometriu. Aby ste mohli pokračovať, prosím nakonfigurujte prístupový kód alebo biometriu v aplikácii Nastavenia.", + "biom_remove_decrypt": "Všetky vaše peňaženky budú odstránené a vaše úložisko bude dešifrované. Naozaj chcete pokračovať?", "currency": "Mena", - "default_wallets": "Zobraziť všetky peňaženky", + "currency_source": "Kurz je získavaný z", + "currency_fetch_error": "Pri získavaní kurzu pre vybranú menu nastala chyba.", + "default_title": "Pri spustení", + "donate": "Prispieť", + "donate_description": "Pomôžte nám udržať Blue zadarmo!", + "electrum_connected": "Pripojené", + "electrum_connected_not": "Nepripojené", + "electrum_error_connect": "Nemožno sa pripojiť k zadanému Electrum serveru", + "electrum_error_connect_tor": "Nemožno sa pripojiť k zadanému Electrum serveru. Prosím uistite sa, že aplikácia Orbot je pripojená a skúste znova.", + "lndhub_uri": "Napr., {example}", + "electrum_host": "Napr., {example}", + "electrum_offline_mode": "Offline režim", + "electrum_offline_description": "Keď je zapnuté, vaše Bitcoinové peňaženky sa nebudú pokúšať načítavať zostatky alebo transakcie.", + "electrum_port": "Port, zvyčajne {example}", + "use_ssl": "Použiť SSL", "electrum_saved": "Vaše zmeny boli úspešne uložené. Aby sa zmeny prejavili, môže byť potrebný reštart.", + "set_electrum_server_as_default": "Nastaviť {server} ako predvolený Electrum server?", + "set_lndhub_as_default": "Nastaviť {url} ako predvolený LNDhub server?", "electrum_settings_server": "Electrum server", "electrum_status": "Stav", - "electrum_clear_alert_cancel": "Zrušiť", + "electrum_preferred_server": "Preferovaný server", + "electrum_preferred_server_description": "Zadajte server, ktorý chcete, aby vaša peňaženka používala pre všetky Bitcoinové aktivity. Po nastavení bude vaša peňaženka výhradne používať tento server na kontrolu zostatkov, odosielanie transakcií a načítavanie sieťových dát. Pred nastavením sa uistite, že tomuto serveru dôverujete.", + "electrum_unable_to_connect": "Nemožno sa pripojiť k {server}.", + "electrum_history": "História", + "electrum_reset_to_default": "Toto umožní BlueWallet náhodne vybrať server zo zoznamu serverov.", + "electrum_reset": "Obnoviť predvolené", + "electrum_reset_to_default_and_clear_history": "Obnoviť predvolené a vymazať históriu", "encrypt_decrypt": "Dešifrovať úložisko", "encrypt_decrypt_q": "Naozaj chcete dešifrovať úložisko? Peňaženky budú prístupné bez hesla.", - "encrypt_enc_and_pass": "Zašifrované a chránené heslom", + "encrypt_enc_and_pass": "Chránené heslom", + "encrypt_storage_explanation_headline": "Zapnúť šifrovanie úložiska", + "encrypt_storage_explanation_description_line1": "Zapnutie šifrovania úložiska pridá ďalšiu vrstvu ochrany pre vašu aplikáciu tým, že zabezpečí spôsob ukladania vašich dát v zariadení. To sťažuje prístup k vašim informáciám bez povolenia.", + "encrypt_storage_explanation_description_line2": "Je však dôležité vedieť, že toto šifrovanie chráni iba prístup k peňaženkám uloženým v zväzku kľúčov zariadenia. Nenastavuje heslo ani žiadnu ďalšiu ochranu na samotné peňaženky.", + "i_understand": "Rozumiem", + "block_explorer": "Prieskumník blokov", + "block_explorer_preferred": "Použiť preferovaný prieskumník blokov", + "block_explorer_error_saving_custom": "Chyba pri ukladaní preferovaného prieskumníka blokov", "encrypt_title": "Bezpečnosť", "encrypt_tstorage": "úložisko", "encrypt_use": "Použiť {type}", + "set_as_preferred": "Nastaviť ako preferované", + "set_as_preferred_electrum": "Nastavenie {host}:{port} ako preferovaného servera zakáže náhodné pripájanie k odporúčanému serveru.", + "encrypted_feature_disabled": "Túto funkciu nemožno použiť so zapnutým šifrovaným úložiskom.", + "encrypt_use_expl": "{type} sa použije na potvrdenie vašej identity pred uskutočnením transakcie, odomknutím, exportom alebo zmazaním peňaženky.", + "biometrics_fail": "Ak {type} nie je zapnutá alebo zlyhá pri odomknutí, môžete ako alternatívu použiť prístupový kód zariadenia.", "general": "Všeobecné", - "general_adv_mode": "Povoliť rozšírené nastavenia", + "general_continuity": "Kontinuita", + "general_continuity_e": "Keď je zapnuté, budete môcť prezerať vybrané peňaženky a transakcie pomocou iných zariadení Apple pripojených k iCloud.", + "groundcontrol_explanation": "GroundControl je bezplatný open-source server pre push notifikácie pre Bitcoinové peňaženky. Môžete si nainštalovať vlastný server GroundControl a sem vložiť jeho URL, aby ste sa nespoliehali na infraštruktúru BlueWallet. Ponechajte prázdne pre použitie predvoleného servera GroundControl.", "header": "Nastavenia", "language": "Jazyk", + "last_updated": "Naposledy aktualizované", + "language_isRTL": "Aby sa orientácia jazyka prejavila, je potrebné reštartovať BlueWallet.", + "license": "Licencia", + "lightning_error_lndhub_uri": "Neplatná LNDhub URI", + "lightning_error_lndhub_uri_tor": "Neplatná LNDhub URI. Prosím uistite sa, že aplikácia Orbot je pripojená a skúste znova.", "lightning_saved": "Vaše zmeny boli úspešne uložené.", "lightning_settings": "Lightning nastavenia", + "lightning_settings_explain": "Ak sa chcete pripojiť k vlastnému LND uzlu, prosím nainštalujte LNDhub a vložte jeho URL sem do nastavení. Pamätajte, že iba peňaženky vytvorené po uložení zmien sa pripoja k zadanému LNDhub.", + "lndhub_github": "GitHub repozitár", "network": "Sieť", + "network_broadcast": "Odoslať transakciu", "network_electrum": "Electrum server", + "electrum_suggested_description": "Keď nie je nastavený preferovaný server, na použitie sa náhodne vyberie odporúčaný server.", + "not_a_valid_uri": "Neplatná URI", "notifications": "Notifikácie", + "open_link_in_explorer": "Otvoriť odkaz v prieskumníkovi", "password": "Heslo", - "password_explain": "Vytvorte si heslo k zašifrovanému úložisku.", - "passwords_do_not_match": "Hesla se nezhodujú", + "password_explain": "Zadajte heslo, ktoré budete používať na odomknutie úložiska.", "plausible_deniability": "Plausible deniability...", + "privacy": "Súkromie", + "privacy_read_clipboard": "Čítať schránku", "privacy_system_settings": "Systémové nastavenia", - "push_notifications": "Push notifikácie", - "retype_password": "Heslo znovu", + "privacy_quickactions": "Skratky peňaženky", + "privacy_quickactions_explanation": "Dotknite sa a podržte ikonu aplikácie BlueWallet pre rýchle zobrazenie zostatku vašej peňaženky.", + "privacy_clipboard_explanation": "Poskytnúť skratky, ak je v schránke nájdená adresa alebo faktúra.", + "privacy_do_not_track": "Vypnúť analytiku", + "privacy_do_not_track_explanation": "Informácie o výkone a spoľahlivosti nebudú odosielané na analýzu.", + "rate": "Kurz", + "push_notifications_explanation": "Zapnutím notifikácií bude token vášho zariadenia odoslaný na server spolu s adresami peňaženiek a ID transakcií pre všetky peňaženky a transakcie vykonané po zapnutí notifikácií. Token zariadenia sa používa na odosielanie notifikácií a informácie o peňaženke nám umožňujú upozorniť vás na prichádzajúci Bitcoin alebo potvrdenia transakcií.\n\nPrenášajú sa iba informácie po zapnutí notifikácií — nič spred toho sa nezhromažďuje.\n\nVypnutím notifikácií sa všetky tieto informácie zo servera odstránia. Okrem toho zmazanie peňaženky z aplikácie tiež odstráni jej súvisiace informácie zo servera.", + "selfTest": "Samokontrola", "save": "Uložiť", - "saved": "Uložené" + "saved": "Uložené", + "success_transaction_broadcasted": "Vaša transakcia bola úspešne odoslaná!", + "total_balance": "Celkový zostatok", + "total_balance_explanation": "Zobraziť celkový zostatok všetkých vašich peňaženiek na widgetoch domovskej obrazovky.", + "widgets": "Widgety", + "tools": "Nástroje" }, "notifications": { - "ask_me_later": "Požiadať neskôr" + "would_you_like_to_receive_notifications": "Chceli by ste dostávať notifikácie pri prichádzajúcich platbách?", + "notifications_subtitle": "Prichádzajúce platby a potvrdenia transakcií", + "no_and_dont_ask": "Nie a viac sa nepýtať.", + "permission_denied_message": "Odmietli ste povolenie na odosielanie notifikácií. Ak chcete dostávať notifikácie, prosím povoľte ich v nastaveniach zariadenia." }, "transactions": { + "cancel_explain": "Túto transakciu nahradíme takou, ktorá platí vám a má vyšší poplatok. Tým sa efektívne zruší súčasná transakcia. Toto sa nazýva RBF — Replace by Fee.", "cancel_no": "Táto transakcia sa nedá nahradiť", "cancel_title": "Zrušiť transakciu (RBF)", + "transaction_loading_error": "Pri načítavaní transakcie nastal problém. Prosím skúste to neskôr.", + "transaction_not_available": "Transakcia nie je dostupná", + "confirmations_lowercase": "{confirmations} potvrdení", + "expand_note": "Rozbaliť poznámku", "cpfp_create": "Vytvoriť", "cpfp_exp": "Vytvoríme novú transakciu, ktorá ako vstup použije výstup z Vašej nepotvrdenej transakcie. Súčet poplatkov bude vyšší, preto sa transakcie overia skôr. Tento postup sa volá CPFP - Child Pays For Parent.", "cpfp_no_bump": "Poplatok za túto transakciu sa nedá navýšiť", "cpfp_title": "Navýšiť poplatok za transakciu (CPFP)", - "details_block": "Výška bloku", + "details_balance_hide": "Skryť zostatok", + "details_balance_show": "Zobraziť zostatok", "details_copy": "Kopírovať", - "details_from": "Vstup", + "details_copy_block_explorer_link": "Skopírovať odkaz prieskumníka blokov", + "details_copy_note": "Skopírovať poznámku", + "details_copy_txid": "Skopírovať ID transakcie", "details_inputs": "Vstupy", "details_outputs": "Výstupy", + "date": "Dátum", "details_received": "Prijaté", - "details_show_in_block_explorer": "Ukázať v block exploreri", + "details_view_in_browser": "Zobraziť v prehliadači", "details_title": "Transakcia", + "incoming_transaction": "Prichádzajúca transakcia", + "outgoing_transaction": "Odchádzajúca transakcia", + "expired_transaction": "Expirovaná transakcia", + "pending_transaction": "Čakajúca transakcia", + "offchain": "off-chain", + "onchain": "on-chain", "details_to": "Výstup", + "enable_offline_signing": "Táto peňaženka sa nepoužíva v spojení s offline podpisovaním. Chceli by ste ho teraz zapnúť?", + "list_conf": "Pot: {number}", "pending": "Čaká...", + "pending_with_amount": "Čaká {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: cca 10 minút", + "eta_3h": "ETA: cca 3 hodiny", + "eta_1d": "ETA: cca 1 deň", "list_title": "transakcie", + "list_title_sent": "Odoslané", + "list_title_received": "Prijaté", + "transaction": "Transakcia", + "open_url_error": "Nemožno otvoriť odkaz v predvolenom prehliadači. Prosím zmeňte predvolený prehliadač a skúste znova.", + "rbf_explain": "Túto transakciu nahradíme takou s vyšším poplatkom, takže bude rýchlejšie vyťažená. Toto sa nazýva RBF — Replace by Fee.", "rbf_title": "Navýšiť poplatok za transakciu (RBF)", "status_bump": "Navýšiť poplatok", "status_cancel": "Zrušiť transakciu", - "transactions_count": "počet transakcií" + "transactions_count": "počet transakcií", + "txid": "ID transakcie", + "updating": "Aktualizuje sa...", + "watchOnlyWarningTitle": "Bezpečnostné upozornenie", + "watchOnlyWarningDescription": "Buďte opatrní pred podvodníkmi, ktorí často používajú peňaženky „iba na sledovanie“ na oklamanie používateľov. Tieto peňaženky vám neumožňujú ovládať ani odosielať prostriedky; umožňujú vám iba zobraziť zostatok.", + "custom_fee_warning_title": "Upozornenie", + "custom_fee_warning_description": "Poplatky pod 1 sat/vB sú platné, ale nemusia byť ďalej šírené kvôli pravidlám uzlov.", + "details_eta_analyzing": "Analyzuje sa...", + "details_sent": "Odoslané", + "details_section": "Detaily", + "details_explorer": "prieskumník", + "details_network_fee": "Sieťový poplatok", + "details_to_address": "Komu", + "details_id": "ID", + "details_note": "Poznámka", + "details_add_note": "pridať", + "details_advanced": "Rozšírené", + "details_fee_rate": "Sadzba poplatku", + "details_size": "Veľkosť", + "details_virtual_size": "Virtuálna veľkosť", + "details_tx_hex": "Hex transakcie", + "details_inputs_count": "Vstupy ({count})", + "details_outputs_count": "Výstupy ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Jednoduchá a výkonná Bitcoinová peňaženka", "add_create": "Vytvoriť", + "total_balance": "Celkový zostatok", + "add_entropy_reset_title": "Resetovať entropiu", + "add_entropy_reset_message": "Zmena typu peňaženky resetuje aktuálnu entropiu. Chcete pokračovať?", + "add_entropy": "Entropia", + "add_entropy_bytes": "{bytes} bytov entropie", "add_entropy_generated": "{gen} bytov vygenerovanej entropie", "add_entropy_provide": "Vytvoriť entropiu pomocou hodov kockou", "add_entropy_remain": "{gen} bytov vygenerovanej entropie. Zvyšných {rem} bytov sa získa zo systémového generátora náhodných čísel.", "add_import_wallet": "Importovať peňaženku", "add_lightning": "Lightning", - "add_lndhub": "Pripojiť sa na LNDHub", + "add_lightning_explain": "Pre platby s okamžitými transakciami", + "add_lndhub": "Pripojiť sa k vášmu LNDhub", + "add_lndhub_error": "Zadaná adresa uzla je neplatný LNDhub uzol.", "add_lndhub_placeholder": "adresa uzla", + "add_placeholder": "moja prvá peňaženka", "add_title": "pridať peňaženku", "add_wallet_name": "názov peňaženky", "add_wallet_type": "typ", + "add_wallet_seed_length": "Dĺžka seed frázy", + "add_wallet_seed_length_12": "12 slov", + "add_wallet_seed_length_24": "24 slov", + "clipboard_bitcoin": "V schránke máte Bitcoinovú adresu. Chcete ju použiť pre transakciu?", + "clipboard_lightning": "V schránke máte faktúru Lightning. Chcete ju použiť pre transakciu?", + "clear_clipboard_on_import": "Vymazať schránku po importe", "details_address": "Adresa", "details_advanced": "Rozšírené", "details_are_you_sure": "Ste si istý?", "details_connected_to": "Napojené na", - "details_del_wb_err": "Napísaný zostatok nesedí so skutočným zostatkom v peňaženke. Skúste znovu", - "details_delete": "Zmazaž", + "details_del_wb_err": "Zadaná čiastka zostatku sa nezhoduje so zostatkom tejto peňaženky. Prosím skúste znova.", + "details_del_wb_q": "Táto peňaženka má zostatok. Pred pokračovaním si uvedomte, že bez seed frázy tejto peňaženky nebudete môcť prostriedky obnoviť. Aby ste predišli náhodnému odstráneniu, prosím zadajte zostatok peňaženky vo výške {balance} satoshi.", + "details_delete": "Zmazať", "details_delete_wallet": "Zmazať peňaženku", - "details_display": "zobraziť v zozname peňaženiek", + "details_derivation_path": "derivačná cesta", + "details_display": "Zobraziť na domovskej obrazovke", "details_export_backup": "Exportovať / zálohovať", + "details_export_history": "Exportovať históriu do CSV", "details_master_fingerprint": "Hlavný odtlačok prsta", - "details_no_cancel": "Nie, zrušiť", - "details_save": "Uložiť", + "details_multisig_type": "multisig", "details_show_xpub": "Ukázať XPUB", + "details_show_addresses": "Zobraziť adresy", "details_title": "Peňaženka", + "wallets": "peňaženky", + "swipe_balance_hide": "Skryť", + "swipe_balance_show": "Zobraziť", + "drag_to_reorder": "Potiahnutím zmeníte poradie", + "clear_search": "Vymazať vyhľadávanie", "details_type": "Typ", - "details_use_with_hardware_wallet": "Použiť s hardwarovou peňaženkou", - "details_yes_delete": "Ano, zmazať", + "details_use_with_hardware_wallet": "Použiť s hardvérovou peňaženkou", + "details_yes_delete": "Áno, zmazať", + "enter_bip38_password": "Zadajte heslo na dešifrovanie", "export_title": "exportovať peňaženku", "import_do_import": "Importovať", + "import_passphrase": "Prístupová fráza", + "import_passphrase_title": "Prístupová fráza", + "import_passphrase_message": "Zadajte prístupovú frázu, ak ste nejakú použili", "import_error": "Chyba pri importe. Prosím uistite sa, že poskytnuté údaje sú správne.", + "import_explanation": "Prosím zadajte vaše seed slová, verejný kľúč, WIF alebo čokoľvek iné, čo máte. BlueWallet sa pokúsi čo najlepšie odhadnúť správny formát a importovať vašu peňaženku.", "import_imported": "Importovaná", "import_scan_qr": "alebo radšej naskenovať QR kód?", "import_success": "Úspech", + "import_success_watchonly": "Vaša peňaženka bola úspešne importovaná. UPOZORNENIE: Toto je peňaženka iba na sledovanie, NEMOŽNO z nej odosielať.", + "import_search_accounts": "Vyhľadať účty", "import_title": "importovať", + "learn_more": "Zistiť viac", + "import_discovery_title": "Objavovanie", + "import_discovery_subtitle": "Vyberte objavenú peňaženku", + "import_discovery_derivation": "Použiť vlastnú derivačnú cestu", + "import_discovery_no_wallets": "Nenašli sa žiadne peňaženky.", + "import_discovery_offline": "BlueWallet je momentálne v offline režime. V tomto režime nemôže overiť existenciu peňaženky, takže budete musieť vybrať správnu manuálne", + "import_derivation_found": "Nájdené", + "import_derivation_found_not": "Nenájdené", + "import_derivation_loading": "Načítava sa...", + "import_derivation_subtitle": "Zadajte vlastnú derivačnú cestu a pokúsime sa objaviť vašu peňaženku.", + "import_derivation_title": "Derivačná cesta", + "import_derivation_unknown": "Neznáme", + "import_wrong_path": "Nesprávna derivačná cesta", "list_create_a_button": "pridať teraz", "list_create_a_wallet": "pridať peňaženku", + "list_create_a_wallet_text": "Je to zadarmo a môžete vytvoriť\ntoľko, koľko chcete.", "list_empty_txs1": "Tu sa zobrazia vaše transakcie,", "list_empty_txs1_lightning": "Lightning peňaženka by sa mala používať na denné transakcie. Sú bleskovo rýchle a poplatky sú neporovnateľne nižšie/žiadne.", + "list_empty_txs2": "Začnite so svojou peňaženkou.", "list_empty_txs2_lightning": "\nKliknite na \"spravovať zostatok\" a naplňte si peňaženku.", "list_latest_transaction": "posledná transakcia", "list_long_choose": "Vyberte fotku", - "list_long_clipboard": "Skopírovať zo schránky", + "paste_from_clipboard": "Vložiť", + "import_file": "Importovať súbor", "list_long_scan": "Skenovať QR kód", "list_title": "peňaženky", "list_tryagain": "Skúste znovu", - "reorder_title": "Zoradiť peňaženky", + "no_ln_wallet_error": "Pred zaplatením faktúry Lightning musíte najprv pridať peňaženku Lightning.", + "looks_like_bip38": "Toto vyzerá ako súkromný kľúč chránený heslom (BIP38).", + "manage_title": "Spravovať peňaženky", + "no_results_found": "Nenašli sa žiadne výsledky.", + "please_continue_scanning": "Prosím pokračujte v skenovaní.", "select_no_bitcoin": "Žiadne Bitcoinové peňaženky nie sú k dispozícii.", + "select_no_bitcoin_exp": "Na doplnenie peňaženiek Lightning je potrebná Bitcoinová peňaženka. Prosím vytvorte alebo importujte jednu.", "select_wallet": "Vyberte peňaženku", - "xpub_copiedToClipboard": "Skopírované do schránky.", - "xpub_title": "XPUB peňaženku" + "pull_to_refresh": "Potiahnutím obnovíte", + "warning_do_not_disclose": "Nikdy nezdieľajte nasledujúce informácie", + "scan_import": "Naskenujte tento QR kód na import vašej peňaženky v inej aplikácii.", + "write_down_header": "Vytvoriť manuálnu zálohu", + "write_down": "Zapíšte si a bezpečne uložte tieto slová. Použite ich na obnovenie peňaženky neskôr.", + "wallet_type_this": "Typ tejto peňaženky je {type}.", + "share_number": "Zdieľať {number}", + "copy_ln_url": "Skopírujte a bezpečne uložte túto URL na obnovenie peňaženky neskôr.", + "copy_ln_public": "Skopírujte a bezpečne uložte tieto informácie na obnovenie peňaženky neskôr.", + "add_ln_wallet_first": "Najprv musíte pridať peňaženku Lightning.", + "identity_pubkey": "Identifikačný verejný kľúč", + "xpub_title": "XPUB peňaženky", + "manage_wallets_search_placeholder": "Hľadať peňaženky, adresy, transakcie a poznámky", + "more_info": "Viac informácií", + "details_delete_wallet_error_message": "Pri potvrdzovaní, či bola táto peňaženka odstránená z notifikácií, nastal problém — mohlo to byť spôsobené sieťovým problémom alebo slabým pripojením. Ak budete pokračovať, môžete naďalej dostávať notifikácie pre transakcie súvisiace s touto peňaženkou, aj po jej zmazaní.", + "details_delete_anyway": "Aj tak zmazať" + }, + "total_balance_view": { + "display_in_bitcoin": "Zobraziť v Bitcoine", + "hide": "Skryť", + "display_in_sats": "Zobraziť v sats", + "display_in_fiat": "Zobraziť v {currency}", + "title": "Celkový zostatok", + "explanation": "Zobraziť celkový zostatok všetkých vašich peňaženiek na obrazovke prehľadu." }, "multisig": { + "multisig_vault": "Multisig trezor", + "default_label": "Multisig trezor", + "multisig_vault_explain": "Najlepšia bezpečnosť pre veľké čiastky", + "provide_signature": "Poskytnúť podpis", + "provide_signature_details": "Použite svoje zariadenie a peňaženku, kde sa nachádza kľúč, na podpísanie tejto transakcie", + "provide_signature_details_bluewallet": "V BlueWallet prejdite do menu obrazovky Poslať a vyberte ", + "provide_signature_next_steps": "Naskenovať alebo importovať podpísanú transakciu", + "provide_signature_next_steps_details": "Keď vaša peňaženka úspešne podpíše transakciu, naskenujte poskytnutý QR kód alebo importujte sprievodný súbor a potom pred odoslaním skontrolujte všetky detaily transakcie.", + "vault_key": "Kľúč trezoru {number}", + "required_keys_out_of_total": "Požadované kľúče z celkového počtu", + "fee": "Poplatok: {number}", + "fee_btc": "{number} BTC", "confirm": "Potvrdiť", "header": "Poslať", - "share": "zdieľať", + "share": "Zdieľať...", "view": "Zobraziť", + "shared_key_detected": "Zdieľaný spolupodpisovateľ", + "shared_key_detected_question": "Bol s vami zdieľaný spolupodpisovateľ, chcete ho importovať?", + "manage_keys": "Spravovať kľúče", + "how_many_signatures_can_bluewallet_make": "koľko podpisov môže BlueWallet vytvoriť", + "signatures_required_to_spend": "Požadované podpisy {number}", + "signatures_we_can_make": "môže vytvoriť {number}", + "scan_or_import_file": "Naskenovať alebo importovať súbor", + "export_coordination_setup": "Exportovať nastavenie koordinácie", + "cosign_this_transaction": "Spolupodpísať túto transakciu?", + "lets_start": "Začnime", "create": "Vytvoriť", + "native_segwit_title": "Najlepšia prax", + "wrapped_segwit_title": "Najlepšia kompatibilita", + "legacy_title": "Legacy", + "co_sign_transaction": "Podpísať transakciu", + "what_is_vault": "Trezor je", + "what_is_vault_numberOfWallets": " {m}-z-{n} multisig ", + "what_is_vault_wallet": "peňaženka.", + "vault_advanced_customize": "Nastavenia trezoru", + "needs": "Potrebuje", + "what_is_vault_description_number_of_vault_keys": " {m} kľúčov trezoru ", + "what_is_vault_description_to_spend": "na minutie a tretí, ktorý\nmôžete použiť ako zálohu.", + "what_is_vault_description_to_spend_other": "na minutie.", + "quorum": "kvórum {m} z {n}", + "quorum_header": "Kvórum", + "of": "z", + "wallet_type": "Typ peňaženky", + "invalid_mnemonics": "Táto mnemonická fráza sa nezdá byť platná.", + "invalid_cosigner": "Neplatné dáta spolupodpisovateľa", + "not_a_multisignature_xpub": "Toto nie je XPUB z multisig peňaženky!", + "invalid_cosigner_format": "Nesprávny spolupodpisovateľ: Toto nie je spolupodpisovateľ pre formát {format}.", "create_new_key": "Vytvoriť novú", + "scan_or_open_file": "Naskenovať alebo otvoriť súbor", + "i_have_mnemonics": "Mám seed frázu pre tento kľúč.", + "type_your_mnemonics": "Vložte seed frázu na import vášho existujúceho kľúča trezoru.", + "this_is_cosigners_xpub": "Toto je XPUB spolupodpisovateľa — pripravený na import do inej peňaženky. Je bezpečné ho zdieľať.", + "this_is_cosigners_xpub_airdrop": "Ak zdieľate cez AirDrop, prijímatelia musia byť na obrazovke koordinácie.", + "wallet_key_created": "Váš kľúč trezoru bol vytvorený. Nájdite si chvíľu na bezpečné zálohovanie vašej mnemonickej seed frázy.", + "are_you_sure_seed_will_be_lost": "Naozaj? Vaša mnemonická seed fráza bude stratená, ak nemáte zálohu.", + "forget_this_seed": "Zabudnúť túto seed frázu a použiť namiesto nej XPUB.", + "view_edit_cosigners": "Zobraziť/upraviť spolupodpisovateľov", + "this_cosigner_is_already_imported": "Tento spolupodpisovateľ je už importovaný.", + "export_signed_psbt": "Exportovať podpísané PSBT", + "input_fp": "Zadajte odtlačok", + "input_fp_explain": "Preskočte pre použitie predvoleného (00000000)", + "input_path": "Vložiť derivačnú cestu", + "input_path_explain": "Preskočte pre použitie predvolenej ({default})", "ms_help": "Pomoc", - "ms_help_title5": "Povoliť rozšírené nastavenia" + "ms_help_title": "Ako fungujú multisig trezory: Tipy a triky", + "ms_help_text": "Peňaženka s viacerými kľúčmi, pre zvýšenú bezpečnosť alebo zdieľanú úschovu", + "ms_help_title1": "Odporúča sa použitie viacerých zariadení.", + "ms_help_1": "Trezor bude fungovať s inými aplikáciami BlueWallet a peňaženkami kompatibilnými s PSBT, ako sú Electrum, Specter, Coldcard, Cobo Vault atď.", + "ms_help_title2": "Úprava kľúčov", + "ms_help_2": "Všetky kľúče trezoru môžete vytvoriť v tomto zariadení a neskôr ich odstrániť alebo upraviť. Mať všetky kľúče na tom istom zariadení má rovnocennú bezpečnosť ako bežná Bitcoinová peňaženka.", + "ms_help_title3": "Zálohy trezoru", + "ms_help_3": "V možnostiach peňaženky nájdete zálohu trezoru a zálohu iba na sledovanie. Táto záloha je ako mapa k vašej peňaženke. Je nevyhnutná na obnovenie peňaženky v prípade, že stratíte jednu zo svojich seed fráz.", + "ms_help_title4": "Import trezorov", + "ms_help_4": "Na import multisig použite svoj záložný súbor a funkciu Import. Ak máte iba seed frázy a XPUB-y, môžete použiť tlačidlo individuálneho importu pri vytváraní kľúčov trezoru.", + "ms_help_title5": "Povoliť rozšírené nastavenia", + "ms_help_5": "BlueWallet predvolene vygeneruje 2-z-3 trezor. Na vytvorenie iného kvóra alebo zmenu typu adresy aktivujte v nastaveniach Rozšírený režim." }, "is_it_my_address": { - "title": "Je to moja adresa?" + "title": "Je to moja adresa?", + "owns": "{label} vlastní {address}", + "enter_address": "Zadajte adresu", + "check_address": "Skontrolovať adresu", + "no_wallet_owns_address": "Žiadna z dostupných peňaženiek nevlastní zadanú adresu.", + "view_qrcode": "Zobraziť QR kód" + }, + "autofill_word": { + "title": "Posledné slovo seed frázy", + "enter": "Zadajte čiastočnú mnemonickú frázu", + "generate_word": "Vygenerovať posledné slovo", + "error": "Vstup nie je 11- alebo 23-slovná čiastočná mnemonická fráza. Prosím skúste znova." + }, + "cc": { + "change": "Drobné", + "coins_selected": "Vybraných mincí ({number})", + "selected_summ": "{value} vybrané", + "empty": "Táto peňaženka momentálne nemá žiadne mince.", + "freeze": "Zmraziť", + "freezeLabel": "Zmraziť", + "freezeLabel_un": "Rozmraziť", + "header": "Správa UTXO", + "use_coin": "Použiť mincu", + "use_coins": "Použiť mince", + "tip": "Táto funkcia vám umožňuje vidieť, označovať, zmrazovať alebo vyberať mince pre lepšiu správu peňaženky. Viac mincí môžete vybrať ťuknutím na farebné krúžky.", + "sort_asc": "Vzostupne", + "sort_desc": "Zostupne", + "sort_height": "Výška", + "sort_value": "Hodnota", + "sort_label": "Popis", + "sort_status": "Stav", + "sort_by": "Zoradiť podľa" }, "units": { + "BTC": "BTC", + "MAX": "Max", + "sat_vbyte": "sat/vByte", "sats": "sats" }, "addresses": { + "copy_private_key": "Skopírovať súkromný kľúč", + "sensitive_private_key": "Upozornenie: súkromné kľúče sú extrémne citlivé. Pokračovať?", + "sign_title": "Podpísať/Overiť správu", + "sign_help": "Tu môžete vytvoriť alebo overiť kryptografický podpis na základe Bitcoinovej adresy.", + "sign_sign": "Podpísať", + "sign_verify": "Overiť", + "sign_signature_correct": "Overenie úspešné!", + "sign_signature_incorrect": "Overenie zlyhalo!", "sign_placeholder_address": "adresa", + "sign_placeholder_message": "Správa", + "sign_placeholder_signature": "Podpis", + "addresses_title": "Adresy", + "type_change": "Drobné", "type_receive": "Prijať", + "type_used": "Použité", "transactions": "transakcie" + }, + "lnurl_auth": { + "register_question_part_1": "Chceli by ste si zaregistrovať účet na", + "register_question_part_2": "pomocou vašej peňaženky Lightning?", + "register_answer": "Úspešne ste si zaregistrovali účet na {hostname}!", + "login_question_part_1": "Chceli by ste sa prihlásiť na", + "login_question_part_2": "pomocou vašej peňaženky Lightning?", + "login_answer": "Úspešne ste sa prihlásili na {hostname}!", + "link_question_part_1": "Chceli by ste prepojiť svoj účet na", + "link_question_part_2": "s vašou peňaženkou Lightning?", + "link_answer": "Vaša peňaženka Lightning bola úspešne prepojená s vaším účtom na {hostname}!", + "auth_question_part_1": "Chceli by ste byť overený na", + "auth_question_part_2": "pomocou vašej peňaženky Lightning?", + "auth_answer": "Úspešne ste boli overený na {hostname}!", + "could_not_auth": "Nepodarilo sa nám overiť vás na {hostname}.", + "authenticate": "Overiť" + }, + "bip47": { + "payment_code": "Platobný kód", + "contacts": "Kontakty", + "bip47_explain": "Opakovateľný a zdieľateľný kód", + "bip47_explain_subtitle": "BIP47", + "purpose": "Opakovateľný a zdieľateľný kód (BIP47)", + "pay_this_contact": "Zaplatiť tomuto kontaktu", + "rename_contact": "Premenovať kontakt", + "copy_payment_code": "Skopírovať platobný kód", + "hide_contact": "Skryť kontakt", + "rename": "Premenovať", + "provide_name": "Zadajte nový názov pre tento kontakt", + "add_contact": "Pridať kontakt", + "provide_payment_code": "Zadajte platobný kód", + "invalid_pc": "Neplatný platobný kód", + "notification_tx_unconfirmed": "Oznamovacia transakcia ešte nie je potvrdená, prosím čakajte", + "failed_create_notif_tx": "Nepodarilo sa vytvoriť on-chain transakciu", + "onchain_tx_needed": "Je potrebná on-chain transakcia", + "notif_tx_sent": "Oznamovacia transakcia bola odoslaná. Prosím počkajte na jej potvrdenie", + "notif_tx": "Oznamovacia transakcia", + "not_found": "Platobný kód nenájdený" } } diff --git a/loc/sl_SI.json b/loc/sl_SI.json index 350a3033115..900fec64df0 100644 --- a/loc/sl_SI.json +++ b/loc/sl_SI.json @@ -1,41 +1,51 @@ { "_": { - "bad_password": "Napačno geslo. Prosimo poskusite ponovno.", + "bad_password": "Napačno geslo. Prosimo, poskusite ponovno.", "cancel": "Prekliči", "continue": "Nadaljuj", "clipboard": "Odložišče", + "copied": "Kopirano!", + "discard_changes": "Zavrzi spremembe?", + "discard_changes_explain": "Imate neshranjene spremembe. Ali jih res želite zavreči in zapustiti zaslon?", "enter_password": "Vnesite geslo", + "enter_url": "Vnesite URL", + "save": "Shrani...", + "close": "Zapri", + "change_input_currency": "Spremeni vhodno valuto", + "refresh": "Osveži", + "pick_image": "Izberi iz knjižnice", + "pick_file": "Izberi datoteko", + "enter_amount": "Vnesite znesek", + "qr_custom_input_button": "Tapnite 10-krat za vnos po meri", + "unlock": "Odkleni", + "port": "Vrata", + "ssl_port": "SSL vrata", + "suggested": "Predlagano", "never": "Nikoli", - "disabled": "Onemogoči", "of": "{number} od {total}", "ok": "OK", "storage_is_encrypted": "Shramba je šifrirana. Za dešifriranje je potrebno geslo.", "yes": "Da", "no": "Ne", - "save": "Shrani", "seed": "Seme", "success": "Uspešno", - "wallet_key": "Ključ denarnice", - "invalid_animated_qr_code_fragment": "Neveljaven del animirane QR kode. Prosimo poskusite ponovno.", - "file_saved": "Datoteka {filePath} je bila shranjena v {destination}.", - "downloads_folder": "Mapa Prenosi" - }, - "alert": { - "default": "Opozorilo" + "wallet_key": "Ključ denarnice" }, "azteco": { "codeIs": "Koda vašega bona je", - "errorBeforeRefeem": "Pred unovčitvijo, morate dodati Bitcoin denarnico.", + "errorBeforeRefeem": "Pred unovčitvijo morate dodati Bitcoin denarnico.", "errorSomething": "Nekaj je šlo narobe. Ali je ta bon še vedno veljaven?", "redeem": "Unovčite v denarnici", "redeemButton": "Unovčite", "success": "Uspešno", + "successMessage": "Bon je bil uspešno unovčen! Vaša sredstva bi se morala kmalu pojaviti v vaši Bitcoin denarnici.", "title": "Unovčite Azte.co bon" }, "entropy": { "save": "Shrani", "title": "Entropija", - "undo": "Razveljavi" + "undo": "Razveljavi", + "amountOfEntropy": "{bits} od {limit} bitov" }, "errors": { "broadcast": "Objava v omrežje neuspešna", @@ -43,66 +53,46 @@ "network": "Omrežna napaka" }, "lnd": { - "active": "Aktivno", - "inactive": "Neaktivno", - "channels": "Kanali", - "no_channels": "Ni odprtih kanalov", - "close_channel": "Zapri kanal", - "new_channel": "Nov kanal", - "errorInvoiceExpired": "Račun je potekel", - "force_close_channel": "Prisilno zapri kanal?", "expired": "Potekel", - "node_alias": "Vzdevek vozlišča", "expiresIn": "Poteče čez {time} min", + "errorInvoiceExpired": "Račun je potekel.", "payButton": "Plačaj", - "placeholder": "Račun", - "open_channel": "Odpri kanal", - "funding_amount_placeholder": "Znesek sredstev, na primer 0.001", - "opening_channnel_for_from": "Odpiranje kanala denarnice {forWalletLabel}, z uporabo sredstev iz {fromWalletLabel}", - "are_you_sure_open_channel": "Ali ste prepričani, da želite odpreti ta kanal?", + "payment": "Plačilo", + "placeholder": "Račun ali naslov", "potentialFee": "Morebitna omrežnina: {fee}", - "remote_host": "Oddaljeni gostitelj", "refill": "Napolni", - "reconnect_peer": "Ponovna vzpostavitev povezave", "refill_create": "Če želite nadaljevati, ustvarite Bitcoin denarnico, s katero boste napolnili.", "refill_external": "Napolni z zunanjo denarnico", "refill_lnd_balance": "Napolni stanje Lightning denarnice", "sameWalletAsInvoiceError": "Plačilo računa ni mogoče z denarnico, s katero je bil ustvarjen.", - "title": "Uredi sredstva", - "can_send": "Mogoče poslati", - "can_receive": "Mogoče prejeti", - "view_logs": "Prikaži dnevnik" + "title": "Uredi sredstva" }, "lndViewInvoice": { "additional_info": "Dodatne Informacije", "for": "Za:", "lightning_invoice": "Lightning Račun", - "open_direct_channel": "Odpri neposreden kanal s tem vozliščem:", - "please_pay_between_and": "Prosim plačajte med {min} in {max}", - "please_pay": "Prosim plačajte", - "preimage": "Preimage", + "preimage": "Predslika", "sats": "sats.", + "date_time": "Datum in čas", + "please_pay_between_and": "Prosim, plačajte med {min} in {max}", + "please_pay": "Prosim, plačajte", "wasnt_paid_and_expired": "Ta račun ni bil plačan in je potekel." }, "plausibledeniability": { "create_fake_storage": "Ustvari šifrirano shrambo", - "create_password": "Ustvarite geslo", "create_password_explanation": "Geslo za lažno shrambo se ne sme ujemati z geslom glavne shrambe.", "help": "V določenih okoliščinah boste morda prisiljeni razkriti geslo. Da zavarujete vaša sredstva, lahko BlueWallet ustvari dodatno šifrirano shrambo z drugačnim geslom. Pod prisilo, lahko to geslo razkrijete tretji osebi. Če ga vnesete v BlueWallet, se bo odklenila 'lažna' shramba. To se bo tretji osebi zdelo verodostojno, vaša skrivna glavna shramba s pravimi sredstvi pa bo ostala varna.", "help2": "Nova shramba bo popolnoma uporabna, za večjo verodostojnost lahko tam hranite manjši znesek.", "password_should_not_match": "Geslo je trenutno v uporabi. Prosimo, poskusite z drugim geslom.", - "passwords_do_not_match": "Gesli se ne ujemata, prosimo poskusite ponovno.", - "retype_password": "Ponovno vpišite geslo", - "success": "Uspešno", "title": "Verodostojno Zanikanje" }, "pleasebackup": { "ask": "Ali ste shranili varnostno kopijo (seznam besed) vaše denarnice? Varnostna kopija je potrebna za dostop do vaših sredstev v primeru izgube naprave. Brez varnostne kopije bodo vaša sredstva trajno izgubljena.", - "ask_no": "Ne, nisem", - "ask_yes": "Da, sem", - "ok": "V redu, sem si zapisal", - "ok_lnd": "V redu, sem shranil", - "text": "Prosimo zapišite si seznam besed (mnemonično seme) na list papirja.\nTo je varnostna kopija, ki jo lahko uporabite za obnovitev denarnice.", + "ask_no": "Ne, še nisem.", + "ask_yes": "Da, sem.", + "ok": "V redu, sem si zapisal.", + "ok_lnd": "V redu, sem shranil.", + "text": "Prosimo, zapišite si seznam besed (mnemonično seme) na list papirja.\nTo je varnostna kopija, ki jo lahko uporabite za obnovitev denarnice.", "text_lnd": "Shranite varnostno kopijo te denarnice. Omogoča vam obnovitev denarnice v primeru izgube te naprave.", "title": "Vaša denarnica je ustvarjena" }, @@ -110,7 +100,11 @@ "details_create": "Ustvari", "details_label": "Opis", "details_setAmount": "Prejmi znesek", - "details_share": "deli", + "details_share": "Deli ...", + "address_not_found": "Prejemnega naslova ni mogoče ustvariti.", + "reset": "Ponastavi", + "qrcode_for_the_address": "QR koda za naslov", + "bip47_explanation": "Plačilne kode so univerzalni naslov, ki ne razkriva naslovov vaše denarnice. Vse storitve jih ne podpirajo.", "header": "Prejmi", "maxSats": "Največji znesek je {max} sats", "maxSatsFull": "Največji znesek je {max} sats ali {currency}", @@ -152,7 +146,6 @@ "details_create": "Ustvari Račun", "details_error_decode": "Ni mogoče dekodirati Bitcoin naslova", "details_fee_field_is_not_valid": "Omrežnina ni veljavna", - "details_frozen": "{amount} BTC zamrznjeno", "details_next": "Naprej", "details_no_signed_tx": "Izbrana datoteka ne vsebuje transakcije, ki jo je mogoče uvoziti.", "details_note_placeholder": "lastna opomba", @@ -161,15 +154,15 @@ "details_total_exceeds_balance": "Znesek presega razpoložljivo stanje.", "details_total_exceeds_balance_frozen": "Znesek presega razpoložljivo stanje. Upoštevajte, da zamrznjeni kovanci niso vključeni.", "details_unrecognized_file_format": "Neprepoznana oblika datoteke", - "details_wallet_before_tx": "Pred ustvarjanjem transakcije, morate dodati Bitcoin denarnico.", + "details_wallet_before_tx": "Pred ustvarjanjem transakcije morate dodati Bitcoin denarnico.", "dynamic_init": "Inicializacija", "dynamic_next": "Naprej", "dynamic_prev": "Nazaj", "dynamic_start": "Začni", "dynamic_stop": "Ustavi", - "fee_10m": "10m", - "fee_1d": "1d", - "fee_3h": "3h", + "fee_10m": "10 min", + "fee_1d": "1 d", + "fee_3h": "3 h", "fee_custom": "Po meri", "fee_fast": "Hitro", "fee_medium": "Srednje", @@ -184,8 +177,6 @@ "permission_camera_message": "Za uporabo kamere potrebujemo dovoljenje.", "psbt_sign": "Podpiši transakcijo", "open_settings": "Odpri Nastavitve", - "permission_storage_later": "Vprašaj me kasneje", - "permission_storage_message": "BlueWallet potrebuje dovoljenje za dostop do vaše shrambe, da shrani to datoteko.", "permission_storage_denied_message": "BlueWallet ne more shraniti te datoteke. Odprite nastavitve naprave in omogočite dovoljenje za shranjevanje.", "permission_storage_title": "Dovoljenje za dostop do shrambe", "psbt_clipboard": "Kopiraj v odložišče", @@ -195,18 +186,33 @@ "outdated_rate": "Tečaj posodobljen: {date}", "psbt_tx_open": "Odpri podpisano transakcijo", "psbt_tx_scan": "Skeniraj podpisano transakcijo", - "qr_error_no_qrcode": "Na izbrani sliki ni bilo mogoče najti QR kode. Prepričajte se, da slika vsebuje samo QR kodo brez dodatne vsebine, kot je besedilo ali gumbi.", "reset_amount": "Ponastavi znesek", "reset_amount_confirm": "Ali želite ponastaviti znesek?", "success_done": "Končano", - "txSaved": "Transakcijska datoteka ({filePath}) je bila shranjena v mapo Prenosi.", - "problem_with_psbt": "Težava s PSBT" + "problem_with_psbt": "Težava s PSBT", + "details_insert_contact": "Vstavi stik", + "details_add_recc_rem_all_alert_description": "Ali ste prepričani, da želite odstraniti vse prejemnike?", + "details_add_rec_rem_all": "Odstrani vse prejemnike", + "details_recipients_title": "Prejemniki", + "details_recipient_title": "Prejemnik #{number} od #{total}", + "please_complete_recipient_title": "Nepopoln prejemnik", + "please_complete_recipient_details": "Pred dodajanjem novega prejemnika izpolnite podrobnosti prejemnika #{number}.", + "details_frozen": "{amount} BTC je zamrznjenih.", + "details_scan_error": "Napaka pri skeniranju", + "insert_custom_fee": "Vnesite omrežnino", + "invalid_psbt": "Posredovan je bil neveljaven PSBT.", + "qr_error_no_qrcode": "V izbrani sliki nismo mogli najti veljavne QR kode. Prepričajte se, da slika vsebuje le QR kodo in nobene dodatne vsebine, kot je besedilo ali gumbi.", + "txSaved": "Datoteka s transakcijo ({filePath}) je bila shranjena.", + "file_saved_at_path": "Datoteka ({filePath}) je bila shranjena.", + "cant_send_to_silentpayment_adress": "Ta denarnica ne more pošiljati na naslove Silent Payments", + "cant_send_to_bip47": "Ta denarnica ne more pošiljati na plačilne kode BIP47", + "cant_find_bip47_notification": "To plačilno kodo najprej dodajte med stike" }, "settings": { "about": "O aplikaciji", "about_awesome": "Zgrajeno z izjemnimi", "about_backup": "Vedno naredite varnostno kopijo vaših ključev!", - "about_free": "BlueWallet je brezplačen in odprtokodni projekt. Ustvarili uporabniki Bitcoina.", + "about_free": "BlueWallet je brezplačen in odprtokodni projekt. Ustvarili so ga uporabniki Bitcoina.", "about_license": "Licenca MIT", "about_release_notes": "Opombe ob izdaji", "about_review": "Ocenite, napišite mnenje", @@ -214,25 +220,16 @@ "about_selftest_electrum_disabled": "Samotestiranje ni na voljo v Electrum načinu brez povezave. Onemogočite način brez povezave in poskusite ponovno.", "about_selftest_ok": "Vsi opravljeni testi so bili uspešni. Denarnica deluje brez napak.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord Strežnik", "about_sm_telegram": "Telegram kanal", - "about_sm_twitter": "Sledite nam na Twitterju", - "advanced_options": "Napredne Možnosti", "biometrics": "Biometrija", "biom_10times": "Geslo ste poskušali vnesti 10-krat. Ali želite ponastaviti shrambo? S tem bodo odstranjene vse denarnice.", "biom_conf_identity": "Prosimo, potrdite svojo identiteto.", - "biom_no_passcode": "Vaša naprava ni zaščitena z geslom. Če želite nadaljevati, v aplikaciji Nastavitve nastavite svoje geslo.", "biom_remove_decrypt": "Shramba bo ponastavljena, pri tem bodo odstranjene vse denarnice. Ali ste prepričani, da želite nadaljevati?", "currency": "Valuta", - "currency_source": "Tečaji so pridobljeni iz", "currency_fetch_error": "Pri pridobivanju tečaja za izbrano valuto je prišlo do napake.", - "default_desc": "Ko je onemogočeno, bo BlueWallet ob zagonu takoj odprl izbrano denarnico.", - "default_info": "Privzeto v", "default_title": "Ob zagonu", - "default_wallets": "Prikaži vse denarnice", "electrum_connected": "Povezano", "electrum_connected_not": "Brez povezave", - "electrum_error_connect": "Povezave s podanim Electrum strežnikom ni mogoče vzpostaviti", "lndhub_uri": "Npr. {example}", "electrum_host": "Npr. {example}", "electrum_offline_mode": "Način brez povezave", @@ -241,32 +238,16 @@ "use_ssl": "SSL", "electrum_saved": "Spremembe so bile uspešno shranjene. Da bodo spremembe začele veljati, bo morda potreben ponovni zagon.", "set_electrum_server_as_default": "Želite nastaviti {server} kot privzeti electrum strežnik?", - "set_lndhub_as_default": "Želite nastaviti {url} kot privzeti LNDHub strežnik?", "electrum_settings_server": "Electrum Strežnik", - "electrum_settings_explain": "Pustite prazno za uporabo privzetih nastavitev.", "electrum_status": "Stanje", - "electrum_clear_alert_title": "Počisti zgodovino?", - "electrum_clear_alert_message": "Ali želite počistiti zgodovino electrum strežnikov?", - "electrum_clear_alert_cancel": "Prekliči", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Izberi", - "electrum_reset": "Ponastavi na privzeto", "electrum_unable_to_connect": "Povezave z {server} ni mogoče vzpostaviti.", - "electrum_history": "Zgodovina strežnikov", - "electrum_reset_to_default": "Ali ste prepričani, da želite ponastaviti nastavitve Electrum strežnika na privzeto?", - "electrum_clear": "Počisti", - "tor_supported": "Tor podprt", - "tor_unsupported": "Povezava preko Tor omrežja ni podprta.", + "electrum_reset": "Ponastavi na privzeto", "encrypt_decrypt": "Dešifriraj Shrambo", "encrypt_decrypt_q": "Ali ste prepričani, da želite dešifrirati shrambo? To bo omogočilo dostop do vaših denarnic brez gesla.", - "encrypt_enc_and_pass": "Šifrirano in zaščiteno z geslom", "encrypt_title": "Varnost", "encrypt_tstorage": "Shramba", "encrypt_use": "Uporabi {type}", - "encrypt_use_expl": "{type} bo uporabljen za potrditev vaše identitete pred izvedbo transakcije, odklepanjem, izvozom ali brisanjem denarnice. {type} ne bo uporabljen za odklepanje šifrirane shrambe.", "general": "Splošno", - "general_adv_mode": "Napredni način", - "general_adv_mode_e": "Ko je omogočen, boste videli napredne možnosti, kot so različni tipi denarnic, možnost določitve LNDHub strežnika, s katerim se želite povezati in entropija po meri pri ustvarjanju denarnice.", "general_continuity": "Neprekinjenost", "general_continuity_e": "Ko je omogočeno, si boste lahko ogledali izbrane denarnice in transakcije z uporabo vaših drugih naprav povezanih z Apple iCloud.", "groundcontrol_explanation": "GroundControl je brezplačen odprtokoden strežnik potisnih obvestil za bitcoin denarnice. Da se ne zanašate na BlueWallet infrastrukturo, lahko namestite svoj strežnik GroundControl in tukaj dodate njegov URL. Pustite prazno, za uporabo privzetega.", @@ -274,11 +255,8 @@ "language": "Jezik", "last_updated": "Posodobljeno", "language_isRTL": "Za spremembo orientacije pisave je potreben ponovni zagon aplikacije.", - "lightning_error_lndhub_uri": "Neveljaven LNDHub URI", "lightning_saved": "Spremembe so bile uspešno shranjene", "lightning_settings": "Lightning Nastavitve", - "tor_settings": "Tor Nastavitve", - "lightning_settings_explain": "Za povezavo z lastnim LND vozliščem, prosimo namestite LndHub in tukaj vnesite URL vozlišča. Pustite prazno za uporabo BlueWallet LNDHub . Z novim LNDHub-om bodo povezane samo denarnice ustvarjene po potrditvi sprememb.", "network": "Omrežje", "network_broadcast": "Objavi transakcijo", "network_electrum": "Electrum Strežnik", @@ -286,40 +264,75 @@ "notifications": "Obvestila", "open_link_in_explorer": "Odpri v raziskovalcu blokov", "password": "Geslo", - "password_explain": "Ustvarite geslo za dešifriranje shrambe", - "passwords_do_not_match": "Gesli se ne ujemata", "plausible_deniability": "Verodostojno zanikanje", "privacy": "Zasebnost", "privacy_read_clipboard": "Branje odložišča", "privacy_system_settings": "Sistemske nastavitve", "privacy_quickactions": "Bližnjice", - "privacy_quickactions_explanation": "Za ogled stanja denarnice se dotaknite in pridržite ikono aplikacije BlueWallet na domačem zaslonu.", "privacy_clipboard_explanation": "Prikaži bližnjice, če je v odložišču najden naslov ali račun.", "privacy_do_not_track": "Onemogoči analitiko", "privacy_do_not_track_explanation": "Informacije o zmogljivosti in zanesljivosti ne bodo poslane v analizo.", - "push_notifications": "Potisna obvestila", "rate": "Tečaj", - "retype_password": "Ponovno vpišite geslo", "selfTest": "Samotestiranje", "save": "Shrani", "saved": "Shranjeno", - "success_transaction_broadcasted": "Vaša transakcija je bila objavljena!", "total_balance": "Skupno stanje", "total_balance_explanation": "Prikaži skupno stanje vseh denarnic na pripomočkih na domačem zaslonu.", "widgets": "Pripomočki", - "tools": "Orodja" + "tools": "Orodja", + "performance_score": "Ocena zmogljivosti: {num}", + "run_performance_test": "Preizkusi zmogljivost", + "block_explorer_invalid_custom_url": "Vneseni URL je neveljaven. Vnesite veljaven URL, ki se začne s http:// ali https://.", + "privacy_temporary_screenshots": "Dovoli zajem zaslona", + "privacy_temporary_screenshots_instructions": "Zaščita zajema zaslona bo začasno izklopljena, kar omogoča posnetke zaslona in snemanje zaslona. Zaščita se bo samodejno znova vklopila, ko zaprete in ponovno odprete BlueWallet.", + "biometrics_no_longer_available": "Nastavitve vaše naprave so se spremenile in se ne ujemajo več z izbranimi varnostnimi nastavitvami v aplikaciji. Ponovno omogočite biometrijo ali kodo za dostop, nato ponovno zaženite aplikacijo, da uveljavite te spremembe.", + "biom_no_passcode": "Vaša naprava nima omogočene kode za dostop ali biometrije. Za nadaljevanje v sistemskih nastavitvah konfigurirajte kodo za dostop ali biometrijo.", + "currency_source": "Tečaj je pridobljen iz", + "donate": "Doniraj", + "donate_description": "Pomagajte nam ohranjati Blue brezplačen!", + "electrum_error_connect": "Povezave z navedenim Electrum strežnikom ni mogoče vzpostaviti", + "electrum_error_connect_tor": "Povezave z navedenim Electrum strežnikom ni mogoče vzpostaviti. Prepričajte se, da je aplikacija Orbot povezana, in poskusite ponovno.", + "set_lndhub_as_default": "Želite nastaviti {url} kot privzeti LNDhub strežnik?", + "electrum_preferred_server": "Prednostni strežnik", + "electrum_preferred_server_description": "Vnesite strežnik, ki naj ga vaša denarnica uporablja za vse Bitcoin dejavnosti. Po nastavitvi bo vaša denarnica izključno uporabljala ta strežnik za preverjanje stanj, pošiljanje transakcij in pridobivanje omrežnih podatkov. Pred nastavitvijo se prepričajte, da temu strežniku zaupate.", + "electrum_history": "Zgodovina", + "electrum_reset_to_default": "S tem bo BlueWallet naključno izbral strežnik s seznama strežnikov.", + "electrum_reset_to_default_and_clear_history": "Ponastavi na privzeto in počisti zgodovino", + "encrypt_enc_and_pass": "Zaščiteno z geslom", + "encrypt_storage_explanation_headline": "Omogoči šifriranje shrambe", + "encrypt_storage_explanation_description_line1": "Omogočanje šifriranja shrambe doda dodatno raven zaščite vaši aplikaciji s tem, da zavaruje način, kako so vaši podatki shranjeni v napravi. To otežuje dostop do vaših informacij brez dovoljenja.", + "encrypt_storage_explanation_description_line2": "Vendar je pomembno vedeti, da to šifriranje ščiti le dostop do vaših denarnic, shranjenih v ključavnici naprave. Same denarnice ne zaščiti z geslom ali drugo dodatno zaščito.", + "i_understand": "Razumem", + "block_explorer": "Raziskovalec blokov", + "block_explorer_preferred": "Uporabi prednostnega raziskovalca blokov", + "block_explorer_error_saving_custom": "Napaka pri shranjevanju prednostnega raziskovalca blokov", + "set_as_preferred": "Nastavi kot prednostno", + "set_as_preferred_electrum": "Nastavitev {host}:{port} kot prednostnega strežnika bo onemogočila naključno povezavo s predlaganim strežnikom.", + "encrypted_feature_disabled": "Te funkcije ni mogoče uporabljati, ko je omogočena šifrirana shramba.", + "encrypt_use_expl": "{type} bo uporabljen za potrditev vaše identitete pred izvedbo transakcije, odklepanjem, izvozom ali brisanjem denarnice.", + "biometrics_fail": "Če {type} ni omogočen ali odklep ne uspe, lahko kot alternativo uporabite kodo za dostop naprave.", + "license": "Licenca", + "lightning_error_lndhub_uri": "Neveljaven LNDhub URI", + "lightning_error_lndhub_uri_tor": "Neveljaven LNDhub URI. Prepričajte se, da je aplikacija Orbot povezana, in poskusite ponovno.", + "lightning_settings_explain": "Za povezavo z lastnim LND vozliščem namestite LNDhub in tukaj v nastavitvah vnesite njegov URL. Upoštevajte, da se bodo z navedenim LNDhubom povezovale le denarnice, ustvarjene po shranjevanju sprememb.", + "lndhub_github": "GitHub repozitorij", + "electrum_suggested_description": "Kadar prednostni strežnik ni nastavljen, bo za uporabo naključno izbran predlagani strežnik.", + "password_explain": "Vnesite geslo, ki ga boste uporabljali za odklepanje shrambe.", + "privacy_quickactions_explanation": "Dotaknite se ikone aplikacije BlueWallet in jo zadržite, da si hitro ogledate stanje denarnice.", + "push_notifications_explanation": "Z omogočanjem obvestil bo žeton vaše naprave poslan na strežnik, skupaj z naslovi denarnic in ID-ji transakcij za vse denarnice in transakcije, izvedene po omogočanju obvestil. Žeton naprave se uporablja za pošiljanje obvestil, podatki o denarnici pa nam omogočajo, da vas obvestimo o prejetih Bitcoinih ali potrditvah transakcij.\n\nPrenašajo se samo informacije od trenutka, ko omogočite obvestila — ničesar od prej se ne zbira.\n\nOnemogočanje obvestil bo iz strežnika odstranilo vse te informacije. Dodatno, brisanje denarnice iz aplikacije bo iz strežnika odstranilo tudi pripadajoče informacije.", + "success_transaction_broadcasted": "Vaša transakcija je bila uspešno objavljena v omrežju!" }, "notifications": { "would_you_like_to_receive_notifications": "Želite prikaz obvestil ob prejemu plačila?", - "no_and_dont_ask": "Ne in ne sprašuj več", - "ask_me_later": "Vprašaj me kasneje" + "notifications_subtitle": "Prejeta plačila in potrditve transakcij", + "no_and_dont_ask": "Ne in me ne sprašujte več.", + "permission_denied_message": "Zavrnili ste dovoljenje za pošiljanje obvestil. Če želite prejemati obvestila, jih omogočite v nastavitvah naprave." }, "transactions": { "cancel_explain": "To transakcijo bomo nadomestili z novo, ki plača višjo omrežnino. Trenutna transakcija bo preklicana (RBF - Replace By Fee).", "cancel_no": "Ta transakcija ni zamenljiva.", "cancel_title": "Prekliči to transakcijo (RBF)", "confirmations_lowercase": "potrditev: {confirmations}", - "copy_link": "Kopiraj povezavo", "expand_note": "Prikaži opis", "cpfp_create": "Ustvari", "cpfp_exp": "Ustvarjena bo nova transakcija, ki porabi vašo nepotrjeno transakcijo. Skupna omrežnina bo višja od omrežnine prvotne transakcije, zato bi morala biti potrditev hitrejša. To se imenuje CPFP - Child Pays For Parent.", @@ -327,19 +340,15 @@ "cpfp_title": "Povečaj omrežnino (CPFP)", "details_balance_hide": "Skrij Stanje", "details_balance_show": "Prikaži Stanje", - "details_block": "Višina Bloka", "details_copy": "Kopiraj", - "details_copy_amount": "Kopiraj znesek", "details_copy_block_explorer_link": "Kopiraj povezavo razisk. blokov", "details_copy_note": "Kopiraj opis", "details_copy_txid": "Kopiraj ID transakcije", - "details_from": "Vhod", + "details_id": "ID", "details_inputs": "Vhodi", "details_outputs": "Izhodi", "date": "Datum", "details_received": "Prejeto", - "transaction_note_saved": "Opomba transakcije je bila uspešno shranjena.", - "details_show_in_block_explorer": "Prikaži v raziskovalcu blokov", "details_title": "Transakcija", "details_to": "Izhod", "enable_offline_signing": "Ta denarnica se ne uporablja skupaj s podpisovanjem brez povezave (offline). Ali ga želite omogočiti?", @@ -351,6 +360,8 @@ "eta_3h": "ETA: Čez ~3 ure", "eta_1d": "ETA: Čez ~1 dan", "list_title": "Transakcije", + "list_title_received": "Prejeto", + "transaction": "Transakcija", "open_url_error": "Povezave ni mogoče odpreti s privzetim brskalnikom. Spremenite privzeti brskalnik in poskusite ponovno.", "rbf_explain": "To transakcijo bomo nadomestili z novo, ki plača višjo omrežnino, zato bo potrditev hitrejša. (RBF - Replace By Fee)", "rbf_title": "Povečaj omrežnino (RBF)", @@ -358,50 +369,74 @@ "status_cancel": "Prekliči transakcijo", "transactions_count": "Število transakcij", "txid": "ID transakcije", - "updating": "Osveževanje..." + "updating": "Osveževanje...", + "transaction_loading_error": "Pri nalaganju transakcije je prišlo do težave. Poskusite znova kasneje.", + "transaction_not_available": "Transakcija ni na voljo", + "details_view_in_browser": "Odpri v brskalniku", + "incoming_transaction": "Prejeta transakcija", + "outgoing_transaction": "Poslana transakcija", + "expired_transaction": "Potekla transakcija", + "pending_transaction": "Transakcija v teku", + "offchain": "off-chain", + "onchain": "on-chain", + "list_title_sent": "Poslano", + "watchOnlyWarningTitle": "Varnostno opozorilo", + "watchOnlyWarningDescription": "Bodite previdni pred prevaranti, ki pogosto uporabljajo opazovalne denarnice za zavajanje uporabnikov. Te denarnice ne omogočajo nadzora ali pošiljanja sredstev; omogočajo le ogled stanja.", + "custom_fee_warning_title": "Opozorilo", + "custom_fee_warning_description": "Omrežnine pod 1 sat/vByte so veljavne, vendar zaradi pravilnikov vozlišč morda ne bodo posredovane.", + "details_eta_analyzing": "Analiziranje ...", + "details_sent": "Poslano", + "details_section": "Podrobnosti", + "details_explorer": "raziskovalec", + "details_network_fee": "Omrežnina", + "details_to_address": "Za", + "details_note": "Opomba", + "details_add_note": "dodaj", + "details_advanced": "Napredno", + "details_fee_rate": "Stopnja omrežnine", + "details_size": "Velikost", + "details_virtual_size": "Virtualna velikost", + "details_tx_hex": "Hex transakcije", + "details_inputs_count": "Vhodi ({count})", + "details_outputs_count": "Izhodi ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Preprosta in zmogljiva Bitcoin denarnica", "add_create": "Ustvari", + "total_balance": "Skupno stanje", + "add_entropy": "Entropija", "add_entropy_generated": "{gen} bajtov ustvarjene entropije", "add_entropy_provide": "Zagotovite entropijo s pomočjo metov kocke", "add_entropy_remain": "{gen} bajtov ustvarjene entropije. Preostalih {rem} bajtov bo pridobljenih iz sistemskega generatorja naključnih števil.", "add_import_wallet": "Uvozi denarnico", "add_lightning": "Lightning", "add_lightning_explain": "Za hitre vsakodnevne transakcije", - "add_lndhub": "Povežite se s svojim LNDHub-om", - "add_lndhub_error": "Podan naslov vozlišča ni veljavno vozlišče LNDHub.", "add_lndhub_placeholder": "naslov vašega vozlišča", "add_placeholder": "moja prva denarnica", "add_title": "Dodajte denarnico", "add_wallet_name": "Ime", "add_wallet_type": "Tip", - "balance": "Stanje", "clipboard_bitcoin": "V odložišču imate Bitcoin naslov. Ali ga želite uporabiti za transakcijo?", "clipboard_lightning": "V odložišču imate Lightning račun. Ali ga želite uporabiti za transakcijo?", "details_address": "Naslov", "details_advanced": "Napredno", "details_are_you_sure": "Ali ste prepričani?", "details_connected_to": "Povezano z", - "details_del_wb_err": "Navedeni znesek stanja se ne ujema s stanjem v tej denarnici. Prosimo poskusite ponovno.", - "details_del_wb_q": "Ta denarnica ima pozitivno stanje. Preden nadaljujete se zavedajte, da sredstev ne boste mogli obnoviti brez seznama besed te denarnice (mnemonično seme). V izogib nenamerni odstranitvi te denarnice, vnesite stanje svoje denarnice {balance} satošijev.", + "details_del_wb_q": "Ta denarnica ima pozitivno stanje. Preden nadaljujete, se zavedajte, da sredstev ne boste mogli obnoviti brez seznama besed te denarnice (mnemonično seme). V izogib nenamerni odstranitvi te denarnice vnesite stanje svoje denarnice {balance} satošijev.", "details_delete": "Izbriši", "details_delete_wallet": "Izbriši denarnico", "details_derivation_path": "pot izpeljave (derivation path)", - "details_display": "Prikaži na seznamu denarnic", "details_export_backup": "Izvozi / Varnostna kopija", "details_export_history": "Izvozi zgodovino v CSV", "details_master_fingerprint": "Glavni prstni odtis (fingerprint)", "details_multisig_type": "multisig", - "details_no_cancel": "Ne, prekliči", - "details_save": "Shrani", "details_show_xpub": "Prikaži XPUB denarnice", "details_show_addresses": "Prikaži naslove", "details_title": "Denarnica", + "wallets": "Denarnice", "details_type": "Tip", "details_use_with_hardware_wallet": "Uporaba s strojno denarnico", - "details_wallet_updated": "Denarnica posodobljena", "details_yes_delete": "Da, izbriši", "enter_bip38_password": "Vnesite geslo za dešifriranje", "export_title": "Izvoz denarnice", @@ -420,44 +455,80 @@ "import_discovery_subtitle": "Izberite odkrito denarnico", "import_discovery_derivation": "Uporabi pot izpeljave po meri", "import_discovery_no_wallets": "Nobena denarnica ni bila najdena.", - "import_derivation_found": "najdeno", - "import_derivation_found_not": "ni najdeno", - "import_derivation_loading": "nalaganje...", - "import_derivation_subtitle": "Vnesite pot izpeljave in poskusili bomo odkriti vašo denarnico", "import_derivation_title": "Pot izpeljave", - "import_derivation_unknown": "neznano", - "import_wrong_path": "napačna pot izpeljave", "list_create_a_button": "Ustvarite", "list_create_a_wallet": "Ustvarite denarnico", - "list_create_a_wallet_text": "Je brezplačno in lahko jih ustvarite\nkolikor želite.", "list_empty_txs1": "Tu bodo prikazane vaše transakcije", "list_empty_txs1_lightning": "Lightning denarnica je namenjena za vsakodnevne transakcije. Omogoča takojšnja plačila z nizkimi stroški.", "list_empty_txs2": "Začnite uporabljati denarnico.", "list_empty_txs2_lightning": "\nČe želite začeti z uporabo, tapnite na \"uredi sredstva\" in napolnite denarnico.", "list_latest_transaction": "Zadnja transakcija", - "list_ln_browser": "LApp Brskalnik", "list_long_choose": "Izberite fotografijo", - "list_long_clipboard": "Kopiraj iz odložišča", + "paste_from_clipboard": "Prilepi", + "import_file": "Uvozi datoteko", "list_long_scan": "Skeniraj QR kodo", "list_title": "Denarnice", "list_tryagain": "Poskusi ponovno", - "no_ln_wallet_error": "Za plačilo Lightning računa, morate najprej dodati Lightning denarnico.", + "no_ln_wallet_error": "Za plačilo Lightning računa morate najprej dodati Lightning denarnico.", "looks_like_bip38": "Zasebni ključ je verjetno zaščiten z geslom (BIP38)", - "reorder_title": "Preureditev Denarnic", - "reorder_instructions": "Tapnite in pridržite denarnico, da jo povlečete po seznamu.", "please_continue_scanning": "Nadaljujte s skeniranjem", "select_no_bitcoin": "Trenutno ni na voljo nobena Bitcoin denarnica.", "select_no_bitcoin_exp": "Za napolnitev Lightning denarnic je potrebna Bitcoin denarnica. Ustvarite ali uvozite denarnico.", "select_wallet": "Izberite Denarnico", - "xpub_copiedToClipboard": "Kopirano v odložišče.", "pull_to_refresh": "Povlecite za osvežitev", - "warning_do_not_disclose": "Opozorilo! Ne razkrivajte", "add_ln_wallet_first": "Najprej morate dodati Lightning denarnico.", "identity_pubkey": "Identity Pubkey", - "xpub_title": "XPUB denarnice" + "xpub_title": "XPUB denarnice", + "add_entropy_reset_title": "Ponastavi entropijo", + "add_entropy_reset_message": "Sprememba tipa denarnice bo ponastavila trenutno entropijo. Želite nadaljevati?", + "add_entropy_bytes": "{bytes} bajtov entropije", + "add_lndhub": "Poveži se z vašim LNDhubom", + "add_lndhub_error": "Posredovan naslov vozlišča ni veljaven LNDhub vozlišča.", + "add_wallet_seed_length": "Dolžina semena", + "add_wallet_seed_length_12": "12 besed", + "add_wallet_seed_length_24": "24 besed", + "clear_clipboard_on_import": "Počisti odložišče po uvozu", + "details_del_wb_err": "Vneseno stanje se ne ujema s stanjem te denarnice. Poskusite ponovno.", + "details_display": "Prikaži na domačem zaslonu", + "swipe_balance_hide": "Skrij", + "swipe_balance_show": "Prikaži", + "drag_to_reorder": "Povlecite za preureditev", + "clear_search": "Počisti iskanje", + "import_success_watchonly": "Vaša denarnica je bila uspešno uvožena. OPOZORILO: To je opazovalna denarnica, iz nje NE morete zapravljati.", + "learn_more": "Več informacij", + "import_discovery_offline": "BlueWallet je trenutno v načinu brez povezave. V tem načinu ne more preveriti obstoja denarnice, zato boste morali pravilno izbrati ročno.", + "import_derivation_found": "Najdeno", + "import_derivation_found_not": "Ni najdeno", + "import_derivation_loading": "Nalaganje ...", + "import_derivation_subtitle": "Vnesite pot izpeljave po meri in poskušali bomo najti vašo denarnico.", + "import_derivation_unknown": "Neznano", + "import_wrong_path": "Napačna pot izpeljave", + "list_create_a_wallet_text": "Je brezplačno in jih lahko ustvarite, \nkolikor želite.", + "manage_title": "Upravljanje denarnic", + "no_results_found": "Ni najdenih zadetkov.", + "warning_do_not_disclose": "Nikoli ne delite spodnjih podatkov", + "scan_import": "Skenirajte to QR kodo za uvoz vaše denarnice v drugo aplikacijo.", + "write_down_header": "Ustvarite ročno varnostno kopijo", + "write_down": "Zapišite si in varno shranite te besede. Uporabite jih za obnovitev denarnice kasneje.", + "wallet_type_this": "Ta tip denarnice je {type}.", + "share_number": "Deli {number}", + "copy_ln_url": "Kopirajte in varno shranite ta URL za obnovitev denarnice kasneje.", + "copy_ln_public": "Kopirajte in varno shranite te podatke za obnovitev denarnice kasneje.", + "manage_wallets_search_placeholder": "Iskanje po denarnicah, naslovih, transakcijah in opombah", + "more_info": "Več informacij", + "details_delete_wallet_error_message": "Pri potrjevanju, ali je bila ta denarnica odstranjena iz obvestil, je prišlo do težave — to je lahko posledica omrežne težave ali slabe povezave. Če nadaljujete, lahko še vedno prejemate obvestila za transakcije, povezane s to denarnico, tudi po njenem brisanju.", + "details_delete_anyway": "Vseeno izbriši" + }, + "total_balance_view": { + "title": "Skupno stanje", + "display_in_bitcoin": "Prikaži v bitcoinih", + "hide": "Skrij", + "display_in_sats": "Prikaži v sats", + "display_in_fiat": "Prikaži v {currency}", + "explanation": "Oglejte si skupno stanje vseh vaših denarnic na pregledovalnem zaslonu." }, "multisig": { - "multisig_vault": "Trezor", + "multisig_vault": "Multisig Trezor", "default_label": "Multisig Trezor", "multisig_vault_explain": "Največja varnost za višje zneske", "provide_signature": "Vnesite podpis", @@ -467,7 +538,6 @@ "fee_btc": "{number} BTC", "confirm": "Potrditev", "header": "Pošlji", - "share": "Deli", "view": "Prikaži", "manage_keys": "Urejanje ključev", "how_many_signatures_can_bluewallet_make": "koliko podpisov lahko naredi BlueWallet", @@ -494,20 +564,14 @@ "quorum_header": "Kvorum", "of": "od", "wallet_type": "Tip denarnice", - "invalid_mnemonics": "Zdi se, da to mnemonično seme ni veljavno", - "invalid_cosigner": "Neveljavni podatki sopodpisnika", "not_a_multisignature_xpub": "Ta xpub ne pripada multisig denarnici!", - "invalid_cosigner_format": "Nepravilen sopodpisnik: to ni sopodpisnik za {format} obliko", "create_new_key": "Ustvari novega", "scan_or_open_file": "Skeniraj ali odpri datoteko", "i_have_mnemonics": "Za ta ključ imam seme...", "type_your_mnemonics": "Vnesite seme za uvoz obstoječega ključa trezorja", - "this_is_cosigners_xpub": "To je XPUB sopodpisnika, pripravljen za uvoz v drugo denarnico. Varno ga lahko delite.", - "wallet_key_created": "Ključ trezorja je bil ustvarjen. Vzemite si trenutek, ter zapišite seznam besed (mnemonično seme) na list papirja.", + "wallet_key_created": "Ključ trezorja je bil ustvarjen. Vzemite si trenutek in zapišite seznam besed (mnemonično seme) na list papirja.", "are_you_sure_seed_will_be_lost": "Ali ste prepričani? Če nimate varnostne kopije, bo vaše mnemonično seme izgubljeno.", "forget_this_seed": "Pozabi to seme in uporabi XPUB", - "view_edit_cosigners": "Prikaži/uredi sopodpisnike", - "this_cosigner_is_already_imported": "Ta sopodpisnik je že uvožen.", "export_signed_psbt": "Izvozi podpisano PSBT", "input_fp": "Vnesite xfp (master key fingerprint)", "input_fp_explain": "preskoči in uporabi privzetega (00000000)", @@ -525,7 +589,21 @@ "ms_help_title4": "Uvoz trezorja", "ms_help_4": "Trezor lahko uvozite z uporabo funkcije uvoza in izbiro datoteke z varnostno kopijo. Če imate samo semena in razširjene ključe, lahko uporabite posamezna polja za uvoz pri ustvarjanju ključev trezorja.", "ms_help_title5": "Napredne možnosti", - "ms_help_5": "Privzeto bo ustvarjen trezor 2 od 3. Za ustvarjanje trezorja, ki ima drugačen kvorum ali tip naslova v nastavitvah omogočite napredni način." + "ms_help_5": "Privzeto bo ustvarjen trezor 2 od 3. Za ustvarjanje trezorja, ki ima drugačen kvorum ali tip naslova, v nastavitvah omogočite napredni način.", + "provide_signature_details": "Uporabite svojo napravo in denarnico, kjer se nahaja ključ, za podpis te transakcije", + "provide_signature_details_bluewallet": "V BlueWallet odprite meni zaslona Pošlji in izberite ", + "provide_signature_next_steps": "Skeniraj ali uvozi podpisano transakcijo", + "provide_signature_next_steps_details": "Ko vaša denarnica uspešno podpiše transakcijo, skenirajte priloženo QR kodo ali uvozite spremljajočo datoteko, nato pa pred objavo v omrežju preverite vse podrobnosti transakcije.", + "share": "Deli ...", + "shared_key_detected": "Deljeni sopodpisnik", + "shared_key_detected_question": "Z vami je bil deljen sopodpisnik, ali ga želite uvoziti?", + "invalid_mnemonics": "Ta mnemonična fraza ni videti veljavna.", + "invalid_cosigner": "Neveljavni podatki sopodpisnika", + "invalid_cosigner_format": "Nepravilen sopodpisnik: To ni sopodpisnik za format {format}.", + "this_is_cosigners_xpub": "To je xpub sopodpisnika — pripravljen za uvoz v drugo denarnico. Varno ga je deliti.", + "this_is_cosigners_xpub_airdrop": "Če delite prek AirDrop, morajo biti prejemniki na zaslonu koordinacije.", + "view_edit_cosigners": "Prikaži/uredi sopodpisnike", + "this_cosigner_is_already_imported": "Ta sopodpisnik je že uvožen." }, "is_it_my_address": { "title": "Je to moj naslov?", @@ -535,18 +613,31 @@ "no_wallet_owns_address": "Nobena od razpoložljivih denarnic ni lastnik navedenega naslova.", "view_qrcode": "Prikaži QR kodo" }, + "autofill_word": { + "title": "Zadnja beseda semena", + "enter": "Vnesite svojo delno mnemonično frazo", + "generate_word": "Ustvari zadnjo besedo", + "error": "Vnos ni 11- ali 23-besedna delna mnemonična fraza. Poskusite znova." + }, "cc": { "change": "Vračilo", "coins_selected": "Izbrani kovanci ({number})", "selected_summ": "{value} izbrano", - "empty": "Ta denarnica trenutno nima kovancev", "freeze": "Zamrznjen", "freezeLabel": "Zamrznitev", "freezeLabel_un": "Odmrznitev", "header": "Nadzor nad kovanci", "use_coin": "Uporabi kovanec", "use_coins": "Uporabi kovance", - "tip": "Omogoča ogled, označevanje, zamrznitev ali izbiro kovancev za boljše upravljanje denarnice." + "tip": "Omogoča ogled, označevanje, zamrznitev ali izbiro kovancev za boljše upravljanje denarnice.", + "sort_status": "Stanje", + "empty": "Ta denarnica trenutno nima nobenih kovancev.", + "sort_asc": "Naraščajoče", + "sort_desc": "Padajoče", + "sort_height": "Višina", + "sort_value": "Vrednost", + "sort_label": "Oznaka", + "sort_by": "Razvrsti po" }, "units": { "BTC": "BTC", @@ -568,9 +659,46 @@ "type_change": "Vračilo", "type_receive": "Prejemni", "type_used": "Uporabljen", - "transactions": "Transakcije" + "transactions": "Transakcije", + "copy_private_key": "Kopiraj zasebni ključ", + "sensitive_private_key": "Opozorilo: zasebni ključi so zelo občutljivi. Nadaljujem?" }, "lnurl_auth": { - "register_question_part_1": "Bi želeli odpreti račun pri" + "register_question_part_1": "Bi želeli odpreti račun pri", + "register_question_part_2": "z uporabo svoje Lightning denarnice?", + "register_answer": "Uspešno ste registrirali račun pri {hostname}!", + "login_question_part_1": "Bi se želeli prijaviti pri", + "login_question_part_2": "z uporabo svoje Lightning denarnice?", + "login_answer": "Uspešno ste se prijavili pri {hostname}!", + "link_question_part_1": "Bi želeli povezati svoj račun pri", + "link_question_part_2": "s svojo Lightning denarnico?", + "link_answer": "Vaša Lightning denarnica je bila uspešno povezana z vašim računom pri {hostname}!", + "auth_question_part_1": "Bi se želeli overiti pri", + "auth_question_part_2": "z uporabo svoje Lightning denarnice?", + "auth_answer": "Uspešno ste se overili pri {hostname}!", + "could_not_auth": "Nismo vas mogli overiti pri {hostname}.", + "authenticate": "Overi" + }, + "bip47": { + "payment_code": "Plačilna koda", + "contacts": "Stiki", + "bip47_explain": "Večkratno uporabna in deljiva koda", + "bip47_explain_subtitle": "BIP47", + "purpose": "Večkratno uporabna in deljiva koda (BIP47)", + "pay_this_contact": "Plačaj temu stiku", + "rename_contact": "Preimenuj stik", + "copy_payment_code": "Kopiraj plačilno kodo", + "hide_contact": "Skrij stik", + "rename": "Preimenuj", + "provide_name": "Vnesite novo ime za ta stik", + "add_contact": "Dodaj stik", + "provide_payment_code": "Vnesite plačilno kodo", + "invalid_pc": "Neveljavna plačilna koda", + "notification_tx_unconfirmed": "Obvestilna transakcija še ni potrjena, počakajte", + "failed_create_notif_tx": "Ustvarjanje on-chain transakcije ni uspelo", + "onchain_tx_needed": "Potrebna je on-chain transakcija", + "notif_tx_sent": "Obvestilna transakcija je bila poslana. Počakajte na potrditev", + "notif_tx": "Obvestilna transakcija", + "not_found": "Plačilna koda ni najdena" } } diff --git a/loc/sq_AL.json b/loc/sq_AL.json new file mode 100644 index 00000000000..61578b8e0b9 --- /dev/null +++ b/loc/sq_AL.json @@ -0,0 +1,704 @@ +{ + "_": { + "bad_password": "Fjalëkalimi është i gabuar. Provojeni përsëri.", + "cancel": "Anulo", + "continue": "Vazhdo", + "clipboard": "Memoria e përkohshme", + "discard_changes": "Anulo ndryshimet?", + "discard_changes_explain": "Ju keni ndryshime të paruajtura. Jeni i sigurt që nuk doni t’i ruani dhe të dilni?", + "enter_password": "Fusni fjalëkalimin", + "never": "Kurrë", + "of": "{number} nga {total}", + "ok": "OK", + "enter_url": "Fusni URL", + "storage_is_encrypted": "Memoria është e kriptuar. Duhet të fusni fjalëkalimin për ta dekriptuar.", + "yes": "Po", + "no": "Jo", + "seed": "Seed", + "success": "Sukses", + "wallet_key": "Çelësi i portofolit", + "close": "Mbylle", + "change_input_currency": "Ndrysho valutën e hyrjes", + "refresh": "Rifresko", + "pick_image": "Zgjidh nga biblioteka", + "pick_file": "Zgjidh file-in", + "enter_amount": "Fut sasinë", + "qr_custom_input_button": "Kliko 10 herë për të futur info të personalizuar", + "unlock": "Hap", + "suggested": "I sugjeruar", + "copied": "U kopjua!", + "save": "Ruaj...", + "port": "Porta", + "ssl_port": "Porta SSL" + }, + "azteco": { + "codeIs": "Kodi promocional është", + "errorBeforeRefeem": "Para përdorimit, duhet të shtoni një portofol Bitcoin-i.", + "errorSomething": "Diçka shkoi gabim. A është ky kod promocional akoma i vlefshëm?", + "redeem": "Përdore në portofol", + "redeemButton": "Përdor", + "success": "Sukses", + "title": "Përdor kodin promocional të Azte.co", + "successMessage": "Kuponi u shfrytëzua me sukses! Fondet tuaja duhet të mbërrijnë së shpejti në portofolin tuaj Bitcoin." + }, + "entropy": { + "save": "Ruaj", + "title": "Entropia", + "undo": "Anulo", + "amountOfEntropy": "{bits} nga {limit} bits" + }, + "errors": { + "broadcast": "Transmetimi nuk funksionoi.", + "error": "Gabim", + "network": "Gabim rrjeti" + }, + "lnd": { + "errorInvoiceExpired": "Fatura ka skaduar.", + "expired": "Skaduar", + "expiresIn": "Skadon në {time} minuta", + "payButton": "Paguaj", + "payment": "Pagesa", + "placeholder": "Fatura ose adresa", + "potentialFee": "Komisioni potencial: {fee}", + "refill": "Rimbush", + "refill_create": "Krijoni një portofol Bitcoin-i për të vazhduar.", + "refill_external": "Rimbush nga një portofol tjetër", + "refill_lnd_balance": "Rimbush balancën e portofolit Lightning.", + "sameWalletAsInvoiceError": "Nuk mund të paguash një faturë me të njëjtin portofol që e ka krijuar atë", + "title": "Menaxho fondet" + }, + "lndViewInvoice": { + "additional_info": "Informacione të tjera", + "for": "Për:", + "lightning_invoice": "Fatura Lightning", + "please_pay_between_and": "Ju lutem paguani ndërmjet {min} dhe {max}", + "please_pay": "Ju lutem paguani", + "preimage": "Pre-imazh", + "wasnt_paid_and_expired": "Fatura nuk është paguar dhe ka skaduar.", + "date_time": "Data dhe ora", + "sats": "sats." + }, + "plausibledeniability": { + "create_fake_storage": "Krijo një memorie të kriptuar", + "create_password_explanation": "Fjalëkalimi për memorien e rreme nuk duhet të jetë i njëjti me atë të memories tuaj kryesore.", + "help": "Në rrethana të caktuara, mund të detyroheni të zbuloni një fjalëkalim. Për të mbajtur monedhat tuaja të sigurta, BlueWallet mund të krijojë një hapësirë tjetër të koduar me një fjalëkalim të ndryshëm. Nën presion, mund ta zbuloni këtë fjalëkalim një pale të tretë. Nëse futet në BlueWallet, ai do të hapë një hapësirë të re “të rreme”. Kjo do të duket e ligjshme për palën e tretë, por në mënyrë të fshehtë do të mbajë të sigurt hapësirën tuaj kryesore me monedhat.", + "help2": "Hapësira e re do të jetë plotësisht funksionale dhe mund të ruani aty shuma minimale në mënyrë që të duket më e besueshme.", + "password_should_not_match": "Fjalëkalimi është në përdorim. Ju lutem, provoni një fjalëkalim tjetër.", + "title": "Mohim i besueshëm" + }, + "pleasebackup": { + "ask": "A i keni ruajtur fjalët për rigjenerimin e portofolit? Këto fjalë do t’ju duhen për të pasur akses në fondet tuaja në rast se humbni këtë pajisje. Pa këto fjalë, fondet tuaja do të jenë të humbura përgjithmonë.", + "ask_no": "Jo, nuk kam.", + "ask_yes": "Po, e kam.", + "ok": "OK, i shkrova.", + "ok_lnd": "OK, i ruajta.", + "text": "Ju lutem merrni një moment për të shkruar këto fjalë në një copë letre.\nËshtë mundësia juaj e vetme për të rigjeneruar portofolin.", + "text_lnd": "Ju lutem ruani një kopje të portofolit. Do t’ju nevojitet për të rigjeneruar portofolin në rast humbje.", + "title": "Portofoli juaj u krijua" + }, + "receive": { + "details_create": "Krijo", + "details_label": "Përshkrim", + "details_setAmount": "Merr me sasi", + "details_share": "Shpërndaj", + "header": "Merr", + "reset": "Rezeto", + "maxSats": "Sasia maksimale është {max} sats", + "maxSatsFull": "Sasia maksimale është {max} sats ose {currency}", + "minSats": "Sasia minimale është {min} sats", + "minSatsFull": "Sasia minimale është {min} sats ose {currency}", + "qrcode_for_the_address": "QR kodi për adresën", + "address_not_found": "Nuk mundi të gjenerohet adresa pritëse.", + "bip47_explanation": "Kodet e pagesës janë një adresë universale që shmang zbulimin e adresave të portofolit tuaj. Jo të gjitha shërbimet do t'i mbështesin ato." + }, + "send": { + "broadcastButton": "Transmetim", + "broadcastError": "Gabim", + "broadcastNone": "Fut Hex-in e transfertës", + "broadcastPending": "Në pritje", + "broadcastSuccess": "Sukses", + "confirm_header": "Konfirmo", + "confirm_sendNow": "Dërgo tani", + "create_amount": "Sasia", + "create_broadcast": "Transmetim", + "create_memo": "Memo", + "create_copy": "Kopjoje dhe transmetoje më vonë", + "create_details": "Detajet", + "create_fee": "Komision", + "create_satoshi_per_vbyte": "Satoshi për vByte", + "create_to": "Te", + "create_tx_size": "Madhësia e Transfertës", + "create_verify": "Verifiko në coinb.in", + "details_insert_contact": "Fut Kontaktin", + "details_add_rec_add": "Shto një marrës", + "details_add_rec_rem": "Hiq një marrës", + "details_add_recc_rem_all_alert_description": "Jeni i sigurt që doni të fshini të gjithë marrësit?", + "details_add_rec_rem_all": "Hiq të gjithë marrësit", + "details_recipients_title": "Marrësi", + "details_address": "Adresa", + "details_address_field_is_not_valid": "Adresa nuk është e vlefshme.", + "details_adv_fee_bump": "Lejo rritjen e komisionit", + "details_adv_full": "Përdor të gjithë Balancën", + "details_adv_full_sure": "Jeni i sigurt që doni të përdorni gjithë balancën e portofolit tuaj për këtë transaksion?", + "details_adv_full_sure_frozen": "Jeni i sigurt që doni të përdorni gjithë balancën e portofolit tuaj për këtë transaksion? Vini re se monedhat e ngrira nuk mund të përdoren.", + "details_adv_import": "Importo një Transaksion", + "details_adv_import_qr": "Importo një Transaksion (QR)", + "details_amount_field_is_not_valid": "Sasia e përdorur nuk është e vlefshme.", + "details_amount_field_is_less_than_minimum_amount_sat": "Sasia e përcaktuar është shumë e vogël. Ju lutem fusni një sasi më të madhe se 500 sats.", + "details_create": "Krijo një Faturë", + "details_error_decode": "E pamundur të dekodohet adresa e Bitcoin-it", + "details_fee_field_is_not_valid": "Komisioni nuk është i vlefshëm.", + "details_frozen": "{amount} BTC janë të ngrira.", + "details_next": "Tjetri", + "details_no_signed_tx": "File-at e zgjedhur nuk përmbajnë asnjë transaksion që mund të importohet.", + "details_note_placeholder": "Shënim për veten", + "details_scan": "Skano", + "details_scan_hint": "Kliko dy herë për skanim ose importo destinacionin.", + "details_total_exceeds_balance": "Sasia që dëshironi të dërgoni e tejkalon balancën e disponueshme.", + "details_total_exceeds_balance_frozen": "Shuma që po dërgoni tejkalon balancën e disponueshme. Ju lutemi vini re se monedhat e ngrira nuk mund të dërgohen.", + "details_unrecognized_file_format": "Formati i file-it është i panjohur.", + "details_wallet_before_tx": "Para se të krijoni një transaksion, duhet të keni një portofol Bitcoin-i.", + "dynamic_init": "Duke filluar", + "dynamic_next": "Tjetri", + "dynamic_prev": "I mëparshëm", + "dynamic_start": "Fillo", + "dynamic_stop": "Ndalo", + "fee_10m": "10min", + "fee_1d": "1ditë", + "fee_3h": "3orë", + "fee_custom": "Personal", + "fee_fast": "Shpejt", + "fee_medium": "Mesatar", + "fee_satvbyte": "në sat/vByte", + "fee_slow": "Ngadalë", + "header": "Dërgo", + "input_clear": "Fshi", + "input_done": "U krye", + "input_paste": "Ngjit", + "input_total": "Totali:", + "psbt_sign": "Firmos një transaksion", + "psbt_tx_export": "Eksporto në file", + "success_done": "U krye", + "provided_address_is_invoice": "Kjo adresë duket të jetë për një faturë Lightning. Ju lutemi, shkoni te portofoli juaj Lightning për të bërë një pagesë për këtë faturë.", + "create_this_is_hex": "Ky është hex-i i transaksionit tuaj—i firmosur dhe gati për t'u transmetuar në rrjet.", + "details_recipient_title": "Marrësi #{number} nga #{total}", + "please_complete_recipient_title": "Marrës i paplotë", + "please_complete_recipient_details": "Ju lutemi plotësoni detajet e marrësit #{number} para se të shtoni një marrës të ri.", + "details_scan_error": "Gabim skanimi", + "insert_custom_fee": "Fut komisionin", + "fee_replace_minvb": "Norma totale e komisionit (satoshi për vByte) që dëshironi të paguani duhet të jetë më e lartë se {min} sat/vByte.", + "permission_camera_message": "Na nevojitet leja juaj për të përdorur kamerën.", + "invalid_psbt": "PSBT i pavlefshëm.", + "open_settings": "Hap cilësimet", + "permission_storage_denied_message": "BlueWallet nuk mund ta ruajë këtë file. Ju lutemi hapni cilësimet e pajisjes dhe aktivizoni lejen e ruajtjes.", + "permission_storage_title": "Leja për akses në ruajtje", + "psbt_clipboard": "Kopjo në kujtesën e përkohshme", + "psbt_this_is_psbt": "Ky është një transaksion Bitcoin pjesërisht i firmosur (PSBT). Ju lutemi përfundoni firmosjen me portofolin tuaj hardware.", + "no_tx_signing_in_progress": "Nuk ka asnjë firmosje transaksioni në proces.", + "outdated_rate": "Norma u përditësua së fundmi: {date}", + "psbt_tx_open": "Hap transaksionin e firmosur", + "psbt_tx_scan": "Skano transaksionin e firmosur", + "qr_error_no_qrcode": "Nuk mundëm të gjenim një QR kod të vlefshëm në imazhin e zgjedhur. Sigurohuni që imazhi përmban vetëm një QR kod dhe asnjë përmbajtje shtesë si tekst ose butona.", + "reset_amount": "Rivendos sasinë", + "reset_amount_confirm": "Dëshironi të rivendosni sasinë?", + "txSaved": "File-i i transaksionit ({filePath}) u ruajt.", + "file_saved_at_path": "File-i ({filePath}) u ruajt.", + "cant_send_to_silentpayment_adress": "Ky portofol nuk mund të dërgojë në adresa Silent Payments", + "cant_send_to_bip47": "Ky portofol nuk mund të dërgojë në kode pagese BIP47", + "cant_find_bip47_notification": "Shtoni fillimisht këtë kod pagese te kontaktet", + "problem_with_psbt": "Problem me PSBT" + }, + "settings": { + "about": "Rreth", + "about_license": "Licenca e tipit MIT", + "about_review": "Na lini një vlerësim", + "about_sm_github": "GitHub", + "about_sm_telegram": "Kanali Telegram", + "biometrics": "Të dhënat Biometrike", + "biom_conf_identity": "Ju lutem konfirmoni identitetin", + "currency": "Valuta", + "electrum_connected": "I lidhur", + "electrum_offline_mode": "Modaliteti Off-Line", + "use_ssl": "Përdor SSL", + "electrum_settings_server": "Serveri Elektrum", + "electrum_status": "Statusi", + "electrum_preferred_server": "Serveri i Preferuar", + "encrypt_tstorage": "Depozita", + "general": "Gjenerale", + "language": "Gjuha", + "last_updated": "Përditësimi i Fundit", + "license": "Licenca", + "lightning_saved": "Ndryshimet tuaja janë ruajtur me sukses.", + "lightning_settings": "Serveri Lightning", + "network": "Rrjeti", + "network_broadcast": "Transmeto Transaksionin", + "network_electrum": "Serveri Elektrum", + "password": "Fjalëkalim", + "plausible_deniability": "Mohim i besueshëm", + "privacy": "Privatësia", + "push_notifications_explanation": "Duke aktivizuar njoftimet, kodi i pajisjes suaj do të dërgohet në server, së bashku me adresat e portofolit dhe ID-të e transaksioneve për të gjitha portofolat dhe transaksionet e kryera pasi të aktivizoni njoftimet. Kodi i pajisjes përdoret për të dërguar njoftime, ndërsa informacioni i portofolit na mundëson t’ju njoftojmë për Bitcoin-in që ju vjen ose për konfirmimet e transaksioneve.\n\nVetëm informacioni i krijuar pas aktivizimit të njoftimeve transmetohet—asnjë e dhënë nga më parë nuk mblidhet.\n\nÇaktivizimi i njoftimeve do të fshijë të gjithë këtë informacion nga serveri. Gjithashtu, fshirja e një portofoli nga aplikacioni do të heqë edhe informacionin e tij përkatës nga serveri.", + "save": "Ruaj", + "saved": "Ruajtur", + "total_balance": "Balanca Totale", + "about_awesome": "Ndërtuar me të mrekullueshmin", + "about_backup": "Gjithmonë bëni një kopje rezervë të çelësave tuaj!", + "about_free": "BlueWallet është një projekt falas dhe me kod të hapur. Krijuar nga përdoruesit e Bitcoin-it.", + "about_release_notes": "Shënimet e versionit", + "performance_score": "Rezultati i performancës: {num}", + "run_performance_test": "Testo performancën", + "about_selftest": "Kryeni vetë-testin", + "block_explorer_invalid_custom_url": "URL-ja e dhënë është e pavlefshme. Ju lutemi futni një URL të vlefshme që fillon me http:// ose https://.", + "about_selftest_electrum_disabled": "Vetë-testimi nuk është i disponueshëm me modalitetin Off-Line të Elektrum. Ju lutemi çaktivizoni modalitetin off-line dhe provoni përsëri.", + "about_selftest_ok": "Të gjitha testet e brendshme u kaluan me sukses. Portofoli funksionon mirë.", + "privacy_temporary_screenshots": "Lejo kapjen e ekranit", + "privacy_temporary_screenshots_instructions": "Mbrojtja e kapjes së ekranit do të çaktivizohet përkohësisht, duke lejuar pamje ekrani dhe regjistrime ekrani. Mbrojtja do të riaktivizohet automatikisht kur ta mbyllni dhe ta rihapni BlueWallet.", + "biometrics_no_longer_available": "Cilësimet e pajisjes tuaj kanë ndryshuar dhe nuk përputhen më me cilësimet e sigurisë të zgjedhura në aplikacion. Ju lutemi riaktivizoni biometrinë ose kodin e hyrjes, pastaj rinisni aplikacionin për të aplikuar këto ndryshime.", + "biom_10times": "Keni provuar të futni fjalëkalimin tuaj 10 herë. Dëshironi të rivendosni memorien tuaj? Kjo do të heqë të gjitha portofolat dhe do të dekriptojë memorien tuaj.", + "biom_no_passcode": "Pajisja juaj nuk ka të aktivizuar një kod hyrjeje ose biometri. Për të vazhduar, ju lutemi konfiguroni një kod hyrjeje ose biometri në aplikacionin Cilësimet.", + "biom_remove_decrypt": "Të gjithë portofolat tuaj do të hiqen dhe memoria juaj do të dekriptohet. Jeni i sigurt që dëshironi të vazhdoni?", + "currency_source": "Norma merret nga", + "currency_fetch_error": "Ndodhi një gabim gjatë marrjes së normës për valutën e zgjedhur.", + "default_title": "Në nisje", + "donate": "Dhuro", + "donate_description": "Na ndihmoni ta mbajmë Blue falas!", + "electrum_connected_not": "I palidhur", + "electrum_error_connect": "Nuk mund të lidhet me serverin Elektrum të dhënë", + "electrum_error_connect_tor": "Nuk mund të lidhet me serverin Elektrum të dhënë. Ju lutemi sigurohuni që aplikacioni Orbot është i lidhur dhe provoni përsëri.", + "lndhub_uri": "P.sh., {example}", + "electrum_host": "P.sh., {example}", + "electrum_offline_description": "Kur është i aktivizuar, portofolat tuaj Bitcoin nuk do të përpiqen të marrin ballancat ose transaksionet.", + "electrum_port": "Porta, zakonisht {example}", + "electrum_saved": "Ndryshimet tuaja u ruajtën me sukses. Mund të kërkohet rinisja e BlueWallet që ndryshimet të hyjnë në fuqi.", + "set_electrum_server_as_default": "Vendos {server} si serverin e parazgjedhur Elektrum?", + "set_lndhub_as_default": "Vendos {url} si serverin e parazgjedhur LNDhub?", + "electrum_preferred_server_description": "Futni serverin që dëshironi të përdorë portofoli juaj për të gjitha aktivitetet në Bitcoin. Pasi të vendoset, portofoli juaj do ta përdorë vetëm këtë server për të kontrolluar ballancat, për të dërguar transaksione dhe për të marrë të dhëna nga rrjeti. Sigurohuni që besoni në këtë server para se ta vendosni.", + "electrum_unable_to_connect": "I pamundur lidhja me {server}.", + "electrum_history": "Historiku", + "electrum_reset_to_default": "Kjo do të lejojë BlueWallet të zgjedhë rastësisht një server nga lista e serverëve.", + "electrum_reset": "Rivendos në të parazgjedhur", + "electrum_reset_to_default_and_clear_history": "Rivendos në të parazgjedhur dhe pastro historikun", + "encrypt_decrypt": "Dekripto memorien", + "encrypt_decrypt_q": "Jeni i sigurt që dëshironi të dekriptoni memorien tuaj? Kjo do të lejojë portofolat tuaj të aksesohen pa një fjalëkalim.", + "encrypt_enc_and_pass": "I mbrojtur me fjalëkalim", + "encrypt_storage_explanation_headline": "Aktivizo enkriptimin e memories", + "encrypt_storage_explanation_description_line1": "Aktivizimi i enkriptimit të memories shton një shtresë shtesë mbrojtjeje për aplikacionin tuaj duke siguruar mënyrën se si ruhen të dhënat tuaja në pajisje. Kjo e bën më të vështirë për këdo që të aksesojë informacionin tuaj pa leje.", + "encrypt_storage_explanation_description_line2": "Megjithatë, është e rëndësishme të dini se ky enkriptim mbron vetëm aksesin në portofolat tuaj të ruajtur në keychain-in e pajisjes. Nuk vendos një fjalëkalim ose ndonjë mbrojtje shtesë në vetë portofolat.", + "i_understand": "E kuptoj", + "block_explorer": "Eksplorues blloqesh", + "block_explorer_preferred": "Përdor eksploruesin e preferuar të blloqeve", + "block_explorer_error_saving_custom": "Gabim gjatë ruajtjes së eksploruesit të preferuar të blloqeve", + "encrypt_title": "Siguria", + "encrypt_use": "Përdor {type}", + "set_as_preferred": "Vendos si të preferuar", + "set_as_preferred_electrum": "Vendosja e {host}:{port} si serveri i preferuar do të çaktivizojë lidhjen me një server të sugjeruar rastësisht.", + "encrypted_feature_disabled": "Kjo veçori nuk mund të përdoret kur enkriptimi i memories është i aktivizuar.", + "encrypt_use_expl": "{type} do të përdoret për të konfirmuar identitetin tuaj para se të kryeni një transaksion, të zhbllokoni, të eksportoni ose të fshini një portofol.", + "biometrics_fail": "Nëse {type} nuk është i aktivizuar, ose dështon në zhbllokim, mund të përdorni kodin e hyrjes së pajisjes si alternativë.", + "general_continuity": "Vazhdimësia", + "general_continuity_e": "Kur është i aktivizuar, do të mund të shikoni portofolat e zgjedhur dhe transaksionet, duke përdorur pajisjet e tjera tuaja Apple të lidhura me iCloud.", + "groundcontrol_explanation": "GroundControl është një server falas dhe me kod të hapur i njoftimeve push për portofolat Bitcoin. Mund të instaloni serverin tuaj GroundControl dhe ta vendosni URL-në e tij këtu për të mos u mbështetur tek infrastruktura e BlueWallet. Lëreni bosh për të përdorur serverin e parazgjedhur të GroundControl.", + "header": "Cilësimet", + "language_isRTL": "Rinisja e BlueWallet kërkohet që orientimi i gjuhës të hyjë në fuqi.", + "lightning_error_lndhub_uri": "URI LNDhub i pavlefshëm", + "lightning_error_lndhub_uri_tor": "URI LNDhub i pavlefshëm. Ju lutemi sigurohuni që aplikacioni Orbot është i lidhur dhe provoni përsëri.", + "lightning_settings_explain": "Për t'u lidhur me nyjen tuaj LND, ju lutemi instaloni LNDhub dhe vendosni URL-në e tij këtu te cilësimet. Vini re që vetëm portofolat e krijuar pas ruajtjes së ndryshimeve do të lidhen me LNDhub-in e specifikuar.", + "lndhub_github": "Depo në GitHub", + "electrum_suggested_description": "Kur një server i preferuar nuk është vendosur, një server i sugjeruar do të zgjidhet rastësisht për përdorim.", + "not_a_valid_uri": "URI i pavlefshëm", + "notifications": "Njoftimet", + "open_link_in_explorer": "Hape lidhjen në eksplorues", + "password_explain": "Futni fjalëkalimin që do të përdorni për të zhbllokuar memorien tuaj.", + "privacy_read_clipboard": "Lexo kujtesën e përkohshme", + "privacy_system_settings": "Cilësimet e sistemit", + "privacy_quickactions": "Shkurtoret e portofolit", + "privacy_quickactions_explanation": "Prekni dhe mbani ikonën e aplikacionit BlueWallet për të parë shpejt ballancën e portofolit tuaj.", + "privacy_clipboard_explanation": "Ofron shkurtore nëse gjendet një adresë ose faturë në kujtesën tuaj të përkohshme.", + "privacy_do_not_track": "Çaktivizo analitikën", + "privacy_do_not_track_explanation": "Informacioni mbi performancën dhe besueshmërinë nuk do të dërgohet për analizë.", + "rate": "Norma", + "selfTest": "Vetë-testimi", + "success_transaction_broadcasted": "Transaksioni juaj u transmetua me sukses!", + "total_balance_explanation": "Shfaq ballancën totale të të gjithë portofolave tuaj në widget-et e ekranit kryesor.", + "widgets": "Widget-et", + "tools": "Mjetet" + }, + "notifications": { + "would_you_like_to_receive_notifications": "Dëshironi të merrni njoftime kur ju vijnë pagesa?", + "notifications_subtitle": "Pagesa hyrëse dhe konfirmime transaksionesh", + "no_and_dont_ask": "Jo, dhe mos më pyet më.", + "permission_denied_message": "Ju keni refuzuar lejen për t'ju dërguar njoftime. Nëse dëshironi të merrni njoftime, ju lutemi aktivizoni ato në cilësimet e pajisjes tuaj." + }, + "transactions": { + "cpfp_create": "Krijo", + "details_copy": "Kopjo", + "details_title": "Transaksion", + "details_to": "Dalje", + "pending": "Në pritje", + "list_title": "Transaksionet", + "transaction": "Transaksion", + "updating": "Duke u përditësuar...", + "cancel_explain": "Do ta zëvendësojmë këtë transaksion me një që ju paguan ju dhe ka komision më të lartë. Kjo praktikisht anulon transaksionin aktual. Kjo quhet RBF—Replace by Fee.", + "cancel_no": "Ky transaksion nuk mund të zëvendësohet.", + "cancel_title": "Anulo këtë transaksion (RBF)", + "transaction_loading_error": "Pati një problem gjatë ngarkimit të transaksionit. Ju lutemi provoni përsëri më vonë.", + "transaction_not_available": "Transaksioni nuk është i disponueshëm", + "confirmations_lowercase": "{confirmations} konfirmime", + "expand_note": "Zgjero shënimin", + "cpfp_exp": "Do të krijojmë një transaksion tjetër që shpenzon transaksionin tuaj të pakonfirmuar. Komisioni total do të jetë më i lartë se komisioni i transaksionit origjinal, kështu që duhet të minohet më shpejt. Kjo quhet CPFP—Child Pays for Parent.", + "cpfp_no_bump": "Komisioni i këtij transaksioni nuk mund të rritet.", + "cpfp_title": "Rrit komisionin (CPFP)", + "details_balance_hide": "Fshih ballancën", + "details_balance_show": "Shfaq ballancën", + "details_copy_block_explorer_link": "Kopjo lidhjen e eksploruesit të blloqeve", + "details_copy_note": "Kopjo shënimin", + "details_copy_txid": "Kopjo ID-në e transaksionit", + "details_id": "ID", + "details_inputs": "Hyrjet", + "details_outputs": "Daljet", + "date": "Data", + "details_received": "U mor", + "details_view_in_browser": "Shiko në shfletues", + "incoming_transaction": "Transaksion hyrës", + "outgoing_transaction": "Transaksion dalës", + "expired_transaction": "Transaksion i skaduar", + "pending_transaction": "Transaksion në pritje", + "offchain": "Off-chain", + "onchain": "On-chain", + "enable_offline_signing": "Ky portofol nuk po përdoret në lidhje me një firmosje off-line. Dëshironi ta aktivizoni tani?", + "list_conf": "Konf: {number}", + "pending_with_amount": "Në pritje {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: Në ~10 minuta", + "eta_3h": "ETA: Në ~3 orë", + "eta_1d": "ETA: Në ~1 ditë", + "list_title_sent": "Dërguar", + "list_title_received": "Marrë", + "open_url_error": "I pamundur hapja e lidhjes me shfletuesin e parazgjedhur. Ju lutemi ndryshoni shfletuesin tuaj të parazgjedhur dhe provoni përsëri.", + "rbf_explain": "Do ta zëvendësojmë këtë transaksion me një me komision më të lartë në mënyrë që të minohet më shpejt. Kjo quhet RBF—Replace by Fee.", + "rbf_title": "Përshpejto (RBF)", + "status_bump": "Përshpejto", + "status_cancel": "Anulo", + "transactions_count": "Numri i transaksioneve", + "txid": "ID-ja e transaksionit", + "watchOnlyWarningTitle": "Paralajmërim sigurie", + "watchOnlyWarningDescription": "Bëni kujdes nga mashtruesit që shpesh përdorin portofola \"vetëm për shikim\" për të mashtruar përdoruesit. Këto portofola nuk ju lejojnë të kontrolloni ose të dërgoni fonde; ato ju lejojnë vetëm të shihni ballancën.", + "custom_fee_warning_title": "Paralajmërim", + "custom_fee_warning_description": "Komisionet nën 1 sat/vB janë të vlefshme, por mund të mos transmetohen për shkak të politikave të nyjeve.", + "details_eta_analyzing": "Duke analizuar...", + "details_sent": "Dërguar", + "details_section": "Detajet", + "details_explorer": "eksplorues", + "details_network_fee": "Komisioni i rrjetit", + "details_to_address": "Te", + "details_note": "Shënim", + "details_add_note": "shto", + "details_advanced": "Të avancuara", + "details_fee_rate": "Norma e komisionit", + "details_size": "Madhësia", + "details_virtual_size": "Madhësia virtuale", + "details_tx_hex": "Hex-i i transaksionit", + "details_inputs_count": "Hyrjet ({count})", + "details_outputs_count": "Daljet ({count})" + }, + "wallets": { + "add_bitcoin": "Bitcoin", + "add_create": "Krijo", + "total_balance": "Balanca Totale", + "add_entropy": "Entropia", + "add_lightning": "Lightning", + "add_title": "Shto Portofol", + "add_wallet_name": "Emër", + "add_wallet_type": "Tipi", + "add_wallet_seed_length_12": "12 fjalë", + "add_wallet_seed_length_24": "24 fjalë", + "details_address": "Adresa", + "details_show_addresses": "Trego adresat", + "details_title": "Portofol", + "wallets": "Portofola", + "details_type": "Tipi", + "import_discovery_title": "Zbulo", + "import_discovery_no_wallets": "Asnjë portofol nuk u gjet.", + "import_derivation_found": "U gjet", + "import_derivation_found_not": "Nuk u gjet", + "import_derivation_loading": "Duke u ngarkuar", + "import_derivation_unknown": "I panjohur", + "list_create_a_button": "Shto tani", + "list_create_a_wallet": "Shto një portofol", + "list_create_a_wallet_text": "Është falas dhe mund të krijoni \nsa të dëshironi.", + "list_long_choose": "Zgjidh Foton", + "paste_from_clipboard": "Ngjit", + "list_long_scan": "Skano QR kodin", + "list_title": "Portofola", + "list_tryagain": "Provojeni përsëri", + "manage_title": "Menaxho Portofolat", + "no_results_found": "Asnjë rezultat.", + "please_continue_scanning": "Vazhdo skanimin.", + "select_no_bitcoin": "Për momentin nuk ka asnjë portofol Bitcoin-i.", + "select_wallet": "Zgjidh Portofolin", + "share_number": "Shpërndaj {number}", + "more_info": "Më shumë Info", + "add_bitcoin_explain": "Portofol Bitcoin i thjeshtë dhe i fuqishëm", + "add_entropy_reset_title": "Rivendos entropinë", + "add_entropy_reset_message": "Ndryshimi i tipit të portofolit do të rivendosë entropinë aktuale. Dëshironi të vazhdoni?", + "add_entropy_bytes": "{bytes} byte entropi", + "add_entropy_generated": "{gen} byte entropi e gjeneruar", + "add_entropy_provide": "Jep entropi nëpërmjet hedhjeve të zarit", + "add_entropy_remain": "{gen} byte entropi e gjeneruar. {rem} byte të mbetura do të merren nga gjeneruesi i numrave të rastit i sistemit.", + "add_import_wallet": "Importo portofol", + "add_lightning_explain": "Për shpenzime me transaksione të menjëhershme", + "add_lndhub": "Lidhu me LNDhub-in tuaj", + "add_lndhub_error": "Adresa e nyjes së dhënë është një nyje LNDhub e pavlefshme.", + "add_lndhub_placeholder": "Adresa juaj e nyjes", + "add_placeholder": "portofoli im i parë", + "add_wallet_seed_length": "Gjatësia e frazës rigjeneruese", + "clipboard_bitcoin": "Keni një adresë Bitcoin në kujtesën e përkohshme. Dëshironi ta përdorni për një transaksion?", + "clipboard_lightning": "Keni një faturë Lightning në kujtesën e përkohshme. Dëshironi ta përdorni për një transaksion?", + "clear_clipboard_on_import": "Pastro kujtesën e përkohshme në import", + "details_advanced": "Të avancuara", + "details_are_you_sure": "Jeni i sigurt?", + "details_connected_to": "I lidhur me", + "details_del_wb_err": "Shuma e ballancës së dhënë nuk përputhet me ballancën e këtij portofoli. Ju lutemi provoni përsëri.", + "details_del_wb_q": "Ky portofol ka një ballancë. Para se të vazhdoni, ju lutemi vini re se nuk do të mund t'i rikuperoni fondet pa frazën rigjeneruese të këtij portofoli. Për të shmangur heqjen aksidentale, ju lutemi futni ballancën e portofolit tuaj prej {balance} satoshi.", + "details_delete": "Fshi", + "details_delete_wallet": "Fshi portofolin", + "details_derivation_path": "shteg derivimi", + "details_display": "Shfaqe në ekranin kryesor", + "details_export_backup": "Eksporto/Kopje rezervë", + "details_export_history": "Eksporto historikun në CSV", + "details_master_fingerprint": "Shenjë gishti kryesore", + "details_multisig_type": "multisig", + "details_show_xpub": "Shfaq XPUB-in e portofolit", + "swipe_balance_hide": "Fshih", + "swipe_balance_show": "Shfaq", + "drag_to_reorder": "Tërhiq për të rirenditur", + "clear_search": "Pastro kërkimin", + "details_use_with_hardware_wallet": "Përdor me portofol hardware", + "details_yes_delete": "Po, fshije", + "enter_bip38_password": "Fut fjalëkalimin për të dekriptuar", + "export_title": "Eksporti i portofolit", + "import_do_import": "Importo", + "import_passphrase": "Frazë sekrete", + "import_passphrase_title": "Frazë sekrete", + "import_passphrase_message": "Fut frazën sekrete nëse keni përdorur ndonjë", + "import_error": "Importimi dështoi. Ju lutemi sigurohuni që të dhënat e dhëna janë të vlefshme.", + "import_explanation": "Ju lutemi futni fjalët e frazës rigjeneruese, çelësin publik, WIF-in ose çfarëdo që keni. BlueWallet do të bëjë më të mirën për të hamendësuar formatin e duhur dhe për të importuar portofolin tuaj.", + "import_imported": "U importua", + "import_scan_qr": "Skano ose importo një file", + "import_success": "Portofoli juaj u importua me sukses.", + "import_success_watchonly": "Portofoli juaj u importua me sukses. PARALAJMËRIM: Ky është një portofol vetëm për shikim, NUK mund të shpenzoni prej tij.", + "import_search_accounts": "Kërko llogaritë", + "import_title": "Importo", + "learn_more": "Mëso më shumë", + "import_discovery_subtitle": "Zgjidh një portofol të zbuluar", + "import_discovery_derivation": "Përdor shteg derivimi të personalizuar", + "import_discovery_offline": "BlueWallet aktualisht është në modalitetin off-line. Në këtë modalitet, nuk mund të verifikojë ekzistencën e portofolit, kështu që duhet të zgjidhni manualisht atë të duhurin", + "import_derivation_subtitle": "Futni një shteg derivimi të personalizuar, dhe do të përpiqemi të zbulojmë portofolin tuaj.", + "import_derivation_title": "Shteg derivimi", + "import_wrong_path": "Shteg derivimi i gabuar", + "list_empty_txs1": "Transaksionet tuaja do të shfaqen këtu.", + "list_empty_txs1_lightning": "Portofoli Lightning duhet të përdoret për transaksionet tuaja të përditshme. Komisionet janë padrejtësisht të lira dhe shpejtësia është mahnitëse.", + "list_empty_txs2": "Filloni me portofolin tuaj.", + "list_empty_txs2_lightning": "\nPër ta filluar përdorimin, prekni Menaxho fondet dhe rimbushni ballancën tuaj.", + "list_latest_transaction": "Transaksioni i fundit", + "import_file": "Importo file", + "no_ln_wallet_error": "Para se të paguani një faturë Lightning, duhet të shtoni fillimisht një portofol Lightning.", + "looks_like_bip38": "Kjo duket si një çelës privat i mbrojtur me fjalëkalim (BIP38).", + "select_no_bitcoin_exp": "Kërkohet një portofol Bitcoin për të rimbushur portofolat Lightning. Ju lutemi krijoni ose importoni një.", + "pull_to_refresh": "Tërhiq për të rifreskuar", + "warning_do_not_disclose": "Asnjëherë mos ndani informacionin e mëposhtëm", + "scan_import": "Skano këtë QR kod për të importuar portofolin tuaj në një aplikacion tjetër.", + "write_down_header": "Krijo një kopje rezervë manuale", + "write_down": "Shkruani dhe ruani në mënyrë të sigurt këto fjalë. Përdorini ato për të rikuperuar portofolin tuaj më vonë.", + "wallet_type_this": "Ky tip portofoli është {type}.", + "copy_ln_url": "Kopjoni dhe ruani në mënyrë të sigurt këtë URL për të rikuperuar portofolin tuaj më vonë.", + "copy_ln_public": "Kopjoni dhe ruani në mënyrë të sigurt këtë informacion për të rikuperuar portofolin tuaj më vonë.", + "add_ln_wallet_first": "Duhet të shtoni fillimisht një portofol Lightning.", + "identity_pubkey": "Çelësi publik i identitetit", + "xpub_title": "XPUB-i i portofolit", + "manage_wallets_search_placeholder": "Kërko portofola, adresa, transaksione dhe memo", + "details_delete_wallet_error_message": "Pati një problem në konfirmimin nëse ky portofol u hoq nga njoftimet—kjo mund të jetë për shkak të një problemi me rrjetin ose lidhjes së dobët. Nëse vazhdoni, mund të vazhdoni të merrni njoftime për transaksionet që lidhen me këtë portofol, edhe pas fshirjes së tij.", + "details_delete_anyway": "Fshije gjithsesi" + }, + "total_balance_view": { + "hide": "Fshihe", + "title": "Balanca Totale", + "display_in_bitcoin": "Shfaq në Bitcoin", + "display_in_sats": "Shfaq në sats", + "display_in_fiat": "Shfaq në {currency}", + "explanation": "Shiko ballancën totale të të gjithë portofolave tuaj në ekranin e përmbledhjes." + }, + "multisig": { + "confirm": "Konfirmo", + "header": "Dërgo", + "share": "Shpërndaj", + "view": "Shiko", + "manage_keys": "Menaxho Çelësat", + "signatures_required_to_spend": "Firmat janë të nevojshme {number}", + "signatures_we_can_make": "mund të bëjmë {number}", + "scan_or_import_file": "Skano ose importo nga një file", + "lets_start": "Le të fillojmë", + "create": "Krijo", + "native_segwit_title": "Praktikat më të mira", + "co_sign_transaction": "Firmos një transaksion", + "what_is_vault_wallet": "portofol.", + "needs": "I duhet", + "quorum_header": "Kuorumi", + "wallet_type": "Tipi i Portofolit", + "create_new_key": "Krijo të ri", + "scan_or_open_file": "Skano ose hap file-in", + "input_fp": "Fut shenjat e gishtave", + "ms_help": "Ndihmë", + "ms_help_title2": "Ndrysho Çelësat", + "multisig_vault": "Kasafortë multisig", + "default_label": "Kasafortë multisig", + "multisig_vault_explain": "Siguria më e mirë për shuma të mëdha", + "provide_signature": "Jep firmën", + "provide_signature_details": "Përdor pajisjen dhe portofolin tënd ku ndodhet çelësi për ta firmosur këtë transaksion", + "provide_signature_details_bluewallet": "Në BlueWallet, shko te menyja e ekranit Dërgo dhe zgjidh ", + "provide_signature_next_steps": "Skano ose importo transaksionin e firmosur", + "provide_signature_next_steps_details": "Pasi portofoli juaj ta ketë firmosur me sukses transaksionin, skanoni QR kodin e dhënë ose importoni file-in shoqërues, dhe pastaj shqyrtoni të gjitha detajet e transaksionit para se ta transmetoni.", + "vault_key": "Çelësi i kasafortës {number}", + "required_keys_out_of_total": "Çelësat e kërkuar nga totali", + "fee": "Komisioni: {number}", + "fee_btc": "{number} BTC", + "shared_key_detected": "Bashkë-firmëtar i ndarë", + "shared_key_detected_question": "Një bashkë-firmëtar u nda me ju, dëshironi ta importoni?", + "how_many_signatures_can_bluewallet_make": "sa firma mund të bëjë BlueWallet", + "export_coordination_setup": "Eksporto konfigurimin e koordinimit", + "cosign_this_transaction": "Të bashkë-firmosni këtë transaksion?", + "wrapped_segwit_title": "Përputhshmëria më e mirë", + "legacy_title": "Legacy", + "what_is_vault": "Një kasafortë është një", + "what_is_vault_numberOfWallets": " multisig {m}-nga-{n} ", + "vault_advanced_customize": "Cilësimet e kasafortës", + "what_is_vault_description_number_of_vault_keys": " {m} çelësa kasafortë ", + "what_is_vault_description_to_spend": "për të shpenzuar dhe një të tretë që \nmund ta përdorni si kopje rezervë.", + "what_is_vault_description_to_spend_other": "për të shpenzuar.", + "quorum": "kuorum {m} nga {n}", + "of": "nga", + "invalid_mnemonics": "Kjo frazë mnemonike nuk duket të jetë e vlefshme.", + "invalid_cosigner": "Të dhëna të pavlefshme bashkë-firmëtari", + "not_a_multisignature_xpub": "Ky nuk është një XPUB nga një portofol multisig!", + "invalid_cosigner_format": "Bashkë-firmëtar i pasaktë: Ky nuk është një bashkë-firmëtar për formatin {format}.", + "i_have_mnemonics": "Kam një frazë rigjeneruese për këtë çelës.", + "type_your_mnemonics": "Fut një frazë rigjeneruese për të importuar çelësin tënd ekzistues të kasafortës.", + "this_is_cosigners_xpub": "Ky është XPUB-i i bashkë-firmëtarit—gati për t'u importuar në një portofol tjetër. Është e sigurt të ndahet.", + "this_is_cosigners_xpub_airdrop": "Nëse ndani nëpërmjet AirDrop, marrësit duhet të jenë në ekranin e koordinimit.", + "wallet_key_created": "Çelësi i kasafortës suaj u krijua. Merrni një moment për të bërë një kopje rezervë të sigurt të frazës mnemonike.", + "are_you_sure_seed_will_be_lost": "Jeni i sigurt? Fraza juaj mnemonike do të humbasë nëse nuk keni një kopje rezervë.", + "forget_this_seed": "Harroje këtë frazë rigjeneruese dhe përdor XPUB-in në vend të saj.", + "view_edit_cosigners": "Shiko/Modifiko bashkë-firmëtarët", + "this_cosigner_is_already_imported": "Ky bashkë-firmëtar tashmë është i importuar.", + "export_signed_psbt": "Eksporto PSBT-në e firmosur", + "input_fp_explain": "Kapërceje për të përdorur atë të parazgjedhur (00000000)", + "input_path": "Fut shtegun e derivimit", + "input_path_explain": "Kapërceje për të përdorur atë të parazgjedhur ({default})", + "ms_help_title": "Si funksionojnë kasafortat multisig: këshilla dhe truke", + "ms_help_text": "Një portofol me çelësa të shumtë, për siguri të shtuar ose ruajtje të ndarë", + "ms_help_title1": "Rekomandohen pajisje të shumta.", + "ms_help_1": "Kasaforta do të funksionojë me aplikacionet e tjera BlueWallet dhe me portofolat e përputhshëm me PSBT, si Electrum, Specter, Coldcard, Cobo Vault, etj.", + "ms_help_2": "Mund të krijoni të gjithë çelësat e kasafortës në këtë pajisje dhe t'i hiqni ose t'i modifikoni më vonë. Pasja e të gjithë çelësave në të njëjtën pajisje ka sigurinë e barabartë me një portofol të zakonshëm Bitcoin.", + "ms_help_title3": "Kopje rezervë të kasafortës", + "ms_help_3": "Te opsionet e portofolit, do të gjeni kopjen rezervë të kasafortës suaj dhe kopjen rezervë vetëm për shikim. Kjo kopje rezervë është si një hartë për portofolin tuaj. Është thelbësore për rikuperimin e portofolit në rast se humbni një nga frazat tuaja rigjeneruese.", + "ms_help_title4": "Importimi i kasafortave", + "ms_help_4": "Për të importuar një multisig, përdorni file-in tuaj të kopjes rezervë dhe veçorinë e importit. Nëse keni vetëm fraza rigjeneruese dhe XPUB, mund të përdorni butonin individual të importit kur krijoni çelësat e kasafortës.", + "ms_help_title5": "Modaliteti i avancuar", + "ms_help_5": "Si parazgjedhje, BlueWallet do të gjenerojë një kasafortë 2-nga-3. Për të krijuar një kuorum të ndryshëm ose për të ndryshuar tipin e adresës, aktivizo modalitetin e avancuar te cilësimet." + }, + "is_it_my_address": { + "title": "A është adresa ime?", + "enter_address": "Fut adresën", + "check_address": "Kontrollo adresën", + "view_qrcode": "Shiko QR kodin", + "owns": "{label} zotëron {address}", + "no_wallet_owns_address": "Asnjë nga portofolat e disponueshëm nuk e zotëron adresën e dhënë." + }, + "autofill_word": { + "generate_word": "Gjenero fjalën përfundimtare.", + "title": "Fjala përfundimtare e frazës rigjeneruese", + "enter": "Fut frazën tënde mnemonike të pjesshme", + "error": "Hyrja nuk është një frazë mnemonike e pjesshme me 11 ose 23 fjalë. Ju lutemi provoni përsëri." + }, + "cc": { + "change": "Ndrysho", + "empty": "Ky portofol s’ka asnjë monedhë për momentin.", + "freeze": "Ngrije", + "freezeLabel": "Ngrije", + "freezeLabel_un": "Shkrije", + "header": "Kontroll i Monedhave", + "use_coin": "Përdor Monedhën", + "use_coins": "Përdor Monedhat", + "sort_height": "Lartësia", + "sort_value": "Vlerë", + "sort_label": "Etiketë", + "sort_status": "Statusi", + "sort_by": "Radhiti sipas", + "coins_selected": "Monedha të zgjedhura ({number})", + "selected_summ": "{value} të zgjedhura", + "tip": "Kjo veçori ju lejon të shihni, të etiketoni, të ngrini ose të zgjidhni monedha për një menaxhim më të mirë të portofolit. Mund të zgjidhni disa monedha duke prekur rrathët me ngjyra.", + "sort_asc": "Në ngjitje", + "sort_desc": "Në zbritje" + }, + "units": { + "BTC": "BTC", + "MAX": "Maks", + "sat_vbyte": "sat/vByte", + "sats": "sats" + }, + "addresses": { + "copy_private_key": "Kopjo çelësin privat", + "sign_sign": "Firmos", + "sign_verify": "Verifiko", + "sign_signature_correct": "Verifikimi u krye me Sukses", + "sign_signature_incorrect": "Verifikimi nuk pati Sukses", + "sign_placeholder_address": "Adresa", + "sign_placeholder_message": "Mesazhi", + "sign_placeholder_signature": "Firma", + "addresses_title": "Adresat", + "type_change": "Ndrysho", + "type_receive": "Merr", + "type_used": "I përdorur", + "transactions": "Transaksionet", + "sensitive_private_key": "Paralajmërim: çelësat privatë janë jashtëzakonisht të ndjeshëm. Të vazhdohet?", + "sign_title": "Firmos/Verifiko mesazhin", + "sign_help": "Këtu mund të krijoni ose të verifikoni një firmë kriptografike bazuar në një adresë Bitcoin." + }, + "lnurl_auth": { + "register_question_part_1": "Dëshironi të regjistroni një llogari në", + "register_question_part_2": "duke përdorur portofolin tuaj Lightning?", + "register_answer": "Ju keni regjistruar me sukses një llogari në {hostname}!", + "login_question_part_1": "Dëshironi të hyni në", + "login_question_part_2": "duke përdorur portofolin tuaj Lightning?", + "login_answer": "Ju keni hyrë me sukses në {hostname}!", + "link_question_part_1": "Dëshironi të lidhni llogarinë tuaj në", + "link_question_part_2": "me portofolin tuaj Lightning?", + "link_answer": "Portofoli juaj Lightning u lidh me sukses me llogarinë tuaj në {hostname}!", + "auth_question_part_1": "Dëshironi të autentikoheni në", + "auth_question_part_2": "duke përdorur portofolin tuaj Lightning?", + "auth_answer": "Ju jeni autentikuar me sukses në {hostname}!", + "could_not_auth": "Nuk mundëm t'ju autentikojmë në {hostname}.", + "authenticate": "Autentiko" + }, + "bip47": { + "payment_code": "Kodi i Pagesës", + "contacts": "Kontaktet", + "pay_this_contact": "Paguaj këtë kontakt", + "rename_contact": "Riemëro kontaktin", + "hide_contact": "Fshihe kontaktin", + "rename": "Riemëro", + "add_contact": "Shto Kontakt", + "provide_payment_code": "Fut kodin e pagesës", + "bip47_explain": "Kod i ripërdorshëm dhe i ndashëm", + "bip47_explain_subtitle": "BIP47", + "purpose": "Kod i ripërdorshëm dhe i ndashëm (BIP47)", + "copy_payment_code": "Kopjo kodin e pagesës", + "provide_name": "Jep një emër të ri për këtë kontakt", + "invalid_pc": "Kod pagese i pavlefshëm", + "notification_tx_unconfirmed": "Transaksioni i njoftimit nuk është konfirmuar ende, ju lutemi prisni", + "failed_create_notif_tx": "Krijimi i transaksionit on-chain dështoi", + "onchain_tx_needed": "Nevojitet transaksion on-chain", + "notif_tx_sent": "Transaksioni i njoftimit u dërgua. Ju lutemi prisni që ai të konfirmohet", + "notif_tx": "Transaksion njoftimi", + "not_found": "Kodi i pagesës nuk u gjet" + } +} diff --git a/loc/sr_RS.json b/loc/sr_RS.json index 55ea714d0ca..6dfa1284d43 100644 --- a/loc/sr_RS.json +++ b/loc/sr_RS.json @@ -1,15 +1,704 @@ { "_": { + "bad_password": "Neispravna lozinka. Pokušajte ponovo.", "cancel": "Poništi", "continue": "Nastavi", - "enter_password": "Unesite password", + "copied": "Kopirano!", + "discard_changes": "Odbaciti izmene?", + "discard_changes_explain": "Imate nesačuvane izmene. Da li ste sigurni da želite da ih odbacite i napustite ekran?", + "enter_password": "Unesite lozinku", "never": "Nikad", "of": "{number} od {total}", + "ok": "U redu", + "enter_url": "Unesite URL", + "storage_is_encrypted": "Vaša memorija je šifrovana. Lozinka je potrebna za dešifrovanje.", "yes": "Da", "no": "Ne", - "wallet_key": "Ključ novčanika" + "save": "Sačuvaj...", + "seed": "Seed", + "success": "Uspešno", + "wallet_key": "Ključ novčanika", + "close": "Zatvori", + "change_input_currency": "Promeni valutu unosa", + "refresh": "Osveži", + "pick_image": "Izaberi iz galerije", + "pick_file": "Izaberi fajl", + "enter_amount": "Unesite iznos", + "qr_custom_input_button": "Dodirnite 10 puta za prilagođeni unos", + "unlock": "Otključaj", + "ssl_port": "SSL port", + "suggested": "Predloženo", + "clipboard": "Privremena memorija", + "port": "Port" + }, + "azteco": { + "codeIs": "Vaš kod vaučera je", + "errorBeforeRefeem": "Pre iskorišćenja, prvo morate dodati Bitcoin novčanik.", + "errorSomething": "Nešto je pošlo po zlu. Da li je ovaj vaučer još uvek važeći?", + "redeem": "Iskoristi u novčanik", + "redeemButton": "Iskoristi", + "success": "Uspešno", + "successMessage": "Vaučer je uspešno iskorišćen! Vaša sredstva bi uskoro trebalo da stignu u vaš Bitcoin novčanik.", + "title": "Iskoristi Azte.co vaučer" + }, + "entropy": { + "save": "Sačuvaj", + "title": "Entropija", + "undo": "Poništi", + "amountOfEntropy": "{bits} od {limit} bitova" + }, + "errors": { + "broadcast": "Slanje nije uspelo.", + "error": "Greška", + "network": "Mrežna greška" + }, + "lnd": { + "errorInvoiceExpired": "Faktura je istekla.", + "expired": "Isteklo", + "expiresIn": "Ističe za {time} minuta", + "payButton": "Plati", + "payment": "Plaćanje", + "placeholder": "Faktura ili adresa", + "potentialFee": "Potencijalna provizija: {fee}", + "refill": "Dopuni", + "refill_create": "Da biste nastavili, kreirajte Bitcoin novčanik za dopunu.", + "refill_external": "Dopuni iz spoljnog novčanika", + "refill_lnd_balance": "Dopuni stanje Lightning novčanika", + "sameWalletAsInvoiceError": "Ne možete platiti fakturu istim novčanikom koji je korišćen za njeno kreiranje.", + "title": "Upravljanje sredstvima" + }, + "lndViewInvoice": { + "additional_info": "Dodatne informacije", + "for": "Za:", + "lightning_invoice": "Lightning faktura", + "please_pay_between_and": "Molimo platite između {min} i {max}", + "please_pay": "Molimo platite", + "preimage": "Pre-image", + "date_time": "Datum i vreme", + "wasnt_paid_and_expired": "Ova faktura nije plaćena i istekla je.", + "sats": "sats." + }, + "plausibledeniability": { + "create_fake_storage": "Kreiraj šifrovanu memoriju", + "create_password_explanation": "Lozinka za lažnu memoriju ne sme da se poklapa sa lozinkom za vašu glavnu memoriju.", + "help": "U određenim okolnostima, možete biti primorani da otkrijete lozinku. Da bi vaši coinovi bili bezbedni, BlueWallet može kreirati još jednu šifrovanu memoriju sa drugom lozinkom. Pod pritiskom, možete otkriti ovu lozinku trećoj strani. Ako je unesete u BlueWallet, otključaće se nova „lažna“ memorija. Trećoj strani će izgledati legitimno, ali će tajno čuvati vašu glavnu memoriju sa coinovima bezbednom.", + "help2": "Nova memorija će biti potpuno funkcionalna, i u nju možete čuvati neke minimalne iznose kako bi izgledala verodostojnije.", + "password_should_not_match": "Lozinka se trenutno koristi. Probajte drugu lozinku.", + "title": "Uverljivo poricanje" + }, + "pleasebackup": { + "ask": "Da li ste sačuvali seed frazu vašeg novčanika? Ova seed fraza je potrebna za pristup vašim sredstvima u slučaju da izgubite ovaj uređaj. Bez seed fraze, vaša sredstva će biti trajno izgubljena.", + "ask_no": "Ne, nisam.", + "ask_yes": "Da, jesam.", + "ok": "U redu, zapisao sam.", + "ok_lnd": "U redu, sačuvao sam.", + "text": "Odvojite trenutak da zapišete ovu mnemoničku frazu na komad papira.\nTo je vaš backup i možete ga iskoristiti za vraćanje novčanika.", + "text_lnd": "Sačuvajte ovaj backup novčanika. Omogućava vam vraćanje novčanika u slučaju gubitka.", + "title": "Vaš novčanik je kreiran." + }, + "receive": { + "details_create": "Kreiraj", + "details_label": "Opis", + "details_setAmount": "Primi sa iznosom", + "details_share": "Podeli...", + "address_not_found": "Nije moguće generisati adresu za primanje.", + "header": "Primi", + "reset": "Resetuj", + "maxSats": "Maksimalni iznos je {max} sats", + "maxSatsFull": "Maksimalni iznos je {max} sats ili {currency}", + "minSats": "Minimalni iznos je {min} sats", + "minSatsFull": "Minimalni iznos je {min} sats ili {currency}", + "qrcode_for_the_address": "QR kod za adresu", + "bip47_explanation": "Payment kodovi su univerzalna adresa koja izbegava otkrivanje adresa vašeg novčanika. Sve usluge ih ne podržavaju." + }, + "send": { + "provided_address_is_invoice": "Izgleda da je ova adresa za Lightning fakturu. Molimo idite u vaš Lightning novčanik da biste izvršili plaćanje za ovu fakturu.", + "broadcastButton": "Pošalji", + "broadcastError": "Greška", + "broadcastNone": "Unesite hex transakcije", + "broadcastPending": "Na čekanju", + "broadcastSuccess": "Uspešno", + "confirm_header": "Potvrdi", + "confirm_sendNow": "Pošalji odmah", + "create_amount": "Iznos", + "create_broadcast": "Pošalji", + "create_copy": "Kopiraj i pošalji kasnije", + "create_details": "Detalji", + "create_fee": "Provizija", + "create_memo": "Beleška", + "create_satoshi_per_vbyte": "Satoši po vByte", + "create_this_is_hex": "Ovo je hex vaše transakcije—potpisan i spreman za slanje na mrežu.", + "create_to": "Za", + "create_tx_size": "Veličina transakcije", + "create_verify": "Proveri na coinb.in", + "details_insert_contact": "Umetni kontakt", + "details_add_rec_add": "Dodaj primaoca", + "details_add_rec_rem": "Ukloni primaoca", + "details_add_recc_rem_all_alert_description": "Da li ste sigurni da želite da uklonite sve primaoce?", + "details_add_rec_rem_all": "Ukloni sve primaoce", + "details_recipients_title": "Primaoci", + "details_recipient_title": "Primalac #{number} od #{total}", + "please_complete_recipient_title": "Nepotpun primalac", + "please_complete_recipient_details": "Molimo popunite detalje primaoca #{number} pre dodavanja novog primaoca.", + "details_address": "Adresa", + "details_address_field_is_not_valid": "Adresa nije važeća.", + "details_adv_fee_bump": "Dozvoli povećanje provizije", + "details_adv_full": "Koristi ukupno stanje", + "details_adv_full_sure": "Da li ste sigurni da želite da koristite ukupno stanje vašeg novčanika za ovu transakciju?", + "details_adv_full_sure_frozen": "Da li ste sigurni da želite da koristite ukupno stanje vašeg novčanika za ovu transakciju? Imajte na umu da su zamrznuti coinovi isključeni.", + "details_adv_import": "Uvezi transakciju", + "details_adv_import_qr": "Uvezi transakciju (QR)", + "details_amount_field_is_not_valid": "Iznos nije važeći.", + "details_amount_field_is_less_than_minimum_amount_sat": "Navedeni iznos je premali. Unesite iznos veći od 500 sats.", + "details_create": "Kreiraj fakturu", + "details_error_decode": "Nije moguće dekodirati Bitcoin adresu", + "details_fee_field_is_not_valid": "Provizija nije važeća.", + "details_frozen": "{amount} BTC je zamrznuto.", + "details_next": "Sledeće", + "details_no_signed_tx": "Izabrani fajl ne sadrži transakciju koja se može uvesti.", + "details_note_placeholder": "Beleška za sebe", + "details_scan": "Skeniraj", + "details_scan_hint": "Dupli dodir za skeniranje ili uvoz odredišta", + "details_scan_error": "Greška pri skeniranju", + "details_total_exceeds_balance": "Iznos slanja premašuje raspoloživo stanje.", + "details_total_exceeds_balance_frozen": "Iznos slanja premašuje raspoloživo stanje. Imajte na umu da su zamrznuti coinovi isključeni.", + "details_unrecognized_file_format": "Neprepoznat format fajla", + "details_wallet_before_tx": "Pre kreiranja transakcije, prvo morate dodati Bitcoin novčanik.", + "dynamic_init": "Inicijalizacija", + "dynamic_next": "Sledeće", + "dynamic_prev": "Prethodno", + "dynamic_start": "Počni", + "dynamic_stop": "Zaustavi", + "fee_custom": "Prilagođeno", + "insert_custom_fee": "Unesite proviziju", + "fee_fast": "Brzo", + "fee_medium": "Srednje", + "fee_replace_minvb": "Ukupna stopa provizije (satoši po vByte) koju želite da platite trebalo bi da bude veća od {min} sat/vByte.", + "fee_satvbyte": "u sat/vByte", + "fee_slow": "Sporo", + "header": "Pošalji", + "input_clear": "Obriši", + "input_done": "Gotovo", + "input_paste": "Nalepi", + "input_total": "Ukupno:", + "permission_camera_message": "Potrebna nam je vaša dozvola za korišćenje kamere.", + "psbt_sign": "Potpiši transakciju", + "invalid_psbt": "Pružen je nevažeći PSBT.", + "open_settings": "Otvori podešavanja", + "permission_storage_denied_message": "BlueWallet nije u mogućnosti da sačuva ovaj fajl. Otvorite podešavanja uređaja i omogućite dozvolu za memoriju.", + "permission_storage_title": "Dozvola za pristup memoriji", + "psbt_clipboard": "Kopiraj u clipboard", + "psbt_this_is_psbt": "Ovo je delimično potpisana Bitcoin transakcija (PSBT). Molimo završite potpisivanje pomoću vašeg hardverskog novčanika.", + "psbt_tx_export": "Izvezi u fajl", + "no_tx_signing_in_progress": "Nema potpisivanja transakcije u toku.", + "outdated_rate": "Kurs je poslednji put ažuriran: {date}", + "psbt_tx_open": "Otvori potpisanu transakciju", + "psbt_tx_scan": "Skeniraj potpisanu transakciju", + "qr_error_no_qrcode": "Nismo uspeli da pronađemo važeći QR kod u izabranoj slici. Uverite se da slika sadrži samo QR kod i nikakav dodatni sadržaj poput teksta ili dugmadi.", + "reset_amount": "Resetuj iznos", + "reset_amount_confirm": "Da li želite da resetujete iznos?", + "success_done": "Gotovo", + "txSaved": "Fajl transakcije ({filePath}) je sačuvan.", + "file_saved_at_path": "Fajl ({filePath}) je sačuvan.", + "cant_send_to_silentpayment_adress": "Ovaj novčanik ne može da šalje na SilentPayment adrese", + "cant_send_to_bip47": "Ovaj novčanik ne može da šalje na BIP47 Payment kodove", + "cant_find_bip47_notification": "Prvo dodajte ovaj Payment kod u kontakte", + "problem_with_psbt": "Problem sa PSBT", + "fee_10m": "10 min", + "fee_3h": "3 h", + "fee_1d": "1 d" }, "settings": { - "electrum_clear_alert_cancel": "Poništi" + "about": "O aplikaciji", + "about_awesome": "Izrađeno uz pomoć izvanrednog", + "about_backup": "Uvek napravite backup svojih ključeva!", + "about_free": "BlueWallet je besplatan projekat otvorenog koda. Kreirali su ga Bitcoin korisnici.", + "about_license": "MIT licenca", + "about_release_notes": "Napomene o izdanju", + "about_review": "Ostavite nam recenziju", + "performance_score": "Rezultat performansi: {num}", + "run_performance_test": "Testiraj performanse", + "about_selftest": "Pokreni samotestiranje", + "block_explorer_invalid_custom_url": "Pruženi URL nije važeći. Unesite važeći URL koji počinje sa http:// ili https://.", + "about_selftest_electrum_disabled": "Samotestiranje nije dostupno u Electrum režimu van mreže. Onemogućite režim van mreže i pokušajte ponovo.", + "about_selftest_ok": "Svi interni testovi su uspešno prošli. Novčanik radi dobro.", + "about_sm_github": "GitHub", + "about_sm_telegram": "Telegram kanal", + "privacy_temporary_screenshots": "Dozvoli snimanje ekrana", + "privacy_temporary_screenshots_instructions": "Zaštita od snimanja ekrana će biti privremeno isključena, omogućavajući snimke ekrana i snimanje ekrana. Zaštita će se automatski ponovo aktivirati kada zatvorite i otvorite BlueWallet.", + "biometrics": "Biometrija", + "biometrics_no_longer_available": "Podešavanja vašeg uređaja su promenjena i više se ne podudaraju sa izabranim bezbednosnim podešavanjima u aplikaciji. Molimo ponovo omogućite biometriju ili pristupni kod, zatim restartujte aplikaciju da bi se primenile ove promene.", + "biom_10times": "Pokušali ste da unesete lozinku 10 puta. Da li želite da resetujete memoriju? Time će se ukloniti svi novčanici i dešifrovati vaša memorija.", + "biom_conf_identity": "Molimo potvrdite svoj identitet.", + "biom_no_passcode": "Vaš uređaj nema postavljen pristupni kod ili biometriju. Da biste nastavili, podesite pristupni kod ili biometriju u podešavanjima aplikacije.", + "biom_remove_decrypt": "Svi vaši novčanici će biti uklonjeni i vaša memorija će biti dešifrovana. Da li ste sigurni da želite da nastavite?", + "currency": "Valuta", + "currency_source": "Kurs se dobija od", + "currency_fetch_error": "Došlo je do greške pri dobijanju kursa za izabranu valutu.", + "default_title": "Pri pokretanju", + "donate": "Doniraj", + "donate_description": "Pomozite nam da Blue ostane besplatan!", + "electrum_connected": "Povezano", + "electrum_connected_not": "Nije povezano", + "electrum_error_connect": "Nije moguće povezati se sa navedenim Electrum serverom", + "electrum_error_connect_tor": "Nije moguće povezati se sa navedenim Electrum serverom. Uverite se da je Orbot aplikacija povezana i pokušajte ponovo.", + "lndhub_uri": "Npr. {example}", + "electrum_host": "Npr. {example}", + "electrum_offline_mode": "Režim van mreže", + "electrum_offline_description": "Kada je omogućeno, vaši Bitcoin novčanici neće pokušavati da preuzmu stanja ili transakcije.", + "electrum_port": "Port, obično {example}", + "use_ssl": "Koristi SSL", + "electrum_saved": "Vaše izmene su uspešno sačuvane. Možda će biti potrebno ponovno pokretanje BlueWallet-a da bi izmene stupile na snagu.", + "set_electrum_server_as_default": "Postavi {server} kao podrazumevani Electrum server?", + "set_lndhub_as_default": "Postavi {url} kao podrazumevani LNDhub server?", + "electrum_settings_server": "Electrum server", + "electrum_preferred_server": "Preferirani server", + "electrum_preferred_server_description": "Unesite server koji želite da vaš novčanik koristi za sve Bitcoin aktivnosti. Jednom postavljen, vaš novčanik će ekskluzivno koristiti ovaj server za proveru stanja, slanje transakcija i preuzimanje mrežnih podataka. Uverite se da verujete ovom serveru pre nego što ga postavite.", + "electrum_unable_to_connect": "Nije moguće povezati se sa {server}.", + "electrum_history": "Istorija", + "electrum_reset_to_default": "Ovo će omogućiti BlueWallet-u da nasumično izabere server sa liste servera.", + "electrum_reset": "Vrati na podrazumevano", + "electrum_reset_to_default_and_clear_history": "Vrati na podrazumevano i obriši istoriju", + "encrypt_decrypt": "Dešifruj memoriju", + "encrypt_decrypt_q": "Da li ste sigurni da želite da dešifrujete svoju memoriju? Time će se omogućiti pristup vašim novčanicima bez lozinke.", + "encrypt_enc_and_pass": "Zaštićeno lozinkom", + "encrypt_storage_explanation_headline": "Omogući šifrovanje memorije", + "encrypt_storage_explanation_description_line1": "Omogućavanje šifrovanja memorije dodaje dodatni sloj zaštite vašoj aplikaciji obezbeđujući način na koji se vaši podaci čuvaju na uređaju. Time se otežava bilo kome da pristupi vašim informacijama bez dozvole.", + "encrypt_storage_explanation_description_line2": "Međutim, važno je znati da ovo šifrovanje štiti samo pristup novčanicima sačuvanim u keychain-u uređaja. Ne postavlja lozinku ili dodatnu zaštitu na same novčanike.", + "i_understand": "Razumem", + "block_explorer": "Block explorer", + "block_explorer_preferred": "Koristi preferirani block explorer", + "block_explorer_error_saving_custom": "Greška pri čuvanju preferiranog block explorer-a", + "encrypt_title": "Bezbednost", + "encrypt_tstorage": "Memorija", + "encrypt_use": "Koristi {type}", + "set_as_preferred": "Postavi kao preferirano", + "set_as_preferred_electrum": "Postavljanje {host}:{port} kao preferiranog servera onemogućiće nasumično povezivanje sa predloženim serverom.", + "encrypted_feature_disabled": "Ova funkcija se ne može koristiti sa omogućenom šifrovanom memorijom.", + "encrypt_use_expl": "{type} će se koristiti za potvrdu vašeg identiteta pre kreiranja transakcije, otključavanja, izvoza ili brisanja novčanika.", + "biometrics_fail": "Ako {type} nije omogućeno ili ne uspe da otključa, možete koristiti pristupni kod uređaja kao alternativu.", + "general": "Opšte", + "general_continuity": "Kontinuitet", + "general_continuity_e": "Kada je omogućeno, moći ćete da pregledate izabrane novčanike i transakcije koristeći ostale Apple iCloud povezane uređaje.", + "groundcontrol_explanation": "GroundControl je besplatan server za push obaveštenja otvorenog koda za Bitcoin novčanike. Možete instalirati sopstveni GroundControl server i ovde uneti njegov URL da ne biste zavisili od infrastrukture BlueWallet-a. Ostavite prazno da biste koristili podrazumevani GroundControl server.", + "header": "Podešavanja", + "language": "Jezik", + "last_updated": "Poslednje ažuriranje", + "language_isRTL": "Potrebno je ponovno pokretanje BlueWallet-a da bi orijentacija jezika stupila na snagu.", + "license": "Licenca", + "lightning_error_lndhub_uri": "Nevažeći LNDhub URI", + "lightning_error_lndhub_uri_tor": "Nevažeći LNDhub URI. Uverite se da je Orbot aplikacija povezana i pokušajte ponovo.", + "lightning_saved": "Vaše izmene su uspešno sačuvane.", + "lightning_settings": "Lightning podešavanja", + "lightning_settings_explain": "Da biste se povezali sa svojim LND nodom, instalirajte LNDhub i ovde u podešavanjima unesite njegov URL. Imajte na umu da će samo novčanici kreirani nakon čuvanja izmena biti povezani sa navedenim LNDhub-om.", + "lndhub_github": "GitHub repozitorijum", + "network": "Mreža", + "network_broadcast": "Pošalji transakciju", + "network_electrum": "Electrum server", + "electrum_suggested_description": "Kada preferirani server nije postavljen, predloženi server će biti izabran za nasumično korišćenje.", + "not_a_valid_uri": "Nevažeći URI", + "notifications": "Obaveštenja", + "open_link_in_explorer": "Otvori link u explorer-u", + "password": "Lozinka", + "password_explain": "Unesite lozinku koju ćete koristiti za otključavanje vaše memorije.", + "plausible_deniability": "Uverljivo poricanje", + "privacy": "Privatnost", + "privacy_read_clipboard": "Čitaj clipboard", + "privacy_system_settings": "Sistemska podešavanja", + "privacy_quickactions": "Prečice novčanika", + "privacy_quickactions_explanation": "Dodirnite i držite ikonu BlueWallet aplikacije da biste brzo pregledali stanje vašeg novčanika.", + "privacy_clipboard_explanation": "Pruža prečice ako se adresa ili faktura pronađe u vašem clipboard-u.", + "privacy_do_not_track": "Onemogući analitiku", + "privacy_do_not_track_explanation": "Informacije o performansama i pouzdanosti neće biti poslate na analizu.", + "rate": "Kurs", + "push_notifications_explanation": "Omogućavanjem obaveštenja, token vašeg uređaja će biti poslat serveru, zajedno sa adresama novčanika i ID-ovima transakcija za sve novčanike i transakcije napravljene nakon omogućavanja obaveštenja. Token uređaja se koristi za slanje obaveštenja, a informacije o novčaniku nam omogućavaju da vas obavestimo o dolaznim Bitcoin uplatama ili potvrdama transakcija.\n\nPrenosi se samo informacije nakon što omogućite obaveštenja—ništa od pre toga se ne prikuplja.\n\nOnemogućavanjem obaveštenja, sve ove informacije će biti uklonjene sa servera. Pored toga, brisanjem novčanika iz aplikacije takođe će se ukloniti pridružene informacije sa servera.", + "selfTest": "Samotestiranje", + "save": "Sačuvaj", + "saved": "Sačuvano", + "success_transaction_broadcasted": "Vaša transakcija je uspešno poslata!", + "total_balance": "Ukupno stanje", + "total_balance_explanation": "Prikaži ukupno stanje svih vaših novčanika na vidžetima početnog ekrana.", + "widgets": "Vidžeti", + "tools": "Alati", + "electrum_status": "Status" + }, + "notifications": { + "would_you_like_to_receive_notifications": "Da li želite da primate obaveštenja kada primite dolazne uplate?", + "notifications_subtitle": "Dolazne uplate i potvrde transakcija", + "no_and_dont_ask": "Ne, i nemoj me više pitati.", + "permission_denied_message": "Odbili ste dozvolu za slanje obaveštenja. Ako želite da primate obaveštenja, omogućite ih u podešavanjima vašeg uređaja." + }, + "transactions": { + "cancel_explain": "Zamenićemo ovu transakciju onom koja vama isplaćuje i ima veće provizije. Time se efektivno otkazuje trenutna transakcija. Ovo se naziva RBF—Replace by Fee.", + "cancel_no": "Ova transakcija se ne može zameniti.", + "cancel_title": "Otkaži ovu transakciju (RBF)", + "transaction_loading_error": "Došlo je do problema pri učitavanju transakcije. Pokušajte ponovo kasnije.", + "transaction_not_available": "Transakcija nije dostupna", + "confirmations_lowercase": "{confirmations} potvrda", + "expand_note": "Proširi belešku", + "cpfp_create": "Kreiraj", + "cpfp_exp": "Kreiraćemo još jednu transakciju koja troši vašu nepotvrđenu transakciju. Ukupna provizija će biti veća od originalne provizije transakcije, tako da bi trebalo da se rudari brže. Ovo se naziva CPFP—Child Pays for Parent.", + "cpfp_no_bump": "Provizija ove transakcije se ne može povećati.", + "cpfp_title": "Povećaj proviziju (CPFP)", + "details_balance_hide": "Sakrij stanje", + "details_balance_show": "Prikaži stanje", + "details_copy": "Kopiraj", + "details_copy_block_explorer_link": "Kopiraj link block explorer-a", + "details_copy_note": "Kopiraj belešku", + "details_copy_txid": "Kopiraj ID transakcije", + "details_inputs": "Ulazi", + "details_outputs": "Izlazi", + "date": "Datum", + "details_received": "Primljeno", + "details_view_in_browser": "Pregledaj u pregledaču", + "details_title": "Transakcija", + "incoming_transaction": "Dolazna transakcija", + "outgoing_transaction": "Odlazna transakcija", + "expired_transaction": "Istekla transakcija", + "pending_transaction": "Transakcija na čekanju", + "offchain": "Off-chain", + "onchain": "On-chain", + "details_to": "Izlaz", + "enable_offline_signing": "Ovaj novčanik se ne koristi u kombinaciji sa potpisivanjem van mreže. Da li želite da to omogućite sada?", + "list_conf": "Potvrde: {number}", + "pending": "Na čekanju", + "pending_with_amount": "Na čekanju {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "Procena: za ~10 minuta", + "eta_3h": "Procena: za ~3 sata", + "eta_1d": "Procena: za ~1 dan", + "list_title": "Transakcije", + "list_title_sent": "Poslato", + "list_title_received": "Primljeno", + "transaction": "Transakcija", + "open_url_error": "Nije moguće otvoriti link pomoću podrazumevanog pregledača. Promenite podrazumevani pregledač i pokušajte ponovo.", + "rbf_explain": "Zamenićemo ovu transakciju onom sa većom provizijom kako bi se brže rudarila. Ovo se naziva RBF—Replace by Fee.", + "rbf_title": "Ubrzaj (RBF)", + "status_bump": "Ubrzaj", + "status_cancel": "Otkaži", + "transactions_count": "Broj transakcija", + "txid": "ID transakcije", + "updating": "Ažuriranje...", + "watchOnlyWarningTitle": "Bezbednosno upozorenje", + "watchOnlyWarningDescription": "Budite oprezni sa prevarantima koji često koriste „watch-only“ novčanike kako bi prevarili korisnike. Ovi novčanici vam ne dozvoljavaju kontrolu ili slanje sredstava; samo vam omogućavaju pregled stanja.", + "custom_fee_warning_title": "Upozorenje", + "custom_fee_warning_description": "Provizije ispod 1 sat/vB su važeće, ali možda neće biti prosleđene zbog politike nodova.", + "details_eta_analyzing": "Analiziranje...", + "details_sent": "Poslato", + "details_section": "Detalji", + "details_network_fee": "Mrežna provizija", + "details_to_address": "Za", + "details_note": "Beleška", + "details_add_note": "dodaj", + "details_advanced": "Napredno", + "details_fee_rate": "Stopa provizije", + "details_size": "Veličina", + "details_virtual_size": "Virtuelna veličina", + "details_tx_hex": "Hex transakcije", + "details_inputs_count": "Ulazi ({count})", + "details_outputs_count": "Izlazi ({count})", + "details_explorer": "explorer", + "details_id": "ID" + }, + "wallets": { + "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Jednostavan i moćan Bitcoin novčanik", + "add_create": "Kreiraj", + "total_balance": "Ukupno stanje", + "add_entropy_reset_title": "Resetuj entropiju", + "add_entropy_reset_message": "Promena tipa novčanika će resetovati trenutnu entropiju. Da li želite da nastavite?", + "add_entropy": "Entropija", + "add_entropy_bytes": "{bytes} bajtova entropije", + "add_entropy_generated": "{gen} bajtova generisane entropije", + "add_entropy_provide": "Obezbedi entropiju bacanjem kockica", + "add_entropy_remain": "{gen} bajtova generisane entropije. Preostalih {rem} bajtova će biti dobijeno iz sistemskog generatora slučajnih brojeva.", + "add_import_wallet": "Uvezi novčanik", + "add_lightning": "Lightning", + "add_lightning_explain": "Za potrošnju trenutnim transakcijama", + "add_lndhub": "Poveži se na vaš LNDhub", + "add_lndhub_error": "Pružena adresa noda je nevažeći LNDhub nod.", + "add_lndhub_placeholder": "Adresa vašeg noda", + "add_placeholder": "moj prvi novčanik", + "add_title": "Dodaj novčanik", + "add_wallet_name": "Ime", + "add_wallet_type": "Tip", + "add_wallet_seed_length": "Dužina seed-a", + "add_wallet_seed_length_12": "12 reči", + "add_wallet_seed_length_24": "24 reči", + "clipboard_bitcoin": "Imate Bitcoin adresu u vašem clipboard-u. Da li želite da je iskoristite za transakciju?", + "clipboard_lightning": "Imate Lightning fakturu u vašem clipboard-u. Da li želite da je iskoristite za transakciju?", + "clear_clipboard_on_import": "Obriši clipboard pri uvozu", + "details_address": "Adresa", + "details_advanced": "Napredno", + "details_are_you_sure": "Da li ste sigurni?", + "details_connected_to": "Povezano na", + "details_del_wb_err": "Pruženi iznos stanja se ne podudara sa stanjem ovog novčanika. Pokušajte ponovo.", + "details_del_wb_q": "Ovaj novčanik ima stanje. Pre nego što nastavite, imajte na umu da nećete moći da povratite sredstva bez seed fraze ovog novčanika. Da biste izbegli slučajno uklanjanje, unesite stanje vašeg novčanika od {balance} satošija.", + "details_delete": "Obriši", + "details_delete_wallet": "Obriši novčanik", + "details_derivation_path": "putanja derivacije", + "details_display": "Prikaži na početnom ekranu", + "details_export_backup": "Izvoz/backup", + "details_export_history": "Izvezi istoriju u CSV", + "details_master_fingerprint": "Glavni otisak", + "details_multisig_type": "multisig", + "details_show_xpub": "Prikaži XPUB novčanika", + "details_show_addresses": "Prikaži adrese", + "details_title": "Novčanik", + "wallets": "Novčanici", + "swipe_balance_hide": "Sakrij", + "swipe_balance_show": "Prikaži", + "drag_to_reorder": "Prevucite za preuređivanje", + "clear_search": "Obriši pretragu", + "details_type": "Tip", + "details_use_with_hardware_wallet": "Koristi sa hardverskim novčanikom", + "details_yes_delete": "Da, obriši", + "enter_bip38_password": "Unesite lozinku za dešifrovanje", + "export_title": "Izvoz novčanika", + "import_do_import": "Uvezi", + "import_passphrase": "Pristupna fraza", + "import_passphrase_title": "Pristupna fraza", + "import_passphrase_message": "Unesite pristupnu frazu ako ste je koristili", + "import_error": "Uvoz nije uspeo. Uverite se da su pruženi podaci važeći.", + "import_explanation": "Unesite svoje seed reči, javni ključ, WIF ili bilo šta što imate. BlueWallet će dati sve od sebe da pogodi ispravan format i uveze vaš novčanik.", + "import_imported": "Uvezeno", + "import_scan_qr": "Skeniraj ili uvezi fajl", + "import_success": "Vaš novčanik je uspešno uvezen.", + "import_success_watchonly": "Vaš novčanik je uspešno uvezen. UPOZORENJE: Ovo je watch-only novčanik, NE možete da trošite iz njega.", + "import_search_accounts": "Pretraži naloge", + "import_title": "Uvoz", + "learn_more": "Saznaj više", + "import_discovery_title": "Otkrivanje", + "import_discovery_subtitle": "Izaberite otkriveni novčanik", + "import_discovery_derivation": "Koristi prilagođenu putanju derivacije", + "import_discovery_no_wallets": "Nije pronađen nijedan novčanik.", + "import_discovery_offline": "BlueWallet je trenutno u režimu van mreže. U ovom režimu ne može da potvrdi postojanje novčanika, pa ćete morati ručno da izaberete ispravan", + "import_derivation_found": "Pronađeno", + "import_derivation_found_not": "Nije pronađeno", + "import_derivation_loading": "Učitavanje...", + "import_derivation_subtitle": "Unesite prilagođenu putanju derivacije i pokušaćemo da otkrijemo vaš novčanik.", + "import_derivation_title": "Putanja derivacije", + "import_derivation_unknown": "Nepoznato", + "import_wrong_path": "Pogrešna putanja derivacije", + "list_create_a_button": "Dodaj sada", + "list_create_a_wallet": "Dodaj novčanik", + "list_create_a_wallet_text": "Besplatno je, i možete kreirati \nkoliko god želite.", + "list_empty_txs1": "Vaše transakcije će se pojaviti ovde.", + "list_empty_txs1_lightning": "Lightning novčanik bi trebalo da se koristi za vaše svakodnevne transakcije. Provizije su neuporedivo niske, a brzina je munjevita.", + "list_empty_txs2": "Započnite sa svojim novčanikom.", + "list_empty_txs2_lightning": "\nDa biste počeli da ga koristite, dodirnite Upravljanje sredstvima i dopunite vaše stanje.", + "list_latest_transaction": "Poslednja transakcija", + "list_long_choose": "Izaberi fotografiju", + "paste_from_clipboard": "Nalepi", + "import_file": "Uvezi fajl", + "list_long_scan": "Skeniraj QR kod", + "list_title": "Novčanici", + "list_tryagain": "Pokušaj ponovo", + "no_ln_wallet_error": "Pre plaćanja Lightning fakture, prvo morate dodati Lightning novčanik.", + "looks_like_bip38": "Ovo izgleda kao privatni ključ zaštićen lozinkom (BIP38).", + "manage_title": "Upravljaj novčanicima", + "no_results_found": "Nije pronađen nijedan rezultat.", + "please_continue_scanning": "Nastavite sa skeniranjem.", + "select_no_bitcoin": "Trenutno nema dostupnih Bitcoin novčanika.", + "select_no_bitcoin_exp": "Bitcoin novčanik je potreban za dopunu Lightning novčanika. Kreirajte ili uvezite jedan.", + "select_wallet": "Izaberi novčanik", + "pull_to_refresh": "Povucite za osvežavanje", + "warning_do_not_disclose": "Nikada ne delite informacije ispod", + "scan_import": "Skenirajte ovaj QR kod da biste uvezli svoj novčanik u drugu aplikaciju.", + "write_down_header": "Kreirajte ručni backup", + "write_down": "Zapišite i bezbedno sačuvajte ove reči. Iskoristite ih za vraćanje vašeg novčanika kasnije.", + "wallet_type_this": "Tip ovog novčanika je {type}.", + "share_number": "Podeli {number}", + "copy_ln_url": "Kopirajte i bezbedno sačuvajte ovaj URL za vraćanje vašeg novčanika kasnije.", + "copy_ln_public": "Kopirajte i bezbedno sačuvajte ove informacije za vraćanje vašeg novčanika kasnije.", + "add_ln_wallet_first": "Prvo morate dodati Lightning novčanik.", + "identity_pubkey": "Javni ključ identiteta", + "xpub_title": "XPUB novčanika", + "manage_wallets_search_placeholder": "Pretraži novčanike, adrese, transakcije i beleške", + "more_info": "Više informacija", + "details_delete_wallet_error_message": "Došlo je do problema pri potvrđivanju da li je ovaj novčanik uklonjen iz obaveštenja—ovo može biti zbog problema sa mrežom ili loše veze. Ako nastavite, možda ćete i dalje primati obaveštenja za transakcije povezane sa ovim novčanikom, čak i nakon što je obrisan.", + "details_delete_anyway": "Obriši svejedno" + }, + "total_balance_view": { + "display_in_bitcoin": "Prikaži u Bitcoin-u", + "hide": "Sakrij", + "display_in_sats": "Prikaži u sats", + "display_in_fiat": "Prikaži u {currency}", + "title": "Ukupno stanje", + "explanation": "Pregledajte ukupno stanje svih vaših novčanika na ekranu pregleda." + }, + "multisig": { + "multisig_vault": "Multisig trezor", + "default_label": "Multisig trezor", + "multisig_vault_explain": "Najbolja bezbednost za veće iznose", + "provide_signature": "Pruži potpis", + "provide_signature_details": "Koristite svoj uređaj i novčanik gde se ključ nalazi da potpišete ovu transakciju", + "provide_signature_details_bluewallet": "U BlueWallet-u, idite u meni ekrana Pošalji i izaberite ", + "provide_signature_next_steps": "Skeniraj ili uvezi potpisanu transakciju", + "provide_signature_next_steps_details": "Kada vaš novčanik uspešno potpiše transakciju, skenirajte pruženi QR kod ili uvezite priloženi fajl, a zatim pregledajte sve detalje transakcije pre nego što je pošaljete.", + "vault_key": "Ključ trezora {number}", + "required_keys_out_of_total": "Potrebni ključevi od ukupnog broja", + "fee": "Provizija: {number}", + "fee_btc": "{number} BTC", + "confirm": "Potvrdi", + "header": "Pošalji", + "share": "Podeli...", + "view": "Pregledaj", + "shared_key_detected": "Deljeni co-signer", + "shared_key_detected_question": "Co-signer je podeljen sa vama, da li želite da ga uvezete?", + "manage_keys": "Upravljaj ključevima", + "how_many_signatures_can_bluewallet_make": "koliko potpisa BlueWallet može da napravi", + "signatures_required_to_spend": "Potrebnih potpisa {number}", + "signatures_we_can_make": "može da napravi {number}", + "scan_or_import_file": "Skeniraj ili uvezi fajl", + "export_coordination_setup": "Izvezi koordinacionu konfiguraciju", + "cosign_this_transaction": "Co-potpiši ovu transakciju?", + "lets_start": "Hajde da počnemo", + "create": "Kreiraj", + "native_segwit_title": "Najbolja praksa", + "wrapped_segwit_title": "Najbolja kompatibilnost", + "legacy_title": "Legacy", + "co_sign_transaction": "Potpiši transakciju", + "what_is_vault": "Trezor je", + "what_is_vault_numberOfWallets": " {m}-od-{n} multisig ", + "what_is_vault_wallet": "novčanik.", + "vault_advanced_customize": "Podešavanja trezora", + "needs": "Potrebno mu je", + "what_is_vault_description_number_of_vault_keys": " {m} ključeva trezora ", + "what_is_vault_description_to_spend": "za trošenje i treći koji \nmožete iskoristiti kao backup.", + "what_is_vault_description_to_spend_other": "za trošenje.", + "quorum": "{m} od {n} kvorum", + "quorum_header": "Kvorum", + "of": "od", + "wallet_type": "Tip novčanika", + "invalid_mnemonics": "Ova mnemonička fraza ne izgleda važeća.", + "invalid_cosigner": "Nevažeći podaci co-signer-a", + "not_a_multisignature_xpub": "Ovo nije XPUB iz multisig novčanika!", + "invalid_cosigner_format": "Neispravan co-signer: Ovo nije co-signer za format {format}.", + "create_new_key": "Kreiraj novi", + "scan_or_open_file": "Skeniraj ili otvori fajl", + "i_have_mnemonics": "Imam seed za ovaj ključ.", + "type_your_mnemonics": "Unesite seed da uvezete postojeći ključ trezora.", + "this_is_cosigners_xpub": "Ovo je XPUB co-signer-a—spreman za uvoz u drugi novčanik. Bezbedno je deliti ga.", + "this_is_cosigners_xpub_airdrop": "Ako delite putem AirDrop-a, primaoci moraju biti na ekranu koordinacije.", + "wallet_key_created": "Vaš ključ trezora je kreiran. Odvojite trenutak da bezbedno napravite backup mnemoničkog seed-a.", + "are_you_sure_seed_will_be_lost": "Da li ste sigurni? Vaš mnemonički seed će biti izgubljen ako nemate backup.", + "forget_this_seed": "Zaboravi ovaj seed i koristi XPUB umesto njega.", + "view_edit_cosigners": "Pregledaj/uredi co-signer-e", + "this_cosigner_is_already_imported": "Ovaj co-signer je već uvezen.", + "export_signed_psbt": "Izvezi potpisani PSBT", + "input_fp": "Unesite otisak", + "input_fp_explain": "Preskočite za korišćenje podrazumevanog (00000000)", + "input_path": "Unesite putanju derivacije", + "input_path_explain": "Preskočite za korišćenje podrazumevane ({default})", + "ms_help": "Pomoć", + "ms_help_title": "Kako rade multisig trezori: Saveti i trikovi", + "ms_help_text": "Novčanik sa više ključeva, za povećanu bezbednost ili podeljeno čuvanje", + "ms_help_title1": "Preporučuje se više uređaja.", + "ms_help_1": "Trezor će raditi sa drugim BlueWallet aplikacijama i PSBT kompatibilnim novčanicima, kao što su Electrum, Specter, Coldcard, Cobo Vault, itd.", + "ms_help_title2": "Uređivanje ključeva", + "ms_help_2": "Možete kreirati sve ključeve trezora na ovom uređaju i kasnije ih ukloniti ili urediti. Imati sve ključeve na istom uređaju ima ekvivalentnu bezbednost kao običan Bitcoin novčanik.", + "ms_help_title3": "Backup trezora", + "ms_help_3": "U opcijama novčanika ćete pronaći svoj backup trezora i watch-only backup. Ovaj backup je kao mapa do vašeg novčanika. Neophodan je za vraćanje novčanika u slučaju da izgubite jedan od svojih seed-ova.", + "ms_help_title4": "Uvoz trezora", + "ms_help_4": "Da biste uvezli multisig, koristite svoj backup fajl i funkciju Uvoz. Ako imate samo seed-ove i XPUB-ove, možete koristiti pojedinačno dugme Uvoz prilikom kreiranja ključeva trezora.", + "ms_help_title5": "Napredni režim", + "ms_help_5": "Podrazumevano, BlueWallet će generisati trezor 2-od-3. Da biste kreirali drugačiji kvorum ili promenili tip adrese, aktivirajte Napredni režim u podešavanjima." + }, + "is_it_my_address": { + "title": "Da li je ovo moja adresa?", + "owns": "{label} poseduje {address}", + "enter_address": "Unesite adresu", + "check_address": "Proveri adresu", + "no_wallet_owns_address": "Nijedan od dostupnih novčanika ne poseduje pruženu adresu.", + "view_qrcode": "Pregledaj QR kod" + }, + "autofill_word": { + "title": "Poslednja reč seed-a", + "enter": "Unesite svoju delimičnu mnemoničku frazu", + "generate_word": "Generiši poslednju reč", + "error": "Unos nije delimična mnemonička fraza od 11 ili 23 reči. Pokušajte ponovo." + }, + "cc": { + "change": "Kusur", + "coins_selected": "Izabrano coina ({number})", + "selected_summ": "{value} izabrano", + "empty": "Ovaj novčanik trenutno nema coina.", + "freeze": "Zamrzni", + "freezeLabel": "Zamrzni", + "freezeLabel_un": "Odmrzni", + "header": "Upravljanje UTXO", + "use_coin": "Koristi coin", + "use_coins": "Koristi coine", + "tip": "Ova funkcija vam omogućava da vidite, označite, zamrznete ili izaberete coine za bolje upravljanje novčanikom. Možete izabrati više coina dodirivanjem obojenih krugova.", + "sort_asc": "Rastuće", + "sort_desc": "Opadajuće", + "sort_height": "Visina", + "sort_value": "Vrednost", + "sort_label": "Oznaka", + "sort_by": "Sortiraj po", + "sort_status": "Status" + }, + "units": { + "BTC": "BTC", + "MAX": "Max", + "sat_vbyte": "sat/vByte", + "sats": "sats" + }, + "addresses": { + "copy_private_key": "Kopiraj privatni ključ", + "sensitive_private_key": "Upozorenje: privatni ključevi su izuzetno osetljivi. Nastaviti?", + "sign_title": "Potpiši/proveri poruku", + "sign_help": "Ovde možete kreirati ili proveriti kriptografski potpis baziran na Bitcoin adresi.", + "sign_sign": "Potpiši", + "sign_verify": "Proveri", + "sign_signature_correct": "Provera uspešna!", + "sign_signature_incorrect": "Provera nije uspela!", + "sign_placeholder_address": "Adresa", + "sign_placeholder_message": "Poruka", + "sign_placeholder_signature": "Potpis", + "addresses_title": "Adrese", + "type_change": "Kusur", + "type_receive": "Primi", + "type_used": "Korišćeno", + "transactions": "Transakcije" + }, + "lnurl_auth": { + "register_question_part_1": "Da li želite da registrujete nalog na", + "register_question_part_2": "koristeći vaš Lightning novčanik?", + "register_answer": "Uspešno ste registrovali nalog na {hostname}!", + "login_question_part_1": "Da li želite da se prijavite na", + "login_question_part_2": "koristeći vaš Lightning novčanik?", + "login_answer": "Uspešno ste se prijavili na {hostname}!", + "link_question_part_1": "Da li želite da povežete vaš nalog na", + "link_question_part_2": "sa vašim Lightning novčanikom?", + "link_answer": "Vaš Lightning novčanik je uspešno povezan sa vašim nalogom na {hostname}!", + "auth_question_part_1": "Da li želite da budete autentifikovani na", + "auth_question_part_2": "koristeći vaš Lightning novčanik?", + "auth_answer": "Uspešno ste autentifikovani na {hostname}!", + "could_not_auth": "Nismo uspeli da vas autentifikujemo na {hostname}.", + "authenticate": "Autentifikuj" + }, + "bip47": { + "payment_code": "Payment kod", + "contacts": "Kontakti", + "bip47_explain": "Kod za višekratnu upotrebu i deljenje", + "bip47_explain_subtitle": "BIP47", + "purpose": "Kod za višekratnu upotrebu i deljenje (BIP47)", + "pay_this_contact": "Plati ovom kontaktu", + "rename_contact": "Preimenuj kontakt", + "copy_payment_code": "Kopiraj Payment kod", + "hide_contact": "Sakrij kontakt", + "rename": "Preimenuj", + "provide_name": "Unesite novo ime za ovaj kontakt", + "add_contact": "Dodaj kontakt", + "provide_payment_code": "Unesite Payment kod", + "invalid_pc": "Nevažeći Payment kod", + "notification_tx_unconfirmed": "Transakcija obaveštenja još nije potvrđena, molimo sačekajte", + "failed_create_notif_tx": "Kreiranje on-chain transakcije nije uspelo", + "onchain_tx_needed": "Potrebna je on-chain transakcija", + "notif_tx_sent": "Transakcija obaveštenja poslata. Sačekajte da bude potvrđena", + "notif_tx": "Transakcija obaveštenja", + "not_found": "Payment kod nije pronađen" } } diff --git a/loc/sv_se.json b/loc/sv_se.json index ceb3c97c24e..c3e79fd22fa 100644 --- a/loc/sv_se.json +++ b/loc/sv_se.json @@ -6,132 +6,117 @@ "clipboard": "Urklipp", "enter_password": "Ange lösenord", "never": "aldrig", - "disabled": "Inaktiverad", "of": "{number} av {total}", "ok": "OK", - "storage_is_encrypted": "Lagringen är krypterad. Lösenords krävs för att dekryptera", + "storage_is_encrypted": "Lagringen är krypterad. Lösenord krävs för att dekryptera.", "yes": "Ja", "no": "Nej", - "save": "Spara", "seed": "Seed", "success": "Framgång", "wallet_key": "Nyckel till plånbok", - "invalid_animated_qr_code_fragment": "Felaktig animerad QR-kod. Vänligen försök igen.", - "file_saved": "Filen {filePath} har sparats i din {destination}.", - "downloads_folder": "Nedladdningsmapp", "close": "Stäng", "change_input_currency": "Ändra inmatningsvaluta", "refresh": "Uppdatera", - "more": "Mer", - "pick_image": "Välj bild från biblioteket", - "pick_file": "Välj en fil", "enter_amount": "Ange belopp", - "qr_custom_input_button": "Tryck 10 gånger för att ange anpassad inmatning" - }, - "alert": { - "default": "Varna" + "qr_custom_input_button": "Tryck 10 gånger för att ange anpassad inmatning", + "copied": "Kopierad!", + "discard_changes": "Förkasta ändringar?", + "discard_changes_explain": "Du har osparade ändringar. Är du säker på att du vill förkasta dem och lämna skärmen?", + "enter_url": "Ange URL", + "save": "Spara...", + "pick_image": "Välj från biblioteket", + "pick_file": "Välj fil", + "unlock": "Lås upp", + "port": "Port", + "ssl_port": "SSL-port", + "suggested": "Föreslagen" }, "azteco": { "codeIs": "Din kupong är", - "errorBeforeRefeem": "Innan inlösen måste du först skapa en Bitcoin plånbok.", + "errorBeforeRefeem": "Innan du löser in måste du skapa en bitcoin-plånbok.", "errorSomething": "Något gick fel. Är din kupong fortfarande giltig?", "redeem": "Lös in till en plånbok", "redeemButton": "Lös in", - "success": "Framgång", + "success": "Klart", + "successMessage": "Kupongen har lösts in! Dina pengar bör snart anlända till din Bitcoin-plånbok.", "title": "Lös in Azte.co kupong" }, "entropy": { "save": "Spara", "title": "Entropi", - "undo": "Ångra" + "undo": "Ångra", + "amountOfEntropy": "{bits} av {limit} bitar" }, "errors": { "broadcast": "Sändning misslyckades", "error": "Fel", - "network": "Nätverks fel" + "network": "Nätverksfel" }, "lnd": { - "active": "Aktiva", - "inactive": "Inaktiv", - "channels": "Kanaler", - "no_channels": "Inga kanaler", - "claim_balance": "Begär saldo {saldo}", - "close_channel": "Säng kanal", - "new_channel": "Ny kanal", - "errorInvoiceExpired": "Fakturan har förfallit", - "force_close_channel": "Tvinga stänga kanal", "expired": "Förfallen", - "node_alias": "Node alias", "expiresIn": "Går ut om {time} minuter", "payButton": "Betala", + "payment": "Betalning", "placeholder": "Faktura eller adress", - "open_channel": "Öppna kanal", - "funding_amount_placeholder": "Sätt in summa, tex 0.001", - "opening_channnel_for_from": "Öppnande kanal för plånbok {forWalletLabel}, genom insättning från {fromWalletLabel}", - "are_you_sure_open_channel": "Är du säker på att du vill öppna den här kanalen? ", - "potentialFee": "Potentiell avgift: {avgift}", - "remote_host": "Fjärrvärd", - "refill": "Sätt in", - "reconnect_peer": "Återanslut peer", + "potentialFee": "Potentiell avgift: {fee}", + "refill": "Fyll på", "refill_create": "För att fortsätta, vänligen skapa en Bitcoin plånbok att fylla på med", "refill_external": "Fyll på från extern plånbok", "refill_lnd_balance": "Fyll på Lightning-plånbok", - "sameWalletAsInvoiceError": "Du kan inte betala en faktura med samma plånbok som användes för att skapa den.", - "title": "sätt in / ta ut", - "can_send": "Kan skicka", - "can_receive": "Kan ta emot", - "view_logs": "Visa loggar" + "sameWalletAsInvoiceError": "Du kan inte betala en faktura från samma plånbok som användes för att skapa den.", + "title": "Hantera medel", + "errorInvoiceExpired": "Fakturan har förfallit." }, "lndViewInvoice": { "additional_info": "Ytterligare information", "for": "För:", - "lightning_invoice": "Lightning faktura", - "open_direct_channel": "Öppna en direkt kanal med denna nod:", + "lightning_invoice": "Lightning-faktura", "please_pay_between_and": "Betala mellan {min} och {max}", "please_pay": "Var god betala", - "preimage": "Förbild", "sats": "sats", - "wasnt_paid_and_expired": "Denna faktura har ej betalats och är nu utgången" + "wasnt_paid_and_expired": "Denna faktura har ej betalats och är nu utgången", + "preimage": "Pre-image", + "date_time": "Datum och tid" }, "plausibledeniability": { "create_fake_storage": "Skapa fejkad lagringsyta", - "create_password": "Skapa ett lösenord", "create_password_explanation": "Lösenordet för den fejkade lagringsytan får inte vara samma som ditt huvudlösenord", "help": "Under vissa omständigheter kan du bli tvingad att uppge ditt lösenord. För att se till att dina pengar är säkra kan BlueWallet skapa ytterligare en krypterad lagringsyta, med ett annat lösenord. Vid tvång kan du uppge detta alternativa lösenord. När det matas in i BlueWallet så kommer det att låsa upp din 'fejkade' lagringsyta. Det kommer att se ut precis som vanligt men i själva verket är dina pengar i säkert förvar på din primära lagringsyta.", "help2": "Den alternativa lagringsytan kommer att vara fullt fungerade och du kan eventuellt spara en mindre summa där för att den ska verka mer trovärdig.", "password_should_not_match": "Lösenordet för den fejkade lagringsytan får inte vara samma som ditt huvudlösenord", - "passwords_do_not_match": "Lösenorden du angav matchar inte. Försök igen.", - "retype_password": "Ange lösenord igen", - "success": "Fejkad lagringsyta skapad!", "title": "Trovärdigt förnekande" }, "pleasebackup": { "ask": "Har du skrivit ner din plånboks uppbacknings fras? Denna frasen är nödvändig för att komma åt dina pengar ifall du förlorar denna enheten. Utan denna uppbacknings frasen är dina pengar borta för alltid", - "ask_no": "Nej", - "ask_yes": "Ja", - "ok": "OK, jag har skrivit ned dem", "ok_lnd": "Ok, jag har sparat det", "text": "Innan du går vidare, skriv ned dessa ord på ett papper och förvara på ett säkert ställe. \nDe är din backup och säkerställer att du kan återställa din plånbok igen om något händer.", "text_lnd": "Spara denna säkerhetskopia av plånboken. Det tillåter dig att återställa plånboken vid förlust.", - "title": "Din plånbok har skapats" + "title": "Din plånbok har skapats...", + "ask_no": "Nej, det har jag inte.", + "ask_yes": "Ja, det har jag.", + "ok": "OK, jag har skrivit ner det." }, "receive": { "details_create": "Skapa", "details_label": "Beskrivning", "details_setAmount": "Ta emot med belopp", - "details_share": "dela", "header": "Ta emot", "maxSats": "Maximalt belopp är {max} sats", "maxSatsFull": "Maximalt belopp är {max} sats eller {currency}", "minSats": "Minsta belopp är {min} sats", - "minSatsFull": "Minsta belopp är {min} sats eller {currency}" + "minSatsFull": "Minsta belopp är {min} sats eller {currency}", + "details_share": "Dela...", + "address_not_found": "Det gick inte att generera mottagaradress.", + "reset": "Återställ", + "qrcode_for_the_address": "QR-kod för adressen", + "bip47_explanation": "Betalningskoder är en universell adress som undviker att avslöja dina plånboksadresser. Alla tjänster har inte stöd för dem." }, "send": { "provided_address_is_invoice": "Den här adressen verkar vara för en Lightning-faktura. Vänligen gå till din Lightning-plånbok för att göra en betalning för denna faktura.", "broadcastButton": "SÄND", "broadcastError": "Fel", - "broadcastNone": "Infoga transaktions hex", - "broadcastPending": "Pågående", + "broadcastNone": "Infoga transaktionshex", + "broadcastPending": "Väntande", "broadcastSuccess": "Klart", "confirm_header": "Bekräfta", "confirm_sendNow": "Skicka nu", @@ -140,7 +125,7 @@ "create_copy": "Kopiera och skicka senare", "create_details": "Detaljer", "create_fee": "Avgift", - "create_memo": "Memo", + "create_memo": "Notering", "create_satoshi_per_vbyte": "Satoshi per vByte", "create_this_is_hex": "Detta är transaktionens hex, signerad och redo att skickas ut på nätverket.", "create_to": "Till", @@ -150,20 +135,18 @@ "details_add_rec_rem": "Ta bort mottagare", "details_address": "adress", "details_address_field_is_not_valid": "Angiven adress är inte giltig", - "details_adv_fee_bump": "Tillåt avgifts bump", - "details_adv_full": "Andvänd hela beloppet", + "details_adv_fee_bump": "Tillåt avgiftshöjning", + "details_adv_full": "Använd hela beloppet", "details_adv_full_sure": "Är du säker på att du vill använda plånbokens hela belopp för denna transaktion?", "details_adv_full_sure_frozen": "Är du säker på att du vill använda plånbokens hela belopp för denna transaktion? Observera att frysta mynt är undantagna.", - "details_adv_import": "Importera Transaction", - "details_adv_import_qr": "Importera Transaction (QR)", + "details_adv_import": "Importera transaktion", + "details_adv_import_qr": "Importera transaktion (QR)", "details_amount_field_is_not_valid": "Angivet belopp är inte giltigt", "details_amount_field_is_less_than_minimum_amount_sat": "Det angivna beloppet är för litet. Vänligen ange ett belopp som är större än 500 sats.", "details_create": "Skapa", "details_error_decode": "Det går inte att avkoda Bitcoin-adress", "details_fee_field_is_not_valid": "Angiven avgift är inte giltig", - "details_frozen": "{amount} BTC är frusna", "details_next": "Nästa", - "details_no_signed_tx": "Den valda filen innehåller inte en transaktion som kan importeras.", "details_note_placeholder": "egen notering", "details_scan": "Skanna", "details_scan_hint": "Dubbeltryck för att skanna eller importera en destination", @@ -184,7 +167,7 @@ "fee_medium": "Mellan", "fee_replace_minvb": "Den totala avgiftssatsen (satoshi per vByte) du vill betala bör vara högre än {min} sat/vByte.", "fee_satvbyte": "i sat/vByte", - "fee_slow": "Långsamt", + "fee_slow": "Långsam", "header": "Skicka", "input_clear": "Töm", "input_done": "Klart", @@ -193,31 +176,45 @@ "permission_camera_message": "Vi behöver ditt godkännande för att använda kameran", "psbt_sign": "Signera en transaktion", "open_settings": "Öppna inställningar", - "permission_storage_later": "Fråga mig senare", - "permission_storage_message": "BlueWallet behöver din tillåtelse för att komma åt din lagring för att spara den här filen.", "permission_storage_denied_message": "BlueWallet kan inte spara den här filen. Öppna dina enhetsinställningar och aktivera lagringstillstånd.", "permission_storage_title": "Lagringstillstånd", "psbt_clipboard": "Kopiera till urklipp", "psbt_this_is_psbt": "Detta är en delvis signerad Bitcoin-transaktion (PSBT). Vänligen avsluta med att signera den med din hårdvaruplånbok.", "psbt_tx_export": "Exportera till fil", "no_tx_signing_in_progress": "Det pågår ingen transaktionssignering.", - "outdated_rate": "Betygsätt senaste uppdateringen: {date}", + "outdated_rate": "Kursen uppdaterades senast: {date}", "psbt_tx_open": "Öppna Signerad transaktion", "psbt_tx_scan": "Skanna Signerad transaktion", - "qr_error_no_qrcode": "Vi kunde inte hitta en QR-kod i den valda bilden. Se till att bilden endast innehåller en QR-kod och inget extra innehåll som text eller knappar.", "reset_amount": "Återställ belopp", "reset_amount_confirm": "Vill du återställa beloppet?", "success_done": "Klart!", - "txSaved": "Transaktionsfilen ({filePath}) har sparats i mappen Nedladdningar.", - "problem_with_psbt": "Problem med PSBT" + "problem_with_psbt": "Problem med PSBT", + "details_insert_contact": "Infoga kontakt", + "details_add_recc_rem_all_alert_description": "Är du säker på att du vill ta bort alla mottagare?", + "details_add_rec_rem_all": "Ta bort alla mottagare", + "details_recipients_title": "Mottagare", + "details_recipient_title": "Mottagare #{number} av #{total}", + "please_complete_recipient_title": "Ofullständig mottagare", + "please_complete_recipient_details": "Vänligen fyll i uppgifterna för mottagare #{number} innan du lägger till en ny mottagare.", + "details_frozen": "{amount} BTC är fryst.", + "details_no_signed_tx": "Den valda filen innehåller ingen transaktion som kan importeras.", + "details_scan_error": "Skanningsfel", + "insert_custom_fee": "Ange avgift", + "invalid_psbt": "Ogiltig PSBT angiven.", + "qr_error_no_qrcode": "Vi kunde inte hitta en giltig QR-kod i den valda bilden. Se till att bilden endast innehåller en QR-kod och inget annat innehåll som text eller knappar.", + "txSaved": "Transaktionsfilen ({filePath}) har sparats.", + "file_saved_at_path": "Filen ({filePath}) har sparats.", + "cant_send_to_silentpayment_adress": "Den här plånboken kan inte skicka till Silent Payments-adresser", + "cant_send_to_bip47": "Den här plånboken kan inte skicka till BIP47-betalningskoder", + "cant_find_bip47_notification": "Lägg först till den här betalningskoden i kontakter" }, "settings": { "about": "Om", "about_awesome": "Byggd med det fantastiska", "about_backup": "Backa alltid upp dina nycklar!", - "about_free": "BlueWallet är ett fritt och öppen källkods projekt. Skapad av Bitcoin användare.", + "about_free": "BlueWallet är gratis och byggs av bitcoin-användare med hjälp av öppen källkod.", "about_license": "MIT-licens", - "about_release_notes": "Release notes", + "about_release_notes": "Versionsinformation", "about_review": "Lämna oss en recension", "performance_score": "Prestandapoäng: {num}", "run_performance_test": "Testa prestanda", @@ -225,195 +222,221 @@ "about_selftest_electrum_disabled": "Självtest är inte tillgänglig med Electrum Offline Mode. Inaktivera offlineläget och försök igen.", "about_selftest_ok": "Alla interna tester har godkänts. Plånboken fungerar bra.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord Server", - "about_sm_telegram": "Telegram chatt", - "about_sm_twitter": "Följ oss på Twitter", - "advanced_options": "Avancerade alternativ", + "about_sm_telegram": "Telegramkanal", "biometrics": "Biometri", "biom_10times": "Du har försökt ange ditt lösenord 10 gånger. Vill du återställa din lagring? Detta tar bort alla plånböcker och dekrypterar din lagring.", "biom_conf_identity": "Vänligen bekräfta din identitet.", - "biom_no_passcode": "Din enhet har inget lösenord. För att fortsätta måste du konfigurera ett lösenord i appen Inställningar.", "biom_remove_decrypt": "Alla dina plånböcker kommer att tas bort och din lagring kommer att dekrypteras. Är du säker på att du vill fortsätta?", "currency": "Valuta", - "currency_source": "Priser hämtas från", "currency_fetch_error": "Det uppstod ett fel när kursen för den valda valutan skulle hämtas.", - "default_desc": "När den är inaktiverad öppnar BlueWallet omedelbart den valda plånboken.", - "default_info": "Standardinformation", "default_title": "Vid uppstart", - "default_wallets": "Visa alla plånböcker", "electrum_connected": "Ansluten", - "electrum_connected_not": "Inte Ansluten", - "electrum_error_connect": "Det går inte att ansluta till den valda Electrum-servern", - "lndhub_uri": "E.g., {example}", - "electrum_host": "E.g., {example}", + "electrum_connected_not": "Inte ansluten", + "lndhub_uri": "T.ex. {example}", + "electrum_host": "T.ex. {example}", "electrum_offline_mode": "Offline läge", "electrum_offline_description": "När det är aktiverat kommer dina Bitcoin-plånböcker inte att försöka hämta saldon eller transaktioner.", - "electrum_port": "Port, vanligtvis {exempel}", + "electrum_port": "Port, vanligtvis {example}", "use_ssl": "Använd SSL", "electrum_saved": "Dina ändringar har sparats. Starta om BlueWallet för att ändringarna ska träda i kraft.", "set_electrum_server_as_default": "Vill du ställa in {server} som standard Electrum-server?", - "set_lndhub_as_default": "Vill du ställa in {url} som standard LNDHub-server?", "electrum_settings_server": "Electrum server", - "electrum_settings_explain": "Lämna tomt för att använda standardinställningen.", "electrum_status": "Status", - "electrum_clear_alert_title": "Rensa historik?", - "electrum_clear_alert_message": "Vill du rensa electrum-servrarnas historik?", - "electrum_clear_alert_cancel": "Avbryt", - "electrum_clear_alert_ok": "Ok", - "electrum_select": "Välj", - "electrum_reset": "Återställ till standard", "electrum_unable_to_connect": "Det gick inte att ansluta till {server}.", - "electrum_history": "Server historia", - "electrum_reset_to_default": "Är du säker på att du vill återställa dina Electrum-inställningar till standardinställningarna?", - "electrum_clear": "Töm", - "tor_supported": "Tor stöds", - "tor_unsupported": "Tor-anslutningar stöds inte.", + "electrum_reset": "Återställ till standard", "encrypt_decrypt": "Dekryptera lagring", "encrypt_decrypt_q": "Är du säker på att du vill dekryptera din lagring? Detta gör att dina plånböcker kan nås utan lösenord.", - "encrypt_enc_and_pass": "Krypterad och lösenordsskyddad", - "encrypt_title": "säkerhet", + "encrypt_title": "Säkerhet", "encrypt_tstorage": "Lagring", "encrypt_use": "Använd {type}", - "encrypt_use_expl": "{type} kommer att användas för att bekräfta din identitet innan du gör en transaktion, låser upp, exporterar eller tar bort en plånbok. {type} kommer inte att användas för att låsa upp krypterad lagring.", - "general": "Allmän", - "general_adv_mode": "Enable advanced mode", - "general_adv_mode_e": "När det är aktiverat kommer du att se avancerade alternativ som olika plånbokstyper, möjligheten att ange den LNDHub-instans du vill ansluta till och anpassad entropi under skapandet av plånboken. ", + "general": "Allmänt", "general_continuity": "Kontinuitet", "general_continuity_e": "När det är aktiverat kommer du att kunna se utvalda plånböcker och transaktioner med dina andra Apple iCloud-anslutna enheter.", "groundcontrol_explanation": "GroundControl är en gratis push-meddelandeserver med öppen källkod för Bitcoin-plånböcker. Du kan installera din egen GroundControl-server och lägga in dess URL här för att inte lita på BlueWallets infrastruktur. Lämna tomt för att använda GroundControls standardserver.", - "header": "inställningar", + "header": "Inställningar", "language": "Språk", "last_updated": "Senast uppdaterad", "language_isRTL": "Att starta om BlueWallet krävs för att språkorienteringen ska träda i kraft.", - "lightning_error_lndhub_uri": "Ogiltig LNDHub-URI", "lightning_saved": "Dina ändringar har sparats.", "lightning_settings": "Lightning Network", - "tor_settings": "Tor-inställningar", - "lightning_settings_explain": "För att ansluta till din egen LND-nod, installera LNDHub och ange dess URL här i inställningarna. Observera att endast plånböcker som skapas efter att ändringar har sparats kommer att ansluta till den angivna LNDHub.", "network": "Nätverk", - "network_broadcast": "Broadcasta Transaktion", + "network_broadcast": "Sänd transaktion", "network_electrum": "Electrum server", "not_a_valid_uri": "Ogiltig URI", "notifications": "Notifikationer", "open_link_in_explorer": "Öppna länken i utforskaren", "password": "Lösenord", - "password_explain": "Skapa ett lösenord som du kommer att använda vid dekryptering", - "passwords_do_not_match": "Lösenorden är olika!", "plausible_deniability": "Trovärdigt förnekande...", "privacy": "Integritet", "privacy_read_clipboard": "Läs Urklipp", "privacy_system_settings": "Systeminställningar", "privacy_quickactions": "Plånboksgenvägar", - "privacy_quickactions_explanation": "Tryck och håll in BlueWallet-appikonen på startskärmen för att snabbt se saldot i din plånbok.", + "privacy_quickactions_explanation": "Tryck och håll in BlueWallet-appikonen för att snabbt se saldot i din plånbok.", "privacy_clipboard_explanation": "Ange genvägar om en adress eller faktura hittas i ditt urklipp.", "privacy_do_not_track": "Inaktivera Analytics", "privacy_do_not_track_explanation": "Information om prestanda och tillförlitlighet kommer inte att skickas in för analys.", - "push_notifications": "Pushmeddelanden", - "rate": "Betygsätta", - "retype_password": "Ange lösenord igen", + "rate": "Kurs", "selfTest": "Självtest", "save": "Spara", "saved": "Sparad", - "success_transaction_broadcasted": "Framgång! Din transaktion har sänts ut!", - "total_balance": "Total balans", + "total_balance": "Totalt saldo", "total_balance_explanation": "Visa det totala saldot för alla dina plånböcker på dina startskärmswidgets.", - "widgets": "Widgets", - "tools": "Verktyg" + "widgets": "Widgetar", + "tools": "Verktyg", + "block_explorer_invalid_custom_url": "Den angivna URL:en är ogiltig. Ange en giltig URL som börjar med http:// eller https://.", + "privacy_temporary_screenshots": "Tillåt skärmdumpar", + "privacy_temporary_screenshots_instructions": "Skydd mot skärmdumpar stängs av tillfälligt, vilket aktiverar skärmdumpar och skärminspelningar. Skyddet återaktiveras automatiskt när du stänger och öppnar BlueWallet igen.", + "biometrics_no_longer_available": "Dina enhetsinställningar har ändrats och stämmer inte längre överens med de valda säkerhetsinställningarna i appen. Aktivera biometri eller enhetskod igen och starta sedan om appen för att tillämpa ändringarna.", + "biom_no_passcode": "Din enhet har ingen enhetskod eller biometri aktiverad. För att fortsätta, konfigurera en enhetskod eller biometri i appen Inställningar.", + "currency_source": "Kursen hämtas från", + "donate": "Donera", + "donate_description": "Hjälp oss att hålla Blue gratis!", + "electrum_error_connect": "Det gick inte att ansluta till den angivna Electrum-servern", + "electrum_error_connect_tor": "Det gick inte att ansluta till den angivna Electrum-servern. Se till att Orbot-appen är ansluten och försök igen.", + "set_lndhub_as_default": "Ange {url} som standard-LNDhub-server?", + "electrum_preferred_server": "Föredragen server", + "electrum_preferred_server_description": "Ange den server du vill att din plånbok ska använda för alla Bitcoin-aktiviteter. När den har angetts kommer din plånbok endast att använda denna server för att kontrollera saldon, sända transaktioner och hämta nätverksdata. Se till att du litar på denna server innan du anger den.", + "electrum_history": "Historik", + "electrum_reset_to_default": "Detta kommer att låta BlueWallet slumpmässigt välja en server från serverlistan.", + "electrum_reset_to_default_and_clear_history": "Återställ till standard och rensa historik", + "encrypt_enc_and_pass": "Lösenordsskyddad", + "encrypt_storage_explanation_headline": "Aktivera lagringskryptering", + "encrypt_storage_explanation_description_line1": "Att aktivera lagringskryptering lägger till ett extra skyddslager till din app genom att säkra hur dina data lagras på din enhet. Detta gör det svårare för någon att komma åt din information utan tillstånd.", + "encrypt_storage_explanation_description_line2": "Det är dock viktigt att veta att denna kryptering endast skyddar åtkomsten till dina plånböcker som lagras i enhetens nyckelring. Den lägger inte ett lösenord eller något extra skydd på själva plånböckerna.", + "i_understand": "Jag förstår", + "block_explorer": "Blockutforskare", + "block_explorer_preferred": "Använd föredragen blockutforskare", + "block_explorer_error_saving_custom": "Det gick inte att spara föredragen blockutforskare", + "set_as_preferred": "Ange som föredragen", + "set_as_preferred_electrum": "Att ange {host}:{port} som föredragen server inaktiverar slumpmässig anslutning till en föreslagen server.", + "encrypted_feature_disabled": "Denna funktion kan inte användas när krypterad lagring är aktiverad.", + "encrypt_use_expl": "{type} kommer att användas för att bekräfta din identitet innan du gör en transaktion, låser upp, exporterar eller raderar en plånbok.", + "biometrics_fail": "Om {type} inte är aktiverat eller inte lyckas låsa upp kan du använda enhetens enhetskod som alternativ.", + "license": "Licens", + "lightning_error_lndhub_uri": "Ogiltig LNDhub-URI", + "lightning_error_lndhub_uri_tor": "Ogiltig LNDhub-URI. Se till att Orbot-appen är ansluten och försök igen.", + "lightning_settings_explain": "För att ansluta till din egen LND-nod, installera LNDhub och ange dess URL här i inställningarna. Observera att endast plånböcker som skapas efter att ändringar har sparats kommer att ansluta till den angivna LNDhub-servern.", + "lndhub_github": "GitHub-arkiv", + "electrum_suggested_description": "När en föredragen server inte är inställd väljs en föreslagen server slumpmässigt.", + "password_explain": "Ange lösenordet du kommer att använda för att låsa upp din lagring.", + "push_notifications_explanation": "Genom att aktivera aviseringar skickas din enhets-token till servern, tillsammans med plånboksadresser och transaktions-ID för alla plånböcker och transaktioner som görs efter att aviseringar har aktiverats. Enhets-token används för att skicka aviseringar, och plånboksinformationen gör att vi kan meddela dig om inkommande Bitcoin eller transaktionsbekräftelser.\n\nEndast information från efter att du aktiverat aviseringar överförs—inget från tidigare samlas in.\n\nAtt inaktivera aviseringar tar bort all denna information från servern. Att radera en plånbok från appen tar också bort dess associerade information från servern.", + "success_transaction_broadcasted": "Din transaktion har sänts!" }, "notifications": { "would_you_like_to_receive_notifications": "Vill du få aviseringar när du får inkommande betalningar?", - "no_and_dont_ask": "Nej och fråga mig inte igen", - "ask_me_later": "Fråga mig senare" + "notifications_subtitle": "Inkommande betalningar och transaktionsbekräftelser", + "no_and_dont_ask": "Nej, och fråga mig inte igen.", + "permission_denied_message": "Du har nekat behörighet att skicka aviseringar. Om du vill få aviseringar, aktivera dem i enhetens inställningar." }, "transactions": { "cancel_explain": "Vi kommer att ersätta denna transaktion med en som betalar dig och har högre avgifter. Detta avbryter i praktiken den aktuella transaktionen. Detta kallas RBF--Replace by Fee--Ersätt med avgift.", "cancel_no": "Denna transaktion är inte utbytbar.", "cancel_title": "Avbryt denna transaktion (RBF)", "confirmations_lowercase": "{confirmations} bekräftelser", - "copy_link": "Kopiera länk", "expand_note": "Expandera anteckning", "cpfp_create": "Skapa", "cpfp_exp": "Vi kommer att skapa en annan transaktion som spenderar din obekräftade transaktion. Den totala avgiften kommer att vara högre än den ursprungliga transaktionsavgiften, så den bör bli bekräftad snabbare. Detta kallas CPFP--Child Pays for Parent--Barn betalar för förälder. ", "cpfp_no_bump": "Denna transaktion är inte höjbar.", "cpfp_title": "Höj avgift (CPFP)", - "details_balance_hide": "Göm saldo", + "details_balance_hide": "Dölj saldo", "details_balance_show": "Visa saldo", - "details_block": "Block höjd", "details_copy": "Kopiera", - "details_copy_amount": "Kopiera belopp", - "details_copy_block_explorer_link": "Kopiera Block Explorer Länk", + "details_copy_block_explorer_link": "Kopiera blockutforskar-länk", "details_copy_note": "Kopiera anteckning", "details_copy_txid": "Kopiera Transaktions ID", - "details_from": "Input", - "details_inputs": "Inputs", - "details_outputs": "Outputs", + "details_inputs": "Indata", + "details_outputs": "Utdata", "date": "Datum", "details_received": "Mottaget", - "transaction_note_saved": "Transaktions anteckningen har sparats.", - "details_show_in_block_explorer": "Visa i block explorer", "details_title": "Transaktion", - "details_to": "Output", + "details_to": "Utdata", "enable_offline_signing": "Den här plånboken används inte i samband med en offlinesignering. Skulle du vilja aktivera det nu?", "list_conf": "Bekräftade: {number}", - "pending": "Pågående", + "pending": "Väntande", "pending_with_amount": "Väntande {amt1} ({amt2})", "received_with_amount": "+{amt1} ({amt2})", - "eta_10m": "ETA: In ~10 minutes", - "eta_3h": "ETA: In ~3 hours", - "eta_1d": "ETA: In ~1 day", - "view_wallet": "Visa {walletLabel}", + "eta_10m": "ETA: Om ~10 minuter", + "eta_3h": "ETA: Om ~3 timmar", + "eta_1d": "ETA: Om ~1 dag", "list_title": "Transaktioner", + "list_title_received": "Mottaget", + "transaction": "Transaktion", "open_url_error": "Det går inte att öppna länken med standardwebbläsaren. Ändra din standardwebbläsare och försök igen.", "rbf_explain": "Vi kommer att ersätta denna transaktion med en med en högre avgift så att den blir bekräftad snabbare. Detta kallas RBF--Replace by Fee--Ersätt med avgift.", "rbf_title": "Höj avgift (RBF)", "status_bump": "Höj avgift", - "status_cancel": "Avbryt transaction", - "transactions_count": "Transaktioner räknas", + "status_cancel": "Avbryt transaktion", + "transactions_count": "Antal transaktioner", "txid": "Transaktions ID", - "updating": "Uppdaterar..." + "updating": "Uppdaterar...", + "transaction_loading_error": "Det uppstod ett problem när transaktionen skulle läsas in. Försök igen senare.", + "transaction_not_available": "Transaktion ej tillgänglig", + "details_view_in_browser": "Visa i webbläsare", + "incoming_transaction": "Inkommande transaktion", + "outgoing_transaction": "Utgående transaktion", + "expired_transaction": "Förfallen transaktion", + "pending_transaction": "Väntande transaktion", + "offchain": "Offchain", + "onchain": "Onchain", + "list_title_sent": "Skickat", + "watchOnlyWarningTitle": "Säkerhetsvarning", + "watchOnlyWarningDescription": "Var försiktig med bedragare som ofta använder plånböcker för endast granskning för att lura användare. Dessa plånböcker tillåter dig inte att kontrollera eller skicka pengar; de låter dig endast se saldot.", + "custom_fee_warning_title": "Varning", + "custom_fee_warning_description": "Avgifter under 1 sat/vB är giltiga, men kanske inte vidarebefordras på grund av nodpolicyer.", + "details_eta_analyzing": "Analyserar...", + "details_sent": "Skickat", + "details_section": "Detaljer", + "details_explorer": "utforskare", + "details_network_fee": "Nätverksavgift", + "details_to_address": "Till", + "details_id": "ID", + "details_note": "Anteckning", + "details_add_note": "lägg till", + "details_advanced": "Avancerat", + "details_fee_rate": "Avgiftsnivå", + "details_size": "Storlek", + "details_virtual_size": "Virtuell storlek", + "details_tx_hex": "Tx hex", + "details_inputs_count": "Indata ({count})", + "details_outputs_count": "Utdata ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Enkel och kraftfull Bitcoin-plånbok", "add_create": "Skapa", + "total_balance": "Totalt saldo", + "add_entropy": "Entropi", "add_entropy_generated": "{gen} byte genererad entropi", "add_entropy_provide": "Ge entropi via tärningskast", "add_entropy_remain": "{gen} byte genererad entropi. Återstående {rem} byte kommer att erhållas från systemets slumptalsgenerator.", "add_import_wallet": "Importera plånbok", "add_lightning": "Lightning", "add_lightning_explain": "För utgifter med omedelbara transaktioner", - "add_lndhub": "Anslut till din LNDHub", - "add_lndhub_error": "Den angivna nodadressen är en ogiltig LNDHub-nod.", "add_lndhub_placeholder": "Din nodadress", "add_placeholder": "Min första plånbok", "add_title": "ny plånbok", "add_wallet_name": "namn", "add_wallet_type": "typ", - "balance": "Balans", "clipboard_bitcoin": "Du har en Bitcoin-adress i ditt urklipp. Vill du använda den för en transaktion?", "clipboard_lightning": "Du har en Lightning-faktura i ditt urklipp. Vill du använda den för en transaktion?", "details_address": "Adress", "details_advanced": "Avancerat", "details_are_you_sure": "Är du säker?", "details_connected_to": "Ansluten till", - "details_del_wb_err": "Det angivna saldobeloppet matchar inte plånbokens saldo. Var god försök igen.", - "details_del_wb_q": "Denna plånbok har en balans. Innan du fortsätter bör du vara medveten om att du inte kommer att kunna få tillbaka pengarna utan denna plånboks seed phrase. För att undvika oavsiktlig borttagning, ange saldot i din plånbok på {balance} satoshis.", + "details_del_wb_q": "Denna plånbok har ett saldo. Innan du fortsätter bör du vara medveten om att du inte kommer att kunna få tillbaka pengarna utan denna plånboks seed phrase. För att undvika oavsiktlig borttagning, ange saldot i din plånbok på {balance} satoshis.", "details_delete": "Radera", "details_delete_wallet": "Radera plånbok", - "details_derivation_path": "derivation path", - "details_display": "visa i listan med plånböcker", + "details_derivation_path": "härledningsväg", "details_export_backup": "Exportera / ta backup", "details_export_history": "Exportera historik till CSV", - "details_master_fingerprint": "Master Fingerprint", + "details_master_fingerprint": "Huvudfingeravtryck", "details_multisig_type": "multisig", - "details_no_cancel": "Nej, avbryt", - "details_save": "Spara", "details_show_xpub": "Visa plånbokens XPUB", "details_show_addresses": "Visa adresser", "details_title": "Plånbok", + "wallets": "Plånböcker", "details_type": "Typ", - "details_use_with_hardware_wallet": "Använd med Hårdvaruplånbok", - "details_wallet_updated": "Plånbok uppdaterad", + "details_use_with_hardware_wallet": "Använd med hårdvaruplånbok", "details_yes_delete": "Ja, ta bort", "enter_bip38_password": "Ange lösenord för att dekryptera", "export_title": "exportera plånbok", @@ -431,56 +454,90 @@ "import_title": "Importera", "import_discovery_title": "Upptäckt", "import_discovery_subtitle": "Välj en upptäckt plånbok", - "import_discovery_derivation": "Använd anpassad derivation path", + "import_discovery_derivation": "Använd anpassad härledningsväg", "import_discovery_no_wallets": "Inga plånböcker hittades.", - "import_derivation_found": "hittades", - "import_derivation_found_not": "Hittades inte", - "import_derivation_loading": "laddar...", - "import_derivation_subtitle": "Ange anpassad derivation path så försöker vi hitta din plånbok", - "import_derivation_title": "Derivation path", - "import_derivation_unknown": "okänd", - "import_wrong_path": "fel derivation path", + "import_derivation_title": "Härledningsväg", "list_create_a_button": "Ny plånbok", "list_create_a_wallet": "Ny plånbok", - "list_create_a_wallet_text": "Det är gratis och du kan skapa\nhur många du vill.", + "list_create_a_wallet_text": "Det är gratis och du kan\nskapa hur många du vill.", "list_empty_txs1": "Dina transaktioner kommer att visas här", "list_empty_txs1_lightning": "Lightningplånboken ska användas för dagliga småtransaktioner. Avgifterna är minimala och transaktioner sker direkt.", "list_empty_txs2": "Börja med din plånbok.", "list_empty_txs2_lightning": "\nFör att komma igång klicka på \"sätt in / ta ut\" ovan och sätt in dina första bitcoin.", "list_latest_transaction": "Senaste transaktion", - "list_ln_browser": "LApp webbläsare", "list_long_choose": "Välj Foto", - "list_long_clipboard": "Kopiera från Urklipp", + "paste_from_clipboard": "Klistra in", + "import_file": "Importera fil", "list_long_scan": "Skanna QR kod", "list_title": "Plånböcker", "list_tryagain": "Försök igen", "no_ln_wallet_error": "Innan du betalar en Lightning-faktura måste du först lägga till en Lightning-plånbok.", "looks_like_bip38": "Detta ser ut som en lösenordsskyddad privat nyckel (BIP38).", - "reorder_title": "Sortera plånböcker", - "reorder_instructions": "Tryck och håll kvar en plånbok för att dra den över listan.", "please_continue_scanning": "Fortsätt att skanna.", "select_no_bitcoin": "Det finns för närvarande inga tillgängliga Bitcoin-plånböcker.", "select_no_bitcoin_exp": "En Bitcoin-plånbok krävs för att fylla på Lightning-plånböcker. Vänligen skapa eller importera en.", "select_wallet": "Välj plånbok", - "xpub_copiedToClipboard": "Kopierad till urklipp", "pull_to_refresh": "Dra för att uppdatera", - "warning_do_not_disclose": "Varning! Avslöja inte.", "add_ln_wallet_first": "Du måste först lägga till en Lightning-plånbok.", "identity_pubkey": "Identitet Pubkey", - "xpub_title": "plånbokens XPUB" + "xpub_title": "plånbokens XPUB", + "add_entropy_reset_title": "Återställ entropi", + "add_entropy_reset_message": "Att ändra plånbokstypen återställer den aktuella entropin. Vill du fortsätta?", + "add_entropy_bytes": "{bytes} byte entropi", + "add_lndhub": "Anslut till din LNDhub", + "add_lndhub_error": "Den angivna nodadressen är en ogiltig LNDhub-nod.", + "add_wallet_seed_length": "Seed-längd", + "add_wallet_seed_length_12": "12 ord", + "add_wallet_seed_length_24": "24 ord", + "clear_clipboard_on_import": "Töm urklipp vid import", + "details_del_wb_err": "Det angivna saldot matchar inte denna plånboks saldo. Försök igen.", + "details_display": "Visa på startskärmen", + "swipe_balance_hide": "Dölj", + "swipe_balance_show": "Visa", + "drag_to_reorder": "Dra för att ändra ordning", + "clear_search": "Rensa sökning", + "learn_more": "Läs mer", + "import_discovery_offline": "BlueWallet är för närvarande i offline-läge. I detta läge kan den inte verifiera plånbokens existens, så du måste välja rätt plånbok manuellt", + "import_derivation_found": "Hittad", + "import_derivation_found_not": "Hittades inte", + "import_derivation_loading": "Läser in...", + "import_derivation_subtitle": "Ange anpassad härledningsväg så försöker vi hitta din plånbok.", + "import_derivation_unknown": "Okänd", + "import_wrong_path": "Felaktig härledningsväg", + "manage_title": "Hantera plånböcker", + "no_results_found": "Inga resultat hittades.", + "warning_do_not_disclose": "Dela aldrig informationen nedan", + "scan_import": "Skanna denna QR-kod för att importera din plånbok i en annan applikation.", + "write_down_header": "Skapa en manuell säkerhetskopia", + "write_down": "Skriv ner och lagra dessa ord säkert. Använd dem för att återställa din plånbok vid ett senare tillfälle.", + "wallet_type_this": "Denna plånbokstyp är {type}.", + "share_number": "Dela {number}", + "copy_ln_url": "Kopiera och lagra denna URL säkert för att kunna återställa din plånbok vid ett senare tillfälle.", + "copy_ln_public": "Kopiera och lagra denna information säkert för att kunna återställa din plånbok vid ett senare tillfälle.", + "manage_wallets_search_placeholder": "Sök plånböcker, adresser, transaktioner och memon", + "more_info": "Mer information", + "details_delete_wallet_error_message": "Det gick inte att bekräfta om denna plånbok togs bort från aviseringar—detta kan bero på ett nätverksproblem eller dålig anslutning. Om du fortsätter kan du fortfarande få aviseringar om transaktioner relaterade till denna plånbok, även efter att den har raderats.", + "details_delete_anyway": "Radera ändå" + }, + "total_balance_view": { + "title": "Totalt saldo", + "display_in_bitcoin": "Visa i Bitcoin", + "hide": "Dölj", + "display_in_sats": "Visa i sats", + "display_in_fiat": "Visa i {currency}", + "explanation": "Visa det totala saldot för alla dina plånböcker på översiktsskärmen." }, "multisig": { - "multisig_vault": "Valv", + "multisig_vault": "Multisig Valv", "default_label": "Multisig Valv", "multisig_vault_explain": "Bästa säkerheten för stora belopp", "provide_signature": "Ge signatur", - "vault_key": "Valvnyckel {number}", + "vault_key": "Valv-nyckel {number}", "required_keys_out_of_total": "Nödvändiga nycklar av totalt antal", "fee": "Avgift: {number}", "fee_btc": "{number} BTC", "confirm": "Bekräfta", "header": "Skicka", - "share": "dela", "view": "Granska", "manage_keys": "Hantera nycklar", "how_many_signatures_can_bluewallet_make": "hur många signaturer kan BlueWallet göra", @@ -493,52 +550,60 @@ "create": "Skapa", "native_segwit_title": "Bästa praxis", "wrapped_segwit_title": "Bästa kompatibilitet", - "legacy_title": "Gamla", + "legacy_title": "Äldre", "co_sign_transaction": "Signera en transaktion", - "what_is_vault": "Ett valv är en", + "what_is_vault": "Ett Valv är en", "what_is_vault_numberOfWallets": " {m}-av-{n} multisig ", "what_is_vault_wallet": "plånbok.", - "vault_advanced_customize": "Valvinställningar", + "vault_advanced_customize": "Valv-inställningar", "needs": "Det behöver", - "what_is_vault_description_number_of_vault_keys": "{m} valvnycklar", + "what_is_vault_description_number_of_vault_keys": "{m} Valv-nycklar", "what_is_vault_description_to_spend": "att spendera och en tredje du\nkan använda som backup.", "what_is_vault_description_to_spend_other": "att spendera.", "quorum": "{m} av {n} kvorum", "quorum_header": "Kvorum", "of": "av", "wallet_type": "Plånbokstyp", - "invalid_mnemonics": "Denna mnemoniska fras verkar inte vara giltig.", - "invalid_cosigner": "Ogiltig medundertecknings-data", "not_a_multisignature_xpub": "Det här är inte en XPUB från en multisignaturplånbok!", - "invalid_cosigner_format": "Felaktig cosigner: Detta är inte en cosigner för formatet {format}.", "create_new_key": "Skapa ny", "scan_or_open_file": "Skanna eller öppna filen", "i_have_mnemonics": "Jag har en seed till den här nyckeln.", "type_your_mnemonics": "Infoga en seed för att importera din befintliga Valv-nyckel.", - "this_is_cosigners_xpub": "Detta är cosignerns XPUB – redo att importeras till en annan plånbok. Det är säkert att dela det.", - "wallet_key_created": "Din Vault-nyckel skapades. Ta en stund för att säkerhetskopiera din mnemonic seed.", - "are_you_sure_seed_will_be_lost": "Är du säker? Din mnemonic seed kommer att gå förlorat om du inte har en backup.", + "wallet_key_created": "Din Valv-nyckel skapades. Ta en stund för att säkerhetskopiera din mnemoniska fras.", + "are_you_sure_seed_will_be_lost": "Är du säker? Din mnemoniska fras kommer att gå förlorad om du inte har en backup.", "forget_this_seed": "Glöm denna seed och använd XPUB istället.", - "view_edit_cosigners": "Visa/redigera Medundertecknare", - "this_cosigner_is_already_imported": "Denna medundertecknare är redan importerad.", "export_signed_psbt": "Exportera Signerad PSBT", "input_fp": "Ange fingerprint", "input_fp_explain": "Hoppa över för att använda standarden (00000000)", - "input_path": "Skriv in Derivation Path", + "input_path": "Skriv in härledningsväg", "input_path_explain": "Hoppa över för att använda standarden ({default})", "ms_help": "Hjälp", "ms_help_title": "Så fungerar Multisig Valv: Tips och tricks", "ms_help_text": "En plånbok med flera nycklar, för ökad säkerhet eller delad vårdnad", "ms_help_title1": "Flera enheter rekommenderas.", - "ms_help_1": "Valvet kommer att fungera med andra BlueWallet-appar och PSBT-kompatibla plånböcker, som Electrum, Spectre, Coldcard, Cobo Vault, etc.", + "ms_help_1": "Ditt Valv fungerar med andra BlueWallet-appar och PSBT-kompatibla plånböcker, som Electrum, Specter, Coldcard, Cobo Vault, etc.", "ms_help_title2": "Redigeringsnycklar", "ms_help_2": "Du kan skapa alla Valv-nycklar på den här enheten och ta bort eller redigera dem senare. Att ha alla nycklar på samma enhet har samma säkerhet som en vanlig Bitcoin-plånbok.", - "ms_help_title3": "Vault Backups", - "ms_help_3": "På plånboksalternativen hittar du din Valv-säkerhetskopia och enbart \"se bara\" backup. Denna säkerhetskopia är som en karta till din plånbok. Det är viktigt för återhämtning av plånboken om du tappar en av dina seed.", + "ms_help_title3": "Valv-säkerhetskopior", + "ms_help_3": "På plånboksalternativen hittar du din Valv-säkerhetskopia och watch-only-säkerhetskopia. Denna säkerhetskopia är som en karta till din plånbok. Det är viktigt för återhämtning av plånboken om du tappar en av dina seed.", "ms_help_title4": "Importerar Valv", "ms_help_4": "För att importera en multisig, använd din säkerhetskopia och importfunktionen. Om du bara har seeds och XPUB:er kan du använda den individuella importknappen när du skapar Valv-nycklar.", - "ms_help_title5": "Enable advanced mode", - "ms_help_5": "Som standard genererar BlueWallet ett 2-av-3 valv. För att skapa ett annat kvorum eller ändra adresstypen, aktivera Avancerat läge i Inställningar." + "ms_help_title5": "Avancerat läge", + "ms_help_5": "Som standard genererar BlueWallet ett 2-av-3 Valv. För att skapa ett annat kvorum eller ändra adresstypen, aktivera Avancerat läge i Inställningar.", + "provide_signature_details": "Använd din enhet och plånbok där nyckeln finns för att signera denna transaktion", + "provide_signature_details_bluewallet": "I BlueWallet, gå till menyn på skickaskärmen och välj ", + "provide_signature_next_steps": "Skanna eller importera signerad transaktion", + "provide_signature_next_steps_details": "När din plånbok har signerat transaktionen, skanna den medföljande QR-koden eller importera den medföljande filen och granska sedan alla transaktionsdetaljer innan du sänder den.", + "share": "Dela...", + "shared_key_detected": "Delad medundertecknare", + "shared_key_detected_question": "En medundertecknare har delats med dig, vill du importera den?", + "invalid_mnemonics": "Denna mnemoniska fras verkar inte vara giltig.", + "invalid_cosigner": "Ogiltig data för medundertecknare", + "invalid_cosigner_format": "Felaktig medundertecknare: Detta är inte en medundertecknare för {format}-format.", + "this_is_cosigners_xpub": "Detta är medundertecknarens XPUB—redo att importeras till en annan plånbok. Det är säkert att dela den.", + "this_is_cosigners_xpub_airdrop": "Om du delar via AirDrop måste mottagarna vara på koordinationsskärmen.", + "view_edit_cosigners": "Visa/redigera medundertecknare", + "this_cosigner_is_already_imported": "Denna medundertecknare är redan importerad." }, "is_it_my_address": { "title": "Är det min adress?", @@ -546,20 +611,33 @@ "enter_address": "Ange adress", "check_address": "Kontrollera adressen", "no_wallet_owns_address": "Ingen av de tillgängliga plånböckerna äger den angivna adressen.", - "view_qrcode": "Visa QR-koden" + "view_qrcode": "Visa QR-kod" + }, + "autofill_word": { + "title": "Sista ordet i seed", + "enter": "Ange din partiella mnemoniska fras", + "generate_word": "Generera det sista ordet", + "error": "Indata är inte en partiell mnemonisk fras med 11 eller 23 ord. Försök igen." }, "cc": { "change": "ändra", "coins_selected": "Valda mynt ({number})", "selected_summ": "{value} har valts", - "empty": "Den här plånboken har inga mynt för tillfället.", "freeze": "Lås", "freezeLabel": "Lås", "freezeLabel_un": "Lås upp", "header": "Myntkontroll", "use_coin": "Använd mynt", "use_coins": "Använd mynten", - "tip": "Den här funktionen låter dig se, märka, låsta eller välja mynt för förbättrad plånbokshantering. Du kan välja flera mynt genom att trycka på de färgade cirklarna." + "tip": "Den här funktionen låter dig se, märka, låsa eller välja mynt för förbättrad plånbokshantering. Du kan välja flera mynt genom att trycka på de färgade cirklarna.", + "sort_label": "Etikett", + "sort_status": "Status", + "empty": "Den här plånboken har inga mynt för tillfället.", + "sort_asc": "Stigande", + "sort_desc": "Fallande", + "sort_height": "Höjd", + "sort_value": "Värde", + "sort_by": "Sortera efter" }, "units": { "BTC": "BTC", @@ -581,7 +659,9 @@ "type_change": "ändra", "type_receive": "Ta emot", "type_used": "Använd", - "transactions": "Transaktioner" + "transactions": "Transaktioner", + "copy_private_key": "Kopiera privat nyckel", + "sensitive_private_key": "Varning: privata nycklar är extremt känsliga. Fortsätta?" }, "lnurl_auth": { "register_question_part_1": "Vill du registrera ett konto på", @@ -601,9 +681,24 @@ }, "bip47": { "payment_code": "Betalningskod", - "payment_codes_list": "Lista över betalningskoder", - "who_can_pay_me": "Vem kan betala mig:", "purpose": "Återanvändbar och delbar kod (BIP47)", - "not_found": "Betalningskoden hittades inte" + "not_found": "Betalningskoden hittades inte", + "contacts": "Kontakter", + "bip47_explain": "Återanvändbar och delbar kod", + "bip47_explain_subtitle": "BIP47", + "pay_this_contact": "Betala denna kontakt", + "rename_contact": "Byt namn på kontakt", + "copy_payment_code": "Kopiera betalningskod", + "hide_contact": "Dölj kontakt", + "rename": "Byt namn", + "provide_name": "Ange nytt namn för denna kontakt", + "add_contact": "Lägg till kontakt", + "provide_payment_code": "Ange betalningskod", + "invalid_pc": "Ogiltig betalningskod", + "notification_tx_unconfirmed": "Aviseringstransaktionen är inte bekräftad ännu, vänligen vänta", + "failed_create_notif_tx": "Det gick inte att skapa on-chain-transaktion", + "onchain_tx_needed": "On-chain-transaktion krävs", + "notif_tx_sent": "Aviseringstransaktion skickad. Vänta tills den bekräftas", + "notif_tx": "Aviseringstransaktion" } } diff --git a/loc/th_th.json b/loc/th_th.json index 5efbc5d8bfb..288ca7cd7f2 100644 --- a/loc/th_th.json +++ b/loc/th_th.json @@ -10,10 +10,26 @@ "storage_is_encrypted": "ที่เก็บข้อมูลของคุณถูกเข้ารหัส. ต้องการรหัสผ่านเพื่อถอดรหัส", "yes": "ถูกต้อง", "no": "ไม่", - "save": "บันทึก", "seed": "ซีด", "success": "สำเร็จ", - "wallet_key": "กุญแจกระเป๋าเงิน" + "wallet_key": "กุญแจกระเป๋าเงิน", + "clipboard": "คลิปบอร์ด", + "copied": "คัดลอกแล้ว!", + "discard_changes": "ละทิ้งการเปลี่ยนแปลง?", + "discard_changes_explain": "ท่านมีการเปลี่ยนแปลงที่ยังไม่ได้บันทึก ท่านแน่ใจหรือไม่ว่าต้องการละทิ้งและออกจากหน้าจอนี้?", + "enter_url": "ใส่ URL", + "save": "บันทึก...", + "close": "ปิด", + "change_input_currency": "เปลี่ยนสกุลเงินที่ป้อน", + "refresh": "รีเฟรช", + "pick_image": "เลือกจากคลังภาพ", + "pick_file": "เลือกไฟล์", + "enter_amount": "ใส่จำนวนเงิน", + "qr_custom_input_button": "แตะ 10 ครั้งเพื่อป้อนข้อมูลกำหนดเอง", + "unlock": "ปลดล็อก", + "port": "พอร์ต", + "ssl_port": "พอร์ต SSL", + "suggested": "ที่แนะนำ" }, "azteco": { "codeIs": "รหัสบัตรกำนัลของท่านคือ", @@ -22,64 +38,78 @@ "redeem": "ไถ่ถอนไปที่กระเป๋าสตางค์", "redeemButton": "ไถ่ถอน", "success": "สำเร็จ", - "title": "ไถ่ถอนบัตรกำนัล Azte.co" + "title": "ไถ่ถอนบัตรกำนัล Azte.co", + "successMessage": "ไถ่ถอนบัตรกำนัลสำเร็จ! เงินของท่านควรจะเข้ามาในกระเป๋าสตางค์ Bitcoin ในไม่ช้า" }, "entropy": { "save": "บันทึก", "title": "เอนโทรปี", - "undo": "ยกเลิก" + "undo": "ยกเลิก", + "amountOfEntropy": "{bits} จาก {limit} บิต" }, "errors": { - "broadcast": "ไม่สามารถบอร์ดคาสต์ได้", + "broadcast": "ไม่สามารถบรอดคาสต์ได้", "error": "ผิดพลาด", "network": "เน็ตเวิร์คผิดพลาด" }, "lnd": { - "errorInvoiceExpired": "ใบวางบิลหมดอายุแล้ว", "expired": "หมดอายุแล้ว", "payButton": "จ่าย", - "placeholder": "ใบวางบิล", "potentialFee": "ค่าธรรมเนียมโดยประมาณ: {fee}", "refill": "เติม", "refill_create": "กรุณาสร้างกระเป๋าสตางค์บิตคอยน์ เพื่อดำเนินการต่อ", "refill_external": "เติมด้วยกระเป๋าสตางค์ภายนอก", "refill_lnd_balance": "เติมกระเป๋าสตางค์ไลท์นิง", "sameWalletAsInvoiceError": "คุณไม่สามารถจ่ายใบแจ้งหนี้นี้ด้วยกระเป๋าสตางค์อันเดียวกันกับที่ใช้สร้างมัน.", - "title": "จัดการเงิน" + "title": "จัดการเงิน", + "errorInvoiceExpired": "ใบแจ้งหนี้หมดอายุแล้ว", + "expiresIn": "หมดอายุใน {time} นาที", + "payment": "การชำระเงิน", + "placeholder": "ใบแจ้งหนี้หรือแอดเดรส" }, "lndViewInvoice": { "additional_info": "ข้อมูลเพิ่มเติม", "for": "สำหรับ:", "lightning_invoice": "ใบแจ้งหนี้ไลท์นิง", - "open_direct_channel": "เปิดช่องโดยตรงไปที่โหนดนี้", "please_pay": "กรุณาจ่าย", - "preimage": "Preimage", "sats": "แซท", - "wasnt_paid_and_expired": "ใบวางบิลนี้ไม่ได้จ่ายและหมดอายุแล้ว" + "wasnt_paid_and_expired": "ใบวางบิลนี้ไม่ได้จ่ายและหมดอายุแล้ว", + "please_pay_between_and": "กรุณาจ่ายระหว่าง {min} และ {max}", + "preimage": "พรีอิมเมจ", + "date_time": "วันที่และเวลา" }, "plausibledeniability": { "create_fake_storage": "สร้างที่เก็บข้อมูลเทียม", - "create_password": "สร้างรหัสผ่าน", - "create_password_explanation": "รหัสผ่านสำหรับที่เก็บข้อมูลเทียมไม่ควรตรงกับรหัสผ่านที่ใช้กับที่เก็บข้อมูลเทียมจริง", - "help": "ภายใต้บางสถานการ์ณ, คุณอาจจะจำเป็นต้องเปิดเผยรหัสผ่าน. เพื่อเก็บเหรียญให้ปลอดถัย บูลวอลเล็ทสามารถสร้างที่เก็บข้อมูลอีกแห่งหนึ่งโดยใช้รหัสผ่านคนละอัน. ภายใต้สถานการ์ณที่จำเป็น คุณสามารถเปิดเลยรหัสผ่านนี้กับบุคคลที่สาม. และเมื่อใส่รหัสผ่านนี้ใน บลูวอลเล็ท ที่เก็บข้อมูลเทียมจะถูกเปิด. และน่าจะเป็นที่ยอมรับได้ต่อบุคลที่สาม, วิธีนี้จะทำให้ที่เก็บข้อมูลหลักมีความปลอดภัยและเป็นความลับ.", + "create_password_explanation": "รหัสผ่านสำหรับที่เก็บข้อมูลเทียมไม่ควรตรงกับรหัสผ่านสำหรับที่เก็บข้อมูลหลักของคุณ", + "help": "ภายใต้บางสถานการณ์ คุณอาจจำเป็นต้องเปิดเผยรหัสผ่าน เพื่อเก็บเหรียญให้ปลอดภัย BlueWallet สามารถสร้างที่เก็บข้อมูลอีกแห่งหนึ่งโดยใช้รหัสผ่านคนละอัน ภายใต้สถานการณ์ที่จำเป็น คุณสามารถเปิดเผยรหัสผ่านนี้กับบุคคลที่สาม และเมื่อใส่รหัสผ่านนี้ใน BlueWallet ที่เก็บข้อมูลเทียมจะถูกเปิด และน่าจะเป็นที่ยอมรับได้ต่อบุคคลที่สาม วิธีนี้จะทำให้ที่เก็บข้อมูลหลักมีความปลอดภัยและเป็นความลับ", "help2": "ที่เก็บข้อมูลอันใหม่จะทำงานได้สมบูรณ์ และคุณสามารถเก็บจำนวนเงินขั้นต่ำได้ โดยที่มีความน่าเชื่อถือ.", - "password_should_not_match": "รหัสผ่านสำหรับที่เก็บข้อมูลเทียมไม่ควรตรงกับรหัสผ่านที่ใช้กับที่เก็บข้อมูลเทียมจริง", - "passwords_do_not_match": "รหัสผ่านไม่ตรงกัน ", - "retype_password": "ใส่รหัสผ่านอีกครั้ง ใส่รหัสผ่านอีกครั้ง", - "success": "สำเร็จ", + "password_should_not_match": "รหัสผ่านนี้ถูกใช้งานอยู่แล้ว กรุณาลองใช้รหัสผ่านอื่น", "title": "การปฏิเสธที่เป็นไปได้" }, "pleasebackup": { "ask": "ท่านได้บันทึก backup phrase แล้วใช่หรือไม่? ในกรณีที่ท่านสูญเสียอุปกรณ์นี้ ท่านสามารถกู้เงินของท่านได้ด้วย backup phrase อันนี้ หากท่านไม่ได้บันทึก backup phrase อันนี้ไว้ ท่านจะสูญเสียเงินของท่านไปอย่างถาวร", - "ask_no": "ยัง ฉันยังไม่ได้ทำ", - "ask_yes": "ใช่ ฉันได้ทำแล้ว" + "ok_lnd": "ฉันได้บันทึกไว้แล้ว", + "title": "ได้สร้างกระเป๋าสตางค์ของท่านแล้ว", + "ask_no": "ไม่ ฉันยังไม่ได้บันทึก", + "ask_yes": "ใช่ ฉันได้บันทึกแล้ว", + "ok": "ตกลง ฉันได้จดบันทึกไว้แล้ว", + "text": "กรุณาใช้เวลาสักครู่จดวลีนีโมนิกนี้ลงบนกระดาษ\nนี่คือสำรองข้อมูลของท่าน และท่านสามารถใช้กู้คืนกระเป๋าสตางค์ได้", + "text_lnd": "กรุณาบันทึกสำรองข้อมูลกระเป๋าสตางค์นี้ ท่านสามารถใช้กู้คืนกระเป๋าสตางค์ในกรณีที่สูญหาย" }, "receive": { "details_create": "สร้าง", "details_label": "คำอธิบาย", "details_setAmount": "รับด้วยจำนวน", - "details_share": "แชร์", - "header": "รับ" + "header": "รับ", + "details_share": "แชร์...", + "address_not_found": "ไม่สามารถสร้างแอดเดรสสำหรับรับได้", + "reset": "รีเซ็ต", + "maxSats": "จำนวนสูงสุดคือ {max} แซท", + "maxSatsFull": "จำนวนสูงสุดคือ {max} แซท หรือ {currency}", + "minSats": "จำนวนขั้นต่ำคือ {min} แซท", + "minSatsFull": "จำนวนขั้นต่ำคือ {min} แซท หรือ {currency}", + "qrcode_for_the_address": "คิวอาร์โค้ดสำหรับแอดเดรส", + "bip47_explanation": "รหัสการชำระเงินคือแอดเดรสสากลที่หลีกเลี่ยงการเปิดเผยแอดเดรสในกระเป๋าสตางค์ของท่าน บริการบางแห่งอาจไม่รองรับ" }, "send": { "broadcastButton": "บรอดคาสต์", @@ -90,12 +120,12 @@ "confirm_header": "ยืนยัน", "confirm_sendNow": "ส่งเดี๋ยวนี้", "create_amount": "จำนวนเงิน", - "create_broadcast": "บรอดคาซท์", - "create_copy": "ค้ดลอกและบอรดคาสต์ภายหลัง", + "create_broadcast": "บรอดคาสต์", + "create_copy": "คัดลอกและบรอดคาสต์ภายหลัง", "create_details": "รายละเอียด", "create_fee": "ค่าธรรมเนียม", "create_memo": "บันทึกช่วยจำ", - "create_this_is_hex": "นี่คือ hex ของธุรกรรม, signed แล้วและพร้อมที่จะบรอดคาซท์ไปยังเน็ตเวิร์ค.", + "create_this_is_hex": "นี่คือ hex ของธุรกรรม, signed แล้วและพร้อมที่จะบรอดคาสต์ไปยังเน็ตเวิร์ค.", "create_to": "ถึง", "create_tx_size": "ขนาดธุรกรรม", "create_verify": "ตรวจสอบที่ coinb.in", @@ -134,16 +164,49 @@ "input_paste": "วาง", "input_total": "ยอดรวม:", "permission_camera_message": "ต้องการคำอนุญาตในการใช้กล้อง", - "permission_camera_title": "อนุญาตในการใช้กล้อง", "psbt_sign": "ลงชื่อในธุรกรรม", "open_settings": "เปิดการตั้งค่า", - "permission_storage_later": "ถามฉันภายหลัง", "psbt_clipboard": "คัดลอกไปที่คลิ๊ปบอร์ด", "psbt_tx_export": "ส่งไปที่ไฟล์", "psbt_tx_open": "เปิดธุรกรรมที่ลงนามไว้แล้ว", "psbt_tx_scan": "สแกนธุรกรรมที่ลงนามไว้แล้ว", "success_done": "สำเร็จ", - "txSaved": "ธุรกรรมนี้ได้บันทึกใน {filePath}" + "provided_address_is_invoice": "แอดเดรสนี้ดูเหมือนจะเป็นใบแจ้งหนี้ Lightning กรุณาไปที่กระเป๋าสตางค์ Lightning ของท่านเพื่อชำระเงินสำหรับใบแจ้งหนี้นี้", + "create_satoshi_per_vbyte": "ซาโตชิต่อ vByte", + "details_insert_contact": "ใส่ผู้ติดต่อ", + "details_add_recc_rem_all_alert_description": "ท่านแน่ใจหรือไม่ว่าต้องการลบผู้รับทั้งหมด?", + "details_add_rec_rem_all": "ลบผู้รับทั้งหมด", + "details_recipients_title": "ผู้รับ", + "details_recipient_title": "ผู้รับที่ #{number} จาก #{total}", + "please_complete_recipient_title": "ผู้รับไม่สมบูรณ์", + "please_complete_recipient_details": "กรุณากรอกรายละเอียดของผู้รับ #{number} ให้ครบถ้วนก่อนเพิ่มผู้รับใหม่", + "details_adv_full_sure_frozen": "ท่านแน่ใจหรือไม่ว่าต้องการใช้ยอดเต็มของกระเป๋าสตางค์สำหรับธุรกรรมนี้? โปรดทราบว่าเหรียญที่ถูกระงับจะไม่ถูกรวม", + "details_adv_import_qr": "นำเข้าธุรกรรม (QR)", + "details_error_decode": "ไม่สามารถถอดรหัสแอดเดรส Bitcoin ได้", + "details_frozen": "{amount} BTC ถูกระงับ", + "details_no_signed_tx": "ไฟล์ที่เลือกไม่มีธุรกรรมที่สามารถนำเข้าได้", + "details_scan_hint": "แตะสองครั้งเพื่อสแกนหรือนำเข้าปลายทาง", + "details_scan_error": "สแกนผิดพลาด", + "details_total_exceeds_balance_frozen": "จำนวนที่ส่งเกินยอดคงเหลือที่มี โปรดทราบว่าเหรียญที่ถูกระงับจะไม่ถูกรวม", + "details_unrecognized_file_format": "ไม่รู้จักรูปแบบไฟล์", + "insert_custom_fee": "ใส่ค่าธรรมเนียม", + "fee_replace_minvb": "อัตราค่าธรรมเนียมรวม (ซาโตชิต่อ vByte) ที่ท่านต้องการจ่ายควรสูงกว่า {min} sat/vByte", + "fee_satvbyte": "ใน sat/vByte", + "invalid_psbt": "PSBT ที่ให้มาไม่ถูกต้อง", + "permission_storage_denied_message": "BlueWallet ไม่สามารถบันทึกไฟล์นี้ได้ กรุณาเปิดการตั้งค่าอุปกรณ์ของท่านและเปิดใช้สิทธิ์การเข้าถึงที่จัดเก็บข้อมูล", + "permission_storage_title": "สิทธิ์การเข้าถึงที่จัดเก็บข้อมูล", + "psbt_this_is_psbt": "นี่คือธุรกรรม Bitcoin ที่ลงนามบางส่วน (PSBT) กรุณาลงนามให้เสร็จสมบูรณ์ด้วยกระเป๋าฮาร์ดแวร์ของท่าน", + "no_tx_signing_in_progress": "ไม่มีการลงนามธุรกรรมที่กำลังดำเนินการอยู่", + "outdated_rate": "อัตราอัปเดตล่าสุด: {date}", + "qr_error_no_qrcode": "เราไม่สามารถพบคิวอาร์โค้ดที่ถูกต้องในรูปภาพที่เลือก กรุณาตรวจสอบให้แน่ใจว่ารูปภาพมีเพียงคิวอาร์โค้ดและไม่มีเนื้อหาเพิ่มเติม เช่น ข้อความหรือปุ่ม", + "reset_amount": "รีเซ็ตจำนวน", + "reset_amount_confirm": "ท่านต้องการรีเซ็ตจำนวนหรือไม่?", + "txSaved": "ไฟล์ธุรกรรม ({filePath}) ถูกบันทึกแล้ว", + "file_saved_at_path": "ไฟล์ ({filePath}) ถูกบันทึกแล้ว", + "cant_send_to_silentpayment_adress": "กระเป๋าสตางค์นี้ไม่สามารถส่งไปยังแอดเดรส Silent Payments ได้", + "cant_send_to_bip47": "กระเป๋าสตางค์นี้ไม่สามารถส่งไปยังรหัสการชำระเงิน BIP47 ได้", + "cant_find_bip47_notification": "เพิ่มรหัสการชำระเงินนี้ไปยังผู้ติดต่อก่อน", + "problem_with_psbt": "มีปัญหากับ PSBT" }, "settings": { "about": "เกี่ยวกับ", @@ -154,59 +217,116 @@ "about_selftest": "ทำการทดสอบ", "about_sm_github": "กิตฮับ", "about_sm_telegram": "เทเลแกรมแช็ท", - "about_sm_twitter": "ติดตามเราในทวิตเตอร์", - "advanced_options": "ตัวเลือกขั้นสูง", - "biometrics": "Biometrics", + "biometrics": "ไบโอเมตริก", "biom_conf_identity": "กรุณายืนยันตัวตนของท่าน", "currency": "สกุลเงิน", - "default_desc": "เมื่อปิดการใช้งาน บูลวอลเล็ทจะเปิดกระเป๋าสตางค์ที่เลือกเมื่อท่านเปิดแอ๊พ", "default_title": "เมื่อเปิดตัว", - "default_wallets": "ดูกระเป๋าสตางค์ทุกอัน", "electrum_connected": "เชื่อมต่อแล้ว", "electrum_connected_not": "ไม่ได้เชื่อมต่อ", - "electrum_error_connect": "ไม่สามารถเชื่อมต่อไปยังเซิร์ฟเวอร์ Electrum ได้", - "electrum_saved": "บันทึกสำเร็จ ท่านอาจจำเป็นต้องรีสตาร์ท", + "electrum_saved": "บันทึกการเปลี่ยนแปลงเรียบร้อยแล้ว ท่านอาจจำเป็นต้องรีสตาร์ท BlueWallet เพื่อให้การเปลี่ยนแปลงมีผล", "electrum_settings_server": "เซิร์ฟเวอร์ Electrum", "electrum_status": "สถานะ", - "electrum_clear_alert_title": "ท่านต้องการลบประวัติการรใช้งานหรือไม่?", - "electrum_clear_alert_message": "ท่านต้องการลบประวัติการใช้งานของ electrum server หรือไม่?", - "electrum_clear_alert_cancel": "ยกเลิก", - "electrum_clear_alert_ok": "ตกลง", - "electrum_select": "เลือก", "electrum_reset": "รีเซ็ตเป็นค่าเริ่มต้น", - "electrum_history": "ประวัติของเซิร์ฟเวอร์", - "electrum_clear": "ลบ", "encrypt_decrypt": "เข้ารหัสที่เก็บข้อมูล", "encrypt_title": "ความปลอดภัย", "encrypt_tstorage": "ที่เก็บข้อมูล", "encrypt_use": "ใช้ {type}", "general": "ทั่วไป", - "general_adv_mode": "โหมดขั้นสูง", "general_continuity": "ความต่อเนื่อง", "header": "ตั้งค่า", "language": "ภาษา", "lightning_saved": "บันทึกสำเร็จ", "lightning_settings": "การตั้งค่าไลท์นิง", "network": "เน็ตเวิร์ค", - "network_broadcast": "บอร์ดคาสต์ธุรกรรม", + "network_broadcast": "บรอดคาสต์ธุรกรรม", "network_electrum": "เซิร์ฟเวอร์ Electrum", "notifications": "แจ้งเตือน", "open_link_in_explorer": "ไปที่เอ๊กโพเลอร์", "password": "รหัสผ่าน", - "password_explain": "สร้างรหัสผ่านที่จะใช้ในการเข้ารหัสที่เก็บข้อมูล", - "passwords_do_not_match": "รหัสผ่านไม่ตรงกัน", "plausible_deniability": "การปฏิเสธที่เป็นไปได้...", "privacy": "ความเป็นส่วนตัว", "privacy_read_clipboard": "อ่านค่าจากคลิปบอร์ด", "privacy_system_settings": "ตั้งค่าระบบ", - "push_notifications": "การแจ้งเตือนแบบ Push", - "retype_password": "ใส่รหัสผ่านอีกครั้ง", "save": "บันทึก", "saved": "บันทึกแล้ว", - "total_balance": "ยอดรวม" + "total_balance": "ยอดรวม", + "about_awesome": "สร้างด้วยความเยี่ยมยอด", + "about_license": "ใบอนุญาต MIT", + "performance_score": "คะแนนประสิทธิภาพ: {num}", + "run_performance_test": "ทดสอบประสิทธิภาพ", + "block_explorer_invalid_custom_url": "URL ที่ให้มาไม่ถูกต้อง กรุณาใส่ URL ที่ถูกต้องโดยขึ้นต้นด้วย http:// หรือ https://", + "about_selftest_electrum_disabled": "การทดสอบไม่สามารถใช้งานได้ในโหมดออฟไลน์ของ Electrum กรุณาปิดโหมดออฟไลน์แล้วลองอีกครั้ง", + "about_selftest_ok": "การทดสอบภายในทั้งหมดผ่านสำเร็จ กระเป๋าสตางค์ทำงานได้ดี", + "privacy_temporary_screenshots": "อนุญาตการจับภาพหน้าจอ", + "privacy_temporary_screenshots_instructions": "การป้องกันการจับภาพหน้าจอจะถูกปิดชั่วคราว ทำให้สามารถถ่ายภาพหน้าจอและบันทึกหน้าจอได้ การป้องกันจะเปิดใช้งานอัตโนมัติเมื่อท่านปิดและเปิด BlueWallet ใหม่", + "biometrics_no_longer_available": "การตั้งค่าอุปกรณ์ของท่านมีการเปลี่ยนแปลงและไม่ตรงกับการตั้งค่าความปลอดภัยที่เลือกในแอป กรุณาเปิดใช้งานไบโอเมตริกหรือรหัสผ่านอุปกรณ์อีกครั้ง จากนั้นรีสตาร์ทแอปเพื่อให้การเปลี่ยนแปลงมีผล", + "biom_10times": "ท่านได้พยายามใส่รหัสผ่าน 10 ครั้ง ท่านต้องการรีเซ็ตที่เก็บข้อมูลหรือไม่? การดำเนินการนี้จะลบกระเป๋าสตางค์ทั้งหมดและถอดรหัสที่เก็บข้อมูลของท่าน", + "biom_no_passcode": "อุปกรณ์ของท่านไม่ได้เปิดใช้งานรหัสผ่านอุปกรณ์หรือไบโอเมตริก เพื่อดำเนินการต่อ กรุณาตั้งค่ารหัสผ่านอุปกรณ์หรือไบโอเมตริกในแอปการตั้งค่า", + "biom_remove_decrypt": "กระเป๋าสตางค์ทั้งหมดของท่านจะถูกลบและที่เก็บข้อมูลของท่านจะถูกถอดรหัส ท่านแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?", + "currency_source": "อัตราได้มาจาก", + "currency_fetch_error": "เกิดข้อผิดพลาดในขณะรับอัตราสำหรับสกุลเงินที่เลือก", + "donate": "บริจาค", + "donate_description": "ช่วยเราให้ Blue ฟรีต่อไป!", + "electrum_error_connect": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ Electrum ที่ให้มา", + "electrum_error_connect_tor": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ Electrum ที่ให้มา กรุณาตรวจสอบให้แน่ใจว่าแอป Orbot เชื่อมต่ออยู่และลองอีกครั้ง", + "lndhub_github": "ที่เก็บโค้ด GitHub", + "lndhub_uri": "เช่น {example}", + "electrum_host": "เช่น {example}", + "electrum_offline_mode": "โหมดออฟไลน์", + "electrum_offline_description": "เมื่อเปิดใช้งาน กระเป๋าสตางค์ Bitcoin ของท่านจะไม่พยายามดึงข้อมูลยอดคงเหลือหรือธุรกรรม", + "electrum_port": "พอร์ต โดยปกติคือ {example}", + "use_ssl": "ใช้ SSL", + "set_electrum_server_as_default": "ตั้งค่า {server} เป็นเซิร์ฟเวอร์ Electrum เริ่มต้น?", + "set_lndhub_as_default": "ตั้งค่า {url} เป็นเซิร์ฟเวอร์ LNDhub เริ่มต้น?", + "electrum_preferred_server": "เซิร์ฟเวอร์ที่ต้องการ", + "electrum_preferred_server_description": "ใส่เซิร์ฟเวอร์ที่ท่านต้องการให้กระเป๋าสตางค์ใช้สำหรับกิจกรรม Bitcoin ทั้งหมด เมื่อตั้งค่าแล้ว กระเป๋าสตางค์ของท่านจะใช้เซิร์ฟเวอร์นี้เท่านั้นในการตรวจสอบยอดคงเหลือ ส่งธุรกรรม และดึงข้อมูลเครือข่าย ตรวจสอบให้แน่ใจว่าท่านเชื่อใจเซิร์ฟเวอร์นี้ก่อนตั้งค่า", + "electrum_unable_to_connect": "ไม่สามารถเชื่อมต่อกับ {server} ได้", + "electrum_history": "ประวัติ", + "electrum_reset_to_default": "การดำเนินการนี้จะให้ BlueWallet สุ่มเลือกเซิร์ฟเวอร์จากรายการเซิร์ฟเวอร์", + "electrum_reset_to_default_and_clear_history": "รีเซ็ตเป็นค่าเริ่มต้นและล้างประวัติ", + "encrypt_decrypt_q": "ท่านแน่ใจหรือไม่ว่าต้องการถอดรหัสที่เก็บข้อมูลของท่าน? การดำเนินการนี้จะทำให้กระเป๋าสตางค์ของท่านสามารถเข้าถึงได้โดยไม่ต้องใช้รหัสผ่าน", + "encrypt_enc_and_pass": "ป้องกันด้วยรหัสผ่าน", + "encrypt_storage_explanation_headline": "เปิดใช้งานการเข้ารหัสที่เก็บข้อมูล", + "encrypt_storage_explanation_description_line1": "การเปิดใช้งานการเข้ารหัสที่เก็บข้อมูลเพิ่มชั้นการป้องกันพิเศษให้กับแอปของท่านโดยทำให้ข้อมูลที่จัดเก็บบนอุปกรณ์ปลอดภัยขึ้น ทำให้ยากต่อการเข้าถึงข้อมูลของท่านโดยไม่ได้รับอนุญาต", + "encrypt_storage_explanation_description_line2": "อย่างไรก็ตาม สิ่งสำคัญที่ต้องทราบคือ การเข้ารหัสนี้ป้องกันเฉพาะการเข้าถึงกระเป๋าสตางค์ที่จัดเก็บใน keychain ของอุปกรณ์เท่านั้น ไม่ได้ใส่รหัสผ่านหรือการป้องกันเพิ่มเติมให้กับกระเป๋าสตางค์เอง", + "i_understand": "ฉันเข้าใจ", + "block_explorer": "ตัวสำรวจบล็อก", + "block_explorer_preferred": "ใช้ตัวสำรวจบล็อกที่ต้องการ", + "block_explorer_error_saving_custom": "เกิดข้อผิดพลาดในการบันทึกตัวสำรวจบล็อกที่ต้องการ", + "set_as_preferred": "ตั้งเป็นที่ต้องการ", + "set_as_preferred_electrum": "การตั้งค่า {host}:{port} เป็นเซิร์ฟเวอร์ที่ต้องการจะปิดการเชื่อมต่อกับเซิร์ฟเวอร์ที่แนะนำแบบสุ่ม", + "encrypted_feature_disabled": "ฟีเจอร์นี้ไม่สามารถใช้งานได้เมื่อเปิดใช้งานการเข้ารหัสที่เก็บข้อมูล", + "encrypt_use_expl": "{type} จะถูกใช้ในการยืนยันตัวตนของท่านก่อนทำธุรกรรม ปลดล็อก ส่งออก หรือลบกระเป๋าสตางค์", + "biometrics_fail": "หาก {type} ไม่ได้เปิดใช้งานหรือไม่สามารถปลดล็อกได้ ท่านสามารถใช้รหัสผ่านอุปกรณ์เป็นทางเลือกได้", + "general_continuity_e": "เมื่อเปิดใช้งาน ท่านจะสามารถดูกระเป๋าสตางค์และธุรกรรมที่เลือกได้ โดยใช้อุปกรณ์ Apple iCloud อื่นๆ ที่เชื่อมต่ออยู่", + "groundcontrol_explanation": "GroundControl คือเซิร์ฟเวอร์การแจ้งเตือนแบบ push สำหรับกระเป๋าสตางค์ Bitcoin ที่ฟรีและโอเพนซอร์ส ท่านสามารถติดตั้งเซิร์ฟเวอร์ GroundControl ของตนเองและใส่ URL ที่นี่เพื่อไม่ต้องพึ่งพาโครงสร้างพื้นฐานของ BlueWallet เว้นว่างไว้เพื่อใช้เซิร์ฟเวอร์เริ่มต้นของ GroundControl", + "last_updated": "อัปเดตล่าสุด", + "language_isRTL": "การรีสตาร์ท BlueWallet จำเป็นสำหรับให้ทิศทางของภาษามีผล", + "license": "ใบอนุญาต", + "lightning_error_lndhub_uri": "LNDhub URI ไม่ถูกต้อง", + "lightning_error_lndhub_uri_tor": "LNDhub URI ไม่ถูกต้อง กรุณาตรวจสอบให้แน่ใจว่าแอป Orbot เชื่อมต่ออยู่และลองอีกครั้ง", + "lightning_settings_explain": "ในการเชื่อมต่อกับโหนด LND ของท่านเอง กรุณาติดตั้ง LNDhub และใส่ URL ที่นี่ในการตั้งค่า โปรดทราบว่าเฉพาะกระเป๋าสตางค์ที่สร้างหลังจากบันทึกการเปลี่ยนแปลงเท่านั้นที่จะเชื่อมต่อกับ LNDhub ที่ระบุ", + "electrum_suggested_description": "เมื่อไม่ได้ตั้งค่าเซิร์ฟเวอร์ที่ต้องการ เซิร์ฟเวอร์ที่แนะนำจะถูกเลือกใช้แบบสุ่ม", + "not_a_valid_uri": "URI ไม่ถูกต้อง", + "password_explain": "ใส่รหัสผ่านที่ท่านจะใช้ในการปลดล็อกที่เก็บข้อมูลของท่าน", + "privacy_quickactions": "ทางลัดของกระเป๋าสตางค์", + "privacy_quickactions_explanation": "แตะค้างที่ไอคอนแอป BlueWallet เพื่อดูยอดคงเหลือของกระเป๋าสตางค์อย่างรวดเร็ว", + "privacy_clipboard_explanation": "ให้ทางลัดหากพบแอดเดรสหรือใบแจ้งหนี้ในคลิปบอร์ดของท่าน", + "privacy_do_not_track": "ปิดการวิเคราะห์", + "privacy_do_not_track_explanation": "ข้อมูลประสิทธิภาพและความน่าเชื่อถือจะไม่ถูกส่งเพื่อการวิเคราะห์", + "rate": "อัตรา", + "push_notifications_explanation": "เมื่อเปิดใช้งานการแจ้งเตือน โทเค็นอุปกรณ์ของท่านจะถูกส่งไปยังเซิร์ฟเวอร์ พร้อมกับแอดเดรสกระเป๋าสตางค์และรหัสธุรกรรมสำหรับกระเป๋าสตางค์และธุรกรรมทั้งหมดที่ทำหลังจากเปิดใช้งานการแจ้งเตือน โทเค็นอุปกรณ์ใช้สำหรับส่งการแจ้งเตือน และข้อมูลกระเป๋าสตางค์ทำให้เราสามารถแจ้งเตือนท่านเกี่ยวกับ Bitcoin ที่เข้ามาหรือการยืนยันธุรกรรม\n\nเฉพาะข้อมูลหลังจากที่ท่านเปิดใช้งานการแจ้งเตือนเท่านั้นที่จะถูกส่ง—ไม่มีการเก็บข้อมูลก่อนหน้า\n\nการปิดการแจ้งเตือนจะลบข้อมูลทั้งหมดนี้ออกจากเซิร์ฟเวอร์ นอกจากนี้ การลบกระเป๋าสตางค์จากแอปจะลบข้อมูลที่เกี่ยวข้องออกจากเซิร์ฟเวอร์ด้วย", + "selfTest": "ทดสอบตนเอง", + "success_transaction_broadcasted": "ธุรกรรมของท่านได้รับการบรอดคาสต์เรียบร้อยแล้ว!", + "total_balance_explanation": "แสดงยอดคงเหลือทั้งหมดของกระเป๋าสตางค์ของท่านบนวิดเจ็ตหน้าจอหลัก", + "widgets": "วิดเจ็ต", + "tools": "เครื่องมือ" }, "notifications": { - "ask_me_later": "ถามฉันภายหลัง" + "would_you_like_to_receive_notifications": "ท่านต้องการรับการแจ้งเตือนเมื่อมีการชำระเงินเข้ามาหรือไม่?", + "notifications_subtitle": "การชำระเงินเข้ามาและการยืนยันธุรกรรม", + "no_and_dont_ask": "ไม่ และอย่าถามฉันอีก", + "permission_denied_message": "ท่านได้ปฏิเสธสิทธิ์ในการส่งการแจ้งเตือน หากท่านต้องการรับการแจ้งเตือน กรุณาเปิดใช้งานในการตั้งค่าอุปกรณ์ของท่าน" }, "transactions": { "cancel_no": "ธุรกรรมนี้ไม่สามารทเปลี่ยนแทนได้", @@ -217,29 +337,77 @@ "cpfp_title": "เพิ่มค่าธรรมเนียม (CPFP)", "details_balance_hide": "ซ่อนยอดคงเหลือ", "details_balance_show": "แสดงยอดคงเหลือ", - "details_block": "บล็อกไฮต์", "details_copy": "ก๊อปปี้", - "details_from": "อินพุท", + "details_id": "ID", "details_inputs": "อินพุท", "details_outputs": "เอ้าพุท", "details_received": "ได้รับแล้ว", - "details_show_in_block_explorer": "แสดงด้วย block explorer", "details_title": "ธุรกรรม", "details_to": "เอ้าพุท", "pending": "รอดำเนินการ", "list_title": "ธุรกรรม", + "list_title_received": "ได้รับแล้ว", + "transaction": "ธุรกรรม", "rbf_title": "เพิ่มค่าธรรมเนียม (RBF)", "status_bump": "เพิ่มค่าธรรมเนียม", "status_cancel": "ยกเลิกธุรกรรม", - "transactions_count": "จำนวนธุรกรรม" + "transactions_count": "จำนวนธุรกรรม", + "cancel_explain": "เราจะแทนที่ธุรกรรมนี้ด้วยธุรกรรมที่จ่ายให้ท่านเองและมีค่าธรรมเนียมสูงขึ้น ซึ่งจะเป็นการยกเลิกธุรกรรมปัจจุบันอย่างมีประสิทธิภาพ วิธีนี้เรียกว่า RBF—Replace by Fee", + "transaction_loading_error": "เกิดปัญหาในการโหลดธุรกรรม กรุณาลองอีกครั้งในภายหลัง", + "transaction_not_available": "ไม่พบธุรกรรม", + "expand_note": "ขยายโน้ต", + "cpfp_exp": "เราจะสร้างธุรกรรมอีกอันที่ใช้จ่ายธุรกรรมที่ยังไม่ยืนยันของท่าน ค่าธรรมเนียมรวมจะสูงกว่าค่าธรรมเนียมของธุรกรรมเดิม จึงควรถูกขุดเร็วขึ้น วิธีนี้เรียกว่า CPFP—Child Pays for Parent", + "details_copy_block_explorer_link": "คัดลอกลิงก์ตัวสำรวจบล็อก", + "details_copy_note": "คัดลอกโน้ต", + "details_copy_txid": "คัดลอกรหัสธุรกรรม", + "date": "วันที่", + "details_view_in_browser": "ดูในเบราว์เซอร์", + "incoming_transaction": "ธุรกรรมขาเข้า", + "outgoing_transaction": "ธุรกรรมขาออก", + "expired_transaction": "ธุรกรรมหมดอายุ", + "pending_transaction": "ธุรกรรมที่รอดำเนินการ", + "offchain": "ออฟเชน", + "onchain": "ออนเชน", + "enable_offline_signing": "กระเป๋าสตางค์นี้ไม่ได้ใช้งานร่วมกับการลงนามแบบออฟไลน์ ท่านต้องการเปิดใช้งานตอนนี้หรือไม่?", + "list_conf": "ยืนยัน: {number}", + "pending_with_amount": "รอดำเนินการ {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: ภายใน ~10 นาที", + "eta_3h": "ETA: ภายใน ~3 ชั่วโมง", + "eta_1d": "ETA: ภายใน ~1 วัน", + "list_title_sent": "ส่งแล้ว", + "open_url_error": "ไม่สามารถเปิดลิงก์ด้วยเบราว์เซอร์เริ่มต้นได้ กรุณาเปลี่ยนเบราว์เซอร์เริ่มต้นและลองอีกครั้ง", + "rbf_explain": "เราจะแทนที่ธุรกรรมนี้ด้วยธุรกรรมที่มีค่าธรรมเนียมสูงขึ้นเพื่อให้ถูกขุดเร็วขึ้น วิธีนี้เรียกว่า RBF—Replace by Fee", + "txid": "รหัสธุรกรรม", + "updating": "กำลังอัปเดต...", + "watchOnlyWarningTitle": "คำเตือนด้านความปลอดภัย", + "watchOnlyWarningDescription": "ระวังนักหลอกลวงที่มักใช้กระเป๋าสตางค์ “ดูอย่างเดียว” เพื่อหลอกลวงผู้ใช้ กระเป๋าสตางค์เหล่านี้ไม่อนุญาตให้ท่านควบคุมหรือส่งเงินได้ แต่ให้ดูยอดคงเหลือได้เท่านั้น", + "custom_fee_warning_title": "คำเตือน", + "custom_fee_warning_description": "ค่าธรรมเนียมต่ำกว่า 1 sat/vB ถือว่าถูกต้อง แต่อาจไม่ได้รับการส่งต่อเนื่องจากนโยบายของโหนด", + "details_eta_analyzing": "กำลังวิเคราะห์...", + "details_sent": "ส่งแล้ว", + "details_section": "รายละเอียด", + "details_explorer": "ตัวสำรวจ", + "details_network_fee": "ค่าธรรมเนียมเครือข่าย", + "details_to_address": "ถึง", + "details_note": "โน้ต", + "details_add_note": "เพิ่ม", + "details_advanced": "ขั้นสูง", + "details_fee_rate": "อัตราค่าธรรมเนียม", + "details_size": "ขนาด", + "details_virtual_size": "ขนาดเสมือน", + "details_tx_hex": "Tx hex", + "details_inputs_count": "อินพุต ({count})", + "details_outputs_count": "เอาต์พุต ({count})" }, "wallets": { "add_bitcoin": "บิตคอยน์", "add_bitcoin_explain": "กระเป๋าเงินบิตคอยน์ที่เรียบง่ายและทรงพลัง", "add_create": "สร้าง", + "total_balance": "ยอดรวม", + "add_entropy": "เอนโทรปี", "add_import_wallet": "นำเข้ากระเป๋าสตางค์", "add_lightning": "ไลท์นิง", - "add_lndhub": "เชื่อมต่ปไปที่ LNDHub ของท่าน", "add_lndhub_placeholder": "แอดเดรสของโหนดของท่าน", "add_title": "เพิ่มกระเป๋าสตางค์", "add_wallet_name": "ชื่อกระเป๋าสตางค์", @@ -248,19 +416,15 @@ "details_advanced": "ขั้นสูง", "details_are_you_sure": "คุณแน่ใจหรือไม่?", "details_connected_to": "เชื่อมไปที่", - "details_del_wb_err": "จำนวนเงินไม่ตรงกับยอดเงินในกระเป๋าสตางค์", "details_delete": "ลบ", "details_delete_wallet": "ลบกระเป๋าสตางค์", - "details_display": "แสดงในรายการกระเป๋าสตางค์", "details_export_backup": "ส่งออก / สำรอง", "details_master_fingerprint": "ลายนิ้วมือต้นแบบ", - "details_no_cancel": "ไม่ใช่, ยกเลิก", - "details_save": "เก็บ", "details_show_xpub": "แสดง XPUB ของกระเป๋าสตางค์", "details_title": "กระเป๋าสตางค์", + "wallets": "กระเป๋าสตางค์", "details_type": "ชนิด", "details_use_with_hardware_wallet": "ใช้ด้วยกระเป๋าฮาร์ดแวร์", - "details_wallet_updated": "อัพเดทกระเป๋าสตางค์แล้ว", "details_yes_delete": "ใช่, ลบเลย", "enter_bip38_password": "ใส่รหัสผ่านเพื่อถอดรหัส", "export_title": "ส่งออกกระเป๋าสตางค์", @@ -273,25 +437,98 @@ "list_create_a_button": "เพิ่มตอนนี้", "list_create_a_wallet": "สร้างกระเป๋าสตางค์", "list_empty_txs1": "ธุรกรรมจะปรากฏที่นี่,", - "list_empty_txs1_lightning": "ควรใช้ไลท์นิงเน็ตเวิร์คสำหรับธุรกรรมประจำวันเท่านั้น ธุรกรรมทันใจและมีค่าธรรมเนียมน้อยมาก", + "list_empty_txs1_lightning": "ควรใช้กระเป๋าสตางค์ไลท์นิงสำหรับธุรกรรมประจำวันของคุณ ค่าธรรมเนียมถูกอย่างไม่น่าเชื่อและความเร็วรวดเร็วมาก", "list_empty_txs2_lightning": "\nแตะที่ \"จัดการเงิน\" เพื่อเริ่มใช้งาน และเติมเงิน", "list_latest_transaction": "ธุรกรรมล่าสุด", "list_long_choose": "เลือกรูปภาพ", - "list_long_clipboard": "คัดลอกจากคลิปบอร์ด", + "paste_from_clipboard": "วาง", + "import_file": "นำเข้าไฟล์", "list_long_scan": "สแกนคิวอาร์โค้ด", "list_title": "กระเป๋าสตางค์", "list_tryagain": "พยายามอีกครั้ง", - "reorder_title": "เปลี่ยนลำดับกระเป๋าสตางค์", "select_no_bitcoin": "ขณะนี้ไม่มีกระเป๋าสตางค์บิตคอยน์", "select_no_bitcoin_exp": "ก่อนที่จะเติมเงินเข้ากระเป๋าสตางค์ไลท์นิง ท่านต้องมีกระเป๋าสตางค์บิตคอยน์", "select_wallet": "เลือกกระเป๋าสตางค์", - "xpub_copiedToClipboard": "ก๊อปปี้ไปที่คลิปบอร์ดแล้ว.", "pull_to_refresh": "ดึงเพื่อรีเฟรช", "add_ln_wallet_first": "คุณต้องเพิ่มกระเป๋าเงินไลท์นิงก่อน", - "xpub_title": "XPUB ของกระเป๋าสตางค์" + "xpub_title": "XPUB ของกระเป๋าสตางค์", + "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_lightning_explain": "สำหรับใช้จ่ายด้วยธุรกรรมที่รวดเร็ว", + "add_lndhub": "เชื่อมต่อกับ LNDhub ของท่าน", + "add_lndhub_error": "แอดเดรสของโหนดที่ให้มาไม่ใช่โหนด LNDhub ที่ถูกต้อง", + "add_placeholder": "กระเป๋าสตางค์แรกของฉัน", + "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": "ล้างคลิปบอร์ดเมื่อนำเข้า", + "details_del_wb_err": "จำนวนยอดคงเหลือที่ให้มาไม่ตรงกับยอดคงเหลือของกระเป๋าสตางค์นี้ กรุณาลองอีกครั้ง", + "details_del_wb_q": "กระเป๋าสตางค์นี้มียอดคงเหลือ ก่อนดำเนินการต่อ โปรดทราบว่าท่านจะไม่สามารถกู้คืนเงินได้หากไม่มีวลีนีโมนิกของกระเป๋าสตางค์นี้ เพื่อหลีกเลี่ยงการลบโดยไม่ตั้งใจ กรุณาใส่ยอดคงเหลือของกระเป๋าสตางค์ของท่านเป็นจำนวน {balance} ซาโตชิ", + "details_derivation_path": "เส้นทางอนุพันธ์", + "details_display": "แสดงในหน้าจอหลัก", + "details_export_history": "ส่งออกประวัติเป็น CSV", + "details_multisig_type": "หลายลายเซ็น", + "details_show_addresses": "แสดงแอดเดรส", + "swipe_balance_hide": "ซ่อน", + "swipe_balance_show": "แสดง", + "drag_to_reorder": "ลากเพื่อจัดเรียงใหม่", + "clear_search": "ล้างการค้นหา", + "import_passphrase": "วลีรหัสผ่าน", + "import_passphrase_title": "วลีรหัสผ่าน", + "import_passphrase_message": "ใส่วลีรหัสผ่านหากท่านได้ใช้", + "import_explanation": "กรุณาใส่คำซีด กุญแจสาธารณะ WIF หรือสิ่งที่ท่านมี BlueWallet จะพยายามคาดเดารูปแบบที่ถูกต้องและนำเข้ากระเป๋าสตางค์ของท่าน", + "import_success_watchonly": "นำเข้ากระเป๋าสตางค์ของท่านสำเร็จแล้ว คำเตือน: นี่คือกระเป๋าสตางค์แบบดูอย่างเดียว ท่านไม่สามารถใช้จ่ายจากกระเป๋าสตางค์นี้ได้", + "import_search_accounts": "ค้นหาบัญชี", + "learn_more": "เรียนรู้เพิ่มเติม", + "import_discovery_title": "การค้นพบ", + "import_discovery_subtitle": "เลือกกระเป๋าสตางค์ที่ค้นพบ", + "import_discovery_derivation": "ใช้เส้นทางอนุพันธ์แบบกำหนดเอง", + "import_discovery_no_wallets": "ไม่พบกระเป๋าสตางค์", + "import_discovery_offline": "ขณะนี้ BlueWallet อยู่ในโหมดออฟไลน์ ในโหมดนี้ไม่สามารถยืนยันการมีอยู่ของกระเป๋าสตางค์ได้ ท่านจึงต้องเลือกกระเป๋าสตางค์ที่ถูกต้องด้วยตนเอง", + "import_derivation_found": "พบ", + "import_derivation_found_not": "ไม่พบ", + "import_derivation_loading": "กำลังโหลด...", + "import_derivation_subtitle": "ใส่เส้นทางอนุพันธ์แบบกำหนดเอง และเราจะพยายามค้นหากระเป๋าสตางค์ของท่าน", + "import_derivation_title": "เส้นทางอนุพันธ์", + "import_derivation_unknown": "ไม่ทราบ", + "import_wrong_path": "เส้นทางอนุพันธ์ผิด", + "list_create_a_wallet_text": "ฟรี และท่านสามารถสร้าง \nได้มากเท่าที่ต้องการ", + "list_empty_txs2": "เริ่มต้นด้วยกระเป๋าสตางค์ของท่าน", + "no_ln_wallet_error": "ก่อนชำระใบแจ้งหนี้ Lightning ท่านต้องเพิ่มกระเป๋าสตางค์ Lightning ก่อน", + "looks_like_bip38": "ดูเหมือนว่านี่จะเป็นกุญแจส่วนตัวที่ได้รับการป้องกันด้วยรหัสผ่าน (BIP38)", + "manage_title": "จัดการกระเป๋าสตางค์", + "no_results_found": "ไม่พบผลลัพธ์", + "please_continue_scanning": "กรุณาสแกนต่อ", + "warning_do_not_disclose": "อย่าแชร์ข้อมูลด้านล่าง", + "scan_import": "สแกนคิวอาร์โค้ดนี้เพื่อนำเข้ากระเป๋าสตางค์ของท่านในแอปพลิเคชันอื่น", + "write_down_header": "สร้างสำรองข้อมูลด้วยตนเอง", + "write_down": "จดและจัดเก็บคำเหล่านี้อย่างปลอดภัย ใช้กู้คืนกระเป๋าสตางค์ของท่านในภายหลัง", + "wallet_type_this": "ชนิดกระเป๋าสตางค์นี้คือ {type}", + "share_number": "แชร์ {number}", + "copy_ln_url": "คัดลอกและจัดเก็บ URL นี้อย่างปลอดภัยเพื่อกู้คืนกระเป๋าสตางค์ของท่านในภายหลัง", + "copy_ln_public": "คัดลอกและจัดเก็บข้อมูลนี้อย่างปลอดภัยเพื่อกู้คืนกระเป๋าสตางค์ของท่านในภายหลัง", + "identity_pubkey": "กุญแจสาธารณะระบุตัวตน", + "manage_wallets_search_placeholder": "ค้นหากระเป๋าสตางค์ แอดเดรส ธุรกรรม และบันทึกช่วยจำ", + "more_info": "ข้อมูลเพิ่มเติม", + "details_delete_wallet_error_message": "เกิดปัญหาในการยืนยันว่ากระเป๋าสตางค์นี้ถูกลบออกจากการแจ้งเตือนหรือไม่—อาจเกิดจากปัญหาเครือข่ายหรือการเชื่อมต่อไม่ดี หากท่านดำเนินการต่อ ท่านอาจยังคงได้รับการแจ้งเตือนสำหรับธุรกรรมที่เกี่ยวข้องกับกระเป๋าสตางค์นี้แม้หลังจากที่ลบไปแล้ว", + "details_delete_anyway": "ลบต่อไป" + }, + "total_balance_view": { + "title": "ยอดรวม", + "display_in_bitcoin": "แสดงเป็น Bitcoin", + "hide": "ซ่อน", + "display_in_sats": "แสดงเป็นแซท", + "display_in_fiat": "แสดงเป็น {currency}", + "explanation": "ดูยอดคงเหลือทั้งหมดของกระเป๋าสตางค์ของท่านในหน้าจอภาพรวม" }, "multisig": { - "multisig_vault": "ห้องนิรภัย", + "multisig_vault": "ห้องนิรภัยหลายลายเซ็น", "default_label": "ห้องนิรภัยหลายลายเซ็น", "multisig_vault_explain": "ความปลอดภัยที่ดีที่สุดสำหรับเงินจำนวนมาก", "provide_signature": "ใส่ลายเซ็น", @@ -300,7 +537,6 @@ "fee_btc": "{number} บิตคอยน์", "confirm": "ยืนยัน", "header": "ส่ง", - "share": "แชร์", "view": "ดู", "manage_keys": "จัดการกุญแจ", "signatures_required_to_spend": "ต้องมีลายเซ็น {number}", @@ -326,13 +562,62 @@ "ms_help_title2": "แก้ไขกุญแจ", "ms_help_title3": "สำรองห้องนิรภัย", "ms_help_title4": "นำเข้าห้องนิรภัย", - "ms_help_title5": "โหมดขั้นสูง" + "ms_help_title5": "โหมดขั้นสูง", + "provide_signature_details": "ใช้อุปกรณ์และกระเป๋าสตางค์ที่กุญแจอยู่เพื่อลงนามในธุรกรรมนี้", + "provide_signature_details_bluewallet": "ใน BlueWallet ไปที่เมนูหน้าจอส่งและเลือก ", + "provide_signature_next_steps": "สแกนหรือนำเข้าธุรกรรมที่ลงนามแล้ว", + "provide_signature_next_steps_details": "เมื่อกระเป๋าสตางค์ของท่านลงนามในธุรกรรมสำเร็จแล้ว สแกนคิวอาร์โค้ดที่ให้มาหรือนำเข้าไฟล์ที่แนบมา จากนั้นตรวจสอบรายละเอียดธุรกรรมทั้งหมดก่อนบรอดคาสต์", + "vault_key": "กุญแจห้องนิรภัย {number}", + "share": "แชร์...", + "shared_key_detected": "ผู้ร่วมลงนามที่แชร์", + "shared_key_detected_question": "มีผู้ร่วมลงนามที่แชร์มาให้ท่าน ท่านต้องการนำเข้าหรือไม่?", + "how_many_signatures_can_bluewallet_make": "BlueWallet สามารถสร้างลายเซ็นได้กี่อัน", + "export_coordination_setup": "ส่งออกการตั้งค่าการประสานงาน", + "lets_start": "มาเริ่มกันเถอะ", + "native_segwit_title": "แนวปฏิบัติที่ดีที่สุด", + "wrapped_segwit_title": "ความเข้ากันได้ที่ดีที่สุด", + "legacy_title": "Legacy", + "what_is_vault_description_to_spend": "เพื่อใช้จ่าย และอีกหนึ่งอันที่ท่าน \nสามารถใช้เป็นสำรองข้อมูล", + "invalid_mnemonics": "วลีนีโมนิกนี้ดูเหมือนจะไม่ถูกต้อง", + "invalid_cosigner": "ข้อมูลผู้ร่วมลงนามไม่ถูกต้อง", + "not_a_multisignature_xpub": "นี่ไม่ใช่ XPUB จากกระเป๋าสตางค์หลายลายเซ็น!", + "invalid_cosigner_format": "ผู้ร่วมลงนามไม่ถูกต้อง: นี่ไม่ใช่ผู้ร่วมลงนามสำหรับรูปแบบ {format}", + "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_title": "วิธีการทำงานของห้องนิรภัยหลายลายเซ็น: เคล็ดลับและเทคนิค", + "ms_help_text": "กระเป๋าสตางค์ที่มีหลายกุญแจ สำหรับความปลอดภัยที่เพิ่มขึ้นหรือการดูแลร่วมกัน", + "ms_help_title1": "แนะนำให้ใช้หลายอุปกรณ์", + "ms_help_1": "ห้องนิรภัยจะทำงานร่วมกับแอป BlueWallet อื่นๆ และกระเป๋าสตางค์ที่รองรับ PSBT เช่น Electrum, Specter, Coldcard, Cobo Vault เป็นต้น", + "ms_help_2": "ท่านสามารถสร้างกุญแจห้องนิรภัยทั้งหมดบนอุปกรณ์นี้และลบหรือแก้ไขในภายหลัง การมีกุญแจทั้งหมดบนอุปกรณ์เดียวกันมีความปลอดภัยเทียบเท่ากับกระเป๋าสตางค์ Bitcoin ทั่วไป", + "ms_help_3": "ในตัวเลือกของกระเป๋าสตางค์ ท่านจะพบสำรองข้อมูลห้องนิรภัยและสำรองข้อมูลแบบดูอย่างเดียว สำรองข้อมูลนี้เปรียบเสมือนแผนที่ของกระเป๋าสตางค์ของท่าน จำเป็นสำหรับการกู้คืนกระเป๋าสตางค์ในกรณีที่ท่านสูญเสียซีดอันใดอันหนึ่ง", + "ms_help_4": "หากต้องการนำเข้ากระเป๋าสตางค์หลายลายเซ็น ใช้ไฟล์สำรองข้อมูลและฟีเจอร์นำเข้า หากท่านมีเพียงซีดและ XPUB ท่านสามารถใช้ปุ่มนำเข้าแยกเมื่อสร้างกุญแจห้องนิรภัย", + "ms_help_5": "โดยค่าเริ่มต้น BlueWallet จะสร้างห้องนิรภัยแบบ 2-จาก-3 หากต้องการสร้างองค์ประชุมที่ต่างออกไปหรือเปลี่ยนชนิดแอดเดรส เปิดโหมดขั้นสูงในการตั้งค่า" }, "is_it_my_address": { "title": "ใช่แอดเดรสของฉันหรือไม่?", "owns": "{label} เป็นเจ้าของ {address}", "enter_address": "ใส่แอดเดรส", - "check_address": "ตรวจสอบแอดเดรส" + "check_address": "ตรวจสอบแอดเดรส", + "no_wallet_owns_address": "ไม่มีกระเป๋าสตางค์ใดที่เป็นเจ้าของแอดเดรสที่ให้มา", + "view_qrcode": "ดูคิวอาร์โค้ด" + }, + "autofill_word": { + "title": "คำสุดท้ายของซีด", + "enter": "ใส่วลีนีโมนิกที่ไม่สมบูรณ์ของท่าน", + "generate_word": "สร้างคำสุดท้าย", + "error": "ข้อมูลที่ใส่ไม่ใช่วลีนีโมนิกที่ไม่สมบูรณ์ 11 หรือ 23 คำ กรุณาลองอีกครั้ง" }, "cc": { "change": "เงินทอน", @@ -342,16 +627,78 @@ "freezeLabel_un": "ยกเลิกการระงับ", "header": "ควบคุมเหรียญ", "use_coin": "ใช้เหรียญ", - "use_coins": "ใช้เหรียญ" + "use_coins": "ใช้เหรียญ", + "sort_label": "ป้าย", + "sort_status": "สถานะ", + "selected_summ": "เลือก {value}", + "empty": "กระเป๋าสตางค์นี้ไม่มีเหรียญในขณะนี้", + "tip": "ฟีเจอร์นี้ทำให้ท่านสามารถดู ติดป้าย ระงับ หรือเลือกเหรียญเพื่อการจัดการกระเป๋าสตางค์ที่ดียิ่งขึ้น ท่านสามารถเลือกเหรียญหลายอันโดยแตะที่วงกลมสี", + "sort_asc": "จากน้อยไปหามาก", + "sort_desc": "จากมากไปหาน้อย", + "sort_height": "ความสูง", + "sort_value": "มูลค่า", + "sort_by": "เรียงตาม" }, "units": { "BTC": "บิตคอยน์", - "sats": "แซท" + "sats": "แซท", + "MAX": "สูงสุด", + "sat_vbyte": "sat/vByte" }, "addresses": { "sign_placeholder_address": "แอดเดรส", "type_change": "เงินทอน", "type_receive": "รับ", - "transactions": "ธุรกรรม" + "transactions": "ธุรกรรม", + "copy_private_key": "คัดลอกกุญแจส่วนตัว", + "sensitive_private_key": "คำเตือน: กุญแจส่วนตัวมีความละเอียดอ่อนอย่างมาก ดำเนินการต่อหรือไม่?", + "sign_title": "ลงนาม/ตรวจสอบข้อความ", + "sign_help": "ที่นี่ท่านสามารถสร้างหรือตรวจสอบลายเซ็นทางการเข้ารหัสตามแอดเดรส Bitcoin ได้", + "sign_sign": "ลงนาม", + "sign_verify": "ตรวจสอบ", + "sign_signature_correct": "การตรวจสอบสำเร็จ!", + "sign_signature_incorrect": "การตรวจสอบล้มเหลว!", + "sign_placeholder_message": "ข้อความ", + "sign_placeholder_signature": "ลายเซ็น", + "addresses_title": "แอดเดรส", + "type_used": "ใช้แล้ว" + }, + "lnurl_auth": { + "register_question_part_1": "ท่านต้องการลงทะเบียนบัญชีที่", + "register_question_part_2": "โดยใช้กระเป๋าสตางค์ Lightning ของท่านหรือไม่?", + "register_answer": "ท่านได้ลงทะเบียนบัญชีที่ {hostname} สำเร็จแล้ว!", + "login_question_part_1": "ท่านต้องการเข้าสู่ระบบที่", + "login_question_part_2": "โดยใช้กระเป๋าสตางค์ Lightning ของท่านหรือไม่?", + "login_answer": "ท่านได้เข้าสู่ระบบที่ {hostname} สำเร็จแล้ว!", + "link_question_part_1": "ท่านต้องการเชื่อมโยงบัญชีของท่านที่", + "link_question_part_2": "กับกระเป๋าสตางค์ Lightning ของท่านหรือไม่?", + "link_answer": "กระเป๋าสตางค์ Lightning ของท่านได้เชื่อมโยงกับบัญชีที่ {hostname} สำเร็จแล้ว!", + "auth_question_part_1": "ท่านต้องการยืนยันตัวตนที่", + "auth_question_part_2": "โดยใช้กระเป๋าสตางค์ Lightning ของท่านหรือไม่?", + "auth_answer": "ท่านได้ยืนยันตัวตนที่ {hostname} สำเร็จแล้ว!", + "could_not_auth": "เราไม่สามารถยืนยันตัวตนของท่านที่ {hostname} ได้", + "authenticate": "ยืนยันตัวตน" + }, + "bip47": { + "payment_code": "รหัสการชำระเงิน", + "contacts": "ผู้ติดต่อ", + "bip47_explain": "รหัสที่ใช้ซ้ำและแชร์ได้", + "bip47_explain_subtitle": "BIP47", + "purpose": "รหัสที่ใช้ซ้ำและแชร์ได้ (BIP47)", + "pay_this_contact": "ชำระเงินให้ผู้ติดต่อนี้", + "rename_contact": "เปลี่ยนชื่อผู้ติดต่อ", + "copy_payment_code": "คัดลอกรหัสการชำระเงิน", + "hide_contact": "ซ่อนผู้ติดต่อ", + "rename": "เปลี่ยนชื่อ", + "provide_name": "ระบุชื่อใหม่สำหรับผู้ติดต่อนี้", + "add_contact": "เพิ่มผู้ติดต่อ", + "provide_payment_code": "ระบุรหัสการชำระเงิน", + "invalid_pc": "รหัสการชำระเงินไม่ถูกต้อง", + "notification_tx_unconfirmed": "ธุรกรรมแจ้งเตือนยังไม่ได้รับการยืนยัน กรุณารอ", + "failed_create_notif_tx": "ไม่สามารถสร้างธุรกรรมแบบออนเชนได้", + "onchain_tx_needed": "ต้องการธุรกรรมแบบออนเชน", + "notif_tx_sent": "ส่งธุรกรรมแจ้งเตือนแล้ว กรุณารอการยืนยัน", + "notif_tx": "ธุรกรรมแจ้งเตือน", + "not_found": "ไม่พบรหัสการชำระเงิน" } } diff --git a/loc/tr_tr.json b/loc/tr_tr.json index adfb73dfbc1..482bd355242 100644 --- a/loc/tr_tr.json +++ b/loc/tr_tr.json @@ -1,41 +1,51 @@ { "_": { - "bad_password": "Hatalı şifre, lütfen tekrar deneyiniz.", - "cancel": "Vazgeç", + "bad_password": "Yanlış şifre. Lütfen tekrar deneyin.", + "cancel": "İptal", "continue": "Devam et", "clipboard": "Pano", - "enter_password": "Şifre gir", + "discard_changes": "Değişiklikleri silmek istiyor musunuz?", + "discard_changes_explain": "Kaydedilmemiş değişiklikleriniz var. Bunları silip ekrandan çıkmak istediğinizden emin misiniz?", + "enter_password": "Şifreyi girin", "never": "Asla", - "disabled": "Devre dışı", "of": "{number} / {total}", "ok": "Tamam", - "storage_is_encrypted": "Depolama alanınız şifrelidir, kilidi kaldırmak için şifrenizi girin.", + "enter_url": "URL girin", + "storage_is_encrypted": "Depolama alanınız şifrelenmiş. Şifrelemeyi çözmek için şifre gereklidir.", "yes": "Evet", "no": "Hayır", - "save": "Kaydet", "seed": "Seed", "success": "Başarılı", "wallet_key": "Cüzdan anahtarı", - "invalid_animated_qr_code_fragment": "Geçersiz QRCode fragmanı. Lütfen tekrar deneyiniz.", - "file_saved": "Dosya {filepath} kaydedildi {destination}", - "downloads_folder": "İndirilenler Dosyası" - }, - "alert": { - "default": "Uyarı" + "close": "Kapat", + "change_input_currency": "Giriş para birimini değiştir", + "copied": "Kopyalandı!", + "save": "Kaydet...", + "refresh": "Yenile", + "pick_image": "Kütüphaneden seç", + "pick_file": "Dosya seç", + "enter_amount": "Miktarı girin", + "qr_custom_input_button": "Özel giriş için 10 kez dokunun", + "unlock": "Kilidi aç", + "ssl_port": "SSL Portu", + "port": "Port", + "suggested": "Önerilen" }, "azteco": { - "codeIs": "Bilet kodunuz ", - "errorBeforeRefeem": "Hesabiniza yuklemeden once bir Bitcoin cuzdani olusturmalisiniz", - "errorSomething": "Birseyler yanlis gitti. Bu kod hala gecerli mi?", + "codeIs": "Kupon kodunuz ", + "errorBeforeRefeem": "Bozdurmadan önce, ilk olarak bir Bitcoin cüzdanı eklemelisiniz.", + "errorSomething": "Bir şeyler yanlış gitti. Bu kupon hâlâ geçerli mi?", "redeem": "Cüzdana yükle", "redeemButton": "Yükle", "success": "İşlem başarılı", - "title": "Azte.co kuponu bozdurun" + "title": "Azte.co kuponu bozdurun", + "successMessage": "Kupon başarıyla bozduruldu! Paranız kısa süre içinde Bitcoin cüzdanınıza ulaşacaktır." }, "entropy": { "save": "Kaydet", "title": "Entropi", - "undo": "Geri al" + "undo": "Geri al", + "amountOfEntropy": "{bits} / {limit} bit" }, "errors": { "broadcast": "Yayın başarısız oldu.", @@ -43,74 +53,68 @@ "network": "Ağ Hatası" }, "lnd": { - "active": "Aktif", - "inactive": "Inaktif", - "channels": "Kanallar", - "no_channels": "Kanal yok", - "claim_balance": "Talep balansı (balans)", - "close_channel": "Kanalı kapa", - "new_channel": "Yeni kanal", - "errorInvoiceExpired": "Fatura zaman aşımına uğradı", - "force_close_channel": "Kanalı kapamaya zorla", "expired": "Süresi doldu", - "node_alias": "Node mahlası", "expiresIn": "{time} dakika içerisinde sona erer", "payButton": "Öde", - "placeholder": "Fatura", - "open_channel": "Kanal aç", - "funding_amount_placeholder": "Fonlama miktarı, örnek 0.001", - "opening_channnel_for_from": "{fromWalletLabel} tarafından fonlanan kanal {forWalletLabel} için aılıyor", - "are_you_sure_open_channel": "Bu kanalı açmak istediğinizden emin misiniz?", "refill": "Yükle", - "reconnect_peer": "Eş'e tekrar bağlan", "refill_lnd_balance": "Lightning cüzdana bakiye yükle", "sameWalletAsInvoiceError": "Bir faturayı, oluştururken kullandığınız cüzdan ile ödeyemezsiniz.", "title": "Bakiyeleri Yönet", - "can_send": "Gönderebilir", - "can_receive": "Alabilir", - "view_logs": "Logları gör" + "errorInvoiceExpired": "Faturanın süresi doldu.", + "payment": "Ödeme", + "placeholder": "Fatura veya adres", + "potentialFee": "Olası ücret: {fee}", + "refill_create": "Devam edebilmek için, yüklemek üzere bir Bitcoin cüzdanı oluşturun.", + "refill_external": "Harici cüzdan ile yükle" }, "lndViewInvoice": { "additional_info": "Ek Bilgi", "for": "İçin: ", "lightning_invoice": "Lightning faturası", - "open_direct_channel": "Bu noda direkt kanal aç: ", "please_pay_between_and": "{min} ve {max} arasında bir ödeme yapın", "please_pay": "Lütfen ödeyin", - "preimage": "Preimage", - "sats": "sats.", - "wasnt_paid_and_expired": "Bu fatura ödenmeden zaman aşımına uğradı." + "wasnt_paid_and_expired": "Bu fatura ödenmeden zaman aşımına uğradı.", + "preimage": "Pre-image", + "sats": "sat.", + "date_time": "Tarih ve saat" }, "plausibledeniability": { "create_fake_storage": "Sahte şifreli depolama oluşturun", - "create_password": "Şifre oluştur", "create_password_explanation": "Sahte depolama şifreniz, ana depolama şifrenizle aynı olmamalıdır.", - "help": "Bazı koşullar altında, şifrenizi açıklamanız gerekebilir. Paralarınızı güvende tutmak için, BlueWallet başka bir şifre ile şifreli depolama alanı yaratabilir. Baskı altında, Bu şifreyi 3. bir tarafa söyleyebilirsiniz. Girilirse BlueWallet, yeni 'sahte' bir depolamanın kilidini açacaktır. Bu 3. şahıslara normal görünecektir, ancak paraların olduğu ana depolama alanınızı gizlice saklamaya devam edecektir.", + "help": "Bazı koşullar altında, şifrenizi açıklamanız gerekebilir. Paralarınızı güvende tutmak için, BlueWallet başka bir şifre ile şifreli depolama alanı yaratabilir. Baskı altında, bu şifreyi 3. bir tarafa söyleyebilirsiniz. Girilirse BlueWallet, yeni 'sahte' bir depolamanın kilidini açacaktır. Bu 3. şahıslara normal görünecektir, ancak paraların olduğu ana depolama alanınızı gizlice saklamaya devam edecektir.", "help2": "Yeni depolama alanı tamamen işlevsel olacak ve ufak bir miktar tutarsanız daha inanılır görünecektir.", "password_should_not_match": "Sahte depolama şifreniz, ana depolama şifrenizle aynı olmamalıdır", - "retype_password": "Şifrenizi yeniden yazın", - "success": "Başarılı", "title": "Makul Ret" }, "pleasebackup": { + "ok_lnd": "Tamam, kaydettim.", + "text_lnd": "Lütfen kurtarma kodunuzu kaydedin. Cüzdanınızı kaybetmeniz durumunda onu geri oluşturabilmenizi sağlar.", + "title": "Cüzdanınız oluşturuldu.", + "ask": "Cüzdanınızın kurtarma ifadesini kaydettiniz mi? Bu kurtarma ifadesi, bu cihazı kaybetmeniz durumunda paranıza erişebilmek için gereklidir. Kurtarma ifadesi olmadan paranız kalıcı olarak kaybolur.", "ask_no": "Hayır, kaydetmedim.", "ask_yes": "Evet, kaydettim.", - "ok": "Evet, deftere yazdım.", - "ok_lnd": "Evet, kaydettim.", - "text_lnd": "Lütfen kurtarma kodunuzu kaydedin. Cüzdanınızı kayıp etmeniz durumunda onu geri oluşturabilmenizi sağlar.", - "title": "Cüzdanınız oluşturuldu." + "ok": "Tamam, kaydettim.", + "text": "Lütfen bu mnemonik ifadeyi bir kağıda yazmak için bir an ayırın.\nBu sizin yedeğinizdir ve cüzdanı kurtarmak için kullanabilirsiniz." }, "receive": { "details_create": "Oluştur", "details_label": "Açıklama", "details_setAmount": "Miktar ile al", - "details_share": "Paylaş", - "header": "Al" + "header": "Al", + "details_share": "Paylaş...", + "address_not_found": "Alım adresi oluşturulamadı.", + "reset": "Sıfırla", + "maxSats": "Maksimum miktar {max} sat", + "maxSatsFull": "Maksimum miktar {max} sat veya {currency}", + "minSats": "Minimum miktar {min} sat", + "minSatsFull": "Minimum miktar {min} sat veya {currency}", + "qrcode_for_the_address": "Adres için QR kodu", + "bip47_explanation": "Ödeme kodları, cüzdan adreslerinizi açığa çıkarmaktan kaçınan evrensel bir adrestir. Tüm hizmetler bunları desteklemeyebilir." }, "send": { "broadcastButton": "Yayınla", "broadcastError": "Hata", - "broadcastNone": "Işlemin Hex kodunu girin", + "broadcastNone": "İşlemin Hex kodunu girin", "broadcastPending": "Beklemede", "broadcastSuccess": "İşlem başarılı", "confirm_header": "Onayla", @@ -131,7 +135,7 @@ "details_address_field_is_not_valid": "Adres geçerli değil", "details_adv_fee_bump": "Ücret arttırımına izin ver", "details_adv_full": "Bakiyenin tamamını kullan", - "details_adv_full_sure": "Cüzdanınızın tam bakiyesini bu işlem için kullanmak istediğinize emin misiniz ?", + "details_adv_full_sure": "Cüzdanınızın tam bakiyesini bu işlem için kullanmak istediğinizden emin misiniz?", "details_adv_import": "İşlemi dışarı aktar", "details_amount_field_is_not_valid": "Miktar alanı geçerli değil", "details_amount_field_is_less_than_minimum_amount_sat": "Belirlediğiniz tutar çok az. Lütfen 500 sat'tan daha büyük bir tutar giriniz.", @@ -164,53 +168,242 @@ "permission_camera_message": "Kamerayı kullanmak için izin vermelisiniz.", "psbt_sign": "Bir işlemi imzalayın", "open_settings": "Ayarları aç", - "permission_storage_later": "Daha sonra hatırlat", "psbt_clipboard": "Panoya kopyala", - "success_done": "Tamam" + "success_done": "Tamam", + "provided_address_is_invoice": "Bu adres bir Lightning faturasına ait gibi görünüyor. Lütfen bu fatura için ödeme yapmak üzere Lightning cüzdanınıza gidin.", + "create_satoshi_per_vbyte": "Satoshi / vByte", + "details_insert_contact": "Kişi ekle", + "details_add_recc_rem_all_alert_description": "Tüm alıcıları kaldırmak istediğinizden emin misiniz?", + "details_add_rec_rem_all": "Tüm Alıcıları Kaldır", + "details_recipients_title": "Alıcılar", + "details_recipient_title": "Alıcı #{number} / #{total}", + "please_complete_recipient_title": "Eksik Alıcı", + "please_complete_recipient_details": "Yeni bir alıcı eklemeden önce lütfen #{number} numaralı alıcının bilgilerini tamamlayın.", + "details_adv_full_sure_frozen": "Cüzdanınızın tam bakiyesini bu işlem için kullanmak istediğinize emin misiniz? Donmuş coinlerin dahil edilmediğini lütfen unutmayın.", + "details_adv_import_qr": "İşlemi İçe Aktar (QR)", + "details_frozen": "{amount} BTC donmuş.", + "details_no_signed_tx": "Seçilen dosya içe aktarılabilecek bir işlem içermiyor.", + "details_scan_hint": "Bir hedef taramak veya içe aktarmak için iki kez dokunun", + "details_scan_error": "Tarama hatası", + "details_total_exceeds_balance_frozen": "Gönderme miktarı mevcut bakiyeyi aşıyor. Donmuş coinlerin dahil edilmediğini lütfen unutmayın.", + "insert_custom_fee": "Ücret girin", + "fee_replace_minvb": "Ödemek istediğiniz toplam ücret oranı (satoshi/vByte) {min} sat/vByte'tan yüksek olmalıdır.", + "fee_satvbyte": "sat/vByte cinsinden", + "invalid_psbt": "Geçersiz PSBT sağlandı.", + "permission_storage_denied_message": "BlueWallet bu dosyayı kaydedemiyor. Lütfen cihaz ayarlarınızı açın ve Depolama İznini etkinleştirin.", + "permission_storage_title": "Depolama Erişim İzni", + "psbt_this_is_psbt": "Bu, Kısmen İmzalanmış Bir Bitcoin İşlemidir (PSBT). Lütfen donanım cüzdanınız ile imzalamayı tamamlayın.", + "psbt_tx_export": "Dosyaya aktar", + "no_tx_signing_in_progress": "Devam eden bir işlem imzalama yok.", + "outdated_rate": "Kur son güncelleme: {date}", + "psbt_tx_open": "İmzalanmış İşlemi Aç", + "psbt_tx_scan": "İmzalanmış İşlemi Tara", + "qr_error_no_qrcode": "Seçilen görselde geçerli bir QR kodu bulamadık. Görselin sadece bir QR kodu içerdiğinden ve metin veya düğmeler gibi ek içerik bulunmadığından emin olun.", + "reset_amount": "Miktarı Sıfırla", + "reset_amount_confirm": "Miktarı sıfırlamak ister misiniz?", + "txSaved": "İşlem dosyası ({filePath}) kaydedildi.", + "file_saved_at_path": "Dosya ({filePath}) kaydedildi.", + "cant_send_to_silentpayment_adress": "Bu cüzdan Silent Payments adreslerine gönderim yapamaz", + "cant_send_to_bip47": "Bu cüzdan BIP47 ödeme kodlarına gönderim yapamaz", + "cant_find_bip47_notification": "Önce bu Ödeme Kodunu kişilere ekleyin", + "problem_with_psbt": "PSBT ile ilgili sorun" }, "settings": { "about": "Hakkında", - "about_sm_discord": "Discord Sunucusu", "about_sm_telegram": "Telegram Kanalı", - "about_sm_twitter": "Bizi Twitter'da takip edin", - "advanced_options": "Gelişmiş Seçenekler", "biometrics": "Biyometrikler", "currency": "Para Birimi", - "electrum_clear_alert_cancel": "Vazgeç", - "electrum_clear": "Temizle", - "general_adv_mode": "Enable advanced mode", - "header": "ayarlar", + "header": "Ayarlar", "language": "Dil", "lightning_settings": "Lightning Ayarları", "password": "Şifre", - "password_explain": "Depolamanın şifresini çözmek için kullanacağınız şifreyi oluşturun", - "passwords_do_not_match": "Şifreler eşleşmedi", "plausible_deniability": "Makul ret...", "privacy": "Gizlilik", "privacy_system_settings": "Sistem Ayarları", "privacy_quickactions": "Cüzdan Kısayolları", - "push_notifications": "Bildirimler", - "retype_password": "Şifrenizi yeniden girin", "save": "Kaydet", "saved": "Kaydedildi", - "total_balance": "Bakiye" + "total_balance": "Bakiye", + "about_awesome": "Şu harika kütüphanelerle yapıldı:", + "about_backup": "Anahtarlarınızı her zaman yedekleyin!", + "about_free": "BlueWallet ücretsiz ve açık kaynaklı bir projedir. Bitcoin kullanıcıları tarafından özenle hazırlanmıştır.", + "about_license": "MIT Lisansı", + "about_release_notes": "Sürüm notları", + "about_review": "Bize bir değerlendirme bırakın", + "performance_score": "Performans skoru: {num}", + "run_performance_test": "Performansı test et", + "about_selftest": "Kendi kendine testi çalıştır", + "block_explorer_invalid_custom_url": "Sağlanan URL geçersiz. Lütfen http:// veya https:// ile başlayan geçerli bir URL girin.", + "about_selftest_electrum_disabled": "Electrum Çevrimdışı Modunda kendi kendine test kullanılamaz. Lütfen çevrimdışı modu devre dışı bırakıp tekrar deneyin.", + "about_selftest_ok": "Tüm dahili testler başarıyla geçti. Cüzdan iyi çalışıyor.", + "about_sm_github": "GitHub", + "privacy_temporary_screenshots": "Ekran Yakalamaya İzin Ver", + "privacy_temporary_screenshots_instructions": "Ekran yakalama koruması geçici olarak kapatılacak, ekran görüntüleri ve ekran kayıtları etkinleştirilecek. BlueWallet'i kapatıp yeniden açtığınızda koruma otomatik olarak yeniden etkinleşir.", + "biometrics_no_longer_available": "Cihaz ayarlarınız değişti ve artık uygulamada seçilen güvenlik ayarlarıyla eşleşmiyor. Bu değişiklikleri uygulamak için lütfen biyometrik doğrulamayı veya PIN'i yeniden etkinleştirin ve ardından uygulamayı yeniden başlatın.", + "biom_10times": "Şifrenizi 10 kez girmeyi denediniz. Depolamanızı sıfırlamak ister misiniz? Bu, tüm cüzdanları kaldırır ve depolamanızın şifrelemesini çözer.", + "biom_conf_identity": "Lütfen kimliğinizi doğrulayın.", + "biom_no_passcode": "Cihazınızda PIN veya biyometrik doğrulama etkin değil. Devam etmek için lütfen Ayarlar uygulamasından bir PIN veya biyometrik doğrulama yapılandırın.", + "biom_remove_decrypt": "Tüm cüzdanlarınız kaldırılacak ve depolamanızın şifrelemesi çözülecek. Devam etmek istediğinizden emin misiniz?", + "currency_source": "Kur şuradan alınır", + "currency_fetch_error": "Seçilen para birimi için kur alınırken bir hata oluştu.", + "default_title": "Başlangıçta", + "donate": "Bağış yap", + "donate_description": "Blue'yu ücretsiz tutmamıza yardımcı olun!", + "electrum_connected": "Bağlandı", + "electrum_connected_not": "Bağlı Değil", + "electrum_error_connect": "Sağlanan Electrum sunucusuna bağlanılamıyor", + "electrum_error_connect_tor": "Sağlanan Electrum sunucusuna bağlanılamıyor. Lütfen Orbot uygulamasının bağlı olduğundan emin olun ve tekrar deneyin.", + "lndhub_uri": "Örn., {example}", + "electrum_host": "Örn., {example}", + "electrum_offline_mode": "Çevrimdışı Mod", + "electrum_offline_description": "Etkinleştirildiğinde, Bitcoin cüzdanlarınız bakiye veya işlem almaya çalışmaz.", + "electrum_port": "Port, genellikle {example}", + "use_ssl": "SSL kullan", + "electrum_saved": "Değişiklikleriniz başarıyla kaydedildi. Değişikliklerin etkili olması için BlueWallet'in yeniden başlatılması gerekebilir.", + "set_electrum_server_as_default": "{server} varsayılan Electrum sunucusu olarak ayarlansın mı?", + "set_lndhub_as_default": "{url} varsayılan LNDhub sunucusu olarak ayarlansın mı?", + "electrum_settings_server": "Electrum Sunucusu", + "electrum_status": "Durum", + "electrum_preferred_server": "Tercih Edilen Sunucu", + "electrum_preferred_server_description": "Cüzdanınızın tüm Bitcoin etkinlikleri için kullanmasını istediğiniz sunucuyu girin. Ayarlandıktan sonra cüzdanınız bakiyeleri kontrol etmek, işlem göndermek ve ağ verilerini almak için yalnızca bu sunucuyu kullanır. Ayarlamadan önce bu sunucuya güvendiğinizden emin olun.", + "electrum_unable_to_connect": "{server} adresine bağlanılamadı.", + "electrum_history": "Geçmiş", + "electrum_reset_to_default": "Bu, BlueWallet'in sunucu listesinden rastgele bir sunucu seçmesine olanak tanır.", + "electrum_reset": "Varsayılana sıfırla", + "electrum_reset_to_default_and_clear_history": "Varsayılana sıfırla ve geçmişi temizle", + "encrypt_decrypt": "Depolamanın Şifresini Çöz", + "encrypt_decrypt_q": "Depolamanızın şifrelemesini çözmek istediğinize emin misiniz? Bu, cüzdanlarınıza şifre olmadan erişilmesine olanak tanır.", + "encrypt_enc_and_pass": "Şifre Korumalı", + "encrypt_storage_explanation_headline": "Depolama Şifrelemesini Etkinleştir", + "encrypt_storage_explanation_description_line1": "Depolama Şifrelemesini etkinleştirmek, verilerinizin cihazınızda saklanma şeklini güvence altına alarak uygulamanıza ek bir koruma katmanı ekler. Bu, izniniz olmadan başkalarının bilgilerinize erişmesini zorlaştırır.", + "encrypt_storage_explanation_description_line2": "Ancak şunu bilmek önemlidir: bu şifreleme yalnızca cihazın anahtar deposunda saklanan cüzdanlarınıza erişimi korur. Cüzdanların kendilerine şifre veya ek koruma eklemez.", + "i_understand": "Anlıyorum", + "block_explorer": "Blok Tarayıcısı", + "block_explorer_preferred": "Tercih edilen blok tarayıcısını kullan", + "block_explorer_error_saving_custom": "Tercih edilen blok tarayıcısı kaydedilirken hata oluştu", + "encrypt_title": "Güvenlik", + "encrypt_tstorage": "Depolama", + "encrypt_use": "{type} kullan", + "set_as_preferred": "Tercih edilen olarak ayarla", + "set_as_preferred_electrum": "{host}:{port} adresini tercih edilen sunucu olarak ayarlamak, rastgele önerilen bir sunucuya bağlanmayı devre dışı bırakır.", + "encrypted_feature_disabled": "Bu özellik, şifreli depolama etkinken kullanılamaz.", + "encrypt_use_expl": "{type}, bir işlem yapmadan, kilidi açmadan, dışa aktarmadan veya bir cüzdanı silmeden önce kimliğinizi doğrulamak için kullanılır.", + "biometrics_fail": "{type} etkin değilse veya kilidi açamazsa, alternatif olarak cihazınızın PIN'ini kullanabilirsiniz.", + "general": "Genel", + "general_continuity": "Süreklilik", + "general_continuity_e": "Etkinleştirildiğinde, seçilen cüzdanları ve işlemleri Apple iCloud'a bağlı diğer cihazlarınızı kullanarak görüntüleyebilirsiniz.", + "groundcontrol_explanation": "GroundControl, Bitcoin cüzdanları için ücretsiz, açık kaynaklı bir push bildirim sunucusudur. BlueWallet'in altyapısına bağımlı olmamak için kendi GroundControl sunucunuzu kurabilir ve URL'sini buraya yazabilirsiniz. GroundControl'ün varsayılan sunucusunu kullanmak için boş bırakın.", + "last_updated": "Son Güncelleme", + "language_isRTL": "Dil yönlendirmesinin etkili olması için BlueWallet'in yeniden başlatılması gerekir.", + "license": "Lisans", + "lightning_error_lndhub_uri": "Geçersiz LNDhub URI", + "lightning_error_lndhub_uri_tor": "Geçersiz LNDhub URI. Lütfen Orbot uygulamasının bağlı olduğundan emin olun ve tekrar deneyin.", + "lightning_saved": "Değişiklikleriniz başarıyla kaydedildi.", + "lightning_settings_explain": "Kendi LND düğümünüze bağlanmak için lütfen LNDhub kurun ve URL'sini buraya, ayarlara girin. Yalnızca değişiklikler kaydedildikten sonra oluşturulan cüzdanların belirtilen LNDhub'a bağlanacağını lütfen unutmayın.", + "lndhub_github": "GitHub Deposu", + "network": "Ağ", + "network_broadcast": "İşlemi Yayınla", + "network_electrum": "Electrum Sunucusu", + "electrum_suggested_description": "Tercih edilen bir sunucu ayarlanmadığında, kullanım için rastgele önerilen bir sunucu seçilir.", + "not_a_valid_uri": "Geçersiz URI", + "notifications": "Bildirimler", + "open_link_in_explorer": "Bağlantıyı tarayıcıda aç", + "password_explain": "Depolamanızın kilidini açmak için kullanacağınız şifreyi girin.", + "privacy_read_clipboard": "Panoyu Oku", + "privacy_quickactions_explanation": "Cüzdanınızın bakiyesini hızlıca görüntülemek için BlueWallet uygulama simgesine basılı tutun.", + "privacy_clipboard_explanation": "Panonuzda bir adres veya fatura bulunursa kısayollar sağla.", + "privacy_do_not_track": "Analitiği Devre Dışı Bırak", + "privacy_do_not_track_explanation": "Performans ve güvenilirlik bilgileri analiz için gönderilmez.", + "rate": "Kur", + "push_notifications_explanation": "Bildirimleri etkinleştirerek, cihaz tokeniniz, bildirimleri etkinleştirdikten sonra yapılan tüm cüzdanlara ve işlemlere ait cüzdan adresleri ve işlem ID'leri ile birlikte sunucuya gönderilir. Cihaz tokeni bildirim göndermek için kullanılır ve cüzdan bilgileri, gelen Bitcoin veya işlem onayları hakkında sizi bilgilendirmemize olanak tanır.\n\nYalnızca bildirimleri etkinleştirdikten sonraki bilgiler iletilir—öncesinden hiçbir şey toplanmaz.\n\nBildirimleri devre dışı bırakmak, bu bilgilerin tümünü sunucudan kaldırır. Ayrıca, uygulamadan bir cüzdanı silmek, ona ait bilgileri de sunucudan kaldırır.", + "selfTest": "Kendi Kendine Test", + "success_transaction_broadcasted": "İşleminiz başarıyla yayınlandı!", + "total_balance_explanation": "Tüm cüzdanlarınızın toplam bakiyesini ana ekran widget'larınızda görüntüleyin.", + "widgets": "Widget'lar", + "tools": "Araçlar" }, "notifications": { - "no_and_dont_ask": "Hayır, bir daha sorma", - "ask_me_later": "Daha sonra hatırlat" + "would_you_like_to_receive_notifications": "Gelen ödemeleriniz olduğunda bildirim almak ister misiniz?", + "notifications_subtitle": "Gelen ödemeler ve işlem onayları", + "no_and_dont_ask": "Hayır ve bana tekrar sorma.", + "permission_denied_message": "Size bildirim göndermek için izin vermediniz. Bildirim almak isterseniz lütfen cihaz ayarlarınızdan etkinleştirin." }, "transactions": { "cpfp_create": "Oluştur", "details_copy": "Kopya", - "details_from": "Girdi", - "details_show_in_block_explorer": "Blok gezgininde göster", "details_title": "İşlem", "details_to": "Çıktı", "pending": "Beklemede", - "list_title": "işlemler" + "list_title": "işlemler", + "transaction": "İşlem", + "cancel_explain": "Bu işlemi, size ödeme yapan ve daha yüksek ücretlere sahip bir işlemle değiştireceğiz. Bu, mevcut işlemi etkili biçimde iptal eder. Buna RBF—Replace by Fee denir.", + "cancel_no": "Bu işlem değiştirilemez.", + "cancel_title": "Bu işlemi iptal et (RBF)", + "transaction_loading_error": "İşlem yüklenirken bir sorun oluştu. Lütfen daha sonra tekrar deneyin.", + "transaction_not_available": "İşlem mevcut değil", + "confirmations_lowercase": "{confirmations} onay", + "expand_note": "Notu genişlet", + "cpfp_exp": "Onaylanmamış işleminizi harcayan başka bir işlem oluşturacağız. Toplam ücret orijinal işlem ücretinden daha yüksek olacak, böylece daha hızlı madenlenir. Buna CPFP—Child Pays for Parent denir.", + "cpfp_no_bump": "Bu işlemin ücreti artırılamaz.", + "cpfp_title": "Ücret Artır (CPFP)", + "details_balance_hide": "Bakiyeyi Gizle", + "details_balance_show": "Bakiyeyi Göster", + "details_copy_block_explorer_link": "Blok Tarayıcısı Bağlantısını Kopyala", + "details_copy_note": "Notu Kopyala", + "details_copy_txid": "İşlem ID'sini Kopyala", + "details_id": "ID", + "details_inputs": "Girdiler", + "details_outputs": "Çıktılar", + "date": "Tarih", + "details_received": "Alındı", + "details_view_in_browser": "Tarayıcıda Görüntüle", + "incoming_transaction": "Gelen İşlem", + "outgoing_transaction": "Giden İşlem", + "expired_transaction": "Süresi Dolmuş İşlem", + "pending_transaction": "Bekleyen İşlem", + "offchain": "Zincir dışı", + "onchain": "Zincir üstü", + "enable_offline_signing": "Bu cüzdan çevrimdışı imzalama ile birlikte kullanılmıyor. Şimdi etkinleştirmek ister misiniz?", + "list_conf": "Onay: {number}", + "pending_with_amount": "Beklemede {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: ~10 dakika içinde", + "eta_3h": "ETA: ~3 saat içinde", + "eta_1d": "ETA: ~1 gün içinde", + "list_title_sent": "Gönderildi", + "list_title_received": "Alındı", + "open_url_error": "Bağlantı varsayılan tarayıcı ile açılamadı. Lütfen varsayılan tarayıcınızı değiştirip tekrar deneyin.", + "rbf_explain": "Bu işlemi, daha hızlı madenlenmesi için daha yüksek ücretli bir işlemle değiştireceğiz. Buna RBF—Replace by Fee denir.", + "rbf_title": "Hızlandır (RBF)", + "status_bump": "Hızlandır", + "status_cancel": "İptal", + "transactions_count": "İşlem Sayısı", + "txid": "İşlem ID'si", + "updating": "Güncelleniyor...", + "watchOnlyWarningTitle": "Güvenlik uyarısı", + "watchOnlyWarningDescription": "Genellikle kullanıcıları kandırmak için \"watch-only\" cüzdanlarını kullanan dolandırıcılara karşı dikkatli olun. Bu cüzdanlar parayı kontrol etmenize veya göndermenize izin vermez; yalnızca bakiyeyi görüntülemenize olanak tanır.", + "custom_fee_warning_title": "Uyarı", + "custom_fee_warning_description": "1 sat/vB altındaki ücretler geçerlidir, ancak düğüm politikaları nedeniyle iletilmeyebilir.", + "details_eta_analyzing": "Analiz ediliyor...", + "details_sent": "Gönderildi", + "details_section": "Detaylar", + "details_explorer": "tarayıcı", + "details_network_fee": "Ağ Ücreti", + "details_to_address": "Kime", + "details_note": "Not", + "details_add_note": "ekle", + "details_advanced": "Gelişmiş", + "details_fee_rate": "Ücret oranı", + "details_size": "Boyut", + "details_virtual_size": "Sanal boyut", + "details_tx_hex": "İşlem Hex", + "details_inputs_count": "Girdiler ({count})", + "details_outputs_count": "Çıktılar ({count})" }, "wallets": { "add_create": "Oluştur", + "total_balance": "Bakiye", + "add_entropy": "Entropi", "add_import_wallet": "Cüzdan İçeri Yükle", "add_title": "Cüzdan ekle", "add_wallet_name": "İsim", @@ -219,10 +412,9 @@ "details_are_you_sure": "Emin misiniz?", "details_delete": "Sil", "details_export_backup": "Dışa yükle / yedekle", - "details_no_cancel": "Hayır, vazgeç", - "details_save": "Kaydet", "details_show_xpub": "Cüzdan XPUB göster", "details_title": "Cüzdan", + "wallets": "cüzdanlar", "details_type": "Tip", "details_yes_delete": "Evet, sil", "export_title": "cüzdan yedekle", @@ -236,34 +428,277 @@ "list_create_a_wallet": "Cüzdan oluştur", "list_empty_txs1": "İşlemleriniz burada görünür,", "list_latest_transaction": "en son işlem", + "paste_from_clipboard": "Yapıştır", "list_title": "cüzdanlar", - "reorder_title": "Cüzdanları Sırala", "select_wallet": "Cüzdan Seç", - "xpub_copiedToClipboard": "Panoya kopyalandı", - "xpub_title": "cüzdan XPUB" + "xpub_title": "cüzdan XPUB", + "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Basit ve güçlü Bitcoin cüzdanı", + "add_entropy_reset_title": "Entropiyi Sıfırla", + "add_entropy_reset_message": "Cüzdan türünü değiştirmek mevcut entropiyi sıfırlar. Devam etmek istiyor musunuz?", + "add_entropy_bytes": "{bytes} bayt entropi", + "add_entropy_generated": "{gen} bayt üretilmiş entropi", + "add_entropy_provide": "Zar atışlarıyla entropi sağla", + "add_entropy_remain": "{gen} bayt üretilmiş entropi. Kalan {rem} bayt sistem rastgele sayı üretecinden alınacaktır.", + "add_lightning": "Lightning", + "add_lightning_explain": "Anında işlemlerle harcama için", + "add_lndhub": "Kendi LNDhub'unuza bağlanın", + "add_lndhub_error": "Sağlanan düğüm adresi geçersiz bir LNDhub düğümü.", + "add_lndhub_placeholder": "Düğüm Adresiniz", + "add_placeholder": "ilk cüzdanım", + "add_wallet_seed_length": "Seed Uzunluğu", + "add_wallet_seed_length_12": "12 kelime", + "add_wallet_seed_length_24": "24 kelime", + "clipboard_bitcoin": "Panonuzda bir Bitcoin adresi var. Bir işlem için kullanmak ister misiniz?", + "clipboard_lightning": "Panonuzda bir Lightning faturası var. Bir işlem için kullanmak ister misiniz?", + "clear_clipboard_on_import": "İçe aktarımda panoyu temizle", + "details_advanced": "Gelişmiş", + "details_connected_to": "Bağlanılan", + "details_del_wb_err": "Sağlanan bakiye miktarı bu cüzdanın bakiyesiyle eşleşmiyor. Lütfen tekrar deneyin.", + "details_del_wb_q": "Bu cüzdanda bakiye var. Devam etmeden önce, bu cüzdanın seed ifadesi olmadan paraları kurtaramayacağınızı lütfen unutmayın. Kazara silmemek için lütfen cüzdanınızın {balance} satoshi bakiyesini girin.", + "details_delete_wallet": "Cüzdanı Sil", + "details_derivation_path": "türetme yolu", + "details_display": "Ana Ekranda Göster", + "details_export_history": "Geçmişi CSV olarak dışa aktar", + "details_master_fingerprint": "Ana Parmak İzi", + "details_multisig_type": "multisig", + "details_show_addresses": "Adresleri göster", + "swipe_balance_hide": "Gizle", + "swipe_balance_show": "Göster", + "drag_to_reorder": "Yeniden sıralamak için sürükle", + "clear_search": "Aramayı temizle", + "details_use_with_hardware_wallet": "Donanım Cüzdanı ile Kullan", + "enter_bip38_password": "Şifre çözmek için parola girin", + "import_passphrase": "Parola", + "import_passphrase_title": "Parola", + "import_passphrase_message": "Kullandıysanız parolayı girin", + "import_explanation": "Lütfen seed kelimelerinizi, public key, WIF veya elinizde olan her şeyi girin. BlueWallet doğru formatı tahmin etmek ve cüzdanınızı içe aktarmak için elinden geleni yapacak.", + "import_success_watchonly": "Cüzdanınız başarıyla içe aktarıldı. UYARI: Bu bir yalnızca izleme cüzdanıdır, buradan harcama YAPAMAZSINIZ.", + "import_search_accounts": "Hesapları ara", + "learn_more": "Daha fazla bilgi", + "import_discovery_title": "Keşif", + "import_discovery_subtitle": "Keşfedilen bir cüzdan seçin", + "import_discovery_derivation": "Özel türetme yolu kullan", + "import_discovery_no_wallets": "Hiçbir cüzdan bulunamadı.", + "import_discovery_offline": "BlueWallet şu anda çevrimdışı modda. Bu modda cüzdanın varlığını doğrulayamaz, bu yüzden doğru olanı manuel olarak seçmeniz gerekir", + "import_derivation_found": "Bulundu", + "import_derivation_found_not": "Bulunamadı", + "import_derivation_loading": "Yükleniyor...", + "import_derivation_subtitle": "Özel türetme yolu girin, cüzdanınızı keşfetmeye çalışacağız.", + "import_derivation_title": "Türetme yolu", + "import_derivation_unknown": "Bilinmiyor", + "import_wrong_path": "Yanlış türetme yolu", + "list_create_a_wallet_text": "Ücretsizdir ve istediğiniz kadar \noluşturabilirsiniz.", + "list_empty_txs1_lightning": "Lightning cüzdanı günlük işlemleriniz için kullanılmalıdır. Ücretler son derece düşük ve hız çok yüksektir.", + "list_empty_txs2": "Cüzdanınızla başlayın.", + "list_empty_txs2_lightning": "\nKullanmaya başlamak için Bakiyeleri Yönet'e dokunun ve bakiyenizi yükleyin.", + "list_long_choose": "Fotoğraf Seç", + "import_file": "Dosyayı İçe Aktar", + "list_long_scan": "QR Kodu Tara", + "list_tryagain": "Tekrar dene", + "no_ln_wallet_error": "Bir Lightning faturasını ödemeden önce, bir Lightning cüzdanı eklemelisiniz.", + "looks_like_bip38": "Bu, parola korumalı bir özel anahtara benziyor (BIP38).", + "manage_title": "Cüzdanları Yönet", + "no_results_found": "Sonuç bulunamadı.", + "please_continue_scanning": "Lütfen taramaya devam edin.", + "select_no_bitcoin": "Şu anda mevcut Bitcoin cüzdanı yok.", + "select_no_bitcoin_exp": "Lightning cüzdanlarını yüklemek için bir Bitcoin cüzdanı gerekir. Lütfen bir tane oluşturun veya içe aktarın.", + "pull_to_refresh": "Yenilemek için çek", + "warning_do_not_disclose": "Aşağıdaki bilgileri asla paylaşmayın", + "scan_import": "Cüzdanınızı başka bir uygulamada içe aktarmak için bu QR kodunu tarayın.", + "write_down_header": "Manuel bir yedek oluşturun", + "write_down": "Bu kelimeleri yazın ve güvenli bir şekilde saklayın. Cüzdanınızı daha sonra geri yüklemek için kullanın.", + "wallet_type_this": "Bu cüzdan türü {type}.", + "share_number": "Paylaş {number}", + "copy_ln_url": "Cüzdanınızı daha sonra geri yüklemek için bu URL'yi kopyalayın ve güvenli bir şekilde saklayın.", + "copy_ln_public": "Cüzdanınızı daha sonra geri yüklemek için bu bilgiyi kopyalayın ve güvenli bir şekilde saklayın.", + "add_ln_wallet_first": "Önce bir Lightning cüzdanı eklemelisiniz.", + "identity_pubkey": "Kimlik Açık Anahtarı", + "manage_wallets_search_placeholder": "Cüzdanları, adresleri, işlemleri ve notları ara", + "more_info": "Daha Fazla Bilgi", + "details_delete_wallet_error_message": "Bu cüzdanın bildirimlerden kaldırıldığını doğrulamada bir sorun oldu—bu, bir ağ sorunu veya zayıf bağlantı nedeniyle olabilir. Devam ederseniz, silindikten sonra bile bu cüzdanla ilgili işlemler için bildirim almaya devam edebilirsiniz.", + "details_delete_anyway": "Yine de sil" + }, + "total_balance_view": { + "title": "Bakiye", + "display_in_bitcoin": "Bitcoin olarak göster", + "hide": "Gizle", + "display_in_sats": "sat olarak göster", + "display_in_fiat": "{currency} olarak göster", + "explanation": "Tüm cüzdanlarınızın toplam bakiyesini genel bakış ekranında görüntüleyin." }, "multisig": { "confirm": "Onayla", "header": "Gönder", - "share": "Paylaş", "create": "Oluştur", "co_sign_transaction": "Bir işlemi imzalayın", "ms_help": "Yardım", - "ms_help_title5": "Gelişmiş Mod" + "ms_help_title5": "Gelişmiş Mod", + "multisig_vault": "Multisig Kasa", + "default_label": "Multisig Kasa", + "multisig_vault_explain": "Büyük miktarlar için en iyi güvenlik", + "provide_signature": "İmza sağla", + "provide_signature_details": "Bu işlemi imzalamak için anahtarın bulunduğu cihazınızı ve cüzdanınızı kullanın", + "provide_signature_details_bluewallet": "BlueWallet'te Gönder ekranı menüsüne gidin ve şunu seçin: ", + "provide_signature_next_steps": "İmzalanmış İşlemi Tara veya İçe Aktar", + "provide_signature_next_steps_details": "Cüzdanınız işlemi başarıyla imzaladığında, sağlanan QR kodunu tarayın veya beraberindeki dosyayı içe aktarın, ardından yayınlamadan önce tüm işlem detaylarını gözden geçirin.", + "vault_key": "Kasa Anahtarı {number}", + "required_keys_out_of_total": "Toplam içinden gerekli anahtar sayısı", + "fee": "Ücret: {number}", + "fee_btc": "{number} BTC", + "share": "Paylaş...", + "view": "Görüntüle", + "shared_key_detected": "Paylaşılan ortak imzalayan", + "shared_key_detected_question": "Sizinle bir ortak imzalayan paylaşıldı, içe aktarmak istiyor musunuz?", + "manage_keys": "Anahtarları Yönet", + "how_many_signatures_can_bluewallet_make": "BlueWallet kaç imza yapabilir", + "signatures_required_to_spend": "Gerekli imza sayısı {number}", + "signatures_we_can_make": "{number} yapabiliriz", + "scan_or_import_file": "Dosyayı tara veya içe aktar", + "export_coordination_setup": "Koordinasyon Kurulumunu Dışa Aktar", + "cosign_this_transaction": "Bu işlemi ortak imzala?", + "lets_start": "Başlayalım", + "native_segwit_title": "En iyi uygulama", + "wrapped_segwit_title": "En iyi uyumluluk", + "legacy_title": "Legacy", + "what_is_vault": "Kasa, bir", + "what_is_vault_numberOfWallets": " {m}/{n} multisig ", + "what_is_vault_wallet": "cüzdandır.", + "vault_advanced_customize": "Kasa Ayarları", + "needs": "İhtiyacı var", + "what_is_vault_description_number_of_vault_keys": " {m} kasa anahtarı ", + "what_is_vault_description_to_spend": "harcamak için ve yedek olarak \nkullanabileceğiniz üçüncü bir tane.", + "what_is_vault_description_to_spend_other": "harcamak için.", + "quorum": "{m}/{n} yetersayı", + "quorum_header": "Yetersayı", + "of": "/", + "wallet_type": "Cüzdan Türü", + "invalid_mnemonics": "Bu mnemonik ifade geçerli görünmüyor.", + "invalid_cosigner": "Geçersiz ortak imzalayan verisi", + "not_a_multisignature_xpub": "Bu, multisig bir cüzdandan XPUB değil!", + "invalid_cosigner_format": "Yanlış ortak imzalayan: Bu, {format} formatı için bir ortak imzalayan değil.", + "create_new_key": "Yeni Oluştur", + "scan_or_open_file": "Dosyayı tara veya aç", + "i_have_mnemonics": "Bu anahtar için bir seed'im var.", + "type_your_mnemonics": "Mevcut Kasa anahtarınızı içe aktarmak için bir seed girin.", + "this_is_cosigners_xpub": "Bu, ortak imzalayanın XPUB'udur—başka bir cüzdana içe aktarılmaya hazır. Paylaşmak güvenlidir.", + "this_is_cosigners_xpub_airdrop": "AirDrop ile paylaşırsanız alıcıların koordinasyon ekranında olması gerekir.", + "wallet_key_created": "Kasa anahtarınız oluşturuldu. Mnemonik seed'inizi güvenle yedeklemek için bir an ayırın.", + "are_you_sure_seed_will_be_lost": "Emin misiniz? Yedeğiniz yoksa mnemonik seed'iniz kaybolur.", + "forget_this_seed": "Bu seed'i unut ve onun yerine XPUB kullan.", + "view_edit_cosigners": "Ortak İmzalayanları Görüntüle/Düzenle", + "this_cosigner_is_already_imported": "Bu ortak imzalayan zaten içe aktarılmış.", + "export_signed_psbt": "İmzalanmış PSBT'yi Dışa Aktar", + "input_fp": "Fingerprint girin", + "input_fp_explain": "Varsayılanı kullanmak için atlayın (00000000)", + "input_path": "Türetme Yolu Girin", + "input_path_explain": "Varsayılanı kullanmak için atlayın ({default})", + "ms_help_title": "Multisig Kasalar Nasıl Çalışır: İpuçları ve Püf Noktaları", + "ms_help_text": "Artırılmış güvenlik veya paylaşılan saklama için birden fazla anahtarı olan cüzdan", + "ms_help_title1": "Birden fazla cihaz önerilir.", + "ms_help_1": "Kasa, Electrum, Specter, Coldcard, Cobo Vault gibi diğer BlueWallet uygulamaları ve PSBT uyumlu cüzdanlarla çalışacaktır.", + "ms_help_title2": "Anahtarları Düzenleme", + "ms_help_2": "Bu cihazda tüm Kasa anahtarlarını oluşturabilir ve daha sonra kaldırabilir veya düzenleyebilirsiniz. Tüm anahtarları aynı cihazda tutmak, normal bir Bitcoin cüzdanı ile eşdeğer güvenliğe sahiptir.", + "ms_help_title3": "Kasa Yedekleri", + "ms_help_3": "Cüzdan seçeneklerinde Kasa yedeğinizi ve yalnızca izleme yedeğinizi bulacaksınız. Bu yedek, cüzdanınıza giden bir harita gibidir. Seed'lerinizden birini kaybetmeniz durumunda cüzdan kurtarma için gereklidir.", + "ms_help_title4": "Kasaları İçe Aktarma", + "ms_help_4": "Bir multisig'i içe aktarmak için yedek dosyanızı ve İçe Aktar özelliğini kullanın. Yalnızca seed'leriniz ve XPUB'larınız varsa, Kasa anahtarları oluştururken bireysel İçe Aktar düğmesini kullanabilirsiniz.", + "ms_help_5": "BlueWallet varsayılan olarak 2/3'lük bir Kasa üretir. Farklı bir yetersayı oluşturmak veya adres türünü değiştirmek için Ayarlar'dan Gelişmiş Modu etkinleştirin." }, "is_it_my_address": { "title": "Adres bana mı ait?", "enter_address": "Adres gir", - "check_address": "Adresi kontrol et" + "check_address": "Adresi kontrol et", + "owns": "{label}, {address} adresinin sahibidir", + "no_wallet_owns_address": "Mevcut cüzdanların hiçbiri sağlanan adresin sahibi değil.", + "view_qrcode": "QR Kodunu Görüntüle" + }, + "autofill_word": { + "title": "Seed son kelimesi", + "enter": "Kısmi mnemonik ifadenizi girin", + "generate_word": "Son kelimeyi üret", + "error": "Girdi 11 veya 23 kelimelik bir kısmi mnemonik değil. Lütfen tekrar deneyin." + }, + "cc": { + "sort_label": "Etiket", + "change": "Para üstü", + "coins_selected": "Seçilen Coinler ({number})", + "selected_summ": "{value} seçildi", + "empty": "Bu cüzdanda şu anda hiç coin yok.", + "freeze": "Dondur", + "freezeLabel": "Dondur", + "freezeLabel_un": "Çöz", + "header": "Para kontrolü", + "use_coin": "Coin Kullan", + "use_coins": "Coinleri Kullan", + "tip": "Bu özellik, cüzdan yönetimini iyileştirmek için coinleri görmenize, etiketlemenize, dondurmanıza veya seçmenize olanak tanır. Renkli dairelere dokunarak birden fazla coin seçebilirsiniz.", + "sort_asc": "Artan", + "sort_desc": "Azalan", + "sort_height": "Yükseklik", + "sort_value": "Değer", + "sort_status": "Durum", + "sort_by": "Sırala" }, "units": { "BTC": "BTC", "MAX": "Maks", - "sats": "sat" + "sats": "sat", + "sat_vbyte": "sat/vByte" }, "addresses": { "sign_placeholder_address": "Adres", "type_receive": "Al", - "transactions": "işlemler" + "transactions": "işlemler", + "copy_private_key": "Özel anahtarı kopyala", + "sensitive_private_key": "Uyarı: özel anahtarlar son derece hassastır. Devam edilsin mi?", + "sign_title": "Mesaj İmzala/Doğrula", + "sign_help": "Burada bir Bitcoin adresine dayalı kriptografik bir imza oluşturabilir veya doğrulayabilirsiniz.", + "sign_sign": "İmzala", + "sign_verify": "Doğrula", + "sign_signature_correct": "Doğrulama başarılı!", + "sign_signature_incorrect": "Doğrulama başarısız!", + "sign_placeholder_message": "Mesaj", + "sign_placeholder_signature": "İmza", + "addresses_title": "Adresler", + "type_change": "Para üstü", + "type_used": "Kullanılmış" + }, + "lnurl_auth": { + "register_question_part_1": "Lightning cüzdanınızı kullanarak şurada bir hesap kaydetmek ister misiniz:", + "register_question_part_2": "?", + "register_answer": "{hostname} adresinde başarıyla bir hesap oluşturdunuz!", + "login_question_part_1": "Lightning cüzdanınızı kullanarak şurada oturum açmak ister misiniz:", + "login_question_part_2": "?", + "login_answer": "{hostname} adresinde başarıyla oturum açtınız!", + "link_question_part_1": "Hesabınızı şurada Lightning cüzdanınıza bağlamak ister misiniz:", + "link_question_part_2": "?", + "link_answer": "Lightning cüzdanınız {hostname} adresindeki hesabınıza başarıyla bağlandı!", + "auth_question_part_1": "Lightning cüzdanınızı kullanarak şurada kimlik doğrulamak ister misiniz:", + "auth_question_part_2": "?", + "auth_answer": "{hostname} adresinde başarıyla kimliğiniz doğrulandı!", + "could_not_auth": "Sizi {hostname} adresine doğrulayamadık.", + "authenticate": "Kimlik Doğrula" + }, + "bip47": { + "payment_code": "Ödeme Kodu", + "contacts": "Kişiler", + "bip47_explain": "Yeniden kullanılabilir ve paylaşılabilir kod", + "bip47_explain_subtitle": "BIP47", + "purpose": "Yeniden kullanılabilir ve paylaşılabilir kod (BIP47)", + "pay_this_contact": "Bu kişiye öde", + "rename_contact": "Kişiyi yeniden adlandır", + "copy_payment_code": "Ödeme Kodunu Kopyala", + "hide_contact": "Kişiyi gizle", + "rename": "Yeniden adlandır", + "provide_name": "Bu kişi için yeni bir ad girin", + "add_contact": "Kişi Ekle", + "provide_payment_code": "Ödeme Kodu Girin", + "invalid_pc": "Geçersiz Ödeme Kodu", + "notification_tx_unconfirmed": "Bildirim işlemi henüz onaylanmadı, lütfen bekleyin", + "failed_create_notif_tx": "Zincir üstü işlem oluşturulamadı", + "onchain_tx_needed": "Zincir üstü işlem gerekiyor", + "notif_tx_sent": "Bildirim işlemi gönderildi. Lütfen onaylanmasını bekleyin", + "notif_tx": "Bildirim işlemi", + "not_found": "Ödeme kodu bulunamadı" } } diff --git a/loc/ua.json b/loc/ua.json index a8a6b380ea3..1f6476083ee 100644 --- a/loc/ua.json +++ b/loc/ua.json @@ -1,145 +1,182 @@ { "_": { - "bad_password": "Невірний пароль, спробуйте ще раз", - "cancel": "Відміна", + "bad_password": "Неправильний пароль. Спробуйте ще раз.", + "cancel": "Скасувати", "continue": "Продовжити", "clipboard": "Буфер обміну", + "copied": "Скопійовано!", + "discard_changes": "Скасувати зміни?", + "discard_changes_explain": "У вас є незбережені зміни. Ви впевнені, що хочете їх відкинути та залишити екран?", "enter_password": "Введіть пароль", "never": "Ніколи", - "disabled": "Вимкнено", "of": "{number} з {total}", "ok": "OK", + "enter_url": "Введіть URL", "storage_is_encrypted": "Ваше сховище зашифроване. Введіть пароль для розшифровки", "yes": "Так", "no": "Ні", - "save": "Зберегти", + "save": "Зберегти...", "seed": "Сід", "success": "Успіх", "wallet_key": "Ключ гаманця", - "invalid_animated_qr_code_fragment": "Невірно зображений елемент QR-коду. Спробуйте ще раз.", - "file_saved": "Файл {filePath} було збережено у вашому {destination}.", - "downloads_folder": "Папка Завантажень", "close": "Закрити", + "change_input_currency": "Змінити валюту введення", "refresh": "Оновити", - "more": "Більше", - "pick_file": "Вибрати файл" - }, - "alert": { - "default": "Сповіщення" + "pick_image": "Вибрати з бібліотеки", + "pick_file": "Вибрати файл", + "enter_amount": "Введіть суму", + "qr_custom_input_button": "Торкніться 10 разів для користувацького вводу", + "unlock": "Розблокувати", + "port": "Порт", + "ssl_port": "SSL-порт", + "suggested": "Запропоновано" }, "azteco": { "codeIs": "Ваш код ваучера", "errorBeforeRefeem": "Перш ніж підтвердити, ви повинні додати гаманець Bitcoin.", "errorSomething": "Щось пішло не так. Цей ваучер ще дійсний?", - "redeemButton": "Купити", - "success": "Успіх" + "redeem": "Купити на гаманець", + "redeemButton": "Викупити", + "success": "Успіх", + "successMessage": "Ваучер успішно активовано! Кошти невдовзі мають надійти до вашого Bitcoin-гаманця.", + "title": "Викупити ваучер Azte.co" }, "entropy": { "save": "Зберегти", "title": "Ентропія", - "undo": "Відмінити" + "undo": "Скасувати", + "amountOfEntropy": "{bits} з {limit} біт" }, "errors": { "broadcast": "Помилка трансляції.", "error": "Помилка", - "network": "Помилка Мережі" + "network": "Помилка мережі" }, "lnd": { - "active": "Активний", - "inactive": "Неактивний", - "channels": "Канали", - "no_channels": "Немає каналів", - "close_channel": "Закрити канал", - "new_channel": "Новий канал", + "errorInvoiceExpired": "Термін дії інвойсу закінчився.", "expired": "Термін дії закінчився", + "expiresIn": "Закінчується через {time} хвилин", "payButton": "Оплатити", - "open_channel": "Відкрити Канал", - "remote_host": "Віддалений хост", + "payment": "Платіж", + "placeholder": "Рахунок або адреса", + "potentialFee": "Орієнтовна комісія: {fee}", "refill": "Поповнити", + "refill_create": "Щоб продовжити, будь ласка, створіть Bitcoin-гаманець для поповнення.", "refill_external": "Поповнити з зовнішнього гаманця", - "refill_lnd_balance": "Збільшити баланс Lightning гаманця", - "title": "Мої Кошти", - "can_send": "Можна Надіслати", - "can_receive": "Можна Отримати", - "view_logs": "Переглянути Логи" + "refill_lnd_balance": "Поповнити баланс Lightning-гаманця", + "sameWalletAsInvoiceError": "Ви не можете оплатити інвойс тим самим гаманцем, яким його було створено.", + "title": "Керування коштами" }, "lndViewInvoice": { "additional_info": "Додаткова Інформація", "for": "Для:", - "lightning_invoice": "Lightning Рахунок", - "please_pay": "Будь ласка оплатіть", - "preimage": "Прообраз", - "sats": "sats.", + "lightning_invoice": "Lightning-інвойс", + "please_pay_between_and": "Будь ласка, сплатіть від {min} до {max}", + "please_pay": "Будь ласка, оплатіть", + "preimage": "Преімідж", + "sats": "сатоші.", + "date_time": "Дата та час", "wasnt_paid_and_expired": "Цей рахунок не було оплачено, термін його дії закінчився." }, "plausibledeniability": { "create_fake_storage": "Створити Зашифроване Сховище", - "create_password": "Придумайте пароль", - "create_password_explanation": "Пароль для фальшивого сховіща не має буті таким же як основній пароль", - "help": "При певних обставинах вас можуть змусити розкрити пароль. Щоб зберегти ваші монети в безпеці, Bluewallet може створити ще одне зашифроване сховище, з іншим паролем. Під тиском, ви можете розкрити третім особам цей пароль. Якщо ввести цей пароль у Bluewallet, розблокується 'фальшиве' сховище. Це виглядатиме правдоподібно для третіх осіб, але при цьому ваше основне сховище буде в безпеці.", - "help2": "Нове сховище буде повністю функціональним і ви навіть можете зберігати на ньому невелику кількість монет, щоб це виглядало правдоподібніше.", - "password_should_not_match": "Пароль для фальшивого сховища не може бути таким же як основний пароль.", - "passwords_do_not_match": "Паролі не збігаються, спробуйте ще раз.", - "retype_password": "Наберіть пароль ще раз", - "success": "Операція успішна", + "create_password_explanation": "Пароль для фальшивого сховища не має бути таким самим, як основний пароль.", + "help": "За певних обставин вас можуть змусити розкрити пароль. Щоб зберегти ваші монети в безпеці, BlueWallet може створити ще одне зашифроване сховище з іншим паролем. Під тиском ви можете розкрити третім особам цей пароль. Якщо ввести цей пароль у BlueWallet, розблокується «фальшиве» сховище. Це виглядатиме правдоподібно для третіх осіб, але при цьому ваше основне сховище залишиться в безпеці.", + "help2": "Нове сховище буде повністю функціональним, і ви навіть можете зберігати на ньому невелику кількість монет, щоб це виглядало правдоподібніше.", + "password_should_not_match": "Пароль для фальшивого сховища не може бути таким самим, як основний пароль.", "title": "Правдоподібне Заперечення" }, "pleasebackup": { - "ask_no": "Ні, я не маю", - "ask_yes": "Так, я маю", - "ok": "Ок, я записав", - "ok_lnd": "Ок, я зберіг", + "ask": "Чи зберегли ви резервну фразу вашого гаманця? Ця резервна фраза необхідна для доступу до ваших коштів, якщо ви втратите цей пристрій. Без резервної фрази ваші кошти будуть безповоротно втрачені.", + "ask_no": "Ні, не зберіг.", + "ask_yes": "Так, зберіг.", + "ok": "Ок, я записав.", + "ok_lnd": "Гаразд, я зберіг це.", + "text": "Будь ласка, знайдіть хвилинку і запишіть цю мнемонічну фразу на аркуші паперу.\nЦе ваша резервна копія, і ви зможете використати її для відновлення гаманця.", "text_lnd": "Будь ласка, збережіть цю резервну копію гаманця. Вона дозволить відновити гаманець у разі втрати.", - "title": "Гаманець було створено" + "title": "Ваш гаманець створено." }, "receive": { "details_create": "Створити", "details_label": "Опис", - "details_share": "Поділитися", - "header": "Отримати" + "details_setAmount": "Отримання з сумою", + "details_share": "Поділитися...", + "address_not_found": "Не вдалося згенерувати адресу для отримання.", + "header": "Отримати", + "reset": "Скинути", + "maxSats": "Максимальна кількість - {max} сатоші", + "maxSatsFull": "Максимальна сума - {max} сатоші або {currency}", + "minSats": "Мінімальна кількість - {min} сатоші", + "minSatsFull": "Мінімальна сума — {min} сатоші або {currency}", + "qrcode_for_the_address": "QR-код для адреси", + "bip47_explanation": "Платіжні коди — це універсальна адреса, яка не розкриває адреси вашого гаманця. Не всі сервіси їх підтримують." }, "send": { + "provided_address_is_invoice": "Схоже, ця адреса належить інвойсу Lightning. Будь ласка, перейдіть до свого Lightning-гаманця, щоб оплатити цей інвойс.", "broadcastButton": "Транслювати", "broadcastError": "Помилка", + "broadcastNone": "Вставте hex-дані транзакції", "broadcastPending": "Очікування", "broadcastSuccess": "Успішно", "confirm_header": "Підтвердити", "confirm_sendNow": "Надіслати", "create_amount": "Сума", - "create_broadcast": "Відправити", + "create_broadcast": "Транслювати", + "create_copy": "Скопіювати та надіслати в мережу пізніше", "create_details": "Деталі", "create_fee": "Комісія", "create_memo": "Примітка", - "create_this_is_hex": "Це дані транзакції. Транзакція підписана і готова бути трансльована в мережу. Продовжити?", + "create_satoshi_per_vbyte": "Сатоші за vByte", + "create_this_is_hex": "Це hex транзакції — підписаний і готовий до трансляції в мережу.", "create_to": "Куди", "create_tx_size": "Розмір Транзакції", + "create_verify": "Перевірити на coinb.in", + "details_insert_contact": "Вставити контакт", "details_add_rec_add": "Додати Отримувача", "details_add_rec_rem": "Видалити Отримувача", + "details_add_recc_rem_all_alert_description": "Ви впевнені, що хочете видалити всіх отримувачів?", + "details_add_rec_rem_all": "Видалити всіх отримувачів", + "details_recipients_title": "Отримувачі", + "details_recipient_title": "Отримувач #{number} з #{total}", + "please_complete_recipient_title": "Незаповнений отримувач", + "please_complete_recipient_details": "Будь ласка, заповніть дані отримувача #{number}, перш ніж додавати нового.", "details_address": "Адреса", - "details_address_field_is_not_valid": "Поле \"адреса\" не валідне.", + "details_address_field_is_not_valid": "Поле «адреса» не валідне.", + "details_adv_fee_bump": "Дозволити збільшення комісії", "details_adv_full": "Використати Увесь Баланс", - "details_adv_full_sure": "Ви впевнені, що хочете використати весь баланс свого гаманця для цієї трансакції?", - "details_adv_full_sure_frozen": "Ви впевнені, що хочете використати весь баланс свого гаманця для цієї трансакції? Зауважте, що заморожені монети не включені.", + "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_not_valid": "Поле «сума» не валідне.", "details_amount_field_is_less_than_minimum_amount_sat": "Вказана сума занадто мала. Будь ласка, введіть суму більше 500 sats.", "details_create": "Створити рахунок", - "details_fee_field_is_not_valid": "Поле \"комісія\" не валідне.", - "details_next": "Наступний", + "details_error_decode": "Не вдалося декодувати Bitcoin-адресу", + "details_fee_field_is_not_valid": "Поле «комісія» не валідне.", + "details_frozen": "{amount} BTC заморожено.", + "details_next": "Далі", + "details_no_signed_tx": "Обраний файл не містить транзакції, яку можна імпортувати.", "details_note_placeholder": "Примітка Платежу", "details_scan": "Сканувати", + "details_scan_hint": "Подвійний дотик, щоб сканувати або імпортувати адресу призначення", + "details_scan_error": "Помилка сканування", "details_total_exceeds_balance": "Сума транзакції перевищує доступний баланс.", + "details_total_exceeds_balance_frozen": "Сума надсилання перевищує доступний баланс. Зауважте, що заморожені монети не враховуються.", + "details_unrecognized_file_format": "Нерозпізнаний формат файлу", + "details_wallet_before_tx": "Щоб створити транзакцію, спочатку додайте Bitcoin-гаманець.", "dynamic_init": "Ініціалізація", - "dynamic_next": "Наступний", + "dynamic_next": "Далі", "dynamic_prev": "Попередній", "dynamic_start": "Почати", "dynamic_stop": "Зупинити", "fee_10m": "10хв", "fee_1d": "1д", "fee_3h": "3г", + "fee_custom": "Власна", + "insert_custom_fee": "Введіть комісію", "fee_fast": "Швидко", "fee_medium": "Середня", + "fee_replace_minvb": "Загальна ставка комісії (сатоші за vByte), яку ви хочете сплатити, повинна бути більшою за {min} sat/vByte.", "fee_satvbyte": "в sat/vByte", "fee_slow": "Повільно", "header": "Надіслати", @@ -149,209 +186,474 @@ "input_total": "Загалом:", "permission_camera_message": "Нам потрібен дозвіл на використання камери.", "psbt_sign": "Підписати транзакцію", + "invalid_psbt": "Надано недійсний PSBT.", "open_settings": "Відкрити Налаштування", - "permission_storage_later": "Запитати мене пізніше", - "permission_storage_message": "BlueWallet потребує вашого дозволу на доступ до сховища, щоб зберегти цей файл.", "permission_storage_denied_message": "BlueWallet не може зберегти цей файл. Відкрийте налаштування пристрою та дайте дозвіл на зберігання.", "permission_storage_title": "Дозвіл доступу до сховища", "psbt_clipboard": "Копіювати в буфер обміну", "psbt_this_is_psbt": "Це частково підписана біткойн-транзакція (PSBT). Завершіть підписання за допомогою апаратного гаманця.", "psbt_tx_export": "Експортувати у файл", - "no_tx_signing_in_progress": "Підписання транзакції в процесі.", + "no_tx_signing_in_progress": "Немає транзакцій, що підписуються.", "outdated_rate": "Останнє оновлення ціни: {date}", "psbt_tx_open": "Відкрити Підписану Транзакцію", "psbt_tx_scan": "Сканувати Підписану Транзакцію", - "qr_error_no_qrcode": "Нам не вдалося знайти QR-код у вибраному зображенні. Переконайтеся, що зображення містить лише QR-код без додаткового вмісту, наприклад тексту чи кнопок.", + "qr_error_no_qrcode": "Не вдалося знайти дійсний QR-код у вибраному зображенні. Переконайтеся, що зображення містить лише QR-код без додаткового вмісту, такого як текст чи кнопки.", "reset_amount": "Очистити Суму", "reset_amount_confirm": "Бажаєте очистити суму?", "success_done": "Готово", + "txSaved": "Файл транзакції ({filePath}) збережено.", + "file_saved_at_path": "Файл ({filePath}) збережено.", + "cant_send_to_silentpayment_adress": "Цей гаманець не може надсилати на адреси SilentPayment", + "cant_send_to_bip47": "Цей гаманець не може надсилати на платіжні коди BIP47", + "cant_find_bip47_notification": "Спочатку додайте цей платіжний код до контактів", "problem_with_psbt": "Проблема з PSBT" }, "settings": { "about": "Про програму", + "about_awesome": "Створено з використанням чудового", "about_backup": "Завжди створюйте резервні копії ключів!", "about_free": "BlueWallet — це безкоштовний проект з відкритим кодом. Створено користувачами Bitcoin.", "about_license": "MIT Ліцензія", "about_release_notes": "Інформація про реліз", "about_review": "Залиште нам відгук", + "performance_score": "Оцінка продуктивності: {num}", + "run_performance_test": "Перевірити продуктивність", "about_selftest": "Запустити самоперевірку", + "block_explorer_invalid_custom_url": "Надано недійсну URL-адресу. Будь ласка, введіть дійсну URL-адресу, що починається з http:// або https://.", + "about_selftest_electrum_disabled": "Самоперевірка недоступна в автономному режимі Electrum. Будь ласка, вимкніть автономний режим і спробуйте ще раз.", + "about_selftest_ok": "Усі внутрішні тести пройдено успішно. Гаманець працює добре.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord Сервер", "about_sm_telegram": "Telegram канал", - "about_sm_twitter": "Слідкуйте за нами у Twitter", - "advanced_options": "Розширені Налаштування", + "privacy_temporary_screenshots": "Дозволити запис екрана", + "privacy_temporary_screenshots_instructions": "Захист від запису екрана буде тимчасово вимкнено, що дозволить робити знімки та запис екрана. Захист автоматично активується після закриття та повторного відкриття BlueWallet.", "biometrics": "Біометрія", - "biom_conf_identity": "Будь ласка підтвердіть свою особистість.", + "biometrics_no_longer_available": "Налаштування вашого пристрою змінилися та більше не відповідають обраним налаштуванням безпеки в застосунку. Будь ласка, увімкніть біометрію або код доступу заново, а потім перезапустіть застосунок, щоб застосувати ці зміни.", + "biom_10times": "Ви ввели пароль 10 разів. Бажаєте скинути сховище? Це видалить усі гаманці та розшифрує ваше сховище.", + "biom_conf_identity": "Будь ласка, підтвердіть свою особистість.", + "biom_no_passcode": "На вашому пристрої не налаштовано код доступу чи біометрію. Щоб продовжити, налаштуйте код доступу або біометрію в системних налаштуваннях.", + "biom_remove_decrypt": "Усі ваші гаманці буде видалено, а сховище — розшифровано. Ви впевнені, що хочете продовжити?", "currency": "Валюта", - "default_wallets": "Переглянути Всі Гаманці", + "currency_source": "Курс отримано з", + "currency_fetch_error": "Сталася помилка під час отримання курсу для обраної валюти.", + "default_title": "При запуску", + "donate": "Підтримати", + "donate_description": "Допоможіть нам зберегти Blue безкоштовним!", "electrum_connected": "З'єднаний", "electrum_connected_not": "Не З'єднаний", + "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_saved": "Ваші зміни успішно збережено. Для застосування змін може знадобитися перезапуск BlueWallet.", + "set_electrum_server_as_default": "Встановити {server} як стандартний сервер Electrum?", + "set_lndhub_as_default": "Встановити {url} як стандартний сервер LNDhub?", "electrum_settings_server": "Electrum Сервер", "electrum_status": "Статус", - "electrum_clear_alert_title": "Очистити історію?", - "electrum_clear_alert_cancel": "Відмінити", - "electrum_clear_alert_ok": "Ок", - "electrum_select": "Вибрати", - "electrum_clear": "Очистити", + "electrum_preferred_server": "Бажаний сервер", + "electrum_preferred_server_description": "Введіть сервер, який ваш гаманець буде використовувати для всіх дій з Bitcoin. Після встановлення гаманець використовуватиме виключно цей сервер для перевірки балансів, надсилання транзакцій та отримання мережевих даних. Переконайтеся, що ви довіряєте цьому серверу, перш ніж встановлювати його.", + "electrum_unable_to_connect": "Не вдалося з'єднатися з {server}.", + "electrum_history": "Історія", + "electrum_reset_to_default": "Це дозволить BlueWallet випадково обирати сервер зі списку серверів.", + "electrum_reset": "Скинути до стандартних", + "electrum_reset_to_default_and_clear_history": "Скинути до стандартних та очистити історію", + "encrypt_decrypt": "Розшифрувати сховище", + "encrypt_decrypt_q": "Ви впевнені, що хочете розшифрувати своє сховище? Це дозволить отримати доступ до ваших гаманців без пароля.", + "encrypt_enc_and_pass": "Захищено паролем", + "encrypt_storage_explanation_headline": "Увімкнути шифрування сховища", + "encrypt_storage_explanation_description_line1": "Увімкнення шифрування сховища додає додатковий рівень захисту вашому застосунку, забезпечуючи безпеку зберігання даних на пристрої. Це ускладнює доступ до вашої інформації без дозволу.", + "encrypt_storage_explanation_description_line2": "Однак важливо знати, що це шифрування захищає лише доступ до гаманців, що зберігаються у keychain пристрою. Воно не встановлює пароль чи додатковий захист на самі гаманці.", + "i_understand": "Я розумію", + "block_explorer": "Оглядач блоків", + "block_explorer_preferred": "Використовувати бажаний оглядач блоків", + "block_explorer_error_saving_custom": "Помилка збереження бажаного оглядача блоків", "encrypt_title": "Безпека", "encrypt_tstorage": "Сховище", + "encrypt_use": "Використовувати {type}", + "set_as_preferred": "Встановити як бажаний", + "set_as_preferred_electrum": "Встановлення {host}:{port} як бажаного сервера вимкне випадкове з'єднання з запропонованим сервером.", + "encrypted_feature_disabled": "Цю функцію не можна використовувати, коли увімкнено шифрування сховища.", + "encrypt_use_expl": "{type} буде використовуватися для підтвердження вашої особи перед здійсненням транзакції, розблокуванням, експортом чи видаленням гаманця.", + "biometrics_fail": "Якщо {type} не увімкнено або не вдається розблокувати, ви можете використати код доступу пристрою як альтернативу.", "general": "Загальні", - "general_adv_mode": "Enable advanced mode", + "general_continuity": "Безперервність", + "general_continuity_e": "Коли увімкнено, ви зможете переглядати обрані гаманці та транзакції на інших пристроях Apple, підключених до iCloud.", + "groundcontrol_explanation": "GroundControl — це безкоштовний сервер push-сповіщень з відкритим кодом для Bitcoin-гаманців. Ви можете встановити власний сервер GroundControl та вказати його URL тут, щоб не залежати від інфраструктури BlueWallet. Залиште порожнім, щоб використовувати стандартний сервер GroundControl.", "header": "Налаштування", "language": "Мова", + "last_updated": "Останнє оновлення", + "language_isRTL": "Для застосування орієнтації мови потрібен перезапуск BlueWallet.", + "license": "Ліцензія", + "lightning_error_lndhub_uri": "Недійсний URI LNDhub", + "lightning_error_lndhub_uri_tor": "Недійсний URI LNDhub. Переконайтеся, що застосунок Orbot з'єднаний, і спробуйте ще раз.", "lightning_saved": "Ваші зміни успішно збережено.", "lightning_settings": "Налаштування Lightning", - "tor_settings": " Налаштування Tor", + "lightning_settings_explain": "Щоб під'єднатися до вашої власної ноди LND, встановіть LNDhub і вкажіть його URL тут, у налаштуваннях. Зауважте, що лише гаманці, створені після збереження змін, з'єднуватимуться з указаним LNDhub.", + "lndhub_github": "Репозиторій GitHub", "network": "Мережа", + "network_broadcast": "Транслювати транзакцію", "network_electrum": "Electrum Сервер", + "electrum_suggested_description": "Якщо бажаний сервер не встановлено, для використання буде випадково обрано запропонований сервер.", + "not_a_valid_uri": "Недійсний URI", "notifications": "Сповіщення", + "open_link_in_explorer": "Відкрити посилання в оглядачі", "password": "Пароль", - "password_explain": "Придумайте пароль для розшифровки сховища.", - "passwords_do_not_match": "Паролі не збігаються.", + "password_explain": "Введіть пароль, який ви будете використовувати для розблокування свого сховища.", "plausible_deniability": "Правдоподібне заперечення...", "privacy": "Приватність", + "privacy_read_clipboard": "Зчитувати буфер обміну", "privacy_system_settings": "Системні Налаштування", "privacy_quickactions": "Ярлики Гаманця", + "privacy_quickactions_explanation": "Натисніть та утримуйте іконку застосунку BlueWallet, щоб швидко переглянути баланс гаманця.", + "privacy_clipboard_explanation": "Надавати ярлики, якщо в буфері обміну знайдено адресу або інвойс.", "privacy_do_not_track": "Вимкнути Аналітику", - "push_notifications": "Пуш Сповіщення", - "retype_password": "Наберіть пароль ще раз", + "privacy_do_not_track_explanation": "Інформація про продуктивність і надійність не надсилатиметься для аналізу.", + "rate": "Оцінити", + "push_notifications_explanation": "Увімкнувши сповіщення, токен вашого пристрою буде надіслано на сервер разом з адресами гаманців та ідентифікаторами транзакцій для всіх гаманців і транзакцій, створених після увімкнення сповіщень. Токен пристрою використовується для надсилання сповіщень, а інформація про гаманець дозволяє нам повідомляти вас про вхідні Bitcoin або підтвердження транзакцій.\n\nПередається лише інформація після того, як ви увімкнули сповіщення — нічого з попереднього не збирається.\n\nВимкнення сповіщень видалить всю цю інформацію з сервера. Окрім того, видалення гаманця із застосунку також видалить пов'язану з ним інформацію з сервера.", + "selfTest": "Самоперевірка", "save": "Зберегти", "saved": "Збережено", + "success_transaction_broadcasted": "Вашу транзакцію успішно надіслано в мережу!", "total_balance": "Загальний Баланс", + "total_balance_explanation": "Відображати загальний баланс усіх ваших гаманців у віджетах головного екрана.", "widgets": "Віджети", "tools": "Інструменти" }, "notifications": { - "ask_me_later": "Запитати мене пізніше" + "would_you_like_to_receive_notifications": "Чи хотіли б ви отримувати сповіщення про вхідні платежі?", + "notifications_subtitle": "Вхідні платежі та підтвердження транзакцій", + "no_and_dont_ask": "Ні, і більше не питати.", + "permission_denied_message": "Ви відмовили в дозволі надсилати вам сповіщення. Якщо ви хочете отримувати сповіщення, увімкніть їх у налаштуваннях пристрою." }, "transactions": { - "copy_link": "Копіювати Посилання", + "cancel_explain": "Ми замінимо цю транзакцію іншою, яка платить вам і має вищу комісію. Це фактично скасовує поточну транзакцію. Це називається RBF — Replace by Fee.", + "cancel_no": "Цю транзакцію неможливо замінити.", + "cancel_title": "Скасувати цю транзакцію (RBF)", + "transaction_loading_error": "Виникла проблема із завантаженням транзакції. Будь ласка, спробуйте пізніше.", + "transaction_not_available": "Транзакція недоступна", + "confirmations_lowercase": "{confirmations} підтверджень", + "expand_note": "Розгорнути нотатку", "cpfp_create": "Створити", + "cpfp_exp": "Ми створимо ще одну транзакцію, яка витрачає вашу непідтверджену транзакцію. Загальна комісія буде вищою за початкову, тож її повинні швидше включити в блок. Це називається CPFP — Child Pays for Parent.", + "cpfp_no_bump": "Цю транзакцію неможливо прискорити.", + "cpfp_title": "Збільшити комісію (CPFP)", "details_balance_hide": "Приховати Баланс", + "details_balance_show": "Показати баланс", "details_copy": "Копіювати", - "details_copy_amount": "Копіювати Суму", + "details_copy_block_explorer_link": "Скопіювати посилання на оглядач блоків", "details_copy_note": "Копіювати Нотатку", - "details_from": "Від", - "details_show_in_block_explorer": "Show in block explorer", + "details_copy_txid": "Скопіювати ID транзакції", + "details_inputs": "Входи", + "details_outputs": "Виходи", + "date": "Дата", + "details_received": "Отримано", + "details_view_in_browser": "Переглянути в браузері", "details_title": "Деталі транзакції", - "details_to": "Кому", + "incoming_transaction": "Вхідна транзакція", + "outgoing_transaction": "Вихідна транзакція", + "expired_transaction": "Прострочена транзакція", + "pending_transaction": "Транзакція в очікуванні", + "offchain": "оф-чейн", + "onchain": "он-чейн", + "details_to": "Вихід", + "enable_offline_signing": "Цей гаманець не використовується разом з автономним підписанням. Бажаєте увімкнути його зараз?", + "list_conf": "Підтв.: {number}", "pending": "Очікування", + "pending_with_amount": "Очікує {amt1} ({amt2})", "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: приблизно за 10 хвилин", + "eta_3h": "ETA: приблизно за 3 години", + "eta_1d": "ETA: приблизно за 1 день", "list_title": "Транзакції", + "list_title_sent": "Надіслано", + "list_title_received": "Отримано", + "transaction": "Деталі транзакції", + "open_url_error": "Не вдалося відкрити посилання у стандартному браузері. Будь ласка, змініть стандартний браузер і спробуйте ще раз.", + "rbf_explain": "Ми замінимо цю транзакцію іншою з вищою комісією, щоб її швидше включили в блок. Це називається RBF — Replace by Fee.", + "rbf_title": "Прискорити (RBF)", + "status_bump": "Прискорити", + "status_cancel": "Скасувати", "transactions_count": "Кількість Транзакцій", - "updating": "Оновлення..." + "txid": "ID транзакції", + "updating": "Оновлення...", + "watchOnlyWarningTitle": "Попередження безпеки", + "watchOnlyWarningDescription": "Будьте обережні з шахраями, які часто використовують гаманці «лише для перегляду», щоб обдурити користувачів. Такі гаманці не дозволяють контролювати чи надсилати кошти — вони лише дозволяють переглядати баланс.", + "custom_fee_warning_title": "Попередження", + "custom_fee_warning_description": "Комісії менше 1 sat/vB є дійсними, але можуть не передаватися мережею через політики нод.", + "details_eta_analyzing": "Аналізуємо...", + "details_sent": "Надіслано", + "details_section": "Деталі", + "details_explorer": "оглядач", + "details_network_fee": "Комісія мережі", + "details_to_address": "Кому", + "details_id": "ID", + "details_note": "Нотатка", + "details_add_note": "додати", + "details_advanced": "Розширені", + "details_fee_rate": "Ставка комісії", + "details_size": "Розмір", + "details_virtual_size": "Віртуальний розмір", + "details_tx_hex": "Hex транзакції", + "details_inputs_count": "Входи ({count})", + "details_outputs_count": "Виходи ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", - "add_bitcoin_explain": "Простий і потужний Біткойн гаманець", + "add_bitcoin_explain": "Простий і потужний Bitcoin-гаманець", "add_create": "Створити", + "total_balance": "Загальний Баланс", + "add_entropy_reset_title": "Скинути ентропію", + "add_entropy_reset_message": "Зміна типу гаманця скине поточну ентропію. Бажаєте продовжити?", + "add_entropy": "Ентропія", + "add_entropy_bytes": "{bytes} байтів ентропії", + "add_entropy_generated": "{gen} байтів згенерованої ентропії", + "add_entropy_provide": "Надати ентропію через кидки кубика", + "add_entropy_remain": "{gen} байтів згенерованої ентропії. Решта {rem} байтів буде отримана від системного генератора випадкових чисел.", "add_import_wallet": "Імпортувати гаманець", "add_lightning": "Lightning", - "add_lndhub": "Підключитися до вашого LNDHub", + "add_lightning_explain": "Для витрат із миттєвими транзакціями", + "add_lndhub": "З'єднатися з вашим LNDhub", + "add_lndhub_error": "Надана адреса ноди є недійсною нодою LNDhub.", "add_lndhub_placeholder": "Адреса Вашої Ноди", "add_placeholder": "мій перший гаманець", "add_title": "Додати Гаманець", "add_wallet_name": "ім'я гаманця", "add_wallet_type": "Тип Гаманця", - "balance": "Баланс", + "add_wallet_seed_length": "Довжина seed-фрази", + "add_wallet_seed_length_12": "12 слів", + "add_wallet_seed_length_24": "24 слова", + "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": "Цей гаманець має баланс. Перш ніж продовжити, зверніть увагу, що ви не зможете відновити кошти без seed-фрази цього гаманця. Щоб уникнути випадкового видалення, введіть баланс гаманця, що дорівнює {balance} сатоші.", "details_delete": "Видалити", "details_delete_wallet": "Видалити Гаманець", + "details_derivation_path": "шлях деривації", + "details_display": "Відображати на головному екрані", "details_export_backup": "Експорт / резервна копія", - "details_no_cancel": "Ні, відмінити", - "details_save": "Зберегти", + "details_export_history": "Експортувати історію в CSV", + "details_master_fingerprint": "Відбиток майстер-ключа", + "details_multisig_type": "multisig", "details_show_xpub": "Показати XPUB Гаманця", "details_show_addresses": "Показати адреси", "details_title": "Гаманець", + "wallets": "Гаманці", + "swipe_balance_hide": "Приховати", + "swipe_balance_show": "Показати", + "drag_to_reorder": "Перетягніть, щоб змінити порядок", + "clear_search": "Очистити пошук", "details_type": "Тип", - "details_wallet_updated": "Гаманець оновлено", + "details_use_with_hardware_wallet": "Використовувати з апаратним гаманцем", "details_yes_delete": "Так, видалити", "enter_bip38_password": "Введіть пароль для розшифрування", "export_title": "Експорт Гаманця", "import_do_import": "Імпорт", - "import_error": "Невдача. Це взашалі валідно?", - "import_explanation": "Мнемоніка, приватний ключ, чи будь що. BlueWallet спробує вгадати вірний формат.", - "import_imported": "імпортовано", + "import_passphrase": "Кодова фраза", + "import_passphrase_title": "Кодова фраза", + "import_passphrase_message": "Введіть кодову фразу, якщо ви її використовували", + "import_error": "Не вдалося імпортувати. Переконайтеся, що надані дані дійсні.", + "import_explanation": "Введіть seed-фразу, публічний ключ, WIF або будь-що інше. BlueWallet спробує визначити правильний формат і імпортувати ваш гаманець.", + "import_imported": "Імпортовано", "import_scan_qr": "Імпортувати файл чи сканувати QR-код?", "import_success": "Ваш гаманець було успішно імпортовано.", + "import_success_watchonly": "Ваш гаманець успішно імпортовано. УВАГА: це гаманець лише для перегляду — з нього НЕ можна витрачати.", "import_search_accounts": "Пошук облікових записів", - "import_title": "імпорт", - "import_derivation_found": "знайдено", - "import_derivation_found_not": "не знайдено", - "import_derivation_loading": "завантаження...", - "import_derivation_unknown": "невідомий", + "import_title": "Імпорт", + "learn_more": "Дізнатися більше", + "import_discovery_title": "Виявлення", + "import_discovery_subtitle": "Оберіть виявлений гаманець", + "import_discovery_derivation": "Використати власний шлях деривації", + "import_discovery_no_wallets": "Гаманців не знайдено.", + "import_discovery_offline": "BlueWallet наразі в автономному режимі. У цьому режимі неможливо перевірити існування гаманця, тому вам доведеться обрати правильний вручну", + "import_derivation_found": "Знайдено", + "import_derivation_found_not": "Не знайдено", + "import_derivation_loading": "Завантаження...", + "import_derivation_subtitle": "Введіть власний шлях деривації, і ми спробуємо виявити ваш гаманець.", + "import_derivation_title": "Шлях деривації", + "import_derivation_unknown": "Невідомо", + "import_wrong_path": "Неправильний шлях деривації", "list_create_a_button": "Додати зараз", "list_create_a_wallet": "Додати гаманець", - "list_empty_txs1": "Транзакціі з'являться тут.", + "list_create_a_wallet_text": "Це безкоштовно, і ви можете створити \nстільки, скільки забажаєте.", + "list_empty_txs1": "Тут з'являться ваші транзакції.", + "list_empty_txs1_lightning": "Lightning-гаманець варто використовувати для щоденних транзакцій. Комісії несправедливо низькі, а швидкість блискавична.", "list_empty_txs2": "Почніть зі свого гаманця.", + "list_empty_txs2_lightning": "\nЩоб почати ним користуватися, торкніться «Мої Кошти» та поповніть баланс.", "list_latest_transaction": "Остання Транзакція", "list_long_choose": "Виберіть Фото", + "paste_from_clipboard": "Вставити", + "import_file": "Імпортувати Файл", "list_long_scan": "Сканувати QR-код", "list_title": "Гаманці", "list_tryagain": "Спробуйте ще раз", + "no_ln_wallet_error": "Перш ніж оплачувати інвойс Lightning, спочатку додайте Lightning-гаманець.", + "looks_like_bip38": "Схоже, це приватний ключ, захищений паролем (BIP38).", + "manage_title": "Керування гаманцями", + "no_results_found": "Нічого не знайдено.", "please_continue_scanning": "Продовжуйте сканування.", + "select_no_bitcoin": "Наразі немає доступних Bitcoin-гаманців.", + "select_no_bitcoin_exp": "Bitcoin-гаманець потрібен для поповнення Lightning-гаманців. Будь ласка, створіть або імпортуйте його.", "select_wallet": "Обрати Гаманець", - "xpub_copiedToClipboard": "Зкопіювано", "pull_to_refresh": "Потягніть щоб оновити", - "xpub_title": "XPUB Гаманця" + "warning_do_not_disclose": "Ніколи не діліться інформацією нижче", + "scan_import": "Скануйте цей QR-код, щоб імпортувати ваш гаманець в іншому застосунку.", + "write_down_header": "Створити ручну резервну копію", + "write_down": "Запишіть і надійно збережіть ці слова. Використовуйте їх, щоб відновити ваш гаманець пізніше.", + "wallet_type_this": "Тип цього гаманця — {type}.", + "share_number": "Поділитися {number}", + "copy_ln_url": "Скопіюйте та надійно збережіть цей URL, щоб відновити гаманець пізніше.", + "copy_ln_public": "Скопіюйте та надійно збережіть цю інформацію, щоб відновити гаманець пізніше.", + "add_ln_wallet_first": "Спочатку додайте Lightning-гаманець.", + "identity_pubkey": "Публічний ключ ідентичності", + "xpub_title": "XPUB Гаманця", + "manage_wallets_search_placeholder": "Пошук гаманців, адрес, транзакцій та приміток", + "more_info": "Докладніше", + "details_delete_wallet_error_message": "Виникла проблема з підтвердженням, чи цей гаманець було видалено зі сповіщень — це може бути через проблему з мережею або поганий зв'язок. Якщо ви продовжите, ви все ще можете отримувати сповіщення про транзакції, пов'язані з цим гаманцем, навіть після його видалення.", + "details_delete_anyway": "Видалити в будь-якому разі" + }, + "total_balance_view": { + "display_in_bitcoin": "Відображати у Bitcoin", + "hide": "Приховати", + "display_in_sats": "Відображати у сатоші", + "display_in_fiat": "Відображати у {currency}", + "title": "Загальний Баланс", + "explanation": "Перегляньте загальний баланс усіх гаманців на екрані огляду." }, "multisig": { - "multisig_vault": "Сховище", + "multisig_vault": "Multisig сейф", + "default_label": "Multisig сейф", + "multisig_vault_explain": "Найкраща безпека для великих сум", + "provide_signature": "Надати підпис", + "provide_signature_details": "Використайте свій пристрій та гаманець, де зберігається ключ, щоб підписати цю транзакцію", + "provide_signature_details_bluewallet": "У BlueWallet перейдіть до меню екрана Надіслати та оберіть ", + "provide_signature_next_steps": "Сканувати або імпортувати підписану транзакцію", + "provide_signature_next_steps_details": "Щойно ваш гаманець успішно підпише транзакцію, відскануйте наданий QR-код або імпортуйте відповідний файл, а потім перевірте всі деталі транзакції перед її трансляцією в мережу.", + "vault_key": "Ключ сейфа {number}", + "required_keys_out_of_total": "Необхідно ключів із загальної кількості", "fee": "Комісія: {number}", "fee_btc": "{number} BTC", "confirm": "Підтвердити", "header": "Надіслати", - "share": "Поділитися", + "share": "Поділитися...", "view": "Переглянути", + "shared_key_detected": "Спільний співпідписант", + "shared_key_detected_question": "З вами поділилися співпідписантом — бажаєте його імпортувати?", "manage_keys": "Керувати Ключами", "how_many_signatures_can_bluewallet_make": "скільки підписів може зробити BlueWallet", - "signatures_required_to_spend": "Потрібно {number} підписи", + "signatures_required_to_spend": "Потрібно підписів: {number}", "signatures_we_can_make": "може зробити {number}", "scan_or_import_file": "Відсканувати або імпортувати файл", + "export_coordination_setup": "Експортувати координаційні налаштування", + "cosign_this_transaction": "Підписати цю транзакцію спільно?", "lets_start": "Давайте розпочнемо", "create": "Створити", "native_segwit_title": "Найкраща практика", + "wrapped_segwit_title": "Найкраща сумісність", + "legacy_title": "Legacy", "co_sign_transaction": "Підписати транзакцію", "what_is_vault": "Vault це", + "what_is_vault_numberOfWallets": " {m}-з-{n} multisig ", "what_is_vault_wallet": "гаманець.", "vault_advanced_customize": "Налаштування Vault", + "needs": "Йому потрібно", + "what_is_vault_description_number_of_vault_keys": " {m} ключів сейфа ", + "what_is_vault_description_to_spend": "для витрачання та третій, який ви \nможете використати як резервну копію.", + "what_is_vault_description_to_spend_other": "для витрачання.", + "quorum": "{m} з {n} (кворум)", + "quorum_header": "Кворум", "of": "з", "wallet_type": "Тип Гаманця", + "invalid_mnemonics": "Здається, ця мнемонічна фраза недійсна.", + "invalid_cosigner": "Недійсні дані співпідписанта", + "not_a_multisignature_xpub": "Це не XPUB від multisig-гаманця!", + "invalid_cosigner_format": "Некоректний співпідписант: це не співпідписант для формату {format}.", "create_new_key": "Створити Новий", "scan_or_open_file": "Відсканувати QR-код або імпортувати файл", + "i_have_mnemonics": "У мене є seed для цього ключа.", + "type_your_mnemonics": "Введіть seed, щоб імпортувати існуючий ключ сейфа.", + "this_is_cosigners_xpub": "Це XPUB співпідписанта — готовий до імпорту в інший гаманець. Ним можна безпечно ділитися.", + "this_is_cosigners_xpub_airdrop": "Якщо ви ділитеся через AirDrop, отримувачі повинні бути на екрані координації.", + "wallet_key_created": "Ваш ключ сейфа створено. Знайдіть хвилину, щоб надійно зробити резервну копію своєї мнемонічної seed-фрази.", + "are_you_sure_seed_will_be_lost": "Ви впевнені? Вашу мнемонічну seed-фразу буде втрачено, якщо ви не маєте резервної копії.", + "forget_this_seed": "Забути цю 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": "Як працюють Multisig-сейфи: поради та хитрощі", + "ms_help_text": "Гаманець із кількома ключами — для підвищеної безпеки чи спільного зберігання", + "ms_help_title1": "Рекомендується кілька пристроїв.", + "ms_help_1": "Сейф працює з іншими застосунками BlueWallet та сумісними з PSBT гаманцями, такими як Electrum, Specter, Coldcard, Cobo Vault тощо.", + "ms_help_title2": "Редагування ключів", + "ms_help_2": "Ви можете створити всі ключі сейфа на цьому пристрої та видалити чи редагувати їх пізніше. Зберігання всіх ключів на одному пристрої має безпеку, еквівалентну звичайному Bitcoin-гаманцю.", "ms_help_title3": "Резервні Копії Vault", - "ms_help_title5": "Enable advanced mode" + "ms_help_3": "У параметрах гаманця ви знайдете резервну копію сейфа та копію лише для перегляду. Ця резервна копія — мапа до вашого гаманця. Вона важлива для відновлення гаманця у разі втрати однієї з seed-фраз.", + "ms_help_title4": "Імпорт сейфів", + "ms_help_4": "Щоб імпортувати multisig, використайте файл резервної копії та функцію Імпорт. Якщо у вас є лише seed-фрази та XPUB, ви можете скористатися окремою кнопкою Імпорт під час створення ключів сейфа.", + "ms_help_title5": "Розширений режим", + "ms_help_5": "За замовчуванням BlueWallet згенерує сейф 2-з-3. Щоб створити інший кворум або змінити тип адреси, активуйте розширений режим у Налаштуваннях." }, "is_it_my_address": { + "title": "Це моя адреса?", + "owns": "{label} володіє {address}", + "enter_address": "Введіть адресу", + "check_address": "Перевірити адресу", + "no_wallet_owns_address": "Жоден з доступних гаманців не володіє вказаною адресою.", "view_qrcode": "Переглянути QR-код" }, + "autofill_word": { + "title": "Останнє слово seed-фрази", + "enter": "Введіть вашу часткову мнемонічну фразу", + "generate_word": "Згенерувати останнє слово", + "error": "Введене не є частковою мнемонічною фразою з 11 чи 23 слів. Будь ласка, спробуйте ще раз." + }, "cc": { "change": "Змінити", "coins_selected": "Монет Вибрано ({number})", - "empty": "На даний момент у цьому гаманці немає монет.", + "selected_summ": "{value} обрано", + "empty": "У цього гаманця наразі немає монет.", "freeze": "Заморозити", "freezeLabel": "Заморозити", "freezeLabel_un": "Розморозити", "header": "Керування Монетою", "use_coin": "Використати Монету", - "use_coins": "Використовувати Монети" + "use_coins": "Використовувати Монети", + "tip": "Ця функція дозволяє переглядати, маркувати, заморожувати або обирати монети для покращеного керування гаманцем. Ви можете обирати кілька монет, торкаючись кольорових кружечків.", + "sort_asc": "За зростанням", + "sort_desc": "За спаданням", + "sort_height": "Висота", + "sort_value": "Значення", + "sort_label": "Мітка", + "sort_status": "Статус", + "sort_by": "Сортувати за" }, "units": { "BTC": "BTC", "MAX": "Макс", - "sat_vbyte": "sat/vByte" + "sat_vbyte": "sat/vByte", + "sats": "сатоші" }, "addresses": { + "copy_private_key": "Скопіювати приватний ключ", + "sensitive_private_key": "Попередження: приватні ключі надзвичайно чутливі. Продовжити?", + "sign_title": "Підписати/перевірити повідомлення", + "sign_help": "Тут ви можете створити або перевірити криптографічний підпис на основі Bitcoin-адреси.", "sign_sign": "Підписати", "sign_verify": "Перевірити", + "sign_signature_correct": "Перевірку успішно виконано!", + "sign_signature_incorrect": "Перевірка не вдалася!", "sign_placeholder_address": "Адреса", "sign_placeholder_message": "Повідомлення", "sign_placeholder_signature": "Підпис", @@ -360,5 +662,43 @@ "type_receive": "Отримати", "type_used": "Використаний", "transactions": "Транзакцій" + }, + "lnurl_auth": { + "register_question_part_1": "Бажаєте зареєструвати обліковий запис на", + "register_question_part_2": "за допомогою вашого Lightning-гаманця?", + "register_answer": "Ви успішно зареєстрували обліковий запис на {hostname}!", + "login_question_part_1": "Бажаєте увійти на", + "login_question_part_2": "за допомогою вашого Lightning-гаманця?", + "login_answer": "Ви успішно увійшли на {hostname}!", + "link_question_part_1": "Бажаєте прив'язати ваш обліковий запис на", + "link_question_part_2": "до вашого Lightning-гаманця?", + "link_answer": "Ваш Lightning-гаманець успішно прив'язано до облікового запису на {hostname}!", + "auth_question_part_1": "Бажаєте автентифікуватися на", + "auth_question_part_2": "за допомогою вашого Lightning-гаманця?", + "auth_answer": "Ви успішно автентифікувалися на {hostname}!", + "could_not_auth": "Не вдалося автентифікувати вас на {hostname}.", + "authenticate": "Автентифікувати" + }, + "bip47": { + "payment_code": "Платіжний код", + "contacts": "Контакти", + "bip47_explain": "Багаторазовий код, яким можна ділитися", + "bip47_explain_subtitle": "BIP47", + "purpose": "Багаторазовий код, яким можна ділитися (BIP47)", + "pay_this_contact": "Оплатити цьому контакту", + "rename_contact": "Перейменувати контакт", + "copy_payment_code": "Скопіювати платіжний код", + "hide_contact": "Приховати контакт", + "rename": "Перейменувати", + "provide_name": "Введіть нове ім'я для цього контакту", + "add_contact": "Додати контакт", + "provide_payment_code": "Введіть платіжний код", + "invalid_pc": "Недійсний платіжний код", + "notification_tx_unconfirmed": "Транзакція сповіщення ще не підтверджена, будь ласка, зачекайте", + "failed_create_notif_tx": "Не вдалося створити он-чейн транзакцію", + "onchain_tx_needed": "Потрібна он-чейн транзакція", + "notif_tx_sent": "Транзакцію сповіщення надіслано. Будь ласка, зачекайте на її підтвердження", + "notif_tx": "Транзакція сповіщення", + "not_found": "Платіжний код не знайдено" } } diff --git a/loc/vi_vn.json b/loc/vi_vn.json index 65a6483c895..37eab9f3e64 100644 --- a/loc/vi_vn.json +++ b/loc/vi_vn.json @@ -6,30 +6,30 @@ "clipboard": "Bảng tạm", "enter_password": "Nhập mật khẩu", "never": "Không bao giờ", - "disabled": "Bị vô hiệu hóa", "of": "{number} của {total}", "ok": "OK", "storage_is_encrypted": "Lưu trữ của bạn được mã hoá. Mật khẩu được yêu cầu để giải mã", "yes": "Có", "no": "Không", - "save": "Lưu", - "seed": "Hạt giống", + "seed": "Seed", "success": "Thành công", "wallet_key": "Khóa ví", - "invalid_animated_qr_code_fragment": "Phần mã hoạt hình QR không hợp lệ. Vui lòng thử lại.", - "file_saved": "Tập tin {filePath} đã được lưu vào {destination} của bạn.", - "downloads_folder": "Thư mục tải xuống", "close": "Đóng", "change_input_currency": "Thay tiền tệ đầu vào", "refresh": "Làm mới", - "more": "Thêm", - "pick_image": "Chọn hình ảnh từ thư viện", - "pick_file": "Chọn một tập tin", "enter_amount": "Nhập số tiền", - "qr_custom_input_button": "Chạm 10 lần để nhập đầu vào tùy chỉnh" - }, - "alert": { - "default": "Báo động" + "qr_custom_input_button": "Chạm 10 lần để nhập đầu vào tùy chỉnh", + "copied": "Đã sao chép!", + "discard_changes": "Bỏ các thay đổi?", + "discard_changes_explain": "Bạn có những thay đổi chưa được lưu. Bạn có chắc muốn bỏ chúng và rời khỏi màn hình này?", + "enter_url": "Nhập URL", + "save": "Lưu...", + "pick_image": "Chọn từ thư viện", + "pick_file": "Chọn tệp", + "unlock": "Mở khoá", + "port": "Cổng", + "ssl_port": "Cổng SSL", + "suggested": "Đề xuất" }, "azteco": { "codeIs": "Mã voucher của bạn là", @@ -38,97 +38,82 @@ "redeem": "Đổi phiếu vào ví", "redeemButton": "Đổi phiếu", "success": "Thành công", - "title": "Đồi phiếu voucher Azte.co" + "title": "Đổi phiếu voucher Azte.co", + "successMessage": "Đã đổi phiếu thành công! Tiền của bạn sẽ sớm đến ví Bitcoin của bạn." }, "entropy": { "save": "Lưu", "title": "Entropy", - "undo": "Hoàn tác" + "undo": "Hoàn tác", + "amountOfEntropy": "{bits} trong {limit} bit" }, "errors": { - "broadcast": "Phát sóng giao dịch đã thất bại.", + "broadcast": "Truyền giao dịch đã thất bại.", "error": "Lỗi", "network": "Lỗi mạng" }, "lnd": { - "active": "Kích hoạt", - "inactive": "Không kích hoạt", - "channels": "Các kênh", - "no_channels": "Không có kênh", - "claim_balance": "Yêu cầu số dư {balance}", - "close_channel": "Đóng kênh", - "new_channel": "Kênh mới", - "errorInvoiceExpired": "Hóa đơn hết hạn", - "force_close_channel": "Buộc đóng kênh", "expired": "Hết hạn", - "node_alias": "Bí danh node", "expiresIn": "Hết hạn sau {time} phút", "payButton": "Thanh toán", "placeholder": "Hóa đơn hoặc địa chỉ", - "open_channel": "Mở kênh", - "funding_amount_placeholder": "Số tiền tài trợ, ví dụ 0.001", - "opening_channnel_for_from": "Đang mở kênh cho ví {forWalletLabel} bằng cách tài trợ từ {fromWalletLabel}", - "are_you_sure_open_channel": "Bạn có chắc muốn mở kênh này không?", - "potentialFee": "Phí tiềm năng: {fee}", - "remote_host": "Máy chủ từ xa", "refill": "Đổ đầy", - "reconnect_peer": "Kết nối lại với peer", "refill_create": "Để tiếp tục, vui lòng tạo một ví Bitcoin để nạp tiền. ", "refill_external": "Nạp lại với ví bên ngoài", "refill_lnd_balance": "Nạp lại số dư ví Lightning", - "sameWalletAsInvoiceError": "Bạn không thể thanh toán hóa đơn với cùng một ví được sử dụng để tạo nó.", "title": "Quản lý quỹ", - "can_send": "Gửi được", - "can_receive": "Nhận được", - "view_logs": "Xem các bản ghi" + "errorInvoiceExpired": "Hoá đơn đã hết hạn.", + "payment": "Khoản thanh toán", + "potentialFee": "Phí dự kiến: {fee}", + "sameWalletAsInvoiceError": "Bạn không thể thanh toán một hoá đơn bằng chính ví đã tạo ra nó." }, "lndViewInvoice": { "additional_info": "Thông tin thêm", "for": "Cho:", "lightning_invoice": "Hoá đơn Lightning", - "open_direct_channel": "Mở kênh trực tiếp với node này:", "please_pay_between_and": "Vui lòng thanh toán từ {min} đến {max}", "please_pay": "Vui lòng thanh toán", - "preimage": "Nghịch ảnh", "sats": "sats.", - "wasnt_paid_and_expired": "Hoá đơn này chưa được thanh toán và đã hết hạn. " + "wasnt_paid_and_expired": "Hoá đơn này chưa được thanh toán và đã hết hạn. ", + "preimage": "Pre-image", + "date_time": "Ngày và giờ" }, "plausibledeniability": { "create_fake_storage": "Tạo lưu trữ được mã hoá", - "create_password": "Tạo mật khẩu", "create_password_explanation": "Mật khẩu lưu trữ giả không được khớp với mật khẩu lưu trữ chính của bạn.", "help": "Trong một số trường hợp nhất định, bạn có thể bị buộc phải tiết lộ mật khẩu. Để giữ cho tiền của bạn an toàn, BlueWallet có thể tạo một lưu trữ được mã hoá khác có một mật khẩu khác. Trong trường hợp bị áp lực, bạn có thể tiết lộ mật khẩu đó. Khi nhập nó vào BlueWallet, nó sẽ mở khoá một lưu trữ “giả” mới. Điều này có vẻ hợp pháp với người khác nhưng sẽ bí mật giữ lưu trữ chính của bạn với tiền xu an toàn.", - "help2": "Lưu trữ mới sẻ hoạt động hoàn toàn, và bạn có thể nạp một ít tiền tại đó để nó có vẻ đáng tin cậy.", + "help2": "Lưu trữ mới sẽ hoạt động hoàn toàn, và bạn có thể nạp một ít tiền tại đó để nó có vẻ đáng tin cậy.", "password_should_not_match": "Mật khẩu hiện đang được sử dụng. Vui lòng thử một mật khẩu khác.", - "passwords_do_not_match": "Mật khẩu không phù hợp. Vui lòng thử lại.", - "retype_password": "Nhập lại mật khẩu", - "success": "Thành công", "title": "Sự từ chối hợp lý " }, "pleasebackup": { - "ask": "Bán đã lưu cụm từ sao lưu của ví chưa? Cụm từ này sẽ được yêu cầu để truy cập tiền của bạn trong trường hợp mất thiết bị này. Nếu không có cụm từ sao lưu thì tiền của bạn sẽ bị mất vĩnh viễn.", - "ask_no": "Không, tôi chưa lưu", - "ask_yes": "Vâng, tôi lưu rồi", - "ok": "OK, tôi viết ra rồi.", - "ok_lnd": "OK, tôi lưu rồi", - "text": "Vui lòng dành một chút thời gian để viết ra cụm từ ghi nhớ này trên một tờ giấy.\nBản sao lưu này có thể sử dùng để khôi phục ví của bạn.", - "text_lnd": "Vui lòng lưu bản sao lưu ví này dể bạn khôi phục ví trong trường hợp mất.", - "title": "Ví của bạn đã được tạo" + "ask": "Bạn đã lưu cụm từ sao lưu của ví chưa? Cụm từ này sẽ được yêu cầu để truy cập tiền của bạn trong trường hợp mất thiết bị này. Nếu không có cụm từ sao lưu thì tiền của bạn sẽ bị mất vĩnh viễn.", + "text": "Vui lòng dành một chút thời gian để viết ra cụm từ ghi nhớ này trên một tờ giấy.\nBản sao lưu này có thể sử dụng để khôi phục ví của bạn.", + "text_lnd": "Vui lòng lưu bản sao lưu ví này để bạn khôi phục ví trong trường hợp mất.", + "ask_no": "Chưa, tôi chưa lưu.", + "ask_yes": "Rồi, tôi đã lưu.", + "ok": "OK, tôi đã ghi lại.", + "ok_lnd": "OK, tôi đã lưu lại.", + "title": "Ví của bạn đã được tạo." }, "receive": { "details_create": "Tạo ", "details_label": "Mô tả", "details_setAmount": "Nhận với số tiền", - "details_share": "Chia sẻ", "header": "Nhận", "maxSats": "Số tiền tối đa là {max} sats", "maxSatsFull": "Số tiền tối đa là {max} sats hoặc {currency} ", - "minSats": "Số tiền tối thiểu là {max} sats", - "minSatsFull": "Số tiền tối thiểu là {max} sats hoặc {currency} " + "minSats": "Số tiền tối thiểu là {min} sats", + "minSatsFull": "Số tiền tối thiểu là {min} sats hoặc {currency} ", + "details_share": "Chia sẻ...", + "address_not_found": "Không thể tạo địa chỉ nhận.", + "reset": "Đặt lại", + "qrcode_for_the_address": "Mã QR cho địa chỉ", + "bip47_explanation": "Mã thanh toán là một địa chỉ phổ quát giúp tránh tiết lộ các địa chỉ ví của bạn. Không phải tất cả các dịch vụ đều hỗ trợ chúng." }, "send": { "provided_address_is_invoice": "Địa chỉ này dường như là cho một hóa đơn Lightning. Vui lòng sử dụng ví Lightning của bạn để thanh toán hoá đơn này.", - "broadcastButton": "Phát sóng", + "broadcastButton": "Truyền", "broadcastError": "Lỗi", "broadcastNone": "Chèn hex giao dịch", "broadcastPending": "Đang chờ giải quyết", @@ -136,13 +121,13 @@ "confirm_header": "Xác nhận", "confirm_sendNow": "Gửi ngay", "create_amount": "Số tiền", - "create_broadcast": "Phát sóng", - "create_copy": "Sao chép và phát sóng sau", + "create_broadcast": "Truyền", + "create_copy": "Sao chép và truyền sau", "create_details": "Chi tiết", "create_fee": "Phí", "create_memo": "Ghi nhớ", "create_satoshi_per_vbyte": "Satoshi mỗi vByte", - "create_this_is_hex": "Đây là hex giao dịch của bạn, đã ký và sẵn sàng phát sóng đến mạng.", + "create_this_is_hex": "Đây là hex giao dịch của bạn, đã ký và sẵn sàng truyền đến mạng.", "create_to": "Đến", "create_tx_size": "Kích cỡ giao dịch", "create_verify": "Xác minh giao dịch trên coinb.in", @@ -153,22 +138,20 @@ "details_adv_fee_bump": "Cho phép tăng phí", "details_adv_full": "Sử dụng hết số dư", "details_adv_full_sure": "Bạn có chắc muốn sử dụng hết số dư ví của bạn cho giao dịch này? ", - "details_adv_full_sure_frozen": "Bạn có chắc muốn sử dụng hết số dư ví của bạn cho giao dịch này? Hãy nhớ rằng những coin đong lạnh bị loại trừ.", + "details_adv_full_sure_frozen": "Bạn có chắc muốn sử dụng hết số dư ví của bạn cho giao dịch này? Hãy nhớ rằng những coin đã đóng băng bị loại trừ.", "details_adv_import": "Nhập giao dịch", "details_adv_import_qr": "Nhập giao dịch (QR)", "details_amount_field_is_not_valid": "Số tiền này không hợp lệ.", - "details_amount_field_is_less_than_minimum_amount_sat": "Số tiền quá nhở. Vui lòng nhập số tiên lớn hơn 500 sats.", + "details_amount_field_is_less_than_minimum_amount_sat": "Số tiền quá nhỏ. Vui lòng nhập số tiền lớn hơn 500 sats.", "details_create": "Tạo hoá đơn", "details_error_decode": "Không thể giải mã địa chỉ Bitcoin", "details_fee_field_is_not_valid": "Phí này không hợp lệ.", - "details_frozen": "{amount} BTC được đong lạnh", "details_next": "Tiếp", - "details_no_signed_tx": "Tệp đã chọn không có giao dịch được nhập. ", "details_note_placeholder": "Lưu ý đến bản thân", "details_scan": "Quét", "details_scan_hint": "Chạm đúp để quét hoặc nhập một đích đến", "details_total_exceeds_balance": "Số tiền gửi vượt quá số dư khả dụng.", - "details_total_exceeds_balance_frozen": "Số tiền gửi vượt quá số dư khả dụng. Hãy nhớ rằng những coin đong lạnh bị loại trừ.", + "details_total_exceeds_balance_frozen": "Số tiền gửi vượt quá số dư khả dụng. Hãy nhớ rằng những coin đã đóng băng bị loại trừ.", "details_unrecognized_file_format": "Không được nhận ra định dạng tệp ", "details_wallet_before_tx": "Trước khi tạo một giao dịch, bạn phải thêm ví Bitcoin.", "dynamic_init": "Đang khởi tạo", @@ -177,11 +160,11 @@ "dynamic_start": "Bắt đầu", "dynamic_stop": "Ngừng", "fee_10m": "10p", - "fee_1d": "1d", + "fee_1d": "1ng", "fee_3h": "3g", "fee_custom": "Tuỳ chỉnh", "fee_fast": "Nhanh", - "fee_medium": "Medium", + "fee_medium": "Trung bình", "fee_replace_minvb": "Tổng lệ phí (satoshi mỗi vByte) bạn muốn trả nên lớn hơn {min} sat/vByte.", "fee_satvbyte": "bằng sat/vByte", "fee_slow": "Chậm", @@ -190,26 +173,40 @@ "input_done": "Đã xong", "input_paste": "Dán", "input_total": "Tổng", - "permission_camera_message": "Chúng tôi cần sự cho phép của bạn đẻ sử dụng máy ảnh.", + "permission_camera_message": "Chúng tôi cần sự cho phép của bạn để sử dụng máy ảnh.", "psbt_sign": "Ký giao dịch", "open_settings": "Mở Cài đặt", - "permission_storage_later": "Hỏi tôi sau", - "permission_storage_message": "BlueWallet cần sự cho phép của bạn để truy cập lưu trữ và lưu tệp này.", "permission_storage_denied_message": "BlueWallet không thể lưu tệp này. Vui lòng mở cài đặt thiết bị của bạn và bật Quyền lưu trữ. ", "permission_storage_title": "Quyền truy cập lưu trữ", "psbt_clipboard": "Sao chép vào bảng tạm", - "psbt_this_is_psbt": "Đay là “giao dịch Bitcoin đã ký một phần” (PSBT). Vui lòng ký xong nó bằng ví phần cứng của bạn.", + "psbt_this_is_psbt": "Đây là “giao dịch Bitcoin đã ký một phần” (PSBT). Vui lòng ký xong nó bằng ví phần cứng của bạn.", "psbt_tx_export": "Xuất sang tệp", "no_tx_signing_in_progress": "Không có việc ký giao dịch trong tiến trình.", "outdated_rate": "Tỷ lệ được cập nhật lần cuối: {date}", "psbt_tx_open": "Mở giao dịch đã ký", "psbt_tx_scan": "Quét giao dịch đã ký", - "qr_error_no_qrcode": "Chúng tôi không tìm được mã QR trong hình ảnh đã chọn. Hãy xác minh rằng hình chỉ chứa mã QR và không chứa nội dung khác như văn bản hoặc nút.", "reset_amount": "Đặt lại số tiền", "reset_amount_confirm": "Bạn có muốn đặt lại số tiền không?", "success_done": "Đã xong", - "txSaved": "Tệp giao dịch ({filePath}) đã được lưu vào thư mục Tải xuống. ", - "problem_with_psbt": "PSBT có lỗi" + "problem_with_psbt": "PSBT có lỗi", + "details_insert_contact": "Chèn liên hệ", + "details_add_recc_rem_all_alert_description": "Bạn có chắc muốn loại bỏ tất cả người nhận?", + "details_add_rec_rem_all": "Loại bỏ tất cả người nhận", + "details_recipients_title": "Người nhận", + "details_recipient_title": "Người nhận #{number} của #{total}", + "please_complete_recipient_title": "Người nhận chưa đầy đủ", + "please_complete_recipient_details": "Vui lòng điền đầy đủ thông tin của người nhận #{number} trước khi thêm người nhận mới.", + "details_frozen": "{amount} BTC đã bị đóng băng.", + "details_no_signed_tx": "Tệp đã chọn không chứa giao dịch có thể nhập.", + "details_scan_error": "Lỗi quét", + "insert_custom_fee": "Nhập phí", + "invalid_psbt": "PSBT được cung cấp không hợp lệ.", + "qr_error_no_qrcode": "Chúng tôi không thể tìm thấy mã QR hợp lệ trong hình ảnh đã chọn. Hãy đảm bảo hình ảnh chỉ chứa mã QR và không có nội dung bổ sung như văn bản hoặc nút bấm.", + "txSaved": "Tệp giao dịch ({filePath}) đã được lưu.", + "file_saved_at_path": "Tệp ({filePath}) đã được lưu.", + "cant_send_to_silentpayment_adress": "Ví này không thể gửi đến các địa chỉ SilentPayment", + "cant_send_to_bip47": "Ví này không thể gửi đến các mã thanh toán BIP47", + "cant_find_bip47_notification": "Trước tiên hãy thêm mã thanh toán này vào danh bạ" }, "settings": { "about": "Giới thiệu", @@ -225,59 +222,34 @@ "about_selftest_electrum_disabled": "Tự kiểm tra không có sẵn trong khi Electrum ở chế độ ngoại tuyến. Vui lòng tắt chế độ ngoại tuyến và thử lại. ", "about_selftest_ok": "Các bài test nội bộ đã vượt qua thành công. Ví hoạt động tốt.", "about_sm_github": "GitHub", - "about_sm_discord": "Máy chủ Discord", "about_sm_telegram": "Kênh Telegram", - "about_sm_twitter": "Theo chúng tôi trên Twitter", - "advanced_options": "Tùy chọn nâng cao", "biometrics": "Sinh trắc", "biom_10times": "Bạn đã thử nhập mật khẩu 10 lần rồi. Bạn có muốn đặt lại lưu trữ không? Điều này sẽ loại bỏ tất cả các ví và giải mã lưu trữ của bạn.", "biom_conf_identity": "Vui lòng xác nhận danh tính của bạn.", - "biom_no_passcode": "Thiết bị của bạn không có mật mã. Vui lòng cấu hình môt mật mã trong Cài Đặt để tiến hành.", "biom_remove_decrypt": "Tất cả các ví của bạn sẽ được gỡ bỏ và lưu trữ của bạn sẽ được giải mã. Bạn có chắc muốn tiếp tục?", "currency": "Tiền tệ", - "currency_source": "Giá có được từ", "currency_fetch_error": "Có một lỗi trong khi truy xuất tỷ lệ cho tiền tệ đã chọn.", - "default_desc": "Nếu vô hiệu hoá, BlueWallet sẻ sẽ ngay mở ví được chọn khi khởi động.", - "default_info": "Thông tin mặc định", "default_title": "Khi khởi động", - "default_wallets": "Xem các ví", "electrum_connected": "Đã kết nối", "electrum_connected_not": "Chưa kết nối", - "electrum_error_connect": "Không được kết nối với máy chủ Electrum đã cung cấp ", "lndhub_uri": "Ví dụ: {example}", "electrum_host": "Ví dụ: {example}", "electrum_offline_mode": "Chế độ ngoại tuyến", - "electrum_offline_description": "Néu kích hoạt, các ví Bitcoin của bạn sẽ không nỗ lực đi lấy số dư hoặc giao dịch.", + "electrum_offline_description": "Nếu kích hoạt, các ví Bitcoin của bạn sẽ không nỗ lực đi lấy số dư hoặc giao dịch.", "electrum_port": "Cổng, thông thường là {example}", "use_ssl": "Sử dụng SSL", "electrum_saved": "Những thay đổi của bạn đã được lưu thành công. Khởi động lại Bluewallet có thể được yêu cầu cho các thay đổi có hiệu lực.", "set_electrum_server_as_default": "Đặt làm {server} máy chủ Electrum mặc định không?", - "set_lndhub_as_default": "Đặt làm {server} máy chủ LNDHub mặc định không?", "electrum_settings_server": "Máy chủ Electrum", - "electrum_settings_explain": "Để trống để sử dụng mặc định.", "electrum_status": " Trạng thái", - "electrum_clear_alert_title": "Xoá lịch sử không?", - "electrum_clear_alert_message": "Bạn có muốn xoá lịch sử của máy chủ Electrum không?", - "electrum_clear_alert_cancel": "Huỷ", - "electrum_clear_alert_ok": "OK", - "electrum_select": "Chọn", - "electrum_reset": "Đặt lại về mặc định ", "electrum_unable_to_connect": "Không thể kết nối với máy chủ {server}.", - "electrum_history": "Lich sử máy chủ", - "electrum_reset_to_default": "Bạn có chắc muốn đặt lại cài đặt Electrum về mặc định không?", - "electrum_clear": "Xoá", - "tor_supported": "Tor được hỗ trợ", - "tor_unsupported": "Các kết nối Tor không được hỗ trợ", + "electrum_reset": "Đặt lại về mặc định ", "encrypt_decrypt": "Giải mã lưu trữ", "encrypt_decrypt_q": "Bạn có chắc muốn giải mã lưu trữ không? Điều này sẽ làm cho ví của bạn có thể truy cập được mà không cần mật khẩu.", - "encrypt_enc_and_pass": "Được mã hóa và bảo vệ bằng mật khẩu", "encrypt_title": "An toàn", "encrypt_tstorage": "Lưu trữ", - "encrypt_use": "Sử dùng {type}", - "encrypt_use_expl": "{type} sẽ được dùng để xác nhận danh tính của bạn trước khi thực hiện giao dịch, mở khoá ví, xuất ví, hoặc xoá ví. {type} sẽ không được dùng để mở khoá lưu trữ được mã hoá.", + "encrypt_use": "Sử dụng {type}", "general": "Cài đặt chung", - "general_adv_mode": "Chế độ nâng cao", - "general_adv_mode_e": "Nếu kích hoạt, bạn sẽ xem tùy chọn nâng cao như loại khác ví, khả năng xác định thể hiện LNDHub nào để kết nối, và entropy tuỳ chỉnh khi tạo ví. ", "general_continuity": "Sự liên tục", "general_continuity_e": "Nếu kích hoạt, bạn sẽ xem những ví và giao dịch đã chọn trên các thiết bị Apple khác kết nối đến iCloud. ", "groundcontrol_explanation": "GroundControl là một máy chủ đẩy thông báo có tự do và nguồn mở cho ví Bitcoin. Bạn có thể cài đặt máy chủ GroundControl riêng của mình và nhập URL của nó tại đây để không dựa vào hạ tầng của BlueWallet. Để trống để sử dụng máy chủ GroundControl mặc định.", @@ -285,72 +257,97 @@ "language": "Ngôn ngữ", "last_updated": "Cập nhật cuối ", "language_isRTL": "Khởi động lại BlueWallet để định hướng ngôn ngữ có hiệu lực.", - "lightning_error_lndhub_uri": "URI LNDHub không hợp lệ", "lightning_saved": "Những thay đổi của bạn đã được lưu thành công.", "lightning_settings": "Cài đặt Lightning", - "tor_settings": "Cài đặt Tor", - "lightning_settings_explain": "Để kết nối node LND riêng của bạn, vui lòng cài đặt LNDHub va nhập URL của nó vào cài đặt tại đây. Xin lưu ý rằng chỉ các ví được tạo sau khi lưu các thay đổi sẽ kết nối với LNDHub được chỉ định.", "network": "Mạng", - "network_broadcast": "Phát sóng giao dịch", + "network_broadcast": "Truyền giao dịch", "network_electrum": "Máy chủ Electrum", "not_a_valid_uri": "URI không hợp lệ", "notifications": "Thông báo", "open_link_in_explorer": "Mở liên kết trong explorer", "password": "Mật khẩu", - "password_explain": "Tạo mật khẩu bạn sẽ sử dụng để giải mã lưu trữ.", - "passwords_do_not_match": "Mật khẩu không trùng khớp", "plausible_deniability": "Sự từ chối hợp lý ", "privacy": "Riêng tư", "privacy_read_clipboard": "Đọc bảng tạm", "privacy_system_settings": "Cài đặt hệ thống", "privacy_quickactions": "Các phím tắt ví", - "privacy_quickactions_explanation": "Chạm và giữ biểu tượng ứng dụng BlueWallet trên màn hình Home để xem nhanh số dư ví của bạn.", "privacy_clipboard_explanation": "Cung cấp phím tắt trong trường hợp một địa chỉ hoặc hóa đơn được tìm thấy trong bảng tạm của bạn.", "privacy_do_not_track": "Vô hiệu hoá phân tích ", - "privacy_do_not_track_explanation": "Thông tin về hiệu suất và độ tin cậy không sẽ được gửi để phân tích.", - "push_notifications": " Thông báo đẩy", + "privacy_do_not_track_explanation": "Thông tin về hiệu suất và độ tin cậy sẽ không được gửi để phân tích.", "rate": "Tỷ lệ", - "retype_password": "Nhập lại mật khẩu", "selfTest": "Tự kiểm tra", "save": "Lưu", "saved": "Đã lưu", - "success_transaction_broadcasted": "Thành công! Giao dịch của bạn đã được phát sóng!", "total_balance": "Tổng số dư", "total_balance_explanation": "Hiển thị tổng số dư của các ví của bạn trong widget màn hình Home. ", "widgets": "Các widgets", - "tools": "Các tools" + "tools": "Các tools", + "block_explorer_invalid_custom_url": "URL được cung cấp không hợp lệ. Vui lòng nhập một URL hợp lệ bắt đầu bằng http:// hoặc https://.", + "privacy_temporary_screenshots": "Cho phép chụp màn hình", + "privacy_temporary_screenshots_instructions": "Bảo vệ chụp màn hình sẽ bị tắt tạm thời, cho phép chụp ảnh màn hình và quay video màn hình. Bảo vệ sẽ tự động kích hoạt lại khi bạn đóng và mở lại BlueWallet.", + "biometrics_no_longer_available": "Cài đặt thiết bị của bạn đã thay đổi và không còn khớp với cài đặt bảo mật đã chọn trong ứng dụng. Vui lòng kích hoạt lại sinh trắc học hoặc mã PIN, sau đó khởi động lại ứng dụng để áp dụng các thay đổi này.", + "biom_no_passcode": "Thiết bị của bạn chưa có mã PIN hoặc sinh trắc học được bật. Để tiếp tục, vui lòng thiết lập mã PIN hoặc sinh trắc học trong ứng dụng Cài đặt.", + "currency_source": "Tỷ giá được lấy từ", + "donate": "Quyên góp", + "donate_description": "Hãy giúp chúng tôi giữ cho Blue miễn phí!", + "electrum_error_connect": "Không thể kết nối đến máy chủ Electrum được cung cấp", + "electrum_error_connect_tor": "Không thể kết nối đến máy chủ Electrum được cung cấp. Vui lòng đảm bảo ứng dụng Orbot đã được kết nối và thử lại.", + "set_lndhub_as_default": "Đặt {url} làm máy chủ LNDhub mặc định?", + "electrum_preferred_server": "Máy chủ ưu tiên", + "electrum_preferred_server_description": "Nhập máy chủ bạn muốn ví của mình sử dụng cho tất cả các hoạt động Bitcoin. Sau khi đã đặt, ví của bạn sẽ chỉ sử dụng máy chủ này để kiểm tra số dư, gửi giao dịch và lấy dữ liệu mạng. Hãy đảm bảo bạn tin cậy máy chủ này trước khi đặt.", + "electrum_history": "Lịch sử", + "electrum_reset_to_default": "Điều này sẽ cho phép BlueWallet chọn ngẫu nhiên một máy chủ từ danh sách máy chủ.", + "electrum_reset_to_default_and_clear_history": "Đặt lại về mặc định và xoá lịch sử", + "encrypt_enc_and_pass": "Được bảo vệ bằng mật khẩu", + "encrypt_storage_explanation_headline": "Bật mã hoá lưu trữ", + "encrypt_storage_explanation_description_line1": "Bật mã hoá lưu trữ sẽ thêm một lớp bảo vệ cho ứng dụng của bạn bằng cách bảo mật cách dữ liệu được lưu trên thiết bị. Điều này khiến người khác khó truy cập thông tin của bạn nếu không có quyền.", + "encrypt_storage_explanation_description_line2": "Tuy nhiên, cần lưu ý rằng việc mã hoá này chỉ bảo vệ quyền truy cập vào các ví được lưu trong keychain của thiết bị. Nó không đặt mật khẩu hay bất kỳ bảo vệ bổ sung nào lên chính các ví.", + "i_understand": "Tôi hiểu", + "block_explorer": "Trình khám phá khối", + "block_explorer_preferred": "Sử dụng trình khám phá khối ưu tiên", + "block_explorer_error_saving_custom": "Lỗi khi lưu trình khám phá khối ưu tiên", + "set_as_preferred": "Đặt làm ưu tiên", + "set_as_preferred_electrum": "Đặt {host}:{port} làm máy chủ ưu tiên sẽ vô hiệu hoá việc kết nối ngẫu nhiên đến máy chủ được đề xuất.", + "encrypted_feature_disabled": "Tính năng này không thể được sử dụng khi mã hoá lưu trữ đã được bật.", + "encrypt_use_expl": "{type} sẽ được sử dụng để xác nhận danh tính của bạn trước khi thực hiện giao dịch, mở khoá, xuất hoặc xoá ví.", + "biometrics_fail": "Nếu {type} không được bật, hoặc không mở khoá được, bạn có thể sử dụng mã PIN của thiết bị làm phương án thay thế.", + "license": "Giấy phép", + "lightning_error_lndhub_uri": "URI LNDhub không hợp lệ", + "lightning_error_lndhub_uri_tor": "URI LNDhub không hợp lệ. Vui lòng đảm bảo ứng dụng Orbot đã được kết nối và thử lại.", + "lightning_settings_explain": "Để kết nối với node LND của riêng bạn, vui lòng cài đặt LNDhub và đặt URL của nó tại đây trong cài đặt. Lưu ý rằng chỉ những ví được tạo sau khi lưu các thay đổi mới kết nối với LNDhub đã chỉ định.", + "lndhub_github": "Kho lưu trữ GitHub", + "electrum_suggested_description": "Khi không đặt máy chủ ưu tiên, một máy chủ được đề xuất sẽ được chọn để sử dụng ngẫu nhiên.", + "password_explain": "Nhập mật khẩu mà bạn sẽ dùng để mở khoá lưu trữ.", + "privacy_quickactions_explanation": "Chạm và giữ biểu tượng ứng dụng BlueWallet để xem nhanh số dư của ví.", + "push_notifications_explanation": "Bằng cách bật thông báo, mã token thiết bị của bạn sẽ được gửi đến máy chủ, cùng với các địa chỉ ví và ID giao dịch cho tất cả các ví và giao dịch được thực hiện sau khi bật thông báo. Mã token thiết bị được dùng để gửi thông báo, và thông tin ví cho phép chúng tôi thông báo cho bạn về Bitcoin đến hoặc xác nhận giao dịch.\n\nChỉ thông tin từ sau khi bạn bật thông báo mới được truyền — không có gì từ trước đó được thu thập.\n\nTắt thông báo sẽ xoá tất cả thông tin này khỏi máy chủ. Ngoài ra, xoá một ví khỏi ứng dụng cũng sẽ xoá thông tin liên quan của nó khỏi máy chủ.", + "success_transaction_broadcasted": "Giao dịch của bạn đã được truyền tải thành công!" }, "notifications": { "would_you_like_to_receive_notifications": "Bạn có muốn được thông báo khi nào nhận thanh toán đến không?", - "no_and_dont_ask": "Không, và đừng hỏi tôi nữa", - "ask_me_later": "Hỏi tôi sau" + "notifications_subtitle": "Khoản thanh toán đến và xác nhận giao dịch", + "no_and_dont_ask": "Không, và đừng hỏi tôi nữa.", + "permission_denied_message": "Bạn đã từ chối quyền gửi thông báo. Nếu bạn muốn nhận thông báo, vui lòng bật chúng trong cài đặt thiết bị." }, "transactions": { "cancel_explain": "Chúng tôi sẽ thay thế giao dịch này với một cái có thanh toán cho bạn và có phí lớn hơn. Điều này hiệu quả hủy bỏ giao dịch hiện tại. Đây là RBF: Replace By Fee - Thay thế bằng phí.", "cancel_no": "Giao dịch này không được thay thế.", "cancel_title": "Huỷ bỏ giao dịch này (RBF)", "confirmations_lowercase": "{confirmations} xác nhận", - "copy_link": "Sao chép liên kết", "expand_note": "Mở rộng ghi chú", "cpfp_create": "Tạo", - "cpfp_exp": "Chúng tôi sẽ tạo một giao dịch khác chi tiêu giao dịch chưa được xác nhận của bạn. Tổng phí sẽ lớn hơn phí của giao dịch góc nên sẽ được khai thác nhanh hơn. Đây là CPFP: Child Pays For Parent - Con trả cho mẹ.", + "cpfp_exp": "Chúng tôi sẽ tạo một giao dịch khác chi tiêu giao dịch chưa được xác nhận của bạn. Tổng phí sẽ lớn hơn phí của giao dịch gốc nên sẽ được khai thác nhanh hơn. Đây là CPFP: Child Pays For Parent - Con trả cho mẹ.", "cpfp_no_bump": "Giao dịch này không thể được tăng.", "cpfp_title": "Phí tăng (CPFP)", "details_balance_hide": "Giấu số dư", "details_balance_show": "Hiển thị số dư ", - "details_block": "Chiều cao khối", "details_copy": "Sao chép", - "details_copy_amount": "Sao chép số tiền", "details_copy_block_explorer_link": "Sao chép liên kết Block Explorer", "details_copy_note": "Sao chép ghi chú", "details_copy_txid": "Sao chép ID giao dịch", - "details_from": "Đầu vào", "details_inputs": "Các đầu vào", "details_outputs": "Các đầu ra", "date": "Ngày", "details_received": "Đã nhận", - "transaction_note_saved": "Ghi chú giao dịch đã được lưu thành công.", - "details_show_in_block_explorer": "Xem trong Block Explorer", "details_title": "Giao dịch", "details_to": "Đầu ra", "enable_offline_signing": "Ví này không được sử dụng cùng với một bản ký ngoại tuyến. Bạn có muốn kích hoạt nó ngay bây giờ không?", @@ -358,62 +355,88 @@ "pending": "Đang chờ giải quyết", "pending_with_amount": "Đang chờ giải quyết {amt1} ({amt2})", "received_with_amount": "+{amt1} ({amt2})", - "eta_10m": "Dến trong ~10 phút", - "eta_3h": "Dến trong ~3 giờ", - "eta_1d": "Dến trong ~1 ngày", - "view_wallet": "Xem {walletLabel}", + "eta_10m": "Đến trong ~10 phút", + "eta_3h": "Đến trong ~3 giờ", + "eta_1d": "Đến trong ~1 ngày", "list_title": "Các giao dịch", + "list_title_received": "Đã nhận", + "transaction": "Giao dịch", "open_url_error": "Không thể mở liên kết với trình duyệt mặc định. Vui lòng thay đổi trình duyệt mặc định và thử lại.", - "rbf_explain": "Chúng tôi sẽ thay thế giao dịch này với một cái mới có phí lớn hơn để nó được khai thác nhanh hơn. Cái dó gọi là RBF: Replace By Fee.", + "rbf_explain": "Chúng tôi sẽ thay thế giao dịch này với một cái mới có phí lớn hơn để nó được khai thác nhanh hơn. Cái đó gọi là RBF: Replace By Fee.", "rbf_title": "Tăng phí (RBF)", "status_bump": "Tăng phí", "status_cancel": "Huỷ giao dịch", "transactions_count": "Số lượng giao dịch ", "txid": "ID giao dịch", - "updating": "Đang cập nhật..." + "updating": "Đang cập nhật...", + "transaction_loading_error": "Đã có sự cố khi tải giao dịch. Vui lòng thử lại sau.", + "transaction_not_available": "Giao dịch không có sẵn", + "details_view_in_browser": "Xem trong trình duyệt", + "incoming_transaction": "Giao dịch đến", + "outgoing_transaction": "Giao dịch đi", + "expired_transaction": "Giao dịch đã hết hạn", + "pending_transaction": "Giao dịch đang chờ", + "offchain": "Offchain", + "onchain": "Onchain", + "list_title_sent": "Đã gửi", + "watchOnlyWarningTitle": "Cảnh báo bảo mật", + "watchOnlyWarningDescription": "Hãy cẩn trọng với những kẻ lừa đảo thường sử dụng ví “chỉ xem” để đánh lừa người dùng. Các ví này không cho phép bạn kiểm soát hoặc gửi tiền; chúng chỉ cho phép bạn xem số dư.", + "custom_fee_warning_title": "Cảnh báo", + "custom_fee_warning_description": "Các mức phí dưới 1 sat/vB là hợp lệ, nhưng có thể không được chuyển tiếp do chính sách của node.", + "details_eta_analyzing": "Đang phân tích...", + "details_sent": "Đã gửi", + "details_section": "Chi tiết", + "details_explorer": "trình khám phá", + "details_network_fee": "Phí mạng", + "details_to_address": "Đến", + "details_id": "ID", + "details_note": "Ghi chú", + "details_add_note": "thêm", + "details_advanced": "Nâng cao", + "details_fee_rate": "Mức phí", + "details_size": "Kích cỡ", + "details_virtual_size": "Kích cỡ ảo", + "details_tx_hex": "Hex giao dịch", + "details_inputs_count": "Đầu vào ({count})", + "details_outputs_count": "Đầu ra ({count})" }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Ví Bitcoin đơn giản và mạnh mẽ", "add_create": "Tạo", + "total_balance": "Tổng số dư", + "add_entropy": "Entropy", "add_entropy_generated": "{gen} bytes entropy đã được tạo", "add_entropy_provide": "Cung cấp entropy bằng dice roll", - "add_entropy_remain": "{gen} bytes entropy đã được tạo. {gen} bytes còn lại sẽ lấy được từ số ngẫu nhiên của hệ thống. ", + "add_entropy_remain": "{gen} bytes entropy đã được tạo. {rem} bytes còn lại sẽ lấy được từ số ngẫu nhiên của hệ thống. ", "add_import_wallet": "Nhập ví", "add_lightning": "Lightning", "add_lightning_explain": "Để chi tiêu với các giao dịch tức thời ", - "add_lndhub": "Kết nối với LNDHub của bạn", - "add_lndhub_error": "Địa chỉ cung cấp là một node LNDHub không hợp lệ.", "add_lndhub_placeholder": "Địa chỉ node của bạn", "add_placeholder": "Ví đầu tiên của tôi", "add_title": "Thêm ví", "add_wallet_name": "Tên", "add_wallet_type": "Loại", - "balance": "Số dư", - "clipboard_bitcoin": "Có môt địa chỉ Bitcoin trên cái bảng của bạn. Bạn có muốn sử dụng nó cho một giao dịch?", - "clipboard_lightning": "Có môt hoá đơn Lightning trên cái bảng của bạn. Bạn có muốn sử dụng nó cho một giao dịch?", + "clipboard_bitcoin": "Có một địa chỉ Bitcoin trên bảng tạm của bạn. Bạn có muốn sử dụng nó cho một giao dịch?", + "clipboard_lightning": "Có một hoá đơn Lightning trên bảng tạm của bạn. Bạn có muốn sử dụng nó cho một giao dịch?", "details_address": "Địa chỉ", "details_advanced": "Nâng cao", "details_are_you_sure": "Bạn có chắc không?", "details_connected_to": "Nối đến", - "details_del_wb_err": "Số tiền cung cấp không khớp với số dư của ví này. Vui lòng thử lại. ", - "details_del_wb_q": "Ví này có số dư. Trước khi tiếp tục, hãy nhớ rằng bạn không thể thu hồi vốn nếu không có cụm từ hạt giống của ví này. Ðể nhằm ngăn chặn việc xoá nhầm, vui lòng nhập số dư ví của bạn có {balance} satoshis.", + "details_del_wb_q": "Ví này có số dư. Trước khi tiếp tục, hãy nhớ rằng bạn không thể thu hồi vốn nếu không có cụm từ seed của ví này. Để nhằm ngăn chặn việc xoá nhầm, vui lòng nhập số dư ví của bạn có {balance} satoshis.", "details_delete": "Xoá", - "details_delete_wallet": "Xoa ví", + "details_delete_wallet": "Xoá ví", "details_derivation_path": "Đường dẫn xuất ", - "details_display": "Hiển thị trong danh sách ví", "details_export_backup": "Xuất/Sao lưu ", "details_export_history": "Xuất lịch sử sang CSV ", "details_master_fingerprint": "Vân tay chủ", "details_multisig_type": "Multisig", - "details_no_cancel": "Không, huỷ", - "details_save": "Lưu", "details_show_xpub": "Hiển thị XPUB của ví", "details_show_addresses": "Hiển thị các địa chỉ", "details_title": "Ví", + "wallets": "Các ví", "details_type": "Loại", "details_use_with_hardware_wallet": "Sử dụng với ví phần cứng", - "details_wallet_updated": "Ví đã cập nhật", "details_yes_delete": "Vâng, xoá", "enter_bip38_password": "Nhập mật khẩu để giải mã", "export_title": "Xuất ví", @@ -422,7 +445,7 @@ "import_passphrase_title": "Passphrase", "import_passphrase_message": "Nếu bạn đã sử dụng bất kỳ passphrase thì nhập nó", "import_error": "Nhập đã thất bại. Vui lòng đảm bảo dữ liệu cung cấp có hợp lệ.", - "import_explanation": "Vui lòng nhập cụm từ hạt giống, khoá công khai, WIF, hoặc bất cứ điều gì bạn có. BlueWallet sẽ cố gắng hết sức để đoán định dạng đúng và nhập ví của bạn.", + "import_explanation": "Vui lòng nhập cụm từ seed, khoá công khai, WIF, hoặc bất cứ điều gì bạn có. BlueWallet sẽ cố gắng hết sức để đoán định dạng đúng và nhập ví của bạn.", "import_imported": "Đã nhập", "import_scan_qr": "Quét hoặc nhập tệp", "import_success": "Ví của bạn đã được nhập thành công.", @@ -433,44 +456,79 @@ "import_discovery_subtitle": "Chọn một ví được phát hiện", "import_discovery_derivation": "Sử dụng đường dẫn xuất tùy chỉnh", "import_discovery_no_wallets": "Không tìm thấy ví.", - "import_derivation_found": "đã tìm thấy", - "import_derivation_found_not": "không được tìm thấy", - "import_derivation_loading": "đang tải...", - "import_derivation_subtitle": "Nhập đường dẫn xuất tùy chỉnh. Chúng tồi sẽ cố gắng khám phá ví của bạn", "import_derivation_title": "Đường dẫn xuất", - "import_derivation_unknown": "không xác định", - "import_wrong_path": "đường dẫn xuất sai", "list_create_a_button": "Thêm ngay", "list_create_a_wallet": "Thêm ví", - "list_create_a_wallet_text": "Nó miễn phí và bạn có thể\ntạo bao nhiêu tuỳ thích.", "list_empty_txs1": "Các giao dịch của bạn sẽ xuất hiện ở đây.", "list_empty_txs1_lightning": "Ví Lightning nên được sử dụng cho giao dịch hàng ngày. Có phí giao dịch rất nhỏ với tốc độ nhanh.", "list_empty_txs2": "Bắt đầu với ví của bạn.", "list_empty_txs2_lightning": "\nĐể bắt đầu sử dụng nó, chạm vào Quản lý tiền, và nạp tiền vào số dư của bạn. ", "list_latest_transaction": "Giao dịch mới nhất", - "list_ln_browser": "Trình duyệt LApp", "list_long_choose": "Chọn hình", - "list_long_clipboard": "Sao chép từ bảng tạm", + "paste_from_clipboard": "Dán", "list_long_scan": "Quét mã QR", "list_title": "Các ví", "list_tryagain": "Thử lại", "no_ln_wallet_error": "Trước khi thanh toán hoá đơn Lightning, đầu tiên bạn phải thêm một ví Lightning.", "looks_like_bip38": "Cái này trông giống một khoá cá nhân được bảo vệ bằng mật khẩu (BIP38).", - "reorder_title": "Sắp xếp lại các vị", - "reorder_instructions": "Chạm và giữ một ví để kéo nó trong danh sách.", "please_continue_scanning": "Vui lòng quét tiếp.", "select_no_bitcoin": "Hiện tại không có ví Bitcoin có sẵn.", - "select_no_bitcoin_exp": "Bạn phải sử dụng ví Bitcoin đễ nạp tiền vào ví Lightning. Hãy tạo hoặc nhập một ví Bitcoin.", + "select_no_bitcoin_exp": "Bạn phải sử dụng ví Bitcoin để nạp tiền vào ví Lightning. Hãy tạo hoặc nhập một ví Bitcoin.", "select_wallet": "Chọn ví", - "xpub_copiedToClipboard": "Đã sao chép vào bảng tạm.", "pull_to_refresh": "Kéo để làm mới", - "warning_do_not_disclose": "Cảnh báo! Không tiết lộ.", "add_ln_wallet_first": "Đầu tiên bạn phải thêm một ví Lightning.", "identity_pubkey": "PubKey danh tính", - "xpub_title": "XPUB của ví" + "xpub_title": "XPUB của ví", + "add_entropy_reset_title": "Đặt lại entropy", + "add_entropy_reset_message": "Thay đổi loại ví sẽ đặt lại entropy hiện tại. Bạn có muốn tiếp tục?", + "add_entropy_bytes": "{bytes} byte entropy", + "add_lndhub": "Kết nối đến LNDhub của bạn", + "add_lndhub_error": "Địa chỉ node được cung cấp không phải là một node LNDhub hợp lệ.", + "add_wallet_seed_length": "Độ dài cụm từ khôi phục", + "add_wallet_seed_length_12": "12 từ", + "add_wallet_seed_length_24": "24 từ", + "clear_clipboard_on_import": "Xoá bảng tạm khi nhập", + "details_del_wb_err": "Số dư được cung cấp không khớp với số dư của ví này. Vui lòng thử lại.", + "details_display": "Hiển thị ở màn hình chính", + "swipe_balance_hide": "Ẩn", + "swipe_balance_show": "Hiển thị", + "drag_to_reorder": "Kéo để sắp xếp lại", + "clear_search": "Xoá tìm kiếm", + "learn_more": "Tìm hiểu thêm", + "import_discovery_offline": "BlueWallet hiện đang ở chế độ ngoại tuyến. Ở chế độ này, ứng dụng không thể xác minh sự tồn tại của ví, vì vậy bạn cần chọn ví đúng theo cách thủ công", + "import_derivation_found": "Đã tìm thấy", + "import_derivation_found_not": "Không tìm thấy", + "import_derivation_loading": "Đang tải...", + "import_derivation_subtitle": "Nhập đường dẫn dẫn xuất tuỳ chỉnh, và chúng tôi sẽ thử khám phá ví của bạn.", + "import_derivation_unknown": "Không xác định", + "import_wrong_path": "Đường dẫn dẫn xuất sai", + "list_create_a_wallet_text": "Miễn phí, và bạn có thể tạo \nbao nhiêu tuỳ thích.", + "import_file": "Nhập tệp", + "manage_title": "Quản lý ví", + "no_results_found": "Không tìm thấy kết quả.", + "warning_do_not_disclose": "Không bao giờ chia sẻ thông tin bên dưới", + "scan_import": "Quét mã QR này để nhập ví của bạn vào ứng dụng khác.", + "write_down_header": "Tạo bản sao lưu thủ công", + "write_down": "Viết ra và lưu trữ các từ này một cách an toàn. Sử dụng chúng để khôi phục ví của bạn sau này.", + "wallet_type_this": "Loại ví này là {type}.", + "share_number": "Chia sẻ {number}", + "copy_ln_url": "Sao chép và lưu trữ URL này một cách an toàn để khôi phục ví của bạn sau này.", + "copy_ln_public": "Sao chép và lưu trữ thông tin này một cách an toàn để khôi phục ví của bạn sau này.", + "manage_wallets_search_placeholder": "Tìm kiếm ví, địa chỉ, giao dịch và ghi chú", + "more_info": "Thêm thông tin", + "details_delete_wallet_error_message": "Đã có sự cố khi xác nhận liệu ví này có được gỡ khỏi thông báo hay không — có thể do sự cố mạng hoặc kết nối kém. Nếu bạn tiếp tục, bạn vẫn có thể nhận thông báo về các giao dịch liên quan đến ví này, ngay cả sau khi nó bị xoá.", + "details_delete_anyway": "Vẫn xoá" + }, + "total_balance_view": { + "title": "Tổng số dư", + "display_in_bitcoin": "Hiển thị bằng Bitcoin", + "hide": "Ẩn", + "display_in_sats": "Hiển thị bằng sats", + "display_in_fiat": "Hiển thị bằng {currency}", + "explanation": "Xem tổng số dư của tất cả các ví của bạn trong màn hình tổng quan." }, "multisig": { - "multisig_vault": "Vault", + "multisig_vault": "Vault Multisig", "default_label": "Vault Multisig", "multisig_vault_explain": "An toàn cao nhất cho số tiền lớn", "provide_signature": "Cung cấp chữ ký", @@ -480,7 +538,6 @@ "fee_btc": "{number} BTC", "confirm": "Xác nhận", "header": "Gửi", - "share": "Chia sẻ", "view": "Xem", "manage_keys": "Quản lý các khóa ", "how_many_signatures_can_bluewallet_make": "Bluewallet có thể tạo bao nhiêu chữ ký", @@ -489,11 +546,11 @@ "scan_or_import_file": "Quét hoặc nhập tệp", "export_coordination_setup": "Cài đặt phối hợp xuất", "cosign_this_transaction": "Đồng ký giao dịch này không?", - "lets_start": "Haỹ bắt đầu", + "lets_start": "Hãy bắt đầu", "create": "Tạo", "native_segwit_title": "Thực tiễn tốt nhất", "wrapped_segwit_title": "Tương thích tốt nhất", - "legacy_title": "Thừa kế ", + "legacy_title": "Legacy ", "co_sign_transaction": "Ký giao dịch", "what_is_vault": "Một Vault là một", "what_is_vault_numberOfWallets": "Multisig {m}-của-{n}", @@ -507,21 +564,15 @@ "quorum_header": "Nhóm túc số ", "of": "của", "wallet_type": "Loại ví", - "invalid_mnemonics": "Cụm ghi nhớ này trông không hợp lệ.", - "invalid_cosigner": "Dữ liệu khoá đồng ký không hợp lệ", - "not_a_multisignature_xpub": "Cái nài không phải là XPUB từ ví Multisig!", - "invalid_cosigner_format": "Khoá đồng ký không đúng: không thể đồng ký cho định dạng {format}.", + "not_a_multisignature_xpub": "Cái này không phải là XPUB từ ví Multisig!", "create_new_key": "Tạo mới", "scan_or_open_file": "Quét hoặc mở tệp", - "i_have_mnemonics": "Tôi có hạt giống cho chìa khoá này.", - "type_your_mnemonics": "Chèn một hạt giống để nhập khoá Vault hiện tại của bạn.", - "this_is_cosigners_xpub": "Đây là XPUB của khoá đồng ký, sẵn sàng để được nhập vào ví khác. Có an toàn để chia sẻ nó.", - "wallet_key_created": "Khoá Vault của bạn đã được tạo. Hãy dành một chút thời gian để sao lưu an toàn hạt giống ghi nhớ của bạn.", - "are_you_sure_seed_will_be_lost": "Bạn có chắc không? Hạt giống ghi nhớ của bạn sẽ bị mất nếu bạn không có bản sao lưu.", - "forget_this_seed": "Bỏ qua hạt giống này và sử dụng XPUB thay thế.", - "view_edit_cosigners": "Xem/Chỉnh sửa các khoá đồng ký", - "this_cosigner_is_already_imported": "Khoá đồng ký này đã được nhập rồi.", - "export_signed_psbt": "Xuất PBST đã ký", + "i_have_mnemonics": "Tôi có seed cho chìa khoá này.", + "type_your_mnemonics": "Chèn một seed để nhập khoá Vault hiện tại của bạn.", + "wallet_key_created": "Khoá Vault của bạn đã được tạo. Hãy dành một chút thời gian để sao lưu an toàn cụm từ seed của bạn.", + "are_you_sure_seed_will_be_lost": "Bạn có chắc không? Cụm từ seed của bạn sẽ bị mất nếu bạn không có bản sao lưu.", + "forget_this_seed": "Bỏ qua seed này và sử dụng XPUB thay thế.", + "export_signed_psbt": "Xuất PSBT đã ký", "input_fp": "Nhập vân tay", "input_fp_explain": "Bỏ qua để sử dụng mặc định (00000000)", "input_path": "Chèn đường dẫn xuất", @@ -529,19 +580,33 @@ "ms_help": "Trợ giúp", "ms_help_title": " Cách Multisig hoạt động: một số thủ thuật ", "ms_help_text": "Một ví sử dụng nhiều khoá để tăng an toàn hoặc chia sẻ quyền giám sát", - "ms_help_title1": "Nến có nhiều thiết bị.", + "ms_help_title1": "Nên có nhiều thiết bị.", "ms_help_1": "Vault sẽ hoạt động với các ứng dụng Bluewallet khác và ví tương thích PSBT như Electrum, Spectre, Coldcard, Cobo Vault, v.v.", "ms_help_title2": "Đang chỉnh sửa những khoá", "ms_help_2": "Bạn có thể tạo tất cả các khóa Vault trong thiết bị này để xóa hoặc chỉnh sửa sau. Có tất cả các phím trên cùng một thiết bị có an toàn giống như ví bitcoin thông thường.", "ms_help_title3": "Các bản sao lưu Vault", - "ms_help_3": "Trên các tùy chọn ví, bạn sẽ thấy bản sao lưu Vault và bản sao lưu “chỉ xem”. Bản sao lưu này giống như một bản đồ vào ví của bạn. Nó cần thiết cho việc phục hồi ví trong trường hợp bạn mất một trong những hạt giống của mình.", + "ms_help_3": "Trên các tùy chọn ví, bạn sẽ thấy bản sao lưu Vault và bản sao lưu “chỉ xem”. Bản sao lưu này giống như một bản đồ vào ví của bạn. Nó cần thiết cho việc phục hồi ví trong trường hợp bạn mất một trong những seed của mình.", "ms_help_title4": "Đang nhập các Vault", - "ms_help_4": "Để nhập một Multisig, hãy sử dụng tệp sao lưu của bạn và tính năng nhập. Nếu bạn chỉ có những hạt giống và XPUB, bạn có thể sử dụng nút Nhập riêng lẻ khi tạo các khoá Vault.", + "ms_help_4": "Để nhập một Multisig, hãy sử dụng tệp sao lưu của bạn và tính năng nhập. Nếu bạn chỉ có những seed và XPUB, bạn có thể sử dụng nút Nhập riêng lẻ khi tạo các khoá Vault.", "ms_help_title5": "Chế độ nâng cao", - "ms_help_5": "Theo mặc định, Bluewallet sẽ tạo một Vault 2-trong-3. Để tạo một nhóm túc khác hoặc thay đổi loại địa chỉ, hãy kích hoạt chế độ nâng cao trong cài đặt." + "ms_help_5": "Theo mặc định, Bluewallet sẽ tạo một Vault 2-trong-3. Để tạo một nhóm túc khác hoặc thay đổi loại địa chỉ, hãy kích hoạt chế độ nâng cao trong cài đặt.", + "provide_signature_details": "Sử dụng thiết bị và ví nơi lưu khoá để ký giao dịch này", + "provide_signature_details_bluewallet": "Trong BlueWallet, vào menu màn hình Gửi và chọn ", + "provide_signature_next_steps": "Quét hoặc nhập giao dịch đã ký", + "provide_signature_next_steps_details": "Sau khi ví của bạn ký giao dịch thành công, quét mã QR được cung cấp hoặc nhập tệp đi kèm, sau đó xem lại tất cả chi tiết giao dịch trước khi truyền tải.", + "share": "Chia sẻ...", + "shared_key_detected": "Người đồng ký được chia sẻ", + "shared_key_detected_question": "Một người đồng ký đã được chia sẻ với bạn, bạn có muốn nhập không?", + "invalid_mnemonics": "Cụm từ ghi nhớ này có vẻ không hợp lệ.", + "invalid_cosigner": "Dữ liệu người đồng ký không hợp lệ", + "invalid_cosigner_format": "Người đồng ký không chính xác: Đây không phải là người đồng ký cho định dạng {format}.", + "this_is_cosigners_xpub": "Đây là XPUB của người đồng ký — sẵn sàng để được nhập vào một ví khác. Có thể chia sẻ an toàn.", + "this_is_cosigners_xpub_airdrop": "Nếu bạn chia sẻ qua AirDrop, người nhận phải đang ở màn hình điều phối.", + "view_edit_cosigners": "Xem/Sửa người đồng ký", + "this_cosigner_is_already_imported": "Người đồng ký này đã được nhập." }, "is_it_my_address": { - "title": "Đó là địa chỉ của tôi không?", + "title": "Đây có phải địa chỉ của tôi không?", "owns": "{label} sở hữu {address}", "enter_address": "Nhập địa chỉ", "check_address": "Kiểm tra địa chỉ", @@ -552,14 +617,21 @@ "change": "Tiền thừa", "coins_selected": "Coin đã chọn ({number})", "selected_summ": "{value} đã được chọn", - "empty": "Ví này không có coin hiện này", "freeze": "Đóng băng", "freezeLabel": "Đóng băng", "freezeLabel_un": "Giải tỏa", "header": "Kiểm soát coin", - "use_coin": "Sử dùng coin", + "use_coin": "Sử dụng coin", "use_coins": "Sử dụng các coin", - "tip": "Tính năng này cho phép bạn xem, dán nhãn, đóng băng hoặc chọn tiền để quản lý ví cải tiến. Bạn có thể chọn nhiều coin bằng cách chạm vào các vòng tròn màu." + "tip": "Tính năng này cho phép bạn xem, dán nhãn, đóng băng hoặc chọn tiền để quản lý ví cải tiến. Bạn có thể chọn nhiều coin bằng cách chạm vào các vòng tròn màu.", + "sort_status": " Trạng thái", + "empty": "Ví này hiện chưa có coin nào.", + "sort_asc": "Tăng dần", + "sort_desc": "Giảm dần", + "sort_height": "Chiều cao", + "sort_value": "Giá trị", + "sort_label": "Nhãn", + "sort_by": "Sắp xếp theo" }, "units": { "BTC": "BTC", @@ -581,7 +653,9 @@ "type_change": "Tiền thừa", "type_receive": "Nhận", "type_used": "Đã sử dụng", - "transactions": "Giao dịch" + "transactions": "Giao dịch", + "copy_private_key": "Sao chép khoá riêng tư", + "sensitive_private_key": "Cảnh báo: khoá riêng tư cực kỳ nhạy cảm. Tiếp tục?" }, "lnurl_auth": { "register_question_part_1": "Bạn có muốn đăng ký một tài khoản tại", @@ -601,9 +675,30 @@ }, "bip47": { "payment_code": "Mã thanh toán", - "payment_codes_list": "Danh sách mã thanh toán", - "who_can_pay_me": "Ai có thể trả cho tôi:", "purpose": "Mã có thể sử dụng lại và có thể chia sẻ (BIP47)", - "not_found": "Không tìm thấy mã thanh toán" + "not_found": "Không tìm thấy mã thanh toán", + "contacts": "Danh bạ", + "bip47_explain": "Mã có thể sử dụng lại và có thể chia sẻ", + "bip47_explain_subtitle": "BIP47", + "pay_this_contact": "Thanh toán cho liên hệ này", + "rename_contact": "Đổi tên liên hệ", + "copy_payment_code": "Sao chép mã thanh toán", + "hide_contact": "Ẩn liên hệ", + "rename": "Đổi tên", + "provide_name": "Cung cấp tên mới cho liên hệ này", + "add_contact": "Thêm liên hệ", + "provide_payment_code": "Cung cấp mã thanh toán", + "invalid_pc": "Mã thanh toán không hợp lệ", + "notification_tx_unconfirmed": "Giao dịch thông báo chưa được xác nhận, vui lòng chờ", + "failed_create_notif_tx": "Không thể tạo giao dịch on-chain", + "onchain_tx_needed": "Cần giao dịch on-chain", + "notif_tx_sent": "Đã gửi giao dịch thông báo. Vui lòng chờ giao dịch được xác nhận", + "notif_tx": "Giao dịch thông báo" + }, + "autofill_word": { + "title": "Từ cuối của cụm từ khôi phục", + "enter": "Nhập cụm từ ghi nhớ một phần của bạn", + "generate_word": "Tạo từ cuối", + "error": "Đầu vào không phải là cụm từ ghi nhớ một phần gồm 11 hoặc 23 từ. Vui lòng thử lại." } } diff --git a/loc/vocabulary.md b/loc/vocabulary.md new file mode 100644 index 00000000000..d9fbd6c9740 --- /dev/null +++ b/loc/vocabulary.md @@ -0,0 +1,343 @@ +# BlueWallet Translation Vocabulary + +Reference glossary for translating BlueWallet's UI strings. Use this file as ground truth when improving or generating translations with LLMs — it documents the **chosen** rendering of each term per language (not just any literal dictionary translation) plus the reasoning behind those choices. + +## How to use + +1. Find the term in **Glossary of terms** for the English meaning + nuance (e.g. "Bitcoin the network" vs "bitcoin the unit"). +2. Open the **per-language section** for the target locale. +3. Translate the new string consistent with the table. If you must deviate (context demands it), add a note here. +4. If a term is missing or marked **TODO**, propose a value and update both the per-language table and (if cross-cutting) the glossary. + +Conventions: +- Brand/protocol names (Bitcoin, Lightning, Electrum, LNDhub, LNURL, Tor, PSBT) generally stay **untranslated** — capitalise as in English. +- Unit `sats` is lowercase; `BTC` is uppercase. +- Where the existing locale file already commits to a rendering, the table reflects what the app ships today. Discrepancies = TODO. + +Sources: `loc/en.json` (canonical) + each locale file. Strings quoted verbatim where extracted. + +--- + +## External reference translations + +When picking or vetting a translation for a new term, cross-check how mature Bitcoin/Lightning wallets render the same term. Cite the source in the Notes column when borrowing a rendering. Do **not** blindly copy — verify the term still matches BlueWallet's UX context first. + +### Wallet sources (primary) + +| Project | Format | Path / URL | Coverage | +|---------|--------|------------|----------| +| **Bitcoin Core** — reference Bitcoin client | Qt `.ts` / `.xlf` per locale | (`bitcoin_.ts`) | Authoritative on-chain vocabulary (transaction, address, fee, mempool, PSBT, RBF, descriptor, UTXO). Most widely-translated. | +| **Electrum** — long-running reference SPV wallet | gettext `.po` per locale | (`/electrum.po`) | Decades of translator polish for on-chain + advanced UX (descriptors, fee bumping, hardware, Lightning). 60+ locales. | +| **Phoenix** (ACINQ) — Lightning wallet | Android XML `strings.xml` per locale | (`values-/strings.xml`) | Strong Lightning vocabulary (invoice, channel, liquidity, swap, BOLT11/12). | +| **Zeus** (ZeusLN) — Lightning mobile wallet | JSON per locale | (`.json`) | Lightning + node management terms; similar JSON layout to BlueWallet's own files. | +| **Trezor Suite** — hardware-wallet companion | JSON `messages.json` per locale | | Multisig / passphrase / derivation / Coinjoin vocabulary; precise hardware-wallet terms. | +| **Green** (Blockstream) — Bitcoin + Liquid wallet | Compose MP string resources per locale | (`values-/strings.xml`) | Multisig, hardware, Lightning + Liquid asset vocabulary. | +| **Bisq** — P2P Bitcoin exchange | Java `.properties` per locale | (`displayStrings_.properties`) | Trade, fee, escrow, deposit terminology; mature for ~30 languages. | +| **Cake Wallet** — multi-currency mobile wallet | Flutter `.arb` per locale | (`strings_.arb`) | 25+ languages including non-Latin (Burmese, Hausa, Yoruba, Urdu). Useful when other refs don't cover a locale. | +| **Mycelium** — Android Bitcoin wallet | Android XML per locale | (`values-/strings.xml`) | Long-standing Android-Bitcoin idioms (legacy mobile vocabulary). | +| **Samourai Wallet** (archived) — Android privacy wallet | Android XML per locale | (`values-/strings.xml`) | Coin control, mixing, fee bumping. Archive only — verify terms still current. | +| **Breez** — Lightning self-custodial mobile | Dart i18n | | Lightning UX terms (send, receive, swap, channel state) for the mobile flow. | + +### Wikipedia (native-script references) + +For locales where wallet projects don't cover the language well, or to validate a culturally-natural rendering of a concept, fetch the **Wikipedia article in that language** for the underlying term. Wikipedia article titles + opening paragraphs are often the most-cited native rendering of a technical concept. + +Useful interwiki anchors (English title → follow the language switcher in the left sidebar): + +| English term | Wikipedia anchor | +|--------------|------------------| +| Bitcoin | | +| Lightning Network | | +| Bitcoin Core | | +| Cryptographic hash function | | +| Public-key cryptography | | +| Mnemonic phrase / BIP39 | | +| Multi-signature | | +| QR code | | +| Hexadecimal | | +| Plausible deniability | | +| Tor (network) | | + +Use Wikipedia only as a tie-breaker / cultural validator. Wallet UI prefers a shorter, terser rendering than Wikipedia's article title (e.g. `Адреса` not `Криптовалютна адреса`). + +### How to use these + +1. Identify the term and target locale (e.g. "derivation path" in Czech). +2. Open the matching upstream file (Bitcoin Core's `bitcoin_cs.ts`, Electrum's `cs/electrum.po`, etc.). +3. Search for the English term; copy the established translation if it fits BlueWallet's screen. +4. If sources disagree, fall back to the Wikipedia article in the target language to see which form is most idiomatic. +5. Update `loc/.json` and the corresponding row in `loc/vocabulary/.md`; in **Notes**, cite the source, e.g. `Notes: matches Bitcoin Core cs` or `Notes: per cs.wikipedia.org/wiki/Lightning_Network`. + +### Priority when sources disagree + +1. **Bitcoin Core** — on-chain terms (transaction, address, fee, PSBT, RBF, descriptor, UTXO). +2. **Electrum** — fallback on-chain + early Lightning + hardware. +3. **Phoenix** / **Zeus** / **Breez** — Lightning-specific (invoice, channel, swap, liquidity). +4. **Trezor Suite** — multisig, hardware, passphrase, Coinjoin. +5. **Green** / **Bisq** — tie-breakers; Bisq is good for trade/fee terms. +6. **Cake** / **Mycelium** / **Samourai** — mobile UX + locales the others miss. +7. **Wikipedia** in the target language — cultural fit / native rendering. + +--- + +## Categories + +Terms are grouped into 10 categories. Same order is used in the glossary and in every per-language section. + +1. **Brand & protocol** — proper nouns; usually untranslated. +2. **Units & amounts** — bitcoin, sats, fee-rate units. +3. **Wallet, keys & seeds** — wallet types, keys, mnemonics, derivation, fingerprints. +4. **On-chain transactions** — tx parts, states, broadcast, explorers, layer filters. +5. **Fees & fee bumping** — fee terminology and RBF / CPFP. +6. **Lightning** — invoices, payments, preimage. +7. **Multisig & advanced addressing** — co-signers, PSBT, BIP47, SilentPayment. +8. **Coin control** — UTXO management. +9. **Security & storage** — encryption, biometrics, plausible deniability. +10. **Backup, import & UX** — backup/restore, common verbs, QR/clipboard/memo. + +--- + +## Vocabulary entry conventions + +These rules apply to every per-language file. The English glossary below uses the same conventions. + +1. **POS column.** Every term carries a part-of-speech: `noun` / `verb` / `adj` / `acronym` / `brand`. The Translation in per-language files **must** match the POS. If the English term is a noun, store a noun in the target language. If a UI string actually uses the verb, list both forms (see rule 4). +2. **Canonical form = noun unless UI uses verb.** Default to the noun form (e.g. *Payment* → noun *Платіж*, **not** verb *Оплатити*). Add the verb form as a secondary entry only when at least one shipped UI string uses it. +3. **Target-locale natural casing.** Do **not** mirror English Title Case. Use the casing the target locale uses for ordinary nouns (Cyrillic, Greek, Arabic, etc. are usually lowercase). Brand rows are the only exception. +4. **Multi-form syntax: `compact / explanatory` (or `noun / verb`), leftmost = preferred.** When two valid forms exist (chip-label vs body-text, technical vs mainstream, noun vs verb), list them slash-separated in the Translation column. The Notes column tags them, e.g. `chip / body` or `noun / verb` or `technical / mainstream`. Translator picks the form that fits the screen. +5. **Brand rows are not translated.** Rows marked `brand` in the POS column keep the English spelling unless the target locale has an established Cyrillic/Hangul/Devanagari/etc. transliteration in widespread use. When in doubt, keep Latin. +6. **Acronyms stay as-is; gloss is optional.** Rows marked `acronym` keep the English letters (RBF, CPFP, PSBT, WIF, BIP38, BIP47, xpub, UTXO, LNURL). A target-locale explanatory gloss may appear in Notes, never as the Translation value. +7. **Anti-meaning callouts.** If a term has a dangerous look-alike in many locales (verb-vs-noun, modify-vs-leftover, etc.), the Notes column starts with `⚠️ NOT `. Don't drop these callouts when filling. +8. **Source citation.** When you fill a row from an upstream wallet or Wikipedia, append the source: `Notes: · Bitcoin Core cs` or `· uk.wikipedia.org/wiki/
`. + +--- + +## Glossary of terms + +### 1. Brand & protocol + +| Term | POS | Meaning | +|------|-----|---------| +| **Bitcoin** | brand | The network/protocol. Capitalised. As the unit, lowercase "bitcoin" or `BTC`. | +| **Lightning / Lightning Network** | brand | Layer-2 payment network on top of Bitcoin. Usually left in English. | +| **Electrum (server)** | brand | Electrum protocol server BlueWallet uses for on-chain data. | +| **LNDhub** | brand | Custodial Lightning backend service. | +| **LND** | brand | Lightning Network Daemon. | +| **LNURL** | brand | URL-based Lightning protocol family (LNURL-pay, LNURL-withdraw). | +| **Tor** | brand | The Tor anonymity network. | +| **Orbot** | brand | Tor proxy app on Android required for routing BlueWallet over Tor. | +| **GroundControl** | brand | BlueWallet's open-source push-notification server. | + +### 2. Units & amounts + +| Term | POS | Meaning | +|------|-----|---------| +| **bitcoin / BTC** | noun | Unit of currency. 1 BTC = 100,000,000 sats. `BTC` is the ticker; `bitcoin` is the unit name. | +| **sats / satoshis** | noun | Smallest Bitcoin unit (1 sat = 0.00000001 BTC). Lowercase. | +| **sat/vByte** | noun | Fee rate unit: satoshis per virtual byte. Casing matters: lowercase `sat`, capital `B` in `vByte`. | +| **vByte** | noun | Virtual byte — SegWit-discounted size unit. | + +### 3. Wallet, keys & seeds + +| Term | POS | Meaning | +|------|-----|---------| +| **Wallet** | noun | A keyset + UI for managing funds. Use target-locale lowercase. | +| **Vault** | noun | BlueWallet's user-facing name for a multisig wallet. **NOT** a brand — translate using target-locale word for "safe / strongbox" (e.g. `сейф`, `Tresor`, `coffre-fort`). | +| **Watch-only** | adj | Wallet that holds only public keys; can view but not spend. ⚠️ NOT "view mode" / "read mode" — it's specifically a wallet type. | +| **Hardware wallet** | noun | External signing device (Coldcard, Trezor, Ledger). | +| **Seed** | noun | The BIP39 mnemonic. In mainstream UI prefer the locale's word for "recovery phrase" over a transliteration of "seed". List both forms when applicable (`seed-фраза / фраза відновлення`). | +| **Mnemonic / Seed phrase** | noun | BIP39 word list that encodes the wallet's master seed. | +| **Passphrase** | noun | BIP39 optional 25th word. ⚠️ NOT the device "passcode" and NOT the app "password" — use a distinct word (e.g. `Кодова фраза`, not `Пароль`). | +| **Public key** | noun | Asymmetric pubkey; derives addresses. Target-locale lowercase. | +| **Private key** | noun | Asymmetric privkey; signs transactions. Target-locale lowercase. | +| **WIF** | acronym | Wallet Import Format — base58-encoded private key. Letters kept; gloss in Notes if helpful. | +| **xpub / extended public key** | acronym | BIP32 extended public key. Letters kept; prefer lowercase `xpub` (only force `XPUB` if the locale's convention does). | +| **Descriptor** | noun | Output descriptor — script template describing how to spend an output (`wpkh(...)`, `wsh(multi(...))`). | +| **Derivation path** | noun | BIP32 path (e.g. `m/84'/0'/0'`). | +| **Master fingerprint** | noun | First 4 bytes of HASH160(master pubkey). Identifies a wallet's root key. | +| **BIP38** | acronym | Password-protected private-key encoding. Letters kept. ⚠️ NOT a verb / NOT "password" alone — keep `BIP38` as the term. | + +### 4. On-chain transactions + +| Term | POS | Meaning | +|------|-----|---------| +| **Transaction (tx)** | noun | An on-chain Bitcoin transaction. | +| **Address** | noun | A destination for receiving on-chain bitcoin. | +| **Input** | noun | A tx input — spends a previous UTXO. ⚠️ NOT "login / entrance" — pair with `вихід` / `output` if the locale's "вхід"-like word is ambiguous; list ` / ` (e.g. `вхід / вхід транзакції`). | +| **Output** | noun | A tx output — creates a spendable UTXO. ⚠️ NOT the UI recipient label "To:" (that is a separate UI string). | +| **UTXO** | acronym | Unspent Transaction Output. Letters kept. | +| **Change (output)** | noun | The leftover output that returns to the sender's wallet. ⚠️ NOT the verb "to change/modify" — must be a noun (e.g. `здача`, `Wechselgeld`, `Resto`). | +| **Hex** | noun | Hexadecimal-encoded transaction blob. List as `hex / hex-дані / шістнадцяткові дані` style: short form first, explanatory form second. ⚠️ NOT "hash" and NOT "transaction data". | +| **Pending** | adj | Tx broadcast but not yet in a block. Adjective/state form; **not** the noun "expectation/waiting". | +| **Unconfirmed** | adj | Tx in mempool, no confirmations. Use the adjective state form. | +| **Confirmed** | adj | Tx mined with ≥1 confirmation. Adjective state form. | +| **Mempool** | noun | Pool of unconfirmed transactions. | +| **Broadcast** | verb / noun | Verb: to submit a signed tx to the network. List both `verb / noun` if both are used in the UI (button vs status). | +| **Block explorer** | noun | Web service for viewing on-chain data. | +| **Onchain** | adj | Layer-1 Bitcoin filter chip. List `compact / explanatory` (e.g. `он-чейн / у блокчейні`). | +| **Offchain** | adj | Lightning (L2) filter chip. List `compact / explanatory` (e.g. `оф-чейн / поза блокчейном`). | + +### 5. Fees & fee bumping + +| Term | POS | Meaning | +|------|-----|---------| +| **Fee / Network fee / Mining fee** | noun | The miner/network fee paid for an on-chain tx. | +| **Fee Bump** | noun | Action of raising a tx's fee post-broadcast (umbrella for RBF + CPFP). | +| **RBF (Replace-by-fee)** | acronym | BIP125. Acronym kept; gloss optional in Notes (e.g. "замінити за комісією"). | +| **CPFP (Child-pays-for-parent)** | acronym | Fee-bumping by attaching a high-fee child tx. Acronym kept. ⚠️ NOT a verb like "Create" — `CPFP` stays as `CPFP`. | +| **Speed Up** | verb | User-facing label for RBF in tx detail screen. Verb form (button label). | + +### 6. Lightning + +| Term | POS | Meaning | +|------|-----|---------| +| **Invoice** | noun | A payment request. List `technical / mainstream` (e.g. `інвойс / рахунок` or `інвойс / платіжний запит`). | +| **Lightning Invoice** | noun | BOLT11 payment request encoded as `lnbc…`. Pair "Lightning" (brand) with the localised noun: `інвойс Lightning / платіжний запит Lightning`. | +| **Preimage** | noun | The 32-byte secret that, when hashed, equals the payment hash; revealing it settles a Lightning payment. Math term `preimage` (e.g. `прообраз`). | +| **Payment** | noun | A Lightning payment (distinct from on-chain "Transaction"). ⚠️ NOT the verb "to pay" — must be a noun (e.g. `Платіж`, **not** `Оплатити`). | +| **Expired** | adj | Invoice/state that passed its validity window. Adjective/state form. | + +### 7. Multisig & advanced addressing + +| Term | POS | Meaning | +|------|-----|---------| +| **Co-signer / Signer** | noun | Participant in a multisig setup who provides a signature. ⚠️ NOT "co-owner" — must be a signer noun (e.g. `співпідписант`, **not** `співвласник`). | +| **Quorum** | noun | The m-of-n threshold required to spend from a multisig. List ` / ` (e.g. `кворум / поріг підписів`). | +| **PSBT** | acronym | Partially Signed Bitcoin Transaction (BIP174). Letters kept. | +| **Provide signature** | verb | Action: sign a PSBT as one of the multisig co-signers. | +| **BIP47 / Payment Code** | acronym + noun | `BIP47` is an acronym (kept as-is). `Payment Code` is a translatable noun (e.g. `платіжний код`). | +| **Notification transaction** | noun | BIP47-specific 0-value tx that announces a payment code to the recipient. | +| **SilentPayment** | brand + noun | BIP352 reusable static address scheme. The protocol name `Silent Payments` (note plural) stays in English; an optional explanatory locale gloss may follow (e.g. `Silent Payments / тихі платежі`). | + +### 8. Coin control + +| Term | POS | Meaning | +|------|-----|---------| +| **Coin Control** | noun | Per-UTXO selection feature. ⚠️ NOT Title Case in target locale. Prefer the technical UTXO form when possible (e.g. `керування UTXO / керування монетами`). | +| **Frozen** | adj | UTXO marked unspendable by the user. ⚠️ NOT the verb "to freeze" — must be an adjective/state form (e.g. `заморожено`, **not** `заморозити`). | + +### 9. Security & storage + +| Term | POS | Meaning | +|------|-----|---------| +| **Encrypted storage / Storage encryption** | noun | Whole-app encryption of the wallet store with a password. ⚠️ NOT Title Case in target locale. | +| **Plausible Deniability** | noun | Feature creating a fake encrypted storage with a different password, to disclose under coercion. ⚠️ NOT Title Case in target locale. | +| **Biometrics** | noun | Face ID / Touch ID / fingerprint unlock. | +| **Passcode** | noun | Device-level unlock code (iOS/Android). ⚠️ NOT the same as app "password" — use a distinct word (e.g. `код доступу`, **not** `пароль`). | + +### 10. Backup, import & UX + +| Term | POS | Meaning | +|------|-----|---------| +| **Backup / Export** | noun / verb | Saving wallet data outside the app. List `noun / verb` (e.g. `резервна копія / зробити резервну копію`). | +| **Restore** | verb / noun | Recreate a wallet from its backup/mnemonic. List `verb / noun` (e.g. `відновити / відновлення`). | +| **Import** | verb / noun | Loading a wallet from a backup or external source. List `verb / noun`. | +| **Voucher** | noun | Azte.co prepaid bitcoin voucher. | +| **Redeem** | verb | Convert a voucher into wallet balance. ⚠️ NOT "buy to wallet" / NOT "transfer" — use the locale's word for *activate* or *cash in* (e.g. `активувати / погасити`). | +| **Send** | verb | Primary user action: spend funds. | +| **Receive** | verb | Primary user action: get an address/invoice. | +| **Settings** | noun | App preferences screen. | +| **Confirm** | verb / noun | User action. Also: "confirmations" = blocks on a tx (noun, plural). | +| **QR Code** | noun | Square barcode used to share addresses / invoices / PSBTs. | +| **Clipboard** | noun | OS clipboard. | +| **Memo** | noun | Sender note on outgoing tx. | +| **Description** | noun | Free-text label on a receive invoice. | +| **Label** | noun | Free-text annotation on a wallet or address. | + +--- + +## Per-language sections + +Each locale lives in its own file under [`vocabulary/`](vocabulary/). One Markdown file per locale, named to match the JSON file (e.g. `pl.md` ↔ `loc/pl.json`). Open the file matching the locale you are editing. + +--- + +## Open TODOs / known issues + +### Typos and clear bugs in shipped strings + +- **es** `transactions.transaction`: `Transaccion` → `Transacción` (missing accent). +- **es** `lndViewInvoice.lightning_invoice`: `Factura Lighting` → `Factura Lightning`. +- **es** `send/broadcastNone`: "Introduce el hash de la transacción" → should be "hex" not "hash". +- **fr** `lndViewInvoice.lightning_invoice`: `Facture Ligthning` → `Facture Lightning`. +- **fr** `cc.change`: ungrammatical `monnaie rendu` → `Monnaie rendue` or just `Monnaie`. +- **fr** `wallets/details_master_fingerprint`: `Empreinte maitresse` → `Empreinte maîtresse`. +- **fr** `cc/header`: `Controle des UTXO` → `Contrôle des UTXO`. +- **fr** `send/details_adv_fee_bump`: `Autoriser le Fee Bum` → `Autoriser le Fee Bump` (missing "p"). +- **fr** `azteco/title`: references `Azte.com` → should be `Azte.co`. +- **de** `cc.change`: `Ändern` (verb "to change") → `Wechselgeld` (change-output). +- **de** `multisig/provide_signature`: `Schlüssel eingeben` (= "enter key") → `Signatur bereitstellen`. +- **pt_br** `send/broadcastNone`: "Insira o Hash da Transação" → should be "Hex" not "Hash". +- **pt_br** `multisig/quorum`: `Quantidade mínima {m} de máxima {n}` semantically wrong (m and n are signers-required and total). Suggest `{m} de {n} (quórum)`. +- **ko** `cc.change`: `변경` ("to change/alter") → `거스름돈` (change-output). +- **ko** `azteco/title`: `제목` ("title") → `바우처` or `Azte.co 바우처`. +- **ko** `receive/details_label`: `형태` ("form/shape") → `설명` (description). +- **ko** `transactions/rbf_title`: `급행 수수료(CPFP)` references CPFP but feature is RBF — fix the acronym. +- **ar** `cc.change`: `تغيير` (alteration) → `الباقي` (remainder/change). +- **ar** Azteco strings reference `Atze.co` → `Azte.co`. +- **pl** `send/broadcastNone`: contains `postaci szestnastkowej` — typo; should be `postaci szesnastkowej`. +- **pl** `azteco/redeem`: ships `Odbierz` ("collect") for Redeem — recommended is `Zrealizuj` / `Aktywuj` per Bitcoin Core pl / Cake pl. +- **th** Mnemonic: shipped English passthrough `backup phrase` — should be localised (e.g. `วลีนีโมนิก` / `วลีกู้คืน`). +- **th** Biometrics: shipped English passthrough `Biometrics` — should be localised (e.g. `ไบโอเมตริก` / `การยืนยันตัวตนทางชีวภาพ`). +- **th** `send/details_adv_fee_bump`: shipped `อนุญาติให้เพิ่มธรรมเนียมได้` has typo `อนุญาติ` → should be `อนุญาต`. +- **vi_vn** Vault: shipped UI uses Latin `Vault` — should be Vietnamese `két` / `két an toàn`. +- **vi_vn** Passphrase: shipped UI keeps Latin `Passphrase` — should be `cụm mật khẩu`. +- **vi_vn** Derivation path: shipped `Đường dẫn xuất` is incomplete — should be `đường dẫn dẫn xuất`. +- **vi_vn** Pending: shipped `Đang chờ giải quyết` is wordy — prefer `đang chờ`. +- **vi_vn** Confirmed: shipped `xác nhận` is verb — should be adjective `đã xác nhận`. +- **vi_vn** Block explorer: shipped Latin `Block Explorer` — should be `trình khám phá khối`. +- **vi_vn** Broadcast: shipped `Phát sóng` literally = radio broadcast — prefer `phát đi mạng` / `truyền tải`. +- **vi_vn** Speed Up: shipped `Tăng phí` collides with Fee Bump — should be `tăng tốc`. +- **vi_vn** Memo: shipped `Ghi nhớ` (= "remember") is wrong sense — should be `ghi chú`. +- **vi_vn** Label: shipped `dán nhãn` is verb ("to label") — should be noun `nhãn`. +- **ms** `settings.groundcontrol_explanation`: body has typo `GrounControl` → `GroundControl`. +- **ms** mnemonic strings ship misspelling `nemonik` → standard ms `mnemonik`. +- **ms** `sat/vBait` / `vBait` localise "byte"→"bait" — per convention keep Latin `sat/vByte` / `vByte`. +- **ms** wallet/vault/hardware-wallet/master-fingerprint/plausible-deniability strings ship Title Case (`Dompet`, `Dompet Perkakas`, `Cap Jari Induk`, etc.) — should be lowercase. +- **ms** `XPUB` shipped uppercase; convention prefers lowercase `xpub`. +- **ms** `transactions.details_outputs` UI uses `Kepada` (= "To:" preposition) for transaction-output noun — must be a noun (`keluaran`). +- **ms** `cc.change`: shipped `Ubah` (verb "to change/modify") → noun form (`baki` / `duit baki`). +- **ms** `unconfirmed`: shipped `tak terperaku` uses uncommon root `peraku` — prefer standard root `sah` (`belum disahkan` / `tidak disahkan`). +- **ms** `confirmed`: shipped `perakuan` is a noun using non-standard `peraku` — prefer `disahkan` (adj). +- **ms** `cpfp_exp`: glosses CPFP as `Cabang Pembayar Fi Pokok` (creative backronym, not standard) — use plain gloss "anak bayar untuk induk". +- **ms** Payment shipped `Bayar` is a verb — should be noun (`bayaran` / `pembayaran`). +- **ms** Frozen shipped `Bekukan` is a verb ("freeze!") — should be adj/state (`dibekukan` / `beku`). +- **ms** Passcode shipped `Kata Laluan` collides with password term — use `kod laluan`. +- **ms** Restore shipped `mengembalikan` is gerund — prefer base verb `kembalikan`. +- **ms** Confirm shipped `Pasti` (= "sure/certain") is awkward — use `sahkan`. +- **ms** Clipboard shipped `Papan Sepit` (= "pinching board") is non-standard — prefer `papan klip`. +- **ms** Label shipped `melabel` is verb form — should be noun (`label` / `tanda`). +- **ms** Coin Control shipped Title Case — should be lowercase `kawalan UTXO` / `kawalan duit`. + +### Ambiguity / consistency issues + +- **ru** `wallets/import_passphrase` = "Пароль" — collides with "password". Recommend "Passphrase" or "Кодовая фраза". +- **pt_br** `wallets/import_passphrase` = "Senha" — same ambiguity with "password". +- **ko** `wallets/import_passphrase` = "암호" — same ambiguity. +- **ko** mnemonic backup screen mixes English "mnemonic phrase" inside Korean text — localise to `시드 문구` / `복구 문구`. +- **ko** wallet term inconsistent: `지갑` vs `월렛`. Standardise on `지갑`. +- **ko** clipboard uses non-standard `붙여넣기판` — recommend `클립보드`. +- **zh_cn** `sat/vByte` rendered `聪/字节` drops the "v" prefix; technically incorrect vs SegWit-aware vByte. +- **jp** `wallets/details_derivation_path`: drop redundant English `(derivation path)` parenthetical. +- **ar** `wallets/details_derivation_path`: drop redundant English `(derivation path)` parenthetical. +- **da** shipped UI: `XPUB` uppercase — vocabulary prefers lowercase `xpub`. +- **th** shipped UI: `XPUB` uppercase — vocabulary prefers lowercase `xpub`. +- **th** Confirmed: shipped `ยืนยัน` is verb form — prefer adj/state `ยืนยันแล้ว`. +- **th** `wallets/import_passphrase` Passcode: shipped `รหัสผ่าน` collides with "password" — use distinct form (e.g. `รหัสผ่านอุปกรณ์` / `PIN`). +- **id** shipped UI: `LNDHub` casing — vocabulary prefers `LNDhub` per glossary casing. +- **id** shipped UI: `XPUB` uppercase — vocabulary prefers lowercase `xpub`. +- **id** shipped UI mixes `backup` Latin — prefer `cadangan` per Bitcoin Core id + Cake id. +- **id** shipped UI: `setting` is an anglicism — fix to `pengaturan` per Bitcoin Core id + Cake id. +- **id** shipped UI: `Tarif` for Fee (= tariff/rate) is misleading — prefer `biaya` per id.wikipedia.org/wiki/Bitcoin. +- **vi_vn** shipped UI: `XPUB` uppercase — vocabulary prefers lowercase `xpub`. +- **vi_vn** Passcode: shipped `Mật khẩu` collides with "password" — should be `mã PIN` / `mã mở khóa`. + +--- + +## Adding a new term + +1. Decide which of the 10 categories it belongs to. +2. Add a row to **Glossary of terms** under that category with the English meaning. +3. Add a row to each per-language section under the same category (between the category's divider row and the next divider row). Mark unknown values **TODO**. +4. If the term is brand/protocol (capitalised proper noun), default to **keep in English** across locales unless a locale has an established convention. +5. If you change a shipped string in a locale's `.json`, update the corresponding row here in the same PR. diff --git a/loc/vocabulary/ar.md b/loc/vocabulary/ar.md new file mode 100644 index 00000000000..d6f1e268282 --- /dev/null +++ b/loc/vocabulary/ar.md @@ -0,0 +1,100 @@ +# Arabic translation vocabulary (`ar.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +> RTL locale. Brand names appear in Latin script inside the surrounding script; values below are the rendered form used in `loc/ar.json`. + +RTL. Brand names appear in Latin inside Arabic text; the table below gives the Arabic rendering used. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · gloss: بيتكوين · ar.wikipedia.org/wiki/بيتكوين. | +| Lightning | Lightning | brand kept Latin · gloss: البرق (lit. "lightning") used in body text. | +| Electrum | Electrum | Brand. | +| LNDhub | LNDhub | — | +| LND | LND | — | +| LNURL | LNURL | — | +| Tor | Tor | — | +| Orbot | Orbot | — | +| GroundControl | GroundControl | — | +| **_Units & amounts_** | | | +| bitcoin / BTC | BTC | — | +| sats / satoshi | ساتوشي | noun · Transliteration · lowercase per convention. | +| sat/vByte | ساتوشي لكل بايت افتراضي | Lit. "satoshi per virtual byte". | +| vByte | بايت افتراضي | — | +| **_Wallet, keys & seeds_** | | | +| Wallet | محفظة / المحافظ | — | +| Vault | خزنة | noun · "safe/strongbox" · ⚠️ NOT a brand; translated. | +| Watch-only | للقراءة فقط | adj · ⚠️ NOT "view mode" / NOT "read mode" — wallet type. | +| Hardware wallet | محفظة جهاز | noun · external signing device. · Bitcoin Core ar | +| Seed | seed / عبارة الاسترداد | noun · technical / mainstream · BIP39 mnemonic. | +| Mnemonic | عبارة الاسترداد | noun · "recovery phrase". | +| Passphrase | عبارة المرور | ⚠️ NOT كلمة المرور (password) — must be distinct. Lit. "passphrase". · Bitcoin Core ar | +| Public key | المفتاح العام | — | +| Private key | المفتاح الخاص | — | +| WIF | WIF | — | +| xpub | xpub | — | +| Descriptor | واصف | Lit. "descriptor". · Bitcoin Core ar | +| Derivation path | مسار الاشتقاق | Note: shipped has redundant `(derivation path)` parenthetical. | +| Master fingerprint | البصمة الرئيسية | — | +| BIP38 | BIP38 | — | +| **_On-chain transactions_** | | | +| Transaction | المعاملة / العملية | noun · canonical / UI alternative · "المعاملة" canonical; app ships "العملية". · Bitcoin Core ar | +| Address | العنوان | — | +| Input | مدخل / مدخلات | tx input (noun). · Bitcoin Core ar | +| Output | مخرج / مخرجات | tx output (noun). ⚠️ NOT the UI label "إلى:" (To:). · Bitcoin Core ar | +| UTXO | UTXO | Acronym kept. Gloss: مخرج معاملة غير منفق. · Bitcoin Core ar | +| Change | الباقي | ⚠️ NOT تغيير (verb "to change/alter"). Shipped `cc.change` ships تغيير — should be الباقي (remainder). | +| Hex | Hex | Latin kept. | +| Pending | قيد الانتظار | — | +| Unconfirmed | غير مؤكدة | — | +| Confirmed | مؤكدة | — | +| Mempool | الذاكرة المعلقة | — | +| Broadcast | بث / إذاعة | verb / noun · button vs status. | +| Block explorer | مستكشف الكتل | noun · web service for on-chain data. · Bitcoin Core ar | +| Onchain | أون-تشين / على السلسلة | adj · compact / explanatory · Layer-1 filter chip. | +| Offchain | أوف-تشين / خارج السلسلة | adj · compact / explanatory · Lightning (L2) filter chip. | +| **_Fees & fee bumping_** | | | +| Fee | الرسوم | — | +| Fee Bump | السماح بزيادة الرسوم | — | +| RBF | RBF — الاستبدال بالرسوم | Acronym + Arabic gloss. | +| CPFP | CPFP | acronym · letters kept · gloss: تسريع المعاملة. | +| Speed Up | تسريع العملية | verb · UI button label. | +| **_Lightning_** | | | +| Invoice | فاتورة / برقية | "فاتورة" for on-chain; "برقية" used specifically for LN invoice. | +| Lightning Invoice | فاتورة Lightning / طلب دفع Lightning | noun · pair "Lightning" (brand) with localised noun · ⚠️ NOT برقية (telegram). | +| Preimage | صورة أولية | Math term: pre-image of a hash. | +| Payment | دفعة / مدفوعات | ⚠️ NOT the verb دفع/يدفع — must be noun. · Bitcoin Core ar · ar.wikipedia.org/wiki/شبكة_البرق | +| Expired | منتهية الصلاحية | — | +| **_Multisig & advanced addressing_** | | | +| Co-signer | شريك التوقيع | ⚠️ NOT شريك ملكية (co-owner) — must be a signer noun. · Electrum ar_SA | +| Quorum | النصاب / عتبة التوقيعات | noun · canonical / UI alternative. | +| PSBT | PSBT | — | +| Provide signature | قدم توقيعًا | — | +| BIP47 / Payment Code | BIP47 / رمز الدفع | Acronym kept; "Payment Code" → رمز الدفع (literal). | +| Notification transaction | معاملة الإشعار | BIP47 0-value announce tx. Literal calque. | +| SilentPayment | Silent Payments / المدفوعات الصامتة | Brand kept in Latin; gloss "silent payments". | +| **_Coin control_** | | | +| Coin Control | التحكم في UTXO / التحكم في العملات | noun · ⚠️ NOT Title Case · technical / mainstream. | +| Frozen | مجمدة | ⚠️ NOT تجميد (gerund "freezing") and NOT verb جمّد — use adj/state form. | +| **_Security & storage_** | | | +| Encrypted storage | التخزين المشفر | Lit. "the encrypted storage". · Bitcoin Core ar (محفظة مشفرة) | +| Plausible Deniability | الإنكار المقبول | noun · ⚠️ NOT Title Case. | +| Biometrics | القياسات الحيوية | — | +| Passcode | رمز المرور | ⚠️ NOT كلمة المرور (app password) — distinct device-level code. | +| **_Backup, import & UX_** | | | +| Backup | النسخ الاحتياطي / إنشاء نسخة احتياطية | noun / verb · save outside app. | +| Restore | استعادة / الاستعادة | verb / noun · recreate from backup. | +| Import | استيراد / الاستيراد | verb / noun · load from backup/external. | +| Voucher | قسيمة | Note: shipped Azteco strings reference `Atze.co` — typo, should be `Azte.co`. | +| Redeem | الاسترداد | ⚠️ NOT "buy to wallet" / NOT "transfer" — sense is "cash in / activate" the voucher. | +| Send | إرسال | — | +| Receive | استلام | — | +| Settings | الإعدادات | — | +| Confirm | تأكيد / التأكيد | verb / noun · button vs status. | +| QR Code | رمز QR / رمز الاستجابة السريعة | Compact + explanatory. · ar.wikipedia.org/wiki/رمز_استجابة_سريعة | +| Clipboard | الحافظة | — | +| Memo | مذكرة | — | +| Description | الوصف | — | +| Label | علامة | — | diff --git a/loc/vocabulary/be@tarask.md b/loc/vocabulary/be@tarask.md new file mode 100644 index 00000000000..10544018ed8 --- /dev/null +++ b/loc/vocabulary/be@tarask.md @@ -0,0 +1,96 @@ +# Belarusian (Taraškievica) translation vocabulary (`be@tarask.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | — | +| Lightning | Lightning | brand — kept Latin · be-tarask.wikipedia.org has no Lightning Network article. | +| Electrum | Electrum | brand — kept Latin. | +| LNDhub | LNDhub | brand — kept Latin. | +| LND | LND | brand — kept Latin. | +| LNURL | LNURL | acronym — letters kept. | +| Tor | Tor | brand — kept Latin · be-tarask.wikipedia.org has no Tor (network) article. | +| Orbot | Orbot | brand — kept Latin. | +| GroundControl | GroundControl | brand — kept Latin. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | unit — lowercase `bitcoin`, uppercase `BTC` per glossary. | +| sats | sats | lowercase per glossary. | +| sat/vByte | sat/vByte | casing kept per glossary. | +| vByte | vByte | casing kept per glossary. | +| **_Wallet, keys & seeds_** | | | +| Wallet | кашалёк | shipped value; Bitcoin Core be / be-tarask.wikipedia.org/wiki/Біткойн use `гаманец` — both valid Belarusian, кашалёк is more colloquial. | +| Vault | сэйф | "safe / strongbox" — Tarask `э`. Vault is NOT a brand per glossary. | +| Watch-only | толькі для прагляду | ⚠️ NOT "view mode" — specifically a wallet type. | +| Hardware wallet | апаратны кашалёк | matches shipped Wallet=кашалёк · technical phrase, target-locale lowercase. | +| Seed | Сід-фраза / фраза-аднаўленьня | technical / mainstream · established crypto loanword `Сід-фраза` (shipped via locsync31 cleanup); фраза-аднаўленьня = recovery phrase (Tarask `-ньне` suffix) · ⚠️ NOT `семя` (biological seed/semen — wrong register). | +| Mnemonic | мнэмоніка / фраза-аднаўленьня | technical / mainstream · be-tarask.wikipedia.org spelling `мнэмоніка` (Tarask `э`); фраза-аднаўленьня = recovery phrase (Tarask `-ньне` suffix). | +| Passphrase | кодавая фраза | ⚠️ NOT "password" (пароль) and NOT device passcode · Bitcoin Core be uses `Кодавая фраза`. | +| Public key | публічны ключ | target-locale lowercase. | +| Private key | прыватны ключ | target-locale lowercase · Bitcoin Core be uses `Прыватныя ключы`. | +| WIF | WIF | acronym — letters kept (Wallet Import Format). | +| xpub | xpub | acronym — lowercase per glossary. | +| Descriptor | дэскрыптар | Tarask `э`; output descriptor. | +| Derivation path | шлях дэрывацыі | technical phrase; Tarask `э`. | +| Master fingerprint | майстар-адбітак | "master fingerprint"; Tarask `майстар` (vs regular `майстер`). | +| BIP38 | BIP38 | acronym — letters kept · ⚠️ NOT a verb / NOT "password". | +| **_On-chain transactions_** | | | +| Transaction | трансакцыя | Tarask form (per shipped `трансляцыі`-style spelling with `с`) · Electrum be_BY uses `Трансакцыя`; Bitcoin Core be uses `Транзакцыя`. Tarask convention prefers `трансакцыя`. | +| Address | адрас | target-locale lowercase · Bitcoin Core be, Electrum be_BY both use `Адрас` · be-tarask.wikipedia.org/wiki/Біткойн uses `адрас`. | +| Input | уваход / уваход трансакцыі | ⚠️ NOT "login / entrance" — tx input. Pair short/full forms per glossary rule. | +| Output | выхад / выхад трансакцыі | ⚠️ NOT the UI label "To:" — tx output noun. | +| UTXO | UTXO | acronym — letters kept (Unspent Transaction Output). | +| Change | рэшта | ⚠️ NOT the verb "to change/modify" — must be the noun "leftover/change-output". Tarask `э`. | +| Hex | hex / шаснаццатковы | short / explanatory · ⚠️ NOT "hash" and NOT "transaction data". | +| Pending | у чаканьні | adjective/state form; Tarask `-ньні` suffix · ⚠️ NOT noun "expectation". | +| Unconfirmed | непацьверджаная | adjective/state form; Tarask `пацьв-` (soft sign) · Electrum be_BY uses `Непацверджана` (regular spelling). | +| Confirmed | пацьверджаная | adjective/state form; Tarask `пацьв-` · Electrum be_BY uses `Пацверджана`. | +| Mempool | mempool | technical term — typically kept Latin in Slavic locales. | +| Broadcast | трансляцыя / транслюваць | shipped noun; verb form `транслюваць` for button labels (noun / verb per glossary). | +| Block explorer | аглядальнік блёкаў | Tarask `блёк` (with `ё`) per be-tarask.wikipedia.org/wiki/Біткойн; `аглядальнік` = explorer/viewer. | +| Onchain | он-чэйн / у блёкчэйне | compact / explanatory · Tarask `блёк-` and `э`. | +| Offchain | оф-чэйн / па-за блёкчэйнам | compact / explanatory · Tarask `блёк-` and `э`. | +| **_Fees & fee bumping_** | | | +| Fee | камісія | Bitcoin Core be / Electrum be_BY use `Камісія` — target-locale lowercase here. | +| Fee Bump | павышэньне камісіі | Tarask `-ньне` suffix; "raising of fee" umbrella term for RBF + CPFP. | +| RBF | RBF | acronym — letters kept; gloss "замяніць па камісіі" available in Notes. | +| CPFP | CPFP | acronym — letters kept · ⚠️ NOT a verb like "Create". | +| Speed Up | паскорыць | verb form for tx detail button label (RBF action). | +| **_Lightning_** | | | +| Invoice | інвойс / рахунак | technical / mainstream · "інвойс" common in BY fintech; "рахунак" = bill/invoice noun. | +| Lightning Invoice | інвойс Lightning / рахунак Lightning | brand "Lightning" + localised noun. | +| Preimage | прообраз | math term (preimage of a hash function) · matches Slavic math vocabulary convention (ru/uk/be wikipedias use `прообраз` for math preimage). | +| Payment | плацёж | ⚠️ NOT the verb "to pay" (плаціць) — must be a noun. Tarask spelling preserves `ё`. | +| Expired | пратэрмінаваны / састарэлы | adj · ⚠️ shipped `Скончыўся` is past-tense verb ("ended") — should be adjective state form. Flag for fix. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | сападпісант | ⚠️ NOT "co-owner" (сууладальнік) — must be a signer noun. Tarask `сапа-` prefix. | +| Quorum | кворум / парог подпісаў | canonical / UI alternative (threshold of signatures). | +| PSBT | PSBT | acronym — letters kept (Partially Signed Bitcoin Transaction, BIP174). | +| Provide signature | падпісаць | verb: sign a PSBT. | +| BIP47 / Payment Code | BIP47 / плацежны код | acronym + translatable noun · "плацежны код" with Tarask `ё`-preserving stem. | +| Notification transaction | трансакцыя-паведамленьне | BIP47-specific 0-value notification tx · Tarask `-ньне` suffix on `паведамленьне` (notification). Preferred over `натыфікацыйная` (anglicism) per native Tarask preference. | +| SilentPayment | Silent Payments / ціхія плацяжы | brand kept English (note plural); explanatory Tarask gloss. | +| **_Coin control_** | | | +| Coin Control | кіраваньне UTXO / кіраваньне манэтамі | technical / mainstream · ⚠️ NOT Title Case · Tarask `-ньне` suffix, `э` in `манэта`. | +| Frozen | замарожаны | ⚠️ NOT the verb "to freeze" (замарозіць) — must be adjective/state form. | +| **_Security & storage_** | | | +| Encrypted storage | сховішча зашыфравана | shipped value · ⚠️ NOT Title Case · matches storage_is_encrypted in be@tarask.json. | +| Plausible Deniability | праўдападобнае адмаўленьне | Tarask `-ньне` suffix; ⚠️ NOT Title Case · matches en.wikipedia.org/wiki/Plausible_deniability concept. | +| Biometrics | біяметрыя | be-tarask.wikipedia.org spelling `Біяметрыя`. | +| Passcode | код доступу | ⚠️ NOT "password" (пароль) — distinct word for device unlock code. Shipped `пароль` collides with app password; flag for fix. | +| **_Backup, import & UX_** | | | +| Backup | рэзервовая копія / зрабіць рэзервовую копію | noun / verb · Electrum be_BY uses `Рэзервовая копія`. Tarask `э`. | +| Restore | аднавіць / аднаўленьне | verb / noun · Tarask `-ньне` suffix. | +| Import | імпартаваць / імпарт | verb / noun · Electrum be_BY uses `Імпарт`. | +| Voucher | ваўчар | shipped value (azteco.codeIs). | +| Redeem | перавесьці | shipped value (decapitalised); ⚠️ NOT "buy to wallet" / NOT "transfer" — but shipped UI uses Tarask `перавесьці` (lit. "transfer"). Acceptable as locale's "cash in" rendering. | +| Send | даслаць | shipped JSON uses `daslac`-style for "send" elsewhere · Electrum be_BY `Даслаць`. | +| Receive | атрымаць | Electrum be_BY uses `Атрымаць`. | +| Settings | налады | Tarask form (vs Bitcoin Core be `Наладкі`). | +| Confirm | пацьвердзіць / пацьверджаньне | verb / noun · Tarask `пацьв-` (soft sign) and `-ньне` suffix. | +| QR Code | QR-код | be-tarask.wikipedia.org/wiki/QR-код form. | +| Clipboard | буфер абмену | shipped value (decapitalised per target-locale lowercase). | +| Memo | нататка | sender note on outgoing tx; ⚠️ NOT "remember" verb. | +| Description | апісаньне | free-text label on receive invoice; Tarask `-ньне` suffix. | +| Label | метка / цэтлік | label noun · Bitcoin Core be / Electrum be_BY use `Метка` / `Адмеціны`. | diff --git a/loc/vocabulary/bg_bg.md b/loc/vocabulary/bg_bg.md new file mode 100644 index 00000000000..aa90a2ff294 --- /dev/null +++ b/loc/vocabulary/bg_bg.md @@ -0,0 +1,96 @@ +# Bulgarian translation vocabulary (`bg_bg.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / биткойн | brand kept Latin; `биткойн` in explanatory text and as unit · bg.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand kept Latin · not covered by bg Wikipedia article. | +| Electrum | Electrum | brand kept Latin. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | биткойн / BTC | noun unit + ticker · bg.wikipedia.org/wiki/Bitcoin | +| sats | сатоши | noun, lowercase · plural sense. | +| sat/vByte | sat/vByte | technical unit; UI keeps Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | портфейл | noun, lowercase. | +| Vault | сейф / хранилище | noun · `сейф` for safe/strongbox sense; avoid Latin "Vault". | +| Watch-only | само за наблюдение | adj · Electrum bg_BG. | +| Hardware wallet | хардуерен портфейл | noun, lowercase · Bitcoin Core bg. | +| Seed | сийд / фраза за възстановяване | noun · technical / mainstream · Electrum bg_BG (Сийд). | +| Mnemonic | мнемонична фраза / фраза за възстановяване | noun · technical / mainstream. | +| Passphrase | тайна фраза | noun · ⚠️ NOT `парола` (= password) · Electrum bg_BG. | +| Public key | публичен ключ | noun, lowercase · Electrum bg_BG. | +| Private key | частен ключ | noun, lowercase · Electrum bg_BG. | +| WIF | WIF | acronym · gloss: формат за импорт на портфейл. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | дескриптор | noun, lowercase. | +| Derivation path | път на деривация | noun, lowercase. | +| Master fingerprint | отпечатък на главния ключ | noun, lowercase. | +| BIP38 | BIP38 | acronym kept · gloss: парола BIP38. | +| **_On-chain transactions_** | | | +| Transaction | транзакция | noun, lowercase · Electrum bg_BG; variant `трансакция` exists. | +| Address | адрес | noun, lowercase · Bitcoin Core bg + Electrum bg_BG. | +| Input | вход / вход на транзакция | noun · short / full · Electrum bg_BG (Входове). | +| Output | изход / изход на транзакция | noun · short / full · ⚠️ NOT "До:" (recipient UI label). | +| UTXO | UTXO | acronym · gloss: неизразходван изход на транзакция. | +| Change | ресто / адрес за ресто | noun · ⚠️ NOT verb "промени"; `ресто` = leftover coin · Electrum bg_BG. | +| Hex | hex / шестнадесетични данни | noun · short / explanatory · ⚠️ NOT "hash". | +| Pending | в очакване | adj/state · Bitcoin Core bg · avoid noun "очакване". | +| Unconfirmed | непотвърдена | adj · feminine to agree with `транзакция` · Electrum bg_BG. | +| Confirmed | потвърдена / потвърдено | adj · feminine (за транзакция) / neuter (за плащане) · Bitcoin Core bg. | +| Mempool | мемпул / Mempool | noun · Cyrillic / Latin (per Electrum bg_BG). | +| Broadcast | излъчване / излъчи | noun / verb · Electrum bg_BG. | +| Block explorer | блок експлорър / блоков обозревател | noun, lowercase · Electrum bg_BG. | +| Onchain | он-чейн / в блокчейна | adj · compact (chip) / explanatory (body). | +| Offchain | оф-чейн / извън блокчейна | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | такса | noun, lowercase · Bitcoin Core bg + Electrum bg_BG. | +| Fee Bump | увеличаване на таксата | noun, lowercase. | +| RBF | RBF | acronym · gloss: замяна с по-висока такса / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: детето плаща за родителя. ⚠️ NOT verb "Създай". | +| Speed Up | ускори | verb · button label. | +| **_Lightning_** | | | +| Invoice | фактура / платежна заявка | noun · mainstream / technical. | +| Lightning Invoice | фактура Lightning / платежна заявка Lightning | noun · brand kept Latin + localised noun. | +| Preimage | прообраз | noun · cryptographic preimage (math term). | +| Payment | плащане | noun · ⚠️ NOT verb "Плати" (current shipped value is verb form). | +| Expired | изтекла / изтекъл | adj · feminine (фактура) / masculine · Electrum bg_BG. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | съподписващ | noun · ⚠️ NOT "съсобственик" (co-owner) · Electrum bg_BG. | +| Quorum | кворум / праг на подписите | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym · Bitcoin Core bg; drop Cyrillic `ЧПБТ` — non-standard. | +| Provide signature | предостави подпис / подпиши транзакцията | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / платежен код | acronym kept; "Payment Code" → "платежен код". | +| Notification transaction | транзакция за уведомление | noun · BIP47-specific. | +| SilentPayment | Silent Payments / тихи плащания | protocol name kept Latin (plural); explanatory `тихи плащания` if needed. | +| **_Coin control_** | | | +| Coin Control | управление на UTXO / управление на монетите | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case. | +| Frozen | замразена / замразен | adj · feminine (транзакция/монета) / masculine · ⚠️ NOT verb "замрази" · Electrum bg_BG. | +| **_Security & storage_** | | | +| Encrypted storage | криптирано хранилище | noun, lowercase · ⚠️ NOT Title Case. | +| Plausible Deniability | правдоподобно отричане | noun, lowercase · currently English-passthrough in shipped strings. | +| Biometrics | биометрични данни | noun, lowercase. | +| Passcode | код за достъп | noun · ⚠️ NOT `парола` (= password). | +| **_Backup, import & UX_** | | | +| Backup | резервно копие / архивирай | noun / verb · Electrum bg_BG (Архивиране) + Bitcoin Core bg (Бекъп). | +| Restore | възстанови / възстановяване | verb / noun · ⚠️ NOT shipped misspelling `въстанови` · Bitcoin Core bg + Electrum bg_BG. | +| Import | импортирай / импортиране | verb / noun · Electrum bg_BG. | +| Voucher | ваучер | noun, lowercase · ⚠️ NOT shipped `ваучър` (misspelling). | +| Redeem | осребри / активирай | verb · ⚠️ NOT "купи в портфейла". | +| Send | изпрати | verb · Bitcoin Core bg + Electrum bg_BG. | +| Receive | получи / получаване | verb / noun · Bitcoin Core bg (Получи) + Electrum bg_BG (Получаване). | +| Settings | настройки | noun, lowercase · Bitcoin Core bg + Electrum bg_BG. | +| Confirm | потвърди / потвърждение | verb / noun. Plural "потвърждения" for tx confirmations. | +| QR Code | QR код | noun · Electrum bg_BG. | +| Clipboard | клипборд | noun, lowercase · Bitcoin Core bg + Electrum bg_BG. | +| Memo | бележка | noun, lowercase. | +| Description | описание | noun, lowercase · Electrum bg_BG. | +| Label | етикет | noun, lowercase · Bitcoin Core bg + Electrum bg_BG. | diff --git a/loc/vocabulary/bqi.md b/loc/vocabulary/bqi.md new file mode 100644 index 00000000000..21789c9d73f --- /dev/null +++ b/loc/vocabulary/bqi.md @@ -0,0 +1,98 @@ +# Bakhtiari translation vocabulary (`bqi.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +> RTL locale. Brand names appear in Latin script inside the surrounding script; values below are the rendered form used in `loc/bqi.json`. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · gloss: بیت کوین. | +| Lightning | لایتنینگ | Transliteration. | +| Electrum | Electrum | brand kept Latin · glosses: الکترام / الکتروم. | +| LNDhub | LNDhub | — | +| LND | LND | brand · borrowed from fa: brand kept Latin. | +| LNURL | LNURL | brand · borrowed from fa: brand kept Latin. | +| Tor | Tor | brand · borrowed from fa: brand kept Latin. | +| Orbot | Orbot | brand · borrowed from fa: brand kept Latin. | +| GroundControl | GroundControl | brand · borrowed from fa: brand kept Latin. | +| **_Units & amounts_** | | | +| bitcoin / BTC | بیت کوین | — | +| sats | ساتۊشی | — | +| sat/vByte | ساتۊشی سی هر بایت مجازی | — | +| vByte | بایت مجازی | "virtual byte". | +| **_Wallet, keys & seeds_** | | | +| Wallet | کیف پیل | Lit. "money bag". | +| Vault | گاوصندوق | noun · borrowed from fa: shared Persian word for "safe/strongbox" · ⚠️ NOT a brand; translated. | +| Watch-only | فقط‌خواندنی | adj · borrowed from fa: read-only sense · ⚠️ NOT "view mode" — wallet type. | +| Hardware wallet | کیف پیل سخت ٱفزاری | — | +| Seed | سید | Transliteration. | +| Mnemonic | عبارت بازیابی | noun · borrowed from fa: "recovery phrase". | +| Passphrase | عبارت عبور | noun · ⚠️ NOT password / NOT passcode · borrowed from fa: عبارت عبور. | +| Public key | کلید عمومی | noun · borrowed from fa: shared Iranian terminology. | +| Private key | کلید خصوصی | noun · borrowed from fa: shared Iranian terminology. | +| WIF | WIF | acronym · borrowed from fa: letters kept. | +| xpub | xpub | acronym · lowercase per convention. | +| Descriptor | TODO | — | +| Derivation path | تور موشتق وابیڌن | — | +| Master fingerprint | جا کلک ٱلسی | — | +| BIP38 | BIP38 | acronym · borrowed from fa: letters kept · ⚠️ NOT a verb. | +| **_On-chain transactions_** | | | +| Transaction | تراکونش | Transliteration. | +| Address | آدرس | — | +| Input | وۊرۊڌی | noun · ⚠️ NOT "login/entrance" · borrowed from fa: ورودی. | +| Output | خروجی | noun · ⚠️ NOT the UI label "To:" · borrowed from fa: خروجی. | +| UTXO | UTXO | acronym · borrowed from fa: letters kept. | +| Change | آلشتکاری / باقی‌مانده | noun · native bqi `آلشتکاری` preferred (shipped via locsync31 cleanup); fa loanword `باقی‌مانده` ("leftover") fallback · ⚠️ NOT verb "to change/modify". | +| Hex | هگزادسیمال | — | +| Pending | د انتظار | adj/state · borrowed from fa: در انتظار. | +| Unconfirmed | TODO | — | +| Confirmed | تاییڌ وابیڌه | adj/state · parallels bqi `مونقزی وابیڌه` (expired) pattern. | +| Mempool | mempool | noun · borrowed from fa: technical Latin loanword (common noun, not a brand). | +| Broadcast | انتشار | — | +| Block explorer | گشت گر بلاک | noun · borrowed from fa: کاوشگر بلاک. | +| Onchain | آنچین | Transliteration. | +| Offchain | آفچین | Transliteration. | +| **_Fees & fee bumping_** | | | +| Fee | کارمزد | "work-wage". | +| Fee Bump | ائتمال بیشتر وابیڌن کارمزد | — | +| RBF | RBF | — | +| CPFP | CPFP | — | +| Speed Up | تسریع | verb · borrowed from fa: button label · disambiguates from Fee Bump. | +| **_Lightning_** | | | +| Invoice | سۊرت هساو | "bill/account". | +| Lightning Invoice | سۊرت هساو لایتنینگ | — | +| Preimage | پؽش شؽوات | Calque. | +| Payment | پرداخت | — | +| Expired | مونقزی وابیڌه | — | +| **_Multisig & advanced addressing_** | | | +| Co-signer | امزا کوݩ هومبهر | noun · borrowed from fa: cognate via lrc (Northern Luri) · ⚠️ NOT "co-owner" — must be a signer noun. | +| Quorum | حد نصاب | noun · borrowed from fa: Arabic-Persian loanword shared across Iranian languages. | +| PSBT | PSBT | — | +| Provide signature | دین امزا | verb · borrowed from fa: cognate via lrc (Northern Luri) "give signature". | +| BIP47 / Payment Code | BIP47 / کود پرداخت | acronym + noun · borrowed from fa: BIP47 kept Latin; "Payment Code" via lrc cognate. | +| Notification transaction | TODO | — | +| SilentPayment | Silent Payments | brand · borrowed from fa: protocol name kept Latin (plural). | +| **_Coin control_** | | | +| Coin Control | مدیریت UTXO | noun · borrowed from fa: technical UTXO form · ⚠️ NOT Title Case. | +| Frozen | مسدۊد | adj/state · "blocked" · ⚠️ NOT verb "to freeze" · borrowed from fa: مسدود. | +| **_Security & storage_** | | | +| Encrypted storage | فعال کردن رزم ناهاڌن ری جاگه زفت کردنی | — | +| Plausible Deniability | انکار مووجه | noun · ⚠️ NOT Title Case · borrowed from fa: انکار موجه. | +| Biometrics | بیومتریک | — | +| Passcode | کد دسترسی | noun · ⚠️ NOT app password · borrowed from fa: کد دسترسی. | +| **_Backup, import & UX_** | | | +| Backup | و در کردن نوسخه لادرار | — | +| Restore | بازیابی | verb / noun · borrowed from fa: Arabic-Persian loanword shared across Iranian languages. | +| Import | و من ٱووردن | "bring in". | +| Voucher | بن / ووچر | noun · Azte.co prepaid voucher · borrowed from fa: بن / ووچر. | +| Redeem | فعال کردن | "activate". | +| Send | فشناڌن | — | +| Receive | گرؽڌن | — | +| Settings | سامووا | — | +| Confirm | تاییڌ کرڌن / تاییڌ | verb / noun · button vs status. | +| QR Code | QR کود | — | +| Clipboard | ویرگه | — | +| Memo | ویرداشت | — | +| Description | گوڌنا دیاری | — | +| Label | برچسب | noun · ⚠️ NOT verb · borrowed from fa: برچسب. | diff --git a/loc/vocabulary/ca.md b/loc/vocabulary/ca.md new file mode 100644 index 00000000000..f84244ab840 --- /dev/null +++ b/loc/vocabulary/ca.md @@ -0,0 +1,96 @@ +# Catalan translation vocabulary (`ca.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · ca.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker · ca.wikipedia.org/wiki/Bitcoin | +| sats | sats | noun, lowercase; ships as `sats` in ca.json. | +| sat/vByte | sat/vByte | technical unit; UI keeps Latin. ⚠️ ca.json ships `Satoshis per vByte` in body text. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | moneder | noun, lowercase · BlueWallet convention (ca.wikipedia uses `cartera electrònica`, but app ships `moneder`). | +| Vault | caixa forta / cofre | noun; safe/strongbox sense. Avoid Latin "Vault". | +| Watch-only | només lectura / només visualització | adj · Bitcoin Core ca | +| Hardware wallet | moneder de maquinari / moneder hardware | noun, lowercase · Bitcoin Core ca uses `cartera de maquinari`; BlueWallet ships `moneder hardware`. | +| Seed | llavor / frase de recuperació | noun · literal / mainstream; ca.json ships `Llavor` and `frase de recuperació`. | +| Mnemonic | frase mnemònica / frase de recuperació | noun · technical / mainstream. | +| Passphrase | frase de contrasenya | noun · ⚠️ NOT just `contrasenya` (= password). Bitcoin Core ca collapses to `contrasenya`; disambiguate here. | +| Public key | clau pública | noun, lowercase. | +| Private key | clau privada | noun, lowercase · Bitcoin Core ca | +| WIF | WIF | acronym · gloss: format d'importació de moneder. | +| xpub | xpub | acronym, lowercase preferred · ⚠️ ca.json ships `XPUB`. | +| Descriptor | descriptor | noun, lowercase. | +| Derivation path | camí de derivació | noun. | +| Master fingerprint | empremta mestra / petjada digital mestra | noun · ⚠️ ca.json ships `Petjada digital mestre`; `empremta mestra` is more natural for fingerprint. | +| BIP38 | BIP38 | acronym kept · gloss: clau privada xifrada amb contrasenya. | +| **_On-chain transactions_** | | | +| Transaction | transacció | noun, lowercase. | +| Address | adreça | noun, lowercase · ⚠️ ca.json mixes `Adreça` and `Direcció` (Spanish-influenced); prefer `adreça`. | +| Input | entrada / entrada de transacció | noun · short / full. ca.json ships plural `Entrades`. | +| Output | sortida / sortida de transacció | noun · short / full. ca.json ships plural `Sortides`. | +| UTXO | UTXO | acronym · gloss: sortida de transacció sense gastar. | +| Change | canvi / adreça de canvi | noun · ⚠️ NOT verb "canviar". `canvi` = leftover; `adreça de canvi` for change-address · Bitcoin Core ca | +| Hex | hex / hexadecimal | noun · short / explanatory · ⚠️ NOT "hash". | +| Pending | pendent | adj/state. | +| Unconfirmed | sense confirmar / no confirmada | adj · short / fem-agreement. | +| Confirmed | confirmada / confirmat | adj · fem / masc-agreement. Noun `confirmacions` = confirmation count. | +| Mempool | mempool | noun, lowercase. | +| Broadcast | emetre / transmetre | verb · UI-clear / technical · Bitcoin Core ca. Noun form: emissió / transmissió. | +| Block explorer | explorador de blocs | noun, lowercase. | +| Onchain | en cadena / a la cadena | adj · compact / explanatory. | +| Offchain | fora de cadena | adj. | +| **_Fees & fee bumping_** | | | +| Fee | comissió | noun, lowercase. | +| Fee Bump | augment de comissió / ampliar la comissió | noun / verb-phrase · ca.json ships `Permeteu ampliar la comissió`. | +| RBF | RBF | acronym · gloss: substitució per comissió / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: el fill paga pel pare. ⚠️ NOT "Crear" — ca.json `cpfp_create` ships `Crear` (button label, not gloss). | +| Speed Up | accelerar | verb. | +| **_Lightning_** | | | +| Invoice | factura | noun, lowercase. | +| Lightning Invoice | factura Lightning | noun · ca.json ships `Factura Lightning`. | +| Preimage | preimatge | noun · math term. | +| Payment | pagament | noun · ⚠️ NOT verb "pagar". | +| Expired | caducada / caducat | adj · fem / masc-agreement. ca.json ships `Caducat`. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | cosignant / signant | noun · ⚠️ NOT "copropietari" (co-owner) · Bitcoin Core ca uses `signants`. | +| Quorum | quòrum / llindar de signatures | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym. | +| Provide signature | aportar signatura / signar la transacció | verb · generic / specific. ca.json ships `Signar una transacció`. | +| BIP47 / Payment Code | BIP47 / codi de pagament | acronym kept; "Payment Code" → "codi de pagament". | +| Notification transaction | transacció de notificació | noun · BIP47-specific. | +| SilentPayment | Silent Payments / pagaments silenciosos | protocol name kept English (plural); explanatory `pagaments silenciosos` if needed. | +| **_Coin control_** | | | +| Coin Control | control de monedes / gestió d'UTXO | noun, lowercase · mainstream / technical · ⚠️ NOT Title Case · Bitcoin Core ca uses `control de les monedes`. | +| Frozen | congelada / bloquejada | adj · state, fem-agreement (sortida/moneda) · ⚠️ NOT verb "congelar". | +| **_Security & storage_** | | | +| Encrypted storage | emmagatzematge xifrat | noun, lowercase · ⚠️ NOT Title Case. | +| Plausible Deniability | negació plausible | noun, lowercase · ⚠️ NOT Title Case. ca.json ships `Negació plausible`. | +| Biometrics | biometria | noun, lowercase. | +| Passcode | codi d'accés | noun · ⚠️ NOT `contrasenya` (= password); ca.json conflates them. | +| **_Backup, import & UX_** | | | +| Backup | còpia de seguretat / fer una còpia de seguretat | noun / verb · Bitcoin Core ca. ⚠️ ca.json ships `Exportar / Guardar` (= export / save). | +| Restore | restaurar / restauració | verb / noun · Bitcoin Core ca | +| Import | importar / importació | verb / noun. | +| Voucher | val / cupó | noun, lowercase · preferred / shipped alt · Bitcoin Core ca + Cake ca use `val`; ca.json mixes `val` and `cupó`. | +| Redeem | bescanviar / activar | verb · ⚠️ NOT `canviar` alone (= "to change/exchange") — ca.json ships `Canviar` which is ambiguous. | +| Send | enviar | verb. | +| Receive | rebre | verb. | +| Settings | configuració | noun, lowercase. | +| Confirm | confirmar / confirmació | verb / noun. | +| QR Code | codi QR | noun · Bitcoin Core ca | +| Clipboard | porta-retalls | noun, lowercase. | +| Memo | nota / comentari | noun, lowercase · ca.json ships `Comentari` (= comment). | +| Description | descripció | noun, lowercase. | +| Label | etiqueta | noun, lowercase · Bitcoin Core ca | diff --git a/loc/vocabulary/cs_cz.md b/loc/vocabulary/cs_cz.md new file mode 100644 index 00000000000..12124ab49ce --- /dev/null +++ b/loc/vocabulary/cs_cz.md @@ -0,0 +1,96 @@ +# Czech translation vocabulary (`cs_cz.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin; inflected ("bitcoinovou") in body · cs.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand · cs.wikipedia.org/wiki/Lightning_Network | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand · Bitcoin Core cs + Electrum cs keep as-is. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker; inflected in body. | +| sats | sats / satoshi | noun, lowercase; `satoshi` in explanatory text. | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | peněženka | noun, lowercase · Bitcoin Core cs + Electrum cs | +| Vault | trezor / úložiště | noun; `trezor` = safe/strongbox sense (matches glossary; current app ships `Úložiště`). | +| Watch-only | pouze pro sledování | adj · Bitcoin Core cs + Electrum cs | +| Hardware wallet | hardwarová peněženka | noun, lowercase · Electrum cs | +| Seed | seed / fráze pro obnovení | noun; mainstream form preferred. Current app keeps `Seed`. | +| Mnemonic | mnemotechnická fráze | noun · Electrum cs | +| Passphrase | přístupová fráze | noun · ⚠️ NOT `heslo` (= password) · Bitcoin Core cs uses `přístupová fráze`. | +| Public key | veřejný klíč | noun, lowercase · Bitcoin Core cs + Electrum cs | +| Private key | soukromý klíč | noun, lowercase · Bitcoin Core cs | +| WIF | WIF | acronym. | +| xpub | xpub | acronym, lowercase preferred (current app ships `XPUB`). | +| Descriptor | deskriptor | noun, lowercase · Bitcoin Core cs uses `deskriptor`. | +| Derivation path | derivační cesta | noun. | +| Master fingerprint | hlavní otisk / otisk hlavního klíče | noun · short / explanatory. | +| BIP38 | BIP38 | acronym kept · gloss: heslem chráněný soukromý klíč. | +| **_On-chain transactions_** | | | +| Transaction | transakce | noun, lowercase · Bitcoin Core cs + Electrum cs | +| Address | adresa | noun, lowercase · Bitcoin Core cs | +| Input | vstup / vstup transakce | noun · short / full · Bitcoin Core cs | +| Output | výstup / výstup transakce | noun · short / full · ⚠️ NOT the UI recipient label "Komu". | +| UTXO | UTXO | acronym · gloss: neutracený transakční výstup. | +| Change | drobné / adresa drobných | noun · ⚠️ NOT verb "změnit". `drobné` = leftover coin output · Bitcoin Core cs (`Drobné`). | +| Hex | hex / hex data | noun · short / explanatory · ⚠️ NOT "hash" / NOT "transakční data". | +| Pending | čekající / čeká | adj/state. Avoid noun "čekání". | +| Unconfirmed | nepotvrzená / nepotvrzeno | adj · feminine-agreement / state · Bitcoin Core cs | +| Confirmed | potvrzená / potvrzeno | adj · feminine-agreement / state · Bitcoin Core cs + Electrum cs | +| Mempool | mempool | noun, lowercase · Bitcoin Core cs + Electrum cs keep `Mempool`. | +| Broadcast | odeslat do sítě / odvysílat | verb · UI-clear / technical. Noun form: odeslání / vysílání. | +| Block explorer | průzkumník bloků / block explorer | noun, lowercase · Bitcoin Core cs uses `block explorer`. | +| Onchain | on-chain / v blockchainu | adj · compact (chip) / explanatory (body) · Electrum cs | +| Offchain | off-chain / mimo blockchain | adj · compact (chip) / explanatory (body) · Electrum cs | +| **_Fees & fee bumping_** | | | +| Fee | poplatek | noun, lowercase · Bitcoin Core cs + Electrum cs | +| Fee Bump | navýšení poplatku | noun · Electrum cs | +| RBF | RBF | acronym · gloss: nahrazení vyšším poplatkem / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: potomek platí za rodiče. ⚠️ NOT a verb like "Vytvořit". | +| Speed Up | zrychlit | verb (button label) · ⚠️ current app `Poplatek za popostrčení` used both for RBF and CPFP — prefer `zrychlit` for the RBF button. | +| **_Lightning_** | | | +| Invoice | faktura / platební požadavek | noun · technical / mainstream · Electrum cs uses `faktura`. | +| Lightning Invoice | Lightning faktura / Lightning platební požadavek | noun · technical / mainstream · `Lightning` brand + `faktura` (Electrum cs) / `platební požadavek`. | +| Preimage | předobraz | noun · math term · Electrum cs | +| Payment | platba | noun · ⚠️ NOT verb "zaplatit" · Electrum cs | +| Expired | expirováno / vypršela platnost | adj/state · compact / explanatory · canonical Czech form is `vypršela platnost` (alt. `prošlá platnost`); `expirováno` is colloquial. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | spolupodepisující / spolupodepisovatel | noun · ⚠️ NOT "spolumajitel" (co-owner) · Electrum cs uses `spolupodepisující`. | +| Quorum | kvórum / práh podpisů | noun · canonical / UI-clear · Electrum cs | +| PSBT | PSBT | acronym · gloss: částečně podepsaná bitcoinová transakce. | +| Provide signature | poskytnout podpis / podepsat transakci | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / platební kód | acronym kept; "Payment Code" → "platební kód" · Electrum cs | +| Notification transaction | oznamovací transakce | noun · BIP47-specific. | +| SilentPayment | Silent Payments / tiché platby | protocol name kept English (plural); optional explanatory `tiché platby`. | +| **_Coin control_** | | | +| Coin Control | správa UTXO / správa mincí | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case · Bitcoin Core cs uses `Ovládání mincí`. | +| Frozen | zmrazeno / zmrazený | adj · state / masc-agreement · ⚠️ NOT verb "zmrazit" · Bitcoin Core cs (`Zmrazeno`). | +| **_Security & storage_** | | | +| Encrypted storage | šifrované úložiště / šifrování úložiště | noun, lowercase · ⚠️ NOT Title Case. | +| Plausible Deniability | věrohodná popiratelnost | noun, lowercase · ⚠️ NOT Title Case · cs.wikipedia.org/wiki/Věrohodná_popiratelnost. | +| Biometrics | biometrie | noun, lowercase · Electrum cs | +| Passcode | přístupový kód | noun · ⚠️ NOT `heslo` (= password) · Electrum cs | +| **_Backup, import & UX_** | | | +| Backup | záloha / zálohovat | noun / verb. | +| Restore | obnovit / obnovení | verb / noun · Bitcoin Core cs | +| Import | importovat / import | verb / noun. | +| Voucher | voucher / poukaz | noun, lowercase; both forms shipped in app strings. | +| Redeem | uplatnit | verb · ⚠️ NOT "koupit do peněženky" · Electrum cs | +| Send | odeslat | verb · Bitcoin Core cs | +| Receive | přijmout | verb · Bitcoin Core cs | +| Settings | nastavení | noun, lowercase. | +| Confirm | potvrdit / potvrzení | verb / noun. | +| QR Code | QR kód | noun · Electrum cs | +| Clipboard | schránka | noun, lowercase · Electrum cs | +| Memo | poznámka | noun, lowercase · Electrum cs | +| Description | popis | noun, lowercase · Electrum cs | +| Label | popisek / označení | noun, lowercase · Electrum cs uses `Popisek`, Bitcoin Core cs uses `Označení`. | diff --git a/loc/vocabulary/cy.md b/loc/vocabulary/cy.md new file mode 100644 index 00000000000..e92c25f9307 --- /dev/null +++ b/loc/vocabulary/cy.md @@ -0,0 +1,96 @@ +# Welsh translation vocabulary (`cy.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · cy.wikipedia.org/wiki/Bitcoin | +| Lightning | Mellten | brand; Welsh for "lightning" — established in shipped UI (`Anfoneb Mellten`, `Mellten`). | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker; lowercase as unit. | +| sats | sats / satoshis | noun, lowercase · Bitcoin Core cy keeps `satoshi`. | +| sat/vByte | sat/vByte | technical unit; Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | waled / waledi | noun, lowercase; `waledi` plural (shipped `Waledi`). | +| Vault | claddgell / cist | noun · Welsh for "vault/strongbox"; avoid Latin "Vault". | +| Watch-only | gwylio'n unig | adj · Bitcoin Core cy. | +| Hardware wallet | waled caledwedd | noun, lowercase. | +| Seed | Seed / ymadrodd adfer | noun · English loanword preferred (UI convention) · ⚠️ NOT `hadyn` / `had` (botanical kernel — wrong register for crypto seed phrase). | +| Mnemonic | ymadrodd cofiadwy / ymadrodd adfer | noun · technical / mainstream ("recovery phrase"). | +| Passphrase | geiriau pas | noun · ⚠️ NOT `cyfrinair` (= password) · Bitcoin Core cy. | +| Public key | allwedd gyhoeddus | noun, lowercase. | +| Private key | allwedd breifat | noun, lowercase · Bitcoin Core cy. | +| WIF | WIF | acronym · gloss: fformat mewnforio waled. | +| xpub | xpub | acronym, lowercase. | +| Descriptor | disgrifydd | noun, lowercase. | +| Derivation path | llwybr deilliant | noun · BIP32 path. | +| Master fingerprint | ôl bys y brif allwedd | noun. | +| BIP38 | BIP38 | acronym kept · ⚠️ NOT a verb; gloss only: `cyfrinair i ddad-gryptio` (shipped paraphrase — should not replace acronym). | +| **_On-chain transactions_** | | | +| Transaction | trafodyn / trafodiad | noun · shipped `trafodyn` / Bitcoin Core cy `trafodiad`. | +| Address | cyfeiriad | noun, lowercase · Bitcoin Core cy. | +| Input | mewnbwn / mewnbynau | noun · singular / plural (shipped). | +| Output | allbwn / allbynau | noun · singular / plural · ⚠️ NOT the UI recipient label "At:" (that's separate). | +| UTXO | UTXO | acronym · gloss: allbwn trafodyn heb ei wario. | +| Change | newid | noun · ⚠️ NOT verb "to change/alter". Welsh `newid` is ambiguous (also "to change"); consider `gweddill` (remainder) for change-output. | +| Hex | hex / hecsadegol | noun · short / explanatory · ⚠️ NOT "hash" / NOT "transaction data" · cy.wikipedia.org/wiki/Hecsadegol | +| Pending | yn aros / disgwyl | adj/state · ⚠️ shipped `Disgwyl` literally means "wait" (verb); `yn aros` = "waiting/pending" reads better. | +| Unconfirmed | heb gadarnhau | adj · negation of `cadarnhawyd`. | +| Confirmed | cadarnhawyd | adj/state · ⚠️ shipped `Wedi Cysylltu` literally means "connected" — WRONG, must be fixed · Bitcoin Core cy. | +| Mempool | mempool / pwll trafodion | noun · technical / explanatory. | +| Broadcast | darlledu | verb · also noun: `darllediad`. | +| Block explorer | archwiliwr blociau | noun, lowercase. | +| Onchain | ar y gadwyn / ar y blockchain | adj · compact / explanatory. | +| Offchain | oddi ar y gadwyn / tu allan i'r gadwyn | adj · compact / explanatory. | +| **_Fees & fee bumping_** | | | +| Fee | ffi | noun, lowercase · Bitcoin Core cy uses `ffî`. | +| Fee Bump | codi'r ffi | noun · "raise the fee". | +| RBF | RBF | acronym · gloss: amnewid yn ôl ffi (Replace-By-Fee). | +| CPFP | CPFP | acronym · gloss: plentyn yn talu dros riant · ⚠️ shipped `Creu` (= "create") is WRONG; CPFP must stay as acronym. | +| Speed Up | cyflymu | verb. | +| **_Lightning_** | | | +| Invoice | anfoneb | noun, lowercase · shipped. | +| Lightning Invoice | anfoneb Mellten | noun · shipped. | +| Preimage | cynddelw | noun · Welsh math term for "preimage". | +| Payment | taliad | noun · ⚠️ NOT verb `talu` (= "to pay"); shipped uses verb where noun is needed. | +| Expired | wedi dod i ben | adj/state · ⚠️ shipped `Gorffen` is verb "finish/end"; `wedi dod i ben` = "has expired". | +| **_Multisig & advanced addressing_** | | | +| Co-signer | cyd-lofnodwr | noun · ⚠️ NOT `cyd-berchennog` (co-owner). | +| Quorum | cworwm / trothwy llofnodion | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym · ⚠️ shipped paraphrase `Arwyddo trafodyn` (= "sign transaction") is WRONG — must keep acronym `PSBT`. | +| Provide signature | darparu llofnod / llofnodi'r trafodyn | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / cod taliad | acronym kept; "Payment Code" → `cod taliad`. | +| Notification transaction | trafodyn hysbysu | noun · BIP47-specific. | +| SilentPayment | Silent Payments / taliadau tawel | protocol name kept English (plural); explanatory `taliadau tawel` if needed. | +| **_Coin control_** | | | +| Coin Control | rheoli UTXO / rheoli darnau arian | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case. | +| Frozen | wedi'i rewi | adj/state · ⚠️ NOT verb `rhewi` (= to freeze). | +| **_Security & storage_** | | | +| Encrypted storage | storfa wedi'i hamgryptio | noun, lowercase · ⚠️ NOT Title Case. Shipped string `Creu storfa wedi encryptio` mixes Welsh + English-derived `encryptio`; prefer `amgryptio` · Bitcoin Core cy. | +| Plausible Deniability | gwadu credadwy | noun, lowercase. | +| Biometrics | biometreg | noun, lowercase. | +| Passcode | cod mynediad | noun · ⚠️ NOT `cyfrinair` (= password). Shipped uses `Cyfrinair` — collides with password. | +| **_Backup, import & UX_** | | | +| Backup | copi wrth gefn / gwneud copi wrth gefn | noun / verb · Bitcoin Core cy. | +| Restore | adfer / adferiad | verb / noun · Bitcoin Core cy uses `ail-adfer`. | +| Import | mewnforio / mewnforiad | verb / noun · Bitcoin Core cy. | +| Voucher | taleb | noun, lowercase. | +| Redeem | adbrynu / actifadu | verb · for vouchers prefer `actifadu` (activate). | +| Send | anfon | verb · Bitcoin Core cy. | +| Receive | derbyn | verb · Bitcoin Core cy. | +| Settings | gosodiadau | noun, lowercase · Bitcoin Core cy. | +| Confirm | cadarnhau / cadarnhad | verb / noun. | +| QR Code | cod QR | noun · cy.wikipedia.org/wiki/Cod_QR | +| Clipboard | clipfwrdd | noun, lowercase · Bitcoin Core cy. | +| Memo | nodyn | noun, lowercase. | +| Description | disgrifiad | noun, lowercase · shipped. | +| Label | label / tag | noun, lowercase · Bitcoin Core cy. | diff --git a/loc/vocabulary/da_dk.md b/loc/vocabulary/da_dk.md new file mode 100644 index 00000000000..ccc6eebea5d --- /dev/null +++ b/loc/vocabulary/da_dk.md @@ -0,0 +1,100 @@ +# Danish translation vocabulary (`da_dk.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · da.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand · ⚠️ NOT "Lightning" (shipped string conflates the two). | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker · da.wikipedia.org/wiki/Bitcoin | +| sats | sats / satoshi | noun, lowercase · da.wikipedia.org/wiki/Bitcoin uses "satoshi". | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | wallet | noun, lowercase · English loanword preferred by Danish Bitcoin community; ships in da_dk.json. Native `tegnebog` avoided. | +| Vault | Vault | noun · English loanword kept (BlueWallet brand-adjacent term); avoid Danish `boks`/`hvælving`. | +| Watch-only | watch-only | adj · English loanword preferred; avoid `kun-kigge`/`kigge-tegnebog`. | +| Hardware wallet | hardware wallet | noun, lowercase · English loanword preferred; avoid `hardwaretegnebog`. | +| Seed | seed | noun · English loanword preferred by Danish Bitcoin community; Electrum da_DK also keeps "Seed". Avoid native `frø`. | +| Mnemonic | mnemonic | noun · English loanword preferred; avoid `mnemonisk frase`. | +| Passphrase | adgangsfrase | noun · ⚠️ NOT "adgangskode" (= password). Bitcoin Core da conflates with "Adgangskode" — distinct word preferred to avoid collision with `settings.password`. | +| Public key | offentlig nøgle | noun, lowercase · Bitcoin Core da + Electrum da_DK. | +| Private key | privat nøgle | noun, lowercase · Bitcoin Core da + Electrum da_DK. | +| WIF | WIF | acronym · gloss: format til import af tegnebog. | +| xpub | xpub | acronym, lowercase preferred · ⚠️ known discrepancy: shipped JSON ships uppercase `XPUB` — vocabulary prefers lowercase `xpub`. | +| Descriptor | deskriptor | noun, lowercase · standard da transliteration; Danish tech literature uses `deskriptor`. | +| Derivation path | afledningssti | noun, lowercase · `afledning` is standard da math/CS term for derivation; `sti` = path. | +| Fingerprint | fingerprint | noun, lowercase · English loanword preferred; avoid `fingeraftryk`. | +| Master fingerprint | master fingerprint | noun, lowercase · English form preferred; avoid `hovednøgle-fingeraftryk`. | +| BIP38 | BIP38 | acronym kept · ⚠️ NOT a verb · gloss: krypteret privat nøgle. | +| **_On-chain transactions_** | | | +| Transaction | transaktion | noun, lowercase · Bitcoin Core da ("Transaktion"); Electrum da_DK uses "Overførsel" but BlueWallet ships "Transaktion". | +| Address | adresse | noun, lowercase · Bitcoin Core da + Electrum da_DK. | +| Input | input | noun, lowercase · Bitcoin Core da keeps "Input"/"inputs". | +| Output | output | noun, lowercase · Bitcoin Core da keeps "output"/"outputs" · ⚠️ NOT "Til" (UI recipient label). | +| UTXO | UTXO | acronym · gloss: ubrugt transaktionsoutput. | +| Change | byttepenge | noun · Bitcoin Core da · ⚠️ NOT verb "skifte/ændre". | +| Hex | hex | noun, lowercase · shipped da_dk.json · ⚠️ NOT "hash". | +| Pending | afventende | adj/state · Bitcoin Core da. | +| Unconfirmed | ubekræftet | adj · Bitcoin Core da. | +| Confirmed | bekræftet | adj · Bitcoin Core da. | +| Mempool | mempool | noun · English loanword preferred; avoid `hukommelsespulje`. | +| Broadcast | transmitter / transmittere | verb · Danish `transmitter` preferred (real Danish verb "to broadcast/transmit"); shipped + Bitcoin Core da. Avoid raw English `broadcast`. | +| Block explorer | block explorer | noun, lowercase · English loanword preferred; avoid `blokudforsker`. | +| Onchain | onchain | adj · English loanword preferred; avoid `i blokkæden`. | +| Offchain | offchain | adj · English loanword preferred; avoid `uden for blokkæden`. | +| **_Fees & fee bumping_** | | | +| Fee | gebyr | noun, lowercase · Bitcoin Core da + Electrum da_DK. | +| Fee Bump | gebyrforøgelse | noun · Bitcoin Core da ("Bekræft gebyrforøgelse"). | +| RBF | RBF | acronym · gloss: erstat-med-gebyr · Bitcoin Core da ("Erstat-med-gebyr"). | +| CPFP | CPFP | acronym · gloss: barn-betaler-for-forælder · ⚠️ NOT a verb like "Opret". | +| Speed Up | fremskynd | verb (imperative) · standard da `fremskynde` (to expedite/speed up). | +| **_Lightning_** | | | +| Invoice | faktura / betalingsanmodning | noun · technical / mainstream. | +| Lightning Invoice | Lightning-faktura / Lightning-betalingsanmodning | noun · technical / mainstream. | +| Preimage / Pre-image | Pre-image | noun · English loanword preferred; avoid native `urbillede`. | +| Payment | betaling | noun · ⚠️ NOT verb "betale". | +| Expired | udløbet | adj · Electrum da_DK. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | co-signer | noun · English loanword preferred; avoid `medunderskriver`. ⚠️ NOT "medejer" (co-owner). | +| Multisig | Multisig | noun · English loanword preferred; do not translate. | +| Multisig Vault | Multisig Vault | noun · English loanword preferred; do not translate. | +| Quorum | quorum | noun · English loanword preferred; avoid `kvorum`/`signaturtærskel`. | +| PSBT | PSBT | acronym · Bitcoin Core da keeps. | +| Provide signature | signer / lever signatur | verb · English-flavored Danish (`signer` not `underskriv`). | +| BIP47 / Payment Code | BIP47 / Payment Code | acronym + English term kept; avoid `betalingskode`. | +| Notification transaction | notification transaction | noun · English form preferred; avoid `underretningstransaktion`. | +| SilentPayment | Silent Payments | protocol name kept English (plural). | +| **_Coin control_** | | | +| Coin Control | Coin Control | noun · English loanword preferred; avoid `UTXO-håndtering`/`møntstyring`. | +| Coins (Bitcoin coins / UTXO) | coins | noun, lowercase · English loanword preferred; avoid `mønter`. | +| Frozen | frosset | adj · ⚠️ NOT verb "fryse". | +| **_Security & storage_** | | | +| Encrypted storage | krypteret lager | noun, lowercase · shipped da_dk.json · ⚠️ NOT Title Case. | +| Plausible Deniability | sandsynlig benægtelse | noun, lowercase · shipped da_dk.json · ⚠️ NOT Title Case. | +| Biometrics | biometri | noun, lowercase. | +| Passcode | kode / enheds-PIN | noun · ⚠️ NOT "adgangskode" (= password) — shipped JSON conflates the two; use `kode` or `enheds-PIN` to disambiguate. | +| **_Backup, import & UX_** | | | +| Backup | sikkerhedskopi / backup | noun · Bitcoin Core da ("sikkerhedskopi"); shipped JSON uses loanword "backup". | +| Restore | gendan / gendannelse | verb / noun · Bitcoin Core da. | +| Import | importér / import | verb / noun · Electrum da_DK ("Importér"); shipped JSON: "Importer". | +| Voucher | værdibevis / voucher | noun · native-Danish / loanword · `værdibevis` is the standard da term for prepaid voucher; loanword `voucher` widely used. | +| Redeem | indløs / aktivér | verb · ⚠️ NOT "køb til tegnebog". | +| Send | send | verb · Bitcoin Core da + Electrum da_DK. | +| Receive | modtag | verb · Bitcoin Core da + Electrum da_DK. | +| Settings | indstillinger | noun, lowercase · shipped JSON; Bitcoin Core da also uses "indstillinger" (Electrum da_DK alt: "Opsætning"). | +| Confirm | bekræft / bekræftelse | verb / noun · Bitcoin Core da ("Bekræftelser" = confirmations). | +| QR Code | QR-kode | noun · Bitcoin Core da + Electrum da_DK (hyphenated). | +| Clipboard | udklipsholder | noun, lowercase · Bitcoin Core da + Electrum da_DK. | +| Memo | notat | noun, lowercase. | +| Description | beskrivelse | noun, lowercase · Electrum da_DK. | +| Label | mærkat / etiket | noun, lowercase · `mærkat` per Bitcoin Core da + Electrum da_DK; shipped JSON uses "Etiket". | diff --git a/loc/vocabulary/de_de.md b/loc/vocabulary/de_de.md new file mode 100644 index 00000000000..809f5792705 --- /dev/null +++ b/loc/vocabulary/de_de.md @@ -0,0 +1,98 @@ +# German translation vocabulary (`de_de.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · de.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand · de.wikipedia.org/wiki/Lightning_Network | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | Bitcoin / BTC | noun unit + ticker; `Bitcoin` capitalised as German noun · de.wikipedia.org/wiki/Bitcoin | +| sats | sats | noun, lowercase; English unit retained · Bitcoin Core de | +| sat/vByte | sat/vByte / Satoshi pro vByte | technical unit · compact / explanatory (shipped uses long form). | +| vByte | vByte | technical unit; SegWit-discounted size. | +| **_Wallet, keys & seeds_** | | | +| Wallet | Wallet | noun · English loanword dominant in German crypto UI; `Geldbörse` (dictionary form) reads forced and is avoided · Phoenix de + Trezor de + Electrum de | +| Vault | Tresor / Vault | noun · native `Tresor` preferred per master; brand-style "Vault" acceptable in product contexts. | +| Watch-only | watch-only / Watch-only-Wallet | adj · English loanword retained; short / explanatory compound · ⚠️ NOT `Lesemodus` — it is a wallet type · Phoenix de + Trezor de | +| Hardware wallet | Hardware Wallet | noun · English loanword, two words (no hyphen) · Trezor de + Ledger de | +| Seed | Seed | noun · English loanword; `Wiederherstellungsphrase` reads forced and is avoided · Phoenix de + Trezor de | +| Mnemonic | Mnemonic / Seed | noun · English loanword retained; treated as a synonym of `Seed` · Phoenix de | +| Passphrase | Passphrase | noun · ⚠️ NOT `Passwort` (= password) / NOT `Passcode` (= device code) · Bitcoin Core de + Trezor de | +| Public key | öffentlicher Schlüssel | noun, lowercase adjective · Bitcoin Core de | +| Private key | privater Schlüssel | noun, lowercase adjective · Bitcoin Core de + Electrum de | +| WIF | WIF | acronym · gloss: Wallet Import Format. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | Descriptor / Deskriptor | noun · English loanword preferred; `Deskriptor` is the Germanised form · Green de keeps Latin "Descriptor" | +| Derivation path | Derivation Path / Ableitungspfad | noun · English loanword preferred; Germanised form retained as fallback because shipped strings already use it widely | +| Master fingerprint | Master-Fingerabdruck / Master Fingerprint | noun · native compound preferred; English loanword acceptable in technical contexts · Trezor de + Ledger de | +| BIP38 | BIP38 | acronym kept · gloss: passwortgeschützter privater Schlüssel. ⚠️ NOT a verb / NOT just "Passwort". | +| **_On-chain transactions_** | | | +| Transaction | Transaktion | noun · Bitcoin Core de | +| Address | Adresse | noun · Bitcoin Core de | +| Input | Eingang / Eingabe | ⚠️ NOT Eingang-as-entrance / NOT Login · noun · short / canonical · shipped `details_inputs` uses `Eingänge` · Bitcoin Core de + Electrum de | +| Output | Ausgang / Ausgabe | noun · short / canonical · shipped `details_outputs` uses `Ausgänge` · ⚠️ NOT recipient label "An:" · Bitcoin Core de | +| UTXO | UTXO | acronym · gloss: nicht ausgegebener Transaktionsausgang. | +| Change | Wechselgeld / Wechselgeldadresse | noun · ⚠️ NOT verb `ändern`; coin-change noun. `Wechselgeldadresse` for change-address field · Bitcoin Core de + Bisq de | +| Hex | Hex / Hexadezimal | noun · compact / explanatory · ⚠️ NOT "Hash" / NOT "Rohtransaktion" · Bitcoin Core de + Electrum de | +| Pending | ausstehend | adj/state · Bitcoin Core de + Electrum de | +| Unconfirmed | unbestätigt | adj · Bitcoin Core de | +| Confirmed | bestätigt | adj · Bitcoin Core de | +| Mempool | Mempool | noun · loanword retained · Bitcoin Core de + Electrum de | +| Broadcast | Broadcast / broadcasten | noun / verb · English loanword preferred; `übertragen` retained only in shipped phrasings already using it | +| Block explorer | Block Explorer | noun · English loanword, two words (no hyphen) · Mempool.space de | +| Onchain | Onchain / On-Chain | adj · compact (chip) / hyphenated (body) · Electrum de | +| Offchain | Offchain / Off-Chain | adj · compact (chip) / hyphenated (body) · Electrum de | +| **_Fees & fee bumping_** | | | +| Fee | Gebühr | noun · Bitcoin Core de + Electrum de | +| Fee Bump | Gebührenerhöhung / Gebühr erhöhen | noun / verb · shipped string is more verbose (`Erhöhung TRX-Gebühr nach Senden erlauben`) · Bitcoin Core de + Electrum de | +| RBF | RBF | acronym · gloss: Replace-by-Fee · Bitcoin Core de + Electrum de | +| CPFP | CPFP | acronym · gloss: Child-pays-for-parent · ⚠️ NOT a verb. | +| Speed Up | beschleunigen | verb · UI button label for RBF · Electrum de + Trezor de | +| **_Lightning_** | | | +| Invoice | Rechnung / Zahlungsanforderung | noun · technical / mainstream · Bitcoin Core de + Electrum de + Phoenix de + Zeus de | +| Lightning Invoice | Lightning-Rechnung / Lightning-Zahlungsanforderung | noun, hyphenated compound · technical / mainstream · Phoenix de + Electrum de | +| Preimage | Pre-image | noun · English loanword preferred; `Urbild` (math term) is avoided in UI · Phoenix de | +| Payment | Zahlung | noun · ⚠️ NOT verb `Zahlen` · Bitcoin Core de + Phoenix de | +| Expired | abgelaufen | adj/state · ⚠️ NOT verb form · Bitcoin Core de + Electrum de | +| **_Multisig & advanced addressing_** | | | +| Multisig | Multisig | noun · English loanword retained; `Mehrfachsignatur` / `Multisignatur` avoided · Electrum de + Sparrow de | +| Multisig Vault | Multisignatur Tresor / Multisig Vault | noun · native compound preferred per master; brand-style acceptable. | +| Co-signer | Mitsignierer / Mitunterzeichner | noun · BlueWallet UI / Electrum de · ⚠️ NOT `Miteigentümer` (co-owner). | +| Quorum | Quorum / Signaturschwelle | noun · canonical / UI-clear · shipped uses `signaturfähig` (approximation) · Bitcoin Core de + Electrum de | +| PSBT | PSBT | acronym · gloss: partiell signierte Bitcoin-Transaktion (BIP174). | +| Provide signature | Signatur bereitstellen / Transaktion signieren | verb · generic / specific · ⚠️ NOT `Schlüssel eingeben` (= enter key) · Bitcoin Core de + Electrum de | +| BIP47 / Payment Code | BIP47 / Zahlungscode | acronym kept; `Payment Code` rendered native `Zahlungscode` per master. | +| Notification transaction | Benachrichtigungstransaktion | noun · BIP47-specific 0-value tx · shipped uses this compound. | +| SilentPayment | Silent Payments / stille Zahlungen | protocol name kept English (plural); explanatory `stille Zahlungen` if needed. | +| **_Coin control_** | | | +| Coin Control | Münzkontrolle / Coin Control | noun · native compound preferred per master; loanword acceptable in technical contexts · Electrum de + Zeus de | +| Frozen | eingefroren | adj/state · ⚠️ NOT verb `einfrieren` · Bitcoin Core de + Zeus de | +| **_Security & storage_** | | | +| Encrypted storage | Speicherverschlüsselung | noun, single compound (avoid `verschlüsselte Speicherung`) · shipped `settings.encrypt_storage_explanation_headline` | +| Plausible Deniability | glaubhafte Abstreitbarkeit / glaubhafte Täuschung | ⚠️ NOT Täuschung-as-deception · noun · canonical (matches Wikipedia de) / UI-friendly secondary · shipped uses `Glaubhafte Täuschung` · de.wikipedia.org/wiki/Plausible_Deniability | +| Biometrics | Biometrie | noun · shipped `settings.biometrics` + Trezor de + Zeus de | +| Passcode | Gerätepasscode / Gerätecode | noun · ⚠️ NOT `Passwort` (= app password); device-level code · shipped `settings.biometrics_fail` uses `Gerätepasscode` · Green de + Trezor de | +| **_Backup, import & UX_** | | | +| Backup | Backup / sichern | noun / verb · loanword `Backup` dominant; verb form `sichern` · Bitcoin Core de + Electrum de + Phoenix de | +| Restore | wiederherstellen / Wiederherstellung | verb / noun · Bitcoin Core de + Electrum de | +| Import | importieren / Import | verb / noun · Bitcoin Core de | +| Voucher | Gutschein | noun · shipped `azteco.codeIs` + Zeus de + Cake de | +| Redeem | einlösen | verb · ⚠️ NOT `kaufen` (buy) · activate/cash-in · shipped `azteco.redeemButton` + Zeus de + Cake de | +| Send | senden | verb · Bitcoin Core de uses `Überweisen`; shipped + Electrum + Phoenix use `senden` | +| Receive | empfangen | verb · ⚠️ shipped `receive.header` uses `Erhalten` (passive); prefer `empfangen` (active) · Bitcoin Core de + Electrum de + Phoenix de | +| Settings | Einstellungen | noun · Bitcoin Core de + Electrum de | +| Confirm | bestätigen / Bestätigung | verb / noun · plural noun `Bestätigungen` for on-chain confirmations · Bitcoin Core de | +| QR Code | QR-Code | noun, hyphenated · Bitcoin Core de + Electrum de | +| Clipboard | Zwischenablage | noun · Bitcoin Core de + Electrum de | +| Memo | Notiz | noun · shipped `send.create_memo` + Phoenix de | +| Description | Beschreibung | noun · Bitcoin Core de + Electrum de | +| Label | Bezeichnung / Etikett | noun · shipped uses `Bezeichnung`; Phoenix de `Etikett` · Bitcoin Core de | diff --git a/loc/vocabulary/el.md b/loc/vocabulary/el.md new file mode 100644 index 00000000000..059dab7c338 --- /dev/null +++ b/loc/vocabulary/el.md @@ -0,0 +1,96 @@ +# Greek translation vocabulary (`el.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · el.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand · acronym kept Latin. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker; lowercase unit name. | +| sats | sats | noun, lowercase · ships in el.json. | +| sat/vByte | sat/vByte | technical unit; Latin kept. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | πορτοφόλι | noun, lowercase · el.wikipedia.org/wiki/Bitcoin | +| Vault | χρηματοκιβώτιο / θησαυροφυλάκιο | noun · safe/strongbox sense · ⚠️ distinct from `πορτοφόλι` (wallet). | +| Watch-only | μόνο για παρακολούθηση / μόνο ανάγνωσης | adj · Electrum el_GR + Zeus el ("Πορτοφόλι μόνο για ανάγνωση"). | +| Hardware wallet | πορτοφόλι υλικού / πορτοφόλι hardware | noun · Greek leftmost preferred; Bitcoin Core el keeps `hardware πορτοφόλι` mixed as fallback. | +| Seed | μνημονική φράση / φράση ανάκτησης | noun · mainstream user-facing form preferred. | +| Mnemonic | μνημονική φράση / φράση απομνημόνευσης | noun · technical / shipped form in el.json. | +| Passphrase | κωδική φράση | noun · ⚠️ NOT `κωδικός` (= password / passcode) · Bitcoin Core el: `φράση πρόσβασης`. | +| Public key | δημόσιο κλειδί | noun, lowercase · el.wikipedia.org/wiki/Bitcoin + Electrum el_GR | +| Private key | ιδιωτικό κλειδί | noun, lowercase · Electrum el_GR + Bitcoin Core el | +| WIF | WIF | acronym · gloss: μορφή εισαγωγής πορτοφολιού. | +| xpub | xpub | acronym, lowercase preferred · shipped UI uses uppercase `XPUB` (see TODOs). | +| Descriptor | περιγραφέας | noun, lowercase · output descriptor sense. | +| Derivation path | μονοπάτι παραγωγής / διαδρομή παραγωγής | noun · BIP32 path · Zeus el: `Διαδρομή προέλευσης`. | +| Master fingerprint | αποτύπωμα κύριου κλειδιού | noun · Zeus el: `Δακτυλικό αποτύπωμα κύριου κλειδιού`. | +| BIP38 | BIP38 | acronym kept · gloss: κωδικός BIP38 για αποκρυπτογράφηση ιδιωτικού κλειδιού. | +| **_On-chain transactions_** | | | +| Transaction | συναλλαγή | noun, lowercase · Electrum el_GR + Bitcoin Core el | +| Address | διεύθυνση | noun, lowercase · Electrum el_GR + Bitcoin Core el | +| Input | είσοδος / είσοδος συναλλαγής | noun · short / full · ⚠️ NOT `Εισερχόμενες διευθύνσεις` (verbose & inaccurate — that's an address-side label, not a tx input). | +| Output | έξοδος / έξοδος συναλλαγής | noun · short / full · ⚠️ NOT `Εξερχόμενες διευθύνσεις` (verbose & inaccurate). | +| UTXO | UTXO | acronym · gloss: μη ξοδεμένη έξοδος συναλλαγής. | +| Change | ρέστα / διεύθυνση ρεστών | noun · ⚠️ NOT `Αλλαγή` (= "change/modify"). Bitcoin Core el: `ρέστα`. | +| Hex | hex / δεκαεξαδική μορφή | noun · short / explanatory · ⚠️ NOT "hash" / NOT "δεδομένα συναλλαγής". | +| Pending | σε εκκρεμότητα / εκκρεμεί | adj/state · Bitcoin Core el: `Εκκρεμούν` · shipped `Σε επεξεργασία` is acceptable but loose. | +| Unconfirmed | ανεπιβεβαίωτη / μη επικυρωμένη | adj · feminine agreement w/ `συναλλαγή` · Bitcoin Core el + Zeus el | +| Confirmed | επιβεβαιωμένη / επικυρωμένη | adj · feminine agreement · ⚠️ NOT the noun `επιβεβαιώσεις` (= "confirmations") · Bitcoin Core el | +| Mempool | mempool / δεξαμενή μνήμης | noun · technical / explanatory · Bitcoin Core el: `Δεξαμενή μνήμης`. | +| Broadcast | μεταδίδω / μετάδοση | verb / noun · imperative `μετάδωσέ το στο δίκτυο` · Electrum el_GR + Bitcoin Core el | +| Block explorer | εξερευνητής μπλοκ | noun, lowercase · Electrum el_GR | +| Onchain | on-chain / εντός αλυσίδας | adj · compact (chip) / explanatory (body) · Zeus el: `Εντός της αλυσίδας`. | +| Offchain | off-chain / εκτός αλυσίδας | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | προμήθεια / χρέωση | noun, lowercase · Electrum el_GR + el.wikipedia.org/wiki/Bitcoin (`χρέωση`). Shipped `Κόστος` is loose. | +| Fee Bump | αύξηση προμήθειας | noun. | +| RBF | RBF | acronym · gloss: αντικατάσταση με υψηλότερη προμήθεια / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: το παιδί πληρώνει για τον γονέα · ⚠️ NOT `Δημιουργία` (shipped string is wrong — it means "Create"). | +| Speed Up | επιτάχυνση | verb · button label for RBF. | +| **_Lightning_** | | | +| Invoice | τιμολόγιο / αίτημα πληρωμής | noun · technical / mainstream · Electrum el_GR + Zeus el | +| Lightning Invoice | τιμολόγιο Lightning / αίτημα πληρωμής Lightning | noun · technical / mainstream. | +| Preimage | προεικόνα | noun · math term · Zeus el: `Προεικόνα R`. | +| Payment | πληρωμή | noun · ⚠️ NOT the verb "πληρώνω". Electrum el_GR + Zeus el | +| Expired | έληξε / έχει λήξει | adj/state · Electrum el_GR + Zeus el | +| **_Multisig & advanced addressing_** | | | +| Co-signer | συν-υπογράφων | noun · ⚠️ NOT "συν-ιδιοκτήτης" (co-owner). | +| Quorum | απαρτία / όριο υπογραφών | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym · ships kept Latin. | +| Provide signature | παροχή υπογραφής / υπόγραψε συναλλαγή | verb · generic / specific · matches shipped string. | +| BIP47 / Payment Code | BIP47 / κωδικός πληρωμής | acronym kept; "Payment Code" → `κωδικός πληρωμής` (matches shipped). | +| Notification transaction | συναλλαγή ειδοποίησης | noun · BIP47-specific. | +| SilentPayment | Silent Payments / σιωπηλές πληρωμές | protocol name kept English (plural); explanatory Greek gloss optional. | +| **_Coin control_** | | | +| Coin Control | διαχείριση UTXO / διαχείριση νομισμάτων | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case. Bitcoin Core el: `δυνατοτήτων επιλογής κερμάτων`. | +| Frozen | παγωμένη / δεσμευμένη | adj · feminine state agreement w/ έξοδος · ⚠️ NOT verb "πάγωσε". | +| **_Security & storage_** | | | +| Encrypted storage | κρυπτογραφημένη αποθήκευση | noun, lowercase · ⚠️ NOT Title Case · Bitcoin Core el: `κρυπτογράφηση του πορτοφολιού`. | +| Plausible Deniability | εύλογη δυνατότητα άρνησης | noun, lowercase · shipped form is correct. | +| Biometrics | βιομετρικά | noun, lowercase · matches shipped. | +| Passcode | κωδικός πρόσβασης / PIN | noun · ⚠️ shipped `Κωδικός` collides with "password" — disambiguate to `κωδικός πρόσβασης συσκευής` where the screen is the OS-level lock. | +| **_Backup, import & UX_** | | | +| Backup | αντίγραφο ασφαλείας / δημιούργησε αντίγραφο ασφαλείας | noun / verb · imperative for verb slot · Electrum el_GR + Bitcoin Core el | +| Restore | επαναφορά / επανάκτηση | verb / noun · Electrum el_GR + Bitcoin Core el | +| Import | εισάγω / εισαγωγή | verb / noun · matches shipped. | +| Voucher | κουπόνι | noun, lowercase · ⚠️ shipped `κωδικός κουπονιού` = "voucher code" (longer form, OK if context demands). | +| Redeem | εξαργύρωσε / ενεργοποίησε | verb · imperative · matches shipped. | +| Send | στείλε / αποστολή | verb / noun · Electrum el_GR + Bitcoin Core el | +| Receive | λαμβάνω / λήψη | verb / noun · matches shipped. | +| Settings | ρυθμίσεις | noun, lowercase · Bitcoin Core el | +| Confirm | επιβεβαίωσε / επιβεβαίωση | verb / noun · Bitcoin Core el. ⚠️ shipped uses `Επικύρωση` ("validation/ratification") — `επιβεβαίωση` is more idiomatic and consistent with `confirmations`. | +| QR Code | κωδικός QR | noun, lowercase · Electrum el_GR + Bitcoin Core el | +| Clipboard | πρόχειρο | noun, lowercase · Electrum el_GR + Bitcoin Core el | +| Memo | σημείωση | noun, lowercase · matches shipped. | +| Description | περιγραφή | noun, lowercase · Electrum el_GR. | +| Label | ετικέτα | noun, lowercase · Electrum el_GR + Bitcoin Core el | diff --git a/loc/vocabulary/en.md b/loc/vocabulary/en.md new file mode 100644 index 00000000000..7ae89f9c86e --- /dev/null +++ b/loc/vocabulary/en.md @@ -0,0 +1,98 @@ +# English translation vocabulary (`en.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +Reference. All other locales translate from this. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | Capitalised when referring to the network. | +| Lightning | Lightning | Always capitalised. | +| Electrum | Electrum | Brand. | +| LNDhub | LNDhub | Brand, camelcase. | +| LND | LND | — | +| LNURL | LNURL | Always uppercase. | +| Tor | Tor | — | +| Orbot | Orbot | — | +| GroundControl | GroundControl | Brand, camelcase. | +| **_Units & amounts_** | | | +| bitcoin / BTC | BTC | Unit. | +| sats | sats | Lowercase, plural. | +| sat/vByte | sat/vByte | Casing matters. | +| vByte | vByte | — | +| **_Wallet, keys & seeds_** | | | +| Wallet / Wallets | Wallet / Wallets | — | +| Vault | Multisig Vault | BlueWallet's user-facing name for multisig. | +| Watch-only | watch-only | Hyphenated, lowercase in body text; "Watch-only Wallet" in titles. | +| Hardware wallet | Hardware Wallet | — | +| Seed | Seed | — | +| Mnemonic | mnemonic phrase | — | +| Passphrase | Passphrase | — | +| Public key | public key | — | +| Private key | private key | — | +| WIF | WIF | Acronym, uppercase. | +| xpub | xpub | Lowercase. | +| Descriptor | descriptor | — | +| Derivation path | derivation path | Lowercase in body. | +| Master fingerprint | Master Fingerprint | Title-cased. | +| BIP38 | BIP38 | — | +| **_On-chain transactions_** | | | +| Transaction | Transaction | — | +| Address | Address | — | +| Input | Input | tx input. | +| Output | Output | tx output. | +| UTXO | UTXO | — | +| Change | Change | The output kind. | +| Hex | Hex | — | +| Pending | Pending | — | +| Unconfirmed | unconfirmed | — | +| Confirmed | confirmed | — | +| Mempool | Mempool | — | +| Broadcast | Broadcast | — | +| Block explorer | Block Explorer | — | +| Onchain | Onchain | One word, no hyphen. | +| Offchain | Offchain | One word, no hyphen. | +| **_Fees & fee bumping_** | | | +| Fee | Fee | — | +| Fee Bump | Allow Fee Bump | Setting label. | +| RBF | RBF — Replace by Fee | Acronym + expansion on first use. | +| CPFP | Bump Fee (CPFP) | — | +| Speed Up | Speed Up (RBF) | — | +| **_Lightning_** | | | +| Invoice | Invoice | — | +| Lightning Invoice | Lightning Invoice | — | +| Preimage | Pre-image | Hyphenated in shipped string `lndViewInvoice.preimage`. | +| Payment | Payment | LN context. | +| Expired | Expired | — | +| **_Multisig & advanced addressing_** | | | +| Co-signer | co-signer | Hyphenated, lowercase. | +| Quorum | quorum | — | +| PSBT | PSBT | Expanded once as "Partially Signed Bitcoin Transaction (PSBT)". | +| Provide signature | Provide signature | — | +| BIP47 / Payment Code | BIP47 payment code | — | +| Notification transaction | Notification transaction | BIP47-specific. | +| SilentPayment | SilentPayment | One word, capital-S, capital-P. | +| **_Coin control_** | | | +| Coin Control | Coin Control | Title-cased proper feature name. | +| Frozen | Freeze (verb) / frozen | — | +| **_Security & storage_** | | | +| Encrypted storage | Encrypted Storage / Storage Encryption | — | +| Plausible Deniability | Plausible Deniability | — | +| Biometrics | Biometrics | — | +| Passcode | passcode | — | +| **_Backup, import & UX_** | | | +| Backup / Export | Export/Backup | — | +| Restore | Restore | — | +| Import | Import | — | +| Voucher | voucher | Azte.co context. | +| Redeem | Redeem | — | +| Send | Send | — | +| Receive | Receive | — | +| Settings | Settings | — | +| Confirm | Confirm | — | +| QR Code | QR Code | — | +| Clipboard | Clipboard | — | +| Memo | Memo | Sender note on outgoing tx. | +| Description | Description | Invoice text. | +| Label | Label | Wallet/address name. | diff --git a/loc/vocabulary/es.md b/loc/vocabulary/es.md new file mode 100644 index 00000000000..8ed3cdcfd3d --- /dev/null +++ b/loc/vocabulary/es.md @@ -0,0 +1,96 @@ +# Spanish translation vocabulary (`es.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · es.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning / red Lightning | brand · `red Lightning` in explanatory text · es.wikipedia.org/wiki/Lightning_Network | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand · kept Latin (es.json `groundcontrol_explanation` already keeps it). | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker; lowercase for the unit. | +| sats | sats / satoshis | noun, lowercase · short / full plural · Cake es + Electrum es. | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | cartera / monedero | noun, lowercase · app standardises on `cartera`; `monedero` is a synonym seen in Bitcoin Core es. | +| Vault | bóveda / caja fuerte | noun · ⚠️ NOT Latin "Vault". Shipped strings still say `Vault multifirma`; recommend migrating to `bóveda multifirma`. | +| Watch-only | solo lectura / solo observación | adj · UI / explanatory · Bitcoin Core es uses `solo de observación`. | +| Hardware wallet | cartera de hardware / monedero de hardware | noun, lowercase · Bitcoin Core es. | +| Seed | semilla / frase de recuperación | noun · technical / mainstream. | +| Mnemonic | frase mnemónica / frase mnemotécnica | noun · es.json uses both forms. | +| Passphrase | frase de contraseña / frase de seguridad | noun · ⚠️ NOT `contraseña` alone (= password) · Bitcoin Core es + Electrum es. Shipped strings still keep English `Passphrase`. | +| Public key | llave pública / clave pública | noun, lowercase · app uses `llave`; `clave` is the Bitcoin Core es form. | +| Private key | llave privada / clave privada | noun, lowercase. | +| WIF | WIF | acronym. | +| xpub | xpub | acronym, lowercase. | +| Descriptor | descriptor | noun, lowercase · Electrum es + Bitcoin Core es. | +| Derivation path | ruta de derivación | noun, lowercase. | +| Master fingerprint | huella maestra / huella dactilar maestra | noun, lowercase · short / shipped form. | +| BIP38 | BIP38 | acronym · gloss: contraseña BIP38. | +| **_On-chain transactions_** | | | +| Transaction | transacción | noun, lowercase · ⚠️ shipped `Transaccion` missing accent — fix. | +| Address | dirección | noun, lowercase. | +| Input | entrada / entrada de transacción | noun · short / full · Electrum es + Cake es · ⚠️ NOT "inicio de sesión". Shipped es.json uses English `Inputs`. | +| Output | salida / salida de transacción | noun · short / full · Bitcoin Core es + Electrum es · ⚠️ NOT the UI recipient label "Para:". Shipped es.json uses English `Outputs`. | +| UTXO | UTXO | acronym · gloss: salida de transacción no gastada. | +| Change | cambio / dirección de cambio | noun · ⚠️ NOT verb "cambiar". `cambio` = leftover coin; `dirección de cambio` for change-address field · Electrum es. | +| Hex | hex / hexadecimal | noun · short / explanatory · ⚠️ NOT "hash" / NOT "datos de transacción". Shipped `broadcastNone` says `hash` — fix to `hex`. | +| Pending | pendiente / en espera | adj/state · short / body. Avoid verb forms. | +| Unconfirmed | sin confirmar / no confirmada | adj · short / feminine-agreement · Bitcoin Core es + Electrum es. | +| Confirmed | confirmada / confirmado | adj · feminine (transacción) / masculine · Bitcoin Core es + Electrum es. | +| Mempool | mempool | noun, lowercase · Electrum es + Zeus es (kept Latin). | +| Broadcast | emitir / transmitir | verb · UI / technical · Bitcoin Core es + Electrum es. Noun form: emisión / transmisión. | +| Block explorer | explorador de bloques | noun, lowercase · Bitcoin Core es + Electrum es. | +| Onchain | on-chain / en la cadena | adj · compact (chip) / explanatory (body). | +| Offchain | off-chain / fuera de la cadena | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | comisión / tarifa | noun, lowercase · app uses `comisión`; `tarifa` is a synonym. | +| Fee Bump | aumento de comisión / permitir aumentar la comisión | noun · canonical / shipped phrasing. | +| RBF | RBF | acronym · gloss: reemplazo por comisión / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: el hijo paga por el padre. ⚠️ NOT a verb like "Crear". | +| Speed Up | acelerar / incrementar comisión | verb · short / shipped phrasing. | +| **_Lightning_** | | | +| Invoice | factura / solicitud de pago | noun · technical / mainstream. | +| Lightning Invoice | factura Lightning / solicitud de pago Lightning | noun · ⚠️ shipped `Factura Lighting` typo — fix to `Factura Lightning`. | +| Preimage | preimagen | noun, lowercase · Electrum es + Zeus es. | +| Payment | pago | noun, lowercase · ⚠️ NOT verb "Pagar" · Zeus es. | +| Expired | caducada / caducado | adj · feminine (factura) / masculine · alt: expirada / expirado · Bitcoin Core es + Electrum es. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | cofirmante / cosignatario | noun, lowercase · ⚠️ NOT "copropietario" (co-owner) · Electrum es uses `cosignatario`. | +| Quorum | quórum / umbral de firmas | noun, lowercase · canonical / UI-clear. | +| PSBT | PSBT | acronym. | +| Provide signature | proporcionar firma / firmar transacción | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / código de pago | acronym kept; "Payment Code" → `código de pago`. | +| Notification transaction | transacción de notificación | noun · BIP47-specific. | +| SilentPayment | Silent Payments / pagos silenciosos | protocol name kept English (plural); explanatory `pagos silenciosos` if needed. | +| **_Coin control_** | | | +| Coin Control | control de monedas / selección de monedas | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case · Cake es + Electrum es. Shipped `cc.header` is still `Coin control`. | +| Frozen | congelado / congelada | adj · masc / fem-agreement · ⚠️ NOT verb "congelar" · Electrum es. | +| **_Security & storage_** | | | +| Encrypted storage | almacenamiento cifrado | noun, lowercase · ⚠️ NOT Title Case. | +| Plausible Deniability | negación plausible | noun, lowercase · ⚠️ NOT Title Case. | +| Biometrics | biometría | noun, lowercase. | +| Passcode | código de acceso | noun, lowercase · ⚠️ NOT `contraseña` (= app password). | +| **_Backup, import & UX_** | | | +| Backup | copia de seguridad / hacer copia de seguridad | noun / verb · Bitcoin Core es. Shipped `details_export_backup` says `Exportar / Guardar`. | +| Restore | restaurar / restauración | verb / noun. | +| Import | importar / importación | verb / noun. | +| Voucher | cupón | noun, lowercase. | +| Redeem | canjear | verb · ⚠️ NOT "comprar" / NOT "transferir" · Cake es. | +| Send | enviar | verb. | +| Receive | recibir | verb. | +| Settings | ajustes / configuración | noun, lowercase · app uses `ajustes`; `configuración` is a synonym. | +| Confirm | confirmar / confirmación | verb / noun. | +| QR Code | código QR | noun, lowercase. | +| Clipboard | portapapeles | noun, lowercase. | +| Memo | comentario / nota | noun, lowercase · app uses `comentario`; `nota` also seen. | +| Description | descripción | noun, lowercase. | +| Label | etiqueta | noun, lowercase · Cake es. | diff --git a/loc/vocabulary/es_419.md b/loc/vocabulary/es_419.md new file mode 100644 index 00000000000..6ccb1d7b654 --- /dev/null +++ b/loc/vocabulary/es_419.md @@ -0,0 +1,96 @@ +# Spanish, Latin America translation vocabulary (`es_419.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | — | +| Lightning | Lightning | — | +| Electrum | Electrum | — | +| LNDhub | LNDhub | — | +| LND | LND | — | +| LNURL | LNURL | — | +| Tor | Tor | — | +| Orbot | Orbot | — | +| GroundControl | GroundControl | — | +| **_Units & amounts_** | | | +| bitcoin / BTC | BTC | — | +| sats | sats | — | +| sat/vByte | sat/vByte | — | +| vByte | vByte | — | +| **_Wallet, keys & seeds_** | | | +| Wallet | Billetera | — | +| Vault | Bóveda | — | +| Watch-only | solo ver / solo lectura | adj, lowercase · ⚠️ shipped JSON mixes `Sólo ver` (old spelling) and `Solo ver` (current RAE) — prefer `solo` without accent per RAE 2010; Bitcoin Core es `solo lectura`. | +| Hardware wallet | Billetera de Hardware | — | +| Seed | Semilla | — | +| Mnemonic | Frase mnemotécnica | — | +| Passphrase | Frase de acceso (Passphrase) | — | +| Public key | Clave pública | — | +| Private key | Clave privada | — | +| WIF | WIF | — | +| xpub | XPUB | — | +| Descriptor | descriptor | noun, lowercase · Bitcoin Core es (`Descriptor`). | +| Derivation path | Ruta de derivación | — | +| Master fingerprint | Huella Digital Maestra | — | +| BIP38 | BIP38 | — | +| **_On-chain transactions_** | | | +| Transaction | Transacción | — | +| Address | Dirección | — | +| Input | Entrada | — | +| Output | Salida | — | +| UTXO | UTXO | acronym · gloss: salida de transacción no gastada · Bitcoin Core es. | +| Change | Cambio | — | +| Hex | hex | — | +| Pending | Pendiente | — | +| Unconfirmed | No confirmada | — | +| Confirmed | Confirmada | — | +| Mempool | mempool | noun, lowercase · Bitcoin Core es keeps loanword. | +| Broadcast | Transmisión | — | +| Block explorer | Explorador de bloques | — | +| Onchain | En cadena | — | +| Offchain | Fuera de cadena | — | +| **_Fees & fee bumping_** | | | +| Fee | comisión / tarifa | noun, lowercase · ⚠️ shipped JSON mixes `Tasa` (= "rate"), `Tarifa`, `Comisión` inconsistently · Cake es_419 prefers `comisión` (= miner commission); Bitcoin Core es uses `comisión`. | +| Fee Bump | Aumento de tarifas | — | +| RBF | RBF | — | +| CPFP | CPFP | — | +| Speed Up | acelerar / aumentar comisión | verb, lowercase · ⚠️ shipped `Aumentar Comisión` collides with Fee Bump label · prefer `acelerar` for the Speed Up button. | +| **_Lightning_** | | | +| Invoice | Factura | — | +| Lightning Invoice | Factura Lightning | — | +| Preimage | preimagen / imagen previa | noun, lowercase · math term `preimagen` (es math literature) preferred over shipped `Imagen previa`. | +| Payment | Pago | — | +| Expired | Expirado / Caducada | — | +| **_Multisig & advanced addressing_** | | | +| Co-signer | Cofirmante | — | +| Quorum | Quórum | — | +| PSBT | PSBT | — | +| Provide signature | Proporcionar firma | — | +| BIP47 / Payment Code | Código de pago BIP47 | — | +| Notification transaction | Transacción de notificación | — | +| SilentPayment | SilentPayment | — | +| **_Coin control_** | | | +| Coin Control | Control de Monedas | — | +| Frozen | Congelado / Congelar | — | +| **_Security & storage_** | | | +| Encrypted storage | Cifrado de almacenamiento | — | +| Plausible Deniability | Negación plausible | — | +| Biometrics | Biometría | — | +| Passcode | Código de acceso | — | +| **_Backup, import & UX_** | | | +| Backup | Copia de seguridad | — | +| Restore | Recuperar | — | +| Import | Importar | — | +| Voucher | Cupón | — | +| Redeem | Canjear | — | +| Send | Enviar | — | +| Receive | Recibir | — | +| Settings | Ajustes | — | +| Confirm | Confirmar | — | +| QR Code | Código QR | — | +| Clipboard | Portapapeles | — | +| Memo | Nota | — | +| Description | Descripción | — | +| Label | Etiqueta | — | diff --git a/loc/vocabulary/et_EE.md b/loc/vocabulary/et_EE.md new file mode 100644 index 00000000000..0a2b2117933 --- /dev/null +++ b/loc/vocabulary/et_EE.md @@ -0,0 +1,96 @@ +# Estonian translation vocabulary (`et_EE.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · et.wikipedia.org/wiki/Bitcoin (uppercase = network, lowercase = unit). | +| Lightning | Lightning | brand. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand/acronym. | +| Tor | Tor | brand · et.wikipedia.org/wiki/Tor | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker · et.wikipedia.org/wiki/Bitcoin (lowercase for unit). | +| sats | satid / satoshid | noun, lowercase · Estonian plural form, but `sats` may be kept untranslated in UI chips. | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | rahakott | noun, lowercase · Bitcoin Core et + et.wikipedia.org/wiki/Krüptoraha | +| Vault | seif / hoidla | noun; `seif` for safe/strongbox sense. Avoid Latin "Vault". | +| Watch-only | ainult vaatamiseks / vaatlusrežiim | adj · short / wallet-type · Bitcoin Core et `ainult vaatamiseks`. | +| Hardware wallet | riistvaraline rahakott | noun, lowercase · et.wikipedia.org/wiki/Krüptoraha (inferred standard form). | +| Seed | seeme / taastefraas | noun; shipped UI uses `Seeme`. Mainstream form: `taastefraas` (recovery phrase). | +| Mnemonic | mnemooniline fraas / taastefraas | noun · technical / mainstream. | +| Passphrase | salafraas | noun · ⚠️ NOT `parool` (= password) · Bitcoin Core et. | +| Public key | avalik võti | noun, lowercase · et.wikipedia.org/wiki/Krüptoraha | +| Private key | privaatvõti | noun, lowercase · Bitcoin Core et. Shipped `võti` is too generic — should specify private. | +| WIF | WIF | acronym · gloss: rahakoti impordi formaat. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | deskriptor | noun, lowercase · Estonian tech literature standard transliteration. | +| Derivation path | tuletustee / tuletusrada | noun · BIP32 path · constructed from `tuletus` (derivation, standard et math/CS term) + `tee`/`rada` (path). | +| Master fingerprint | põhivõtme sõrmejälg | noun · constructed from `põhivõti` (master key) + `sõrmejälg` (fingerprint). | +| BIP38 | BIP38 | acronym kept · gloss: BIP38 parool / krüpteeritud privaatvõti. | +| **_On-chain transactions_** | | | +| Transaction | tehing | noun, lowercase · Bitcoin Core et + et.wikipedia.org/wiki/Bitcoin | +| Address | aadress | noun, lowercase · Bitcoin Core et. | +| Input | sisend | noun, lowercase · Bitcoin Core et. | +| Output | väljund | noun, lowercase · Bitcoin Core et · ⚠️ NOT "Saaja" (= recipient UI label). | +| UTXO | UTXO | acronym · gloss: kulutamata tehingu väljund. | +| Change | vahetusraha / tagastusaadress | noun · ⚠️ NOT verb "muuta". `vahetusraha` = change coin (Bitcoin Core et); `tagastusaadress` for change-address field. | +| Hex | hex / kuueteistkümnendsüsteem | noun · short / explanatory · ⚠️ NOT "hash" / NOT "tehingu andmed". | +| Pending | ootel | adj/state · Bitcoin Core et. Avoid noun "ootamine". | +| Unconfirmed | kinnitamata | adj · Bitcoin Core et. | +| Confirmed | kinnitatud | adj · Bitcoin Core et. | +| Mempool | mempool / tehingute ootejärjekord | noun · technical loanword / explanatory ("transaction wait-queue"). | +| Broadcast | leviedasta / saada võrku | verb · UI-clear. Noun: levitamine. ⚠️ Shipped `Ülekanne` (= "transfer") is wrong sense — fix to `leviedastus` / `saada võrku`. | +| Block explorer | plokiuurija / plokiahela uurija | noun, lowercase · constructed from et.wikipedia.org/wiki/Bitcoin `plokiahel`. | +| Onchain | ahelas / plokiahelas | adj · compact (chip) / explanatory (body) · from `plokiahel`. | +| Offchain | väljaspool ahelat / off-chain | adj · explanatory / compact (chip). | +| **_Fees & fee bumping_** | | | +| Fee | tasu / võrgutasu | noun, lowercase · Bitcoin Core et. | +| Fee Bump | tasu suurendamine | noun · constructed from `tasu` (fee) + `suurendamine` (increase). | +| RBF | RBF | acronym · gloss: tasu asendamine / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: laps maksab vanema eest. ⚠️ NOT a verb. | +| Speed Up | kiirenda | verb (imperative) · standard et `kiirendama` (to speed up). | +| **_Lightning_** | | | +| Invoice | arve / maksenõue | noun · mainstream / explanatory · `arve` is standard et invoice term; `maksenõue` = payment demand. | +| Lightning Invoice | Lightning arve / Lightningi maksenõue | noun · brand + localised noun. | +| Preimage | eelpilt / preimage | noun · et math literature `eelpilt` (preimage of a function) / English fallback. | +| Payment | makse | noun · ⚠️ NOT verb "maksta". | +| Expired | aegunud | adj/state form. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | kaasallkirjastaja | noun · ⚠️ NOT "kaasomanik" (co-owner). From `allkirjastaja` (Bitcoin Core et `allkirjasta`). | +| Quorum | kvoorum / allkirjade lävi | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym · gloss: osaliselt allkirjastatud Bitcoini tehing. | +| Provide signature | anna allkiri / allkirjasta tehing | verb · generic / specific · Bitcoin Core et `allkiri` / `allkirjasta`. | +| BIP47 / Payment Code | BIP47 / maksekood | acronym kept; "Payment Code" → "maksekood". | +| Notification transaction | teavitustehing | noun · BIP47-specific; constructed. | +| SilentPayment | Silent Payments / vaiksed maksed | protocol name kept English (plural); explanatory `vaiksed maksed` if needed. | +| **_Coin control_** | | | +| Coin Control | UTXO haldus / müntide haldus | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case. | +| Frozen | külmutatud | adj · state form · ⚠️ NOT verb "külmutada". | +| **_Security & storage_** | | | +| Encrypted storage | krüpteeritud hoidla | noun, lowercase · shipped · ⚠️ NOT Title Case. | +| Plausible Deniability | usutav eitatavus / usutava eitamise võimalus | noun, lowercase · short / full · constructed. | +| Biometrics | biomeetria | noun, lowercase. | +| Passcode | pääsukood | noun · ⚠️ NOT `parool` (= password). Shipped `parool` collides with password — recommend `pääsukood`. | +| **_Backup, import & UX_** | | | +| Backup | varundus / varunda | noun / verb · Bitcoin Core et `varunda` / `varundamine`. | +| Restore | taasta / taastamine | verb / noun · Bitcoin Core et. | +| Import | impordi / import | verb / noun. | +| Voucher | vautšer | noun, lowercase · shipped. | +| Redeem | lunasta | verb (imperative) · shipped · ⚠️ NOT "osta rahakotti". | +| Send | saada | verb · Bitcoin Core et. | +| Receive | võta vastu | verb · Bitcoin Core et. | +| Settings | seaded | noun, lowercase · Bitcoin Core et. | +| Confirm | kinnita / kinnitus | verb / noun · plural kinnitused = confirmations. | +| QR Code | QR-kood | noun · Bitcoin Core et + et.wikipedia.org/wiki/QR-kood (alt: `ruutkood`). | +| Clipboard | lõikelaud | noun, lowercase · standard Estonian software term · ⚠️ shipped Bitcoin Core et uses `vahemälu` (= cache) which is incorrect — prefer `lõikelaud`. | +| Memo | märkus / memo | noun, lowercase. | +| Description | kirjeldus | noun, lowercase. | +| Label | silt / märgis | noun, lowercase · Bitcoin Core et. | diff --git a/loc/vocabulary/fa.md b/loc/vocabulary/fa.md new file mode 100644 index 00000000000..2e74db761f8 --- /dev/null +++ b/loc/vocabulary/fa.md @@ -0,0 +1,98 @@ +# Persian translation vocabulary (`fa.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +> RTL locale. Brand names appear in Latin script inside the surrounding script; values below are the rendered form used in `loc/fa.json`. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / بیت‌کوین | brand kept Latin; بیت‌کوین in body text · fa.wikipedia.org/wiki/بیت‌کوین | +| Lightning | Lightning / لایتنینگ | brand kept Latin; transliteration in body text. | +| Electrum | Electrum | brand kept Latin · Persian gloss `الکترام` only in explanatory text. | +| LNDhub | LNDhub | brand kept Latin. | +| LND | LND | brand kept Latin. | +| LNURL | LNURL | brand kept Latin. | +| Tor | Tor | brand kept Latin · fa.wikipedia.org/wiki/تور_(شبکه) | +| Orbot | Orbot | brand kept Latin. | +| GroundControl | GroundControl | brand kept Latin. | +| **_Units & amounts_** | | | +| bitcoin / BTC | بیت‌کوین / BTC | noun unit + ticker; ticker Latin. | +| sats | ساتوشی | noun; transliteration of "satoshi". | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin · gloss: ساتوشی بر بایت مجازی. | +| vByte | vByte | technical unit · gloss: بایت مجازی. | +| **_Wallet, keys & seeds_** | | | +| Wallet | کیف پول | noun · lit. "money bag". | +| Vault | گاوصندوق | noun · lit. "safe/strongbox" · ⚠️ NOT a brand; translated. | +| Watch-only | فقط‌خواندنی / کیف پول ناظر | adj · read-only / observer-wallet sense · Bitcoin Core fa "فقط دیدنی". | +| Hardware wallet | کیف پول سخت‌افزاری | noun. | +| Seed | سید / عبارت بازیابی | noun · technical / mainstream. | +| Mnemonic | عبارت یادیار / عبارت بازیابی | noun · technical / mainstream. | +| Passphrase | عبارت عبور | noun · ⚠️ NOT `گذرواژه` (= password) and NOT `رمز عبور`. App ships `پس‌فریز (Passphrase)` — distinct from password. | +| Public key | کلید عمومی | noun. | +| Private key | کلید خصوصی | noun. | +| WIF | WIF | acronym · gloss: قالب وارد‌کردن کیف پول. | +| xpub | xpub | acronym · prefer lowercase · ⚠️ shipped `XPUB` uppercase — vocabulary prefers `xpub`. | +| Descriptor | توصیف‌گر | noun · math/script-template sense. | +| Derivation path | مسیر اشتقاق | noun. | +| Master fingerprint | اثر انگشت اصلی / اثر انگشت کلید مادر | noun · short / explanatory. | +| BIP38 | BIP38 | acronym · gloss: کلید خصوصی محافظت‌شده با گذرواژه. ⚠️ NOT a verb. | +| **_On-chain transactions_** | | | +| Transaction | تراکنش | noun. | +| Address | آدرس | noun. | +| Input | ورودی / ورودی تراکنش | noun · short / full · ⚠️ NOT "login/entrance" sense. | +| Output | خروجی / خروجی تراکنش | noun · short / full · ⚠️ NOT the UI recipient label "به:". | +| UTXO | UTXO | acronym · gloss: خروجی تراکنش خرج‌نشده. | +| Change | باقی‌مانده / آدرس باقی‌مانده | noun · ⚠️ NOT verb "تغییر دادن". `باقی‌مانده` = leftover; `آدرس باقی‌مانده` for change-address. Bitcoin Core fa. | +| Hex | hex / هگزادسیمال | noun · short / explanatory · Latin `hex` is an intentional loanword (glossary-allowed); target-script form is the explanatory `هگزادسیمال` · ⚠️ NOT "hash" and NOT "دادهٔ تراکنش". | +| Pending | در انتظار ثبت / در انتظار | adj/state · body / chip. ⚠️ NOT noun "انتظار". | +| Unconfirmed | تأییدنشده | adj/state · Bitcoin Core fa. | +| Confirmed | تأییدشده / تأیید | adj/state · recommended `تأییدشده` (adj/state) vs shipped `تأیید` (noun "confirmation") · ⚠️ state form needs the `-شده` suffix. | +| Mempool | حافظهٔ تراکنش‌ها / mempool | noun · explanatory Persian / technical Latin · common noun (not a brand) · Bitcoin Core fa. | +| Broadcast | انتشار / منتشر کردن | noun / verb · button vs status · Electrum fa. | +| Block explorer | مرورگر بلاک / کاوشگر بلاک | noun · short / explanatory. | +| Onchain | آن‌چین / روی زنجیره | adj · compact (chip) / explanatory (body). | +| Offchain | آف‌چین / خارج از زنجیره | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | کارمزد | noun. | +| Fee Bump | افزایش کارمزد | noun. | +| RBF | RBF | acronym · gloss: جایگزینی با کارمزد بالاتر / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: فرزند به‌جای والد می‌پردازد. ⚠️ NOT a verb like "ایجاد". | +| Speed Up | تسریع | verb · UI button label · ⚠️ shipped `افزایش کارمزد` collides with `Fee Bump` — use `تسریع` to disambiguate. | +| **_Lightning_** | | | +| Invoice | صورت‌حساب / فاکتور | noun · mainstream / technical · Electrum fa uses `فاکتور`. | +| Lightning Invoice | صورت‌حساب Lightning / فاکتور Lightning | noun · brand kept Latin + Persian noun. | +| Preimage | پیش‌تصویر / preimage | noun · math-translation / technical Latin · ⚠️ NOT "تصویر پیشین". | +| Payment | پرداخت | noun · ⚠️ NOT verb "پرداختن". | +| Expired | منقضی‌شده / منقضی | adj/state. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | هم‌امضاکننده / امضاکنندهٔ مشترک | noun · ⚠️ NOT "هم‌مالک" (co-owner) · Bitcoin Core fa "امضاکننده". | +| Quorum | حد نصاب / آستانهٔ امضا | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym · gloss: تراکنش بیت‌کوینی به‌صورت جزئی امضاشده. | +| Provide signature | ارائهٔ امضا / امضای تراکنش | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / کد پرداخت | acronym kept Latin; "Payment Code" → `کد پرداخت`. ⚠️ shipped `بیپ47` (transliteration) — prefer Latin `BIP47`. | +| Notification transaction | تراکنش اعلان / تراکنش آگاه‌سازی | noun · BIP47-specific. | +| SilentPayment | Silent Payments / پرداخت‌های خاموش | protocol name kept Latin (plural); explanatory `پرداخت‌های خاموش`. | +| **_Coin control_** | | | +| Coin Control | مدیریت UTXO / مدیریت کوین‌ها | noun · technical / mainstream · ⚠️ NOT Title Case. | +| Frozen | مسدودشده / مسدود | adj/state · ⚠️ NOT verb "مسدودکردن". | +| **_Security & storage_** | | | +| Encrypted storage | فضای ذخیره‌سازی رمزگذاری‌شده | noun · ⚠️ NOT Title Case. | +| Plausible Deniability | انکار موجه / امکان انکار موجه | noun · short / explanatory · ⚠️ NOT Title Case. | +| Biometrics | بیومتریک / احراز هویت بیومتریک | noun · short / explanatory. | +| Passcode | رمز دستگاه / کد عبور دستگاه | noun · ⚠️ NOT `گذرواژه` (= app password) — shipped `گذرواژه` collides with password; use a distinct device-level word. | +| **_Backup, import & UX_** | | | +| Backup | نسخهٔ پشتیبان / تهیهٔ نسخهٔ پشتیبان | noun / verb. | +| Restore | بازیابی / بازیابی کردن | noun / verb. | +| Import | وارد کردن / وارد‌سازی | verb / noun. | +| Voucher | بن / کوپن | noun · ⚠️ shipped `کد تخفیف` (= discount code) — fix. `بن` = voucher; `کوپن` = coupon-style fallback. | +| Redeem | فعال‌سازی / نقد کردن | verb · activate / cash-in · matches glossary intent. App ships `فعال‌سازی` — OK. | +| Send | ارسال / فرستادن | verb. | +| Receive | دریافت | verb. | +| Settings | تنظیمات | noun. | +| Confirm | تأیید / تأیید کردن | noun / verb · `تأییدیه` (plural `تأییدیه‌ها`) for tx-block confirmations. | +| QR Code | کد QR | noun. | +| Clipboard | کلیپ‌بورد / حافظهٔ موقت | noun · loanword / explanatory. | +| Memo | یادداشت | noun. | +| Description | توضیحات / شرح | noun · standard / shipped. | +| Label | برچسب | noun. | diff --git a/loc/vocabulary/fi_fi.md b/loc/vocabulary/fi_fi.md new file mode 100644 index 00000000000..d305abad61c --- /dev/null +++ b/loc/vocabulary/fi_fi.md @@ -0,0 +1,96 @@ +# Finnish translation vocabulary (`fi_fi.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · fi.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning / Salama | brand · Latin form per Bitcoin Core fi + fi.wikipedia.org/wiki/Lightning_Network; mainstream Finnish form `Salama` per Zeus fi and shipped BlueWallet strings. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand · standardise on `LNDhub`; drop variant `LNDHub`. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand · Bitcoin Core fi | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit (lowercase) + ticker · fi.wikipedia.org/wiki/Bitcoin | +| sats | satoshi / sattia | noun, lowercase · singular / partitive plural. | +| sat/vByte | sat/vByte | technical unit; Latin kept. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | lompakko | noun, lowercase · Bitcoin Core fi | +| Vault | holvi | noun, lowercase · ⚠️ NOT brand "Vault" — translate to safe/strongbox sense. | +| Watch-only | pelkästään katseltava / katselulompakko | adj / noun-wallet · Bitcoin Core fi (`pelkästään katseltavissa`, `katselulompakot`). ⚠️ NOT "view mode" — wallet type. | +| Hardware wallet | laitelompakko / laitteistolompakko | noun, lowercase · Bitcoin Core fi (`laitelompakko`, `laitteistolompakkoa`). Avoid hybrid `hardware-lompakko`. | +| Seed | siemen / palautuslause | noun · technical / mainstream (recovery phrase). | +| Mnemonic | palautuslause / muistilause / juurisanat | noun · mainstream / technical / Bitcoin Core fi (`juurisanat` for HD seed); UX prefers `palautuslause`. | +| Passphrase | tunnuslause | noun · Bitcoin Core fi · ⚠️ NOT `salasana` (= password). | +| Public key | julkinen avain | noun, lowercase. | +| Private key | yksityinen avain | noun, lowercase. | +| WIF | WIF | acronym · gloss: lompakon tuontimuoto. | +| xpub | xpub | acronym, lowercase preferred (drop UPPERCASE variant). | +| Descriptor | kuvain / deskriptori | noun, lowercase · Bitcoin Core fi (`kuvain`) / technical loan. | +| Derivation path | johdantopolku / johdospolku | noun · shipped form / Zeus fi (`derivaatiopolku`) — `johdantopolku` matches current strings. | +| Master fingerprint | pääavaimen sormenjälki | noun, lowercase · Zeus fi (`Pääavaimen sormenjälki`). Fix shipped `Pää sormenjälki` (mis-spaced). | +| BIP38 | BIP38 | acronym kept · ⚠️ NOT a verb / NOT "password" alone. | +| **_On-chain transactions_** | | | +| Transaction | siirtotapahtuma | noun, lowercase · Bitcoin Core fi | +| Address | osoite | noun, lowercase · Bitcoin Core fi | +| Input | syöte / siirron syöte | noun · short / full. Use singular `syöte`; plural `syötteet` only for lists. | +| Output | ulostulo / siirron ulostulo | noun · short / full · ⚠️ NOT the UI recipient label "Vastaanottaja". | +| UTXO | UTXO | acronym · gloss: käyttämätön siirtotapahtuman ulostulo. | +| Change | vaihtoraha / vaihtoraha-osoite | noun · ⚠️ NOT verb `vaihda` (= to swap/change). `vaihtoraha` = leftover · Bitcoin Core fi (`vaihtoraha`). Fix shipped `vaihto`. | +| Hex | hex / heksadesimaali | noun · short / explanatory · ⚠️ NOT "hash" and NOT "hex-numero" (number). | +| Pending | odottaa / vahvistusta odottava | adj/state · button vs body · Bitcoin Core fi (`Odotetaan:`, `Odottaa vahvistusta`). | +| Unconfirmed | vahvistamaton | adj · Bitcoin Core fi | +| Confirmed | vahvistettu | adj · Bitcoin Core fi · ⚠️ Fix shipped `vahvistukset` (= "confirmations" noun). | +| Mempool | mempool / muistiallas | noun · Latin technical / Bitcoin Core fi gloss (`Muistiallas`). | +| Broadcast | lähetä verkkoon / lähetys | verb / noun · Bitcoin Core fi (`Lähetä Tx`, `valmis lähetettäväksi`). | +| Block explorer | lohkoselain | noun, lowercase · shipped form matches idiom. | +| Onchain | ketjussa / pääketju / pääketjussa | adj (inessive) / noun (chip label) / adj-phrase (body) · Zeus fi · Finnish noun-form `pääketju` acceptable for compact chip. | +| Offchain | salamaverkossa / pääketjun ulkopuolella | adj · compact (Lightning context) / explanatory · Zeus fi (`salamaverkossa`). | +| **_Fees & fee bumping_** | | | +| Fee | siirtomaksu | noun, lowercase · Zeus fi | +| Fee Bump | siirtomaksun nosto / siirtomaksun korotus | noun · shipped / alt-form per Bitcoin Core fi (`korottaa siirtotapahtuman palkkiota`). | +| RBF | RBF | acronym · gloss: Replace-By-Fee · Bitcoin Core fi keeps `Replace-By-Fee` Latin. | +| CPFP | CPFP | acronym · gloss: lapsi maksaa vanhemman puolesta · ⚠️ NOT a verb. | +| Speed Up | nopeuta / nosta siirtomaksua | verb · short button / explanatory action. | +| **_Lightning_** | | | +| Invoice | lasku / maksupyyntö | noun · mainstream / technical · Zeus fi (`Laskut`). | +| Lightning Invoice | Lightning-lasku / Salamalasku | noun · Latin-brand form / mainstream · Zeus fi (`Salamalasku`); shipped strings use `Salamalasku`. | +| Preimage | alkukuva / preimage | noun · shipped Finnish / technical loan · ⚠️ math sense, not "preview image". | +| Payment | maksu | noun · ⚠️ NOT verb `maksaa`. | +| Expired | vanhentunut | adj · Zeus fi · `Erääntynyt` (shipped) implies "due/matured" — prefer `vanhentunut` for invoice expiry. | +| **_Multisig & advanced addressing_** | | | +| Co-signer / Signer | osa-allekirjoittaja / allekirjoittaja | noun · co-signer (specific) / signer (generic, Bitcoin Core fi `allekirjoittaja`). ⚠️ NOT "co-owner". | +| Quorum | kynnys / quorum | noun · UI-clear / Latin technical. ⚠️ Lowercase. | +| PSBT | PSBT | acronym · gloss: osittain allekirjoitettu bitcoin-siirtotapahtuma · Bitcoin Core fi | +| Provide signature | allekirjoita / toimita allekirjoitus | verb · short button / explanatory. | +| BIP47 / Payment Code | BIP47 / maksukoodi | acronym + noun · shipped form. | +| Notification transaction | ilmoitustapahtuma | noun · BIP47-specific; literal compound. | +| SilentPayment | Silent Payments / hiljaiset maksut | protocol name Latin (plural); explanatory `hiljaiset maksut` if needed. | +| **_Coin control_** | | | +| Coin Control | UTXO:iden hallinta / kolikoiden hallinta | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case. Bitcoin Core fi (`Kolikoiden valinta`). | +| Frozen | jäädytetty | adj · ⚠️ NOT verb `jäädytä` (= "freeze!"). Zeus fi (`Jäädytetty`). | +| **_Security & storage_** | | | +| Encrypted storage | salattu tallennustila / tallennustilan salaus | noun, lowercase · ⚠️ NOT Title Case · Bitcoin Core fi (`Salaa lompakko`). | +| Plausible Deniability | uskottava kiistettävyys | noun, lowercase · ⚠️ NOT Title Case · en.wikipedia.org/wiki/Plausible_deniability. | +| Biometrics | biometriikka / biometriset tiedot | noun, lowercase · short / full. | +| Passcode | pääsykoodi | noun · ⚠️ NOT `salasana` (= password). Distinct device-unlock term. | +| **_Backup, import & UX_** | | | +| Backup | varmuuskopio / varmuuskopioi | noun / verb · Bitcoin Core fi (`Varmuuskopioi lompakko`). | +| Restore | palauta / palautus | verb / noun · Bitcoin Core fi (`Palauta ja siirrä lompakko`). | +| Import | tuo / tuonti | verb / noun · Bitcoin Core fi | +| Voucher | kuponki | noun, lowercase. | +| Redeem | lunasta / lunastus | verb / noun · ⚠️ NOT "buy to wallet". | +| Send | lähetä | verb · Bitcoin Core fi | +| Receive | vastaanota | verb · Zeus fi | +| Settings | asetukset | noun, lowercase. | +| Confirm | vahvista / vahvistus | verb / noun. | +| QR Code | QR-koodi | noun · Bitcoin Core fi (`QR koodit`). | +| Clipboard | leikepöytä | noun, lowercase · Bitcoin Core fi | +| Memo | muistio / muistiinpano | noun, lowercase · Zeus fi (`muistiota`). | +| Description | kuvaus / selite | noun, lowercase · mainstream / shipped. | +| Label | nimike / merkintä | noun, lowercase · Bitcoin Core fi (`Nimike`) / Zeus fi (`Merkintä`). Replace shipped `Etiketti`. | diff --git a/loc/vocabulary/fo.md b/loc/vocabulary/fo.md new file mode 100644 index 00000000000..9d0137dcae4 --- /dev/null +++ b/loc/vocabulary/fo.md @@ -0,0 +1,96 @@ +# Faroese translation vocabulary (`fo.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand · fo.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker, lowercase as unit. | +| sats | sats | noun, lowercase · shipped `units.sats` = `sats`. | +| sat/vByte | sat/tBýt | technical unit · shipped UI uses `sat/tBýt` (`units.sat_vbyte`, `send.create_satoshi_per_vbyte`). | +| vByte | tBýt | coined Faroese form for vByte; `být` = byte. | +| **_Wallet, keys & seeds_** | | | +| Wallet | mappa | noun, lowercase · lit. "folder/pouch" · Bitcoin Core fo + shipped fo.json. | +| Vault | virðisgoymsla | noun, lowercase · lit. "value storage" · ⚠️ NOT a brand · shipped `multisig.multisig_vault`. | +| Watch-only | eygleiðingarmappa | noun · "observation wallet" · shipped `wallets.import_success_watchonly` + `transactions.watchOnlyWarningDescription`. | +| Hardware wallet | tólbúnaðarmappa | noun, lowercase · shipped `wallets.details_use_with_hardware_wallet`. | +| Seed | rótorð | noun, lowercase · lit. "root-words" · shipped `_.seed` + `pleasebackup.ask`. | +| Mnemonic | áminningarramsa | noun, lowercase · lit. "reminder-rhyme" · shipped `pleasebackup.text` + `multisig.invalid_mnemonics`. | +| Passphrase | loynisetning | noun · lit. "secret sentence" · ⚠️ distinct from `loyniorð` (password) · shipped `wallets.import_passphrase`. | +| Public key | almennur lykil | noun, lowercase · shipped `_.wallet_key` = "Almennur mappulykil". | +| Private key | privatur lykil | noun, lowercase · shipped `addresses.copy_private_key` + `wallets.looks_like_bip38`. | +| WIF | WIF | acronym · shipped `wallets.import_explanation`. | +| xpub | XPUB | acronym · shipped UI ships uppercase `XPUB` (`wallets.xpub_title`, `details_show_xpub`); vocabulary prefers lowercase but app convention kept. | +| Descriptor | lyklalýsing | noun, lowercase · lit. "key description" · Bitcoin Core fo (`Private keys and addresses can be imported using descriptors` → `lyklalýsingun`). | +| Derivation path | avleiðsluleið | noun, lowercase · lit. "derivation path" · shipped `wallets.details_derivation_path` + `import_derivation_title`. | +| Master fingerprint | høvuðseyðkenni | noun, lowercase · lit. "master identifier" · shipped `wallets.details_master_fingerprint`. | +| BIP38 | BIP38 | acronym · gloss: loyniorðsvarður privatur lykil · shipped `wallets.looks_like_bip38`. | +| **_On-chain transactions_** | | | +| Transaction | flyting | noun, lowercase · lit. "transfer" · shipped `transactions.transaction` + Bitcoin Core fo. | +| Address | adressa | noun, lowercase · shipped `send.details_address` + Bitcoin Core fo. | +| Input | inntøk | noun · lit. "income/intake" · shipped `transactions.details_inputs` + `details_from`. | +| Output | úttøk | noun · lit. "outgo/outlay" · shipped `transactions.details_outputs` + `details_to`. ⚠️ NOT the UI "Til" recipient label. | +| UTXO | UTXO | acronym · gloss: ónýttur flytingar-úttak (Bitcoin Core fo: `ónýtt` = unspent). | +| Change | vekslipeningur | noun, lowercase · lit. "change money" · ⚠️ NOT verb "broyta" · shipped `cc.change` + Bitcoin Core fo (`Change:` → `Vekslipeningur:`). | +| Hex | sekstandatøl / sekstandatalsskipan | noun · short / explanatory · lit. "base-sixteen number" · shipped `send.broadcastNone` + `create_this_is_hex`. ⚠️ NOT "hash". | +| Pending | ávegis / óváttað | adj · lit. "on the way" / "unconfirmed" · shipped `send.broadcastPending` = `Ávegis`; `transactions.pending` = `Óváttað`. | +| Unconfirmed | óváttað | adj · shipped `transactions.pending` + Bitcoin Core fo. | +| Confirmed | váttað | adj · shipped Bitcoin Core fo (`Confirmed` → `Váttað`). | +| Mempool | minnispulja | noun, lowercase · lit. "memory pool" · Bitcoin Core fo (`Memory Pool` → `Minnispulja`; `in memory pool` → `í minnispulju`). | +| Broadcast | útvarpa / útvarping | verb / noun · shipped `send.broadcastButton` (verb) + `errors.broadcast` = `Útvarping` (noun). | +| Block explorer | blokksjóneyka | noun, lowercase · lit. "block viewer" · shipped `settings.block_explorer`. | +| Onchain | áketu | adj · shipped `transactions.onchain` · coined form; no established fo equivalent. | +| Offchain | avketu | adj · shipped `transactions.offchain` · coined form; pairs with `áketu`. | +| **_Fees & fee bumping_** | | | +| Fee | avgjald | noun, lowercase · shipped `send.create_fee` + Bitcoin Core fo (`Fee:` → `Avgjald:`). | +| Fee Bump | avgjaldshækkan | noun, lowercase · shipped `send.details_adv_fee_bump` = `Loyv avgjaldshækkan`. | +| RBF | RBF | acronym · gloss: Replace-By-Fee · shipped `transactions.cancel_explain`, `rbf_explain` keep `RBF—Replace by Fee` · Bitcoin Core fo. | +| CPFP | CPFP | acronym · gloss: Child Pays for Parent · shipped `transactions.cpfp_exp` keeps `CPFP—Child Pays for Parent`. ⚠️ NOT a verb. | +| Speed Up | hækka avgjald | verb · lit. "raise fee" · shipped `transactions.cpfp_title`, `rbf_title`, `status_bump`. | +| **_Lightning_** | | | +| Invoice | gjaldsumbøn | noun, lowercase · lit. "payment request" · shipped `lnd.placeholder`, `errorInvoiceExpired`. | +| Lightning Invoice | Lightning gjaldsumbøn | noun · shipped `lndViewInvoice.lightning_invoice`. | +| Preimage | frumvirði | noun, lowercase · lit. "primary value" · shipped `lndViewInvoice.preimage` · fo.wikipedia.org/wiki/Bitcoin is a stub with no crypto-math term; shipped form is the canonical reference. | +| Payment | gjald | noun, lowercase · shipped `lnd.payment`. ⚠️ NOT verb "rinda". | +| Expired | fyrnað | adj · shipped `lnd.expired` + `lndViewInvoice.wasnt_paid_and_expired`. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | samundirritari | noun, lowercase · lit. "co-signer" · shipped `multisig.shared_key_detected`, `co_sign_transaction`. ⚠️ NOT "samogn" (co-owner). | +| Quorum | undirritanartreyt | noun, lowercase · lit. "signing requirement" · shipped `multisig.quorum_header`. | +| PSBT | PSBT | acronym · gloss: Partvís Undirritað Bitcoin Flyting · shipped `send.psbt_this_is_psbt` + Bitcoin Core fo. | +| Provide signature | útvega undirskrift | verb · shipped `multisig.provide_signature`. | +| BIP47 / Payment Code | BIP47 / gjaldkota | acronym + noun · `gjaldkota` lit. "payment code" · shipped `bip47.payment_code` + `bip47.contacts`. | +| Notification transaction | fráboðanarflyting | noun, lowercase · shipped `bip47.notif_tx` + `notification_tx_unconfirmed`. | +| SilentPayment | SilentPayment | brand kept · shipped `send.cant_send_to_silentpayment_adress` · no Faroese gloss in app. | +| **_Coin control_** | | | +| Coin Control | mynt-val | noun, lowercase · lit. "coin selection" · shipped `cc.header`. ⚠️ NOT Title Case. | +| Frozen | læs / frystur | adj · `læs` = "locked" (shipped `cc.freezeLabel`, `send.details_frozen`); `frystur` = "frozen". ⚠️ NOT verb "læsa/frysta". | +| **_Security & storage_** | | | +| Encrypted storage | bronglað goymsla | noun, lowercase · lit. "encrypted storage" · shipped `_.storage_is_encrypted` and `settings.encrypt_storage_explanation_*` use `goymslubronglan` (the encryption action) and `bronglað goymsla` (the encrypted store). ⚠️ NOT Title Case. | +| Plausible Deniability | haldgóð avsannan | noun, lowercase · lit. "credible denial" · shipped `plausibledeniability.title` + `settings.plausible_deniability`. | +| Biometrics | lívmátilig atgongd | noun, lowercase · lit. "biometric access" · shipped `settings.biometrics`. | +| Passcode | atgonguloynital | noun, lowercase · lit. "access secret-number" · shipped `settings.biom_no_passcode`. ⚠️ NOT `loyniorð` (password). | +| **_Backup, import & UX_** | | | +| Backup | trygdaravrit / trygdaravrita | noun / verb · lit. "security copy" · shipped `pleasebackup.text` (noun) + `wallets.details_export_backup` `Trygdaravrita` (verb) + Bitcoin Core fo. | +| Restore | endurinnles / endurinnlesa | verb / noun · lit. "re-load" · shipped `pleasebackup.text` + Bitcoin Core fo (`Restore Wallet…` → `Endurinnles mappu…`). | +| Import | innles / innlesa | verb / noun · lit. "read in" · shipped `wallets.add_import_wallet`, `import_title`, `import_do_import`. | +| Voucher | virðiskota | noun, lowercase · lit. "value code" · shipped `azteco.codeIs`, `successMessage`. | +| Redeem | útloysa | verb · lit. "release/redeem" · shipped `azteco.redeemButton` = `Útloys`. ⚠️ NOT "keypa" (buy). | +| Send | senda / útgjald | verb / noun · `senda` (verb, Bitcoin Core fo) / `útgjald` (noun, shipped `send.header` + `transactions.outgoing_transaction`). | +| Receive | móttaka / inngjald | verb / noun · `móttaka` (verb) / `inngjald` (noun, shipped `receive.header` + `transactions.incoming_transaction`). | +| Settings | stillingar | noun, lowercase · shipped `settings.header`. | +| Confirm | vátta / váttan | verb / noun · shipped `send.confirm_header` (verb) + `transactions.list_conf` `Váttanir` (plural noun = confirmations). | +| QR Code | QR kota | noun · lit. "QR code" · shipped `receive.qrcode_for_the_address`, `send.qr_error_no_qrcode` + Bitcoin Core fo. | +| Clipboard | setiborð | noun, lowercase · lit. "set-board" · shipped `_.clipboard` + Bitcoin Core fo. | +| Memo | viðmerking | noun, lowercase · lit. "annotation/comment" · shipped `send.create_memo`, `details_note_placeholder`. | +| Description | tekstboð / frágreiðing | noun, lowercase · `tekstboð` lit. "text message" (shipped `receive.details_label`); `frágreiðing` lit. "description" (shipped `lndViewInvoice.for`). | +| Label | spjaldur | noun, lowercase · lit. "card/tag" · shipped `cc.sort_label` + Bitcoin Core fo (`Label` → `Spjaldur`). | diff --git a/loc/vocabulary/fr_fr.md b/loc/vocabulary/fr_fr.md new file mode 100644 index 00000000000..b8f70f2bb21 --- /dev/null +++ b/loc/vocabulary/fr_fr.md @@ -0,0 +1,96 @@ +# French translation vocabulary (`fr_fr.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · fr.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand · fr.wikipedia.org/wiki/Lightning_Network | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker, lowercase. | +| sats | sats | noun, lowercase; kept as English unit. | +| sat/vByte | sat/vByte | technical fee-rate unit; UI keeps Latin (casing matters). | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | portefeuille | noun, lowercase. | +| Vault | coffre-fort | noun · ⚠️ NOT Latin "Vault"; `coffre-fort` = safe/strongbox. | +| Watch-only | lecture seule / portefeuille en lecture seule | adj · compact / explanatory · Zeus fr + Electrum fr (`spectateur` alt) | +| Hardware wallet | portefeuille matériel | noun, lowercase · Cake fr + Phoenix-ecosystem usage | +| Seed | graine / phrase de récupération | noun · literal / mainstream. | +| Mnemonic | phrase mnémonique / phrase de récupération | noun · technical / mainstream · Electrum fr | +| Passphrase | phrase secrète | noun · ⚠️ NOT "mot de passe" (= password) · Electrum fr + Bitcoin Core fr (`phrase de passe`) | +| Public key | clé publique | noun, lowercase. | +| Private key | clé privée | noun, lowercase. | +| WIF | WIF | acronym · gloss: format d'importation de portefeuille. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | descripteur / descripteur de sortie | noun, lowercase · canonical / full · Phoenix fr | +| Derivation path | chemin de dérivation | noun, lowercase · Electrum fr + Phoenix fr + Cake fr | +| Master fingerprint | empreinte maîtresse / empreinte digitale maîtresse | noun, lowercase · compact / full · Electrum fr · ⚠️ FIX shipped `Empreinte maitresse` (missing circumflex). | +| BIP38 | BIP38 | acronym kept · gloss: clé privée chiffrée par mot de passe. | +| **_On-chain transactions_** | | | +| Transaction | transaction | noun, lowercase. | +| Address | adresse | noun, lowercase. | +| Input | entrée / entrée de transaction | noun · short / full · Electrum fr (`Entrées`) + fr.wikipedia.org/wiki/Bitcoin (`entrées (inputs)`) · ⚠️ NOT "saisie / connexion". | +| Output | sortie / sortie de transaction | noun · short / full · Electrum fr (`Sorties`) + fr.wikipedia.org/wiki/Bitcoin (`sorties (outputs)`) · ⚠️ NOT the UI recipient label "À :". | +| UTXO | UTXO | acronym · gloss: sortie de transaction non dépensée. | +| Change | monnaie / monnaie rendue | noun · short / explicit · ⚠️ NOT the verb "changer / modifier"; `monnaie` = leftover coin · ⚠️ FIX shipped `monnaie rendu` → `monnaie rendue` (feminine agreement) · Electrum fr | +| Hex | hex / hexadécimal | noun · compact / explanatory · ⚠️ NOT "hash" / NOT "données de transaction". | +| Pending | en cours / en attente | adj/state · UI button / body · Electrum fr + Phoenix fr | +| Unconfirmed | non confirmé / non confirmée | adj · masc / fem-agreement · Electrum fr + Bitcoin Core fr | +| Confirmed | confirmé / confirmée | adj · masc / fem-agreement · Bitcoin Core fr | +| Mempool | mempool | noun, lowercase loanword · Electrum fr + Phoenix fr | +| Broadcast | diffuser / diffusion | verb / noun · Electrum fr + Bitcoin Core fr | +| Block explorer | explorateur de blocs | noun, lowercase. | +| Onchain | onchain / en chaîne | adj · compact (chip) / explanatory (body). | +| Offchain | offchain / hors chaîne | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | frais | noun, masculine plural form used. | +| Fee Bump | augmentation des frais | noun · Electrum fr (`Augmenter les frais`) · ⚠️ FIX shipped `Autoriser le Fee Bum` → `Autoriser le Fee Bump` (missing "p"). | +| RBF | RBF | acronym · gloss: remplacement par frais (Replace-By-Fee). | +| CPFP | CPFP | acronym · gloss: l'enfant paie pour le parent · Electrum fr · ⚠️ NOT a verb. | +| Speed Up | accélérer | verb · button label for RBF · Phoenix fr (`Accélération de transactions`) | +| **_Lightning_** | | | +| Invoice | facture / requête de paiement | noun · technical / mainstream · Electrum fr + Phoenix fr | +| Lightning Invoice | facture Lightning / requête de paiement Lightning | noun · technical / mainstream · Electrum fr + Phoenix fr · ⚠️ FIX shipped `Facture Ligthning` (typo) → `Facture Lightning`. | +| Preimage | préimage | noun, lowercase · per Phoenix fr (`paymentdetails_preimage_label` = `Préimage`) + fr.wikipedia.org/wiki/Image_réciproque (lists `préimage` alongside `image réciproque`); hyphenated `pré-image` is not the established form. | +| Payment | paiement | noun · ⚠️ NOT verb "payer" · Electrum fr + Phoenix fr | +| Expired | expiré / expirée | adj · masc / fem-agreement · Electrum fr + Phoenix fr | +| **_Multisig & advanced addressing_** | | | +| Co-signer | cosignataire | noun · ⚠️ NOT "copropriétaire" (co-owner) · Electrum fr | +| Quorum | quorum / seuil de signatures | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym · gloss: transaction Bitcoin partiellement signée. | +| Provide signature | fournir la signature / signer la transaction | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / code de paiement | acronym kept; "Payment Code" → "code de paiement". | +| Notification transaction | transaction de notification | noun · BIP47-specific. | +| SilentPayment | Silent Payments / paiements silencieux | protocol name kept English (plural); explanatory `paiements silencieux` if needed. | +| **_Coin control_** | | | +| Coin Control | contrôle des UTXO / contrôle des pièces | noun, lowercase · technical / mainstream · Zeus fr (`Contrôle des pièces`) + Cake fr · ⚠️ NOT Title Case · ⚠️ FIX shipped `Controle des UTXO` → `Contrôle des UTXO`. | +| Frozen | gelé / gelée | adj · masc / fem-agreement · Zeus fr · ⚠️ NOT verb "geler". | +| **_Security & storage_** | | | +| Encrypted storage | stockage chiffré / chiffrement du stockage | noun, lowercase · ⚠️ NOT Title Case. | +| Plausible Deniability | déni plausible | noun, lowercase · ⚠️ NOT Title Case. | +| Biometrics | biométrie / authentification biométrique | noun, lowercase · compact / explanatory · Cake fr + Green fr | +| Passcode | code d'accès | noun · ⚠️ NOT "mot de passe" (= app password) · matches shipped `biom_no_passcode` ("code d'accès"). | +| **_Backup, import & UX_** | | | +| Backup | sauvegarde / sauvegarder | noun / verb. | +| Restore | restaurer / restauration | verb / noun. | +| Import | importer / importation | verb / noun. | +| Voucher | bon | noun, lowercase · Azte.co prepaid voucher · ⚠️ FIX shipped `azteco/title` `Azte.com` → `Azte.co`. | +| Redeem | utiliser / activer | verb · ⚠️ NOT "collecter" alone (current shipped `Collecter` is too narrow); Cake fr uses `échanger / remboursement`. For vouchers prefer `utiliser` / `activer`. | +| Send | envoyer | verb. | +| Receive | recevoir | verb. | +| Settings | réglages / paramètres | noun, lowercase · app-style / general · app uses "Réglages" (Apple-style). | +| Confirm | confirmer / confirmation | verb / noun. | +| QR Code | code QR | noun, lowercase · Zeus fr + Phoenix fr · word order inverted. | +| Clipboard | presse-papier / presse-papiers | noun, lowercase · Zeus fr (`Presse-papiers`). | +| Memo | mémo / note | noun, lowercase · loanword / native · Zeus fr (`Note`). | +| Description | description | noun, lowercase. | +| Label | étiquette | noun, lowercase · Zeus fr. | diff --git a/loc/vocabulary/he.md b/loc/vocabulary/he.md new file mode 100644 index 00000000000..41223339e35 --- /dev/null +++ b/loc/vocabulary/he.md @@ -0,0 +1,98 @@ +# Hebrew translation vocabulary (`he.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +> RTL locale. Hebrew has no letter case; "lowercase" rules don't apply. Brand rows stay in Latin script. Acronyms keep English letters. Hebrew is gendered — noun forms here default to the form used in the shipped `loc/he.json`. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / ביטקוין | brand kept Latin; ביטקוין in explanatory text · he.wikipedia.org/wiki/ביטקוין | +| Lightning | Lightning / ברק | brand · shipped UI uses native ברק ("lightning bolt"); Latin Lightning acceptable · he.wikipedia.org/wiki/רשת_הברק | +| Electrum | Electrum | brand · keep Latin (shipped אלקטרום is a transliteration). | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | ביטקוין / BTC | noun unit + ticker. | +| sats | סאטושי / sats | noun · transliteration / Latin · plural also סאטושיים. | +| sat/vByte | sat/vByte | technical unit; keep Latin · ⚠️ shipped `סאטושי עבור vByte` drops the `v` semantics — prefer Latin token. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | ארנק | noun · singular form; shipped header uses plural ארנקים. | +| Vault | כספת / כספת רבת-חתימות | noun · ⚠️ NOT a brand; כספת = "safe/strongbox". Long form clarifies multisig context. | +| Watch-only | לצפייה בלבד / ארנק לצפייה בלבד | adj · short / full · Bitcoin Core he + Electrum he | +| Hardware wallet | ארנק חומרה | noun · Electrum he | +| Seed | גרעין / מילות שחזור | noun · technical (lit. "kernel", shipped) / mainstream "recovery words". Avoid זרע (Electrum he) — sexual connotation in modern Hebrew UI. | +| Mnemonic | פסוקית מנמונית / מילות שחזור | noun · technical / mainstream. | +| Passphrase | מילת צופן / ביטוי סיסמה | noun · ⚠️ NOT סיסמה (= password) · Bitcoin Core he uses "החלפת מילת צופן" for Change Passphrase. Shipped `סיסמה` collides with password — TODO in he.json. | +| Public key | מפתח ציבורי | noun · Electrum he + Bitcoin Core he | +| Private key | מפתח פרטי | noun · Electrum he + Bitcoin Core he | +| WIF | WIF | acronym · gloss: פורמט ייבוא ארנק. | +| xpub | xpub | acronym, prefer lowercase · ⚠️ shipped `מפתח צפייה` ("view key") conflates with watch-only — keep `xpub` as the term. | +| Descriptor | דסקריפטור / מתאר פלט | noun · transliteration / explanatory. | +| Derivation path | נתיב גזירה | noun · Electrum he + Bitcoin Core he (shipped). | +| Master fingerprint | טביעת אצבע ראשית | noun · Electrum he (shipped). | +| BIP38 | BIP38 | acronym · gloss: מפתח פרטי מוצפן בסיסמה. | +| **_On-chain transactions_** | | | +| Transaction | עסקה | noun · ⚠️ FIX: shipped `פעולה` (= "action") is non-standard. Bitcoin Core he + Electrum he + he.wikipedia.org/wiki/ביטקוין all use עסקה. | +| Address | כתובת | noun · Bitcoin Core he + Electrum he | +| Input | קלט / קלט עסקה | noun · short / full · Electrum he | +| Output | פלט / פלט עסקה | noun · short / full · ⚠️ NOT the UI "To:" label · Electrum he | +| UTXO | UTXO | acronym · gloss: פלט עסקה לא מנוצל. | +| Change | עודף / כתובת עודף | noun · ⚠️ NOT verb "לשנות"; עודף = leftover. `כתובת עודף` for change-address · Electrum he (shipped). | +| Hex | הקסדצימלי / hex | noun · ⚠️ FIX: shipped `קלט גיבוב פעולה` means "tx hash input" — wrong. Use Latin `hex` or transliteration. ⚠️ NOT "hash" / NOT "נתוני העסקה" (= "transaction data"). | +| Pending | ממתין / ממתינה | adj · masc / fem · shipped. | +| Unconfirmed | לא מאושרת / לא אושרה | adj · adj-form / state · Electrum he `לא מאושר` | +| Confirmed | מאושרת / אושרה | adj · adj-form / state · shipped uses אושר/לא אושרה inconsistently. | +| Mempool | ממפול / מאגר עסקאות לא מאושרות | noun · transliteration / explanatory · Electrum he uses long form. | +| Broadcast | שידור / לשדר | noun / verb · shipped uses שידור (noun) for button label. | +| Block explorer | סייר בלוקים | noun · shipped. | +| Onchain | בשרשרת / על השרשרת | adj · compact (chip) / explanatory · Electrum he `בשרשרת` / `שרשרתית`. | +| Offchain | מחוץ לשרשרת | adj · explanatory · Lightning (L2) filter. | +| **_Fees & fee bumping_** | | | +| Fee | עמלה | noun · Bitcoin Core he + Electrum he. | +| Fee Bump | הקפצת עמלה / העלאת עמלה | noun · shipped / alt · Bitcoin Core he uses "החלפה על ידי עמלה" for RBF. | +| RBF | RBF | acronym · gloss: החלפה על ידי עמלה / Replace-By-Fee · Bitcoin Core he. | +| CPFP | CPFP | acronym · gloss: עסקת ילד משלמת על ההורה · ⚠️ NOT a verb. | +| Speed Up | האצה / להאיץ | noun / verb · button label. Shipped `העלאת עמלה` = "raise fee" — semantically OK but verbose. | +| **_Lightning_** | | | +| Invoice | חשבונית | noun · Electrum he + Bitcoin Core he. | +| Lightning Invoice | חשבונית Lightning / חשבונית ברק | noun · Latin brand / native shipped form. | +| Preimage | תמונה מקורית / preimage | noun · math term (lit. "pre-image") / Latin fallback acceptable in technical UI · he.wikipedia hash-function article frames it as `קושי בהיפוך` (preimage resistance); Latin form preferred when terse. | +| Payment | תשלום | noun · ⚠️ NOT verb "לשלם" · shipped. | +| Expired | פג / פג תוקף | adj · short (shipped) / explanatory · Electrum he `פג התוקף`. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | חותם משותף / חותם-שותף | noun · ⚠️ NOT "שותף" (= partner) alone · Electrum he `חותֵם-במשותף`. Shipped חותם-שותף acceptable. | +| Quorum | קוורום / סף חתימות | noun · transliteration / UI-clear. | +| PSBT | PSBT | acronym · gloss: עסקת ביטקוין חתומה חלקית · Bitcoin Core he. | +| Provide signature | לספק חתימה / חתימה | verb / noun · shipped imperative `ספקו חתימה`. | +| BIP47 / Payment Code | BIP47 / קוד תשלום | acronym kept; "Payment Code" → "קוד תשלום". | +| Notification transaction | עסקת התראה | noun · BIP47-specific (was `פעולת התראה`). | +| SilentPayment | Silent Payments / תשלום שקט | brand kept English (plural); explanatory shipped `תשלום שקט`. | +| **_Coin control_** | | | +| Coin Control | ניהול UTXO / שליטת מטבעות | noun · technical / mainstream (shipped). | +| Frozen | מוקפא / מוקפאת | adj · masc / fem · ⚠️ NOT verb "להקפיא". Shipped also uses noun הקפאה for the action. | +| **_Security & storage_** | | | +| Encrypted storage | אחסון מוצפן / הצפנת אחסון | noun · shipped uses both forms. | +| Plausible Deniability | יכולת הכחשה סבירה | noun · shipped · en.wikipedia.org/wiki/Plausible_deniability. | +| Biometrics | זיהוי ביומטרי / ביומטריה | noun · shipped (long form). | +| Passcode | קוד גישה / קוד מכשיר | noun · ⚠️ NOT סיסמה (= password); shipped `סיסמה` collides — TODO in he.json. | +| **_Backup, import & UX_** | | | +| Backup | גיבוי / לגבות | noun / verb · Bitcoin Core he + Electrum he. | +| Restore | שחזור / לשחזר | noun / verb · Bitcoin Core he (`שחזור ארנק`). | +| Import | ייבוא / לייבא | noun / verb · Electrum he `ייבא`; shipped uses יבוא (defective spelling) — prefer ייבוא. | +| Voucher | שובר | noun · shipped. | +| Redeem | לממש / מימוש | verb / noun · ⚠️ NOT "לקנות" (buy); shipped `מימוש`. | +| Send | שליחה / לשלוח | noun / verb · shipped noun for nav label; Electrum he `שלח`. | +| Receive | קבלה / לקבל | noun / verb · shipped noun for nav label; Electrum he `קבל`. | +| Settings | הגדרות | noun · Electrum he + shipped. | +| Confirm | אישור / לאשר | noun / verb · shipped. | +| QR Code | קוד QR | noun · Electrum he + Bitcoin Core he. | +| Clipboard | לוח / לוח גזירים | noun · short (Electrum he) / explanatory (Bitcoin Core he, shipped). Avoid Latin "clipboard" transliteration `קליפבורד`. | +| Memo | תזכיר / הערה | noun · shipped / alt. | +| Description | תיאור | noun · Electrum he + shipped. | +| Label | תווית | noun · Electrum he + shipped. | diff --git a/loc/vocabulary/hr_hr.md b/loc/vocabulary/hr_hr.md new file mode 100644 index 00000000000..559d0a272d1 --- /dev/null +++ b/loc/vocabulary/hr_hr.md @@ -0,0 +1,96 @@ +# Croatian translation vocabulary (`hr_hr.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · hr.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker; lowercase unit. | +| sats | sats / satoshiji | noun, lowercase; `sats` widely kept in UI. | +| sat/vByte | sat/vByte | technical unit; UI keeps Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | novčanik | noun, lowercase · Bitcoin Core hr + Cake hr + hr.wikipedia.org/wiki/Bitcoin. ⚠️ Shipped `Volet` is colloquial — replace. | +| Vault | trezor / sef | noun · safe/strongbox sense; avoid Latin "Vault". `trezor` is preferred for vault-as-strongbox sense — note collision with Trezor hardware-wallet brand. | +| Watch-only | samo gledanje / promatrački novčanik | adj · Bitcoin Core hr ("adrese koje su isključivo za promatranje"). ⚠️ NOT view mode / read mode. | +| Hardware wallet | hardverski novčanik | noun, lowercase · Bitcoin Core hr + Cake hr. | +| Seed | sjeme / sigurnosna fraza | noun · Bitcoin Core hr (`Sjeme`) + Cake hr. ⚠️ Shipped `Izvor` (= source) is wrong — replace. | +| Mnemonic | mnemonička fraza / sigurnosna fraza | noun · technical / mainstream · Cake hr. | +| Passphrase | kodna fraza | noun · Bitcoin Core hr. ⚠️ NOT `lozinka` (= password); Bitcoin Core hr uses `Lozinka` for passphrase but collides with password in BlueWallet UI. | +| Public key | javni ključ | noun, lowercase · Cake hr. | +| Private key | privatni ključ | noun, lowercase · Bitcoin Core hr + Cake hr. | +| WIF | WIF | acronym · gloss: format za uvoz novčanika. | +| xpub | xpub | acronym, lowercase preferred. Shipped `XPUB` may stay uppercase per convention. | +| Descriptor | deskriptor | noun, lowercase · standard hr transliteration; hr.wikipedia "deskriptor". | +| Derivation path | put derivacije | noun · Cake hr. | +| Master fingerprint | otisak glavnog ključa | noun, lowercase · constructed from `otisak` (fingerprint) + `glavni ključ` (master key) per hr cryptography conventions. | +| BIP38 | BIP38 | acronym kept · gloss: BIP38 lozinka za dešifriranje. | +| **_On-chain transactions_** | | | +| Transaction | transakcija | noun, lowercase · Bitcoin Core hr + Cake hr + hr.wikipedia.org/wiki/Bitcoin. | +| Address | adresa | noun, lowercase · Bitcoin Core hr + Cake hr. | +| Input | ulaz / ulaz transakcije | noun · short / full · Bitcoin Core hr (`ulaz`). | +| Output | izlaz / izlaz transakcije | noun · short / full · ⚠️ NOT UI recipient label "Za:". | +| UTXO | UTXO | acronym · gloss: nepotrošeni izlaz transakcije. | +| Change | ostatak / vraćeno | noun · Bitcoin Core hr (`Vraćeno/ostatak`). ⚠️ NOT verb `promijeniti` and NOT `promjena` (= "alteration"). | +| Hex | hex / heksadekadski zapis | noun · short / explanatory · ⚠️ NOT "hash". | +| Pending | u tijeku / na čekanju | adj/state. Avoid noun `čekanje`. | +| Unconfirmed | nepotvrđeno | adj/state · Bitcoin Core hr. | +| Confirmed | potvrđeno | adj/state · Bitcoin Core hr. | +| Mempool | mempool / memorijski bazen | noun · Latin kept (mainstream) / Bitcoin Core hr gloss. | +| Broadcast | objavi / emitiraj | verb · UI / technical · Bitcoin Core hr (`Objavi`). Noun: objava. | +| Block explorer | preglednik blokova | noun, lowercase · constructed; standard hr software term `preglednik` (browser/explorer). | +| Onchain | on-chain / na lancu | adj · compact (chip) / explanatory (body) · loanword + hr.wikipedia "lanac blokova". | +| Offchain | off-chain / izvan lanca | adj · compact (chip) / explanatory (body) · loanword + hr.wikipedia "lanac blokova". | +| **_Fees & fee bumping_** | | | +| Fee | naknada | noun, lowercase · Bitcoin Core hr + Cake hr + Zeus hr. | +| Fee Bump | povećanje naknade | noun · constructed from `naknada` (fee) + `povećanje` (increase). | +| RBF | RBF | acronym · gloss: zamjena naknadom / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: dijete plaća za roditelja. ⚠️ NOT verb `Stvori`. | +| Speed Up | ubrzaj | verb. | +| **_Lightning_** | | | +| Invoice | faktura / račun | noun · technical / mainstream · Zeus hr (`Faktura`) / shipped `račun`. | +| Lightning Invoice | Lightning faktura / Lightning račun | noun · brand + localised noun. | +| Preimage | preimage / prethodna slika | noun · Latin kept (Zeus hr) / explanatory math gloss. | +| Payment | plaćanje | noun · Zeus hr · ⚠️ NOT verb `platiti`. | +| Expired | isteklo | adj/state · shipped `Isteklo` already correct (lowercase in body). | +| **_Multisig & advanced addressing_** | | | +| Co-signer | supotpisnik | noun · ⚠️ NOT `suvlasnik` (= co-owner) · standard hr legal/notary term. | +| Quorum | kvorum / prag potpisa | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym. | +| Provide signature | priloži potpis / potpiši transakciju | verb · generic / specific · Bitcoin Core hr (`Potpis` / `Potpiši`). | +| BIP47 / Payment Code | BIP47 / kod plaćanja | acronym kept; "Payment Code" → `kod plaćanja`. | +| Notification transaction | obavijesna transakcija | noun · BIP47-specific; constructed from `obavijest` (notification). | +| SilentPayment | Silent Payments / tiha plaćanja | protocol name kept English (plural); explanatory `tiha plaćanja` if needed. | +| **_Coin control_** | | | +| Coin Control | upravljanje UTXO-ima / upravljanje kovanicama | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case. | +| Frozen | zamrznuto | adj · state form · ⚠️ NOT verb `zamrznuti`. | +| **_Security & storage_** | | | +| Encrypted storage | šifrirani spremnik | noun, lowercase · Bitcoin Core hr (`Šifrirani novčanik`). Shipped `spremnik je kriptiran` is a full sentence — prefer noun form. | +| Plausible Deniability | uvjerljivo poricanje | noun, lowercase. ⚠️ Shipped `Fejk volet` is slang ("fake wallet") — replace. | +| Biometrics | biometrija | noun, lowercase. | +| Passcode | kod za otključavanje / PIN | noun · ⚠️ NOT `lozinka` (= password). Shipped uses `Lozinka` from `settings/password` — collides; recommend distinct word. | +| **_Backup, import & UX_** | | | +| Backup | sigurnosna kopija / izraditi sigurnosnu kopiju | noun / verb · Bitcoin Core hr + Cake hr. Shipped `Izvoz / bekap` is colloquial. | +| Restore | obnovi / obnova | verb / noun · Cake hr (`Vratiti` / `Oporavi`). | +| Import | uvezi / uvoz | verb / noun · Bitcoin Core hr (`Uvoziti`) + Cake hr (`Uvoz`). Shipped `Unesi` ("enter") is weaker. | +| Voucher | bon / vaučer | noun, lowercase · `bon` standard hr commercial term / `vaučer` loanword; Cake hr uses `poklon kartica` (gift card) but Azteco context differs. | +| Redeem | iskoristi / aktiviraj | verb · ⚠️ NOT "kupiti" / NOT "prebaciti" · standard hr commercial term for redeeming vouchers. | +| Send | pošalji / šalji | verb · Bitcoin Core hr (`Pošalji`). Shipped `Šalji` is imperative-imperfective. | +| Receive | primi | verb · Bitcoin Core hr + Cake hr. | +| Settings | postavke | noun, lowercase · Bitcoin Core hr. | +| Confirm | potvrdi / potvrda | verb / noun · Bitcoin Core hr. | +| QR Code | QR kod | noun · Bitcoin Core hr + Cake hr. | +| Clipboard | međuspremnik | noun, lowercase · Bitcoin Core hr + Cake hr. | +| Memo | bilješka | noun, lowercase. | +| Description | opis | noun, lowercase · Cake hr. | +| Label | oznaka | noun, lowercase · Bitcoin Core hr + Cake hr. | diff --git a/loc/vocabulary/hu_hu.md b/loc/vocabulary/hu_hu.md new file mode 100644 index 00000000000..cbf49ea09ea --- /dev/null +++ b/loc/vocabulary/hu_hu.md @@ -0,0 +1,96 @@ +# Hungarian translation vocabulary (`hu_hu.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · hu.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning / Villám | brand kept Latin; `Villám` ("lightning") appears in shipped invoice strings — keep both. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand · normalise casing (was `LNDHub`). | +| LND | LND | brand. | +| LNURL | LNURL | brand · Electrum hu_HU keeps as-is. | +| Tor | Tor | brand · Electrum hu_HU keeps as-is. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit (lowercase) + ticker. | +| sats | satoshi / sats | noun, lowercase; shipped strings use both. | +| sat/vByte | sat/vByte / satoshi vbájtonként | technical unit (chip) / explanatory (body). | +| vByte | vByte / vbájt | technical / mainstream. | +| **_Wallet, keys & seeds_** | | | +| Wallet | tárca / pénztárca | noun, lowercase · Bitcoin Core hu uses `Tárca`; longer form `pénztárca` also shipped. | +| Vault | széf / multisig tárca | noun, lowercase · ⚠️ NOT brand "Trezor" — shipped strings mix `Trezor` / `Vault` / `Széf`; canonicalise on `széf` (safe/strongbox) per vocabulary.md rule. | +| Watch-only | csak megtekintésre / figyelő-tárca | adj · UI / Electrum hu_HU + Bitcoin Core hu (`figyelő`). | +| Hardware wallet | hardveres tárca | noun, lowercase · drop space (was `hardver tárca`). | +| Seed | mag-kifejezés / helyreállítási kifejezés | noun · technical / mainstream · ⚠️ NOT `jelszó sorozat` ("password sequence"); reuses `kifejezés` like Trezor hu. | +| Mnemonic | mnemonikus kifejezés / helyreállítási kifejezés | noun · technical / mainstream · ⚠️ NOT `jelszó sorozat`. | +| Passphrase | jelmondat | noun · ⚠️ distinct from `jelszó` (password) · Bitcoin Core hu (`jelmondat`). | +| Public key | nyilvános kulcs | noun, lowercase · Bitcoin Core hu + hu.wikipedia.org/wiki/Bitcoin | +| Private key | privát kulcs | noun, lowercase · Bitcoin Core hu + hu.wikipedia.org/wiki/Bitcoin | +| WIF | WIF | acronym kept · gloss: tárcaimportáló formátum. | +| xpub | xpub | acronym, lowercase preferred (was `XPUB`). | +| Descriptor | leíró | noun, lowercase · Bitcoin Core hu (`leíró`). | +| Derivation path | származtatási útvonal / derivációs útvonal | noun · Electrum hu_HU (`Származtatási útvonal`) / technical alt. | +| Master fingerprint | mester ujjlenyomat | noun, lowercase. | +| BIP38 | BIP38 | acronym kept · gloss: BIP38 jelszó. | +| **_On-chain transactions_** | | | +| Transaction | tranzakció | noun, lowercase · Bitcoin Core hu + Electrum hu_HU. | +| Address | cím | noun, lowercase · Bitcoin Core hu. | +| Input | bemenet | noun · Bitcoin Core hu + Electrum hu_HU · ⚠️ replaces ambiguous `Bejövő utalás` ("incoming transfer"). | +| Output | kimenet | noun · Electrum hu_HU · ⚠️ replaces ambiguous `Kimenő utalás` ("outgoing transfer"). | +| UTXO | UTXO | acronym · gloss: elköltetlen tranzakciós kimenet. | +| Change | visszajáró / visszajáró cím | noun · Bitcoin Core hu (`Visszajáró`) + Electrum hu_HU · ⚠️ NOT verb `váltás` ("switching"). | +| Hex | hex / hexadecimális | noun · short / explanatory · ⚠️ NOT "hash". | +| Pending | függőben / függőben lévő | adj/state · short / adjective-agreement form · Bitcoin Core hu + Electrum hu_HU. | +| Unconfirmed | megerősítetlen / nem megerősített | adj · Electrum hu_HU (`Nem megerősített`). | +| Confirmed | megerősítve / megerősített | adj/state · ⚠️ NOT noun `megerősítés` ("confirmation"). | +| Mempool | mempool | noun, lowercase · Electrum hu_HU. | +| Broadcast | továbbítani / közzététel | verb / noun · UI / mainstream · Bitcoin Core hu (`közlése`). | +| Block explorer | blokkböngésző | noun, lowercase · Bitcoin Core hu (`blokkböngésző`). | +| Onchain | on-chain / blokkláncon | adj · compact (chip) / explanatory (body) · Electrum hu_HU (`Blokkláncon`). | +| Offchain | off-chain / blokkláncon kívül | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | díj / tranzakciós díj | noun, lowercase · Bitcoin Core hu + Electrum hu_HU. | +| Fee Bump | díj utólagos növelése | noun · Electrum hu_HU. | +| RBF | RBF | acronym · gloss: díj utólagos növelése / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: díjfizetés másodlagos tranzakcióval (Electrum hu_HU) · ⚠️ NOT a verb. | +| Speed Up | gyorsítás | verb · UI button label · ⚠️ NOT `Kiváltási díj (RBF)` (that's the fee-bump noun). | +| **_Lightning_** | | | +| Invoice | számla / fizetési kérelem | noun · mainstream / technical · Electrum hu_HU (`fizetési kérelem`). | +| Lightning Invoice | Lightning számla / Lightning fizetési kérelem | noun · brand kept Latin + Hungarian noun · Electrum hu_HU. | +| Preimage | preimage | noun · Electrum hu_HU keeps English passthrough. | +| Payment | fizetés / kifizetés | noun · ⚠️ NOT verb `fizetni`. | +| Expired | lejárt | adj/state · lowercase. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | társaláíró | noun · Electrum hu_HU (`Társaláíró`) · ⚠️ NOT "co-owner". | +| Quorum | kvórum / aláírási küszöb | noun, lowercase · canonical / UI-clear. | +| PSBT | PSBT | acronym · gloss: részlegesen aláírt Bitcoin tranzakció. | +| Provide signature | aláírás megadása / tranzakció aláírása | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / fizetési kód | acronym kept; "Payment Code" → `fizetési kód`. | +| Notification transaction | értesítési tranzakció | noun · BIP47-specific. | +| SilentPayment | Silent Payments / csendes fizetések | protocol name kept English (plural); explanatory `csendes fizetések` if needed. | +| **_Coin control_** | | | +| Coin Control | érmekezelés / UTXO-kezelés | noun, lowercase · Electrum hu_HU (`Érmekezelés`) · ⚠️ NOT Title Case. | +| Frozen | fagyasztva / befagyasztott | adj · state / adjective-agreement · Electrum hu_HU · ⚠️ NOT verb `fagyasztani`. | +| **_Security & storage_** | | | +| Encrypted storage | titkosított tárhely / tárhely titkosítása | noun, lowercase · ⚠️ NOT Title Case. | +| Plausible Deniability | elfogadható tagadhatóság | noun, lowercase · ⚠️ NOT Title Case. | +| Biometrics | biometrikus azonosítás | noun, lowercase. | +| Passcode | feloldó kód | noun · ⚠️ distinct from `jelszó` (= password) and `jelmondat` (= passphrase). | +| **_Backup, import & UX_** | | | +| Backup | biztonsági mentés / biztonsági mentés készítése | noun / verb-phrase · Bitcoin Core hu + Electrum hu_HU. | +| Restore | visszaállítás / helyreállítás | noun-verb · Bitcoin Core hu. | +| Import | importálás / importálni | noun / verb · Bitcoin Core hu. | +| Voucher | kupon | noun, lowercase. | +| Redeem | beváltás / jóváírás | verb-noun · `beváltás` for activate/cash in; `jóváírás` shipped for Azte.co · ⚠️ NOT "buy to wallet". | +| Send | küldés | verb · Bitcoin Core hu. | +| Receive | fogadás | verb · Bitcoin Core hu. | +| Settings | beállítások | noun, lowercase · Bitcoin Core hu. | +| Confirm | megerősítés / megerősíteni | noun / verb · also "confirmations" = blokk-megerősítések. | +| QR Code | QR-kód | noun · Bitcoin Core hu + Electrum hu_HU · canonicalise on hyphen form. | +| Clipboard | vágólap | noun, lowercase · Bitcoin Core hu. | +| Memo | megjegyzés | noun, lowercase. | +| Description | leírás | noun, lowercase. | +| Label | címke | noun, lowercase · Bitcoin Core hu (`Címke`) · ⚠️ fix shipped typo `Cimke` → `címke`. | diff --git a/loc/vocabulary/id_id.md b/loc/vocabulary/id_id.md new file mode 100644 index 00000000000..f4d64f373d8 --- /dev/null +++ b/loc/vocabulary/id_id.md @@ -0,0 +1,96 @@ +# Indonesian translation vocabulary (`id_id.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · id.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker, lowercase unit per glossary. | +| sats | sats | noun, lowercase. | +| sat/vByte | sat/vByte | technical unit; keep Latin casing. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | dompet | noun, lowercase · Bitcoin Core id + id.wikipedia.org/wiki/Bitcoin | +| Vault | brankas | noun; safe/strongbox sense. Avoid Latin "Vault". | +| Watch-only | hanya-lihat / dompet hanya-lihat | adj · compact / explanatory · Electrum id (`hanya dapat dilihat`). ⚠️ NOT mode pembacaan. | +| Hardware wallet | dompet perangkat keras | noun, lowercase · Bitcoin Core id + Cake id | +| Seed | frasa pemulihan / seed | noun · mainstream / technical. ⚠️ shipped `Benih` (= plant seed) is misleading; prefer `frasa pemulihan`. | +| Mnemonic | frasa mnemonik / frasa pemulihan | noun · technical / mainstream. | +| Passphrase | frasa sandi | noun · ⚠️ distinct from `kata sandi` (password) and `kode sandi` (passcode). | +| Public key | kunci publik | noun, lowercase · Cake id + id.wikipedia.org/wiki/Bitcoin | +| Private key | kunci privat | noun, lowercase · Bitcoin Core id + Cake id | +| WIF | WIF | acronym · gloss: format impor dompet. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | deskriptor | noun, lowercase · Bitcoin Core id. | +| Derivation path | jalur derivasi | noun, lowercase. | +| Master fingerprint | sidik jari utama | noun, lowercase · Electrum id. | +| BIP38 | BIP38 | acronym kept; gloss: kunci privat terenkripsi BIP38. | +| **_On-chain transactions_** | | | +| Transaction | transaksi | noun, lowercase. | +| Address | alamat | noun, lowercase · id.wikipedia.org/wiki/Bitcoin | +| Input | masukan / input transaksi | noun · short / full · ⚠️ NOT "login". | +| Output | keluaran / keluaran transaksi | noun · short / full · Electrum id (`keluaran`). ⚠️ NOT the UI "Ke:" label. | +| UTXO | UTXO | acronym · gloss: keluaran transaksi yang belum dibelanjakan. | +| Change | kembalian / alamat kembalian | noun · ⚠️ NOT verb `ubah` (= to modify). `kembalian` = leftover; `alamat kembalian` for change-address · Bitcoin Core id (`Kembalian`) + id.wikipedia.org/wiki/Bitcoin (`uang kembalian`). | +| Hex | hex / data heksadesimal | noun · short / explanatory · ⚠️ NOT "hash" and NOT "data transaksi". | +| Pending | tertunda | adj/state · button vs body. Avoid noun form. | +| Unconfirmed | belum dikonfirmasi | adj · Bitcoin Core id + Electrum id + Cake id. | +| Confirmed | dikonfirmasi / terkonfirmasi | adj · ⚠️ shipped `konfirmasi` is the noun "confirmation"; use the adj/state form. · Bitcoin Core id + Cake id. | +| Mempool | mempool | noun, lowercase · Electrum id keeps Latin. | +| Broadcast | siarkan / penyiaran | verb / noun · UI-clear verb + noun form. | +| Block explorer | penjelajah blok | noun, lowercase · Zeus id. | +| Onchain | on-chain / di blockchain | adj · compact (chip) / explanatory (body). | +| Offchain | off-chain / di luar blockchain | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | biaya | noun, lowercase · ⚠️ NOT `Tarif` (= tariff/rate) · id.wikipedia.org/wiki/Bitcoin (`biaya transaksi`). | +| Fee Bump | kenaikan biaya / naikkan biaya | noun / verb · ⚠️ `Lonjakan Biaya` reads as "fee surge"; prefer `kenaikan biaya`. | +| RBF | RBF | acronym · gloss: ganti dengan biaya lebih tinggi (Replace-By-Fee). | +| CPFP | CPFP | acronym · gloss: anak membayar untuk induk. ⚠️ NOT a verb. | +| Speed Up | percepat | verb (button label). | +| **_Lightning_** | | | +| Invoice | faktur | noun, lowercase · Electrum id + Zeus id + Cake id. | +| Lightning Invoice | faktur Lightning | noun · ⚠️ shipped `Fraktur Lightning` is a typo (Fraktur ≠ Faktur) · Zeus id. | +| Preimage | preimage | noun · English kept (no established Indonesian rendering) · Zeus id also keeps `Preimage`. | +| Payment | pembayaran | noun · ⚠️ NOT verb `bayar`. | +| Expired | kedaluwarsa | adj/state · ⚠️ shipped `Kadaluarsa` is the older spelling; modern KBBI form is `kedaluwarsa` (already used in `lnd.errorInvoiceExpired`). | +| **_Multisig & advanced addressing_** | | | +| Co-signer | penanda tangan / penanda tangan bersama | noun · short / full · ⚠️ NOT "pemilik bersama" (co-owner). | +| Quorum | kuorum / ambang tanda tangan | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym · gloss: transaksi Bitcoin yang ditandatangani sebagian. | +| Provide signature | berikan tanda tangan / tandatangani transaksi | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / kode pembayaran | acronym kept; "Payment Code" → `kode pembayaran`. | +| Notification transaction | transaksi pemberitahuan | noun · BIP47-specific. | +| SilentPayment | Silent Payments / pembayaran senyap | protocol name kept English (plural); explanatory `pembayaran senyap` if needed. | +| **_Coin control_** | | | +| Coin Control | kontrol koin / kelola UTXO | noun, lowercase · mainstream / technical · ⚠️ NOT Title Case · Bitcoin Core id (`pengaturan koin`). | +| Frozen | dibekukan | adj/state · ⚠️ shipped `Bekukan` is the verb "to freeze"; for state label use `dibekukan` · Electrum id + Zeus id + Cake id. | +| **_Security & storage_** | | | +| Encrypted storage | penyimpanan terenkripsi | noun, lowercase · ⚠️ NOT Title Case. | +| Plausible Deniability | penyangkalan yang masuk akal | noun, lowercase · ⚠️ NOT Title Case. | +| Biometrics | biometrik | noun, lowercase. | +| Passcode | kode sandi | noun · ⚠️ shipped `kata sandi` collides with "password"; recommend `kode sandi` to disambiguate. | +| **_Backup, import & UX_** | | | +| Backup | cadangan / cadangkan | noun / verb · Bitcoin Core id + Cake id. | +| Restore | pulihkan / pemulihan | verb / noun · Bitcoin Core id + Electrum id + Cake id. | +| Import | mengimpor / impor | verb / noun. | +| Voucher | voucher | noun, lowercase · English passthrough accepted (no idiomatic Indonesian; Bitcoin Core/Electrum id do not cover). | +| Redeem | tebus / tukar | verb · ⚠️ NOT "beli ke dompet" · Cake id (`Tukar`). | +| Send | kirim | verb. | +| Receive | terima | verb. | +| Settings | pengaturan | noun, lowercase · Bitcoin Core id + Cake id. | +| Confirm | konfirmasikan / konfirmasi | verb / noun · "konfirmasi" also doubles as plural confirmations count. | +| QR Code | kode QR | noun, lowercase · Bitcoin Core id + Cake id. | +| Clipboard | papan klip / clipboard | noun · Indonesian gloss / English passthrough · ⚠️ shipped `Clipboard` Latin; KBBI form is `papan klip`. | +| Memo | memo / catatan | noun · English borrow / Indonesian gloss · shipped `Memo` passthrough. | +| Description | deskripsi / keterangan | noun, lowercase · Electrum id + Cake id (`Keterangan`). | +| Label | label | noun, lowercase · Bitcoin Core id. | diff --git a/loc/vocabulary/it.md b/loc/vocabulary/it.md new file mode 100644 index 00000000000..a53b9af3b97 --- /dev/null +++ b/loc/vocabulary/it.md @@ -0,0 +1,96 @@ +# Italian translation vocabulary (`it.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand · it.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker · it.wikipedia.org/wiki/Bitcoin | +| sats | sat / satoshi | noun, lowercase · singular `sat` preferred in chips. | +| sat/vByte | sat/vByte | technical unit; keep Latin casing. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | portafoglio | noun, lowercase · Bitcoin Core it | +| Vault | cassaforte | noun, lowercase · safe/strongbox sense · ⚠️ NOT brand. Avoid Latin "Vault". | +| Watch-only | sola lettura / solo osservazione | adj · mainstream / technical · ⚠️ NOT "modalità lettura" — it's a wallet type · Trezor it + Zeus it + Cake it | +| Hardware wallet | portafoglio hardware | noun, lowercase · Bitcoin Core it + Cake it | +| Seed | seed / frase di recupero | noun · technical / mainstream. | +| Mnemonic | frase mnemonica / frase di recupero | noun · technical / mainstream. | +| Passphrase | passphrase / frase segreta | noun · ⚠️ NOT `password` (app) and NOT `PIN` (device passcode) · Electrum it ("frase segreta") + Bitcoin Core it ("passphrase") | +| Public key | chiave pubblica | noun, lowercase. | +| Private key | chiave privata | noun, lowercase. | +| WIF | WIF | acronym · gloss: Wallet Import Format. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | descriptor / descrittore di output | noun · technical / explanatory; `descriptor` is widely kept English in IT wallet UIs. | +| Derivation path | percorso di derivazione | noun, lowercase · Electrum it + Zeus it + Cake it + Trezor it | +| Master fingerprint | master fingerprint / impronta della chiave master | noun · technical / explanatory · Trezor it ("impronta digitale" for fingerprint). | +| BIP38 | BIP38 | acronym kept · gloss: chiave privata cifrata con password · ⚠️ NOT a verb / NOT just "password". | +| **_On-chain transactions_** | | | +| Transaction | transazione | noun, lowercase, feminine · it.wikipedia.org/wiki/Bitcoin | +| Address | indirizzo | noun, lowercase · it.wikipedia.org/wiki/Bitcoin | +| Input | input | noun, masculine · Italian wallets keep English `input` · Electrum it + Trezor it + Cake it | +| Output | output | noun, masculine · ⚠️ NOT the UI recipient label "A:" · Electrum it + Trezor it + Cake it | +| UTXO | UTXO | acronym · gloss: output di transazione non speso · Electrum it + Trezor it | +| Change | resto / indirizzo di resto | noun, masculine · ⚠️ NOT verb "cambiare". `resto` = leftover coin; `indirizzo di resto` for change-address field · Bitcoin Core it + Electrum it | +| Hex | hex / dati esadecimali | noun · short / explanatory · ⚠️ NOT "hash" / NOT "dati della transazione" · Cake it + Trezor it | +| Pending | in attesa / in sospeso | adj/state · UI / body · Bitcoin Core it ("In attesa") + Trezor it ("In sospeso"). | +| Unconfirmed | non confermato / non confermata | adj · masc / fem-agreement (transazione) · Electrum it + Cake it + Trezor it | +| Confirmed | confermato / confermata | adj · masc / fem-agreement · Bitcoin Core it + Cake it + Trezor it | +| Mempool | mempool | noun, lowercase · Electrum it + Trezor it | +| Broadcast | trasmetti / trasmissione | verb / noun · button vs status · Bitcoin Core it | +| Block explorer | block explorer / esploratore di blocchi | noun · loanword / explanatory · Bitcoin Core it + Electrum it keep English `block explorer`. | +| Onchain | on-chain / sulla blockchain | adj · compact (chip) / explanatory · Zeus it. | +| Offchain | off-chain / fuori blockchain | adj · compact (chip) / explanatory. | +| **_Fees & fee bumping_** | | | +| Fee | commissione | noun, lowercase, feminine · Bitcoin Core it + Electrum it + Zeus it + Cake it | +| Fee Bump | aumento della commissione | noun · Electrum it ("Aumenta commissione"). | +| RBF | RBF | acronym · gloss: sostituisci con commissione / Replace-by-Fee · Electrum it. | +| CPFP | CPFP | acronym · gloss: figlio paga per genitore. ⚠️ NOT a verb like "Aumenta". | +| Speed Up | accelera / velocizza | verb · button label · Trezor it ("Accelerazione" noun form). | +| **_Lightning_** | | | +| Invoice | fattura / richiesta di pagamento | noun · technical / mainstream · Electrum it + Zeus it ("Fattura"). | +| Lightning Invoice | fattura Lightning | noun · Electrum it + Zeus it. | +| Preimage | preimage / preimmagine | noun · loanword / Italian math term · Electrum it + Zeus it keep `Preimage`. | +| Payment | pagamento | noun · ⚠️ NOT verb "pagare" · Bitcoin Core it + Electrum it + Zeus it + Cake it. | +| Expired | scaduto / scaduta | adj · masc / fem-agreement (fattura) · Electrum it + Zeus it + Cake it. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | cofirmatario / firmatario | noun · ⚠️ NOT "co-proprietario" (co-owner) · Electrum it ("Cofirmatario") + Bitcoin Core it ("firmatari"). | +| Quorum | quorum / soglia di firme | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym · gloss: Transazione Bitcoin Parzialmente Firmata (already used in shipped it.json). | +| Provide signature | fornisci la firma / firma | verb · specific / generic · already shipped. | +| BIP47 / Payment Code | BIP47 / codice di pagamento | acronym kept; "Payment Code" → "codice di pagamento" (already shipped) · Zeus it. | +| Notification transaction | transazione di notifica | noun · BIP47-specific. | +| SilentPayment | Silent Payments / pagamenti silenziosi | protocol name kept English (plural); explanatory `pagamenti silenziosi` if needed · Cake it keeps `Silent Payment`. | +| **_Coin control_** | | | +| Coin Control | coin control / controllo delle monete | noun · technical (kept English in app) / mainstream · ⚠️ NOT Title Case in body text · Electrum it + Zeus it ("Controllo delle monete") + Cake it ("Controllo valute") + Trezor it ("Controllo coin"). | +| Frozen | congelato / congelata | adj · masc / fem-agreement · ⚠️ NOT verb "congelare" · Electrum it + Zeus it + Cake it. | +| **_Security & storage_** | | | +| Encrypted storage | archivio cifrato / cifratura dell'archivio | noun, lowercase · canonical (Encrypted Storage) / Storage Encryption sense · ⚠️ NOT Title Case · Electrum it ("Cifrato"). | +| Plausible Deniability | negazione plausibile | noun, lowercase · ⚠️ NOT Title Case · Zeus it. | +| Biometrics | dati biometrici / autenticazione biometrica | noun · short (shipped) / explanatory · Zeus it ("Biometrico") + Trezor it. | +| Passcode | codice di accesso / PIN | noun · ⚠️ NOT `password` (app password) · Zeus it + Trezor it + Cake it use `PIN` for device unlock. | +| **_Backup, import & UX_** | | | +| Backup | backup / esegui il backup | noun / verb · Bitcoin Core it + Electrum it + Zeus it + Cake it. | +| Restore | ripristina / ripristino | verb / noun · Bitcoin Core it + Electrum it + Zeus it + Cake it. | +| Import | importa / importazione | verb / noun · Electrum it + Zeus it + Cake it. | +| Voucher | voucher | noun, lowercase · loanword · ⚠️ shipped `azteco/title` references `Atze.co` — typo for `Azte.co`. | +| Redeem | riscatta / riscattare | verb · ⚠️ NOT "acquista nel portafoglio" / NOT "trasferisci" · Zeus it + Cake it ("Riscatta"/"Riscattato"). | +| Send | invia | verb · Bitcoin Core it + Electrum it + Zeus it + Cake it. | +| Receive | ricevi | verb · Bitcoin Core it + Electrum it + Zeus it + Cake it. | +| Settings | impostazioni | noun, lowercase · Bitcoin Core it + Electrum it + Zeus it + Cake it. | +| Confirm | conferma / conferme | verb · noun "conferma/conferme" used for tx confirmations · Bitcoin Core it + Electrum it. | +| QR Code | codice QR | noun, lowercase noun + uppercase acronym · Electrum it + Zeus it + Cake it + Trezor it. | +| Clipboard | appunti | noun, lowercase, plural · Bitcoin Core it + Electrum it + Zeus it + Cake it + Bisq it. | +| Memo | nota / memo | noun, lowercase · mainstream / loanword · Zeus it + Cake it. | +| Description | descrizione | noun, lowercase · Electrum it + Zeus it + Cake it. | +| Label | etichetta | noun, lowercase · Bitcoin Core it + Electrum it + Zeus it + Cake it + Trezor it. | diff --git a/loc/vocabulary/jp_jp.md b/loc/vocabulary/jp_jp.md new file mode 100644 index 00000000000..5a0b2647584 --- /dev/null +++ b/loc/vocabulary/jp_jp.md @@ -0,0 +1,96 @@ +# Japanese translation vocabulary (`jp_jp.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / ビットコイン | brand kept Latin; ビットコイン in body text · ja.wikipedia.org/wiki/ビットコイン | +| Lightning | Lightning / ライトニング | brand mostly Latin; katakana when followed by a Japanese noun. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | ビットコイン / BTC | noun unit (katakana) + Latin ticker. | +| sats | sats | noun, Latin lowercase (matches shipped). | +| sat/vByte | sat/vByte / vByteあたりsatoshi | technical unit; Latin chip / JP-style explanatory · shipped uses the JP form. | +| vByte | vByte | technical unit, Latin. | +| **_Wallet, keys & seeds_** | | | +| Wallet | ウォレット | noun, katakana (not 財布) · Bitcoin Core ja_JP. | +| Vault | 金庫 / マルチシグ金庫 | noun; 金庫 = vault/safe. Pair with マルチシグ when context requires. | +| Watch-only | 閲覧専用 / ウォッチオンリー | adj · mainstream kanji / katakana · Electrum ja_JP + shipped `transactions/watchOnlyWarningDescription` already uses 閲覧専用. | +| Hardware wallet | ハードウェアウォレット | noun, katakana compound. | +| Seed | シード | noun, katakana of "seed". | +| Mnemonic | シードフレーズ / ニーモニックフレーズ | noun · mainstream / technical · shipped mixes both. | +| Passphrase | パスフレーズ | noun · ⚠️ NOT パスワード (password). | +| Public key | 公開鍵 | noun, kanji · standard · Bitcoin Core ja_JP. | +| Private key | 秘密鍵 | noun, kanji · standard · Bitcoin Core ja_JP. | +| WIF | WIF | acronym · gloss: ウォレットインポート形式 (秘密鍵). | +| xpub | xpub | acronym, lowercase preferred · ⚠️ shipped UI uses `XPUB`; vocabulary prefers lowercase `xpub`. | +| Descriptor | ディスクリプター | noun, katakana · Electrum ja_JP. | +| Derivation path | 派生パス | noun · Electrum ja_JP · ⚠️ drop shipped redundant English `(derivation path)` parenthetical. | +| Master fingerprint | マスターフィンガープリント | noun, katakana · Electrum ja_JP (with long vowel — shipped マスタ misses it). | +| BIP38 | BIP38 | acronym kept · gloss: パスワードで暗号化された秘密鍵 (BIP38). ⚠️ NOT a verb / NOT "password" alone. | +| **_On-chain transactions_** | | | +| Transaction | 取引 / トランザクション | noun · kanji / katakana · shipped mixes both; 取引 = mainstream, トランザクション = technical. | +| Address | アドレス | noun, katakana · Bitcoin Core ja_JP. | +| Input | 入力 | noun · ⚠️ NOT 入金 (deposit) — strictly tx-input sense. | +| Output | 出力 | noun · ⚠️ NOT the UI recipient label 宛先. | +| UTXO | UTXO | acronym · gloss: 未使用トランザクション出力. | +| Change | お釣り / 釣り銭 | noun · Electrum ja_JP uses お釣り · ⚠️ NOT verb 変更. Shipped チェンジ (katakana) is ambiguous; お釣り/釣り銭 preferred. | +| Hex | Hex / 16進数 | noun · short / explanatory · Latin per shipped, 16進数 for body · ⚠️ NOT "hash". | +| Pending | 保留中 | adj/state · Electrum ja_JP · ⚠️ shipped 試行中 ("trying") is wrong meaning; 保留中 is the standard form (also matches `transactions/pending_transaction` shipped). | +| Unconfirmed | 未承認 | adj · Bitcoin Core ja + shipped. | +| Confirmed | 承認済み | adj · Bitcoin Core ja + shipped. | +| Mempool | メモリプール | noun · Zeus ja + shipped. | +| Broadcast | ブロードキャスト | verb / noun · katakana · Bitcoin Core ja + shipped. | +| Block explorer | ブロックエクスプローラー | noun · Bitcoin Core ja. | +| Onchain | オンチェーン / ブロックチェーン上 | adj · compact / explanatory · katakana / kanji body form. | +| Offchain | オフチェーン / ブロックチェーン外 | adj · compact / explanatory · katakana / kanji body form. | +| **_Fees & fee bumping_** | | | +| Fee | 手数料 | noun, kanji · standard. | +| Fee Bump | 手数料の引き上げ | noun · Bitcoin Core ja · ⚠️ shipped `details_adv_fee_bump` uses 費用のバンプ(増加) — recommend simplifying to 手数料の引き上げ. | +| RBF | RBF | acronym · gloss: 手数料による置き換え (Replace-By-Fee). | +| CPFP | CPFP | acronym · gloss: 子が親の手数料を負担 (Child-Pays-For-Parent) · ⚠️ NOT a verb. | +| Speed Up | 高速化 / 手数料を引き上げる | verb · Bitcoin Core ja · ⚠️ shipped `transactions/rbf_title` "手数料をバンプ (RBF)" is awkward; 高速化 fits the button. | +| **_Lightning_** | | | +| Invoice | インボイス / 請求書 | noun · technical / mainstream · shipped uses both. | +| Lightning Invoice | ライトニングインボイス | noun, full katakana · shipped. | +| Preimage | プリイメージ | noun, katakana · shipped + Zeus ja (Electrum uses プレイメージ; プリイメージ is more common in JP Lightning literature). | +| Payment | 支払い | noun · ⚠️ NOT verb 支払う. | +| Expired | 失効 | adj/state · shipped `lnd/expired`. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | 共同署名者 | noun · Electrum ja_JP · ⚠️ shipped 共同署名 means "co-signature" (the act); 共同署名者 is the participant. | +| Quorum | 定足数 | noun · standard JP term · shipped. | +| PSBT | PSBT | acronym · gloss: 部分的に署名されたビットコイントランザクション. | +| Provide signature | 署名を提供 / 署名する | verb · noun-phrase / plain verb · shipped. | +| BIP47 / Payment Code | BIP47 / 支払いコード | acronym kept; "Payment Code" → 支払いコード · shipped. | +| Notification transaction | 通知トランザクション | noun · BIP47-specific · shipped. | +| SilentPayment | Silent Payments / サイレントペイメント | protocol name kept English (plural); katakana gloss optional. | +| **_Coin control_** | | | +| Coin Control | コイン管理 / コインコントロール | noun · mainstream / technical · ⚠️ NOT Title Case · shipped uses コイン管理. | +| Frozen | フリーズ済み / フリーズ | adj/state · shipped uses bare フリーズ (verb-leaning); prefer フリーズ済み for adjective state · ⚠️ NOT verb フリーズする. | +| **_Security & storage_** | | | +| Encrypted storage | ストレージ暗号化 | noun · ⚠️ NOT Title Case · shipped + Bitcoin Core ja. | +| Plausible Deniability | 隠匿設定 | noun · ⚠️ NOT Title Case · lit. "concealment setting" · shipped. | +| Biometrics | 生体認証 | noun · shipped + standard. | +| Passcode | パスコード | noun, katakana · ⚠️ NOT パスワード (= app password). | +| **_Backup, import & UX_** | | | +| Backup | バックアップ | noun / verb · Bitcoin Core ja + shipped. | +| Restore | 復元 | verb / noun · Bitcoin Core ja + shipped. | +| Import | インポート | verb / noun · katakana · shipped. | +| Voucher | バウチャー | noun, katakana · shipped. | +| Redeem | 交換する / 引き換える | verb · shipped uses 交換する; 引き換える is also valid · ⚠️ NOT 購入 (buy). | +| Send | 送金 / 送る | verb · 送金 = remit (preferred for tx), 送る = general · shipped. | +| Receive | 入金 / 受け取る | verb · 入金 = deposit (preferred for tx), 受け取る = general · shipped. | +| Settings | 設定 | noun · standard · shipped. | +| Confirm | 確認 | verb / noun · also "confirmations" = 承認 (plural). | +| QR Code | QRコード | noun · Bitcoin Core ja + shipped. | +| Clipboard | クリップボード | noun, katakana · shipped. | +| Memo | メモ | noun, katakana · shipped. | +| Description | 説明 | noun · Bitcoin Core ja · ⚠️ shipped `receive/details_label` uses 概要 ("summary/overview") — should be 説明. | +| Label | ラベル | noun, katakana · Bitcoin Core ja + shipped. | diff --git a/loc/vocabulary/kk@Cyrl.md b/loc/vocabulary/kk@Cyrl.md new file mode 100644 index 00000000000..a46bf24d5da --- /dev/null +++ b/loc/vocabulary/kk@Cyrl.md @@ -0,0 +1,96 @@ +# Kazakh (Cyrillic) translation vocabulary (`kk@Cyrl.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin; Cyrillic gloss `Биткоин` per kk.wikipedia.org/wiki/Биткоин + Bitcoin Core kk (use in body text only). | +| Lightning | Lightning | brand · kept Latin (no established kk.wikipedia article). | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | биткоин / BTC | Lowercase unit name per kk.wikipedia.org/wiki/Биткоин (article uses `биткоин` in body). `BTC` ticker uppercase. | +| sats | сатоши | noun, lowercase; abbreviation `сат.` used in compact UI · transliteration. | +| sat/vByte | sat/vByte | Keep Latin per convention. | +| vByte | vByte | Keep Latin per convention. | +| **_Wallet, keys & seeds_** | | | +| Wallet | әмиян | noun · Bitcoin Core kk + kk.wikipedia.org/wiki/Әмиян (purse/wallet). | +| Vault | сейф | noun (safe/strongbox); ⚠️ NOT a brand — translate the concept. | +| Watch-only | тек қарау | adj · "view-only" (no upstream wallet citation; descriptive). ⚠️ NOT generic "view mode". | +| Hardware wallet | аппараттық әмиян | noun · matches Wallet=әмиян; common Kazakh tech idiom (no upstream wallet citation). | +| Seed | сид / қалпына келтіру сөз тіркесі | noun · technical / mainstream (no upstream wallet citation). | +| Mnemonic | мнемоникалық тіркес | noun · standard Kazakh tech idiom (no upstream wallet citation). | +| Passphrase | құпиясөйлем | noun · Bitcoin Core kk (`Құпиясөйлем`). ⚠️ NOT the app "password" (`құпиясөз`) and NOT the device "passcode". | +| Public key | ашық кілт | noun, target-locale lowercase · parallel to `жеке кілт` (private key). | +| Private key | жеке кілт | noun · Bitcoin Core kk (`жеке кілттер` = private keys). | +| WIF | WIF | acronym. | +| xpub | xpub | acronym; lowercase per convention. | +| Descriptor | дескриптор | noun, target-locale lowercase · transliteration (no upstream wallet citation). | +| Derivation path | шығару жолы | noun, target-locale lowercase · "derivation path" (no upstream wallet citation). | +| Master fingerprint | негізгі саусақ ізі | noun, target-locale lowercase · "main fingerprint" (no upstream wallet citation). | +| BIP38 | BIP38 | acronym. | +| **_On-chain transactions_** | | | +| Transaction | транзакция | noun · Bitcoin Core kk (`Транзакция`) + kk.wikipedia.org/wiki/Биткоин. | +| Address | мекенжай | noun, target-locale lowercase · Bitcoin Core kk + kk.wikipedia. ⚠️ shipped UI uses `Адрес` (Russian loan) — flag for fix. | +| Input | кіріс | noun, target-locale lowercase · ⚠️ NOT "login" (`кіру`) — pair with `шығыс` (output). | +| Output | шығыс | noun, target-locale lowercase · ⚠️ NOT the UI label "Кімге:" — tx output noun. | +| UTXO | UTXO | acronym. | +| Change | қалдық | noun · ⚠️ NOT verb "өзгерту" — must be the noun "leftover/remainder" (standard Kazakh banking term). | +| Hex | hex / он алтылық | noun · short / explanatory · ⚠️ NOT "hash". | +| Pending | күтілуде | adj/state ("is awaiting"); target-locale lowercase · shipped string. | +| Unconfirmed | расталмаған | adj/state · parallel negative of `расталған` (confirmed). | +| Confirmed | расталған / растық | adj / noun · Bitcoin Core kk ships noun `Растық` (= "the confirming") and `Растау саны` for "Confirmations"; UI adjective form is `расталған`. | +| Mempool | mempool | technical term kept Latin (typical in Slavic/Turkic locales). | +| Broadcast | тарату / тарату жасау | noun / verb · standard Kazakh "broadcast/distribute" (no upstream wallet citation). | +| Block explorer | блок шолғышы | noun, target-locale lowercase · "block browser" (no upstream wallet citation). | +| Onchain | он-чейн / блокчейнде | adj · compact (chip) / explanatory (body) (no upstream wallet citation). | +| Offchain | оф-чейн / блокчейннен тыс | adj · compact (chip) / explanatory (body) (no upstream wallet citation). | +| **_Fees & fee bumping_** | | | +| Fee | комиссия | noun, target-locale lowercase · Bitcoin Core kk (`Комиссия`) and kk.wikipedia.org/wiki/Биткоин (`комиссиялар`). | +| Fee Bump | комиссияны арттыру | noun, target-locale lowercase · "raising of fee" umbrella term for RBF + CPFP. | +| RBF | RBF | acronym. | +| CPFP | CPFP | acronym. ⚠️ NOT a verb like "create" — keep `CPFP`. | +| Speed Up | жылдамдату | verb · "speed up" (no upstream wallet citation). | +| **_Lightning_** | | | +| Invoice | шот / шот-фактура | noun · mainstream / technical · standard Kazakh business term (no upstream wallet citation). | +| Lightning Invoice | Lightning шоты | noun · brand kept Latin + localised noun (no upstream wallet citation). | +| Preimage | TODO | math term; uncertain Kazakh rendering. | +| Payment | төлем / төлем жасау | noun / verb · target-locale lowercase · shipped UI uses verb-phrase "make payment"; canonical noun is `төлем`. | +| Expired | мерзімі біткен | adj/state · "validity ended" (no upstream wallet citation). | +| **_Multisig & advanced addressing_** | | | +| Co-signer | қос қол қоюшы | noun, target-locale lowercase · "co-signer" — ⚠️ NOT "қосиеленуші" (co-owner). | +| Quorum | кворум / қол қою шегі | noun · canonical / UI-clear (signature threshold). | +| PSBT | PSBT | acronym. | +| Provide signature | қол қою | verb · Bitcoin Core kk uses `қол қою` for `Sign` ("Хатқа қол қою" = sign a message). | +| BIP47 / Payment Code | BIP47 / төлем коды | acronym kept; `Payment Code` → `төлем коды` (target-locale lowercase). | +| Notification transaction | хабарландыру транзакциясы | noun · BIP47-specific "notification tx" (no upstream wallet citation). | +| SilentPayment | Silent Payments | brand · keep English plural per glossary. | +| **_Coin control_** | | | +| Coin Control | UTXO басқару / тиындарды басқару | noun, target-locale lowercase · technical / mainstream · ⚠️ NOT Title Case. | +| Frozen | қатырылған | adj/state · ⚠️ NOT verb "қатыру" — must be adjective/state form. | +| **_Security & storage_** | | | +| Encrypted storage | шифрланған сақтау орны | noun, target-locale lowercase · ⚠️ NOT Title Case (no upstream wallet citation). | +| Plausible Deniability | TODO | uncertain Kazakh idiomatic rendering for the privacy concept. | +| Biometrics | биометрия | noun, target-locale lowercase · kk.wikipedia.org/wiki/Биометрия. | +| Passcode | құрылғы коды | ⚠️ NOT app "password" (`Құпиясөз` = "secret word") — distinct device-level code · shipped `Құпиясөз` conflates with password; flag for fix. | +| **_Backup, import & UX_** | | | +| Backup | резервтік көшірме | noun · Bitcoin Core kk (`әмиянның резервтік көшірмесі`). | +| Restore | қалпына келтіру | verb / noun · target-locale lowercase · standard Kazakh "restore" (no upstream wallet citation). | +| Import | импорттау | verb, target-locale lowercase · ⚠️ shipped `Енгізу` (= "to enter/input") is wrong sense — flag for fix. | +| Voucher | ваучер | noun · transliteration; established Kazakh loanword. | +| Redeem | TODO | ⚠️ NOT "buy to wallet" / NOT "transfer" — uncertain Kazakh idiomatic verb for "cash in/activate voucher". | +| Send | жіберу | verb, target-locale lowercase · Bitcoin Core kk (`&Жіберу`). | +| Receive | қабылдау | verb · Bitcoin Core kk (`&Қабылдау`). | +| Settings | баптаулар | noun, target-locale lowercase · shipped plural; Bitcoin Core kk uses singular `Баптау`. Both acceptable. | +| Confirm | растау | verb, target-locale lowercase · Bitcoin Core kk (`Растау`). | +| QR Code | QR код | noun · kk.wikipedia.org/wiki/QR_код. | +| Clipboard | алмасу буфері | noun · kk.wikipedia.org/wiki/Алмасу_буфері. | +| Memo | жазба | noun, target-locale lowercase · "note/memo" (no upstream wallet citation). | +| Description | сипаттама | noun, target-locale lowercase · standard Kazakh "description". | +| Label | белгі | noun · Bitcoin Core kk (`Белгі`). | diff --git a/loc/vocabulary/kn.md b/loc/vocabulary/kn.md new file mode 100644 index 00000000000..b259a9cc489 --- /dev/null +++ b/loc/vocabulary/kn.md @@ -0,0 +1,98 @@ +# Kannada translation vocabulary (`kn.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +Note: upstream wallet projects (Bitcoin Core, Electrum, Phoenix, Zeus, Trezor, Cake, Bisq, Green, Breez) do **not** ship Kannada localizations — `bitcoin_kn.ts` exists but is 100% unfinished, all other locale files 404. The only native-script reference is Wikipedia kn (Bitcoin article). Almost every row remains **TODO** by design; fill only with high confidence from Wikipedia or the shipped `kn.json`. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin; Kannada gloss `ಬಿಟ್‌ಕಾಯಿನ್` per kn.wikipedia.org/wiki/ಬಿಟ್‌ಕಾಯಿನ್ (use in body text only). | +| Lightning | Lightning | brand kept Latin (no kn.wikipedia article for Lightning Network). | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | ಬಿಟ್‌ಕಾಯಿನ್ / BTC | noun unit + ticker · kn.wikipedia.org/wiki/ಬಿಟ್‌ಕಾಯಿನ್ | +| sats | ಸತಾಶಿ | noun · transliteration of satoshi as used in kn.wikipedia.org/wiki/ಬಿಟ್‌ಕಾಯಿನ್ | +| sat/vByte | sat/vByte | technical unit; UI keeps Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | ವ್ಯಾಲೆಟ್ | noun · transliteration; matches `_.wallet_key` shipped in kn.json. | +| Vault | ತಿಜೋರಿ | noun · "safe / strongbox" — generic Kannada noun (no upstream wallet citation). ⚠️ NOT a brand. | +| Watch-only | ವೀಕ್ಷಣೆ-ಮಾತ್ರ | adj · descriptive "view-only" (no upstream wallet citation). ⚠️ NOT generic "view mode". | +| Hardware wallet | ಹಾರ್ಡ್‌ವೇರ್ ವ್ಯಾಲೆಟ್ | noun · transliteration parallel to Wallet=ವ್ಯಾಲೆಟ್ (no upstream wallet citation). | +| Seed | ಸೀಡ್ | noun · transliteration (no upstream wallet citation). | +| Mnemonic | ನಿಮೋನಿಕ್ ಫ್ರೇಸ್ | noun · transliteration (no upstream wallet citation). | +| Passphrase | ಪಾಸ್‌ಫ್ರೇಸ್ | noun · ⚠️ NOT ಪಾಸ್ವರ್ಡ್ (password) — distinct transliteration · Bitcoin Core kn | +| Public key | ಸಾರ್ವಜನಿಕ ಕೀ | noun · parallel to ಖಾಸಗಿ ಕೀ · Bitcoin Core kn | +| Private key | ಖಾಸಗಿ ಕೀ | noun · Bitcoin Core kn | +| WIF | WIF | acronym. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | ಡಿಸ್ಕ್ರಿಪ್ಟರ್ | noun · transliteration (no upstream wallet citation). | +| Derivation path | ಡೆರಿವೇಶನ್ ಪಾತ್ | noun · transliteration (no upstream wallet citation). | +| Master fingerprint | ಮಾಸ್ಟರ್ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ | noun · transliteration (no upstream wallet citation). | +| BIP38 | BIP38 | acronym kept. | +| **_On-chain transactions_** | | | +| Transaction | ವಹಿವಾಟು / ವ್ಯವಹಾರ | noun · mainstream / technical (Wikipedia kn / Bitcoin Core kn) · kn.wikipedia.org/wiki/ಬಿಟ್‌ಕಾಯಿನ್ uses ವಹಿವಾಟು; Bitcoin Core kn uses ವ್ಯವಹಾರ. | +| Address | ವಿಳಾಸ | noun · Bitcoin Core kn | +| Input | ಇನ್‌ಪುಟ್ | noun · ⚠️ NOT "login / entrance" · Bitcoin Core kn (transliteration) | +| Output | ಔಟ್‌ಪುಟ್ | noun · transliteration parallel to Input=ಇನ್‌ಪುಟ್ (no upstream wallet citation). ⚠️ NOT the UI recipient label "To:". | +| UTXO | UTXO | acronym. | +| Change | ಚಿಲ್ಲರೆ | noun · "small change / leftover" (Kannada native term, no upstream wallet citation). ⚠️ NOT verb "to change/modify". | +| Hex | ಹೆಕ್ಸ್ | noun · transliteration (no upstream wallet citation). ⚠️ NOT "hash". | +| Pending | ಬಾಕಿ ಇದೆ | adj/state · "is pending/remaining" (no upstream wallet citation). ⚠️ NOT a noun. | +| Unconfirmed | ದೃಢೀಕರಿಸದ | adj · Bitcoin Core kn | +| Confirmed | ದೃಢೀಕರಿಸಲಾಗಿದೆ | adj/state form · Bitcoin Core kn | +| Mempool | mempool | technical term kept Latin (no Kannada upstream rendering). | +| Broadcast | ಪ್ರಸಾರ / ಪ್ರಸಾರ ಮಾಡಿ | noun / verb · Bitcoin Core kn | +| Block explorer | ಬ್ಲಾಕ್ ಎಕ್ಸ್‌ಪ್ಲೋರರ್ | noun · transliteration (no upstream wallet citation). | +| Onchain | ಆನ್-ಚೈನ್ | adj · transliteration; no native Kannada term in upstream refs. | +| Offchain | ಆಫ್-ಚೈನ್ | adj · transliteration; no native Kannada term in upstream refs. | +| **_Fees & fee bumping_** | | | +| Fee | ಶುಲ್ಕ | noun · Bitcoin Core kn | +| Fee Bump | ಶುಲ್ಕ ಹೆಚ್ಚಳ | noun · "fee increase" (no upstream wallet citation). | +| RBF | RBF | acronym. | +| CPFP | CPFP | acronym · ⚠️ NOT a verb. | +| Speed Up | ವೇಗಗೊಳಿಸಿ | verb · imperative "speed up" (no upstream wallet citation). | +| **_Lightning_** | | | +| Invoice | ಸರಕುಪಟ್ಟಿ / ಇನ್‌ವಾಯ್ಸ್ | noun · native (bill) / transliteration (no upstream wallet citation). | +| Lightning Invoice | Lightning ಸರಕುಪಟ್ಟಿ | noun · brand kept Latin + localised noun (no upstream wallet citation). | +| Preimage | TODO | math term; uncertain Kannada rendering. | +| Payment | ಪಾವತಿ | noun · standard Kannada (no upstream wallet citation). ⚠️ NOT verb "to pay". | +| Expired | ಅವಧಿ ಮುಗಿದಿದೆ | adj/state · "validity ended" (no upstream wallet citation). | +| **_Multisig & advanced addressing_** | | | +| Co-signer | ಸಹ-ಸಹಿದಾರ | noun · literal "co-signer" (no upstream wallet citation). ⚠️ NOT "co-owner". | +| Quorum | ಕೋರಂ | noun · transliteration (no upstream wallet citation). | +| PSBT | PSBT | acronym. | +| Provide signature | ಸಹಿ ಮಾಡಿ | verb · "sign" (no upstream wallet citation). | +| BIP47 / Payment Code | BIP47 / TODO | acronym kept; "Payment Code" translatable noun. | +| Notification transaction | ಸೂಚನೆ ವಹಿವಾಟು | noun · BIP47-specific "notification + transaction" (no upstream wallet citation). | +| SilentPayment | Silent Payments / TODO | protocol name kept English (plural); explanatory kn gloss TODO. | +| **_Coin control_** | | | +| Coin Control | ಕಾಯಿನ್ ನಿಯಂತ್ರಣ | noun · "coin control"; Devanagari has casing but Kannada parallel keeps loanword. ⚠️ NOT Title Case (Kannada has no casing). | +| Frozen | ಫ್ರೀಜ್ ಮಾಡಲಾಗಿದೆ | adj/state · "has been frozen" (no upstream wallet citation). ⚠️ NOT verb "to freeze". | +| **_Security & storage_** | | | +| Encrypted storage | ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಲಾದ ಸಂಗ್ರಹಣೆ | noun · ⚠️ NOT Title Case · shipped `_.storage_is_encrypted` uses ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಲಾಗಿದೆ + ಸಂಗ್ರಹಣೆ. Existing kn.json `wallets.add_placeholder` value `ಎನ್‌ಕ್ರಿಪ್ಟ್ (ಸಂಗ್ರಹಣೆ)` is malformed. | +| Plausible Deniability | TODO | ⚠️ NOT Title Case (Kannada has no casing). Abstract privacy concept — uncertain idiomatic Kannada rendering. | +| Biometrics | ಬಯೋಮೆಟ್ರಿಕ್ಸ್ | noun · transliteration (no upstream wallet citation). | +| Passcode | ಪಾಸ್‌ಕೋಡ್ | noun · transliteration · ⚠️ NOT ಪಾಸ್ವರ್ಡ್ (= app password). Distinct word needed for device unlock code. Shipped kn.json conflates the two. | +| **_Backup, import & UX_** | | | +| Backup | ಬ್ಯಾಕಪ್ | noun · transliteration · Bitcoin Core kn | +| Restore | ಪುನರುದ್ಧಾರ / ಪುನರುದ್ಧರಿಸಿ | noun / verb · Bitcoin Core kn | +| Import | ಆಮದು | noun / verb · standard Kannada noun "import" (no upstream wallet citation). | +| Voucher | ವೋಚರ್ | noun · transliteration (no upstream wallet citation). | +| Redeem | ರಿಡೀಮ್ ಮಾಡಿ | verb · transliteration (no upstream wallet citation). ⚠️ NOT "buy to wallet" / NOT "transfer". | +| Send | ಕಳುಹಿಸಿ | verb · Bitcoin Core kn (imperative ಕಳುಹಿಸು; polite ಕಳುಹಿಸಿ preferred for UI) | +| Receive | ಸ್ವೀಕರಿಸಿ | verb · Bitcoin Core kn | +| Settings | ಸೆಟ್ಟಿಂಗ್‌ಗಳು | noun · Bitcoin Core kn | +| Confirm | ದೃಢೀಕರಿಸಿ | verb · Bitcoin Core kn | +| QR Code | QR ಕೋಡ್ | noun · Bitcoin Core kn | +| Clipboard | ಕ್ಲಿಪ್‌ಬೋರ್ಡ್ | noun · transliteration · Bitcoin Core kn | +| Memo | ಮೆಮೊ | noun · transliteration (no upstream wallet citation). | +| Description | ವರ್ಣನೆ | noun · Bitcoin Core kn | +| Label | ಲೇಬಲ್ | noun · transliteration · Bitcoin Core kn | diff --git a/loc/vocabulary/ko_KR.md b/loc/vocabulary/ko_KR.md new file mode 100644 index 00000000000..05bc44d13e0 --- /dev/null +++ b/loc/vocabulary/ko_KR.md @@ -0,0 +1,96 @@ +# Korean translation vocabulary (`ko_KR.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / 비트코인 | brand · Latin in chrome; 비트코인 in body · ko.wikipedia.org/wiki/비트코인 | +| Lightning | Lightning / 라이트닝 | brand · Latin in chrome; 라이트닝 in body · ko.wikipedia.org/wiki/라이트닝_네트워크 | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | 비트코인 / BTC | noun unit + ticker. | +| sats | 사토시 | noun · Hangul of "satoshi". | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin (avoids 가상바이트 ambiguity). | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | 지갑 | noun · ⚠️ standardise on 지갑; NOT "월렛" loanword · Bitcoin Core ko_KR. | +| Vault | 금고 | noun · "safe/strongbox" sense · ⚠️ NOT Latin "Vault". | +| Watch-only | 보기 전용 / 감시 전용 | adj · UI / technical · Electrum ko_KR uses 감시 전용. | +| Hardware wallet | 하드웨어 지갑 | noun. | +| Seed | 시드 / 복구 문구 | noun · technical / mainstream. | +| Mnemonic | 니모닉 / 시드 문구 / 복구 문구 | noun · technical / mainstream · ⚠️ mnemonic backup screen mixes English "mnemonic phrase" — localise. | +| Passphrase | 패스프레이즈 / 시드 패스프레이즈 | noun · ⚠️ NOT "암호" (= password). Distinct from app password/passcode. | +| Public key | 공개 키 | noun · "공개 키" is the standard CS form (vs literal "공용 키") · Bitcoin Core ko_KR. | +| Private key | 개인 키 | noun · "개인 키" is the standard CS form (vs "비밀키") · Bitcoin Core ko_KR. | +| WIF | WIF | acronym · gloss: 지갑 가져오기 형식. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | 디스크립터 | noun · transliteration; "기술자" too literal for output-descriptor sense. | +| Derivation path | 유도 경로 | noun. | +| Master fingerprint | 마스터 지문 | noun · "지문" = fingerprint. | +| BIP38 | BIP38 | acronym · gloss: BIP38 암호 / 복호화 비밀번호. | +| **_On-chain transactions_** | | | +| Transaction | 트랜잭션 / 거래 | noun · technical / mainstream. | +| Address | 주소 | noun · Bitcoin Core ko_KR. | +| Input | 입력 | noun · shipped `transactions.details_inputs` = "입력" · Electrum ko_KR. | +| Output | 출력 | noun · shipped `transactions.details_outputs` = "출력" · Electrum ko_KR · ⚠️ NOT recipient UI label. | +| UTXO | UTXO | acronym · gloss: 미사용 트랜잭션 출력. | +| Change | 거스름돈 | noun · ⚠️ NOT verb "변경" (= to change/alter). Fix shipped `cc.change` = "변경". | +| Hex | 16진수 / 헥스 | noun · explanatory / short · shipped strings use 16진수 · ⚠️ NOT "hash". | +| Pending | 대기 중 / 보류 중 | adj/state · UI / alt. Avoid noun "보류". | +| Unconfirmed | 미확인 / 미확정 | adj · standard / shipped variant. | +| Confirmed | 확인됨 / 확정됨 | adj/state · ⚠️ "확인" alone collides with "Confirm" button — prefer "-됨" state form. | +| Mempool | 멤풀 / 메모리 풀 | noun · short / explanatory · Electrum ko_KR = 메모리 풀. | +| Broadcast | 브로드캐스트 / 네트워크에 전파 | verb / noun · button vs explanatory. | +| Block explorer | 블록 탐색기 | noun. | +| Onchain | 온체인 / 블록체인상 | adj · compact (chip) / explanatory (body) · 블록체인상 no space. | +| Offchain | 오프체인 / 블록체인 외부 | adj · compact (chip) / explanatory (body) · 블록체인 외부 with space. | +| **_Fees & fee bumping_** | | | +| Fee | 수수료 | noun. | +| Fee Bump | 수수료 인상 / 수수료 올리기 | noun / verb · ⚠️ shipped `send/details_adv_fee_bump` = "수수료 인상 허락하기" (= "allow fee bump") — verb-phrase OK in context. | +| RBF | RBF | acronym · gloss: 수수료 교체 / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: 자식이 부모의 수수료를 지불 · ⚠️ NOT "급행 수수료" alone. | +| Speed Up | 속도 올리기 / 가속 | verb · RBF user-facing label · ⚠️ shipped `transactions/rbf_title` = "급행 수수료(CPFP)" references CPFP but feature is RBF — fix acronym. | +| **_Lightning_** | | | +| Invoice | 청구서 / 인보이스 | noun · mainstream / technical. | +| Lightning Invoice | 라이트닝 청구서 / Lightning 인보이스 | noun · mainstream / technical. | +| Preimage | 프리이미지 | noun · transliteration. | +| Payment | 결제 | noun · ⚠️ NOT verb "결제하다". | +| Expired | 만료됨 / 만료되었습니다 | adj · short state / full sentence. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | 공동 서명자 | noun · ⚠️ NOT "공동 소유자" (= co-owner). | +| Quorum | 정족수 / 서명 임계값 | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym · gloss: 부분 서명 비트코인 트랜잭션. | +| Provide signature | 서명 제공하기 / 서명하기 | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / 결제 코드 | acronym + noun. | +| Notification transaction | 알림 트랜잭션 | noun · BIP47-specific. | +| SilentPayment | Silent Payments | brand kept Latin · protocol name kept English (plural); transliteration `사일런트 페이먼트` not in widespread use — optional explanatory gloss `침묵의 결제` only if needed. | +| **_Coin control_** | | | +| Coin Control | UTXO 관리 / 코인 관리 | noun · technical / mainstream · ⚠️ NOT Title Case. | +| Frozen | 동결됨 | adj/state · ⚠️ NOT verb "동결하다". Prefer "-됨" state form over bare "동결". | +| **_Security & storage_** | | | +| Encrypted storage | 저장소 암호화 | noun · ⚠️ NOT Title Case. | +| Plausible Deniability | 그럴듯한 부인 / 당위적 거부 | noun · ⚠️ NOT Title Case · standard CS / shipped variant · ko.wikipedia.org/wiki/그럴듯한_부인 | +| Biometrics | 생체 인증 / 바이오메트릭 | noun · mainstream / transliteration · shipped `biom_no_passcode` uses 생체 인식. | +| Passcode | 기기 암호 / PIN | noun · ⚠️ NOT bare "암호" (collides with password/passphrase). Device-level unlock. | +| **_Backup, import & UX_** | | | +| Backup | 백업 / 백업하기 | noun / verb. | +| Restore | 복원하기 / 복원 | verb / noun. | +| Import | 가져오기 | verb / noun · ⚠️ shipped "들여오기" is archaic — prefer "가져오기" (standard SW). | +| Voucher | 바우처 | noun · ⚠️ fix shipped `azteco/title` = "제목" (= "title", leftover placeholder) → "바우처" or "Azte.co 바우처". | +| Redeem | 사용하기 / 교환하기 | verb · ⚠️ NOT "구매" (= buy). For vouchers prefer "사용하기" (activate/cash in). | +| Send | 보내기 | verb. | +| Receive | 받기 | verb. | +| Settings | 설정 | noun. | +| Confirm | 확인 / 확인하기 | verb / noun · also "확정" for tx confirmations (plural blocks). | +| QR Code | QR 코드 | noun. | +| Clipboard | 클립보드 | noun · ⚠️ shipped uses "붙여넣기판" (= "paste board") — non-standard; use 클립보드. | +| Memo | 메모 | noun. | +| Description | 설명 | noun · ⚠️ fix shipped `receive/details_label` = "형태" (= "form/shape") → "설명". | +| Label | 라벨 / 레이블 | noun · UI / formal. | diff --git a/loc/vocabulary/lrc.md b/loc/vocabulary/lrc.md new file mode 100644 index 00000000000..0a3b3f8745a --- /dev/null +++ b/loc/vocabulary/lrc.md @@ -0,0 +1,98 @@ +# Northern Luri translation vocabulary (`lrc.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +> RTL locale. Brand names appear in Latin script inside the surrounding script; values below are the rendered form used in `loc/lrc.json`. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · gloss: بیت کوین · lrc.wikipedia.org/wiki/Bitcoin. | +| Lightning | Lightning | brand kept Latin · gloss: لایتنینگ. | +| Electrum | Electrum | brand kept Latin · gloss: الکترام. | +| LNDhub | LNDhub | brand kept Latin. | +| LND | LND | brand kept Latin. | +| LNURL | LNURL | brand kept Latin. | +| Tor | Tor | brand kept Latin. | +| Orbot | Orbot | brand kept Latin. | +| GroundControl | GroundControl | brand kept Latin. | +| **_Units & amounts_** | | | +| bitcoin / BTC | بیت کوین | — | +| sats | ساتۊشی | — | +| sat/vByte | ساتۊشی/بایت مجازی | — | +| vByte | بایت مجازی | — | +| **_Wallet, keys & seeds_** | | | +| Wallet | کیف پیلٛ | Lit. "money pocket". | +| Vault | گاوصندوق | noun · lit. "safe/strongbox" · ⚠️ NOT a brand; translated · borrowed from fa: Luri shares vocabulary with Persian. | +| Watch-only | کیف پیلٛ ناظر | adj/noun · observer-wallet sense · ⚠️ NOT "view mode" · borrowed from fa: کیف پول ناظر. | +| Hardware wallet | کیف پیلٛ سخت ٱفزاری | noun · borrowed from fa: کیف پول سخت‌افزاری. | +| Seed | سید | — | +| Mnemonic | عبارت بازیابی | noun · "recovery phrase" · borrowed from fa: عبارت بازیابی. | +| Passphrase | پس فریز | — | +| Public key | کلٛیل عمومی | noun · uses lrc کلٛیل (key, per `_.wallet_key`) · borrowed from fa: کلید عمومی. | +| Private key | کلٛیل خصوصی | noun · borrowed from fa: کلید خصوصی. | +| WIF | WIF | acronym · letters kept. | +| xpub | xpub | acronym · lowercase per convention. | +| Descriptor | توصیف گر | noun · output descriptor · borrowed from fa: توصیف‌گر. | +| Derivation path | تۏر موشتق بیؽن | — | +| Master fingerprint | TODO | — | +| BIP38 | BIP38 | — | +| **_On-chain transactions_** | | | +| Transaction | تراکونش | — | +| Address | آدرس | — | +| Input | ورودی | noun · ⚠️ NOT "login/entrance" · borrowed from fa: ورودی. | +| Output | خروجی | noun · ⚠️ NOT the UI recipient label "و" (to:) · borrowed from fa: خروجی. | +| UTXO | UTXO | acronym · letters kept. | +| Change | باقی منه | Lit. "remaining". | +| Hex | هگزادسیمال | noun · ⚠️ NOT "hash" · borrowed from fa: هگزادسیمال. | +| Pending | د انتظار | adj/state · ⚠️ NOT noun "انتظار" · borrowed from fa: در انتظار. | +| Unconfirmed | تایید نبیه | adj/state · parallels lrc `مونقزی بیه` (expired) pattern · borrowed from fa: تأییدنشده. | +| Confirmed | تایید بیه | adj/state · ⚠️ shipped `تایید` is noun — adj/state needs `بیه` · borrowed from fa: تأییدشده. | +| Mempool | mempool | noun · technical Latin loan · borrowed from fa: mempool/حافظهٔ تراکنش‌ها. | +| Broadcast | مونتشر | — | +| Block explorer | گشت گر بلاک | noun · matches lrc `گشت گر` (explorer) in shipped strings. | +| Onchain | آنچین | adj · transliteration · borrowed from fa: آن‌چین. | +| Offchain | آفچین | adj · transliteration · borrowed from fa: آف‌چین. | +| **_Fees & fee bumping_** | | | +| Fee | کارمزد | — | +| Fee Bump | افزایش کارمزد | noun · borrowed from fa: افزایش کارمزد. | +| RBF | RBF | acronym · letters kept. | +| CPFP | CPFP | acronym · letters kept · ⚠️ NOT a verb. | +| Speed Up | تسریع | verb · UI button label · borrowed from fa: تسریع. | +| **_Lightning_** | | | +| Invoice | سۊرت هساو | Lit. "bill". | +| Lightning Invoice | سۊرت هساو لایتنینگ | — | +| Preimage | preimage | noun · technical Latin loan · borrowed from fa: preimage / پیش‌تصویر. | +| Payment | پرداخت | — | +| Expired | مونقزی بیه | — | +| **_Multisig & advanced addressing_** | | | +| Co-signer | امزا کوݩ هومبهر | — | +| Quorum | حد نصاب | noun · borrowed from fa: حد نصاب. | +| PSBT | PSBT | acronym · letters kept. | +| Provide signature | دین امزا | — | +| BIP47 / Payment Code | کود پرداخت | — | +| Notification transaction | تراکونش اعلان | noun · BIP47-specific · borrowed from fa: تراکنش اعلان. | +| SilentPayment | Silent Payments | brand kept Latin (plural per protocol name). | +| **_Coin control_** | | | +| Coin Control | مدیریت UTXO / مدیریت سکه‌ها | noun · ⚠️ NOT Title Case · borrowed from fa: مدیریت UTXO. | +| Frozen | مسدۊد | adj/state · "blocked" · ⚠️ NOT verb · borrowed from fa: مسدودشده / مسدود. | +| **_Security & storage_** | | | +| Encrypted storage | TODO | — | +| Plausible Deniability | انکار مووجه | noun · ⚠️ NOT Title Case · borrowed from fa: انکار موجه. | +| Biometrics | بیومتریک | — | +| Passcode | کد دسترسی | noun · ⚠️ NOT app password · borrowed from fa: کد دسترسی. | +| **_Backup, import & UX_** | | | +| Backup | نۏسخه لادرار | — | +| Restore | بازیابی | verb / noun · borrowed from fa: بازیابی. | +| Import | و مؽن اووردن | — | +| Voucher | بن / ووچر | noun · Azte.co prepaid voucher · borrowed from fa: بن / ووچر. | +| Redeem | فعال کردن | "activate". | +| Send | کلٛ کردن | — | +| Receive | گرتن | — | +| Settings | سامونیا | — | +| Confirm | تایید | — | +| QR Code | QR کود | noun · matches bqi pattern. | +| Clipboard | ویرگه | — | +| Memo | ویرداشت | — | +| Description | توضیحات | noun · borrowed from fa: توضیحات. | +| Label | برچسب | noun · ⚠️ must be noun, not verb · borrowed from fa: برچسب. | diff --git a/loc/vocabulary/ms.md b/loc/vocabulary/ms.md new file mode 100644 index 00000000000..23ad02eeb57 --- /dev/null +++ b/loc/vocabulary/ms.md @@ -0,0 +1,96 @@ +# Malay translation vocabulary (`ms.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand · ms.wikipedia.org/wiki/Bitcoin keeps Latin "Bitcoin". | +| Lightning | Lightning | brand · no ms.wikipedia article; keep Latin. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand · keep Latin. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker; ms keeps "bitcoin" lowercase, `BTC` uppercase. | +| sats | sat | noun, lowercase; shipped `units.sats` = `sat`. | +| sat/vByte | sat/vByte | technical unit · keep Latin per convention. | +| vByte | vByte | technical unit · keep Latin per convention. | +| **_Wallet, keys & seeds_** | | | +| Wallet | dompet | noun, lowercase · ms.wikipedia.org/wiki/Bitcoin uses `dompet`. | +| Vault | bilik kebal / peti kebal | noun, lowercase · `bilik kebal` shipped; `peti kebal` also appears in `multisig.ms_help_1`. Both = safe/strongbox. | +| Watch-only | lihat-saja / lihat sahaja | adj · `lihat-saja` shipped; `lihat sahaja` more standard ms. | +| Hardware wallet | dompet perkakas | noun, lowercase · shipped form. | +| Seed | frasa mnemonik / benih | noun · `frasa mnemonik` mainstream recovery-phrase form (preferred per glossary); `benih` literal/technical (shipped). | +| Mnemonic | mnemonik / frasa mnemonik | noun · `mnemonik` per Kamus Dewan (technical); `frasa mnemonik` for body-text. | +| Passphrase | frasa nyahsulit / frasa laluan | noun · `frasa nyahsulit` (= decryption phrase) disambiguates from `kata laluan` (password); `frasa laluan` retained as fallback but collides with password term. | +| Public key | kunci awam / kunci umum | noun, lowercase · `kunci awam` per ms.wikipedia.org/wiki/Bitcoin; `kunci umum` also shipped. | +| Private key | kunci persendirian / kunci peribadi | noun, lowercase · `kunci persendirian` shipped; `kunci peribadi` also natural. | +| WIF | WIF | acronym · gloss: format pindah masuk dompet. | +| xpub | xpub | acronym, lowercase preferred per convention. | +| Descriptor | deskriptor | noun, lowercase · transliteration; no ms reference; matches ms phonetics. | +| Derivation path | laluan terbitan | noun, lowercase · shipped form. | +| Master fingerprint | cap jari induk | noun, lowercase · shipped form. | +| BIP38 | BIP38 | acronym kept · gloss: kunci persendirian dilindungi kata laluan. | +| **_On-chain transactions_** | | | +| Transaction | urus niaga / transaksi | noun, lowercase · `urus niaga` shipped; `transaksi` per ms.wikipedia.org/wiki/Bitcoin (mainstream loanword). | +| Address | alamat | noun, lowercase · shipped. | +| Input | masukan / input urus niaga | noun · short / full. | +| Output | keluaran / output urus niaga | noun · short / full · ⚠️ NOT `kepada` (= "to") which is a preposition, not the transaction-output noun. | +| UTXO | UTXO | acronym · gloss: keluaran urus niaga belum dibelanjakan. | +| Change | baki / duit baki | noun · ⚠️ NOT `ubah` (= verb "to change/modify"). `baki` = remainder; `duit baki` for clarity. | +| Hex | heks / data heksadesimal | noun · short / explanatory · ⚠️ NOT `hash` (a different concept). | +| Pending | tergantung / belum selesai | adj/state · `tergantung` shipped; `belum selesai` more natural body-text. | +| Unconfirmed | belum disahkan / tidak disahkan | adj · standard ms uses root `sah` (= valid/confirmed). | +| Confirmed | disahkan / telah disahkan | adj · `disahkan` (= confirmed/validated) is the standard adj form; `telah disahkan` is the explicit past-participle variant for body-text. | +| Mempool | mempool | noun · keep Latin — no established ms rendering. | +| Broadcast | siarkan / penyiaran | verb / noun · `Siar` / `Siarkan` shipped as button; `Penyiaran` as noun. | +| Block explorer | penjelajah blok | noun, lowercase · shipped `penjelajah` in `settings.open_link_in_explorer`. | +| Onchain | on-chain / atas rantai | adj · compact (chip) / explanatory (body). | +| Offchain | off-chain / luar rantai | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | yuran | noun, lowercase · shipped. | +| Fee Bump | penambahan yuran | noun · shipped. | +| RBF | RBF | acronym · gloss: gantikan dengan yuran lebih tinggi. | +| CPFP | CPFP | acronym · gloss: anak bayar untuk induk (child-pays-for-parent). | +| Speed Up | tambah yuran / percepatkan | verb · `Tambah Yuran` shipped (RBF button); `percepatkan` is the direct verb. | +| **_Lightning_** | | | +| Invoice | invois | noun, lowercase · shipped. | +| Lightning Invoice | invois Lightning | noun · shipped. | +| Preimage | preimage / pra-imej | noun · technical (keep Latin) / explanatory transliteration. | +| Payment | bayaran / pembayaran | noun · ⚠️ NOT `bayar` (= verb "to pay"); use the noun form. `bayaran` appears in `notifications.would_you_like_to_receive_notifications`. | +| Expired | tamat tempoh / luput | adj · `tamat tempoh` shipped; `luput` also shipped in `lndViewInvoice.wasnt_paid_and_expired`. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | penandatangan bersama | noun · ⚠️ NOT `pemilik bersama` (co-owner) — must be a signer noun. | +| Quorum | kuorum | noun, lowercase · shipped. | +| PSBT | PSBT | acronym · gloss: urus niaga Bitcoin bertandatangan separa. | +| Provide signature | berikan tandatangan / tandatangani | verb · `berikan tandatangan` shipped; `tandatangani` more direct. | +| BIP47 / Payment Code | BIP47 / kod pembayaran | acronym kept; "Payment Code" → `kod pembayaran`. | +| Notification transaction | urus niaga pemberitahuan | noun · BIP47-specific. | +| SilentPayment | Silent Payments / pembayaran senyap | protocol name kept English (plural); explanatory `pembayaran senyap` if needed. | +| **_Coin control_** | | | +| Coin Control | kawalan UTXO / kawalan duit | noun, lowercase · `kawalan UTXO` technical (preferred per glossary); `kawalan duit` mainstream/shipped. | +| Frozen | dibekukan / beku | adj · ⚠️ NOT `bekukan` (= verb "freeze!"); must be adj/state form. | +| **_Security & storage_** | | | +| Encrypted storage | simpanan disulitkan | noun, lowercase · shipped. | +| Plausible Deniability | penafian munasabah | noun, lowercase · shipped form. | +| Biometrics | biometrik | noun, lowercase · shipped. | +| Passcode | kod laluan | noun · ⚠️ NOT `kata laluan` (= password); `kod laluan` disambiguates. | +| **_Backup, import & UX_** | | | +| Backup | sandaran / sandarkan | noun / verb · `sandarkan` shipped as button; `sandaran` is the noun form. | +| Restore | kembalikan / pemulihan | verb / noun · `kembalikan` base verb; `pemulihan` noun form. | +| Import | pindah masuk / pemindahan masuk | verb / noun · shipped. | +| Voucher | baucar | noun, lowercase · shipped. | +| Redeem | tebus / aktifkan | verb · `tebus` shipped; `aktifkan` (activate) is the cleaner sense. | +| Send | hantar | verb · shipped. | +| Receive | terima | verb · shipped. | +| Settings | tetapan | noun, lowercase · shipped. | +| Confirm | sahkan / pengesahan | verb / noun · ⚠️ NOT `pasti` (= adj "sure/certain"); use verb `sahkan`. | +| QR Code | kod QR | noun · shipped. | +| Clipboard | papan klip / papan sepit | noun, lowercase · `papan klip` more widespread; `papan sepit` (= "pinching board") less standard. | +| Memo | memo / catatan | noun, lowercase · shipped `Memo`; `catatan` also natural. | +| Description | penerangan / huraian | noun, lowercase · shipped `penerangan`. | +| Label | label / tanda | noun · both noun forms; `label` is the standard loanword, `tanda` (= mark/tag) is the native equivalent. | diff --git a/loc/vocabulary/nb_no.md b/loc/vocabulary/nb_no.md new file mode 100644 index 00000000000..cd1935f3683 --- /dev/null +++ b/loc/vocabulary/nb_no.md @@ -0,0 +1,96 @@ +# Norwegian Bokmål translation vocabulary (`nb_no.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand · no.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand · no.wikipedia.org/wiki/Lightning_Network | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker; lowercase for unit. | +| sats | sats | noun, lowercase. | +| sat/vByte | sat/vByte | technical unit; UI keeps Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | lommebok | noun, lowercase · Bitcoin Core nb + Electrum nb. | +| Vault | hvelv / safe | noun · target-locale word for safe/strongbox · ⚠️ NOT brand "Vault". Shipped uses Latin `Vault`; recommend `hvelv`. | +| Watch-only | kun klokke / kun observasjon | adj · Bitcoin Core nb uses "kun klokker" · ⚠️ NOT "view mode" / "read mode". | +| Hardware wallet | maskinvarelommebok | noun, lowercase · Bitcoin Core nb. | +| Seed | seed / gjenopprettingsfrase | noun · technical / mainstream · Electrum nb + Bitcoin Core nb keep `seed`. | +| Mnemonic | mnemonisk frase / gjenopprettingsfrase | noun · technical / mainstream. ⚠️ shipped `mnemonisk` is adj only — full noun is `mnemonisk frase`. | +| Passphrase | passordfrase | noun · Bitcoin Core nb + Electrum nb · ⚠️ NOT `passord` (password). | +| Public key | offentlig nøkkel | noun, lowercase · ⚠️ shipped `offentlige nøkkelen` is definite/inflected — canonical form is `offentlig nøkkel` · Bitcoin Core nb. | +| Private key | privat nøkkel | noun, lowercase · Bitcoin Core nb. | +| WIF | WIF | acronym. | +| xpub | xpub | acronym, lowercase preferred · ⚠️ shipped UI uses `XPUB` uppercase. | +| Descriptor | deskriptor | noun, lowercase. | +| Derivation path | utledningssti / derivation path | noun · localized / technical fallback · ⚠️ shipped is English passthrough `derivation path` — recommend localized `utledningssti`. | +| Master fingerprint | hovednøkkelens fingeravtrykk / fingeravtrykk | noun · Zeus nb · ⚠️ shipped `Master Fingerprint` is English passthrough; fix. | +| BIP38 | BIP38 | acronym kept · gloss: passordbeskyttet privat nøkkel. | +| **_On-chain transactions_** | | | +| Transaction | transaksjon | noun, lowercase · Bitcoin Core nb. | +| Address | adresse | noun, lowercase · Bitcoin Core nb. | +| Input | inndata / inngang | noun · UI / generic · shipped uses `Inndata`. | +| Output | utdata / utgang | noun · UI / generic · ⚠️ NOT the UI label "Til:". | +| UTXO | UTXO | acronym · gloss: ubrukt transaksjonsutdata. | +| Change | veksel / vekslepenger | noun · Bitcoin Core nb + Electrum nb · ⚠️ NOT verb `endre` (shipped `cc.change=Endre` is wrong). | +| Hex | hex / heksadesimal | noun · short / explanatory · ⚠️ NOT "hash". Shipped `heks` is informal. | +| Pending | venter / ventende | adj/state · short / explanatory. | +| Unconfirmed | ubekreftet | adj · Bitcoin Core nb · ⚠️ shipped `ubekreftede` is plural form — canonical singular is `ubekreftet`. | +| Confirmed | bekreftet | adj · ⚠️ shipped `bekreftelser` means "confirmations" (noun plural), not the adj state. Use `bekreftet` for the state. | +| Mempool | mempool | noun · Bitcoin Core nb keeps Latin. | +| Broadcast | kringkast / kringkasting | verb / noun · Bitcoin Core nb + Electrum nb. | +| Block explorer | blokk-utforsker / Block Explorer | noun · localized / brand-style · shipped uses Latin `Block Explorer`. | +| Onchain | på kjeden / onchain | adj · compact / explanatory · Zeus nb uses `On-chain`. | +| Offchain | utenfor kjeden / offchain | adj · compact / explanatory. | +| **_Fees & fee bumping_** | | | +| Fee | gebyr / avgift | noun, lowercase · canonical fee / duty-or-tax · ⚠️ `avgift` = duty/tax; `gebyr` is canonical fee · Bitcoin Core nb. | +| Fee Bump | gebyrøkning / fee bump | noun · localized / English-loan · shipped quotes `Fee Bump`. | +| RBF | RBF | acronym · gloss: erstatt med høyere gebyr (Replace-By-Fee). | +| CPFP | CPFP | acronym · gloss: barn betaler for forelder. | +| Speed Up | fremskynd | verb · canonical verb · ⚠️ shipped `Betal en høyere avgift` is a sentence (verbose form) — flag. | +| **_Lightning_** | | | +| Invoice | faktura | noun · Bitcoin Core nb + Electrum nb + Zeus nb. | +| Lightning Invoice | Lightning-faktura | noun · brand + localized noun. | +| Preimage | forhåndsbilde / preimage | noun · math / English-loan · Zeus nb keeps `Preimage`. | +| Payment | betaling | noun · ⚠️ NOT verb `betal`. | +| Expired | utløpt | adj/state. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | medsignerer / medsigner | noun · participant noun · ⚠️ NOT `medsignatur` (= co-signature act) · ⚠️ NOT "medeier" (co-owner) · Bitcoin Core nb. | +| Quorum | quorum / terskel | noun · canonical / UI · shipped uses `quorum`. | +| PSBT | PSBT | acronym · gloss: delvis signert Bitcoin-transaksjon · Bitcoin Core nb. | +| Provide signature | gi signatur / signer transaksjon | verb · ⚠️ shipped `Skriv signatur` (= "write signature") is awkward — prefer `gi signatur` or `signer`. | +| BIP47 / Payment Code | BIP47 / betalingskode | acronym kept; "Payment Code" → `betalingskode`. | +| Notification transaction | varslingstransaksjon | noun · BIP47-specific. | +| SilentPayment | Silent Payments / stille betalinger | protocol name kept English (plural); explanatory locale gloss `stille betalinger`. | +| **_Coin control_** | | | +| Coin Control | myntkontroll | noun, lowercase · Bitcoin Core nb · ⚠️ NOT Title Case. | +| Frozen | fryst | adj · Zeus nb · ⚠️ NOT verb `fryse`. Shipped uses `frosne` (plural); canonical singular is `fryst`. | +| **_Security & storage_** | | | +| Encrypted storage | kryptert lagring | noun, lowercase · ⚠️ NOT Title Case (shipped `Kryptert Lagring`). | +| Plausible Deniability | plausibel fornektelse | noun, lowercase · ⚠️ NOT Title Case (shipped `Plausibel Fornektelse`). | +| Biometrics | biometri | noun, lowercase · ⚠️ shipped `Biometrics` is English passthrough — fix to `biometri`. | +| Passcode | tilgangskode | noun · ⚠️ NOT `passord` (= password). Shipped `Passord` collides with "password". | +| **_Backup, import & UX_** | | | +| Backup | sikkerhetskopi / sikkerhetskopier | noun / verb · Bitcoin Core nb + Electrum nb · ⚠️ shipped `backup` is informal English-loan. | +| Restore | gjenopprett / gjenoppretting | verb / noun · Bitcoin Core nb. | +| Import | importer / import | verb / noun. | +| Voucher | kupong | noun, lowercase. | +| Redeem | løs inn / innløs | verb. | +| Send | send | verb · ⚠️ shipped `Sende` is infinitive form; button label should be imperative `Send`. | +| Receive | motta | verb. | +| Settings | innstillinger | noun, lowercase. | +| Confirm | bekreft / bekreftelse | verb / noun. | +| QR Code | QR-kode | noun. | +| Clipboard | utklippstavle | noun, lowercase · Bitcoin Core nb. | +| Memo | notat / memo | noun · localized / English-loan · Zeus nb uses `notat`. | +| Description | beskrivelse | noun, lowercase · Bitcoin Core nb + Electrum nb. | +| Label | merkelapp / etikett | noun, lowercase. | diff --git a/loc/vocabulary/ne.md b/loc/vocabulary/ne.md new file mode 100644 index 00000000000..f84038c0d95 --- /dev/null +++ b/loc/vocabulary/ne.md @@ -0,0 +1,96 @@ +# Nepali translation vocabulary (`ne.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / बिटकोइन | brand kept Latin; बिटकोइन in explanatory text · ne.wikipedia.org/wiki/बिटकोइन | +| Lightning | Lightning / लाइटनिङ | brand kept Latin; लाइटनिङ in explanatory text. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | बिटकोइन / BTC | noun unit + ticker. | +| sats | sats / सातोशी | noun; English `sats` preferred in UI (matches ne.json), `सातोशी` in body text (Devanagari has no casing). | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | वालेट | noun; shipped UI uses `वालेट` (also plural `वालेटहरू`). | +| Vault | भल्ट | noun · canonical: `भल्ट` (matches English /ɔː/ → भ); shipped UI mixes `भल्ट` and `वाल्ट` — prefer `भल्ट`. Avoid Latin "Vault". | +| Watch-only | केवल हेर्ने | adj · "view-only" gloss; no upstream Nepali reference, but semantically accurate. | +| Hardware wallet | हार्डवेयर वालेट | noun. | +| Seed | सीड | noun, transliteration; shipped UI uses `सीड`. | +| Mnemonic | निमोनिक फ्रेज | noun, transliteration; shipped UI. | +| Passphrase | पासफ्रेज | noun · ⚠️ distinct from `पासवर्ड` (password). Bitcoin Core ne uses `पासफ्रेज`. | +| Public key | सार्वजनिक कुञ्जी | noun; pairs with `निजी कुञ्जी`. TODO: not in shipped JSON. | +| Private key | निजी कुञ्जी | noun; shipped UI. | +| WIF | WIF | acronym · gloss: वालेट इम्पोर्ट फरम्याट. | +| xpub | xpub | acronym, lowercase preferred · ⚠️ shipped UI uses uppercase `XPUB`. | +| Descriptor | डिस्क्रिप्टर | noun · transliteration (technical term, no native equivalent). | +| Derivation path | डेरिभेसन पथ | noun · transliteration of "derivation" + native `पथ` (path). | +| Master fingerprint | मास्टर फिंगरप्रिन्ट | noun; shipped UI. | +| BIP38 | BIP38 | acronym kept · gloss: पासवर्ड संरक्षित निजी कुञ्जी। | +| **_On-chain transactions_** | | | +| Transaction | लेनदेन / कारोबार | noun · shipped UI uses `लेनदेन`; Bitcoin Core ne uses `कारोबार`. | +| Address | ठेगाना | noun · Bitcoin Core ne. | +| Input | इनपुट | noun · transliteration (standard technical usage in Nepali tech writing). | +| Output | आउटपुट | noun · transliteration (standard technical usage). ⚠️ NOT recipient label "लागी". | +| UTXO | UTXO | acronym · gloss: नखर्चिएको लेनदेन आउटपुट। | +| Change | फिर्ता / बाँकी रकम | noun · ⚠️ NOT verb "परिवर्तन" (to change). `फिर्ता` = leftover returned. shipped UI uses `परिवर्तन` which is wrong. | +| Hex | हेक्स / हेक्स डाटा | noun, transliteration · compact / explanatory · ⚠️ NOT "hash". | +| Pending | विचाराधीन | adj/state · standard spelling (विचार + अधीन, long ī). ⚠️ shipped UI uses misspelled `पेनदिंग`. | +| Unconfirmed | अपुष्ट | adj; shipped UI. | +| Confirmed | पुष्टि भएको | adj/state · ⚠️ shipped UI uses noun `पुष्टिकरण` (confirmation). Adjective form preferred. | +| Mempool | मेमपूल | noun · transliteration (protocol-specific term). | +| Broadcast | प्रसारण गर्नुहोस् / प्रसारण | verb / noun · shipped UI; Bitcoin Core ne `प्रसारण`. | +| Block explorer | ब्लक एक्सप्लोरर | noun · transliteration; no native Nepali rendering in upstream refs (Bitcoin Core ne lacks the entry). | +| Onchain | अन-चेन / ब्लकचेनमा | adj · compact transliteration / explanatory ("on the blockchain"); no native term in upstream refs. | +| Offchain | अफ-चेन / ब्लकचेन बाहिर | adj · compact transliteration / explanatory ("outside the blockchain"). | +| **_Fees & fee bumping_** | | | +| Fee | शुल्क | noun · Bitcoin Core ne. | +| Fee Bump | शुल्क वृद्धि / शुल्क बढाउनुहोस् | noun (canonical) / verb (button action) · ⚠️ shipped UI uses transliterated `शुल्क बम्प`. | +| RBF | RBF | acronym · gloss: शुल्क-द्वारा-प्रतिस्थापन। | +| CPFP | CPFP | acronym · gloss: सन्तानले अभिभावकको शुल्क तिर्छ। ⚠️ NOT "सिर्जना". | +| Speed Up | छिटो बनाउनुहोस् | verb (button label). | +| **_Lightning_** | | | +| Invoice | इनभ्वाइस | noun, transliteration; shipped UI. | +| Lightning Invoice | लाइटनिङ इनभ्वाइस | noun; shipped UI. | +| Preimage | प्रीइमेज | noun · transliteration (cryptographic term, no native math equivalent). | +| Payment | भुक्तानी | noun; shipped UI · ⚠️ NOT verb "तिर्नु". | +| Expired | म्याद सकियो | adj/state phrase; shipped UI. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | सह-हस्ताक्षरकर्ता | noun · literal "co-signatory" (सह + हस्ताक्षरकर्ता). ⚠️ NOT "सह-स्वामी" (co-owner). | +| Quorum | कोरम / हस्ताक्षर सीमा | noun · canonical transliteration `कोरम` / UI-clear gloss `हस्ताक्षर सीमा` ("signature threshold"). Prefer `कोरम` in technical contexts, `हस्ताक्षर सीमा` in user-facing UI. | +| PSBT | PSBT | acronym; shipped UI. | +| Provide signature | हस्ताक्षर गर्नुहोस् | verb; shipped UI. | +| BIP47 / Payment Code | BIP47 / भुक्तानी कोड | acronym kept; "Payment Code" → `भुक्तानी कोड` (shipped UI). | +| Notification transaction | सूचना लेनदेन | noun · BIP47-specific (literal: "notification transaction"). | +| SilentPayment | Silent Payments | protocol name kept English (plural); explanatory gloss `मौन भुक्तानी` if needed. | +| **_Coin control_** | | | +| Coin Control | सिक्का नियन्त्रण | noun; shipped UI · ⚠️ source English uses Title Case but Devanagari has no casing. | +| Frozen | फ्रिज गरिएको | adj · ⚠️ NOT verb "फ्रिज गर्नुहोस्". shipped UI inconsistent (`फ्रिज` / `फ्रोजन`) — pick `फ्रिज गरिएको` (state). | +| **_Security & storage_** | | | +| Encrypted storage | इन्क्रिप्टेड स्टोरेज | noun; shipped UI · ⚠️ source English uses Title Case but Devanagari has no casing. | +| Plausible Deniability | विश्वसनीय इन्कार | noun · `विश्वसनीय` (plausible/credible) + `इन्कार` (denial). ⚠️ shipped UI uses `व्यावहारिक अस्वीकार्यता` ("practical unacceptability") which is awkward and shifts meaning. | +| Biometrics | बायोमेट्रिक्स | noun; shipped UI. | +| Passcode | पासकोड | noun · ⚠️ shipped UI uses `पासवर्ड` (collides with password). Distinct word preferred. | +| **_Backup, import & UX_** | | | +| Backup | ब्याकअप | noun, transliteration; shipped UI. | +| Restore | पुनर्स्थापना गर्नुहोस् / पुनर्स्थापना | verb / noun; shipped UI. | +| Import | आयात गर्नुहोस् / आयात | verb / noun; shipped UI. | +| Voucher | भाउचर | noun, transliteration; shipped UI. | +| Redeem | रिडिम गर्नुहोस् | verb · ⚠️ NOT "किन्नुहोस्". shipped UI uses `रिडिम`. | +| Send | पठाउनुहोस् | verb; shipped UI. | +| Receive | प्राप्त गर्नुहोस् | verb; shipped UI · Bitcoin Core ne. | +| Settings | सेटिङहरू | noun; shipped UI. | +| Confirm | पुष्टि गर्नुहोस् / पुष्टि | verb / noun · ⚠️ shipped UI uses `पक्का` ("sure") for confirmation header — informal. Prefer `पुष्टि गर्नुहोस्`. | +| QR Code | QR कोड | noun · ne.wikipedia.org/wiki/क्यूआर_कोड (alt: `क्यूआर कोड`). | +| Clipboard | क्लिपबोर्ड | noun; shipped UI. | +| Memo | मेमो | noun; shipped UI. | +| Description | विवरण | noun; shipped UI. | +| Label | लेबल | noun · Bitcoin Core ne. | diff --git a/loc/vocabulary/nl_nl.md b/loc/vocabulary/nl_nl.md new file mode 100644 index 00000000000..d94f029b068 --- /dev/null +++ b/loc/vocabulary/nl_nl.md @@ -0,0 +1,96 @@ +# Dutch translation vocabulary (`nl_nl.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · nl.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand · nl.wikipedia.org/wiki/Lightning_Network ("Lightningnetwerk" in body text) | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. Only in key name; URI label localised. | +| LND | LND | brand. Only in key prefixes. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker · nl.wikipedia.org/wiki/Bitcoin | +| sats | sats / satoshi | noun, lowercase · nl.wikipedia.org/wiki/Bitcoin | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. | +| vByte | vByte | technical unit. From "in sat/vByte". | +| **_Wallet, keys & seeds_** | | | +| Wallet | wallet | noun, lowercase · English loanword canonical in nl Bitcoin community · ⚠️ NOT "portemonnee". Shipped already uses "wallet" consistently. | +| Vault | Vault | noun · English loanword preferred · ⚠️ NOT "kluis". Shipped uses "Kluis"/"Multisig Kluis" — flag, swap to "Vault" / "Multisig Vault". | +| Watch-only | watch-only | adj · English loanword canonical in nl Bitcoin community · ⚠️ NOT "alleen-lezen" / "alleen kijken". | +| Hardware wallet | hardware wallet | noun, lowercase · English loanword canonical · ⚠️ NOT "hardwareportemonnee". | +| Seed | seed | noun · English loanword canonical in mainstream nl usage · Zeus nl + Electrum nl_NL keep "Seed". | +| Mnemonic | mnemonic / seed | noun · English loanword preferred · ⚠️ NOT "mnemonische zin" where avoidable. | +| Passphrase | wachtwoordzin | noun · ⚠️ NOT "Wachtwoord" (= password) · Bitcoin Core nl. Shipped uses "Passphrase" + "Wachtwoord" inconsistently — flag. | +| Public key | publieke sleutel | noun, lowercase · nl.wikipedia.org/wiki/Bitcoin. Shipped keeps English "public key". | +| Private key | privésleutel | noun, lowercase · Bitcoin Core nl + Electrum nl_NL. Shipped keeps English "private key". | +| WIF | WIF | acronym. | +| xpub | xpub | acronym, lowercase preferred. Shipped mixes "xpub" / "XPUB". | +| Descriptor | descriptor | noun, lowercase · Electrum nl_NL keeps "Descriptor". | +| Derivation path | derivatiepad | noun, lowercase · Electrum nl_NL. | +| Master fingerprint | master fingerprint | noun, lowercase · English loanword preferred · ⚠️ NOT "master-vingerafdruk". | +| BIP38 | BIP38 | acronym kept. | +| **_On-chain transactions_** | | | +| Transaction | transactie | noun, lowercase · Bitcoin Core nl + Electrum nl_NL. ⚠️ Shipped uses plural "Transacties" for singular — fix to singular "transactie". | +| Address | adres | noun, lowercase · Bitcoin Core nl + Electrum nl_NL. | +| Input | input / transactie-input | noun · short / explanatory · Electrum nl_NL keeps "Input". Shipped uses English "Inputs". | +| Output | output / transactie-output | noun · short / explanatory · Electrum nl_NL keeps "Output". ⚠️ NOT the recipient "Aan:" label. Shipped "Uitvoer" (= computer output) is wrong context. | +| UTXO | UTXO | acronym · gloss: niet-uitgegeven transactie-output. | +| Change | wisselgeld / wisseladres | noun · ⚠️ NOT verb "veranderen". Shipped `addresses.type_change` already correct ("Wisselgeld") but `cc.change` ships wrong "Veranderen" — fix · Bitcoin Core nl + Electrum nl_NL. | +| Hex | hex | noun, lowercase · Electrum nl_NL. ⚠️ NOT "hash". | +| Pending | in afwachting / wachtend | adj/state · Electrum nl_NL ("Wachtend"). | +| Unconfirmed | onbevestigd / niet-bevestigd | adj · Electrum nl_NL ("Onbevestigd"). | +| Confirmed | bevestigd | adj, singular · Bitcoin Core nl + Electrum nl_NL. Shipped only has plural noun "bevestigingen" (= confirmations) — flag, add adj form. | +| Mempool | mempool | noun, lowercase · English loanword canonical · Electrum nl_NL + nl.wikipedia.org/wiki/Bitcoin. | +| Broadcast | broadcast / broadcasten | noun / verb · English loanword preferred · ⚠️ shipped uses "Verzenden" (= send) which is acceptable in send-button contexts but "broadcast" is canonical for the protocol verb. | +| Block explorer | block explorer | noun, lowercase · English loanword preferred · ⚠️ NOT "blokverkenner". | +| Onchain | onchain | adj · English loanword preferred · ⚠️ NOT "op de blockchain". | +| Offchain | offchain | adj · English loanword preferred · ⚠️ NOT "buiten de blockchain". | +| **_Fees & fee bumping_** | | | +| Fee | fee / transactiekosten | noun · technical / mainstream · English "fee" widely used in nl wallets; Bitcoin Core nl uses "Vergoeding". Shipped keeps "Fee". | +| Fee Bump | fee bump / fee verhogen | noun · ⚠️ Shipped typo "Fee Bumb" — fix to "Fee Bump" · Electrum nl_NL "Vergoeding verhogen". | +| RBF | RBF | acronym · gloss: Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: Child-Pays-For-Parent. ⚠️ NOT a verb. | +| Speed Up | versnellen | verb · Electrum nl_NL. | +| **_Lightning_** | | | +| Invoice | factuur | noun, lowercase · Electrum nl_NL + Zeus nl. | +| Lightning Invoice | Lightning-factuur | noun · Electrum nl_NL. | +| Preimage | Pre-image | noun · English loanword canonical · Electrum nl_NL + Zeus nl keep English term. | +| Payment | betaling | noun, lowercase · ⚠️ NOT verb "betalen" · nl.wikipedia.org/wiki/Lightning_Network. | +| Expired | verlopen | adj · Electrum nl_NL. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | medeondertekenaar | noun · ⚠️ NOT "mede-eigenaar" (co-owner) · Electrum nl_NL. | +| Quorum | quorum / handtekeningdrempel | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym. | +| Provide signature | handtekening verstrekken / transactie ondertekenen | verb · generic / specific. Shipped "Geef een handtekening" acceptable. | +| BIP47 / Payment Code | BIP47 / Payment Code | acronym kept; English loanword "Payment Code" preferred · ⚠️ NOT "betaalcode". | +| Notification transaction | notificatietransactie | noun · BIP47-specific. | +| SilentPayment | Silent Payments / stille betalingen | protocol name kept English (plural); explanatory "stille betalingen" if needed. | +| **_Coin control_** | | | +| Coin Control | Coin Control | noun · English loanword canonical · ⚠️ NOT "muntbeheer". Shipped already keeps English. | +| Frozen | bevroren | adj · ⚠️ NOT verb "bevriezen" · Electrum nl_NL + Zeus nl. Shipped "Bevriezen" is the verb — flag, fix for state labels. | +| **_Security & storage_** | | | +| Encrypted storage | versleutelde opslag | noun, lowercase · ⚠️ NOT Title Case · Electrum nl_NL. | +| Plausible Deniability | plausibele ontkenning | noun, lowercase · ⚠️ NOT Title Case. Shipped matches. | +| Biometrics | biometrische beveiliging / biometrie | noun, lowercase. Shipped uses long form. | +| Passcode | toegangscode | noun · ⚠️ NOT "wachtwoord" (= password). | +| **_Backup, import & UX_** | | | +| Backup | back-up / back-up maken | noun / verb · Electrum nl_NL ("Back-up"). Shipped "Exporteren / back-up maken". | +| Restore | herstellen / herstel | verb / noun · Bitcoin Core nl + Electrum nl_NL. | +| Import | importeren / import | verb / noun · Electrum nl_NL. | +| Voucher | voucher | noun · ⚠️ Shipped typo "Atze.co" — fix to "Azte.co". | +| Redeem | inwisselen / verzilveren | verb · ⚠️ NOT "kopen" (buy) / NOT "overdragen" (transfer). Shipped uses both forms · Cake nl. | +| Send | verzenden / versturen | verb. Shipped "Verstuur". | +| Receive | ontvangen | verb. Shipped imperative "Ontvang". | +| Settings | instellingen | noun, lowercase. | +| Confirm | bevestigen / bevestiging | verb / noun. Shipped imperative "Bevestig". | +| QR Code | QR-code | noun · Bitcoin Core nl + Electrum nl_NL. | +| Clipboard | klembord | noun, lowercase · Bitcoin Core nl + Electrum nl_NL. | +| Memo | memo | noun, lowercase · Electrum nl_NL. | +| Description | omschrijving / beschrijving | noun, lowercase · Electrum nl_NL ("Beschrijving"). Shipped "Omschrijving". | +| Label | label | noun, lowercase · Bitcoin Core nl + Electrum nl_NL. ⚠️ Shipped "labelen" is the verb — flag, fix for noun contexts. | diff --git a/loc/vocabulary/pcm.md b/loc/vocabulary/pcm.md new file mode 100644 index 00000000000..613c6ec6a47 --- /dev/null +++ b/loc/vocabulary/pcm.md @@ -0,0 +1,96 @@ +# Nigerian Pidgin translation vocabulary (`pcm.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | — | +| Lightning | Lightning | Brand · stays English. | +| Electrum | Electrum | — | +| LNDhub | LNDhub | Brand · stays English. | +| LND | LND | Brand · stays English. | +| LNURL | LNURL | Brand · stays English. | +| Tor | Tor | Brand · stays English. | +| Orbot | Orbot | Brand · stays English. | +| GroundControl | GroundControl | Brand · stays English. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | English-passthrough · per pcm.wikipedia.org "Bitcoin moni". | +| sats | sats | English-passthrough. | +| sat/vByte | sat/vByte | English-passthrough · unit convention. | +| vByte | vByte | English-passthrough · unit convention. | +| **_Wallet, keys & seeds_** | | | +| Wallet | wallet | English-passthrough. | +| Vault | safe | ⚠️ NOT a brand · Pidgin paraphrase, no upstream · "safe" = strongbox borrowing. | +| Watch-only | watch-only | ⚠️ NOT "view mode" · English-passthrough. | +| Hardware wallet | hardware wallet | English-passthrough. | +| Seed | seed / recovery phrase | English-passthrough · compact / explanatory. | +| Mnemonic | mnemonic / recovery phrase | English-passthrough · technical / mainstream. | +| Passphrase | passphrase | ⚠️ NOT same as "password" · English-passthrough, distinct from shipped "password". | +| Public key | public key | English-passthrough. | +| Private key | private key | English-passthrough. | +| WIF | WIF | Acronym kept. | +| xpub | xpub | Acronym kept, lowercase per convention. | +| Descriptor | descriptor | English-passthrough. | +| Derivation path | derivation path | English-passthrough. | +| Master fingerprint | master fingerprint | English-passthrough. | +| BIP38 | BIP38 | Acronym kept · ⚠️ NOT a verb. | +| **_On-chain transactions_** | | | +| Transaction | transaction | English-passthrough. | +| Address | address | English-passthrough. | +| Input | input | English-passthrough. | +| Output | output | ⚠️ NOT the recipient label "To:" · English-passthrough. | +| UTXO | UTXO | Acronym kept. | +| Change | change wey remain | ⚠️ NOT the verb "to change" · Pidgin paraphrase, no upstream. | +| Hex | hex | English-passthrough · ⚠️ NOT "hash". | +| Pending | pend | Shortened form, lowercase · matches shipped pcm.json. | +| Unconfirmed | e never confam | Pidgin paraphrase pairs with shipped "Confam"; "never" = not yet. | +| Confirmed | don confam | Pidgin paraphrase · "don" = perfective aspect (already), pairs with shipped "Confam". | +| Mempool | mempool | English-passthrough. | +| Broadcast | broadcast | English-passthrough. | +| Block explorer | block explorer | English-passthrough. | +| Onchain | onchain | English-passthrough. | +| Offchain | offchain | English-passthrough. | +| **_Fees & fee bumping_** | | | +| Fee | fee | English-passthrough. | +| Fee Bump | fee bump | English-passthrough. | +| RBF | RBF | Acronym kept. | +| CPFP | CPFP | Acronym kept · ⚠️ NOT a verb. | +| Speed Up | sharp am up | Pidgin paraphrase, no upstream · "sharp" = fast/quick in Naija. | +| **_Lightning_** | | | +| Invoice | invoice | English-passthrough. | +| Lightning Invoice | Lightning invoice | English-passthrough. | +| Preimage | preimage | English-passthrough · math term. | +| Payment | payment | ⚠️ NOT the verb "to pay" · English-passthrough, noun. | +| Expired | kalas | Pidgin idiom "finished" · matches shipped pcm.json. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | co-signer | ⚠️ NOT "co-owner" · English-passthrough. | +| Quorum | quorum | English-passthrough. | +| PSBT | Partially Signed Bitcoin Transaction | Full expansion · matches shipped pcm.json. | +| Provide signature | sign am | Pidgin paraphrase, no upstream · "am" = it/object pronoun. | +| BIP47 / Payment Code | BIP47 / payment code | Acronym + English-passthrough noun. | +| Notification transaction | notification transaction | English-passthrough. | +| SilentPayment | Silent Payments | Brand · stays English (note plural). | +| **_Coin control_** | | | +| Coin Control | coin control | English-passthrough · lowercase. | +| Frozen | dem freeze am | ⚠️ NOT verb "to freeze" · Pidgin paraphrase, no upstream · state form. | +| **_Security & storage_** | | | +| Encrypted storage | encrypted storage | English-passthrough · lowercase. | +| Plausible Deniability | plausible deniability | English-passthrough · lowercase. | +| Biometrics | biometrics | English-passthrough. | +| Passcode | device code / unlock code | ⚠️ NOT app password · shipped `password` collides with app "password"; flag for fix. | +| **_Backup, import & UX_** | | | +| Backup | backup | English-passthrough · noun / verb. | +| Restore | restore | English-passthrough · verb / noun. | +| Import | import | English-passthrough · verb / noun. | +| Voucher | voucher | English-passthrough · Azte.co. | +| Redeem | cash am | ⚠️ NOT "buy to wallet" · Pidgin paraphrase, no upstream · "cash am" = cash it in. | +| Send | push am | Pidgin idiom "push it", lowercase · matches shipped pcm.json. | +| Receive | receive | English-passthrough. | +| Settings | settings | English-passthrough. | +| Confirm | confam | Pidgin spelling, lowercase · matches shipped pcm.json. | +| QR Code | QR code | English-passthrough. | +| Clipboard | clipboard | English-passthrough. | +| Memo | memo | English-passthrough · sender note. | +| Description | description | English-passthrough · invoice label. | +| Label | label | English-passthrough · noun. | diff --git a/loc/vocabulary/pl.md b/loc/vocabulary/pl.md new file mode 100644 index 00000000000..5922991b1c3 --- /dev/null +++ b/loc/vocabulary/pl.md @@ -0,0 +1,96 @@ +# Polish translation vocabulary (`pl.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · pl.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand · pl.wikipedia.org/wiki/Lightning_Network | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker · pl.wikipedia.org/wiki/Bitcoin (capital `Bitcoin` for network, lowercase `bitcoin` for the unit). | +| sats | satoshi | noun, lowercase · pl.wikipedia.org/wiki/Bitcoin | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. | +| vByte | vByte | technical unit; Latin kept. | +| **_Wallet, keys & seeds_** | | | +| Wallet | portfel | noun, lowercase. | +| Vault | skarbiec | noun · ⚠️ NOT Latin "Vault"; `skarbiec` = strongbox/safe · matches shipped `multisig_vault` "Skarbiec wielopodpisowy". | +| Watch-only | tylko do odczytu / portfel tylko do odczytu | adj · short / full · Bitcoin Core pl + Zeus pl + Cake pl | +| Hardware wallet | portfel sprzętowy | noun, lowercase · Bitcoin Core pl + Cake pl + Bisq pl | +| Seed | seed / fraza odzyskiwania | noun · technical / mainstream; shipped UI uses `Seed`/`fraza seed`. | +| Mnemonic | fraza mnemoniczna / fraza seed | noun · technical / mainstream · Cake pl. | +| Passphrase | fraza dostępu | noun · ⚠️ NOT `Hasło` (= password) · distinct word required; matches Trezor passphrase convention. | +| Public key | klucz publiczny | noun, lowercase. | +| Private key | klucz prywatny | noun, lowercase. | +| WIF | WIF | acronym · gloss: format importu portfela. | +| xpub | xpub | acronym, lowercase preferred (shipped string uses `XPUB`). | +| Descriptor | deskryptor | noun, lowercase · Bitcoin Core pl + Bisq pl | +| Derivation path | ścieżka derywacji / ścieżka pochodna | noun · technical / Bitcoin Core form · Bisq pl + Bitcoin Core pl | +| Master fingerprint | główny odcisk palca / odcisk palca klucza głównego | noun, lowercase · short / explanatory · Bitcoin Core pl + Bisq pl + Zeus pl | +| BIP38 | BIP38 | acronym kept · gloss: zaszyfrowany hasłem klucz prywatny. | +| **_On-chain transactions_** | | | +| Transaction | transakcja | noun, lowercase. | +| Address | adres | noun, lowercase · Bitcoin Core pl + Bisq pl | +| Input | wejście / wejście transakcji | noun · short / full · Bitcoin Core pl + Electrum pl + Bisq pl. Shipped UI plural: `Wejścia`. | +| Output | wyjście / wyjście transakcji | noun · short / full · ⚠️ NOT the UI recipient label "Do:" · Bitcoin Core pl + Electrum pl + Bisq pl. Shipped UI plural: `Wyjścia`. | +| UTXO | UTXO | acronym · gloss: niewydane wyjście transakcji. | +| Change | reszta / adres reszty | noun · ⚠️ NOT verb "zmienić" · `reszta` = leftover output; `adres reszty` for change-address field · Bitcoin Core pl + Electrum pl + Bisq pl + Cake pl | +| Hex | hex / postać szesnastkowa | noun · short / explanatory · ⚠️ NOT "hash" / NOT "dane transakcji" · Bitcoin Core pl + Bisq pl | +| Pending | oczekująca / w toku | adj/state · feminine-agreement / Bitcoin Core form · Bitcoin Core pl | +| Unconfirmed | niepotwierdzona / niepotwierdzone | adj · feminine / neuter agreement · Bitcoin Core pl + Bisq pl | +| Confirmed | potwierdzona / potwierdzone | adj · feminine / neuter agreement · Bitcoin Core pl + Bisq pl | +| Mempool | mempool | noun, lowercase · Bitcoin Core pl + Electrum pl + Bisq pl | +| Broadcast | rozgłoś / rozgłoszenie | verb / noun · UI button vs status · Bitcoin Core pl. Shipped UI: `Rozgłoś` button, `Rozgłoszenie` status. | +| Block explorer | eksplorator bloków | noun, lowercase. | +| Onchain | onchain / w łańcuchu bloków | adj · compact (chip) / explanatory (body) · shipped UI: `on-chain` form also used. | +| Offchain | offchain / poza łańcuchem bloków | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | opłata | noun, lowercase · Bitcoin Core pl + Bisq pl + Zeus pl | +| Fee Bump | zwiększenie opłaty | noun · shipped UI label `Zezwól na zwiększanie opłat` keeps verb-phrase form for the toggle. | +| RBF | RBF | acronym · gloss: zastąpienie opłatą / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: dziecko płaci za rodzica · ⚠️ NOT a verb. | +| Speed Up | zwiększ opłatę / przyspiesz | verb · UI-clear / generic. | +| **_Lightning_** | | | +| Invoice | faktura | noun · Bitcoin Core pl + Zeus pl + Cake pl. | +| Lightning Invoice | faktura Lightning | noun · brand `Lightning` + localised noun · shipped UI uses `Faktura Lightning`. | +| Preimage | preimage / obraz pierwotny | noun · technical / explanatory · shipped UI uses `Obraz pierwotny`; Zeus pl keeps `Preimage`. | +| Payment | płatność | noun · ⚠️ NOT verb "Zapłać" · Bitcoin Core pl + Cake pl + Zeus pl | +| Expired | przeterminowana / wygasła | adj · feminine-agreement state forms · ⚠️ NOT verb · Bisq pl + Cake pl | +| **_Multisig & advanced addressing_** | | | +| Co-signer | współsygnatariusz / współpodpisujący | noun · ⚠️ NOT "współwłaściciel" (co-owner) · Bitcoin Core pl + Bisq pl. Shipped UI uses `współsygnatariusz`. | +| Quorum | kworum / próg podpisów | noun · canonical / UI-clear · Bitcoin Core pl | +| PSBT | PSBT | acronym · gloss: częściowo podpisana transakcja Bitcoin. | +| Provide signature | podaj podpis / podpisz transakcję | verb · generic / specific. Shipped UI: `Podaj podpis`. | +| BIP47 / Payment Code | BIP47 / kod płatności | acronym kept; "Payment Code" → "kod płatności" · shipped UI uses `Kod płatności`. | +| Notification transaction | transakcja powiadomienia | noun · BIP47-specific · shipped UI uses this form. | +| SilentPayment | Silent Payments / ciche płatności | protocol name kept English (plural); explanatory `ciche płatności` if needed · shipped UI uses `SilentPayment`. | +| **_Coin control_** | | | +| Coin Control | kontrola UTXO / kontrola monet | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case · Bitcoin Core pl + Electrum pl + Bisq pl. | +| Frozen | zamrożona / zamrożone | adj · feminine / neuter agreement · ⚠️ NOT verb "zamrozić" · Electrum pl + Bisq pl + Cake pl | +| **_Security & storage_** | | | +| Encrypted storage | zaszyfrowane dane / szyfrowanie danych | noun, lowercase · ⚠️ NOT Title Case · shipped UI uses `Włącz szyfrowanie danych` for the toggle. | +| Plausible Deniability | wiarygodna zaprzeczalność / wiarygodne zaprzeczenie | noun, lowercase · ⚠️ NOT Title Case · shipped UI uses `Wiarygodna zaprzeczalność`. | +| Biometrics | biometria | noun, lowercase · Zeus pl. | +| Passcode | kod dostępu | noun · ⚠️ NOT `Hasło` (= password) · distinct word; shipped UI consistent. | +| **_Backup, import & UX_** | | | +| Backup | kopia zapasowa / wykonaj kopię zapasową | noun / verb · Bitcoin Core pl + Electrum pl + Bisq pl. Shipped UI: `Eksport/Kopia zapasowa`. | +| Restore | przywróć / przywracanie | verb / noun · Bitcoin Core pl + Electrum pl + Zeus pl. Shipped UI uses `odtworzyć` for the verb. | +| Import | importuj / import | verb / noun · Electrum pl + Bisq pl. | +| Voucher | voucher | noun, lowercase · shipped UI consistent. | +| Redeem | zrealizuj / aktywuj | verb · ⚠️ NOT "Kup" / NOT "Przelej" · activate/cash-in sense · Cake pl · shipped uses "Odbierz" — drift, see vocabulary.md TODOs | +| Send | wyślij | verb · Electrum pl + Bisq pl + Zeus pl. | +| Receive | odbierz / otrzymaj | verb · Electrum pl + Bisq pl uses `Odbierz`; shipped UI uses `Otrzymaj`. | +| Settings | ustawienia | noun, lowercase. | +| Confirm | potwierdź / potwierdzenie | verb / noun · also "confirmations" = block-count noun. | +| QR Code | kod QR | noun, lowercase · Bitcoin Core pl + Bisq pl | +| Clipboard | schowek | noun, lowercase · Electrum pl + Bisq pl. | +| Memo | notatka | noun, lowercase · Bisq pl. | +| Description | opis | noun, lowercase · Electrum pl + Bisq pl + Zeus pl | +| Label | etykieta | noun, lowercase · Bitcoin Core pl + Electrum pl + Bisq pl. | diff --git a/loc/vocabulary/pt_br.md b/loc/vocabulary/pt_br.md new file mode 100644 index 00000000000..be266fe4276 --- /dev/null +++ b/loc/vocabulary/pt_br.md @@ -0,0 +1,96 @@ +# Portuguese, Brazil translation vocabulary (`pt_br.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · pt.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand · pt.wikipedia.org/wiki/Lightning_Network | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker; lowercase as unit. | +| sats | sats | noun, lowercase. | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. Body text may expand to `satoshi por vByte`. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | carteira | noun, lowercase. | +| Vault | cofre | noun · safe/strongbox sense. Avoid Latin "Vault". App ships "Cofre Multisig" for multisig vault label. | +| Watch-only | somente leitura / somente para assistir | adj · ⚠️ NOT a brand · `somente leitura` per Zeus pt_BR + Cake pt_BR; `somente para assistir` is the form shipped today. | +| Hardware wallet | carteira hardware | noun, lowercase · Cake pt_BR uses `carteira de hardware`. | +| Seed | seed / frase de recuperação | noun · technical / mainstream. App ships loanword `seed`. | +| Mnemonic | frase mnemônica / frase de recuperação | noun · technical / mainstream. | +| Passphrase | frase secreta / frase de segurança | noun · ⚠️ NOT `senha` (= password) · `frase de segurança` per Bitcoin Core pt_BR. | +| Public key | chave pública | noun, lowercase · Bitcoin Core pt_BR + Zeus pt_BR + Cake pt_BR. | +| Private key | chave privada | noun, lowercase · Cake pt_BR. | +| WIF | WIF | acronym · gloss: formato de importação de carteira. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | descritor | noun, lowercase · Bitcoin Core pt_BR (`descritores`). | +| Derivation path | caminho de derivação | noun · Electrum pt_BR + Cake pt_BR. | +| Master fingerprint | impressão digital mestra / fingerprint mestra | noun · ⚠️ shipped `Fingerprint Soberana` mixes English noun with wrong adjective (soberana = sovereign). | +| BIP38 | BIP38 | acronym kept · gloss: chave privada protegida por senha (BIP38). | +| **_On-chain transactions_** | | | +| Transaction | transação | noun, lowercase · Bitcoin Core pt_BR. | +| Address | endereço | noun, lowercase. | +| Input | entrada | noun, lowercase · Bitcoin Core pt_BR + Electrum pt_BR. App ships `Entrada` / `Entradas` in tx details. | +| Output | saída | noun, lowercase · Electrum pt_BR. App ships `Saída` / `Saídas` in tx details. ⚠️ NOT the UI label "Para:". | +| UTXO | UTXO | acronym · gloss: saída de transação não gasta. | +| Change | troco | noun, lowercase · ⚠️ NOT verb "trocar" · Bitcoin Core pt_BR + Electrum pt_BR. | +| Hex | hex / hexadecimal | noun · short / explanatory · ⚠️ NOT "hash" and NOT "dados da transação" · shipped `Hash` is wrong; fix to `Hex` · pt.wikipedia.org/wiki/Sistema_de_numeração_hexadecimal. | +| Pending | pendente | adj/state · Bitcoin Core pt_BR. Avoid noun "pendência". | +| Unconfirmed | não confirmada / não confirmado | adj · feminine (`transação`) / masculine agreement · Bitcoin Core pt_BR + Electrum pt_BR. | +| Confirmed | confirmada / confirmado | adj · feminine / masculine agreement · Bitcoin Core pt_BR. | +| Mempool | mempool | noun, loanword · Electrum pt_BR. | +| Broadcast | transmitir / transmissão | verb / noun · Bitcoin Core pt_BR + Electrum pt_BR. | +| Block explorer | explorador de blocos | noun, lowercase · Electrum pt_BR. | +| Onchain | onchain / na rede principal | adj · compact (chip) / explanatory (body). | +| Offchain | offchain / fora da rede principal | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | taxa | noun, lowercase. | +| Fee Bump | aumento de taxa / aumentar taxa | noun / verb · Cake pt_BR (`Aumentar taxa`). | +| RBF | RBF | acronym · gloss: substituir pela taxa / Replace-By-Fee · Electrum pt_BR. | +| CPFP | CPFP | acronym · gloss: filho paga pelo pai / Child Pays For Parent · Electrum pt_BR. ⚠️ NOT "Criar". | +| Speed Up | acelerar / aumentar taxa | verb · button label. | +| **_Lightning_** | | | +| Invoice | fatura | noun, lowercase · Electrum pt_BR + Zeus pt_BR + Cake pt_BR. | +| Lightning Invoice | fatura Lightning | noun · brand kept Latin. | +| Preimage | pré-imagem | noun, lowercase · Electrum pt_BR + Zeus pt_BR. Shipped `Imagem prévia` is a literal calque; prefer the cryptographic term `pré-imagem`. | +| Payment | pagamento | noun, lowercase · ⚠️ NOT verb "Pagar" · Electrum pt_BR + Zeus pt_BR + Cake pt_BR. | +| Expired | expirada / expirado | adj · feminine (agreeing with `fatura`) / masculine · Electrum pt_BR + Cake pt_BR. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | cossignatário / coassinante | noun · ⚠️ NOT `coassinatura` (= co-signature, the artifact, not the person). Shipped uses `Coassinatura` for the signer role — wrong noun. | +| Quorum | quórum / mínimo de assinaturas | noun · canonical / UI-clear · ⚠️ shipped `Quantidade mínima {m} de máxima {n}` is semantically wrong (m and n are signers-required and total). Suggest `{m} de {n} (quórum)`. | +| PSBT | PSBT | acronym · gloss: transação Bitcoin parcialmente assinada. | +| Provide signature | fornecer assinatura / assinar transação | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / código de pagamento | acronym kept; `Payment Code` → `código de pagamento` (app ships in pt_br.json as `Código de pagamento`). | +| Notification transaction | transação de notificação | noun · BIP47-specific. | +| SilentPayment | Silent Payments / pagamentos silenciosos | protocol name kept English (plural); explanatory `pagamentos silenciosos` if needed. | +| **_Coin control_** | | | +| Coin Control | controle de moedas / controle de UTXO | noun, lowercase · mainstream / technical · ⚠️ NOT Title Case · Electrum pt_BR + Cake pt_BR. | +| Frozen | congelado / congelada | adj · masculine / feminine agreement · ⚠️ NOT verb "congelar" · Electrum pt_BR + Cake pt_BR. | +| **_Security & storage_** | | | +| Encrypted storage | armazenamento criptografado / criptografia de armazenamento | noun, lowercase · ⚠️ NOT Title Case. App ships both forms in different strings. | +| Plausible Deniability | negação plausível | noun, lowercase · ⚠️ NOT Title Case. | +| Biometrics | biometria | noun, lowercase · Cake pt_BR. | +| Passcode | código de acesso | noun · ⚠️ NOT `senha` (= password) — device-level unlock code. App `biometrics_fail` already uses `código de acesso`. | +| **_Backup, import & UX_** | | | +| Backup | backup / fazer backup | noun / verb · loanword widely used · Bitcoin Core pt_BR + Electrum pt_BR + Cake pt_BR. | +| Restore | restaurar / restauração | verb / noun · Bitcoin Core pt_BR + Cake pt_BR. | +| Import | importar / importação | verb / noun · Electrum pt_BR. | +| Voucher | voucher | noun, lowercase, loanword. | +| Redeem | resgatar | verb · Cake pt_BR (`Resgate` / `resgatado`). | +| Send | enviar | verb. | +| Receive | receber | verb. | +| Settings | configurações | noun, lowercase. | +| Confirm | confirmar / confirmação | verb / noun. `confirmações` (plural) = block confirmations. | +| QR Code | código QR / QR Code | noun · explanatory / brand-form · Electrum pt_BR + Cake pt_BR + Bitcoin Core pt_BR. App ships `QR Code`. | +| Clipboard | área de transferência | noun, lowercase · Electrum pt_BR + Bitcoin Core pt_BR + Cake pt_BR. | +| Memo | nota / memorando | noun, lowercase · sender note on outgoing tx. | +| Description | descrição | noun, lowercase · Cake pt_BR. | +| Label | rótulo / etiqueta | noun, lowercase · Bitcoin Core pt_BR + Electrum pt_BR + Cake pt_BR. | diff --git a/loc/vocabulary/pt_pt.md b/loc/vocabulary/pt_pt.md new file mode 100644 index 00000000000..493a0faf9f5 --- /dev/null +++ b/loc/vocabulary/pt_pt.md @@ -0,0 +1,96 @@ +# Portuguese, Portugal translation vocabulary (`pt_pt.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · pt.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand · pt.wikipedia.org/wiki/Lightning_Network | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker; lowercase as unit. | +| sats | sats | noun, lowercase. | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. Body text may expand to `satoshi por vByte` (app ships this in `send/create_satoshi_per_vbyte`). | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | carteira | noun, lowercase. Plural `carteiras`. | +| Vault | cofre | noun · safe/strongbox sense. App ships `Cofre Multiassinatura` for multisig vault label. Avoid Latin "Vault". | +| Watch-only | só de observação / carteira de visualização | adj · technical / mainstream · ⚠️ NOT a brand · `só de observação` per Bitcoin Core pt; `carteira de visualização` is the form shipped today. | +| Hardware wallet | carteira de hardware | noun, lowercase · Bitcoin Core pt + Electrum pt_PT. ⚠️ avoid Title Case `Carteira de Hardware`. | +| Seed | seed / semente | noun · technical loanword / mainstream · Electrum pt_PT uses `semente`. App ships `Seed` today. | +| Mnemonic | frase mnemónica / frase de recuperação | noun · technical / mainstream · PT-PT spelling `mnemónica` (Acordo Ortográfico 1990). | +| Passphrase | frase-passe / frase de segurança | noun · ⚠️ NOT `palavra-passe` (= password) and NOT `senha` · `frase-passe` per Electrum pt_PT; `frase de segurança` per Bitcoin Core pt. App ships English `Passphrase` today. | +| Public key | chave pública | noun, lowercase · Electrum pt_PT. | +| Private key | chave privada | noun, lowercase · Bitcoin Core pt + Electrum pt_PT. | +| WIF | WIF | acronym · gloss: formato de importação de carteira. | +| xpub | xpub | acronym, lowercase preferred. App ships uppercase `XPUB` in some screens. | +| Descriptor | descritor | noun, lowercase · Bitcoin Core pt (`descritores`). | +| Derivation path | caminho de derivação | noun · app ships `caminho de derivação` (lowercase in body, capitalised at start of label). | +| Master fingerprint | impressão digital mestra | noun · ⚠️ shipped `Master Fingerprint` is not localised; replace with `Impressão digital mestra` (PT-PT). | +| BIP38 | BIP38 | acronym kept · gloss: chave privada protegida por palavra-passe (BIP38). ⚠️ NOT a verb. | +| **_On-chain transactions_** | | | +| Transaction | transação | noun, lowercase · Bitcoin Core pt + Electrum pt_PT. App ships `Transação` (sentence-case). | +| Address | endereço | noun, lowercase · Bitcoin Core pt + Electrum pt_PT. | +| Input | entrada / entrada da transação | noun · short / full · ⚠️ shipped `Inputs` is English-passthrough; PT-PT form is `entrada` / `entradas`. | +| Output | saída / saída da transação | noun · short / full · ⚠️ shipped `Outputs` is English-passthrough; PT-PT form is `saída` / `saídas`. NOT the UI label "Para:" (separate string). | +| UTXO | UTXO | acronym · gloss: saída de transação não gasta. | +| Change | troco / endereço de troco | noun · ⚠️ NOT verb "trocar" · `troco` per Bitcoin Core pt + Electrum pt_PT. Use `endereço de troco` for the change-address field. | +| Hex | hex / hexadecimal | noun · short / explanatory · ⚠️ NOT "hash" and NOT "dados da transação". | +| Pending | pendente | adj/state · app ships `Pendente`. Avoid noun form `pendência`. | +| Unconfirmed | não confirmada / não confirmado | adj · feminine (agreeing with `transação`) / masculine · Bitcoin Core pt + Electrum pt_PT. | +| Confirmed | confirmada / confirmado | adj · feminine / masculine agreement · Bitcoin Core pt. | +| Mempool | mempool | noun, loanword kept · Bitcoin Core pt keeps `Mempool`. | +| Broadcast | transmitir / transmissão | verb / noun · Bitcoin Core pt + Electrum pt_PT. App ships `Difundir` / `Transmissão` in some screens — `transmitir` is the more established form. | +| Block explorer | explorador de blocos | noun, lowercase · app ships Title Case `Explorador de Blocos`; PT-PT form should be lowercase. | +| Onchain | onchain / na blockchain | adj · compact (chip) / explanatory (body). | +| Offchain | offchain / fora da blockchain | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | taxa | noun, lowercase · Bitcoin Core pt + Electrum pt_PT. App ships `Taxa` (sentence-case). | +| Fee Bump | aumento de taxa / permitir aumentar a taxa | noun · short / verb-phrase used in `send/details_adv_fee_bump`. | +| RBF | RBF | acronym · gloss: Substituição por Taxa / Replace by Fee. App ships `Substituição da Taxa` and `Replace by Fee` in `transactions/cancel_explain`. | +| CPFP | CPFP | acronym · gloss: Filho paga pelo pai / Child Pays For Parent. ⚠️ NOT a verb like "Criar". | +| Speed Up | aumentar a taxa | verb · UI label for RBF in tx detail. App ships `Aumentar taxa (RBF)`. | +| **_Lightning_** | | | +| Invoice | fatura | noun, lowercase · Electrum pt_PT. App ships `Fatura` (sentence-case). | +| Lightning Invoice | fatura Lightning | noun · brand kept + localised noun · app ships `Fatura Lightning`. | +| Preimage | pré-imagem | noun, lowercase · math/crypto term; app ships `Pré-imagem`. | +| Payment | pagamento | noun · Bitcoin Core pt. ⚠️ NOT verb `Pagar` — must be a noun. | +| Expired | expirada / expirado | adj · feminine (agreeing with `fatura`) / masculine state form · app ships `Expirado` / `Expirada`. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | co-signatário | noun, lowercase · ⚠️ NOT "co-proprietário" (co-owner). App ships `Co-signatário`. | +| Quorum | quórum | noun, lowercase · PT-PT accent · app ships `Quórum`. | +| PSBT | PSBT | acronym · Bitcoin Core pt keeps as-is. Gloss: transação Bitcoin parcialmente assinada. | +| Provide signature | fornecer assinatura / assinar transação | verb · generic / specific. App ships `Fornecer assinatura`. | +| BIP47 / Payment Code | BIP47 / código de pagamento | acronym kept; `Payment Code` → `código de pagamento` (lowercase). App ships `Código de Pagamento` (Title Case). | +| Notification transaction | transação de notificação | noun · BIP47-specific 0-value tx. | +| SilentPayment | Silent Payments / pagamentos silenciosos | protocol name kept English (plural); explanatory `pagamentos silenciosos` if needed. | +| **_Coin control_** | | | +| Coin Control | controlo de moedas / gestão de UTXO | noun, lowercase · technical / mainstream · Electrum pt_PT (`Controlo da moeda`) + Bitcoin Core pt. ⚠️ NOT Title Case. PT-PT uses `controlo` (BR uses `controle`). | +| Frozen | congelado / congelada | adj · masculine / feminine agreement · ⚠️ NOT verb "congelar" · Electrum pt_PT. App ships `Congelado` / `congeladas`. | +| **_Security & storage_** | | | +| Encrypted storage | encriptação de armazenamento | noun, lowercase · ⚠️ NOT Title Case. App ships `Ativar Encriptação de Armazenamento` as headline. PT-PT spelling `encriptação` (Electrum pt_PT). | +| Plausible Deniability | negação plausível | noun, lowercase · app ships sentence-case `Negação plausível` in body, `Negação Plausível` in headlines. | +| Biometrics | biometria | noun, lowercase · app ships `Biometria`. | +| Passcode | código de acesso | noun · ⚠️ NOT `palavra-passe` (= password) and NOT `senha` (BR form). App ships `senha` / `palavra-passe` ambiguously — needs disambiguation. | +| **_Backup, import & UX_** | | | +| Backup | cópia de segurança / fazer cópia de segurança | noun / verb · Bitcoin Core pt + Electrum pt_PT. App ships English loanword `Backup`; PT-PT idiomatic form is `cópia de segurança`. | +| Restore | restaurar / restauro | verb / noun · Bitcoin Core pt + Electrum pt_PT. App ships `Restaurar` / `recuperar`. | +| Import | importar / importação | verb / noun · app ships `Importar`. | +| Voucher | voucher | noun, lowercase · loanword (Azte.co). | +| Redeem | resgatar | verb · Bitcoin Core pt. App ships `Resgatar`. ⚠️ NOT "comprar para a carteira". | +| Send | enviar | verb · Bitcoin Core pt + Electrum pt_PT. | +| Receive | receber | verb · Bitcoin Core pt + Electrum pt_PT. | +| Settings | definições | noun, lowercase · PT-PT term (BR uses `configurações`). App ships `Definições`. | +| Confirm | confirmar / confirmação | verb / noun. Also "confirmations" → `confirmações` (plural). | +| QR Code | código QR | noun · Bitcoin Core pt + Electrum pt_PT (`Código QR`). | +| Clipboard | área de transferência | noun, lowercase · Bitcoin Core pt + Electrum pt_PT. App ships Title Case `Área de Transferência`. | +| Memo | nota / nota pessoal | noun, lowercase · app ships `Nota pessoal`. | +| Description | descrição | noun, lowercase · app ships `Descrição`. | +| Label | etiqueta | noun, lowercase · Bitcoin Core pt + Electrum pt_PT. | diff --git a/loc/vocabulary/ro.md b/loc/vocabulary/ro.md new file mode 100644 index 00000000000..c731cae3f1b --- /dev/null +++ b/loc/vocabulary/ro.md @@ -0,0 +1,96 @@ +# Romanian translation vocabulary (`ro.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / bitcoin | brand / unit · ro.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker · ro.wikipedia.org/wiki/Bitcoin | +| sats | sats / satoshi | compact / full · noun, lowercase · ticker kept Latin in ro.json `units.sats`. | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. Shipped `Satoshi per vByte` is the spelled-out form. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | portofel | noun, lowercase (singular). Plural `portofele` ships in list titles. | +| Vault | seif | noun · ro.json ships `Seif multisig`. ⚠️ NOT Latin "Vault". | +| Watch-only | doar citire / doar de văzut | adj · Bitcoin Core ro / Electrum ro. ⚠️ NOT a wallet noun. | +| Hardware wallet | portofel hardware | noun, lowercase · Bitcoin Core ro | +| Seed | seed / frază de recuperare | noun · technical / mainstream. ro.json ships `Seed`. | +| Mnemonic | frază mnemonică / frază de recuperare | noun, lowercase · technical / mainstream · Bitcoin Core ro | +| Passphrase | frază de acces | noun · ⚠️ distinct from `parolă` (password) · Bitcoin Core ro + Electrum ro | +| Public key | cheie publică | noun, lowercase · ro.wikipedia.org/wiki/Bitcoin | +| Private key | cheie privată | noun, lowercase · ro.wikipedia.org/wiki/Bitcoin | +| WIF | WIF | acronym. | +| xpub | xpub | acronym, lowercase preferred. ro.json ships `XPUB`. | +| Descriptor | descriptor | noun, lowercase. | +| Derivation path | cale de derivare | noun, lowercase · ro.json ships `calea de derivare`. | +| Master fingerprint | amprentă principală | noun, lowercase · ro.json ships `Amprenta principală`. | +| BIP38 | BIP38 | acronym kept · gloss: parolă BIP38 pentru decriptare. | +| **_On-chain transactions_** | | | +| Transaction | tranzacție | noun, lowercase. | +| Address | adresă | noun, lowercase. | +| Input | intrare / intrare tranzacție | noun · short / full. ro.json currently keeps `Input` Latin. | +| Output | ieșire / ieșire tranzacție | noun · short / full. ⚠️ NOT "Către" (recipient UI label). ro.json currently keeps `Output` Latin. | +| UTXO | UTXO | acronym · gloss: ieșire de tranzacție necheltuită. | +| Change | rest / adresă de rest | noun · ⚠️ NOT verb "a schimba". `rest` = leftover; `adresă de rest` for change-address · ro.wikipedia.org/wiki/Bitcoin. ro.json ships `Schimb` (semantically wrong — should be `Rest`). | +| Hex | hex / date hex | noun · short / explanatory · ⚠️ NOT "hash". | +| Pending | în așteptare | adj/state · Avoid noun "așteptare" alone. | +| Unconfirmed | neconfirmată / neconfirmat | adj · fem / masc agreement form. | +| Confirmed | confirmată / confirmat | adj · fem / masc agreement · ⚠️ NOT `confirmări` (noun plural "confirmations") · ro.json `transactions.confirmations_lowercase` and adjective form must not collide. | +| Mempool | mempool | noun, lowercase · Bitcoin Core ro uses `Pool Memorie`; Electrum/community use `mempool`. | +| Broadcast | difuzează / difuzare | verb / noun · ro.json. | +| Block explorer | explorator de blocuri | noun, lowercase · ro.json keeps Latin `Block explorer`. | +| Onchain | on-chain / pe blockchain | adj · compact (chip) / explanatory (body). | +| Offchain | off-chain / în afara blockchainului | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | comision | noun, lowercase · Bitcoin Core ro + Electrum ro + ro.wikipedia.org/wiki/Bitcoin | +| Fee Bump | creșterea comisionului | noun · ro.json. | +| RBF | RBF | acronym · gloss: înlocuiește prin comision / Replace-By-Fee · ro.json `transactions.cancel_explain`. | +| CPFP | CPFP | acronym · gloss: copilul plătește pentru părinte · ro.json. ⚠️ NOT a verb. | +| Speed Up | accelerează / crește comisionul | verb · ro.json ships `Crește comisionul`. | +| **_Lightning_** | | | +| Invoice | factură | noun, lowercase · Electrum ro + ro.json. | +| Lightning Invoice | factură Lightning | noun · ro.json ships `Factură Lightning`. | +| Preimage | preimagine | noun · math sense (cf. `imagine`/`preimagine` in matematică ro). | +| Payment | plată | noun · ⚠️ NOT verb "a plăti" · Electrum ro. | +| Expired | expirat / expirată | adj · masc / fem · ro.json. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | cosemnatar | noun · ⚠️ NOT "coproprietar" · Electrum ro. | +| Quorum | cvorum / prag de semnături | noun · canonical / UI-clear · ro.json. | +| PSBT | PSBT | acronym. | +| Provide signature | furnizează semnătură / semnează tranzacția | verb · generic / specific · ro.json. | +| BIP47 / Payment Code | BIP47 / cod de plată | acronym kept; "Payment Code" → `cod de plată`. | +| Notification transaction | tranzacție de notificare | noun · BIP47-specific. | +| SilentPayment | Silent Payments / plăți silențioase | protocol name kept English (plural); explanatory `plăți silențioase` if needed. | +| **_Coin control_** | | | +| Coin Control | controlul UTXO-urilor / controlul monedelor | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case. ro.json ships `Controlul monedelor`. | +| Frozen | înghețat / înghețată | adj · masc / fem · ⚠️ NOT verb "îngheață". ro.json `cc.freeze`/`cc.freezeLabel` ships verb `Îngheață` (button label OK; state should be `Înghețat`). | +| **_Security & storage_** | | | +| Encrypted storage | spațiu de stocare criptat | noun, lowercase · ⚠️ NOT Title Case · ro.json `_.storage_is_encrypted`. | +| Plausible Deniability | negare plauzibilă | noun, lowercase · ro.json. | +| Biometrics | biometrie | noun, lowercase · ro.json ships `Biometrici` (plural; standard form is `biometrie`). | +| Passcode | cod de acces | noun · ⚠️ NOT `parolă` (= password) · ro.json `settings.password` ships `Parolă` for the app password. | +| **_Backup, import & UX_** | | | +| Backup | copie de rezervă / export | noun · native / alt accepted (ro.json ships `Exportă/Backup`) · Electrum ro. Verb form: `face copie de rezervă`. | +| Restore | restaurează / restaurare | verb / noun · Electrum ro + Bitcoin Core ro. | +| Import | importă / import | verb / noun · ro.json. | +| Voucher | voucher | noun, lowercase · ro.json. | +| Redeem | revendică / activează | verb · ⚠️ NOT "cumpără" · ro.json ships `Revendică`. | +| Send | trimite | verb · ro.json. | +| Receive | primește | verb · ro.json. | +| Settings | setări | noun, lowercase · ro.json. | +| Confirm | confirmă / confirmare | verb / noun · ro.json. | +| QR Code | cod QR | noun · Bitcoin Core ro + Electrum ro. | +| Clipboard | clipboard | noun, lowercase · ro.json keeps Latin (no widespread native equivalent). | +| Memo | notă / memo | noun, lowercase · native / English-passthrough. ro.json ships `Memo`. | +| Description | descriere | noun, lowercase · ro.json. | +| Label | etichetă | noun, lowercase · Bitcoin Core ro + Electrum ro. | diff --git a/loc/vocabulary/ru.md b/loc/vocabulary/ru.md new file mode 100644 index 00000000000..dd914b21920 --- /dev/null +++ b/loc/vocabulary/ru.md @@ -0,0 +1,96 @@ +# Russian translation vocabulary (`ru.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / биткойн | brand kept Latin; `биткойн` in explanatory text · ⚠️ when compounding with Cyrillic noun, use hyphen: `Bitcoin-кошелёк`, NOT `Bitcoin кошелёк` · ru.wikipedia.org/wiki/Биткойн | +| Lightning | Lightning | brand · ⚠️ when compounding with Cyrillic noun, use hyphen: `Lightning-кошелёк`, NOT `Lightning кошелёк` · ru.wikipedia.org/wiki/Lightning_Network | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand · Electrum ru keeps as-is. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | биткойн / BTC | noun unit + ticker · ru.wikipedia.org/wiki/Биткойн | +| sats / satoshis | сатоши | noun, lowercase, plural-invariant · ru.wikipedia.org/wiki/Биткойн | +| sat/vByte | sat/vByte / сат/вБайт | technical unit; Latin form in chips, Cyrillic abbrev in body text (both shipped in `ru.json`). | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | кошелёк | noun, lowercase · Electrum ru + Bitcoin Core ru | +| Vault | сейф / хранилище | noun; `сейф` for safe/strongbox sense; `хранилище` used in shipped `multisig.multisig_vault`. Avoid Latin "Vault". | +| Watch-only | только для просмотра | ⚠️ NOT "режим просмотра" · adj · Electrum ru + Bitcoin Core ru. | +| Hardware wallet | аппаратный кошелёк | noun, lowercase · Trezor ru | +| Seed | сид-фраза / фраза восстановления | noun · technical / mainstream. | +| Mnemonic | мнемоническая фраза / фраза восстановления | noun · technical / mainstream · Electrum ru | +| Passphrase | кодовая фраза | noun · ⚠️ NOT "пароль" (= password) · Electrum ru + Trezor ru | +| Public key | публичный ключ | noun, lowercase · Electrum ru + Cake ru | +| Private key | приватный ключ / закрытый ключ | noun, lowercase · `приватный` mainstream / `закрытый` Bitcoin-Core-style · Electrum ru | +| WIF | WIF | acronym · gloss: формат импорта кошелька. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | дескриптор | noun, lowercase · Bitcoin Core ru | +| Derivation path | путь деривации / путь вывода | noun · transliterated / canonical · Electrum ru | +| Master fingerprint | отпечаток мастер-ключа / мастер-отпечаток | noun, lowercase · Electrum ru | +| BIP38 | BIP38 | acronym kept · gloss: пароль BIP38 для расшифрования приватного ключа. ⚠️ NOT a verb / NOT "пароль" alone. | +| **_On-chain transactions_** | | | +| Transaction | транзакция | noun, lowercase · Electrum ru + Bitcoin Core ru | +| Address | адрес | noun, lowercase · Electrum ru + Bitcoin Core ru | +| Input | вход / вход транзакции | noun · short / full · Bitcoin Core ru | +| Output | выход / выход транзакции | noun · short / full · ⚠️ NOT "Кому" (that's UI recipient label). | +| UTXO | UTXO | acronym · gloss: непотраченный выход транзакции. | +| Change | сдача / адрес сдачи | noun · ⚠️ NOT verb "изменить". `сдача` = leftover coin; `адрес сдачи` for change-address field · Electrum ru + Bitcoin Core ru | +| Hex | hex / hex-данные | noun · short / explanatory · ⚠️ NOT "hash" / NOT "данные транзакции". Shipped `HEX` (uppercase) in some strings — body text uses lowercase. | +| Pending | в процессе / в ожидании | adj/state · button vs body · Electrum ru. Avoid noun "ожидание". | +| Unconfirmed | неподтверждённая / не подтверждено | adj · feminine-agreement / state form · Electrum ru | +| Confirmed | подтверждена / подтверждено | adj · feminine-agreement / state form · Bitcoin Core ru | +| Mempool | мемпул | noun, lowercase · Electrum ru | +| Broadcast | отправить в сеть / транслировать | verb · UI-clear / technical. Noun form: передача / трансляция. | +| Block explorer | блокчейн-обозреватель / обозреватель блоков | noun, lowercase · shipped form / Electrum-ru form | +| Onchain | он-чейн / в цепочке | adj · compact (chip) / explanatory (body) — shipped uses `В цепочке`. | +| Offchain | оф-чейн / вне цепочки | adj · compact (chip) / explanatory (body) — shipped uses `Вне цепочки`. | +| **_Fees & fee bumping_** | | | +| Fee | комиссия | noun, lowercase · Electrum ru + Bitcoin Core ru + Bisq ru | +| Fee Bump | повышение комиссии | noun · shipped `Разрешить повышение комиссии`. | +| RBF | RBF | acronym · gloss: replace-by-fee / замена по комиссии. | +| CPFP | CPFP | acronym · gloss: потомок платит за родителя · Electrum ru. ⚠️ NOT a verb "Создать". | +| Speed Up | ускорить / повысить комиссию | verb · short / explanatory. Shipped: `Повысить комиссию (RBF)`. | +| **_Lightning_** | | | +| Invoice | инвойс / счёт | noun · technical / mainstream · Zeus ru. | +| Lightning Invoice | Lightning инвойс / инвойс Lightning | noun · brand stays English; word order varies in shipped strings. | +| Preimage | прообраз / преимидж | noun · math term / transliteration · Electrum ru uses `прообраз`. Shipped: `Преимидж`. | +| Payment | платёж | noun · ⚠️ NOT verb "оплатить" · Electrum ru + Zeus ru | +| Expired | просрочен / срок действия истёк | adj · short / explanatory · Electrum ru | +| **_Multisig & advanced addressing_** | | | +| Co-signer | соподписант | noun, hyphenated · ⚠️ NOT "со-владелец" (co-owner) · Electrum ru | +| Quorum | кворум / порог подписей | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym · gloss: частично подписанная Bitcoin-транзакция · Bitcoin Core ru. | +| Provide signature | предоставить подпись / подписать транзакцию | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / код оплаты | acronym kept; "Payment Code" → "код оплаты" (shipped `коды оплаты BIP47`). | +| Notification transaction | транзакция уведомления | noun · BIP47-specific (shipped). | +| SilentPayment | SilentPayment / тихие платежи | protocol name kept English (shipped as-is); optional explanatory `тихие платежи`. | +| **_Coin control_** | | | +| Coin Control | управление UTXO / управление монетами | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case. Shipped: `Управление монетами`. | +| Frozen | заморожен / заморожено | adj · masc-agreement / state form · ⚠️ NOT verb "заморозить" · Electrum ru | +| **_Security & storage_** | | | +| Encrypted storage | шифрование хранилища / зашифрованное хранилище | noun, lowercase · process / state · ⚠️ NOT Title Case. | +| Plausible Deniability | двойное дно | noun, lowercase · idiomatic Russian for "hidden compartment"; not a literal calque. Shipped: `Двойное дно`. | +| Biometrics | биометрия | noun, lowercase. | +| Passcode | код устройства / код доступа | noun · device-level unlock · ⚠️ NOT "пароль" (= app password). | +| **_Backup, import & UX_** | | | +| Backup | резервная копия / резервное копирование | noun / verbal-noun · Electrum ru + Bisq ru | +| Restore | восстановить / восстановление | verb / noun · Electrum ru + Bitcoin Core ru | +| Import | импортировать / импорт | verb / noun · Bitcoin Core ru | +| Voucher | ваучер | noun, lowercase. Azte.co context. | +| Redeem | активировать / зачислить | verb · ⚠️ NOT "купить" / NOT "перевести". Shipped uses both: `Активировать` (button) and `Зачислить на кошелёк`. | +| Send | отправить | verb · Electrum ru + Bitcoin Core ru + Zeus ru | +| Receive | получить | verb · Electrum ru + Bitcoin Core ru | +| Settings | настройки | noun, lowercase · Bitcoin Core ru + Zeus ru | +| Confirm | подтвердить / подтверждение | verb / noun · Bitcoin Core ru | +| QR Code | QR-код | noun, hyphenated · Electrum ru + Cake ru | +| Clipboard | буфер обмена | noun, lowercase · Electrum ru | +| Memo | примечание / мемо | noun, lowercase · shipped / transliterated alt. | +| Description | описание | noun, lowercase · Electrum ru | +| Label | метка | noun, lowercase · Electrum ru + Bitcoin Core ru + Bisq ru | diff --git a/loc/vocabulary/si_LK.md b/loc/vocabulary/si_LK.md new file mode 100644 index 00000000000..dbe22fdea00 --- /dev/null +++ b/loc/vocabulary/si_LK.md @@ -0,0 +1,96 @@ +# Sinhala translation vocabulary (`si_LK.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / බිට්කොයින් | brand kept Latin; බිට්කොයින් in explanatory text · si.wikipedia.org/wiki/බිට්කොයින් | +| Lightning | Lightning / ලයිට්නින් | brand kept Latin; transliteration used in body. Avoid native "අකුණු" (= literal lightning bolt) for the protocol. | +| Electrum | Electrum | brand · ⚠️ shipped uses inconsistent ඉලෙක්ට්‍රම් / ඉලෙක්ට්‍රෝම් — standardise on Latin `Electrum`; if transliterated, use ඉලෙක්ට්‍රම් · Electrum si | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. Avoid transliteration ග්‍රවුන්ඩ්කන්ට්‍රෝල්. | +| **_Units & amounts_** | | | +| bitcoin / BTC | බිට්කොයින් / BTC | noun unit + ticker. Ticker kept Latin. | +| sats | සැට්ස් | noun. | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | පසුම්බිය | noun · shipped mixes පසුම්බිය / මුදල් පසුම්බිය — standardise on පසුම්බිය (shorter, used by Bitcoin Core si + Electrum si). | +| Vault | සුරක්ෂිතාගාරය | noun · safe/strongbox sense. | +| Watch-only | නැරඹීමට පමණි | adj · Electrum si | +| Hardware wallet | දෘඩාංග පසුම්බිය | noun. | +| Seed | බීජ වැකිය / ප්‍රතිසාධන වැකිය | noun · technical / mainstream. Bare `බීජ` (= literal seed) is too terse; pair with වැකිය. | +| Mnemonic | සිහිවටන වැකිය / ප්‍රතිසාධන වැකිය | noun · technical / mainstream. | +| Passphrase | මුරවැකිය | noun · ⚠️ NOT මුරපදය (= password). Distinct word for BIP39 25th word · Electrum si "මුර-වැකිකඩ" | +| Public key | පොදු යතුර | noun. | +| Private key | පුද්ගලික යතුර | noun · Bitcoin Core si | +| WIF | WIF | acronym. | +| xpub | xpub | acronym, lowercase preferred · ⚠️ shipped uses XPUB — vocabulary prefers lowercase. | +| Descriptor | විස්තරකය | noun · Electrum si "විස්තරය" — prefer විස්තරකය to disambiguate from generic "description". | +| Derivation path | ව්‍යුත්පන්න මාර්ගය | noun. | +| Master fingerprint | ප්‍රධාන ඇඟිලි සලකුණ | noun. | +| BIP38 | BIP38 | acronym kept. | +| **_On-chain transactions_** | | | +| Transaction | ගනුදෙනුව | noun · Bitcoin Core si + Electrum si | +| Address | ලිපිනය | noun. | +| Input | ආදානය / ගනුදෙනු ආදානය | noun · short / full · ⚠️ shipped uses inconsistent යෙදවුම් / ආදානය — standardise on ආදානය. | +| Output | ප්‍රතිදානය / ගනුදෙනු ප්‍රතිදානය | noun · short / full · ⚠️ NOT UI recipient label "වෙත". | +| UTXO | UTXO | acronym · gloss: වියදම් නොකළ ගනුදෙනු ප්‍රතිදානය. | +| Change | ඉතිරිය / ඉතිරි මුදල | noun · ⚠️ NOT verb "වෙනස් කරන්න" (= to change). Must be a noun for change-output. Shipped `වෙනස් කරන්න` is wrong POS. | +| Hex | හෙක්ස් / හෙක්ස් දත්ත | noun · short / explanatory · ⚠️ NOT "hash" / NOT "ගනුදෙනු දත්ත". | +| Pending | අපේක්ෂිත | adj/state. | +| Unconfirmed | තහවුරු නොකළ | adj · Electrum si | +| Confirmed | තහවුරු කළ | adj · ⚠️ shipped `තහවුරු` drops the adjective suffix — should be තහවුරු කළ. | +| Mempool | මෙම්පූල් | noun · transliteration · Electrum si | +| Broadcast | විකාශනය කරන්න / විකාශනය | verb / noun · Bitcoin Core si + Electrum si | +| Block explorer | බ්ලොක් එක්ස්ප්ලෝරර් | noun. | +| Onchain | ඔන්-චේන් / දාම මත | adj · compact (chip) / explanatory (body) | +| Offchain | ඔෆ්-චේන් / දාමයෙන් පිට | adj · compact (chip) / explanatory (body) | +| **_Fees & fee bumping_** | | | +| Fee | ගාස්තුව | noun · Bitcoin Core si | +| Fee Bump | ගාස්තුව වැඩි කිරීම | noun · ⚠️ shipped `ගාස්තු වැඩි කිරීමට ඉඩ දෙන්න` is a verb phrase ("allow to raise fee"); canonical form is the noun. | +| RBF | RBF | acronym · gloss: ගාස්තුවෙන් ප්‍රතිස්ථාපනය. | +| CPFP | CPFP | acronym · gloss: දරුවා දෙමාපියන්ට ගෙවයි. ⚠️ NOT a verb. | +| Speed Up | වේගවත් කරන්න | verb · ⚠️ shipped `බම්ප් ගාස්තුව` (= "bump fee") doesn't match the Speed Up label. | +| **_Lightning_** | | | +| Invoice | ඉන්වොයිසිය / ගෙවීම් ඉල්ලීම | noun · technical / mainstream · Electrum si | +| Lightning Invoice | Lightning ඉන්වොයිසිය / Lightning ගෙවීම් ඉල්ලීම | noun · brand kept Latin. | +| Preimage | පූර්ව-රූපය | noun · math term for "preimage". | +| Payment | ගෙවීම | noun · ⚠️ NOT verb "ගෙවන්න" (= to pay). Shipped `ගෙවීම්` is plural — singular ගෙවීම preferred. | +| Expired | කල් ඉකුත් වූ / කල් ඉකුත් වී ඇත | adj · short / full state form. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | සම-අත්සන්කරු | noun · ⚠️ NOT "co-owner". | +| Quorum | ගණපූරණය | noun. | +| PSBT | PSBT | acronym · gloss: අර්ධ වශයෙන් අත්සන් කළ බිට්කොයින් ගනුදෙනුව. | +| Provide signature | අත්සන ලබා දෙන්න | verb. | +| BIP47 / Payment Code | BIP47 / ගෙවීම් කේතය | acronym kept; "Payment Code" → "ගෙවීම් කේතය". | +| Notification transaction | දැනුම්දීම් ගනුදෙනුව | noun · BIP47-specific. | +| SilentPayment | Silent Payments / නිහඬ ගෙවීම් | protocol name kept English (plural); explanatory `නිහඬ ගෙවීම්` if needed. | +| **_Coin control_** | | | +| Coin Control | කාසි පාලනය | noun. | +| Frozen | කැටි කළ / නිශ්චල | adj · state form · ⚠️ NOT verb "කැටි කරන්න" (= to freeze). Shipped mixes verb + adj — keep adjective form. | +| **_Security & storage_** | | | +| Encrypted storage | සංකේතනය කළ ගබඩාව | noun. | +| Plausible Deniability | පිළිගතහැකි ප්‍රතික්ෂේප කිරීම | noun. | +| Biometrics | ජෛවමිතික | noun. | +| Passcode | කේතාංකය | noun · ⚠️ NOT මුරපදය (= password). Shipped `මුරපදය` collides with password — recommend කේතාංකය. | +| **_Backup, import & UX_** | | | +| Backup | උපස්ථය / උපස්ථ කරන්න | noun / verb · Electrum si | +| Restore | ප්‍රතිසාධනය කරන්න / ප්‍රතිසාධනය | verb / noun · Electrum si | +| Import | ආනයනය කරන්න / ආනයනය | verb / noun · ⚠️ shipped mixes ආනයන / ආයාත — standardise on ආනයනය. | +| Voucher | වවුචරය | noun. | +| Redeem | මුදවා ගන්න | verb. | +| Send | යවන්න | verb. | +| Receive | ලබා ගන්න | verb. | +| Settings | සැකසුම් | noun. | +| Confirm | තහවුරු කරන්න / තහවුරු කිරීම | verb / noun. | +| QR Code | QR කේතය | noun · Electrum si | +| Clipboard | පසුරු පුවරුව | noun · Electrum si | +| Memo | සංදේශය / සටහන | noun. | +| Description | විස්තරය | noun. | +| Label | ලේබලය | noun · Electrum si | diff --git a/loc/vocabulary/sk_sk.md b/loc/vocabulary/sk_sk.md new file mode 100644 index 00000000000..32c908e537d --- /dev/null +++ b/loc/vocabulary/sk_sk.md @@ -0,0 +1,96 @@ +# Slovak translation vocabulary (`sk_sk.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · sk.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand · Electrum sk_SK keeps as `Lightning` / `sieť Lightning` | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker, lowercase · Electrum sk_SK uses lowercase `bitcoiny` for the unit. | +| sats | sats / satoshi | noun, lowercase · Electrum sk_SK uses `satoshi` in body text. | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | peňaženka | noun, lowercase · Bitcoin Core sk + Electrum sk_SK | +| Vault | trezor / sejf | noun, lowercase · safe/strongbox sense · ⚠️ NOT Latin "Vault". | +| Watch-only | iba na sledovanie / peňaženka iba na sledovanie | adj · short / full · Electrum sk_SK: "Peňaženka iba na sledovanie" | +| Hardware wallet | hardvérová peňaženka | noun, lowercase · Electrum sk_SK uses "Hardvérové Zariadenie" · ⚠️ shipped "hardwarová" is a common anglicism, recommended `hardvérová`. | +| Seed | seed fráza / seedová fráza | noun, lowercase · Electrum sk_SK uses "seedová fráza"; current shipped `Semienko` (= seedling) is misleading and should be replaced. | +| Mnemonic | mnemonická fráza / zálohovacia fráza | noun · technical / mainstream · shipped uses `zálohovacia fráza`. | +| Passphrase | prístupová fráza | noun · ⚠️ NOT `heslo` (= password) · Electrum sk_SK: "prístupová fráza"; Bitcoin Core sk also "prístupová fráza". | +| Public key | verejný kľúč | noun, lowercase · Bitcoin Core sk + Electrum sk_SK | +| Private key | súkromný kľúč | noun, lowercase · Bitcoin Core sk + Electrum sk_SK | +| WIF | WIF | acronym · gloss: formát pre import súkromného kľúča. | +| xpub | xpub | acronym, lowercase preferred · shipped `XPUB` keeps screaming caps; Electrum sk_SK keeps `xpub`. | +| Descriptor | deskriptor | noun, lowercase · technical Bitcoin Core term. | +| Derivation path | derivačná cesta | noun, lowercase · Electrum sk_SK: "Derivačná cesta" | +| Master fingerprint | odtlačok hlavného kľúča | noun, lowercase · ⚠️ shipped `Hlavný odtlačok prsta` literally = "main fingerprint of a finger"; recommend `odtlačok hlavného kľúča` (master-key hash, not a biometric finger). | +| BIP38 | BIP38 | acronym kept · gloss: heslom šifrovaný súkromný kľúč. | +| **_On-chain transactions_** | | | +| Transaction | transakcia | noun, lowercase · Bitcoin Core sk + Electrum sk_SK | +| Address | adresa | noun, lowercase · Bitcoin Core sk + Electrum sk_SK | +| Input | vstup / vstup transakcie | noun · short / full · Electrum sk_SK: "Vstupy" | +| Output | výstup / výstup transakcie | noun · short / full · ⚠️ NOT the UI recipient label "Cieľ" · Electrum sk_SK: "Výstupy" | +| UTXO | UTXO | acronym · gloss: neminutý výstup transakcie · Bitcoin Core sk keeps `UTXO`. | +| Change | drobné / adresa drobných | noun · ⚠️ NOT verb "zmeniť". `drobné` = leftover coin · Electrum sk_SK: "Drobné". | +| Hex | hex / hexadecimálne dáta | noun · short / explanatory · ⚠️ NOT "hash" / NOT "dáta transakcie". | +| Pending | čakajúce / čakajúca | adj · neuter / fem-agreement · Bitcoin Core sk: "Čakajúce potvrdenie" · ⚠️ shipped `čaká` is 3sg verb ("it waits"), not adjective; standalone form should be `čakajúce` (neut) or `čakajúca` (fem) to agree with noun; alt explanatory: `čaká na potvrdenie`. | +| Unconfirmed | nepotvrdené / nepotvrdená | adj · state / fem-agreement · Bitcoin Core sk + Electrum sk_SK · ⚠️ shipped `nepotvrdenej` is genitive case from a longer phrase; standalone form should be `nepotvrdené`. | +| Confirmed | potvrdené / potvrdená | adj · state / fem-agreement · Bitcoin Core sk + Electrum sk_SK | +| Mempool | mempool | noun, lowercase · Electrum sk_SK keeps `mempool`. | +| Broadcast | odoslať / zverejniť | verb · UI-clear / technical · Electrum sk_SK: "Zverejniť" · noun form: vysielanie. | +| Block explorer | prieskumník blokov | noun, lowercase · Electrum sk_SK: "Pozrieť v prieskumníkovi blokov" | +| Onchain | on-chain / v blockchaine | adj · compact (chip) / explanatory (body) | +| Offchain | off-chain / mimo blockchainu | adj · compact (chip) / explanatory (body) | +| **_Fees & fee bumping_** | | | +| Fee | poplatok | noun, lowercase. | +| Fee Bump | navýšenie poplatku | noun · Electrum sk_SK: "Navýšiť poplatok" / "Increase fee" | +| RBF | RBF | acronym · gloss: nahradiť za poplatok / Replace-By-Fee · shipped `Zrušiť transakciu (RBF)` is the UI label for cancel-via-RBF, not the term itself. | +| CPFP | CPFP | acronym · gloss: dieťa platí za rodiča / Child Pays For Parent · ⚠️ NOT a verb · Electrum sk_SK keeps `CPFP`. | +| Speed Up | zrýchliť / navýšiť poplatok | verb · UI button label for RBF. | +| **_Lightning_** | | | +| Invoice | faktúra / platobná požiadavka | noun · technical / mainstream · Electrum sk_SK: "Faktúry"; shipped uses `Faktúra`. | +| Lightning Invoice | faktúra Lightning / platobná požiadavka Lightning | noun · technical / mainstream. | +| Preimage | predobraz / preimage | noun · math term · Electrum sk_SK: "Predobraz - Preimage" | +| Payment | platba | noun · ⚠️ NOT verb "zaplatiť" · Electrum sk_SK: "Platba Lightning" | +| Expired | expirovaná / s uplynutou platnosťou | adj · short / explanatory · Electrum sk_SK: "Platnosť faktúry uplynula" | +| **_Multisig & advanced addressing_** | | | +| Co-signer | spolupodpisovateľ | noun · ⚠️ NOT "spoluvlastník" (co-owner) · Electrum sk_SK: "spolupodpisovateľ" | +| Quorum | kvórum / prah podpisov | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym. | +| Provide signature | poskytnúť podpis / podpísať transakciu | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / platobný kód | acronym kept; "Payment Code" → "platobný kód". | +| Notification transaction | oznamovacia transakcia | noun · BIP47-specific. | +| SilentPayment | Silent Payments / tiché platby | protocol name kept English (plural); explanatory `tiché platby` if needed. | +| **_Coin control_** | | | +| Coin Control | správa UTXO / správa mincí | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case. | +| Frozen | zmrazené / zmrazená | adj · state / fem-agreement · ⚠️ NOT verb "zmraziť" · Electrum sk_SK: "Adresa je zmrazená" | +| **_Security & storage_** | | | +| Encrypted storage | zašifrované úložisko | noun, lowercase · shipped matches. | +| Plausible Deniability | hodnoverná popierateľnosť | noun, lowercase · ⚠️ shipped `Plausible deniability` is English passthrough; Slovak rendering exists per sk.wikipedia.org/wiki/Hodnoverná_popierateľnosť. | +| Biometrics | biometria | noun, lowercase. | +| Passcode | prístupový kód | noun · ⚠️ NOT `heslo` (= password); shipped collapses both to `Heslo`. Use distinct word. | +| **_Backup, import & UX_** | | | +| Backup | záloha / zálohovať | noun / verb · Bitcoin Core sk: "Zálohovanie peňaženky" / "Zálohovať peňaženku" | +| Restore | obnoviť / obnovenie | verb / noun · Bitcoin Core sk: "Restore Wallet" → "Obnoviť"; Electrum sk_SK: "&Nový/Obnoviť" | +| Import | importovať / import | verb / noun. | +| Voucher | voucher / poukaz | noun, lowercase · BlueWallet's azteco context keeps `voucher`. | +| Redeem | uplatniť / aktivovať | verb · ⚠️ NOT "Odobrať" / NOT "Prevziať"; for vouchers prefer `uplatniť`. | +| Send | poslať / odoslať | verb · shipped uses `Poslať`. | +| Receive | prijať | verb. | +| Settings | nastavenia | noun, lowercase. | +| Confirm | potvrdiť / potvrdenie | verb / noun · plural confirmations: potvrdenia. | +| QR Code | QR kód | noun · Electrum sk_SK: "QR kód" | +| Clipboard | schránka | noun, lowercase · ⚠️ shipped `schránky` is genitive ("do schránky"); standalone form is `schránka`. | +| Memo | poznámka | noun, lowercase · ⚠️ shipped collapses memo/description/label all to `Popis`; recommend `poznámka` for sender's note. | +| Description | popis | noun, lowercase · Bitcoin Core sk + Electrum sk_SK | +| Label | menovka / označenie | noun, lowercase · Electrum sk_SK: "Menovka"; Bitcoin Core sk uses "Popis" (collision with description). | diff --git a/loc/vocabulary/sl_SI.md b/loc/vocabulary/sl_SI.md new file mode 100644 index 00000000000..775116c1263 --- /dev/null +++ b/loc/vocabulary/sl_SI.md @@ -0,0 +1,96 @@ +# Slovenian translation vocabulary (`sl_SI.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · sl.wikipedia.org/wiki/Bitcoin (lowercase `bitcoin` as unit). | +| Lightning | Lightning | brand. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker; lowercase as unit. | +| sats | sats / satošiji | noun · technical / mainstream; app ships `sats` in UI. | +| sat/vByte | sat/vByte | fee-rate unit; Latin canonical per glossary (casing matters). ⚠️ App currently ships `sat/vBajt` — shipped-string deviation. | +| vByte | vByte | virtual byte, Latin canonical per glossary. ⚠️ App currently ships `vBajt` — shipped-string deviation. | +| **_Wallet, keys & seeds_** | | | +| Wallet | denarnica | noun, lowercase. | +| Vault | trezor / sejf | noun · ⚠️ NOT a brand — `trezor` = safe/strongbox in Slovenian (app ships `Trezor`); avoid confusion with the Trezor hardware-wallet brand. | +| Watch-only | opazovalna / opazovana | adj · Bitcoin Core sl uses `opazovane denarnice` (watch-only wallets). | +| Hardware wallet | strojna denarnica | noun, lowercase. | +| Seed | seme / obnovitvena fraza | noun · technical / mainstream. | +| Mnemonic | mnemonična fraza / obnovitvena fraza | noun · technical / mainstream. | +| Passphrase | dodatna fraza / dodatna beseda | noun · ⚠️ distinct from `geslo` (password). App ships `Dodatna beseda/niz (passphrase)`. | +| Public key | javni ključ | noun, lowercase. | +| Private key | zasebni ključ | noun, lowercase. | +| WIF | WIF | acronym · gloss: oblika za uvoz denarnice. | +| xpub | xpub | acronym · lowercase preferred (app currently ships `XPUB`). | +| Descriptor | deskriptor | noun, lowercase · output descriptor (BIP380). | +| Derivation path | pot izpeljave | noun · Bitcoin Core sl. App keeps `(derivation path)` parenthetical — recommend dropping. | +| Master fingerprint | prstni odtis glavnega ključa | noun · App keeps `(fingerprint)` parenthetical — recommend dropping. | +| BIP38 | BIP38 | acronym · gloss: z geslom zaščiten zasebni ključ. | +| **_On-chain transactions_** | | | +| Transaction | transakcija | noun, lowercase. | +| Address | naslov | noun, lowercase. | +| Input | vhod / vhod transakcije | noun · short / full · ⚠️ NOT "prijava" (login). | +| Output | izhod / izhod transakcije | noun · short / full · ⚠️ NOT the UI recipient label "Za:". | +| UTXO | UTXO | acronym · gloss: neporabljen izhod transakcije. | +| Change | vračilo / naslov vračila | noun · ⚠️ NOT verb "spremeniti". `vračilo` = leftover; `naslov vračila` for change-address · Bitcoin Core sl. | +| Hex | hex / šestnajstiška vrednost | noun · short / explanatory · ⚠️ NOT "hash". App ships `šestnajstiška vrednost`. | +| Pending | v teku / čaka | adj/state · body / chip. ⚠️ NOT noun "čakanje". | +| Unconfirmed | nepotrjeno / nepotrjena | adj · state / fem-agreement. | +| Confirmed | potrjeno / potrjena | adj · state / fem-agreement. App abbreviates as `Potrd.`. | +| Mempool | mempool | noun · kept Latin (technical term). ⚠️ NOT `čakalna vrsta` (= generic "queue") — anti-meaning risk. | +| Broadcast | objavi v omrežje / objava | verb / noun · App ships `Objavi v omrežju`. | +| Block explorer | raziskovalec blokov | noun, lowercase. | +| Onchain | on-chain / v verigi blokov | adj · compact (chip) / explanatory (body). | +| Offchain | off-chain / izven verige blokov | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | omrežnina / provizija | noun, lowercase · ⚠️ NOT Title Case. App ships `omrežnina` (lit. "network fee"). | +| Fee Bump | povečanje omrežnine | noun, lowercase · ⚠️ NOT Title Case. | +| RBF | RBF | acronym · gloss: zamenjava za višjo omrežnino / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: otrok plača za starša / Child-Pays-For-Parent. ⚠️ NOT verb "Ustvari". | +| Speed Up | povečaj omrežnino / pospeši | verb · App ships `Povečaj omrežnino (RBF)`. | +| **_Lightning_** | | | +| Invoice | račun / plačilni zahtevek | noun · technical (BOLT11) / mainstream. App ships `Račun`. | +| Lightning Invoice | Lightning račun | noun · pair brand `Lightning` + localised noun. | +| Preimage | predslika / praslika | noun · math term (cryptographic preimage) · `predslika` matches Slavic CS-context convention (cf. cs `předobraz`, hr `predslika`); sl.wikipedia uses `praslika` for the math preimage. No Electrum sl citation found. Either acceptable; `predslika` preferred in technical/CS UI. | +| Payment | plačilo | noun · ⚠️ NOT verb "plačati". | +| Expired | potekel / poteklo | adj · masc / neuter-agreement state. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | sopodpisnik | noun · ⚠️ NOT "solastnik" (co-owner) · Bitcoin Core sl uses `zunanji podpisnik` for external signer. | +| Quorum | kvorum / prag podpisov | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym · gloss: delno podpisana bitcoin transakcija (DPBT per Bitcoin Core sl). | +| Provide signature | podpiši transakcijo / zagotovi podpis | verb · App ships `Vnesite podpis` ("enter signature") — recommend revising. | +| BIP47 / Payment Code | BIP47 / plačilna koda | acronym kept; `Payment Code` → `plačilna koda`. | +| Notification transaction | obvestilna transakcija | noun · BIP47-specific. | +| SilentPayment | Silent Payments / tiha plačila | protocol name kept English (plural); explanatory `tiha plačila` if needed. | +| **_Coin control_** | | | +| Coin Control | nadzor nad kovanci / upravljanje UTXO | noun, lowercase · mainstream / technical · ⚠️ NOT Title Case. | +| Frozen | zamrznjen / zamrznjeno | adj · masc-agreement / state · ⚠️ NOT verb "zamrzniti". | +| **_Security & storage_** | | | +| Encrypted storage | šifrirana shramba | noun, lowercase · ⚠️ NOT Title Case. | +| Plausible Deniability | verodostojno zanikanje | noun, lowercase · ⚠️ NOT Title Case (app currently ships `Verodostojno Zanikanje`). | +| Biometrics | biometrija | noun, lowercase. | +| Passcode | koda za dostop | noun · ⚠️ NOT "geslo" (= password); app currently collides with `Geslo` — recommend distinct word. | +| **_Backup, import & UX_** | | | +| Backup | varnostna kopija / izdelaj varnostno kopijo | noun / verb. | +| Restore | obnovi / obnovitev | verb / noun. | +| Import | uvozi / uvoz | verb / noun. | +| Voucher | bon | noun, lowercase. | +| Redeem | unovči / aktiviraj | verb · ⚠️ NOT "kupi" (buy); app ships `Unovčite` (polite imperative). | +| Send | pošlji | verb. | +| Receive | prejmi | verb. | +| Settings | nastavitve | noun, lowercase. | +| Confirm | potrdi / potrditev | verb / noun. Also: `potrditve` (plural) = block confirmations. | +| QR Code | QR koda | noun. | +| Clipboard | odložišče | noun, lowercase. | +| Memo | opomba | noun, lowercase. | +| Description | opis | noun, lowercase. | +| Label | oznaka | noun, lowercase · Bitcoin Core sl. | diff --git a/loc/vocabulary/sq_AL.md b/loc/vocabulary/sq_AL.md new file mode 100644 index 00000000000..be7a2997ad7 --- /dev/null +++ b/loc/vocabulary/sq_AL.md @@ -0,0 +1,96 @@ +# Albanian translation vocabulary (`sq_AL.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · sq.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand kept Latin. | +| Electrum | Electrum | brand kept Latin · Fix: shipped `Elektrum` (Albanianised spelling) — brand rows stay Latin. | +| LNDhub | LNDhub | brand kept Latin. | +| LND | LND | brand kept Latin. | +| LNURL | LNURL | brand kept Latin. | +| Tor | Tor | brand kept Latin. | +| Orbot | Orbot | brand kept Latin. | +| GroundControl | GroundControl | brand kept Latin. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker, lowercase unit. | +| sats | sats / satoshi | noun, lowercase; unit kept as in English (`sats`) · Albanian singular noun form `satoshi` (pl. `satoshi`) acceptable in body text. | +| sat/vByte | sat/vByte | technical unit; kept Latin. | +| vByte | vByte | technical unit; kept Latin. | +| **_Wallet, keys & seeds_** | | | +| Wallet | portofol | noun, lowercase · Bitcoin Core sq + sq.wikipedia.org/wiki/Bitcoin · Fix: shipped `Portofola` is plural ("wallets"); singular is `portofol`. | +| Vault | kasafortë / arkë | noun, lowercase; "safe / strongbox" sense · Fix: shipped `portofol` collapses Vault into Wallet — Vault must be a distinct word. | +| Watch-only | vetëm për shikim / vetëm për vëzhgim | adj · short / explanatory. ⚠️ NOT "view mode" — wallet type. | +| Hardware wallet | portofol hardware / portofol fizik | noun, lowercase · technical / mainstream. | +| Seed | frazë rigjenerimi / farë | noun · mainstream "recovery phrase" / literal · ⚠️ `farë` = botanical seed, NOT BIP39 sense; prefer `frazë rigjenerimi` in UI. | +| Mnemonic | frazë mnemonike / fjalët e rigjenerimit | noun · technical / mainstream. | +| Passphrase | frazë sekrete | noun · ⚠️ NOT `fjalëkalim` (= password) · distinct from app password and device passcode. | +| Public key | çelës publik | noun, lowercase. | +| Private key | çelës privat | noun, lowercase · Fix: shipped `celësin privat` is accusative + missing `ç` diacritic. | +| WIF | WIF | acronym · gloss: format importi për çelësin privat. | +| xpub | xpub | acronym, lowercase. | +| Descriptor | përshkrues | noun, lowercase. | +| Derivation path | shteg derivimi | noun, lowercase · BIP32 path. | +| Master fingerprint | shenjë gishti kryesore | noun, lowercase · gloss for HASH160 prefix of master pubkey. | +| BIP38 | BIP38 | acronym kept · gloss: çelës privat i mbrojtur me fjalëkalim. ⚠️ NOT a verb. | +| **_On-chain transactions_** | | | +| Transaction | transaksion | noun, lowercase · sq.wikipedia.org/wiki/Bitcoin · Fix: shipped `Transferte` ("transfer") loses tx meaning and lacks `ë`. | +| Address | adresë | noun, lowercase · Bitcoin Core sq (`Adresë`) · Fix: shipped `Adresa` is definite form; lemma is `adresë`. | +| Input | hyrje / hyrje transaksioni | noun · short / full. ⚠️ NOT "login". | +| Output | dalje / dalje transaksioni | noun · short / full. ⚠️ NOT UI recipient label "Për". | +| UTXO | UTXO | acronym · gloss: dalje transaksioni e pashpenzuar. ⚠️ Fix: shipped `Xheton` ("token/chip") is wrong — UTXO is an acronym kept as-is. | +| Change | kusur / adresa e kusurit | noun · ⚠️ NOT verb `ndrysho` (= to modify). `kusur` = leftover change · Fix: shipped `Ndrysho` is the wrong POS (verb "modify"). | +| Hex | hex / të dhëna hex | noun · short / explanatory. ⚠️ NOT "hash". | +| Pending | në pritje | adj/state · lowercase. | +| Unconfirmed | i pakonfirmuar / e pakonfirmuar | adj · masc / fem agreement · Bitcoin Core sq (`I pakonfirmuar`). | +| Confirmed | i konfirmuar / e konfirmuar | adj · masc / fem agreement · Bitcoin Core sq (`I/E konfirmuar`). | +| Mempool | mempool | noun, lowercase · kept Latin (no established Albanian term). | +| Broadcast | transmeto / transmetim | verb / noun · UI buttons use both forms in shipped strings. Fix: shipped `Shpërndarja` ("the sharing", definite noun) is inconsistent with `Transmetim` already used in `send.broadcastButton`. | +| Block explorer | eksplorues blloqesh | noun, lowercase. | +| Onchain | on-chain / në zinxhir | adj · compact (chip) / explanatory (body). | +| Offchain | off-chain / jashtë zinxhirit | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | komision | noun, lowercase · shipped uses "commission" sense (acceptable in finance UI). | +| Fee Bump | rritje e komisionit | noun · ⚠️ shipped `Lejo rritjen e komisionit` is the full sentence "Allow fee bump" — the standalone term is `rritje e komisionit`. | +| RBF | RBF | acronym · gloss: zëvendëso me komision më të lartë (Replace-By-Fee). | +| CPFP | CPFP | acronym · gloss: fëmija paguan për prindin (Child-Pays-For-Parent). ⚠️ NOT a verb like "Krijo". | +| Speed Up | përshpejto | verb · button label for RBF. | +| **_Lightning_** | | | +| Invoice | faturë / kërkesë pagese | noun · technical / mainstream · Fix: shipped `Fatura` is definite form; lemma is `faturë`. | +| Lightning Invoice | faturë Lightning / kërkesë pagese Lightning | noun · technical / mainstream. | +| Preimage | preimazh | noun, lowercase · calque of English "preimage". | +| Payment | pagesë | noun · ⚠️ NOT verb `paguaj` ("to pay"). Fix: shipped `Pagesa` is definite form; lemma is `pagesë`. | +| Expired | i skaduar / skaduar | adj · with-article / bare state form. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | bashkë-firmëtar / firmëtar | noun · ⚠️ NOT "bashkëpronar" (co-owner). | +| Quorum | kuorum / prag firmash | noun, lowercase · canonical / UI-clear · Fix: shipped `Kuorumi` is definite form; lemma is `kuorum`. | +| PSBT | PSBT | acronym kept. | +| Provide signature | jep firmën / firmos transaksionin | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / kod pagese | acronym kept; `Payment Code` → `kod pagese` (lowercase, lemma) · Fix: shipped `Kodi i Pagesës` is Title Case + definite; should be lowercase indefinite. | +| Notification transaction | transaksion njoftimi | noun · BIP47-specific 0-value tx. | +| SilentPayment | Silent Payments / pagesa të heshtura | protocol name kept English (plural); explanatory `pagesa të heshtura` if needed. | +| **_Coin control_** | | | +| Coin Control | kontroll i UTXO-ve / kontroll i monedhave | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case. Fix: shipped `Kontrollo Xhetonin` is verb imperative ("control the chip") with wrong noun. | +| Frozen | i ngrirë / e ngrirë | adj · masc / fem agreement · ⚠️ NOT verb `ngrije` ("freeze it") · Fix: shipped `Ngrije` is the imperative button label, not the adjective state. | +| **_Security & storage_** | | | +| Encrypted storage | memorie e enkriptuar | noun, lowercase · Bitcoin Core sq uses `enkriptim` for encryption · ⚠️ NOT Title Case. | +| Plausible Deniability | mohim i besueshëm | noun, lowercase · matches shipped form (drop Title Case if any). | +| Biometrics | biometri / të dhëna biometrike | noun, lowercase · short / explanatory · Fix: shipped `Te dhenat Biometrike` is missing `ë` and uses Title Case. | +| Passcode | kod hyrjeje | noun · ⚠️ NOT `fjalëkalim` (= password) · Fix: shipped `Fjalkalimi` collapses passcode into password and is missing `ë`. | +| **_Backup, import & UX_** | | | +| Backup | kopje rezervë / krijo kopje rezervë | noun / verb. | +| Restore | rikuperoj / rikuperim | verb / noun · also `rivendos` for the verb form. | +| Import | importo / importim | verb / noun. | +| Voucher | kupon / faturë blerjeje | noun, lowercase · Fix: shipped `Përdor kodin promocional të Azte.co` is the full sentence "Use the Azte.co promo code" and collapses voucher into "promo code" — voucher is a distinct word `kupon`. | +| Redeem | shfrytëzo / aktivizo | verb · ⚠️ NOT "buy to wallet" / NOT `transfero`. Fix: shipped `Përdore në portofol` ("use it in wallet") loses the redeem semantics. | +| Send | dërgo | verb · Bitcoin Core sq (`Dërgo`). | +| Receive | merr | verb · Bitcoin Core sq (`Merr`). | +| Settings | cilësime / konfigurime | noun, lowercase · mainstream / Bitcoin Core sq (`Konfigurimet`). | +| Confirm | konfirmo / konfirmim | verb / noun · noun also = `konfirmime` (block confirmations). | +| QR Code | kod QR | noun, lowercase · Fix: shipped `QR kodi` is Anglo-order definite; Albanian noun-first lemma is `kod QR`. | +| Clipboard | kujtesë e përkohshme / memorie e sistemit | noun, lowercase · short / Bitcoin Core sq form (`memorja e sistemit`) · Fix: shipped `Memoria e përkohshme` is acceptable but has Title Case + Italianate `Memoria`; lemma `kujtesë e përkohshme`. | +| Memo | memo / shënim | noun, lowercase · Latin / native. | +| Description | përshkrim | noun, lowercase. | +| Label | etiketë | noun, lowercase · Bitcoin Core sq (`Etiketë`). | diff --git a/loc/vocabulary/sr_RS.md b/loc/vocabulary/sr_RS.md new file mode 100644 index 00000000000..4e247f0802a --- /dev/null +++ b/loc/vocabulary/sr_RS.md @@ -0,0 +1,100 @@ +# Serbian translation vocabulary (`sr_RS.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · sr.wikipedia "Bitkoin" used in explanatory text only. | +| Lightning | Lightning | brand. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker; Latin script per shipped UI. | +| sats | sats / satoši | noun, lowercase · technical / mainstream. | +| sat/vByte | sat/vByte | technical unit kept Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | Wallet / novčanik | noun · `Wallet` kept English in product strings; `novčanik` as descriptive prose form. Established Serbian crypto community usage. | +| Vault | Trezor | noun · established Serbian crypto term for Bitcoin Vault (BlueWallet multisig). ⚠️ NOT `sef`/`kasa` — `Trezor` is the community-accepted rendering despite the hardware-wallet brand collision. | +| Watch-only | watch-only | adj · loanword kept English; established Serbian crypto community usage. | +| Hardware wallet | hardverski novčanik | noun, lowercase · Bitcoin Core sr. | +| Seed | seed | noun, lowercase · loanword kept English; mainstream Serbian crypto usage. | +| Mnemonic | mnemonic / mnemonička fraza | noun · `mnemonic` loanword preferred in technical UI; `mnemonička fraza` for explanatory prose. | +| Passphrase | pristupna fraza | noun · ⚠️ NOT `lozinka` (= password) · Bitcoin Core sr. | +| Public key | javni ključ | noun, lowercase · Bitcoin Core sr. | +| Private key | privatni ključ | noun, lowercase · Bitcoin Core sr. | +| WIF | WIF | acronym · gloss: format za uvoz novčanika. | +| XPUB / xpub | XPUB | acronym · uppercase preferred per community usage. | +| Descriptor | deskriptor | noun, lowercase · Bitcoin Core sr transliteration. | +| Derivation path | putanja derivacije | noun · Bitcoin Core sr. | +| Master fingerprint | glavni otisak | noun · short Bitcoin Core sr form. | +| BIP38 | BIP38 | acronym kept · gloss: BIP38 lozinka. | +| **_On-chain transactions_** | | | +| Transaction | transakcija | noun, lowercase · Latinized from Bitcoin Core sr "Трансакција". | +| Address | adresa | noun, lowercase · Bitcoin Core sr. | +| Input | ulaz / ulaz transakcije | noun · short / full · Bitcoin Core sr. | +| Output | izlaz / izlaz transakcije | noun · short / full · ⚠️ NOT "Prima" (UI recipient label). | +| UTXO | UTXO | acronym · gloss: nepotrošeni izlaz transakcije. | +| Change | Kusur / Promeni | noun · ⚠️ NOT verb "promeniti" when meaning UTXO change. `Kusur` = leftover · Bitcoin Core sr. | +| Hex | hex / heksadecimalni podaci | noun · short / explanatory · ⚠️ NOT "hash" · Bitcoin Core sr. | +| Pending | na čekanju | adj/state · Bitcoin Core sr. | +| Unconfirmed | nepotvrđeno | adj · Bitcoin Core sr. | +| Confirmed | potvrđeno | adj · Bitcoin Core sr. | +| Mempool | mempool | noun · technical term kept Latin; Bitcoin Core sr uses descriptive "удружена меморија" but UI prefers mainstream loanword. | +| Broadcast | Pošalji / broadcast | verb · ⚠️ UI prefers `Pošalji` ("Send") for button labels; `broadcast` loanword acceptable in technical prose. | +| Block explorer | block explorer | noun · loanword kept English; established Serbian crypto community usage. | +| Onchain | on-chain | adj · loanword kept English (hyphenated); established Serbian crypto community usage. | +| Offchain | off-chain | adj · loanword kept English (hyphenated); established Serbian crypto community usage. | +| **_Fees & fee bumping_** | | | +| Fee | provizija | noun, lowercase · established Serbian commerce term; matches Bitcoin Core sr ("Provizija"). ⚠️ NOT `naknada` — `provizija` is the community-accepted rendering. | +| Fee Bump | povećanje provizije | noun · constructed from `provizija` (fee) + `povećanje` (increase). | +| RBF | RBF | acronym · gloss: zamena po proviziji / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: dete plaća za roditelja. ⚠️ NOT verb. | +| Speed Up | povećaj proviziju / ubrzaj | verb · `povećaj proviziju` for RBF/CPFP UI; `ubrzaj` generic. | +| **_Lightning_** | | | +| Invoice | faktura / račun | noun · technical / mainstream · standard sr commercial terms. | +| Lightning Invoice | Lightning faktura / Lightning račun | noun · technical / mainstream. | +| Preimage | Pre-image | noun · loanword kept English (hyphenated form per community usage). | +| Payment | plaćanje | noun · ⚠️ NOT verb "platiti". | +| Expired | isteklo | adj/state · Bitcoin Core sr. | +| **_Multisig & advanced addressing_** | | | +| Multisig | Multisig | noun · loanword kept English; established Serbian crypto community usage. | +| Co-signer | co-signer | noun · loanword kept English; ⚠️ NOT `sapotpisnik` — `co-signer` is the community-accepted rendering. | +| Quorum | kvorum / prag potpisa | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym. | +| Provide signature | pruži potpis / potpiši transakciju | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / Payment kod | acronym kept; "Payment Code" → "Payment kod" — `Payment` kept English per community usage. | +| Notification transaction | notifikaciona transakcija | noun · BIP47-specific. | +| SilentPayment | SilentPayment / tiha plaćanja | protocol name kept English; explanatory `tiha plaćanja`. | +| **_Coin control_** | | | +| Coin Control | kontrola coinova / upravljanje UTXO | noun, lowercase · ⚠️ NOT Title Case. | +| Frozen | zamrznuto | adj · ⚠️ NOT verb "zamrznuti". | +| **_Security & storage_** | | | +| Storage | memorija | noun, lowercase · ⚠️ NOT `skladište` — `memorija` is the community-accepted rendering. | +| Encrypted storage | šifrovana memorija | noun, lowercase · ⚠️ NOT Title Case. | +| Plausible Deniability | verodostojna negacija | noun, lowercase. | +| Biometrics | biometrija | noun, lowercase. | +| Passcode | pristupni kod | noun · ⚠️ NOT "lozinka" (= password). | +| **_Backup, import & UX_** | | | +| Backup | backup | noun · loanword kept English; established Serbian crypto community usage. | +| Restore | obnovi / obnova | verb / noun. | +| Import | uvezi / uvoz | verb / noun · Bitcoin Core sr. | +| Voucher | vaučer | noun, lowercase. | +| Redeem | Iskoristi | verb · ⚠️ NOT `Aktiviraj` (= activate — semantic drift). `Iskoristi` = redeem/use up, the accurate Serbian rendering for vouchers. | +| Send | pošalji | verb · Bitcoin Core sr. | +| Receive | primi | verb · Bitcoin Core sr. | +| Settings | podešavanja | noun, lowercase · Bitcoin Core sr. | +| Confirm | potvrdi / potvrda | verb / noun · Bitcoin Core sr. | +| QR Code | QR kod | noun · Bitcoin Core sr. | +| Clipboard | privremena memorija / clipboard | noun, lowercase · `privremena memorija` for UI prose; `clipboard` kept English where compact label needed. | +| Memo | beleška | noun, lowercase · Bitcoin Core sr. | +| Description | opis | noun, lowercase · Bitcoin Core sr. | +| Label | oznaka | noun, lowercase · Bitcoin Core sr. | +| **_Rates & currency_** | | | +| Rate (exchange rate) | kurs | noun, lowercase · ⚠️ NOT `stopa` when meaning exchange rate. `stopa` reserved for `fee rate` (stopa provizije). | diff --git a/loc/vocabulary/sv_se.md b/loc/vocabulary/sv_se.md new file mode 100644 index 00000000000..26ba9722da0 --- /dev/null +++ b/loc/vocabulary/sv_se.md @@ -0,0 +1,99 @@ +# Swedish translation vocabulary (`sv_se.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · sv.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand kept Latin · Electrum sv_SE + Zeus sv | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand · Bitcoin Core sv + Zeus sv | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker · lowercase unit per sv.wikipedia.org/wiki/Bitcoin | +| sats | sats | noun, lowercase. | +| sat/vByte | sat/vByte | technical unit; UI keeps Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | plånbok | noun, lowercase · master ships "Plånbok"; native preferred. | +| Vault | valv / kassaskåp | noun · master ships "Multisig Valv"; native. | +| Watch-only | endast granskning / "watch-only" | adj · long form preferred; English form acceptable in quotes if needed. | +| Hardware wallet | hårdvaruplånbok | noun · master compound preferred over "hardware wallet". | +| Seed | seed / mnemonisk fras | noun · `seed` retained as Bitcoin technical loanword; `mnemonisk fras` for prose. | +| Mnemonic | mnemonisk fras | noun · native form per master / Bitcoin Core sv. | +| Passphrase | lösenordsfras | noun · ⚠️ NOT "lösenord" (= password) · master ships "Lösenordsfras". | +| Public key | publik nyckel | noun, lowercase · Electrum sv_SE + Zeus sv | +| Private key | privat nyckel | noun, lowercase · Bitcoin Core sv + Electrum sv_SE | +| WIF | WIF | acronym. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | deskriptor | noun, lowercase · Bitcoin Core sv ("descriptor wallets" → "deskriptor"-plånböcker). | +| Derivation path | härledningsväg | noun · native form preferred over English loanword. | +| Master fingerprint | huvudfingeravtryck | noun · native compound preferred over "master fingerprint". | +| BIP38 | BIP38 | acronym kept · gloss: BIP38-krypterad privat nyckel. | +| **_On-chain transactions_** | | | +| Transaction | transaktion | noun, lowercase · Bitcoin Core sv + Electrum sv_SE | +| Address | adress | noun, lowercase · Bitcoin Core sv + Electrum sv_SE | +| Input | indata / transaktionsindata | noun · short / full · Bitcoin Core sv "Inmatningar". | +| Output | utdata / transaktionsutdata | noun · short / full · ⚠️ NOT the UI recipient label "Till". | +| UTXO | UTXO | acronym · gloss: oanvänd transaktionsutdata. | +| Change | växel / växeladress | noun · ⚠️ NOT verb "ändra". `växel` = leftover coin; `växeladress` for change-address field · Bitcoin Core sv ("Spendera obekräftad växel"). | +| Hex | hex / hexadecimal | noun · short / explanatory · ⚠️ NOT "hash" / NOT "transaktionsdata" · Bitcoin Core sv "hex". | +| Pending | väntande | adj/state · matches shipped "Väntande" in send/transactions. | +| Unconfirmed | obekräftad | adj · Bitcoin Core sv + Electrum sv_SE | +| Confirmed | bekräftad | adj · Bitcoin Core sv | +| Mempool | mempool | noun · English loanword (no established Swedish form). | +| Broadcast | sänd / sända | verb · master ships "SÄND". | +| Block explorer | blockutforskare | noun · native compound preferred. | +| Onchain | på kedjan / onchain | adj · native preferred; loanword in quoted contexts. | +| Offchain | utanför kedjan / offchain | adj · native preferred; loanword in quoted contexts. | +| **_Fees & fee bumping_** | | | +| Fee | avgift | noun, lowercase · Bitcoin Core sv + Electrum sv_SE | +| Fee Bump | höjning av avgift / höj avgift | noun / verb · Electrum sv_SE "Öka avgiften". | +| RBF | RBF | acronym · gloss: Replace-By-Fee · Bitcoin Core sv keeps as-is. | +| CPFP | CPFP | acronym · gloss: barn betalar för förälder. ⚠️ NOT a verb. | +| Speed Up | påskynda | verb · button label. | +| **_Lightning_** | | | +| Invoice | faktura | noun · Electrum sv_SE + Zeus sv | +| Lightning Invoice | Lightning-faktura | noun · Zeus sv "Lightning faktura". | +| Preimage | Pre-image | noun · English loanword kept (no native Swedish equivalent in shipping use; matches en.json). | +| Payment | betalning | noun · ⚠️ NOT verb "betala". | +| Expired | förfallen / utgången | adj · short / alt · Electrum sv_SE "Förfallen"; "utgången" also shipped. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | medundertecknare / medsignerare | noun · ⚠️ NOT "delägare" (co-owner). Matches shipped "Medunderteckna". | +| Quorum | kvorum | noun. | +| PSBT | PSBT | acronym · gloss: delvis signerad Bitcoin-transaktion · Bitcoin Core sv. | +| Provide signature | tillhandahåll signatur / signera | verb · generic / specific. | +| Multisig | multisig | acronym/loanword retained; common in Swedish Bitcoin docs. | +| Multisig Vault | Multisig Valv | master ships "Multisig Valv" (mixed). | +| BIP47 / Payment Code | BIP47 / betalningskod | acronym kept; "Payment Code" → native "betalningskod" per master ("Betalningskod"). | +| Notification transaction | aviseringstransaktion | noun · BIP47-specific. | +| SilentPayment | Silent Payments / SilentPayment | protocol name kept English. | +| **_Coin control_** | | | +| Coin Control | myntkontroll | noun · master ships "Myntkontroll"; native preferred. | +| Frozen | fryst | adj · ⚠️ NOT verb "frysa" · Electrum sv_SE "Adressen är frusen"; Zeus sv "Fryst". | +| **_Security & storage_** | | | +| Encrypted storage | krypterad lagring | noun, lowercase · ⚠️ NOT Title Case. | +| Plausible Deniability | trovärdigt förnekande | noun, lowercase. | +| Biometrics | biometri / biometrisk identifiering | noun, lowercase · Zeus sv "Biometri". | +| Passcode | enhetskod | noun · ⚠️ NOT "lösenord" (= password) — distinct from app password. | +| **_Backup, import & UX_** | | | +| Backup | säkerhetskopia / säkerhetskopiera | noun / verb · Bitcoin Core sv + Electrum sv_SE + Zeus sv | +| Restore | återställ / återställning | verb / noun · Electrum sv_SE "återställ". | +| Import | importera / import | verb / noun · Electrum sv_SE | +| Voucher | kupong | noun, lowercase. | +| Redeem | lös in | verb. | +| Send | skicka | verb · Bitcoin Core sv + Electrum sv_SE | +| Receive | ta emot | verb · Bitcoin Core sv + Electrum sv_SE | +| Settings | inställningar | noun, lowercase · Bitcoin Core sv + Electrum sv_SE | +| Confirm | bekräfta / bekräftelse | verb / noun · Bitcoin Core sv | +| QR Code | QR-kod | noun · Electrum sv_SE + Bitcoin Core sv | +| Clipboard | urklipp | noun, lowercase · Bitcoin Core sv + Electrum sv_SE | +| Memo | notering / memo | noun, lowercase · native preferred for prose; "memo" loanword acceptable. | +| Description | beskrivning | noun, lowercase · Electrum sv_SE + Bitcoin Core sv | +| Label | etikett | noun, lowercase · Electrum sv_SE + Bitcoin Core sv | +| Hide | Dölj | verb · preferred over "Göm" in current UI. | diff --git a/loc/vocabulary/th_th.md b/loc/vocabulary/th_th.md new file mode 100644 index 00000000000..4095d18bc04 --- /dev/null +++ b/loc/vocabulary/th_th.md @@ -0,0 +1,96 @@ +# Thai translation vocabulary (`th_th.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / บิตคอยน์ | brand kept Latin; บิตคอยน์ in body text · th.wikipedia.org/wiki/บิตคอยน์ | +| Lightning | Lightning | brand kept Latin · Thai script ไลท์นิง only as explanatory gloss. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand acronym. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand · th.wikipedia.org/wiki/Tor_(เครือข่ายนิรนาม) | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | บิตคอยน์ / BTC | noun unit + ticker. | +| sats | แซท / ซาโตชิ | noun · Thai transliteration; ซาโตชิ for full form. | +| sat/vByte | sat/vByte | technical unit; UI keeps Latin (casing matters). | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | กระเป๋าสตางค์ / กระเป๋าเงิน | noun · primary / shorter form · Bitcoin Core th | +| Vault | ห้องนิรภัย / ตู้นิรภัย | noun · safe room / strongbox · ⚠️ NOT brand "Vault". | +| Watch-only | ดูอย่างเดียว / เฝ้าดูเท่านั้น | adj · ⚠️ NOT "view mode"; wallet type · Bitcoin Core th ("กระเป๋าที่ดูอย่างเดียว") | +| Hardware wallet | กระเป๋าฮาร์ดแวร์ / กระเป๋าเงินฮาร์ดแวร์ | noun · Bitcoin Core th | +| Seed | ซีด / วลีกู้คืน | noun · technical / mainstream ("recovery phrase"). | +| Mnemonic | วลีนีโมนิก / วลีกู้คืน | noun · technical / mainstream. | +| Passphrase | วลีรหัสผ่าน | noun · ⚠️ distinct from `รหัสผ่าน` (password) · Bitcoin Core th ("วลีผ่าน/วลีรหัส") + Electrum th_TH | +| Public key | กุญแจสาธารณะ / คีย์สาธารณะ | noun · th.wikipedia.org/wiki/บิตคอยน์ + Electrum th_TH | +| Private key | กุญแจส่วนตัว / คีย์ส่วนตัว | noun · Bitcoin Core th + th.wikipedia.org/wiki/บิตคอยน์ | +| WIF | WIF | acronym · gloss: รูปแบบนำเข้ากระเป๋า (Wallet Import Format). | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | ดิสคริปเตอร์ / ตัวอธิบายเอาต์พุต | noun · transliteration / explanatory. | +| Derivation path | เส้นทางอนุพันธ์ / เส้นทาง derivation | noun · technical / mixed Latin · BIP32 path. | +| Master fingerprint | ลายนิ้วมือต้นแบบ / ลายนิ้วมือกุญแจหลัก | noun · ships `ลายนิ้วมือต้นแบบ`. | +| BIP38 | BIP38 | acronym kept · gloss: รหัสผ่าน BIP38 / รหัสผ่านสำหรับถอดรหัส BIP38. | +| **_On-chain transactions_** | | | +| Transaction | ธุรกรรม | noun. | +| Address | แอดเดรส / ที่อยู่ | noun · transliteration (shipped) / mainstream · th.wikipedia.org/wiki/บิตคอยน์ uses ที่อยู่. | +| Input | อินพุต / อินพุตธุรกรรม | noun · short / full · ⚠️ NOT "login/entrance". | +| Output | เอาต์พุต / เอาต์พุตธุรกรรม | noun · short / full · ⚠️ NOT UI recipient label "ถึง:". | +| UTXO | UTXO | acronym · gloss: เอาต์พุตธุรกรรมที่ยังไม่ถูกใช้. | +| Change | เงินทอน / แอดเดรสเงินทอน | noun · ⚠️ NOT verb "เปลี่ยน"; `เงินทอน` = leftover coin · Bitcoin Core th ("เงินทอน"). | +| Hex | hex / เลขฐานสิบหก | noun · short Latin / Thai explanatory · ⚠️ NOT "hash" / NOT "ข้อมูลธุรกรรม" · th.wikipedia.org/wiki/เลขฐานสิบหก | +| Pending | กำลังดำเนินการ / รอดำเนินการ | adj/state · ships `กำลังดำเนินการ`. | +| Unconfirmed | ยังไม่ยืนยัน / ยังไม่ได้รับการยืนยัน | adj · short / full · Bitcoin Core th ("ยังไม่ได้รับการยืนยัน"). | +| Confirmed | ยืนยันแล้ว | adj/state · Bitcoin Core th. | +| Mempool | เมมพูล / mempool | noun · Thai transliteration / Latin · Bitcoin Core th keeps `mempool`. | +| Broadcast | บรอดคาสต์ / เผยแพร่ | verb · transliteration (shipped) / Thai equivalent. | +| Block explorer | ตัวสำรวจบล็อก / Block Explorer | noun · Thai / Latin. | +| Onchain | ออนเชน / บนบล็อกเชน | adj · compact (chip) / explanatory (body). | +| Offchain | ออฟเชน / นอกบล็อกเชน | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | ค่าธรรมเนียม | noun. | +| Fee Bump | เพิ่มค่าธรรมเนียม | noun. | +| RBF | RBF | acronym · gloss: แทนที่ด้วยค่าธรรมเนียม (Replace-By-Fee). | +| CPFP | CPFP | acronym · gloss: ลูกจ่ายแทนแม่ (Child-Pays-For-Parent). ⚠️ NOT a verb. | +| Speed Up | เพิ่มความเร็ว / เพิ่มค่าธรรมเนียม | verb · button label for RBF. | +| **_Lightning_** | | | +| Invoice | ใบแจ้งหนี้ / อินวอยซ์ | noun · mainstream / technical transliteration · Electrum th_TH ("ใบแจ้งหนี้"). | +| Lightning Invoice | ใบแจ้งหนี้ Lightning / ใบแจ้งหนี้ไลท์นิง | noun · brand Latin + Thai noun. | +| Preimage | พรีอิมเมจ / ภาพต้นแบบ | noun · transliteration / math term. | +| Payment | การชำระเงิน / การจ่ายเงิน | noun · ⚠️ NOT verb "จ่าย" (shipped `จ่าย` is verb — must be noun). | +| Expired | หมดอายุแล้ว | adj/state · ships correctly. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | ผู้ร่วมลงนาม / ผู้ลงนามร่วม | noun · ⚠️ NOT "co-owner" (เจ้าของร่วม). | +| Quorum | องค์ประชุม / เกณฑ์ลายเซ็น | noun · canonical / UI-clear (signature threshold). | +| PSBT | PSBT | acronym · gloss: ธุรกรรม Bitcoin ที่ลงนามบางส่วน · Bitcoin Core th. | +| Provide signature | ใส่ลายเซ็น / ลงนามธุรกรรม | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / รหัสการชำระเงิน | acronym kept; "Payment Code" → "รหัสการชำระเงิน". | +| Notification transaction | ธุรกรรมแจ้งเตือน | noun · BIP47-specific 0-value tx. | +| SilentPayment | Silent Payments / การชำระเงินแบบเงียบ | protocol name kept English (plural); explanatory Thai gloss. | +| **_Coin control_** | | | +| Coin Control | ควบคุมเหรียญ / จัดการ UTXO | noun · mainstream / technical · ⚠️ NOT Title Case · Bitcoin Core th ("การควบคุมเหรียญ") + Electrum th_TH. | +| Frozen | ถูกระงับ / ระงับ | adj/state · ⚠️ NOT verb "ระงับ" alone in active sense; prefer `ถูกระงับ`. | +| **_Security & storage_** | | | +| Encrypted storage | ที่เก็บข้อมูลเข้ารหัส | noun · ⚠️ NOT Title Case · derived from shipped `ที่เก็บข้อมูลของคุณถูกเข้ารหัส`. | +| Plausible Deniability | การปฏิเสธที่เป็นไปได้ / การปฏิเสธอย่างมีเหตุผล | noun · short / full. | +| Biometrics | ไบโอเมตริก / การยืนยันตัวตนทางชีวภาพ | noun · th.wikipedia.org/wiki/ไบโอเมตริกซ์. | +| Passcode | รหัสผ่านอุปกรณ์ / PIN | noun. | +| **_Backup, import & UX_** | | | +| Backup | สำรองข้อมูล / สำรอง | noun / verb · Bitcoin Core th ("สำรองข้อมูล"). | +| Restore | กู้คืน / การกู้คืน | verb / noun · Electrum th_TH ("การกู้คืน BIP39"). | +| Import | นำเข้า / การนำเข้า | verb / noun · ships `นำเข้า`. | +| Voucher | บัตรกำนัล | noun. | +| Redeem | ไถ่ถอน / ใช้บัตร | verb · ⚠️ NOT "ซื้อเข้ากระเป๋า"; for vouchers use `ไถ่ถอน` (shipped). | +| Send | ส่ง | verb. | +| Receive | รับ | verb. | +| Settings | ตั้งค่า / การตั้งค่า | noun. | +| Confirm | ยืนยัน / การยืนยัน | verb / noun. | +| QR Code | คิวอาร์โค้ด / รหัสคิวอาร์ | noun · th.wikipedia.org/wiki/QR_Code. | +| Clipboard | คลิปบอร์ด | noun · Bitcoin Core th + Electrum th_TH. | +| Memo | บันทึกช่วยจำ / โน้ต | noun. | +| Description | คำอธิบาย | noun. | +| Label | ป้ายกำกับ / ป้าย | noun · Bitcoin Core th ("ป้ายกำกับ"). | diff --git a/loc/vocabulary/tr_tr.md b/loc/vocabulary/tr_tr.md new file mode 100644 index 00000000000..cc5a992df0a --- /dev/null +++ b/loc/vocabulary/tr_tr.md @@ -0,0 +1,98 @@ +# Turkish translation vocabulary (`tr_tr.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · tr.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand kept Latin · tr.wikipedia.org/wiki/Lightning_Network | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker · tr.wikipedia.org/wiki/Bitcoin | +| sats | sat / satoshi | noun, lowercase · singular / plural · shipped singular `sat` · units.sats | +| sat/vByte | sat/vByte | technical unit, Latin kept; SegWit-aware. | +| vByte | vByte | technical unit; SegWit-discounted size. | +| **_Wallet, keys & seeds_** | | | +| Wallet | cüzdan | noun · shipped casing inconsistent (Title vs lower) · Bitcoin Core tr + Electrum tr | +| Vault | Kasa | noun · `Kasa` (= safe/strongbox) — master ships native. | +| Watch-only | yalnızca izleme / watch-only | adj · native compound preferred; English loanword acceptable in quotes · ⚠️ NOT "görüntüleme modu". | +| Hardware wallet | donanım cüzdanı | noun · native possessive compound preferred. | +| Seed | seed | noun · loanword retained for Bitcoin context; native equivalent rare. | +| Mnemonic | mnemonik | noun · transliterated loanword (Turkish-flavored). | +| Passphrase | parola | noun · ⚠️ NOT `şifre` (= app password) / NOT `PIN` (= passcode) — distinct word · Trezor tr + tr.wikipedia.org/wiki/Parola | +| Public key | açık anahtar | noun, lowercase · Electrum tr (Bitcoin Core tr uses `Ortak Anahtar`) | +| Private key | gizli anahtar | noun, lowercase · Bitcoin Core tr + Electrum tr | +| WIF | WIF | acronym · gloss: Wallet Import Format. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | descriptor / çıktı tanımlayıcı | noun · loanword / explanatory · Bitcoin Core tr keeps `descriptor` | +| Derivation path | türetme yolu | noun · Bitcoin Core tr + Electrum tr + Trezor tr | +| Master fingerprint | Ana Parmak İzi / fingerprint | noun · shipped `Ana Parmak İzi`; standalone `fingerprint` accepted as English loanword. | +| BIP38 | BIP38 | acronym kept · gloss: parola korumalı özel anahtar. ⚠️ NOT a verb / NOT just "parola". | +| **_On-chain transactions_** | | | +| Transaction | işlem | noun · shipped `transactions.transaction` · Bitcoin Core tr + Electrum tr | +| Address | adres | noun · shipped `details_address` · Bitcoin Core tr + Electrum tr | +| Input | girdi | noun · ⚠️ NOT "giriş" (login) when ambiguous · shipped `details_from` uses `Girdi` · Bitcoin Core tr + Electrum tr | +| Output | çıktı | noun · shipped `details_to` uses `Çıktı` · ⚠️ NOT recipient label "Kime:" · Bitcoin Core tr + Electrum tr | +| UTXO | UTXO | acronym · gloss: harcanmamış işlem çıktısı. | +| Change | para üstü | noun · ⚠️ NOT verb `değiştir` (= to change/modify) — coin-change noun · Electrum tr | +| Hex | hex / onaltılık | noun · compact / explanatory · shipped `broadcastNone` uses `Hex` · ⚠️ NOT "hash". | +| Pending | beklemede | adj/state · shipped `transactions.pending`. | +| Unconfirmed | onaylanmamış | adj · Bitcoin Core tr + Electrum tr | +| Confirmed | onaylanmış | adj/state · Electrum tr (Bitcoin Core tr uses `Doğrulandı`) | +| Mempool | mempool | noun · English loanword preferred · ⚠️ NOT `bellek havuzu`. | +| Broadcast | yayınla / yayın | verb / noun · button vs status · shipped `broadcastButton` `Yayınla`, `errors.broadcast` `Yayın`. | +| Block explorer | blok tarayıcısı | noun · native compound preferred; English loanword acceptable in technical contexts. | +| Onchain | zincir üstü / on-chain | adj · native preferred; English loanword in quoted contexts. | +| Offchain | zincir dışı / off-chain | adj · native preferred; English loanword in quoted contexts. | +| Mined | madenlenir / mine edilir | verb (passive) · native preferred; loanword acceptable. | +| ETA | ETA | acronym kept untranslated · ⚠️ NOT `Tahmini`. | +| **_Fees & fee bumping_** | | | +| Fee | ücret | noun · shipped `create_fee` `Ücret` · Bitcoin Core tr + Electrum tr | +| Fee Bump | ücret artırımı / ücret artırımına izin ver | compact / shipped explanatory · noun form preferred · Electrum tr (`Komisyonu yeniden ayarlayabilme`) | +| RBF | RBF—Replace by Fee | acronym + English gloss · ⚠️ NOT `Ücret-ile-Değiştirme`. | +| CPFP | CPFP—Child Pays for Parent | acronym + English gloss · ⚠️ NOT a verb · ⚠️ NOT `Çocuğun Ebeveyne Ödemesi`. | +| Speed Up | hızlandır | verb · UI button label for RBF · Phoenix tr + Trezor tr | +| **_Lightning_** | | | +| Invoice | fatura | noun · shipped `lightning_invoice` · Bitcoin Core tr + Electrum tr + Phoenix tr | +| Lightning Invoice | Lightning faturası | noun, possessive suffix · shipped `lndViewInvoice.lightning_invoice` | +| Preimage | Pre-image | noun · English loanword kept (no established native form). | +| Payment | ödeme | noun · ⚠️ NOT verb `öde` · Electrum tr + Phoenix tr | +| Expired | süresi doldu | adj/state · shipped `lnd.expired`. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | ortak imzalayan | noun · ⚠️ NOT `ortak sahip` (= co-owner) · Electrum tr | +| Quorum | yetersayı / Quorum | noun · native preferred; loanword acceptable in technical contexts. | +| PSBT | PSBT | acronym · gloss: kısmen imzalanmış Bitcoin işlemi (BIP174) · Bitcoin Core tr | +| Provide signature | imza sağla / işlemi imzala | verb · generic / specific · shipped `co_sign_transaction` uses `Bir işlemi imzalayın`. | +| BIP47 / Payment Code | BIP47 / ödeme kodu | acronym kept; `Payment Code` rendered native `ödeme kodu`. | +| Notification transaction | bildirim işlemi | noun · BIP47-specific 0-value tx. | +| SilentPayment | Silent Payments / sessiz ödemeler | protocol name kept English (plural); explanatory `sessiz ödemeler` if needed. | +| **_Coin control_** | | | +| Coin Control | Para kontrolü / Coin Control | noun · native compound preferred; loanword acceptable. | +| Frozen | donmuş | adj/state · ⚠️ NOT verb `dondur` (= to freeze) · Electrum tr | +| **_Security & storage_** | | | +| Encrypted storage | şifreli depolama | noun · ⚠️ NOT Title Case · shipped `_.storage_is_encrypted` uses `Depolama alanınız şifrelenmiş`. | +| Plausible Deniability | makul ret / makul inkâr edilebilirlik | noun · ⚠️ NOT Title Case · shipped / canonical · tr.wikipedia.org/wiki/Makul_inkar_edilebilirlik | +| Biometrics | biyometrikler / biyometrik doğrulama | noun · shipped `settings.biometrics` · Phoenix tr (`Biyometrik doğrulama`) | +| Passcode | PIN / cihaz kodu | noun · ⚠️ NOT `şifre` / NOT `parola` — device-level code · Electrum tr + Trezor tr (`PIN kodu`) | +| **_Backup, import & UX_** | | | +| Backup | yedek / yedekle | noun / verb · shipped `export_title` uses verb `yedekle`. | +| Restore | geri yükle / geri yükleme | verb / noun · Bitcoin Core tr + Electrum tr | +| Import | içe aktar / içeri yükle | verb · shipped uses both `İçe Aktar` (`import_do_import`) and `içeri yükle` (`import_title`). | +| Voucher | kupon | noun · shipped `azteco.title` (`Azte.co kuponu`). | +| Redeem | bozdur / yükle | verb · ⚠️ NOT "satın al" — activate/cash-in · shipped `azteco.redeem` `Cüzdana yükle`, `redeemButton` `Yükle`. | +| Send | gönder | verb · shipped `send.header`. | +| Receive | al | verb · shipped `receive.header`. | +| Settings | ayarlar | noun · shipped `settings.header`. | +| Confirm | onayla / onay | verb / noun · plural noun `onaylar` for on-chain confirmations · shipped `confirm_header` `Onayla`. | +| QR Code | QR kodu | noun · Bitcoin Core tr + Electrum tr | +| Clipboard | pano | noun · shipped `_.clipboard` · Bitcoin Core tr + Electrum tr | +| Memo | not | noun · shipped `send.create_memo`. | +| Description | açıklama | noun · shipped `receive.details_label`. | +| Label | etiket | noun · shipped `cc.sort_label` · Electrum tr + Trezor tr | diff --git a/loc/vocabulary/ua.md b/loc/vocabulary/ua.md new file mode 100644 index 00000000000..6e66fb98078 --- /dev/null +++ b/loc/vocabulary/ua.md @@ -0,0 +1,96 @@ +# Ukrainian translation vocabulary (`ua.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / Біткойн | brand kept Latin; Біткойн in explanatory text · uk.wikipedia.org/wiki/Біткойн | +| Lightning | Lightning | brand · uk.wikipedia.org/wiki/Lightning_Network | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand · Electrum uk keeps as-is. | +| Tor | Tor | brand · uk.wikipedia.org/wiki/Tor | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | біткойн / BTC | noun unit + ticker. | +| sats | сатоші | noun, lowercase. | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | гаманець | noun, lowercase. | +| Vault | сейф / сховище | noun; `сейф` for safe/strongbox sense. Avoid Latin "Vault". | +| Watch-only | лише для перегляду / тільки для перегляду | adj · Trezor uk + Green uk | +| Hardware wallet | апаратний гаманець | noun, lowercase · Trezor uk + Green uk | +| Seed | seed-фраза / фраза відновлення | noun; mainstream user-facing form preferred. | +| Mnemonic | мнемонічна фраза / фраза відновлення | noun · technical / mainstream. | +| Passphrase | кодова фраза | noun · ⚠️ distinct from `пароль` (password) · Electrum uk + Trezor uk | +| Public key | публічний ключ | noun, lowercase · Trezor uk + Zeus uk + Cake uk | +| Private key | приватний ключ | noun, lowercase. | +| WIF | WIF | acronym · Cake uk keeps. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | дескриптор | noun, lowercase · Zeus uk. | +| Derivation path | шлях деривації / шлях виведення | noun · canonical BIP32 / alt form · Electrum uk | +| Master fingerprint | відбиток майстер-ключа / відбиток головного ключа | noun · Zeus uk | +| BIP38 | BIP38 | acronym kept · gloss: пароль BIP38 / пароль для розшифрування BIP38. | +| **_On-chain transactions_** | | | +| Transaction | транзакція | noun, lowercase. | +| Address | адреса | noun, lowercase. | +| Input | вхід / вхід транзакції | noun · short / full · Electrum uk + Bitcoin Core uk | +| Output | вихід / вихід транзакції | noun · short / full · ⚠️ NOT "Кому" (that's UI recipient label). | +| UTXO | UTXO | acronym · gloss: невитрачений вихід транзакції. | +| Change | здача / адреса здачі | noun · ⚠️ NOT verb "змінити". `здача` = leftover coin; `адреса здачі` for change-address field. | +| Hex | hex-дані / шістнадцяткові дані | noun · short / explanatory · ⚠️ NOT "hash" / NOT "дані транзакції". | +| Pending | очікує / в очікуванні | adj/state · button vs body. Avoid "Очікування" (noun). | +| Unconfirmed | непідтверджено / непідтверджена | adj · state / feminine-agreement form · Electrum uk + Trezor uk | +| Confirmed | підтверджено / підтверджена | adj · state / feminine-agreement form · Electrum uk + Bitcoin Core uk | +| Mempool | мемпул | noun · Electrum uk | +| Broadcast | надіслати в мережу / транслювати | verb · UI-clear / technical. Noun form: трансляція. | +| Block explorer | оглядач блоків | noun, lowercase · Electrum uk | +| Onchain | он-чейн / у блокчейні | adj · compact (chip) / explanatory (body) | +| Offchain | оф-чейн / поза блокчейном | adj · compact (chip) / explanatory (body) | +| **_Fees & fee bumping_** | | | +| Fee | комісія | noun, lowercase. | +| Fee Bump | збільшення комісії | noun · Electrum uk | +| RBF | RBF | acronym · gloss: замінити за комісією / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: дочірня транзакція платить за батьківську. ⚠️ NOT "Створити". | +| Speed Up | прискорити | verb · Trezor uk | +| **_Lightning_** | | | +| Invoice | інвойс / рахунок | noun · technical / mainstream. | +| Lightning Invoice | інвойс Lightning / платіжний запит Lightning | noun · technical / mainstream. | +| Preimage | преімідж | noun · transliteration of English "preimage". | +| Payment | платіж | noun · ⚠️ NOT verb "Оплатити". | +| Expired | прострочено / термін дії закінчився | adj · short / explanatory. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | співпідписант | noun · ⚠️ NOT "співвласник" (co-owner). | +| Quorum | кворум / поріг підписів | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym. | +| Provide signature | надати підпис / підписати транзакцію | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / платіжний код | acronym kept; "Payment Code" → "платіжний код". | +| Notification transaction | транзакція сповіщення | noun · BIP47-specific. | +| SilentPayment | Silent Payments / тихі платежі | protocol name kept English (plural); explanatory `тихі платежі` if needed. | +| **_Coin control_** | | | +| Coin Control | керування UTXO / керування монетами | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case. | +| Frozen | заморожено / заморожений | adj · state / masc-agreement · ⚠️ NOT verb "заморозити". | +| **_Security & storage_** | | | +| Encrypted storage | зашифроване сховище | noun, lowercase · ⚠️ NOT Title Case. | +| Plausible Deniability | правдоподібне заперечення / можливість правдоподібного заперечення | noun, lowercase · short / full. | +| Biometrics | біометрія | noun, lowercase. | +| Passcode | код доступу | noun · ⚠️ NOT "пароль" (= password). | +| **_Backup, import & UX_** | | | +| Backup | резервна копія / зробити резервну копію | noun / verb · Electrum uk + Bitcoin Core uk | +| Restore | відновити / відновлення | verb / noun · Bitcoin Core uk | +| Import | імпортувати / імпорт | verb / noun. | +| Voucher | ваучер | noun, lowercase. | +| Redeem | активувати / погасити | verb · ⚠️ NOT "Купити на гаманець". For vouchers prefer `активувати`. | +| Send | надіслати / відправити | verb. | +| Receive | отримати | verb. | +| Settings | налаштування | noun, lowercase. | +| Confirm | підтвердити / підтвердження | verb / noun. | +| QR Code | QR-код | noun · Electrum uk + Bitcoin Core uk | +| Clipboard | буфер обміну | noun, lowercase. | +| Memo | примітка / нотатка | noun, lowercase. | +| Description | опис | noun, lowercase · Electrum uk | +| Label | мітка / тег | noun, lowercase · Electrum uk | diff --git a/loc/vocabulary/vi_vn.md b/loc/vocabulary/vi_vn.md new file mode 100644 index 00000000000..c1dddc7af3e --- /dev/null +++ b/loc/vocabulary/vi_vn.md @@ -0,0 +1,96 @@ +# Vietnamese translation vocabulary (`vi_vn.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | brand kept Latin · vi.wikipedia.org/wiki/Bitcoin | +| Lightning | Lightning | brand · vi.wikipedia.org/wiki/Lightning_Network keeps Latin. | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand · Zeus vi keeps `Tor`. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | noun unit + ticker; lowercase unit. | +| sats | sats | lowercase, kept Latin per convention. | +| sat/vByte | sat/vByte | technical unit; kept Latin. | +| vByte | vByte | technical unit. | +| **_Wallet, keys & seeds_** | | | +| Wallet | ví | noun, lowercase. Plural via `các ví`. | +| Vault | két / két an toàn | noun · ⚠️ NOT brand; Vietnamese word for safe/strongbox. | +| Watch-only | chỉ xem | adj · Zeus vi `Chỉ Xem`. | +| Hardware wallet | ví phần cứng | noun, lowercase. | +| Seed | cụm từ khôi phục / seed | noun · mainstream / loanword · ⚠️ NOT `hạt giống` (botanical). | +| Mnemonic | cụm từ ghi nhớ / cụm từ khôi phục | noun · technical / mainstream. | +| Passphrase | cụm mật khẩu | noun · ⚠️ NOT `mật khẩu` (= password) and NOT `mã PIN` (= passcode). | +| Public key | khóa công khai | noun, lowercase. | +| Private key | khóa riêng tư | noun, lowercase · prefer `riêng tư` over `cá nhân` for asymmetric-crypto sense. | +| WIF | WIF | acronym · gloss: định dạng nhập ví. | +| xpub | xpub | acronym, lowercase preferred. | +| Descriptor | bộ mô tả | noun, lowercase · script-template sense. | +| Derivation path | đường dẫn dẫn xuất | noun, lowercase · BIP32 path. | +| Master fingerprint | vân tay khóa chính | noun · Zeus vi `Vân tay khóa chính`. | +| BIP38 | BIP38 | acronym kept · gloss: khóa riêng tư mã hóa bằng mật khẩu. | +| **_On-chain transactions_** | | | +| Transaction | giao dịch | noun, lowercase. | +| Address | địa chỉ | noun, lowercase. | +| Input | đầu vào / đầu vào giao dịch | noun · short / full. ⚠️ NOT "đăng nhập" (= login). | +| Output | đầu ra / đầu ra giao dịch | noun · short / full · ⚠️ NOT the UI recipient label "Đến:". | +| UTXO | UTXO | acronym · gloss: đầu ra giao dịch chưa tiêu · Zeus vi keeps `UTXO`. | +| Change | tiền thừa / địa chỉ tiền thừa | noun · ⚠️ NOT verb `thay đổi`. `tiền thừa` = leftover; `địa chỉ tiền thừa` for change-address. | +| Hex | hex / dữ liệu hex | noun · short / explanatory · ⚠️ NOT `hash` and NOT `dữ liệu giao dịch`; shipped `hex` (Latin) acceptable as compact form. | +| Pending | đang chờ | adj/state · ⚠️ NOT noun "sự chờ đợi". | +| Unconfirmed | chưa xác nhận | adj/state. | +| Confirmed | đã xác nhận | adj/state. | +| Mempool | mempool | noun · kept Latin (no native rendering); Zeus vi keeps `Mempool`. | +| Broadcast | phát đi mạng / truyền tải | verb · UI-clear / technical · Zeus vi `Truyền tải`. | +| Block explorer | trình khám phá khối | noun, lowercase · Zeus vi `Trình khám phá khối`. | +| Onchain | onchain / trên chuỗi | adj · compact (chip) / explanatory (body) · Zeus vi `Trên chuỗi`. | +| Offchain | offchain / ngoài chuỗi | adj · compact (chip) / explanatory (body). | +| **_Fees & fee bumping_** | | | +| Fee | phí | noun, lowercase. | +| Fee Bump | tăng phí | noun, lowercase. | +| RBF | RBF | acronym · gloss: thay thế bằng phí cao hơn / Replace-By-Fee. | +| CPFP | CPFP | acronym · gloss: con trả phí cho cha · ⚠️ NOT a verb. | +| Speed Up | tăng tốc | verb · button label. | +| **_Lightning_** | | | +| Invoice | hóa đơn / yêu cầu thanh toán | noun · technical / mainstream · Zeus vi `Hoá đơn`. | +| Lightning Invoice | hóa đơn Lightning / yêu cầu thanh toán Lightning | noun · technical / mainstream. | +| Preimage | nghịch ảnh / preimage | noun · math term / loanword · ⚠️ NOT `tiền ảnh` (reads as "virtual money"). Zeus vi keeps `Preimage`. | +| Payment | khoản thanh toán / thanh toán | noun · noun / colloquial-verb-as-noun · ⚠️ NOT bare verb `thanh toán` (= to pay). | +| Expired | hết hạn | adj/state. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | người đồng ký / người đồng ký kết | noun · ⚠️ NOT `đồng sở hữu` (co-owner). | +| Quorum | túc số / ngưỡng chữ ký | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym. | +| Provide signature | cung cấp chữ ký / ký giao dịch | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / mã thanh toán | acronym kept; `Payment Code` → `mã thanh toán`. | +| Notification transaction | giao dịch thông báo | noun · BIP47-specific. | +| SilentPayment | Silent Payments / thanh toán im lặng | protocol name kept English (plural); explanatory `thanh toán im lặng` if needed. | +| **_Coin control_** | | | +| Coin Control | kiểm soát UTXO / kiểm soát coin | noun, lowercase · technical / mainstream · ⚠️ NOT Title Case. | +| Frozen | đã đóng băng | adj/state · ⚠️ NOT verb `đóng băng`. Zeus vi `Đóng băng`. | +| **_Security & storage_** | | | +| Encrypted storage | lưu trữ được mã hóa | noun, lowercase · ⚠️ NOT Title Case. | +| Plausible Deniability | khả năng phủ nhận hợp lý / sự từ chối hợp lý | noun, lowercase · short / full. | +| Biometrics | sinh trắc học | noun, lowercase · Zeus vi `Sinh trắc học`. | +| Passcode | mã PIN / mã mở khóa | noun · ⚠️ NOT `mật khẩu` (= password). | +| **_Backup, import & UX_** | | | +| Backup | bản sao lưu / sao lưu | noun / verb. | +| Restore | khôi phục / việc khôi phục | verb / noun. | +| Import | nhập / việc nhập | verb / noun. | +| Voucher | phiếu / voucher | noun, lowercase · mainstream / loanword. | +| Redeem | đổi / kích hoạt | verb · ⚠️ NOT "mua vào ví" / NOT "chuyển". | +| Send | gửi | verb. | +| Receive | nhận | verb. | +| Settings | cài đặt | noun, lowercase. | +| Confirm | xác nhận / sự xác nhận | verb / noun (also "confirmations" = blocks). | +| QR Code | mã QR | noun, lowercase. | +| Clipboard | bảng tạm | noun, lowercase. | +| Memo | ghi chú | noun, lowercase · Zeus vi `Ghi chú`. | +| Description | mô tả | noun, lowercase. | +| Label | nhãn / mác | noun, lowercase. | diff --git a/loc/vocabulary/zar_afr.md b/loc/vocabulary/zar_afr.md new file mode 100644 index 00000000000..e7c0934775a --- /dev/null +++ b/loc/vocabulary/zar_afr.md @@ -0,0 +1,96 @@ +# Afrikaans translation vocabulary (`zar_afr.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | — | +| Lightning | Lightning | — | +| Electrum | Electrum | brand · keep Latin. | +| LNDhub | LNDhub | brand · keep Latin. Shipped `Knoop-punt` ("node-point") is a wrong-translation bug — see vocabulary.md Open TODOs. | +| LND | LND | brand · keep Latin. | +| LNURL | LNURL | brand · keep Latin. | +| Tor | Tor | brand · keep Latin. | +| Orbot | Orbot | brand · keep Latin. | +| GroundControl | GroundControl | brand · keep Latin. | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | unit name lowercase, ticker uppercase. | +| sats | sats | — | +| sat/vByte | sat/vByte | — | +| vByte | vByte | — | +| **_Wallet, keys & seeds_** | | | +| Wallet | beursie | "purse/wallet". | +| Vault | kluis | "safe / strongbox" — translatable noun, NOT a brand. | +| Watch-only | slegs-kyk | "view-only"; ⚠️ NOT "view mode" generic — it is a wallet type. | +| Hardware wallet | hardeware-beursie | compound noun, lowercase. | +| Seed | saad | Literal "seed". | +| Mnemonic | terugval woorde | "fallback words". | +| Passphrase | wagfrase | ⚠️ NOT `wagwoord` (= password) and NOT `wagkode` (= passcode). | +| Public key | openbare sleutel | lowercase common noun. | +| Private key | private sleutel | lowercase common noun. | +| WIF | WIF | acronym kept; gloss: Wallet Import Format. | +| xpub | xpub | acronym kept lowercase per glossary. | +| Descriptor | deskriptor | noun, lowercase · output descriptor; cognate spelling (no upstream wallet citation). | +| Derivation path | afleidingspad | noun · `afleiding` (derivation) + `pad` (path); cognate compound (no upstream wallet citation). | +| Master fingerprint | meester-vingerafdruk | noun · cognate compound (no upstream wallet citation). | +| BIP38 | BIP38 | acronym kept. ⚠️ NOT a verb / NOT "password" alone. | +| **_On-chain transactions_** | | | +| Transaction | transaksie | · af.wikipedia.org/wiki/Bitcoin. | +| Address | adres | — | +| Input | inset | — | +| Output | uitset | noun, lowercase. ⚠️ Shipped `Resultaat` (= "result") is wrong sense for tx output. | +| UTXO | UTXO | acronym kept. | +| Change | kleingeld / wisselgeld | noun · ⚠️ NOT verb "verander" — `kleingeld`/`wisselgeld` = "change/leftover" (no upstream wallet citation). | +| Hex | hex | — | +| Pending | hangende | adjective/state form. ⚠️ NOT the noun "wagting". | +| Unconfirmed | onbevestig | adjective state form. | +| Confirmed | bevestig | adjective state form. | +| Mempool | mempool | technical term kept; Afrikaans Bitcoin coverage is thin. | +| Broadcast | saai uit / sending | verb / noun. | +| Block explorer | blokverkenner | noun · "block explorer" — `blok` + `verkenner` (no upstream wallet citation). | +| Onchain | onchain / op die ketting | adj · compact (chip) / explanatory (body) (no upstream wallet citation). | +| Offchain | offchain / af die ketting | adj · compact (chip) / explanatory (body) (no upstream wallet citation). | +| **_Fees & fee bumping_** | | | +| Fee | fooi | — | +| Fee Bump | fooi-verhoging | noun · "fee raise" — umbrella term for RBF + CPFP (no upstream wallet citation). | +| RBF | RBF | acronym kept (Replace-by-fee). | +| CPFP | CPFP | acronym kept (Child-pays-for-parent). ⚠️ NOT a verb. | +| Speed Up | versnel | verb · "speed up / accelerate" — standard Afrikaans (no upstream wallet citation). | +| **_Lightning_** | | | +| Invoice | faktuur / rekening | technical / mainstream. | +| Lightning Invoice | Lightning faktuur | brand kept Latin, noun lowercase. | +| Preimage | TODO | math term; uncertain Afrikaans rendering (candidates: `voorafbeeld`). | +| Payment | betaling | ⚠️ NOT the verb "betaal". | +| Expired | vervalle | adjective/state form, lowercase. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | mede-tekenaar | ⚠️ NOT "mede-eienaar" (co-owner) — must be a signer noun. | +| Quorum | kworum / handtekeningdrempel | noun · canonical / UI-clear ("signature threshold") (no upstream wallet citation). | +| PSBT | PSBT | acronym kept (Partially Signed Bitcoin Transaction). | +| Provide signature | teken 'n transaksie | "sign a transaction". | +| BIP47 / Payment Code | BIP47 / betalingskode | acronym + translatable noun "payment code". | +| Notification transaction | kennisgewing-transaksie | noun · BIP47-specific (no upstream wallet citation). | +| SilentPayment | Silent Payments | brand-ish protocol name; keep English. | +| **_Coin control_** | | | +| Coin Control | muntbeheer / UTXO-beheer | noun, lowercase · mainstream / technical · ⚠️ NOT Title Case (no upstream wallet citation). | +| Frozen | gevries | adjective/state form. ⚠️ NOT the verb "vries / bevries". | +| **_Security & storage_** | | | +| Encrypted storage | ge-enkripteer (geheue spasie) | ⚠️ NOT Title Case. | +| Plausible Deniability | geloofwaardige ontkenbaarheid | ⚠️ NOT Title Case. | +| Biometrics | biometrie | common noun, lowercase. | +| Passcode | toegangskode | ⚠️ NOT app password (`wagwoord`) — device-level unlock code. | +| **_Backup, import & UX_** | | | +| Backup | rugsteun / rugsteun maak | noun / verb. | +| Restore | herstel | verb / noun. | +| Import | invoer | — | +| Voucher | koepon | "coupon". | +| Redeem | eis / aktiveer | "claim / activate". | +| Send | stuur | — | +| Receive | ontvang | — | +| Settings | instellings | — | +| Confirm | bevestig | — | +| QR Code | QR-kode | · af.wikipedia.org/wiki/QR-kode. Shipped UI uses Latin `QR Code`. | +| Clipboard | knipbord | — | +| Memo | nota | "note"; preferred sense for outgoing-tx note. | +| Description | beskrywing | — | +| Label | etiket | — | diff --git a/loc/vocabulary/zar_xho.md b/loc/vocabulary/zar_xho.md new file mode 100644 index 00000000000..027981f7a17 --- /dev/null +++ b/loc/vocabulary/zar_xho.md @@ -0,0 +1,96 @@ +# Xhosa translation vocabulary (`zar_xho.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin | Brand, kept Latin · English passthrough (no upstream). | +| Lightning | Lightning | Brand, kept Latin. Native gloss: `umbane` (lightning phenomenon). | +| Electrum | Electrum | Brand, kept Latin · English passthrough (no upstream). | +| LNDhub | LNDhub | Brand, kept Latin · English passthrough (no upstream). | +| LND | LND | Brand, kept Latin · English passthrough (no upstream). | +| LNURL | LNURL | Brand, kept Latin · English passthrough (no upstream). | +| Tor | Tor | Brand, kept Latin · English passthrough (no upstream). | +| Orbot | Orbot | Brand, kept Latin · English passthrough (no upstream). | +| GroundControl | GroundControl | Brand, kept Latin · English passthrough (no upstream). | +| **_Units & amounts_** | | | +| bitcoin / BTC | bitcoin / BTC | Unit name and ticker kept as in English · English passthrough (no upstream). | +| sats | sats | Lowercase, kept Latin · English passthrough (no upstream). | +| sat/vByte | sat/vByte | Kept Latin, mixed case per glossary · English passthrough (no upstream). | +| vByte | vByte | Kept Latin · English passthrough (no upstream). | +| **_Wallet, keys & seeds_** | | | +| Wallet | ingxowa | Lit. "bag/pouch". | +| Vault | vault | ⚠️ NOT a brand — translatable noun; no native Xhosa rendering. · en passthrough (no upstream) | +| Watch-only | watch-only | adj · ⚠️ NOT generic "view mode" — wallet type. · en passthrough (no upstream) | +| Hardware wallet | hardware wallet | noun · en passthrough (no upstream) | +| Seed | imbewu | Literal "seed". | +| Mnemonic | mnemonic | noun · en passthrough (no upstream) | +| Passphrase | passphrase | noun · ⚠️ NOT password / NOT passcode. · en passthrough (no upstream) | +| Public key | public key | noun · en passthrough (no upstream) | +| Private key | private key | noun · en passthrough (no upstream) | +| WIF | WIF | Acronym kept · English passthrough (no upstream). | +| xpub | xpub | Acronym kept lowercase per glossary; no localisation. | +| Descriptor | descriptor | noun · en passthrough (no upstream) | +| Derivation path | derivation path | noun · en passthrough (no upstream) | +| Master fingerprint | master fingerprint | noun · en passthrough (no upstream) | +| BIP38 | BIP38 | Acronym kept · English passthrough (no upstream). | +| **_On-chain transactions_** | | | +| Transaction | transaction | ⚠️ NOT `ngeniswa` (= "entered/imported", verb). English passthrough until a noun rendering is verified. | +| Address | Ikheli / idilesi | Inconsistent. | +| Input | negalelo | Lit. "contribution". | +| Output | mveliso | Lit. "product/output". | +| UTXO | UTXO | Acronym kept · English passthrough (no upstream). | +| Change | change | noun · ⚠️ NOT verb "to change/modify" — must be the noun "leftover output". · en passthrough (no upstream) | +| Hex | hex | — | +| Pending | pending | adj/state · ⚠️ NOT noun "waiting". · en passthrough (no upstream) | +| Unconfirmed | unconfirmed | adj/state · en passthrough (no upstream) | +| Confirmed | confirmed | adj/state · en passthrough (no upstream) | +| Mempool | mempool | noun · en passthrough (no upstream) | +| Broadcast | sasazwa / usasazo | verb / noun. | +| Block explorer | block explorer | noun · en passthrough (no upstream) | +| Onchain | onchain | adj · en passthrough (no upstream) | +| Offchain | offchain | adj · en passthrough (no upstream) | +| **_Fees & fee bumping_** | | | +| Fee | ntlawulo | — | +| Fee Bump | fee bump | noun · en passthrough (no upstream) | +| RBF | RBF | Acronym kept · English passthrough (no upstream). | +| CPFP | CPFP | Acronym kept. Native gloss: `yakha` (= "build"). ⚠️ NOT a verb like "Create" — keep `CPFP`. | +| Speed Up | speed up | verb · en passthrough (no upstream) | +| **_Lightning_** | | | +| Invoice | invoyisi | — | +| Lightning Invoice | Lightning invoyisi | noun · brand kept Latin + localised noun. | +| Preimage | preimage | noun · en passthrough (no upstream) | +| Payment | intlawulo | noun · ⚠️ NOT verb "to pay"; pairs with Fee=ntlawulo (with prefix `in-` for noun). | +| Expired | iphelewe lixesha | "time has expired". | +| **_Multisig & advanced addressing_** | | | +| Co-signer | co-signer | noun · ⚠️ NOT "co-owner". · en passthrough (no upstream) | +| Quorum | quorum | noun · en passthrough (no upstream) | +| PSBT | PSBT | Acronym kept · English passthrough (no upstream). | +| Provide signature | provide signature | verb · en passthrough (no upstream) | +| BIP47 / Payment Code | BIP47 / Payment Code | Acronym + technical noun kept · English passthrough (no upstream). | +| Notification transaction | notification transaction | noun · BIP47-specific · en passthrough (no upstream) | +| SilentPayment | Silent Payments | Brand/protocol name kept English (plural per glossary) · English passthrough (no upstream). | +| **_Coin control_** | | | +| Coin Control | coin control | noun · ⚠️ NOT Title Case. · en passthrough (no upstream) | +| Frozen | frozen | adj/state · ⚠️ NOT verb "to freeze". · en passthrough (no upstream) | +| **_Security & storage_** | | | +| Encrypted storage | indawo yokugcina enoguqulelo oluntsonkothileyo | ⚠️ NOT Title Case · en passthrough (no upstream). | +| Plausible Deniability | ukuphika | ⚠️ NOT Title Case. Lit. "denial". | +| Biometrics | biometrics | noun · en passthrough (no upstream) | +| Passcode | Inombolo yokuvula | Lit. "opening number". | +| **_Backup, import & UX_** | | | +| Backup | ugcino / gcina | noun / verb. | +| Restore | buyisela | verb · "bring back / restore" (general Xhosa). | +| Import | ukungenisa | "to bring in". | +| Voucher | ivawutsha | — | +| Redeem | redeem | verb · ⚠️ NOT "buy" / NOT "transfer". · en passthrough (no upstream) | +| Send | thumela | — | +| Receive | fumana | — | +| Settings | izicwangciso | — | +| Confirm | qiniseka | "be sure". | +| QR Code | iQR code | — | +| Clipboard | ibhodi eqhotyoshwayo | — | +| Memo | inqaku kumntu | — | +| Description | inkcazo | — | +| Label | igama | ⚠️ NOT "name" (`igama` also means name). | diff --git a/loc/vocabulary/zh_cn.md b/loc/vocabulary/zh_cn.md new file mode 100644 index 00000000000..8f61a162e2f --- /dev/null +++ b/loc/vocabulary/zh_cn.md @@ -0,0 +1,96 @@ +# Chinese, Simplified translation vocabulary (`zh_cn.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms, vocabulary entry conventions (POS, casing, multi-form syntax, anti-meaning callouts), and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / 比特币 | brand kept Latin; 比特币 in body text · zh.wikipedia.org/wiki/比特币 | +| Lightning | Lightning / 闪电网络 | brand · 闪电网络 in body text · zh.wikipedia.org/wiki/闪电网络 | +| Electrum | Electrum | brand. | +| LNDhub | LNDhub | brand · drop the verbose "闪电网络守护节点集线器" gloss; not standard. | +| LND | LND | brand. | +| LNURL | LNURL | brand. | +| Tor | Tor | brand. | +| Orbot | Orbot | brand. | +| GroundControl | GroundControl | brand. | +| **_Units & amounts_** | | | +| bitcoin / BTC | 比特币 / BTC | noun unit + ticker. Lowercase unit name; `BTC` uppercase. | +| sats | 聪 | noun · canonical CN rendering of satoshi · Bitcoin Core zh_CN. | +| sat/vByte | 聪/vByte | technical unit · ⚠️ shipped `聪/字节` drops the SegWit-aware "v" prefix; vByte should be kept Latin. | +| vByte | vByte | technical unit · keep Latin (no established CN form distinct from 字节). | +| **_Wallet, keys & seeds_** | | | +| Wallet | 钱包 | noun · Bitcoin Core zh_CN. | +| Vault | 金库 / 多重签名金库 | noun · short / explanatory · `金库` = safe/strongbox, used for multisig wallet. | +| Watch-only | 仅查看 / 观察钱包 | adj / noun · chip / wallet-type · Green zh_CN + Bitcoin Core zh_CN. | +| Hardware wallet | 硬件钱包 | noun · Bitcoin Core zh_CN + Trezor zh_CN. | +| Seed | 助记词 / 恢复短语 | noun · technical / mainstream · standard CN crypto term. | +| Mnemonic | 助记词 | noun · same as Seed in shipped strings. | +| Passphrase | 密码短语 | noun · ⚠️ NOT `密码` (= password) · Trezor zh_CN. | +| Public key | 公钥 | noun · Bitcoin Core zh_CN. | +| Private key | 私钥 | noun · Bitcoin Core zh_CN. | +| WIF | WIF | acronym · gloss: 钱包导入格式. | +| xpub | xpub | acronym · lowercase preferred. | +| Descriptor | 输出描述符 / 描述符 | noun · full / short · Bitcoin Core zh_CN. | +| Derivation path | 派生路径 | noun · Electrum zh_CN + Trezor zh_CN. | +| Master fingerprint | 主密钥指纹 / 主指纹 | noun · BlueWallet form / Electrum zh_CN form. | +| BIP38 | BIP38 | acronym · gloss: BIP38 加密私钥 / 受密码保护的私钥. ⚠️ NOT a verb. | +| **_On-chain transactions_** | | | +| Transaction | 交易 | noun · Bitcoin Core zh_CN. | +| Address | 地址 | noun · Bitcoin Core zh_CN. | +| Input | 输入 | noun · ⚠️ NOT the verb "to input/enter"; here it is the tx input. | +| Output | 输出 | noun · ⚠️ NOT UI recipient label "收款人". | +| UTXO | UTXO | acronym · gloss: 未花费的交易输出 · Electrum zh_CN. | +| Change | 找零 | noun · ⚠️ NOT verb `更改/修改` (= modify). `找零` = leftover-coin/change-output · Bitcoin Core zh_CN. | +| Hex | 十六进制 | noun · Bitcoin Core zh_CN. ⚠️ NOT "hash". | +| Pending | 待处理 | adj/state · ⚠️ keep as state form, not the verb "待办". | +| Unconfirmed | 未确认 | adj/state. | +| Confirmed | 已确认 | adj/state. | +| Mempool | 内存池 | noun · Bitcoin Core zh_CN + Electrum zh_CN. | +| Broadcast | 广播 | verb / noun · Bitcoin Core zh_CN. | +| Block explorer | 区块浏览器 | noun. | +| Onchain | 链上 / 在链上 | adj · compact / explanatory. | +| Offchain | 链下 / 在链下 | adj · compact / explanatory. | +| **_Fees & fee bumping_** | | | +| Fee | 矿工费 / 手续费 | noun · mining-fee / generic-fee · both shipped; `矿工费` preferred for on-chain. | +| Fee Bump | 追加矿工费 | noun · Electrum zh_CN (`追加手续费`). | +| RBF | RBF | acronym · gloss: 费用替换 / Replace-by-Fee. | +| CPFP | CPFP | acronym · gloss: 子交易支付父交易. ⚠️ NOT a verb like "创建". | +| Speed Up | 加速 / 追加矿工费 | verb · short button / explanatory · for RBF action. | +| **_Lightning_** | | | +| Invoice | 发票 / 付款请求 | noun · technical / mainstream · Electrum zh_CN + Zeus zh_CN. | +| Lightning Invoice | 闪电网络发票 | noun. | +| Preimage | 原像 | noun · math term · shipped. | +| Payment | 付款 / 支付 | noun · ⚠️ NOT verb-only `去支付`. Both forms ship. | +| Expired | 已过期 | adj/state. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | 联合签名者 | noun · ⚠️ NOT `共有人` (co-owner). Trim shipped `共享联合签名者` (redundant `共享`). | +| Quorum | 法定数 / 签名阈值 | noun · canonical / UI-clear (m-of-n threshold). | +| PSBT | PSBT | acronym · gloss: 部分签名的比特币交易. | +| Provide signature | 提供签名 | verb. | +| BIP47 / Payment Code | BIP47 / 支付码 | acronym kept; `Payment Code` → `支付码`. | +| Notification transaction | 通知交易 | noun · BIP47-specific. | +| SilentPayment | Silent Payments / 静默支付 | protocol name kept English (plural); explanatory CN gloss `静默支付` if needed. | +| **_Coin control_** | | | +| Coin Control | 选币 / 选币功能 | noun · technical / UI-friendly · shipped is `选币功能` (verified — earlier note about `币的控制` was incorrect for current zh_cn.json) · Electrum zh_CN. | +| Frozen | 已冻结 | adj/state · ⚠️ NOT verb `冻结` (= to freeze). | +| **_Security & storage_** | | | +| Encrypted storage | 存储加密 | noun · ⚠️ NOT Title Case · shipped `启用存储加密` includes verb `启用` (= enable); drop for bare noun. | +| Plausible Deniability | 可合理否认 | noun · ⚠️ NOT Title Case. | +| Biometrics | 生物识别认证 | noun. | +| Passcode | 设备密码 | noun · device-level · ⚠️ distinct from app `密码` (password). | +| **_Backup, import & UX_** | | | +| Backup | 备份 | noun / verb · same form. | +| Restore | 恢复 | verb / noun. | +| Import | 导入 | verb / noun. | +| Voucher | 券码 / 代金券 | noun · shipped / mainstream (Zeus zh_CN). | +| Redeem | 领取 / 兑换 | verb · ⚠️ NOT `购买` (buy). `领取` = claim, `兑换` = exchange/redeem · Zeus zh_CN. | +| Send | 发送 | verb. | +| Receive | 接收 | verb. | +| Settings | 设置 | noun. | +| Confirm | 确认 | verb / noun · also "confirmations" = blocks. | +| QR Code | 二维码 | noun · standard CN term. | +| Clipboard | 剪贴板 | noun. | +| Memo | 备注 | noun. | +| Description | 描述 | noun. | +| Label | 标签 | noun. | diff --git a/loc/vocabulary/zh_tw.md b/loc/vocabulary/zh_tw.md new file mode 100644 index 00000000000..2191144abfd --- /dev/null +++ b/loc/vocabulary/zh_tw.md @@ -0,0 +1,96 @@ +# Chinese, Traditional translation vocabulary (`zh_tw.json`) + +See [`../vocabulary.md`](../vocabulary.md) for the glossary of terms and the meaning of each row. + +| Term | Translation | Notes | +|------|-------------|-------| +| **_Brand & protocol_** | | | +| Bitcoin | Bitcoin / 比特幣 | brand kept Latin; 比特幣 in body · zh.wikipedia.org/wiki/比特幣 | +| Lightning | Lightning / 閃電網路 | brand kept Latin; 閃電網路 in body · zh.wikipedia.org/wiki/閃電網絡 (zh-TW form) | +| Electrum | Electrum | — | +| LNDhub | LNDhub | — | +| LND | LND | — | +| LNURL | LNURL | — | +| Tor | Tor | — | +| Orbot | Orbot | — | +| GroundControl | GroundControl | — | +| **_Units & amounts_** | | | +| bitcoin / BTC | 比特幣 / BTC | noun unit + ticker. | +| sats | 聰 | noun · Bitcoin Core zh_TW "satoshi" → 聰. | +| sat/vByte | sat/vByte | technical unit; UI controls keep Latin. ⚠️ NOT 聰/位元組 (drops the "v" prefix). | +| vByte | vByte | technical unit · SegWit-discounted size. | +| **_Wallet, keys & seeds_** | | | +| Wallet | 錢包 | noun · Bitcoin Core zh_TW. | +| Vault | 保管庫 | noun · safe/strongbox sense; not a brand. | +| Watch-only | 僅觀察 / 唯讀 | adj · Bitcoin Core zh_TW `僅觀察` + Zeus zh-TW `唯讀錢包`. ⚠️ NOT "view mode". | +| Hardware wallet | 硬體錢包 | noun. | +| Seed | 種子 / 復原短語 | noun · technical / mainstream. | +| Mnemonic | 助記詞 / 備份短語 | noun · technical / mainstream · zh.wikipedia.org/wiki/BIP39. | +| Passphrase | 密語 | noun · Electrum zh_TW `密語`. ⚠️ NOT `密碼` (= password/passcode). | +| Public key | 公鑰 | noun · Bitcoin Core zh_TW. | +| Private key | 私鑰 | noun · Bitcoin Core zh_TW. | +| WIF | WIF | acronym · gloss: 錢包匯入格式. | +| xpub | xpub | acronym, lowercase · gloss: 擴展公鑰. ⚠️ shipped `錢包公鑰` is ambiguous. | +| Descriptor | 描述符 | noun · output descriptor. | +| Derivation path | 推導路徑 | noun · BIP32 path. | +| Master fingerprint | 主指紋 | noun · first 4 bytes of HASH160(master pubkey). | +| BIP38 | BIP38 | acronym · gloss: BIP38 加密私鑰. | +| **_On-chain transactions_** | | | +| Transaction | 交易 | noun · Bitcoin Core zh_TW. ⚠️ shipped `轉賬` mixes Simplified `账`; prefer 交易. | +| Address | 地址 | noun · Bitcoin Core zh_TW. | +| Input | 輸入 / 交易輸入 | noun · short / full · Bitcoin Core zh_TW. ⚠️ NOT verb "to enter". | +| Output | 輸出 / 交易輸出 | noun · short / full. ⚠️ NOT UI "收件人" label. | +| UTXO | UTXO | acronym · gloss: 未花費的交易輸出 · Bitcoin Core zh_TW. | +| Change | 找零 | noun · Bitcoin Core zh_TW + Electrum zh_TW. ⚠️ NOT verb "更變/改變"; must be the leftover-output noun. | +| Hex | 十六進位 / 十六進制碼 | noun · short / explanatory · Bitcoin Core zh_TW. ⚠️ NOT "hash". | +| Pending | 待處理 / 等待中 | adj/state · ⚠️ shipped `待辦` (= "to-do") is wrong sense. | +| Unconfirmed | 未確認 | adj · Bitcoin Core zh_TW + Electrum zh_TW. | +| Confirmed | 已確認 | adj/state form · ⚠️ shipped `確認` is verb "to confirm"; prefer `已確認`. | +| Mempool | 記憶池 / 記憶體暫存池 | noun · short / full · Bitcoin Core zh_TW + Zeus zh-TW. | +| Broadcast | 廣播 | verb / noun. | +| Block explorer | 區塊瀏覽器 | noun · ⚠️ shipped `資源管理器` is wrong (= "resource manager/file explorer"). | +| Onchain | 鏈上 / 區塊鏈上 | adj · compact (chip) / explanatory (body). | +| Offchain | 鏈下 / 區塊鏈外 | adj · compact / explanatory · ⚠️ NOT `閃電網路` (conflates with Lightning brand). | +| **_Fees & fee bumping_** | | | +| Fee | 手續費 / 費用 | noun · mainstream / generic. | +| Fee Bump | 提高手續費 | noun · Zeus zh-TW. | +| RBF | RBF | acronym · gloss: 以新交易取代 (Replace-By-Fee) · Electrum zh_TW. | +| CPFP | CPFP | acronym · gloss: 子交易為父交易付費. ⚠️ NOT verb "Create". | +| Speed Up | 加速 | verb · Zeus zh-TW `加速交易`. ⚠️ shipped `對碰費用` is unidiomatic. | +| **_Lightning_** | | | +| Invoice | 發票 / 帳單 | noun · technical / mainstream · Electrum zh_TW `發票` + Zeus zh-TW `帳單`. ⚠️ shipped `賬單` mixes Simplified `账`; prefer 帳單. | +| Lightning Invoice | Lightning 發票 / 閃電帳單 | noun · technical / mainstream. | +| Preimage | 原像 | noun · math term (hash preimage); Zeus zh-TW uses contextual `驗證碼`. | +| Payment | 支付 / 付款 | noun · ⚠️ NOT verb only — must work as noun. | +| Expired | 已過期 | adj/state form. | +| **_Multisig & advanced addressing_** | | | +| Co-signer | 共同簽署者 / 協同簽署者 | noun · Electrum zh_TW. ⚠️ NOT "共同擁有者" (co-owner). | +| Quorum | 法定人數 / 簽署門檻 | noun · canonical / UI-clear. | +| PSBT | PSBT | acronym · gloss: 部分簽署的比特幣交易. | +| Provide signature | 提供簽名 / 簽署交易 | verb · generic / specific. | +| BIP47 / Payment Code | BIP47 / 支付代碼 | acronym kept; "Payment Code" → `支付代碼`. | +| Notification transaction | 通知交易 | noun · BIP47-specific 0-value tx. | +| SilentPayment | Silent Payments / 隱形支付 | protocol name kept English (plural); explanatory `隱形支付` if needed. | +| **_Coin control_** | | | +| Coin Control | 幣的控制 / UTXO 控制 | noun · mainstream / technical · ⚠️ NOT Title Case · shipped `币的控制` mixes Simplified `币`; fix to 幣. Bitcoin Core zh_TW uses `錢幣控制`. | +| Frozen | 已凍結 / 凍結 | adj / verb · state form preferred · ⚠️ NOT verb "凍結"; for adj/state prefer `已凍結`. | +| **_Security & storage_** | | | +| Encrypted storage | 加密儲存空間 / 加密儲存 | noun · ⚠️ NOT Title Case. | +| Plausible Deniability | 合理推諉 | noun · ⚠️ NOT Title Case · zh.wikipedia.org/wiki/合理推諉. | +| Biometrics | 生物辨識 / 生物識別 | noun · Zeus zh-TW `生物辨識` (more idiomatic in zh-TW). | +| Passcode | 裝置密碼 / PIN 碼 | noun · ⚠️ shipped `密碼` collides with "password"; prefer `裝置密碼` or `PIN 碼` (Zeus zh-TW). | +| **_Backup, import & UX_** | | | +| Backup | 備份 / 匯出備份 | noun / verb. | +| Restore | 復原 / 還原 | verb / noun. | +| Import | 匯入 | verb / noun. | +| Voucher | 優惠券 / 兌換券 | noun · ⚠️ shipped `兌贖Azte.co優惠券` mashes verb+brand+noun; the term itself is just `優惠券`. | +| Redeem | 兌換 / 兌贖 | verb · mainstream / shipped form. ⚠️ NOT "buy to wallet". | +| Send | 傳送 / 發送 | verb. | +| Receive | 收款 / 接收 | verb. | +| Settings | 設定 | noun. | +| Confirm | 確認 | verb / noun · also "confirmations" = blocks count. | +| QR Code | QR 碼 / 二維碼 | noun · zh-TW prefers `QR 碼` (Zeus zh-TW); `二維碼` is more zh-CN. | +| Clipboard | 剪貼簿 | noun. | +| Memo | 備註 | noun · Zeus zh-TW. ⚠️ shipped `訊息` (= "message") is wrong sense. | +| Description | 描述 / 說明 | noun. | +| Label | 標籤 | noun. | diff --git a/loc/zar_afr.json b/loc/zar_afr.json index 963e7503174..0bd959c844e 100644 --- a/loc/zar_afr.json +++ b/loc/zar_afr.json @@ -4,194 +4,701 @@ "cancel": "Kanselleer", "continue": "Gaan voort", "clipboard": "Knipbord", + "copied": "Gekopieer!", + "discard_changes": "Verwerp veranderinge?", + "discard_changes_explain": "Jy het veranderinge wat nie gestoor is nie. Is jy seker jy wil die skerm verlaat?", "enter_password": "Sleutel wagwoord in", "never": "Nooit", - "disabled": "Af gestel", "of": "{number} van {total}", "ok": "OK", - "storage_is_encrypted": "Jou geheue spasie is nou ge-enkripteer. ‘n Wagwoord word benodig om toegang te verkry. ", + "enter_url": "Sleutel URL in", + "storage_is_encrypted": "Jou bergingspasie is geënkripteer. 'n Wagwoord word benodig om dit te ontsluit.", "yes": "Ja", "no": "Nee", - "save": "Berg", + "save": "Berg...", "seed": "Saad", "success": "Sukses", "wallet_key": "Beursie sleutel", - "invalid_animated_qr_code_fragment": "Ongeldige geanimeerde QRKode fragment. Probeer asb weer.", - "file_saved": "Leêr {filePath} was gestoor in jou {destination}.", - "downloads_folder": "Aflaai Lëer" - }, - "alert": { - "default": "Aandag" + "close": "Sluit", + "change_input_currency": "Verander invoer-geldeenheid", + "refresh": "Verfris", + "pick_image": "Kies uit biblioteek", + "pick_file": "Kies lêer", + "enter_amount": "Sit die bedrag in", + "qr_custom_input_button": "Tik 10 keer om pasgemaakte invoer te gee", + "unlock": "Ontsluit", + "port": "Poort", + "ssl_port": "SSL-poort", + "suggested": "Voorgestel" }, "azteco": { "codeIs": "Jou koepon kode is", "errorBeforeRefeem": "Voordat jy eis moet jy eers 'n Bitcoin beursie aanwys.", - "errorSomething": "Iets het vout gegaan. Is die koepon steeds geldig?", + "errorSomething": "Iets het fout gegaan. Is die koepon steeds geldig?", "redeem": "Eis na beursie", "redeemButton": "Eis", "success": "Sukses", + "successMessage": "Koepon suksesvol geëis! Jou fondse behoort binnekort in jou Bitcoin beursie aan te kom.", "title": "Eis Azte.co koepon" }, "entropy": { "save": "Berg", "title": "Entropie", - "undo": "Ontdoen" + "undo": "Ontdoen", + "amountOfEntropy": "{bits} van {limit} bits" }, "errors": { "broadcast": "Sending het misluk.", "error": "Probleem", - "network": "Netwerk Vout" + "network": "Netwerk Fout" }, "lnd": { - "active": "Aktief", - "inactive": "Onaktief", - "channels": "Kanale", - "no_channels": "Geen kanale", - "claim_balance": "Eis balaans {balance}", - "close_channel": "Sluit kanaal", - "new_channel": "Nuwe kanaal", - "errorInvoiceExpired": "Faktuur verval", - "force_close_channel": "Dwing kanaal sluiting?", + "errorInvoiceExpired": "Faktuur het verval.", "expired": "Verval", - "node_alias": "Knoop-punt noem naam", "expiresIn": "Verval in {time} minute", "payButton": "Betaal", - "placeholder": "Faktuur", - "open_channel": "Open Kanaal", - "funding_amount_placeholder": "Befondsing bedrag, byvoorbeeld 0.001", - "opening_channnel_for_from": "Skep kanaal vir beursie {forWalletLabel}, deur befondsing vanaf {fromWalletLabel}", - "are_you_sure_open_channel": "Is jy seker jy wil hierdie kanaal oop maak?", - "potentialFee": "Potensiele fooi: {fee}", + "payment": "Betaling", + "placeholder": "Faktuur of adres", + "potentialFee": "Moontlike fooi: {fee}", "refill": "Herlaai", - "refill_create": "Om voort te gaan, skep asb 'n Bitcoin beursie om vondse mee aan te vul.", + "refill_create": "Om voort te gaan, skep asb 'n Bitcoin beursie om fondse mee aan te vul.", "refill_external": "Vul aan met Eksterne Beursie", "refill_lnd_balance": "Herlaai Lightning beursie", - "sameWalletAsInvoiceError": "Jy kan nie ‘n faktuur betaal met die selfde beursie waarmee die faktuur geksep is nie.", - "title": "bestuur fondse", - "can_send": "Kan Stuur", - "can_receive": "Kan Ontvang", - "view_logs": "Sien Aktiwiteits Register" + "sameWalletAsInvoiceError": "Jy kan nie 'n faktuur betaal met dieselfde beursie waarmee die faktuur geskep is nie.", + "title": "bestuur fondse" }, "lndViewInvoice": { - "open_direct_channel": "Open direkte kanaal met hierdie knoop-punt:" + "additional_info": "Bykomende Inligting", + "for": "Vir:", + "lightning_invoice": "Lightning Faktuur", + "please_pay_between_and": "Betaal asseblief tussen {min} en {max}", + "please_pay": "Betaal asseblief", + "date_time": "Datum en tyd", + "wasnt_paid_and_expired": "Die faktuur was nie betaal nie en het verval.", + "preimage": "Pre-image", + "sats": "sats." }, "plausibledeniability": { "create_fake_storage": "Skep fantasie berging wagwoord", - "create_password": "Skep ‘n wagwoord", "create_password_explanation": "Die wagwoord vir fop berging moet verskil van die wagwoord vir hoof berging", - "help": "Onder sekere omstandighede mag jy dalk geforseer word om jou wagwoord te deel teen jou wil. Om jou te beskerm kan Bluewallet ‘n tweede “fop” beursie skep wat as skerm kan dien. Indien jy hierdie wagwoord deel sal die 3de party nie toegang tot jou hoof fondse kry nie.", + "help": "Onder sekere omstandighede mag jy dalk geforseer word om jou wagwoord te deel teen jou wil. Om jou te beskerm kan BlueWallet 'n tweede “fop” beursie skep wat as skerm kan dien. Indien jy hierdie wagwoord deel sal die 3de party nie toegang tot jou hoof fondse kry nie.", "help2": "Fop berging is heeltemal funksioneel", "password_should_not_match": "Die wagwoord vir fantasie berging moet verskil van die wagwoord vir hoof berging.", - "passwords_do_not_match": "Wagwoorde vergelyk nie, probeer weer", - "retype_password": "Hervoer wagwoord", - "success": "Sukses", "title": "Geloofwaardige Ontkenbaarheid" }, + "pleasebackup": { + "ask": "Het jy jou beursie se terugval frase gestoor? Dié terugval frase is nodig om jou fondse terug te kry indien jy jou toestel verloor. Sonder die terugval frase sal al jou fondse permanent verlore wees. ", + "ask_no": "Nee, ek het nie. ", + "ask_yes": "Ja, ek het. ", + "ok": "OK, ek het dit neergeskryf.", + "ok_lnd": "OK, ek het dit gestoor.", + "text": "Neem asseblief 'n oomblik om die woorde op 'n stuk papier neer te skryf.\nDit is jou rugsteun-woorde wat jy gebruik om jou beursie terug te kry. ", + "text_lnd": "Stoor asseblief die beursie se rugsteun. Dit gaan jou toelaat om jou beursie terug te kry indien jy hom verloor. ", + "title": "Jou beursie is geskep." + }, "receive": { "details_create": "Skep", "details_label": "Beskrywing", "details_setAmount": "Bedrag ontvang", - "details_share": "deel", - "header": "Ontvang" + "details_share": "Deel...", + "address_not_found": "Kon nie 'n ontvangsadres skep nie.", + "header": "Ontvang", + "reset": "Herstel", + "maxSats": "Die maksimum bedrag is {max} sats", + "maxSatsFull": "Die maksimum bedrag is {max} sats of {currency}", + "minSats": "Die minimale bedrag is {min} sats", + "minSatsFull": "Die minimum bedrag is {min} sats of {currency}", + "qrcode_for_the_address": "QR Code vir die adres", + "bip47_explanation": "Betalingskodes is 'n universele adres wat verhoed dat jou beursie-adresse bekend gemaak word. Nie alle dienste ondersteun hulle nie." }, "send": { + "provided_address_is_invoice": "Hierdie adres lyk soos 'n Lightning faktuur. Gaan asseblief na jou Lightning beursie om hierdie faktuur te betaal.", "broadcastButton": "Saai uit", "broadcastError": "Probleem", + "broadcastNone": "Sit transaksie-hex in", + "broadcastPending": "Hangende", "broadcastSuccess": "Sukses", "confirm_header": "Bevestig", "confirm_sendNow": "Stuur nou", "create_amount": "Bedrag", "create_broadcast": "Saai uit", + "create_copy": "Kopieer en saai later uit", "create_details": "Besonderhede", "create_fee": "Fooi", + "create_memo": "Nota", "create_this_is_hex": "Hierdie is die transaksie hex, geteken en gereed om na die netwerk uitgesaai te word.", "create_to": "Aan", - "create_tx_size": "TX groote", - "details_address": "adres", + "create_tx_size": "TX grootte", + "create_verify": "Verifieer op coinb.in", + "details_insert_contact": "Sit kontak in", + "details_add_rec_add": "Voeg ontvanger by", + "details_add_rec_rem": "Verwyder ontvanger", + "details_add_recc_rem_all_alert_description": "Is jy seker jy wil alle ontvangers verwyder?", + "details_add_rec_rem_all": "Verwyder alle ontvangers", + "details_recipients_title": "Ontvangers", + "details_recipient_title": "Ontvanger #{number} van #{total}", + "please_complete_recipient_title": "Onvolledige ontvanger", + "please_complete_recipient_details": "Voltooi asseblief die besonderhede van ontvanger #{number} voordat jy 'n nuwe ontvanger byvoeg.", + "details_address": "Adres", "details_address_field_is_not_valid": "Adres is ongeldig", + "details_adv_fee_bump": "Laat fooi-verhoging toe", + "details_adv_full": "Gebruik volle balans", + "details_adv_full_sure": "Is jy seker jy wil jou beursie se volle balans vir hierdie transaksie gebruik?", + "details_adv_full_sure_frozen": "Is jy seker jy wil jou beursie se volle balans vir hierdie transaksie gebruik? Let asseblief op dat gevriesde munte uitgesluit word.", + "details_adv_import": "Voer transaksie in", + "details_adv_import_qr": "Voer transaksie in (QR)", "details_amount_field_is_not_valid": "Bedrag is ongeldig", + "details_amount_field_is_less_than_minimum_amount_sat": "Die gespesifiseerde bedrag is te klein. Sleutel asseblief 'n bedrag in groter as 500 sats.", "details_create": "Skep", - "details_fee_field_is_not_valid": "Fooi spasie is ongeldig", + "details_error_decode": "Kon nie Bitcoin adres dekodeer nie", + "details_fee_field_is_not_valid": "Fooi is ongeldig", + "details_frozen": "{amount} BTC is gevries.", + "details_next": "Volgende", + "details_no_signed_tx": "Die gekose lêer bevat nie 'n transaksie wat ingevoer kan word nie.", "details_note_placeholder": "persoonlike notas", "details_scan": "Skandeer", + "details_scan_hint": "Tik dubbel om 'n bestemming te skandeer of in te voer", + "details_scan_error": "Skandeerfout", "details_total_exceeds_balance": "Die bedrag is meer as die beskikbare balans.", + "details_total_exceeds_balance_frozen": "Die stuurbedrag oorskry die beskikbare balans. Let asseblief op dat gevriesde munte uitgesluit word.", + "details_unrecognized_file_format": "Onherkenbare lêerformaat", + "details_wallet_before_tx": "Voordat jy 'n transaksie skep, moet jy eers 'n Bitcoin beursie byvoeg.", + "dynamic_init": "Inisialiseer", + "dynamic_next": "Volgende", + "dynamic_prev": "Vorige", + "dynamic_start": "Begin", + "dynamic_stop": "Stop", + "create_satoshi_per_vbyte": "Satoshi per vByte", + "fee_10m": "10m", + "fee_1d": "1d", + "fee_3h": "3h", + "fee_custom": "Pasgemaak", + "fee_medium": "Gemiddeld", + "fee_satvbyte": "in sat/vByte", + "insert_custom_fee": "Sit fooi in", + "fee_fast": "Vinnig", + "fee_replace_minvb": "Die totale fooi (satoshi per vByte) wat jy wil betaal, moet meer wees as {min} sat/vByte.", + "fee_slow": "Stadig", "header": "Stuur", + "input_clear": "Maak skoon", "input_done": "Klaar", - "success_done": "Klaar" + "input_paste": "Plak", + "input_total": "Totaal:", + "permission_camera_message": "Ons het jou toestemming nodig om jou kamera te gebruik. ", + "psbt_sign": "Teken 'n transaksie", + "invalid_psbt": "Ongeldige PSBT verskaf.", + "open_settings": "Maak instellings oop", + "permission_storage_denied_message": "BlueWallet kan nie hierdie lêer stoor nie. Maak asseblief jou toestelinstellings oop en aktiveer Berging-toestemming.", + "permission_storage_title": "Berging-toegangtoestemming", + "psbt_clipboard": "Kopieer na knipbord", + "psbt_this_is_psbt": "Hierdie is 'n Gedeeltelik Getekende Bitcoin Transaksie (PSBT). Voltooi asseblief die tekening met jou hardeware-beursie.", + "psbt_tx_export": "Voer uit na lêer", + "no_tx_signing_in_progress": "Daar is geen transaksie-tekening aan die gang nie.", + "outdated_rate": "Koers is laas opgedateer: {date}", + "psbt_tx_open": "Maak getekende transaksie oop", + "psbt_tx_scan": "Skandeer getekende transaksie", + "qr_error_no_qrcode": "Ons kon nie 'n geldige QR Code in die gekose beeld vind nie. Maak seker die beeld bevat slegs 'n QR Code en geen bykomende inhoud soos teks of knoppies nie.", + "reset_amount": "Herstel bedrag", + "reset_amount_confirm": "Wil jy die bedrag herstel?", + "success_done": "Klaar", + "txSaved": "Die transaksie-lêer ({filePath}) is gestoor.", + "file_saved_at_path": "Die lêer ({filePath}) is gestoor.", + "cant_send_to_silentpayment_adress": "Hierdie beursie kan nie na Silent Payments adresse stuur nie", + "cant_send_to_bip47": "Hierdie beursie kan nie na BIP47 betalingskodes stuur nie", + "cant_find_bip47_notification": "Voeg eers hierdie betalingskode by kontakte", + "problem_with_psbt": "Probleem met PSBT" }, "settings": { "about": "info", + "about_awesome": "Gebou met die wonderlike", + "about_backup": "Maak altyd 'n rugsteun van jou sleutels!", + "about_free": "BlueWallet is 'n gratis en oopbronprojek. Vervaardig deur Bitcoin gebruikers.", + "about_license": "MIT Lisensie", + "about_release_notes": "Vrystellingnotas", + "about_review": "Los 'n resensie vir ons", + "performance_score": "Prestasietelling: {num}", + "run_performance_test": "Toets prestasie", + "about_selftest": "Voer selftoets uit", + "block_explorer_invalid_custom_url": "Die URL wat verskaf is, is ongeldig. Sleutel asseblief 'n geldige URL in wat met http:// of https:// begin.", + "about_selftest_electrum_disabled": "Selftoetsing is nie beskikbaar in Electrum aflynmodus nie. Skakel asseblief aflynmodus af en probeer weer.", + "about_selftest_ok": "Alle interne toetse het suksesvol geslaag. Die beursie werk goed.", + "about_sm_github": "GitHub", + "about_sm_telegram": "Telegram-kanaal", + "privacy_temporary_screenshots": "Laat skermopname toe", + "privacy_temporary_screenshots_instructions": "Skermopnamebeskerming sal tydelik afgeskakel word om skermkiekies en skermopnames moontlik te maak. Die beskerming sal outomaties heraktiveer wanneer jy BlueWallet sluit en weer oopmaak.", + "biometrics": "Biometrie", + "biometrics_no_longer_available": "Jou toestelinstellings het verander en stem nie meer ooreen met die gekose sekuriteitsinstellings in die app nie. Heraktiveer asseblief biometrie of toegangskode, en herbegin dan die app om hierdie veranderinge toe te pas.", + "biom_10times": "Jy het 10 keer probeer om jou wagwoord in te sleutel. Wil jy jou berging herstel? Dit sal alle beursies verwyder en jou berging dekripteer.", + "biom_conf_identity": "Bevestig asseblief jou identiteit.", + "biom_no_passcode": "Jou toestel het nie 'n toegangskode of biometrie geaktiveer nie. Om voort te gaan, konfigureer asseblief 'n toegangskode of biometrie in die Instellings app.", + "biom_remove_decrypt": "Al jou beursies sal verwyder word en jou berging sal gedekripteer word. Is jy seker jy wil voortgaan?", "currency": "Geldeenheid", - "electrum_clear_alert_cancel": "Kanselleer", - "general_adv_mode": "Enable advanced mode", + "currency_source": "Koers is verkry van", + "currency_fetch_error": "Daar was 'n fout terwyl die koers vir die gekose geldeenheid verkry is.", + "default_title": "By aanvang", + "donate": "Skenk", + "donate_description": "Help ons om Blue gratis te hou!", + "electrum_connected": "Gekoppel", + "electrum_connected_not": "Nie gekoppel nie", + "electrum_status": "Status", + "widgets": "Widgets", + "electrum_error_connect": "Kan nie aan die verskafde Electrum-bediener koppel nie", + "electrum_error_connect_tor": "Kan nie aan die verskafde Electrum-bediener koppel nie. Maak asseblief seker dat die Orbot app gekoppel is en probeer weer.", + "lndhub_uri": "Bv., {example}", + "electrum_host": "Bv., {example}", + "electrum_offline_mode": "Aflynmodus", + "electrum_offline_description": "Wanneer geaktiveer, sal jou Bitcoin beursies nie probeer om balanse of transaksies te verkry nie.", + "electrum_port": "Poort, gewoonlik {example}", + "use_ssl": "Gebruik SSL", + "electrum_saved": "Jou veranderinge is suksesvol gestoor. 'n Herbegin van BlueWallet mag vereis word om die veranderinge in werking te stel.", + "set_electrum_server_as_default": "Stel {server} as die verstek Electrum-bediener?", + "set_lndhub_as_default": "Stel {url} as die verstek LNDhub-bediener?", + "electrum_settings_server": "Electrum-bediener", + "electrum_preferred_server": "Voorkeurbediener", + "electrum_preferred_server_description": "Sleutel die bediener in wat jy wil hê jou beursie moet vir alle Bitcoin-aktiwiteite gebruik. Sodra dit gestel is, sal jou beursie uitsluitlik hierdie bediener gebruik om balanse te kontroleer, transaksies te stuur, en netwerkdata te verkry. Verseker dat jy hierdie bediener vertrou voordat jy dit stel.", + "electrum_unable_to_connect": "Kan nie aan {server} koppel nie.", + "electrum_history": "Geskiedenis", + "electrum_reset_to_default": "Dit sal BlueWallet toelaat om 'n bediener uit die bedienerlys lukraak te kies.", + "electrum_reset": "Herstel na verstek", + "electrum_reset_to_default_and_clear_history": "Herstel na verstek en maak geskiedenis skoon", + "encrypt_decrypt": "Dekripteer berging", + "encrypt_decrypt_q": "Is jy seker jy wil jou berging dekripteer? Dit sal toelaat dat jou beursies sonder 'n wagwoord verkry word.", + "encrypt_enc_and_pass": "Wagwoordbeskerm", + "encrypt_storage_explanation_headline": "Aktiveer berging-enkripsie", + "encrypt_storage_explanation_description_line1": "Deur berging-enkripsie te aktiveer, voeg jy 'n ekstra laag beskerming by jou app deur die manier waarop jou data op jou toestel gestoor word te beveilig. Dit maak dit moeiliker vir iemand om sonder toestemming by jou inligting uit te kom.", + "encrypt_storage_explanation_description_line2": "Dit is egter belangrik om te weet dat hierdie enkripsie net die toegang tot jou beursies wat op die toestel se sleutelhouer gestoor is, beskerm. Dit plaas nie 'n wagwoord of enige ekstra beskerming op die beursies self nie.", + "i_understand": "Ek verstaan", + "block_explorer": "Blokverkenner", + "block_explorer_preferred": "Gebruik voorkeur-blokverkenner", + "block_explorer_error_saving_custom": "Fout met stoor van voorkeur-blokverkenner", + "encrypt_title": "Sekuriteit", + "encrypt_tstorage": "Berging", + "encrypt_use": "Gebruik {type}", + "set_as_preferred": "Stel as voorkeur", + "set_as_preferred_electrum": "Deur {host}:{port} as voorkeurbediener te stel, sal lukrake koppeling met 'n voorgestelde bediener afgeskakel word.", + "encrypted_feature_disabled": "Hierdie kenmerk kan nie gebruik word terwyl berging-enkripsie geaktiveer is nie.", + "encrypt_use_expl": "{type} sal gebruik word om jou identiteit te bevestig voordat 'n transaksie gemaak word, ontsluit, uitgevoer, of 'n beursie verwyder word.", + "biometrics_fail": "Indien {type} nie geaktiveer is nie, of nie kan ontsluit nie, kan jy jou toestel se toegangskode as alternatief gebruik.", + "general": "Algemeen", + "general_continuity": "Kontinuïteit", + "general_continuity_e": "Wanneer geaktiveer, sal jy gekose beursies en transaksies kan bekyk op jou ander Apple iCloud-gekoppelde toestelle.", + "groundcontrol_explanation": "GroundControl is 'n gratis, oopbron-stootkennisgewingbediener vir Bitcoin beursies. Jy kan jou eie GroundControl-bediener installeer en die URL hier plaas om nie op BlueWallet se infrastruktuur staat te maak nie. Laat leeg om GroundControl se verstekbediener te gebruik.", "header": "instellings", "language": "Taal", + "last_updated": "Laas opgedateer", + "language_isRTL": "'n Herbegin van BlueWallet word vereis om die taal-oriëntasie in werking te stel.", + "license": "Lisensie", + "lightning_error_lndhub_uri": "Ongeldige LNDhub URI", + "lightning_error_lndhub_uri_tor": "Ongeldige LNDhub URI. Maak asseblief seker dat die Orbot app gekoppel is en probeer weer.", + "lightning_saved": "Jou veranderinge is suksesvol gestoor.", "lightning_settings": "Lightning Instellings", - "lightning_settings_explain": "Om jou eie LND knoop-punt te konnekteer, installeer asseblief LndHub en pos die URL hier in verstellings. Los leeg om die standaard LndHub te gebruik. Beursies geskep na verstellings geberg is sal koppel aan die gespesifiseerde LNDHub.", + "lightning_settings_explain": "Om aan jou eie LND-knoop te koppel, installeer asseblief LNDhub en plaas die URL hier in instellings. Let asseblief op dat slegs beursies wat na die stoor van veranderinge geskep is, aan die gespesifiseerde LNDhub sal koppel.", + "lndhub_github": "GitHub-bewaarplek", + "network": "Netwerk", + "network_broadcast": "Saai transaksie uit", + "network_electrum": "Electrum-bediener", + "electrum_suggested_description": "Wanneer 'n voorkeurbediener nie gestel is nie, sal 'n voorgestelde bediener lukraak vir gebruik gekies word.", + "not_a_valid_uri": "Ongeldige URI", + "notifications": "Kennisgewings", + "open_link_in_explorer": "Maak skakel in verkenner oop", "password": "Wagwoord", - "password_explain": "Skep die wagwoord wat jy sal gebruik om jou berging te de-enkripteer", - "passwords_do_not_match": "Wagwoorde stem nie oor een nie", + "password_explain": "Sleutel die wagwoord in wat jy sal gebruik om jou berging te ontsluit.", "plausible_deniability": "Geloofwaardige ontkenbaarheid...", - "retype_password": "Hervoer wagwoord", - "save": "stoor" + "privacy": "Privaatheid", + "privacy_read_clipboard": "Lees knipbord", + "privacy_system_settings": "Stelselinstellings", + "privacy_quickactions": "Beursie-kortpaaie", + "privacy_quickactions_explanation": "Raak en hou die BlueWallet app-ikoon om jou beursie se balans vinnig te sien.", + "privacy_clipboard_explanation": "Verskaf kortpaaie indien 'n adres of faktuur in jou knipbord gevind word.", + "privacy_do_not_track": "Skakel analise af", + "privacy_do_not_track_explanation": "Prestasie- en betroubaarheidsinligting sal nie vir ontleding ingedien word nie.", + "rate": "Koers", + "push_notifications_explanation": "Deur kennisgewings te aktiveer, sal jou toestel-token na die bediener gestuur word, saam met beursie-adresse en transaksie-ID's vir alle beursies en transaksies wat ná die aktivering van kennisgewings gemaak is. Die toestel-token word gebruik om kennisgewings te stuur, en die beursie-inligting laat ons toe om jou in kennis te stel van inkomende Bitcoin of transaksie-bevestigings.\n\nSlegs inligting van ná jy kennisgewings aktiveer, word oorgedra—niks van voorheen word ingesamel nie.\n\nDie afskakeling van kennisgewings sal al hierdie inligting van die bediener verwyder. Verder sal die verwydering van 'n beursie uit die app ook die gepaardgaande inligting van die bediener verwyder.", + "selfTest": "Selftoets", + "save": "stoor", + "saved": "Gestoor", + "success_transaction_broadcasted": "Jou transaksie is suksesvol uitgesaai!", + "total_balance": "Totale balans", + "total_balance_explanation": "Vertoon die totale balans van al jou beursies op jou tuisskerm-widgets.", + "tools": "Gereedskap" + }, + "notifications": { + "would_you_like_to_receive_notifications": "Wil jy kennisgewings ontvang wanneer jy inkomende betalings kry?", + "notifications_subtitle": "Inkomende betalings en transaksie-bevestigings", + "no_and_dont_ask": "Nee, en moet my nie weer vra nie.", + "permission_denied_message": "Jy het toestemming geweier om vir jou kennisgewings te stuur. Indien jy kennisgewings wil ontvang, aktiveer hulle asseblief in jou toestelinstellings." }, "transactions": { + "cancel_explain": "Ons sal hierdie transaksie vervang met een wat aan jou betaal en hoër fooie het. Dit kanselleer effektief die huidige transaksie. Dit word RBF—Replace by Fee genoem.", + "cancel_no": "Hierdie transaksie is nie vervangbaar nie.", + "cancel_title": "Kanselleer hierdie transaksie (RBF)", + "transaction_loading_error": "Daar was 'n probleem met die laai van die transaksie. Probeer asseblief later weer.", + "transaction_not_available": "Transaksie nie beskikbaar nie", + "confirmations_lowercase": "{confirmations} bevestigings", + "expand_note": "Brei nota uit", "cpfp_create": "Skep", + "cpfp_exp": "Ons sal nog 'n transaksie skep wat jou onbevestigde transaksie bestee. Die totale fooi sal hoër wees as die oorspronklike transaksiefooi, sodat dit vinniger gemyn behoort te word. Dit word CPFP—Child Pays for Parent genoem.", + "cpfp_no_bump": "Hierdie transaksie kan nie verhoog word nie.", + "cpfp_title": "Verhoog fooi (CPFP)", + "details_balance_hide": "Versteek balans", + "details_balance_show": "Wys balans", "details_copy": "Kopieer", - "details_from": "Inset", - "details_show_in_block_explorer": "Wys in blok verkenner", + "details_copy_block_explorer_link": "Kopieer blokverkenner-skakel", + "details_copy_note": "Kopieer nota", + "details_copy_txid": "Kopieer transaksie-ID", + "details_inputs": "Insette", + "details_outputs": "Uitsette", + "date": "Datum", + "details_received": "Ontvang", + "details_view_in_browser": "Bekyk in blaaier", "details_title": "Transaksie", - "details_to": "Resultaat", - "list_title": "transaksies" + "incoming_transaction": "Inkomende transaksie", + "outgoing_transaction": "Uitgaande transaksie", + "expired_transaction": "Vervalle transaksie", + "pending_transaction": "Hangende transaksie", + "offchain": "Offchain", + "onchain": "Onchain", + "details_to": "Uitset", + "enable_offline_signing": "Hierdie beursie word nie saam met aflyn-tekening gebruik nie. Wil jy dit nou aktiveer?", + "list_conf": "Bev: {number}", + "pending": "Hangende", + "pending_with_amount": "Hangende {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: In ~10 minute", + "eta_3h": "ETA: In ~3 uur", + "eta_1d": "ETA: In ~1 dag", + "list_title": "transaksies", + "list_title_sent": "Gestuur", + "list_title_received": "Ontvang", + "transaction": "Transaksie", + "open_url_error": "Kan nie die skakel met die verstekblaaier oopmaak nie. Verander asseblief jou verstekblaaier en probeer weer.", + "rbf_explain": "Ons sal hierdie transaksie vervang met een met 'n hoër fooi sodat dit vinniger gemyn sal word. Dit word RBF—Replace by Fee genoem.", + "rbf_title": "Versnel (RBF)", + "status_bump": "Versnel", + "status_cancel": "Kanselleer", + "transactions_count": "Transaksietelling", + "txid": "Transaksie-ID", + "updating": "Opdateer...", + "watchOnlyWarningTitle": "Sekuriteitswaarskuwing", + "watchOnlyWarningDescription": "Wees versigtig vir bedrieërs wat dikwels “slegs-kyk” beursies gebruik om gebruikers te mislei. Hierdie beursies laat jou nie toe om fondse te beheer of te stuur nie; hulle laat jou net die balans bekyk.", + "custom_fee_warning_title": "Waarskuwing", + "custom_fee_warning_description": "Fooie onder 1 sat/vB is geldig, maar mag nie deurgestuur word weens knoop-beleide nie.", + "details_eta_analyzing": "Ontleed...", + "details_sent": "Gestuur", + "details_section": "Besonderhede", + "details_explorer": "verkenner", + "details_network_fee": "Netwerkfooi", + "details_to_address": "Aan", + "details_note": "Nota", + "details_add_note": "voeg by", + "details_advanced": "Gevorderd", + "details_fee_rate": "Fooi-koers", + "details_size": "Grootte", + "details_virtual_size": "Virtuele grootte", + "details_tx_hex": "Tx hex", + "details_inputs_count": "Insette ({count})", + "details_outputs_count": "Uitsette ({count})", + "details_id": "ID" }, "wallets": { "add_bitcoin": "Bitcoin", + "add_bitcoin_explain": "Eenvoudige en kragtige Bitcoin beursie", "add_create": "Skep", + "total_balance": "Totale balans", + "add_entropy_reset_title": "Herstel entropie", + "add_entropy_reset_message": "Deur die beursie-tipe te verander, sal die huidige entropie herstel word. Wil jy voortgaan?", + "add_entropy": "Entropie", + "add_entropy_bytes": "{bytes} grepe entropie", + "add_entropy_generated": "{gen} grepe gegenereerde entropie", + "add_entropy_provide": "Verskaf entropie deur dobbelsteenrolle", + "add_entropy_remain": "{gen} grepe gegenereerde entropie. Oorblywende {rem} grepe sal van die stelsel se ewekansige-getalgenereerder verkry word.", "add_import_wallet": "Beursie Invoer", - "add_lndhub_error": "Die voorgestelde knoop-punt se adres is 'n ongeldige LNDHub knoop-punt.", - "add_lndhub_placeholder": "Jou Knoop-punt Adres", + "add_lightning": "Lightning", + "add_lightning_explain": "Vir besteding met onmiddellike transaksies", + "add_lndhub": "Koppel aan jou LNDhub", + "add_lndhub_error": "Die verskafde knoop-adres is 'n ongeldige LNDhub-knoop.", + "add_lndhub_placeholder": "Jou Node-adres", + "add_placeholder": "my eerste beursie", "add_title": "skep beursie", "add_wallet_name": "beursie naam", "add_wallet_type": "tipe", - "details_address": "AdresAddress", + "add_wallet_seed_length": "Saadlengte", + "add_wallet_seed_length_12": "12 woorde", + "add_wallet_seed_length_24": "24 woorde", + "clipboard_bitcoin": "Jy het 'n Bitcoin adres op jou knipbord. Wil jy dit vir 'n transaksie gebruik?", + "clipboard_lightning": "Jy het 'n Lightning faktuur op jou knipbord. Wil jy dit vir 'n transaksie gebruik?", + "clear_clipboard_on_import": "Maak knipbord skoon by invoer", + "details_address": "Adres", + "details_advanced": "Gevorderd", "details_are_you_sure": "Is jy seker?", + "details_connected_to": "Gekoppel aan", + "details_del_wb_err": "Die verskafde balansbedrag stem nie ooreen met hierdie beursie se balans nie. Probeer asseblief weer.", + "details_del_wb_q": "Hierdie beursie het 'n balans. Voordat jy voortgaan, neem asseblief kennis dat jy nie die fondse sal kan herwin sonder hierdie beursie se saadfrase nie. Om toevallige verwydering te vermy, sleutel asseblief jou beursie se balans van {balance} satoshis in.", "details_delete": "Vernietig", + "details_delete_wallet": "Vernietig beursie", + "details_derivation_path": "afleidingspad", + "details_display": "Vertoon op tuisskerm", "details_export_backup": "voer uit / kopieer", - "details_no_cancel": "Nee, kanseleerl", - "details_save": "Berg", + "details_export_history": "Voer geskiedenis na CSV uit", + "details_master_fingerprint": "Meester-vingerafdruk", + "details_multisig_type": "multisig", "details_show_xpub": "Wys beursie XPUB", - "details_title": "Beursiet", + "details_show_addresses": "Wys adresse", + "details_title": "Beursie", + "wallets": "beursies", + "swipe_balance_hide": "Versteek", + "swipe_balance_show": "Wys", + "drag_to_reorder": "Sleep om te herrangskik", + "clear_search": "Maak soektog skoon", "details_type": "Tipe", + "details_use_with_hardware_wallet": "Gebruik met hardeware-beursie", "details_yes_delete": "Ja, vernietig", + "enter_bip38_password": "Sleutel wagwoord in om te dekripteer", "export_title": "beursie uitvoer", "import_do_import": "Invoer", - "import_error": "U invoer het misluk. Maak asseblief seker u data is korrek en geldig.", + "import_passphrase": "Wagfrase", + "import_passphrase_title": "Wagfrase", + "import_passphrase_message": "Sleutel wagfrase in indien jy enige gebruik het", + "import_error": "Jou invoer het misluk. Maak asseblief seker jou data is korrek en geldig.", + "import_explanation": "Sleutel asseblief jou saadwoorde, openbare sleutel, WIF, of enigiets wat jy het in. BlueWallet sal sy bes doen om die korrekte formaat te raai en jou beursie in te voer.", "import_imported": "Invoer suksesvol", "import_scan_qr": "of skandeer QR kode?", "import_success": "Suksesvol", + "import_success_watchonly": "Jou beursie is suksesvol ingevoer. WAARSKUWING: Dit is 'n slegs-kyk beursie, jy kan NIE daaruit bestee nie.", + "import_search_accounts": "Soek rekeninge", "import_title": "Invoer", + "learn_more": "Lees meer", + "import_discovery_title": "Ontdekking", + "import_discovery_subtitle": "Kies 'n ontdekte beursie", + "import_discovery_derivation": "Gebruik pasgemaakte afleidingspad", + "import_discovery_no_wallets": "Geen beursies is gevind nie.", + "import_discovery_offline": "BlueWallet is tans in aflynmodus. In hierdie modus kan dit nie die bestaan van die beursie verifieer nie, so jy sal die korrekte een handmatig moet kies", + "import_derivation_found": "Gevind", + "import_derivation_found_not": "Nie gevind nie", + "import_derivation_loading": "Laai...", + "import_derivation_subtitle": "Sleutel pasgemaakte afleidingspad in, en ons sal probeer om jou beursie te ontdek.", + "import_derivation_title": "Afleidingspad", + "import_derivation_unknown": "Onbekend", + "import_wrong_path": "Verkeerde afleidingspad", + "list_create_a_button": "Voeg nou by", "list_create_a_wallet": "Skep ‘n beursie", + "list_create_a_wallet_text": "Dit is gratis, en jy kan \nso veel skep as wat jy wil.", "list_empty_txs1": "Jou transaksies sal hier verskyn.", + "list_empty_txs1_lightning": "Lightning beursie moet vir jou daaglikse transaksies gebruik word. Fooie is onbillik goedkoop en die spoed is blitsig vinnig.", "list_empty_txs2": "Begin met jou beursie.", + "list_empty_txs2_lightning": "\nOm dit te begin gebruik, tik op Bestuur Fondse en vul jou balans aan.", "list_latest_transaction": "laaste transaksie", + "list_long_choose": "Kies foto", + "paste_from_clipboard": "Plak", + "import_file": "Voer lêer in", + "list_long_scan": "Skandeer QR-kode", "list_title": "beursies", + "list_tryagain": "Probeer weer", "no_ln_wallet_error": "Voordat jy 'n Lightning faktuur betaal moet jy eers 'n Lightning beursie aanwys.", - "reorder_title": "Herorganiseer Beursies", + "looks_like_bip38": "Dit lyk soos 'n wagwoordbeskermde private sleutel (BIP38).", + "manage_title": "Bestuur beursies", + "no_results_found": "Geen resultate gevind nie.", + "please_continue_scanning": "Gaan asseblief voort met skandering.", + "select_no_bitcoin": "Daar is tans geen Bitcoin beursies beskikbaar nie.", + "select_no_bitcoin_exp": "'n Bitcoin beursie is nodig om Lightning beursies aan te vul. Skep of voer asseblief een in.", "select_wallet": "Kies Beursie", - "xpub_copiedToClipboard": "Gestuur na klipbord.", + "pull_to_refresh": "Trek om te verfris", + "warning_do_not_disclose": "Deel nooit die inligting hieronder nie", + "scan_import": "Skandeer hierdie QR-kode om jou beursie in 'n ander toepassing in te voer.", + "write_down_header": "Skep 'n handmatige rugsteun", + "write_down": "Skryf hierdie woorde neer en stoor dit veilig. Gebruik hulle om jou beursie later te herstel.", + "wallet_type_this": "Hierdie beursie-tipe is {type}.", + "share_number": "Deel {number}", + "copy_ln_url": "Kopieer en stoor hierdie URL veilig om jou beursie later te herstel.", + "copy_ln_public": "Kopieer en stoor hierdie inligting veilig om jou beursie later te herstel.", "add_ln_wallet_first": "Jy moet eers 'n Lightning beursie byvoeg.", - "xpub_title": "beursie XPUB" + "identity_pubkey": "Identiteits-pubkey", + "xpub_title": "beursie XPUB", + "manage_wallets_search_placeholder": "Soek beursies, adresse, transaksies en notas", + "more_info": "Meer inligting", + "details_delete_wallet_error_message": "Daar was 'n probleem om te bevestig of hierdie beursie van kennisgewings verwyder is—dit kan weens 'n netwerkprobleem of swak verbinding wees. Indien jy voortgaan, mag jy steeds kennisgewings ontvang vir transaksies wat met hierdie beursie verband hou, selfs nadat dit verwyder is.", + "details_delete_anyway": "Vernietig in elk geval" + }, + "total_balance_view": { + "display_in_bitcoin": "Vertoon in Bitcoin", + "hide": "Versteek", + "display_in_sats": "Vertoon in sats", + "display_in_fiat": "Vertoon in {currency}", + "title": "Totale balans", + "explanation": "Bekyk die totale balans van al jou beursies op die oorsigskerm." }, "multisig": { + "multisig_vault": "Multisig kluis", + "default_label": "Multisig kluis", + "multisig_vault_explain": "Beste sekuriteit vir groot bedrae", + "provide_signature": "Teken 'n transaksie", + "provide_signature_details": "Gebruik jou toestel en beursie waar die sleutel woonagtig is om hierdie transaksie te teken", + "provide_signature_details_bluewallet": "In BlueWallet, gaan na die Stuur-skerm-kieslys en kies ", + "provide_signature_next_steps": "Skandeer of voer getekende transaksie in", + "provide_signature_next_steps_details": "Sodra jou beursie die transaksie suksesvol geteken het, skandeer die verskafde QR-kode of voer die gepaardgaande lêer in, en hersien dan al die transaksie-besonderhede voordat dit uitgesaai word.", + "vault_key": "Kluis-sleutel {number}", + "required_keys_out_of_total": "Vereiste sleutels uit die totaal", + "fee": "Fooi: {number}", + "fee_btc": "{number} BTC", "confirm": "Bevestig", "header": "Stuur", - "share": "deel", + "share": "Deel...", + "view": "Bekyk", + "shared_key_detected": "Gedeelde mede-tekenaar", + "shared_key_detected_question": "'n Mede-tekenaar is met jou gedeel, wil jy dit invoer?", + "manage_keys": "Bestuur sleutels", + "how_many_signatures_can_bluewallet_make": "hoeveel handtekeninge kan BlueWallet maak", + "signatures_required_to_spend": "Handtekeninge benodig {number}", + "signatures_we_can_make": "kan {number} maak", + "scan_or_import_file": "Skandeer of voer lêer in", + "export_coordination_setup": "Voer koördinasie-opstelling uit", + "cosign_this_transaction": "Mede-teken hierdie transaksie?", + "lets_start": "Kom ons begin", "create": "Skep", - "ms_help_title5": "Enable advanced mode" + "native_segwit_title": "Beste praktyk", + "wrapped_segwit_title": "Beste verenigbaarheid", + "legacy_title": "Legacy", + "co_sign_transaction": "Teken 'n transaksie", + "what_is_vault": "'n Kluis is 'n", + "what_is_vault_numberOfWallets": " {m}-uit-{n} multisig ", + "what_is_vault_wallet": "beursie.", + "vault_advanced_customize": "Kluis-instellings", + "needs": "Dit benodig", + "what_is_vault_description_number_of_vault_keys": " {m} kluis-sleutels ", + "what_is_vault_description_to_spend": "om te bestee en 'n derde een wat jy \nas rugsteun kan gebruik.", + "what_is_vault_description_to_spend_other": "om te bestee.", + "quorum": "{m} van {n} kworum", + "quorum_header": "Kworum", + "of": "van", + "wallet_type": "Beursie-tipe", + "invalid_mnemonics": "Hierdie terugval woorde lyk nie geldig nie.", + "invalid_cosigner": "Ongeldige mede-tekenaar-data", + "not_a_multisignature_xpub": "Hierdie is nie 'n XPUB van 'n multisig beursie nie!", + "invalid_cosigner_format": "Verkeerde mede-tekenaar: Hierdie is nie 'n mede-tekenaar vir {format} formaat nie.", + "create_new_key": "Skep nuwe", + "scan_or_open_file": "Skandeer of maak lêer oop", + "i_have_mnemonics": "Ek het 'n saad vir hierdie sleutel.", + "type_your_mnemonics": "Sit 'n saad in om jou bestaande kluis-sleutel in te voer.", + "this_is_cosigners_xpub": "Hierdie is die mede-tekenaar se XPUB—gereed om in 'n ander beursie ingevoer te word. Dit is veilig om dit te deel.", + "this_is_cosigners_xpub_airdrop": "Indien jy via AirDrop deel, moet die ontvangers in die koördinasie-skerm wees.", + "wallet_key_created": "Jou kluis-sleutel is geskep. Vat 'n oomblik om jou terugval woorde veilig te rugsteun.", + "are_you_sure_seed_will_be_lost": "Is jy seker? Jou terugval woorde sal verlore gaan as jy nie 'n rugsteun het nie.", + "forget_this_seed": "Vergeet hierdie saad en gebruik eerder die XPUB.", + "view_edit_cosigners": "Bekyk/wysig mede-tekenaars", + "this_cosigner_is_already_imported": "Hierdie mede-tekenaar is reeds ingevoer.", + "export_signed_psbt": "Voer getekende PSBT uit", + "input_fp": "Sleutel vingerafdruk in", + "input_fp_explain": "Slaan oor om die verstek te gebruik (00000000)", + "input_path": "Sit afleidingspad in", + "input_path_explain": "Slaan oor om die verstek te gebruik ({default})", + "ms_help": "Hulp", + "ms_help_title": "Hoe Multisig Kluise Werk: Wenke en Truuks", + "ms_help_text": "'n Beursie met veelvoudige sleutels, vir verhoogde sekuriteit of gedeelde bewaring", + "ms_help_title1": "Veelvoudige toestelle word aanbeveel.", + "ms_help_1": "Die kluis sal werk met ander BlueWallet apps en PSBT-versoenbare beursies, soos Electrum, Specter, Coldcard, Cobo Vault, ens.", + "ms_help_title2": "Wysig sleutels", + "ms_help_2": "Jy kan alle kluis-sleutels op hierdie toestel skep en hulle later verwyder of wysig. Om alle sleutels op dieselfde toestel te hê, het die ekwivalente sekuriteit van 'n gewone Bitcoin beursie.", + "ms_help_title3": "Kluis-rugsteune", + "ms_help_3": "Op die beursie-opsies sal jy jou kluis-rugsteun en slegs-kyk rugsteun vind. Hierdie rugsteun is soos 'n kaart na jou beursie. Dit is noodsaaklik vir beursie-herwinning indien jy een van jou sade verloor.", + "ms_help_title4": "Invoer van kluise", + "ms_help_4": "Om 'n multisig in te voer, gebruik jou rugsteun-lêer en die Invoer-funksie. Indien jy slegs sade en XPUB's het, kan jy die individuele Invoer-knoppie gebruik wanneer jy kluis-sleutels skep.", + "ms_help_title5": "Gevorderde modus", + "ms_help_5": "Standaard sal BlueWallet 'n 2-uit-3 kluis genereer. Om 'n ander kworum te skep of die adres-tipe te verander, aktiveer Gevorderde Modus in die Instellings." + }, + "is_it_my_address": { + "title": "Is dit my adres?", + "owns": "{label} besit {address}", + "enter_address": "Sleutel adres in", + "check_address": "Kontroleer adres", + "no_wallet_owns_address": "Geen van die beskikbare beursies besit die verskafde adres nie.", + "view_qrcode": "Bekyk QR-kode" + }, + "autofill_word": { + "title": "Saad finale woord", + "enter": "Sleutel jou gedeeltelike terugval woorde in", + "generate_word": "Genereer die finale woord", + "error": "Die invoer is nie 'n 11- of 23-woord gedeeltelike mnemoniek nie. Probeer asseblief weer." + }, + "cc": { + "change": "Kleingeld", + "coins_selected": "Munte gekies ({number})", + "selected_summ": "{value} gekies", + "empty": "Hierdie beursie het tans geen munte nie.", + "freeze": "Vries", + "freezeLabel": "Vries", + "freezeLabel_un": "Ontvries", + "header": "Muntbeheer", + "use_coin": "Gebruik munt", + "use_coins": "Gebruik munte", + "tip": "Hierdie kenmerk laat jou toe om munte te sien, te etiketteer, te vries of te kies vir verbeterde beursie-bestuur. Jy kan veelvoudige munte kies deur op die gekleurde sirkels te tik.", + "sort_asc": "Stygend", + "sort_desc": "Dalend", + "sort_height": "Hoogte", + "sort_value": "Waarde", + "sort_label": "Etiket", + "sort_by": "Sorteer volgens", + "sort_status": "Status" + }, + "units": { + "BTC": "BTC", + "MAX": "Maks", + "sat_vbyte": "sat/vByte", + "sats": "sats" }, "addresses": { + "copy_private_key": "Kopieer private sleutel", + "sensitive_private_key": "Waarskuwing: private sleutels is uiters sensitief. Gaan voort?", + "sign_title": "Teken/verifieer boodskap", + "sign_help": "Hier kan jy 'n kriptografiese handtekening skep of verifieer op grond van 'n Bitcoin adres.", + "sign_sign": "Teken", + "sign_verify": "Verifieer", + "sign_signature_correct": "Verifikasie suksesvol!", + "sign_signature_incorrect": "Verifikasie het misluk!", "sign_placeholder_address": "adres", + "sign_placeholder_message": "Boodskap", + "sign_placeholder_signature": "Handtekening", + "addresses_title": "Adresse", + "type_change": "Kleingeld", "type_receive": "Ontvang", + "type_used": "Gebruik", "transactions": "transaksies" + }, + "lnurl_auth": { + "register_question_part_1": "Wil jy 'n rekening registreer by", + "register_question_part_2": "met jou Lightning beursie?", + "register_answer": "Jy het 'n rekening suksesvol by {hostname} geregistreer!", + "login_question_part_1": "Wil jy aanmeld by", + "login_question_part_2": "met jou Lightning beursie?", + "login_answer": "Jy het suksesvol by {hostname} aangemeld!", + "link_question_part_1": "Wil jy jou rekening koppel by", + "link_question_part_2": "aan jou Lightning beursie?", + "link_answer": "Jou Lightning beursie is suksesvol aan jou rekening by {hostname} gekoppel!", + "auth_question_part_1": "Wil jy gewaarmerk word by", + "auth_question_part_2": "met jou Lightning beursie?", + "auth_answer": "Jy is suksesvol by {hostname} gewaarmerk!", + "could_not_auth": "Ons kon jou nie by {hostname} waarmerk nie.", + "authenticate": "Waarmerk" + }, + "bip47": { + "payment_code": "Betalingskode", + "contacts": "Kontakte", + "bip47_explain": "Herbruikbare en deelbare kode", + "bip47_explain_subtitle": "BIP47", + "purpose": "Herbruikbare en deelbare kode (BIP47)", + "pay_this_contact": "Betaal hierdie kontak", + "rename_contact": "Hernoem kontak", + "copy_payment_code": "Kopieer betalingskode", + "hide_contact": "Versteek kontak", + "rename": "Hernoem", + "provide_name": "Verskaf nuwe naam vir hierdie kontak", + "add_contact": "Voeg kontak by", + "provide_payment_code": "Verskaf betalingskode", + "invalid_pc": "Ongeldige betalingskode", + "notification_tx_unconfirmed": "Kennisgewing-transaksie is nog nie bevestig nie, wag asseblief", + "failed_create_notif_tx": "Kon nie aan-die-ketting transaksie skep nie", + "onchain_tx_needed": "Aan-die-ketting transaksie benodig", + "notif_tx_sent": "Kennisgewing-transaksie gestuur. Wag asseblief vir bevestiging", + "notif_tx": "Kennisgewing-transaksie", + "not_found": "Betalingskode nie gevind nie" } } diff --git a/loc/zar_xho.json b/loc/zar_xho.json index 7ee8fa33f7f..3a8437c8f49 100644 --- a/loc/zar_xho.json +++ b/loc/zar_xho.json @@ -3,137 +3,702 @@ "bad_password": "Iphasiwedi engalunganga, zama kwakhona", "cancel": "Rhoxisa", "continue": "Qhubeka", - "enter_password": "Faka inombolo yokuvula", - "never": "Ungalingi", - "ok": "OK", - "storage_is_encrypted": "Ukugcinwa kwakho kubhaliwe. Inombolo yokuvula iyadingeka ukuba ichithwe", - "save": "ndoloza", - "success": "Iphumelele" + "clipboard": "Ibhodi eqhotyoshwayo", + "discard_changes": "Lahla utshintsho?", + "discard_changes_explain": "Unotshintsho olungagcinwanga. Uqinisekile ukuba ufuna ukuzilahla kwaye ushiye isikrini?", + "enter_password": "Ngenisa igama lokugqitha", + "never": "Soze", + "ok": "Kulungile", + "copied": "Ikopishiwe!", + "of": "{number} ye-{total}", + "enter_url": "Faka i-URL", + "save": "Gcina...", + "pick_image": "Khetha kwilayibrari", + "pick_file": "Khetha ifayile", + "unlock": "Vula", + "port": "Iporti", + "ssl_port": "Iporti ye-SSL", + "suggested": "Ecetyisiweyo", + "storage_is_encrypted": "Indawo yakho yokugcina inoguqulelo oluntsonkothileyo. Igama lokugqitha liyafuneka ukuyisusa ngokuntsonkotha.", + "yes": "Ewe", + "no": "Hayi", + "seed": "Imbewu", + "success": "Impumelelo", + "wallet_key": "Isitshixo sesipaji", + "close": "Vala", + "change_input_currency": "Guqula imali yokufaka", + "refresh": "Hlaziya", + "enter_amount": "Faka imali", + "qr_custom_input_button": "Cofa izihlandlo ezili-10 ukufaka igalelo lesiqhelo" }, "azteco": { - "success": "Iphumelele" + "codeIs": "Ikhowudi yakho yevawutsha ithi", + "errorBeforeRefeem": "Ngaphambi kokuba uhlawule, kufuneka uqale wongeze i-wallet ye-Bitcoin.", + "errorSomething": "Kukho into engahambanga kakuhle. Ingaba le vawutsha isasebenza?", + "redeem": "Khulula kwingxowa", + "redeemButton": "Khulula", + "success": "Iphumelele", + "successMessage": "Ivawutsha ikhululwe ngempumelelo! Imali yakho kufuneka ifike kwingxowa yakho ye-Bitcoin kungekudala.", + "title": "Khulula ivawutsha ye-Azte.co" }, "entropy": { - "save": "ndoloza" + "save": "Ndoloza", + "title": "I-Entropy", + "undo": "Buyisela", + "amountOfEntropy": "{bits} kwii-bits ezi-{limit}" + }, + "errors": { + "broadcast": "Usasazo aluphumelelanga.", + "error": "Impazamo", + "network": "Impazamo yothungelwano" }, "lnd": { "expired": "Iphelewe lixesha", + "errorInvoiceExpired": "Invoyisi iphelewe lixesha.", + "expiresIn": "Iphelelwa lixesha kwi-{time} imizuzu", + "payButton": "Hlawula", + "payment": "Intlawulo", + "placeholder": "Invoyisi okanye idilesi", + "potentialFee": "Intlawulo enokwenzeka: {fee}", "refill": "Gcwalisa", + "refill_create": "Ukuze uqhubeke, nceda yenza ingxowa ye-Bitcoin yokugcwalisa.", + "refill_external": "Gcwalisa ngeNgxowa yangaphandle", "refill_lnd_balance": "Gcwalisa ingxowa yakho yemali", - "title": "lawula imali" + "sameWalletAsInvoiceError": "Awukwazi ukuhlawula i-invoyisi ngengxowa oyisebenzisile ukudala leyo invoyisi.", + "title": "Lawula iMali" + }, + "lndViewInvoice": { + "additional_info": "Ulwazi olongezelelweyo", + "for": "Kuye:", + "lightning_invoice": "I-invoyisi ye-Lightning", + "please_pay_between_and": "Nceda hlawula phakathi kwe-{min} kunye ne-{max}", + "please_pay": "Nceda hlawula", + "preimage": "Pre-image", + "sats": "sats.", + "date_time": "Umhla neXesha", + "wasnt_paid_and_expired": "Le invoyisi ayihlawulwanga kwaye iphelelwe lixesha." + }, + "pleasebackup": { + "ask": "Ingaba uyigcinile i-backup phrase yengxowa yakho? Le backup phrase iyafuneka ukufikelela kwimali yakho ukuba ulahlekelwa sesi sixhobo. Ngaphandle kwe-backup phrase, imali yakho iya kulahleka unaphakade.", + "ask_no": "Hayi, andikayigcini.", + "ask_yes": "Ewe, ndiyigcinile.", + "ok": "Kulungile, ndiyibhalile phantsi.", + "ok_lnd": "Kulungile, ndiyigcinile.", + "text": "Nceda thabatha umzuzu ubhale eli binzana lokukhumbula kwiphepha.\nLeli libinzana lakho lokuqwalasela kwaye unokulisebenzisa ukubuyisela ingxowa.", + "text_lnd": "Nceda gcina le backup yengxowa. Ikuvumela ukuba ubuyisele ingxowa xa ulahlekelwa.", + "title": "Ingxowa yakho idaliwe." }, "plausibledeniability": { "create_fake_storage": "Ukudala igumbi lokugcina elifihlakeleyo", - "create_password": "Yenza inombolo yokuvula", - "create_password_explanation": "Inombolo yakho yokuvula igumbi lokugcina inkohliso akumele ifane ne nombolo yokuvula igumbi lakho elinyanisekileyo", - "help": "Phantsi kweemeko unokunyaneliswa ukuba uchaze a inombolo yokuvula. BlueWallet ukugcina imali yakho ikhuselekile, unokudala enye ukugcinwa kwekhowudi, ngegama eligqithisiweyo. Phantsi kwefuthe, unako ukutyhila le phasiwedi kumntu wesithatu. Ukuba ungenayo BlueWallet, iya kuvula ukugcinwa kwetyala ‘entsha’. Oku kuya kubonakala Umlenze kumntu wesithathu kodwa uza kugcina ngasese ukugcinwa kwakho ngemali ekhuselekile..", + "create_password_explanation": "Iphasiwedi yendawo yakho yokugcina yenkohliso akumele ifane neyendawo yakho engundoqo yokugcina.", + "help": "Phantsi kweemeko unokunyanzeliswa ukuba uchaze iphasiwedi. Ukugcina iimali zakho zikhuselekile, BlueWallet inokudala enye indawo yokugcina enoguqulelo oluntsonkothileyo, ngephasiwedi eyahlukileyo. Phantsi kwefuthe, ungayityhila le phasiwedi kumntu wesithathu. Ukuba ifakwe kwi-BlueWallet, iya kuvula indawo ‘yobuxoki’ yokugcina. Oku kuya kubonakala kufanelekile kumntu wesithathu kodwa kuya kugcina ngasese indawo yakho engundoqo yokugcina iimali ikhuselekile.", "help2": "Igumbi lokugcina elitsha liza kusebenza ngokupheleleyo, kwaye unako ukugcina okunye ‘ + ‘lxabiso elincinci apho likhangeleka ngakumbi.", - "password_should_not_match": "Inombolo yakho yokuvula igumbi lokugcina inkohliso akumele ifane ne nombolo yokuvula igumbi lakho elinyanisekileyo", - "passwords_do_not_match": "Inombolo yokuvula ayihambelani, zama kwakhona", - "retype_password": "Phinda inombolo yokuvula", - "success": "Iphumelele", + "password_should_not_match": "Iphasiwedi yendawo yakho yokugcina yenkohliso akumele ifane neyendawo yakho engundoqo yokugcina.", "title": "Ukuphika" }, "receive": { "details_create": "Yenza", "details_label": "Inkcazo", "details_setAmount": "Fumana ngexabiso", - "details_share": "yabelana", + "details_share": "Yabelana...", + "address_not_found": "Akukwazeki ukuvelisa idilesi yokufumana.", + "reset": "Cwangcisa kwakhona", + "maxSats": "Isixa esiphezulu yi-{max} sats", + "maxSatsFull": "Isixa esiphezulu yi-{max} sats okanye {currency}", + "minSats": "Isixa esisezantsi yi-{min} sats", + "minSatsFull": "Isixa esisezantsi yi-{min} sats okanye {currency}", + "qrcode_for_the_address": "iQR code yedilesi", + "bip47_explanation": "Ii-payment codes yidilesi yendalo iphela ekugwema ukutyhilwa kweedilesi zengxowa yakho. Iinkonzo azizukuyixhasa zonke.", "header": "Fumana" }, "send": { "broadcastButton": "Sasazwa", "broadcastSuccess": "Iphumelele", + "provided_address_is_invoice": "Le dilesi ibonakala iyi-Lightning invoyisi. Nceda, yiya kwingxowa yakho ye-Lightning ukwenza intlawulo yale invoyisi.", + "broadcastError": "Impazamo", + "broadcastNone": "Faka i-Transaction Hex", + "broadcastPending": "Ilindile", "confirm_header": "Qiniseka", "confirm_sendNow": "Thumela ngoku", "create_amount": "Isixa", "create_broadcast": "Sasazwa", + "create_copy": "Kopa kwaye usasaze emva", "create_details": "Iinkcukacha", "create_fee": "Ntlawulo", - "create_this_is_hex": "Le yi ntengo hex, ityikityiwe ilungele ukukhutshelwa kumnatha.", + "create_memo": "Inqaku", + "create_satoshi_per_vbyte": "Satoshi nge-vByte", + "create_this_is_hex": "Le yi-hex yentengiselwano yakho, ityikityiwe ilungele ukukhutshelwa kumnatha.", "create_to": "Iya ku", "create_tx_size": "TX ubungakanani", - "details_address": "idilesi", + "create_verify": "Qinisekisa kwi-coinb.in", + "details_address": "Idilesi", "details_address_field_is_not_valid": "Intsimi yeedilesi ayivumelekanga", - "details_amount_field_is_not_valid": "intsimi yexabiso ayivumelekanga", + "details_amount_field_is_not_valid": "Intsimi yexabiso ayivumelekanga", + "details_insert_contact": "Faka iKontakthi", + "details_add_rec_add": "Yongeza Umamkeli", + "details_add_rec_rem": "Susa Umamkeli", + "details_add_recc_rem_all_alert_description": "Uqinisekile ukuba ufuna ukususa bonke abamkeli?", + "details_add_rec_rem_all": "Susa Bonke Abamkeli", + "details_recipients_title": "Abamkeli", + "details_recipient_title": "Umamkeli #{number} kwi-#{total}", + "please_complete_recipient_title": "Umamkeli ongaphelelanga", + "please_complete_recipient_details": "Nceda phelelisa iinkcukacha zomamkeli #{number} phambi kokongeza umamkeli omtsha.", + "details_adv_fee_bump": "Vumela i-Fee Bump", + "details_adv_full": "Sebenzisa iBhalansi epheleleyo", + "details_adv_full_sure": "Uqinisekile ufuna ukusebenzisa yonke ibhalansi yengxowa yakho kule ntengiselwano?", + "details_adv_full_sure_frozen": "Uqinisekile ufuna ukusebenzisa yonke ibhalansi yengxowa yakho kule ntengiselwano? Nceda qaphela ukuba iimali ezikhenkceziweyo zikhutshelwe ngaphandle.", + "details_adv_import": "Ngenisa iTransaction", + "details_adv_import_qr": "Ngenisa iTransaction (QR)", + "details_amount_field_is_less_than_minimum_amount_sat": "Isixa esikhankanyiweyo sincinci kakhulu. Nceda faka isixa esikhulu kuneesats ezi-500.", "details_create": "Yenza", + "details_error_decode": "Akukwazeki ukufunda idilesi ye-Bitcoin", "details_fee_field_is_not_valid": "Intsimi yentlawulo ayivumelekanga ", - "details_note_placeholder": "inqaku kumntu", - "details_scan": "Ukutshekisha", + "details_frozen": "I-{amount} BTC i-frozen.", + "details_next": "Okulandelayo", + "details_no_signed_tx": "Ifayile ekhethiweyo ayinayo i-transaction enokungeniswa.", + "details_note_placeholder": "Inqaku kumntu", + "details_scan": "Skena", + "details_scan_hint": "Cofa kabini ukuskena okanye ukungenisa indawo yokuya", + "details_scan_error": "Impazamo yokuskena", "details_total_exceeds_balance": "Imali yokuthumela idlula imali ekhoyo.", + "details_total_exceeds_balance_frozen": "Isixa sokuthumela sidlula ibhalansi ekhoyo. Nceda qaphela ukuba iicoins ezi-frozen aziqukwa.", + "details_unrecognized_file_format": "Ifomathi yefayile engaqondakaliyo", + "details_wallet_before_tx": "Phambi kokudala i-transaction, kufuneka uqale wongeze ingxowa ye-Bitcoin.", + "dynamic_init": "Kuqaliswa", + "dynamic_next": "Okulandelayo", + "dynamic_prev": "Okwangaphambili", + "dynamic_start": "Qala", + "dynamic_stop": "Yima", + "fee_10m": "10m", + "fee_1d": "1d", + "fee_3h": "3h", + "fee_custom": "Esiqhelo", + "insert_custom_fee": "Faka intlawulo", + "fee_fast": "Ngokukhawuleza", + "fee_medium": "Phakathi", + "fee_replace_minvb": "Ireyithi yentlawulo iyonke (satoshi nge-vByte) ofuna ukuyihlawula kufuneka iphezulu kune-{min} sat/vByte.", + "fee_satvbyte": "kwi-sat/vByte", + "fee_slow": "Cothayo", + "input_clear": "Cima", + "input_paste": "Ncamathisela", + "input_total": "Iyonke:", + "permission_camera_message": "Sifuna imvume yakho yokusebenzisa ikhamera yakho.", + "psbt_sign": "Sayina i-transaction", + "invalid_psbt": "I-PSBT engasebenziyo inikiwe.", + "open_settings": "Vula iZicwangciso", + "permission_storage_denied_message": "I-BlueWallet ayikwazi ukugcina le fayile. Nceda vula izicwangciso zesixhobo sakho kwaye uvumele iMvume yokuGcina.", + "permission_storage_title": "Imvume yokuFikelela kwiGcino", + "psbt_clipboard": "Kopa kwi-Clipboard", + "psbt_this_is_psbt": "Le yiNtengiselwano ye-Bitcoin Etyikitywe Ngokuyinxenye (PSBT). Nceda gqibezela ukuyityikitya ngengxowa yakho yehardware.", + "psbt_tx_export": "Khupha kwifayile", + "no_tx_signing_in_progress": "Akukho ntengiselwano iqhubekayo yokutyikitya.", + "outdated_rate": "Ireyithi ihlaziywe okokugqibela: {date}", + "psbt_tx_open": "Vula i-Transaction etyikityiweyo", + "psbt_tx_scan": "Skena i-Transaction etyikityiweyo", + "qr_error_no_qrcode": "Asikwazanga kufumana iQR Code esebenzayo kumfanekiso okhethiweyo. Qiniseka ukuba umfanekiso uqulethe iQR Code kuphela kwaye akukho omnye umxholo onjengetekisi okanye amaqhosha.", + "reset_amount": "Cwangcisa kwakhona iSixa", + "reset_amount_confirm": "Ungathanda ukucwangcisa kwakhona isixa?", + "txSaved": "Ifayile ye-transaction ({filePath}) igciniwe.", + "file_saved_at_path": "Ifayile ({filePath}) igciniwe.", + "cant_send_to_silentpayment_adress": "Le ngxowa ayikwazi ukuthumela kwiidilesi ze-SilentPayment", + "cant_send_to_bip47": "Le ngxowa ayikwazi ukuthumela kwii-BIP47 payment codes", + "cant_find_bip47_notification": "Yongeza le Khowudi yeNtlawulo kuqala kwiqhakamshelo", + "problem_with_psbt": "Ingxaki nge-PSBT", "header": "Thumela", "input_done": "Kwenzekile", "success_done": "Kwenzekile" }, "settings": { "about": "Malunga", + "about_awesome": "Yakhiwe nge", + "about_backup": "Hlala usenza i-backup yezitshixo zakho!", + "about_free": "I-BlueWallet yiprojekthi yasimahla kwaye yi-open-source. Yenziwe ngabasebenzisi be-Bitcoin.", + "about_license": "Ilayisensi ye-MIT", + "about_release_notes": "Amanqaku okukhupha", + "about_review": "Sishiyele uphengululo", + "performance_score": "Inqaku lokusebenza: {num}", + "run_performance_test": "Vavanya ukusebenza", + "about_selftest": "Yenza uvavanyo lwakho", + "block_explorer_invalid_custom_url": "I-URL enikiweyo ayisebenzi. Nceda faka i-URL esebenzayo eqala nge-http:// okanye https://.", + "about_selftest_electrum_disabled": "Uvavanyo lwakho aluphumeleli kuMode wokungaqhagamshelwanga we-Electrum. Nceda yivale imode yokungaqhagamshelwanga uzame kwakhona.", + "about_selftest_ok": "Zonke iimvavanyo zangaphakathi zigqitywe ngempumelelo. Ingxowa isebenza kakuhle.", + "about_sm_github": "GitHub", + "about_sm_telegram": "Isiqendu seTelegram", + "privacy_temporary_screenshots": "Vumela ukuThatha kweSkrini", + "privacy_temporary_screenshots_instructions": "Ukhuseleko lokuthatha iskrini luyakucinywa okwexeshana, kuvumela ii-screenshot kunye nokurekhoda iskrini. Ukhuseleko luya kuvuselelwa ngokuzenzekelayo xa uvala uvule kwakhona i-BlueWallet.", + "biometrics": "I-Biometrics", + "biometrics_no_longer_available": "Izicwangciso zesixhobo sakho zitshintshile kwaye azisahambelani nezicwangciso zokhuseleko ezikhethiweyo kwiapp. Nceda yenza kwakhona i-biometrics okanye i-passcode, emva koko uphinde uqalise iapp ukusebenzisa olu tshintsho.", + "biom_10times": "Uzamile ukufaka iphasiwedi yakho amaxesha ali-10. Ungathanda ukucwangcisa kwakhona indawo yokugcina? Oku kuza kususa zonke iingxowa kwaye kuvule ukugcina kwakho.", + "biom_conf_identity": "Nceda qinisekisa isazisi sakho.", + "biom_no_passcode": "Isixhobo sakho asinayo i-passcode okanye i-biometrics ekhutshwayo. Ukuze uqhubeke, nceda misa i-passcode okanye i-biometric kwi-app yeZicwangciso.", + "biom_remove_decrypt": "Zonke iingxowa zakho ziya kususwa kwaye indawo yakho yokugcina iya kuvulwa. Uqinisekile ukuba ufuna ukuqhubeka?", + "currency_source": "Ireyithi ifunyenwe kwi", + "currency_fetch_error": "Bekukho impazamo ngelixa ufumana ireyithi yemali ekhethiweyo.", + "default_title": "Ekuqaliseni", + "donate": "Nikela", + "donate_description": "Sincede sigcine i-Blue isimahla!", + "electrum_connected": "Iqhagamshelwe", + "electrum_connected_not": "Ayiqhagamshelwanga", + "electrum_error_connect": "Ayikwazi ukuqhagamshela kwiseva ye-Electrum enikiweyo", + "electrum_error_connect_tor": "Ayikwazi ukuqhagamshela kwiseva ye-Electrum enikiweyo. Nceda qiniseka ukuba i-Orbot app iqhagamshelwe uzame kwakhona.", + "lndhub_uri": "Umz., {example}", + "electrum_host": "Umz., {example}", + "electrum_offline_mode": "iMode yokungaqhagamshelwanga", + "electrum_offline_description": "Xa ikhutshiwe, iingxowa zakho ze-Bitcoin azisayi kuzama ukufumana iibhalansi okanye ii-transactions.", + "electrum_port": "Iporti, ngokuqhelekileyo {example}", + "use_ssl": "Sebenzisa i-SSL", + "electrum_saved": "Utshintsho lwakho lugciniwe ngempumelelo. Ukuvuselela i-BlueWallet kunokufuneka ukuze utshintsho lusebenze.", + "set_electrum_server_as_default": "Misa i-{server} njengeseva ye-Electrum eyenzelwe ngokwesiqhelo?", + "set_lndhub_as_default": "Misa i-{url} njengeseva ye-LNDhub eyenzelwe ngokwesiqhelo?", + "electrum_settings_server": "Iseva ye-Electrum", + "electrum_status": "Imeko", + "electrum_preferred_server": "Iseva ekhethwayo", + "electrum_preferred_server_description": "Faka iseva ofuna ingxowa yakho iyisebenzisele zonke iindawo ze-Bitcoin. Xa imisiwe, ingxowa yakho iya kusebenzisa kuphela le seva ukujonga iibhalansi, ukuthumela ii-transactions, kunye nokufumana idatha yothungelwano. Qiniseka ukuba uyathemba le seva ngaphambi kokuyimisa.", + "electrum_unable_to_connect": "Ayikwazi ukuqhagamshela kwi-{server}.", + "electrum_history": "Imbali", + "electrum_reset_to_default": "Oku kuya kuvumela i-BlueWallet ikhethe ngokungenamthetho iseva kuluhlu lweeseva.", + "electrum_reset": "Cwangcisa kwakhona kweyokuqala", + "electrum_reset_to_default_and_clear_history": "Cwangcisa kwakhona kweyokuqala kwaye usule imbali", + "encrypt_decrypt": "Vula uGcino", + "encrypt_decrypt_q": "Uqinisekile ukuba ufuna ukuvula uGcino lwakho? Oku kuya kuvumela iingxowa zakho zifikelelwe ngaphandle kwephasiwedi.", + "encrypt_enc_and_pass": "Ikhuselwe ngePhasiwedi", + "encrypt_storage_explanation_headline": "Yenza uGuqulelo lokuGcina", + "encrypt_storage_explanation_description_line1": "Ukuvula uGuqulelo lokuGcina kongeza umsonto omtsha wokhuseleko kwi-app yakho ngokukhusela indlela edatha yakho igcinwa ngayo kwisixhobo sakho. Oku kwenza kube nzima kubantu ukufikelela kulwazi lwakho ngaphandle kwemvume.", + "encrypt_storage_explanation_description_line2": "Nangona kunjalo, kubalulekile ukwazi ukuba olu guqulelo lukhusela kuphela ukufikelela kwiingxowa zakho ezigcinwe kwi-keychain yesixhobo. Aluyibeki iphasiwedi okanye nasiphi na ukhuseleko olongezelelweyo kwiingxowa ngokwazo.", + "i_understand": "Ndiyaqonda", + "block_explorer": "I-Block Explorer", + "block_explorer_preferred": "Sebenzisa i-block explorer ekhethwayo", + "block_explorer_error_saving_custom": "Impazamo ekugcineni i-block explorer ekhethwayo", + "encrypt_title": "Ukhuseleko", + "encrypt_tstorage": "uGcino", + "encrypt_use": "Sebenzisa i-{type}", + "set_as_preferred": "Misa njengekhethwayo", + "set_as_preferred_electrum": "Ukumisa i-{host}:{port} njengeseva ekhethwayo kuya kuvala ukuqhagamshela kwiseva ecetyiswayo ngokungenamthetho.", + "encrypted_feature_disabled": "Lo mphandle awunakusetyenziswa nogcino oluguqulelwe.", + "encrypt_use_expl": "I-{type} iya kusetyenziswa ukuqinisekisa isazisi sakho phambi kokwenza i-transaction, ukuvula, ukukhupha, okanye ukucima ingxowa.", + "biometrics_fail": "Ukuba i-{type} ayikhutshwanga, okanye yehluleka ukuvula, ungasebenzisa i-passcode yesixhobo sakho njengenye indlela.", + "general": "Jikelele", + "general_continuity": "Ukuqhubeka", + "general_continuity_e": "Xa ikhutshiwe, uya kukwazi ukujonga iingxowa ezikhethiweyo, kunye nee-transactions, usebenzisa ezinye izixhobo zakho ze-Apple iCloud eziqhagamshelweyo.", + "groundcontrol_explanation": "I-GroundControl yiseva yokupusha izaziso esimahla kwaye yi-open-source kwiingxowa ze-Bitcoin. Unganokufaka eyakho iseva ye-GroundControl ufake i-URL yayo apha ukuze ungaxhomekeki kwi-infrastructure ye-BlueWallet. Yishiye ingenanto ukusebenzisa iseva yokuqala ye-GroundControl.", + "last_updated": "Lihlaziywe okokugqibela", + "language_isRTL": "Ukuvuselela i-BlueWallet kuyafuneka ukuze ulwimi lujikeleze lusebenze.", + "license": "Ilayisensi", + "lightning_error_lndhub_uri": "I-LNDhub URI engasebenziyo", + "lightning_error_lndhub_uri_tor": "I-LNDhub URI engasebenziyo. Nceda qiniseka ukuba i-Orbot app iqhagamshelwe uzame kwakhona.", + "lightning_saved": "Utshintsho lwakho lugciniwe ngempumelelo.", + "lightning_settings_explain": "Ukuqhagamshela kwi-LND node yakho, nceda faka i-LNDhub kwaye ufake i-URL yayo apha kwizicwangciso. Nceda qaphela ukuba kuphela iingxowa eziyilwa emva kokugcina utshintsho ziya kuqhagamshela kwi-LNDhub ekhankanyiweyo.", + "lndhub_github": "Ividi ye-GitHub", + "network": "Uthungelwano", + "network_broadcast": "Sasaza iTransaction", + "network_electrum": "Iseva ye-Electrum", + "electrum_suggested_description": "Xa iseva ekhethwayo ingamiselwanga, iseva ecetyiswayo iya kukhethwa ngokungenamthetho ukuze isetyenziswe.", + "not_a_valid_uri": "I-URI engasebenziyo", + "notifications": "Izaziso", + "open_link_in_explorer": "Vula ikhonkco kwi-explorer", + "password_explain": "Faka iphasiwedi oza kuyisebenzisa ukuvula indawo yakho yokugcina.", + "privacy": "Ubumfihlo", + "privacy_read_clipboard": "Funda i-Clipboard", + "privacy_system_settings": "Izicwangciso zeNkqubo", + "privacy_quickactions": "Iindlela ezikhawulezayo zeNgxowa", + "privacy_quickactions_explanation": "Bamba uchukumise iikoni ye-app ye-BlueWallet ukuze ukhawuleze ujonge ibhalansi yengxowa yakho.", + "privacy_clipboard_explanation": "Bonelela ngeendlela ezikhawulezayo ukuba idilesi okanye i-invoyisi ifumaneka kwi-clipboard yakho.", + "privacy_do_not_track": "Vala uHlalutyo", + "privacy_do_not_track_explanation": "Ulwazi lokusebenza nokuthembeka aluzukuthunyelwa ukuze luhlalutywe.", + "rate": "Ireyithi", + "push_notifications_explanation": "Ngokuvula izaziso, itoken yesixhobo sakho iya kuthunyelwa kwiseva, kunye needilesi zengxowa kunye nee-IDs ze-transactions zazo zonke iingxowa kunye nee-transactions ezenziwe emva kokuvula izaziso. Itoken yesixhobo isetyenziselwa ukuthumela izaziso, kwaye ulwazi lwengxowa lusivumela ukukwazisa malunga ne-Bitcoin engenayo okanye ukuqinisekiswa kwe-transactions.\n\nUlwazi kuphela kususela ekuvuleni kwakho izaziso luyathunyelwa—akukho nto ngaphambili iqokelelwayo.\n\nUkuvala izaziso kuya kususa lonke olu lwazi kwiseva. Ukongezelela, ukucima ingxowa kwi-app kukwakhulula ulwazi lwayo lwadibanayo kwiseva.", + "selfTest": "Uvavanyo lwakho", + "saved": "Igciniwe", + "success_transaction_broadcasted": "I-transaction yakho isasazwe ngempumelelo!", + "total_balance": "iBhalansi iyonke", + "total_balance_explanation": "Bonisa ibhalansi epheleleyo yazo zonke iingxowa zakho kwiiwijethi zesikrini sakho sasekhaya.", + "widgets": "Iiwijethi", + "tools": "Izixhobo", "currency": "Lwemali", - "electrum_clear_alert_cancel": "Rhoxisa", - "general_adv_mode": "Enable advanced mode", "header": "Izicwangciso", "language": "Ulwimi", - "lightning_settings": "Izixhobo zokukhanyisa", + "lightning_settings": "Izicwangciso ze-Lightning", "password": "Inombolo yokuvula", - "password_explain": "Ukudala iinombolo yokuvula oyisebenzisayo ukucima ukugcina", - "passwords_do_not_match": "Inombolo yokuvula azifani", "plausible_deniability": "Ukuphika...", - "retype_password": "Phina inombolo yokuvula", - "save": "ndoloza" + "save": "Ndoloza" + }, + "notifications": { + "would_you_like_to_receive_notifications": "Ungathanda ukufumana izaziso xa ufumana iintlawulo ezingenayo?", + "notifications_subtitle": "Iintlawulo ezingenayo nokuqinisekiswa kwe-transactions", + "no_and_dont_ask": "Hayi, kwaye ungandibuzi kwakhona.", + "permission_denied_message": "Ulile imvume yokukuthumela izaziso. Ukuba ungathanda ukufumana izaziso, nceda zivule kwizicwangciso zesixhobo sakho." }, "transactions": { + "cancel_explain": "Siya kubeka kwakhona le ntengiselwano ngenye ekuhlawulayo nenentlawulo ephezulu. Oku kurhoxisa ngempumelelo intengiselwano yangoku. Oku kubizwa ngokuba yi-RBF—Replace by Fee.", + "cancel_no": "Le ntengiselwano ayinakubuyiselwa.", + "cancel_title": "Rhoxisa le ntengiselwano (RBF)", + "transaction_loading_error": "Bekukho ingxaki yokulayisha i-transaction. Nceda zama kwakhona kamva.", + "transaction_not_available": "I-Transaction ayifumaneki", + "confirmations_lowercase": "{confirmations} iziqinisekiso", + "expand_note": "Yandlala iNqaku", "cpfp_create": "Yakha", + "cpfp_exp": "Siza kuyila enye i-transaction echitha i-transaction yakho engaqinisekiswanga. Intlawulo iyonke iya kuba phezulu kunentlawulo yokuqala ye-transaction, ngoko ke kufuneka i-mined ngokukhawuleza. Oku kubizwa ngokuba yi-CPFP—Child Pays for Parent.", + "cpfp_no_bump": "Le ntengiselwano ayinokuphakanyiswa.", + "cpfp_title": "Nyusa iNtlawulo (CPFP)", + "details_balance_hide": "Fihla iBhalansi", + "details_balance_show": "Bonisa iBhalansi", "details_copy": "Ikopi", - "details_from": "Negalelo", - "details_show_in_block_explorer": "Bonisa ibhloko umhloi", - "details_title": "Ngeniswa", - "details_to": "Mveliso", - "list_title": "ngeniswa" + "details_copy_block_explorer_link": "Kopa iKhonkco le-Block Explorer", + "details_copy_note": "Kopa iNqaku", + "details_copy_txid": "Kopa i-ID ye-Transaction", + "details_inputs": "Iinputs", + "details_outputs": "Iioutputs", + "date": "Umhla", + "details_received": "Ifunyenwe", + "details_title": "Intengiselwano", + "details_to": "Imveliso", + "details_view_in_browser": "Bonisa kwiBrawza", + "incoming_transaction": "Intengiselwano Engenayo", + "outgoing_transaction": "Intengiselwano Ephumayo", + "expired_transaction": "Intengiselwano Ephelelweyo", + "pending_transaction": "Intengiselwano Elindileyo", + "offchain": "Offchain", + "onchain": "Onchain", + "enable_offline_signing": "Le ngxowa ayisetyenziswa kunye nokutyikitya okungaqhagamshelwanga. Ungathanda ukukuvula ngoku?", + "list_conf": "Iziqin: {number}", + "pending": "Ilindile", + "pending_with_amount": "I-Pending {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "ETA: Kwi-~10 imizuzu", + "eta_3h": "ETA: Kwi-~3 iiyure", + "eta_1d": "ETA: Kwi-~1 usuku", + "list_title": "Iintengiselwano", + "list_title_sent": "Ithunyelwe", + "list_title_received": "Ifunyenwe", + "open_url_error": "Akukwazeki ukuvula ikhonkco ngebrawza yokuqala. Nceda tshintsha ibrawza yakho yokuqala uzame kwakhona.", + "rbf_explain": "Siya kubeka kwakhona le ntengiselwano ngenye enentlawulo ephezulu ukuze imbiwe ngokukhawuleza. Oku kubizwa ngokuba yi-RBF—Replace by Fee.", + "rbf_title": "Phakamisa Intlawulo (RBF)", + "status_bump": "Phakamisa Intlawulo", + "status_cancel": "Rhoxisa", + "transactions_count": "Inani lee-Transaction", + "txid": "I-ID ye-Transaction", + "updating": "Iyahlaziya...", + "watchOnlyWarningTitle": "Isilumkiso sokhuseleko", + "watchOnlyWarningDescription": "Lumka ngabaqhathi abadla ngokusebenzisa iingxowa ze-watch-only ukukhohlisa abasebenzisi. Ezi ngxowa azikuvumeli ukuba ulawule okanye uthumele imali; zikuvumela kuphela ukuba ujonge ibhalansi.", + "custom_fee_warning_title": "Isilumkiso", + "custom_fee_warning_description": "Iintlawulo ezingaphantsi kwe-1 sat/vB ziyasebenza, kodwa azinakwaziswa ngenxa yemigaqo ye-node.", + "details_eta_analyzing": "Iyahlalutya...", + "details_sent": "Ithunyelwe", + "details_section": "Iinkcukacha", + "details_explorer": "i-explorer", + "details_network_fee": "Intlawulo yoThungelwano", + "details_to_address": "Kuye", + "details_id": "I-ID", + "details_note": "Inqaku", + "details_add_note": "yongeza", + "details_advanced": "Esezantsi", + "details_fee_rate": "Ireyithi yeNtlawulo", + "details_size": "Ubungakanani", + "details_virtual_size": "Ubungakanani be-Virtual", + "details_tx_hex": "Tx Hex", + "details_inputs_count": "Iinputs ({count})", + "details_outputs_count": "Iioutputs ({count})", + "transaction": "Intengiselwano" }, "wallets": { - "add_bitcoin_explain": "Simple and powerful Bitcoin wallet", + "add_bitcoin": "I-Bitcoin", + "total_balance": "iBhalansi iyonke", + "add_bitcoin_explain": "Ingxowa ye-Bitcoin elula nenamandla", "add_create": "Yakha", - "add_import_wallet": "Ukungenisa ingxowa", + "add_entropy_reset_title": "Cwangcisa kwakhona i-Entropy", + "add_entropy_reset_message": "Ukutshintsha uhlobo lwengxowa kuya kucwangcisa kwakhona i-entropy yangoku. Ufuna ukuqhubeka?", + "add_entropy": "I-Entropy", + "add_entropy_bytes": "{bytes} iibytes ze-entropy", + "add_entropy_generated": "{gen} iibytes ze-entropy eveliswayo", + "add_entropy_provide": "Bonelela nge-entropy ngokugingqa kwedayisi", + "add_entropy_remain": "{gen} iibytes ze-entropy eveliswayo. Iibytes ezi-{rem} eziseleyo ziya kufunyanwa kuMqondisi wamanani angekho mthethweni weNkqubo.", + "add_import_wallet": "Ngenisa ingxowa", "add_lightning": "Umbane", - "add_lightning_explain": "For spending with instant transactions", - "add_title": "yongeza ingxowa", - "add_wallet_name": "igama lengxowa", - "add_wallet_type": "uhlobo", - "details_address": "Ikheli", + "add_lndhub": "Qhagamshela kwi-LNDhub yakho", + "add_lndhub_error": "Idilesi ye-node enikiweyo yi-LNDhub node engasebenziyo.", + "add_lndhub_placeholder": "Idilesi yakho ye-Node", + "add_lightning_explain": "Yokusebenzisa ngeentengiselwano ezikhawulezayo", + "add_placeholder": "ingxowa yam yokuqala", + "add_title": "Yongeza ingxowa", + "add_wallet_name": "Igama lengxowa", + "add_wallet_seed_length": "Ubude be-Seed", + "add_wallet_seed_length_12": "Amagama ali-12", + "add_wallet_seed_length_24": "Amagama angama-24", + "add_wallet_type": "Uhlobo", + "clipboard_bitcoin": "Unedilesi ye-Bitcoin kwi-clipboard yakho. Ungathanda ukuyisebenzisa kwintengiselwano?", + "clipboard_lightning": "Unayo i-invoyisi ye-Lightning kwi-clipboard yakho. Ungathanda ukuyisebenzisa kwintengiselwano?", + "clear_clipboard_on_import": "Cima i-clipboard xa kungeniswa", + "details_address": "Idilesi", + "details_advanced": "Okuphambili", "details_are_you_sure": "Ingaba uqinisekile?", + "details_connected_to": "Iqhagamshelwe kwi", + "details_del_wb_err": "Isixa sebhalansi esinikiweyo asihambelani nebhalansi yale ngxowa. Nceda zama kwakhona.", + "details_del_wb_q": "Le ngxowa inebhalansi. Phambi kokuqhubeka, nceda qaphela ukuba awunakukwazi ukubuyisa imali ngaphandle kwe-seed phrase yale ngxowa. Ukugwema ukususa ngengozi, nceda faka ibhalansi yengxowa yakho yee-satoshis ezi-{balance}.", "details_delete": "Cima", - "details_export_backup": "Ukuthumela ngaphandle / yokugcina", - "details_no_cancel": "Hayi, rhoxisa", - "details_save": "Gcina", - "details_show_xpub": "Bonise ingxowa XPUB", + "details_delete_wallet": "Cima iNgxowa", + "details_derivation_path": "Umendo wokuphuma", + "details_display": "Bonisa kwiSkrini sasekhaya", + "details_export_backup": "Thumela ngaphandle / Gcina", + "details_export_history": "Khupha iMbali iye kwi-CSV", + "details_master_fingerprint": "I-Fingerprint engundoqo", + "details_multisig_type": "i-multisig", + "details_show_addresses": "Bonisa iidilesi", + "swipe_balance_hide": "Fihla", + "swipe_balance_show": "Bonisa", + "drag_to_reorder": "Tsala ukuhlela kwakhona", + "clear_search": "Cima ukhangelo", + "details_use_with_hardware_wallet": "Sebenzisa ne-Hardware Wallet", + "details_show_xpub": "Bonisa i-XPUB yeNgxowa", "details_title": "Ingxowa", + "enter_bip38_password": "Faka iphasiwedi ukuvula", + "wallets": "Ingxowa", "details_type": "Uhlobo", "details_yes_delete": "Ewe, yisuse", - "export_title": "ukuthunyelwa kweebhanki ", + "export_title": "Ukuthunyelwa kweNgxowa", + "import_passphrase": "Ibinzana lokugqitha", + "import_passphrase_title": "Ibinzana lokugqitha", + "import_passphrase_message": "Faka ibinzana lokugqitha ukuba ulisebenzisile", "import_do_import": "Ngeniswe", "import_error": "Ayiphumelelanga ukungenisa. Nceda, uqiniseka ukuba idata ehlinzekiweyo iyasebenza.", + "import_explanation": "Nceda faka amagama akho embewu, isitshixo sikawonke-wonke, i-WIF, okanye nayiphi na into onayo. I-BlueWallet iya kuzama ngandlela zonke ukuqikelela ifomati echanekileyo nokungenisa ingxowa yakho.", "import_imported": "Ngenisiwe", + "import_success_watchonly": "Ingxowa yakho ingeniswe ngempumelelo. ISILUMKISO: Le yingxowa ye-watch-only, AWUKWAZI ukuchitha kuyo.", + "import_search_accounts": "Khangela iiakhawunti", + "learn_more": "Funda okungakumbi", + "import_discovery_title": "Ufumaniso", + "import_discovery_subtitle": "Khetha ingxowa efunyenweyo", + "import_discovery_derivation": "Sebenzisa i-derivation path yesiqhelo", + "import_discovery_no_wallets": "Akukho ngxowa ezifumanekayo.", + "import_discovery_offline": "I-BlueWallet ngoku ikwimo yokungaqhakamshelani. Kule mo, ayikwazi ukuqinisekisa ubukho bengxowa, ngoko ke kuya kufuneka ukhethe ngokwakho echanekileyo", + "import_derivation_found": "Ifunyenwe", + "import_derivation_found_not": "Ayifunyanwanga", + "import_derivation_loading": "Iyalayisha...", + "import_derivation_subtitle": "Faka i-derivation path yesiqhelo, kwaye siza kuzama ukufumana ingxowa yakho.", + "import_derivation_title": "Umendo wokuphuma", + "import_derivation_unknown": "Ayaziwa", + "import_wrong_path": "Umendo wokuphuma ongachanekanga", "import_scan_qr": "okanye ukukhangela iQR code?", "import_success": "Iphumelele", - "import_title": "ukungenisa", + "import_title": "Ngenisa", + "list_create_a_button": "Yongeza ngoku", "list_create_a_wallet": "Yenza ingxowa", - "list_empty_txs1": "Intengiso yakho iya kubonakala apha,", + "list_create_a_wallet_text": "Yi-free, kwaye ungenza\nzininzi ngangoko ufuna.", + "list_empty_txs1": "Iintengiselwano zakho ziya kubonakala apha,", + "list_empty_txs1_lightning": "I-Lightning wallet kufuneka isetyenziselwe ii-transactions zakho zemihla ngemihla. Iintlawulo zicheap kakhulu kwaye isantya siphezulu kakhulu.", + "list_empty_txs2": "Qala ngengxowa yakho.", + "list_empty_txs2_lightning": "\nUkuze uqalise ukuyisebenzisa, cofa kwiManage Funds kwaye ugcwalise ibhalansi yakho.", + "list_long_choose": "Khetha umfanekiso", + "paste_from_clipboard": "Ncamathisela", + "import_file": "Ngenisa iFayile", + "list_long_scan": "Skena iQR Code", + "list_tryagain": "Zama kwakhona", + "no_ln_wallet_error": "Phambi kokuhlawula i-Lightning invoyisi, kufuneka uqale wongeze i-Lightning wallet.", + "looks_like_bip38": "Le ibonakala njengesitshixo sangasese esikhuselwe ngephasiwedi (BIP38).", + "manage_title": "Lawula iiNgxowa", + "no_results_found": "Akukho ziphumo zifunyenweyo.", + "please_continue_scanning": "Nceda qhubeka ukuskena.", + "select_no_bitcoin": "Akukho ngxowa ze-Bitcoin ezifumanekayo ngoku.", + "select_no_bitcoin_exp": "I-Bitcoin wallet iyafuneka ukugcwalisa ii-Lightning wallets. Nceda yenza okanye ungenise enye.", + "pull_to_refresh": "Tsala ukuhlaziya", + "warning_do_not_disclose": "Ungaze wabelane ngolwazi olungezantsi", + "scan_import": "Skena le QR code ukungenisa ingxowa yakho kwenye iapp.", + "write_down_header": "Yenza i-backup ngesandla", + "write_down": "Bhala phantsi kwaye ugcine ngokukhuselekileyo la magama. Wasebenzise ukubuyisela ingxowa yakho kamva.", + "wallet_type_this": "Uhlobo lwale ngxowa lu-{type}.", + "share_number": "Yabelana {number}", + "copy_ln_url": "Kopa kwaye ugcine ngokukhuselekileyo le URL ukubuyisela ingxowa yakho kamva.", + "copy_ln_public": "Kopa kwaye ugcine ngokukhuselekileyo olu lwazi ukubuyisela ingxowa yakho kamva.", + "add_ln_wallet_first": "Kufuneka uqale wongeze i-Lightning wallet.", + "identity_pubkey": "I-Pubkey yeSazisi", + "manage_wallets_search_placeholder": "Khangela iingxowa, iidilesi, ii-transactions kunye nee-memos", + "more_info": "Ulwazi olungaphezulu", + "details_delete_wallet_error_message": "Bekukho ingxaki yokuqinisekisa ukuba le ngxowa isuswe kwizaziso—oku kungenxa yengxaki yothungelwano okanye uqhagamshelo olungalunganga. Ukuba uyaqhubeka, usenokufumana izaziso ze-transactions ezinxulumene nale ngxowa, nasemva kokuba icinyiwe.", + "details_delete_anyway": "Cima nakuba kunjalo", "list_latest_transaction": "Utshintsho olutsha", "list_title": "Ingxowa", - "reorder_title": "Yenza kwakhona ingxowa", "select_wallet": "Khetha ingxowa", - "xpub_copiedToClipboard": "Ikopishwe kwi-clipboard", - "xpub_title": "ingxowa XPUB" + "xpub_title": "I-XPUB yeNgxowa" + }, + "total_balance_view": { + "display_in_bitcoin": "Bonisa kwi-Bitcoin", + "hide": "Fihla", + "display_in_sats": "Bonisa kwi-sats", + "display_in_fiat": "Bonisa kwi-{currency}", + "title": "iBhalansi iyonke", + "explanation": "Bonisa ibhalansi epheleleyo yazo zonke iingxowa zakho kwisikrini sokujongela." }, "multisig": { + "multisig_vault": "I-Vault ye-Multisig", + "default_label": "I-Vault ye-Multisig", + "multisig_vault_explain": "Ukhuseleko olungcono kakhulu kwiintsalela ezinkulu", + "provide_signature": "Bonelela ngotyikityo", + "provide_signature_details": "Sebenzisa isixhobo nengxowa yakho apho isitshixo sikhoyo ukutyikitya le ntengiselwano", + "provide_signature_details_bluewallet": "Kwi-BlueWallet, yiya kwimenyu yesikrini se-Send kwaye ukhethe", + "provide_signature_next_steps": "Skena okanye uNgenise iTransaction etyikityiweyo", + "provide_signature_next_steps_details": "Xa ingxowa yakho ityikitye ngempumelelo i-transaction, skena iQR code enikiweyo okanye ungenise ifayile ekhutshelweyo, emva koko uphengulule zonke iinkcukacha ze-transaction phambi kokuyisasaza.", + "vault_key": "Isitshixo se-Vault {number}", + "required_keys_out_of_total": "Izitshixo ezifunekayo kwiyonke", + "fee": "Intlawulo: {number}", + "fee_btc": "{number} BTC", + "share": "Yabelana...", + "view": "Bonisa", + "shared_key_detected": "Umtyikityi ohambisiweyo", + "shared_key_detected_question": "I-co-signer yabelwane nawe, ufuna ukuyingenisa?", + "manage_keys": "Lawula iZitshixo", + "how_many_signatures_can_bluewallet_make": "zingaphi iisignatures i-BlueWallet enokuzenza", + "signatures_required_to_spend": "Iisignatures ezifunekayo {number}", + "signatures_we_can_make": "ingenza {number}", + "scan_or_import_file": "Skena okanye ungenise ifayile", + "export_coordination_setup": "Khupha uMisiselo lokuLungelelanisa", + "cosign_this_transaction": "Tyikitya kunye le ntengiselwano?", + "lets_start": "Masiqalise", + "native_segwit_title": "Inkqubo eyona ilungileyo", + "wrapped_segwit_title": "Ukuhambisana okugcono", + "legacy_title": "Legacy", + "co_sign_transaction": "Sayina i-transaction", + "what_is_vault": "I-vault yi", + "what_is_vault_numberOfWallets": " {m}-kwi-{n} multisig ", + "what_is_vault_wallet": "ingxowa.", + "vault_advanced_customize": "Izicwangciso ze-Vault", + "needs": "Iyafuna", + "what_is_vault_description_number_of_vault_keys": " {m} izitshixo ze-vault ", + "what_is_vault_description_to_spend": "ukuchitha kunye nesithathu onokukwazi\nukusisebenzisa njenge-backup.", + "what_is_vault_description_to_spend_other": "ukuchitha.", + "quorum": "I-quorum ye-{m} kwi-{n}", + "quorum_header": "I-Quorum", + "of": "kwi", + "wallet_type": "Uhlobo lweNgxowa", + "invalid_mnemonics": "Eli binzana lemnemoniki alibonakali lisebenza.", + "invalid_cosigner": "Idatha ye-co-signer engasebenziyo", + "not_a_multisignature_xpub": "Le ayisiyi-XPUB esuka kwingxowa ye-multisignature!", + "invalid_cosigner_format": "I-Co-signer engalunganga: Le ayisiyo i-co-signer ye-{format} format.", + "create_new_key": "Yenza Entsha", + "scan_or_open_file": "Skena okanye uvule ifayile", + "i_have_mnemonics": "Ndine-seed yesi sitshixo.", + "type_your_mnemonics": "Faka i-seed ukungenisa isitshixo se-Vault esikhoyo.", + "this_is_cosigners_xpub": "Le yi-XPUB ye-co-signer—ilungele ukungeniswa kwenye ingxowa. Kukhuselekile ukuyabelana.", + "this_is_cosigners_xpub_airdrop": "Ukuba uyabelana nge-AirDrop abamkeli kufuneka babe kwisikrini sokulungelelanisa.", + "wallet_key_created": "Isitshixo seVolthi yakho senziwe. Thabatha umzuzu ukugcina ngokukhuselekileyo imbewu yakho yokukhumbula.", + "are_you_sure_seed_will_be_lost": "Uqinisekile? Imbewu yakho yokukhumbula iya kulahleka ukuba awunalo iqweqwe.", + "forget_this_seed": "Libala le mbewu kwaye usebenzise i-XPUB endaweni yayo.", + "view_edit_cosigners": "Bonisa/Hlela ii-Co-signers", + "this_cosigner_is_already_imported": "Lo mtyikityi ekunye sele engenisiwe.", + "export_signed_psbt": "Khupha i-PSBT etyikityiweyo", + "input_fp": "Faka i-fingerprint", + "input_fp_explain": "Tsiba ukusebenzisa eyokuqala (00000000)", + "input_path": "Faka iDerivation Path", + "input_path_explain": "Tsiba ukusebenzisa eyokuqala ({default})", + "ms_help": "Uncedo", + "ms_help_title": "Indlela ze-Multisig Vaults ezisebenza ngayo: Iingcebiso neeNcebo", + "ms_help_text": "Ingxowa enezitshixo ezininzi, yokukhulisa ukhuseleko okanye ukulondoloza okwabelwene", + "ms_help_title1": "Izixhobo ezininzi ziyacetyiswa.", + "ms_help_1": "I-Vault iya kusebenza kunye nezinye iiapps ze-BlueWallet kunye neengxowa ezisebenzisana ne-PSBT, ezinje nge-Electrum, Specter, Coldcard, Cobo Vault, njl njl.", + "ms_help_title2": "Ukuhlela iZitshixo", + "ms_help_2": "Unganokwenza zonke izitshixo ze-Vault kwesi sixhobo kwaye uzisuse okanye uzihlele kamva. Ukuba zonke izitshixo zikwisixhobo esinye kunokhuseleko olufanayo nengxowa ye-Bitcoin eqhelekileyo.", + "ms_help_title3": "Ii-Backup ze-Vault", + "ms_help_3": "Kwiindlela zengxowa, uya kufumana i-backup ye-Vault yakho kunye ne-backup ye-watch-only. Le backup ifana ne-map kwingxowa yakho. Ibalulekile ekubuyiseleni ingxowa xa ulahlekelwa ngenye yee-seeds zakho.", + "ms_help_title4": "Ukungenisa ii-Vaults", + "ms_help_4": "Ukungenisa i-multisig, sebenzisa ifayile yakho ye-backup kunye ne-feature ye-Import. Ukuba unee-seeds kunye nee-XPUBs kuphela, ungasebenzisa iqhosha le-Import elinye xa wenza izitshixo ze-Vault.", + "ms_help_5": "Ngokwesiqhelo, i-BlueWallet iya kuvelisa i-Vault ye-2-of-3. Ukwenza i-quorum eyahlukileyo okanye utshintshe uhlobo lwedilesi, vula i-Advanced Mode kwiZicwangciso.", "confirm": "Qiniseka", "header": "Thumela", - "share": "yabelana", "create": "Yakha", - "ms_help_title5": "Enable advanced mode" + "ms_help_title5": "Vula imo ephambili" + }, + "is_it_my_address": { + "title": "Ingaba yidilesi yam?", + "owns": "I-{label} inayo i-{address}", + "enter_address": "Faka idilesi", + "check_address": "Jonga idilesi", + "no_wallet_owns_address": "Akukho ngxowa ifumanekayo enedilesi enikiweyo.", + "view_qrcode": "Bonisa iQR Code" + }, + "autofill_word": { + "title": "Igama lokugqibela le-Seed", + "enter": "Faka i-mnemonic phrase yakho engaphelelanga", + "generate_word": "Velisa igama lokugqibela", + "error": "Igalelo asilobinzana lembewu elinamagama ali-11 okanye angama-23. Nceda zama kwakhona." + }, + "cc": { + "change": "Tshintsha", + "coins_selected": "Iicoins ezikhethiweyo ({number})", + "selected_summ": "{value} ekhethiweyo", + "empty": "Le ngxowa ayinazo iicoins ngoku.", + "freeze": "Khenkceza", + "freezeLabel": "Khenkceza", + "freezeLabel_un": "Khulula", + "header": "Ulawulo lweeNgqekembe", + "use_coin": "Sebenzisa iCoin", + "use_coins": "Sebenzisa iiCoins", + "tip": "Lo mphandle ukuvumela ubone, ufake ilebhile, u-freeze okanye ukhethe iicoins zolawulo olungcono lwengxowa. Unganokukhetha iicoins ezininzi ngokucofa kwiicircles ezinemibala.", + "sort_asc": "Inyukayo", + "sort_desc": "Ihla", + "sort_height": "Ubude", + "sort_value": "Ixabiso", + "sort_status": "Imeko", + "sort_by": "Hlela nge", + "sort_label": "Igama" + }, + "units": { + "BTC": "BTC", + "MAX": "Phezulu", + "sat_vbyte": "sat/vByte", + "sats": "sats" }, "addresses": { + "copy_private_key": "Khuphela isitshixo sangasese", + "sensitive_private_key": "Isilumkiso: izitshixo zangasese zibuthathaka kakhulu. Qhubeka?", + "sign_title": "Sayina/Qinisekisa uMyalezo", + "sign_help": "Apha unokuyila okanye uqinisekise utyikityo lwecryptography olusekelwe kwidilesi ye-Bitcoin.", + "sign_sign": "Sayina", + "sign_verify": "Qinisekisa", + "sign_signature_correct": "Uqinisekiso luphumelele!", + "sign_signature_incorrect": "Uqinisekiso aluphumelelanga!", + "sign_placeholder_message": "Umyalezo", + "sign_placeholder_signature": "Utyikityo", + "addresses_title": "Iidilesi", + "type_change": "Tshintsha", + "type_used": "Esetyenzisiweyo", "sign_placeholder_address": "idilesi", "type_receive": "Fumana", - "transactions": "ngeniswa" + "transactions": "Iintengiselwano" + }, + "lnurl_auth": { + "register_question_part_1": "Ungathanda ukubhalisa iakhawunti kwi", + "register_question_part_2": "usebenzisa i-Lightning wallet yakho?", + "register_answer": "Ubhalise ngempumelelo iakhawunti kwi-{hostname}!", + "login_question_part_1": "Ungathanda ukungena kwi", + "login_question_part_2": "usebenzisa i-Lightning wallet yakho?", + "login_answer": "Ungene ngempumelelo kwi-{hostname}!", + "link_question_part_1": "Ungathanda ukunxulumanisa iakhawunti yakho kwi", + "link_question_part_2": "kwi-Lightning wallet yakho?", + "link_answer": "I-Lightning wallet yakho inxulumaniswe ngempumelelo kwiakhawunti yakho kwi-{hostname}!", + "auth_question_part_1": "Ungathanda ukuqinisekiswa kwi", + "auth_question_part_2": "usebenzisa i-Lightning wallet yakho?", + "auth_answer": "Uqinisekiswe ngempumelelo kwi-{hostname}!", + "could_not_auth": "Asikwazanga kukuqinisekisa kwi-{hostname}.", + "authenticate": "Qinisekisa" + }, + "bip47": { + "payment_code": "Ikhowudi yeNtlawulo", + "contacts": "Iikontakthi", + "bip47_explain": "Ikhowudi enokusetyenziswa kwakhona enokwabelwana ngayo", + "bip47_explain_subtitle": "BIP47", + "purpose": "Ikhowudi enokusetyenziswa kwakhona enokwabelwana ngayo (BIP47)", + "pay_this_contact": "Hlawula le kontakthi", + "rename_contact": "Thiya kwakhona ikontakthi", + "copy_payment_code": "Kopa iPayment Code", + "hide_contact": "Fihla ikontakthi", + "rename": "Thiya kwakhona", + "provide_name": "Bonelela ngegama elitsha lale kontakthi", + "add_contact": "Yongeza iKontakthi", + "provide_payment_code": "Bonelela ngePayment Code", + "invalid_pc": "Ikhowudi yeNtlawulo engasebenziyo", + "notification_tx_unconfirmed": "Intengiselwano yesaziso ayikaqinisekiswa, nceda linda", + "failed_create_notif_tx": "Akuphumelelekanga ukuyila intengiselwano ye-on-chain", + "onchain_tx_needed": "Kufuneka intengiselwano ye-on-chain", + "notif_tx_sent": "Intengiselwano yesaziso ithunyelwe. Nceda linda ukuba iqinisekiswe", + "notif_tx": "Intengiselwano yesaziso", + "not_found": "I-Payment code ayifumaneki" } } diff --git a/loc/zh_cn.json b/loc/zh_cn.json index dd5b31841d1..7f1aef434e5 100644 --- a/loc/zh_cn.json +++ b/loc/zh_cn.json @@ -1,41 +1,51 @@ { "_": { - "bad_password": "密码无效,请重试。", + "bad_password": "密码错误,请重试。", "cancel": "取消", "continue": "继续", "clipboard": "剪贴板", + "copied": "已复制!", + "discard_changes": "放弃更改?", + "discard_changes_explain": "您有未保存的更改。您确定要放弃这些更改并离开此页面吗?", "enter_password": "输入密码", - "never": "永不", - "disabled": "禁用", - "of": "{total}其中之{number}", - "ok": "好的", - "storage_is_encrypted": "你的储存资料已经被加密, 请输入密码解密。", + "never": "暂无", + "of": "{number}/{total}", + "ok": "确定", + "enter_url": "输入网址", + "storage_is_encrypted": "您的存储已加密,需要密码才能解密。", "yes": "是", "no": "不", - "save": "保存", - "seed": "种子", + "save": "保存……", + "seed": "助记词", "success": "成功", "wallet_key": "钱包密钥", - "invalid_animated_qr_code_fragment": "无效的动态二维码,请重试。", - "file_saved": "文件已保存", - "downloads_folder": "下载文件夹" - }, - "alert": { - "default": "提示" + "close": "关闭", + "change_input_currency": "切换输入货币", + "refresh": "刷新", + "pick_image": "从文件库中选择", + "pick_file": "选择文件", + "enter_amount": "输入金额", + "qr_custom_input_button": "连续按 10 次以进入自定义输入", + "unlock": "解锁", + "port": "端口", + "ssl_port": "SSL 端口", + "suggested": "推荐" }, "azteco": { - "codeIs": "您的兑换券代码为", - "errorBeforeRefeem": "在兑赎之前,你需先新增一个比特币钱包。", - "errorSomething": "出了些问题。 此兑换券仍然有效吗?", - "redeem": "兑赎到钱包", - "redeemButton": "兑赎", + "codeIs": "您的券码是", + "errorBeforeRefeem": "在领取之前,您必须先添加一个比特币钱包。", + "errorSomething": "出现错误。这个兑换码仍然有效吗?", + "redeem": "领取到钱包", + "redeemButton": "领取", "success": "成功", - "title": "兑赎Azte.co兑换券" + "successMessage": "券码领取成功!资金应很快到账您的比特币钱包。", + "title": "领取 Azte.co 券码" }, "entropy": { "save": "保存", - "title": "随机熵值", - "undo": "还原" + "title": "熵", + "undo": "撤销", + "amountOfEntropy": "{bits}/{limit} 位" }, "errors": { "broadcast": "广播失败", @@ -43,433 +53,652 @@ "network": "网络错误" }, "lnd": { - "active": "激活", - "inactive": "无效", - "errorInvoiceExpired": "账单已过期", + "errorInvoiceExpired": "发票已过期。", "expired": "已过期", + "expiresIn": "{time} 分钟后过期", "payButton": "支付", - "placeholder": "账单", + "payment": "付款", + "placeholder": "发票或地址", "potentialFee": "潜在费用:{fee}", "refill": "充值", - "refill_create": "为了继续进行,请创建一个要充值的比特币钱包。", - "refill_external": "用外部钱包充值", - "refill_lnd_balance": "给闪电钱包充值余额", - "sameWalletAsInvoiceError": "你不能用创建账单的钱包去支付此账单", + "refill_create": "为了继续操作,请先创建一个比特币钱包以进行充值。", + "refill_external": "使用外部钱包充值", + "refill_lnd_balance": "充值闪电网络钱包余额", + "sameWalletAsInvoiceError": "无法使用创建发票的同一个钱包进行支付。", "title": "管理资金" }, "lndViewInvoice": { - "additional_info": "附加信息", - "for": "为了:", - "lightning_invoice": "闪电账单", - "open_direct_channel": "使用此节点来开啟直接频道:", + "additional_info": "其他信息", + "for": "收款方:", + "lightning_invoice": "闪电网络发票", + "please_pay_between_and": "请支付介于 {min} 与 {max} 之间的金额", "please_pay": "请支付", "preimage": "原像", "sats": "聪", - "wasnt_paid_and_expired": "此账单尚未支付,已过期。" + "date_time": "日期与时间", + "wasnt_paid_and_expired": "该发票未支付,已过期。" }, "plausibledeniability": { "create_fake_storage": "创建加密存储", - "create_password": "创建密码", - "create_password_explanation": "虚假存储空间密码不能和主存储空间密码相同", - "help": "在某些情况下,你不得不暴露密码。为了让你的比特币更加安全,BlueWallet可以创建另一个用不同密码的加密空间,在压力之下,你可能暴露这个密码。如果进入 BlueWallet,我们会解锁一个新的虚假存储空间。对第三方来说看上去是合理的,但会偷偷的帮你保证主钱包的安全,币也就安全了。", - "help2": "新的储存空间具备完整的功能,你可以存入少量的金额在里面。", - "password_should_not_match": "此密码已被使用,请用另一个密码。", - "passwords_do_not_match": "密码不匹配,请再试一遍。", - "retype_password": "重输密码", - "success": "成功", - "title": "合理推诿" + "create_password_explanation": "虚拟存储的密码不应与主存储的密码相同。", + "help": "在某些情况下,您可能被迫暴露密码。为了保护您的资金安全,BlueWallet 可以创建另一个使用不同密码的加密存储。在胁迫下,您可以向第三方提供这个密码。输入到 BlueWallet 后,它将解锁一个新的“虚假”存储。对第三方来说,这看起来像是正常存储,但实际上会秘密保护您主存储中的资金安全。", + "help2": "新的存储将完全可用,您可以在其中存入少量资金,使其看起来更可信。", + "password_should_not_match": "该密码已被使用,请尝试其他密码。", + "title": "可合理否认" }, "pleasebackup": { - "ask": "您是否保存了钱包的备份短语? 如果您丢失了此设备,则需要此备份短语来访问您的资金。没有此备份短语,您的资金将永久丢失。", - "ask_no": "不,我还没有", - "ask_yes": "是的,我完成了", - "text_lnd": "请保存此钱包备份。这个备份可以在装置遗失时用来恢复此钱包。" + "ask": "您是否已保存钱包的备份助记词?如果设备丢失,该助记词是访问资金所必需的。没有备份助记词,您的资金将永久丢失。", + "ask_no": "不,我还没有。", + "ask_yes": "是的,我已有。", + "ok": "好的,我把它写下来了。", + "ok_lnd": "好的,我已经保存好了。", + "text": "请花一点时间将此助记词抄写在纸上。\n它是您的备份,可用于恢复钱包。", + "text_lnd": "请保存此钱包备份。它可用于在丢失时恢复钱包。", + "title": "您的钱包已创建。" }, "receive": { "details_create": "创建", "details_label": "描述", - "details_setAmount": "收款金额", - "details_share": "分享", - "header": "收款" + "details_setAmount": "按金额接收", + "details_share": "共享……", + "address_not_found": " 无法生成接收地址。", + "header": "接收", + "reset": "重置", + "maxSats": "最大金额为 {max} 聪", + "maxSatsFull": "最大金额为 {max} 聪,或 {currency}", + "minSats": "最小金额为 {min} 聪", + "minSatsFull": "最小金额为 {min} 聪,或 {currency}", + "qrcode_for_the_address": "该地址的二维码", + "bip47_explanation": "支付码是一种通用地址,可防止泄露您的钱包地址。但并非所有服务都支持支付码。" }, "send": { + "provided_address_is_invoice": "该地址似乎对应一个闪电网络发票。请前往您的闪电网络钱包以支付此发票。", "broadcastButton": "广播", "broadcastError": "错误", - "broadcastNone": "输入交易的十六进制码", - "broadcastPending": "待办", + "broadcastNone": "输入交易的十六进制数据", + "broadcastPending": "待处理", "broadcastSuccess": "成功", "confirm_header": "确认", - "confirm_sendNow": "现在发送", + "confirm_sendNow": "立即发送", "create_amount": "金额", "create_broadcast": "广播", - "create_copy": "稍后复制并广播", + "create_copy": "复制并稍后广播", "create_details": "详情", - "create_fee": "手续费", - "create_memo": "消息", - "create_this_is_hex": "这个是您的交易的十六进制码,已签署并准备好广播到网络。", + "create_fee": "矿工费", + "create_memo": "备注", + "create_satoshi_per_vbyte": "聪/字节", + "create_this_is_hex": "这是您的交易十六进制数据——已签名,可随时广播到网络。", "create_to": "到", "create_tx_size": "交易大小", - "create_verify": "在coinb.in上验证", - "details_add_rec_add": "添加收件人", - "details_add_rec_rem": "删除收件人", + "create_verify": "在 coinb.in 上验证", + "details_insert_contact": "添加联系人", + "details_add_rec_add": "添加收款人", + "details_add_rec_rem": "移除收款人", + "details_add_recc_rem_all_alert_description": "您确定要移除所有收款人吗?", + "details_add_rec_rem_all": "移除所有收款人", + "details_recipients_title": "收款人", + "details_recipient_title": "第 #{number} 位收款人 / 总共 #{total} 位", + "please_complete_recipient_title": "收款人信息不完整", + "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_import": "汇入交易", - "details_amount_field_is_not_valid": "金额无效", - "details_amount_field_is_less_than_minimum_amount_sat": "指定的金额太小,请输入大于500聪 的金额。", - "details_create": "创建", - "details_error_decode": "错误:无法解密比特币地址", - "details_fee_field_is_not_valid": "费用无效", + "details_adv_full_sure": "您确定要在此交易中使用钱包的全部余额吗?", + "details_adv_full_sure_frozen": "您确定要使用钱包中的全部余额进行此交易吗?请注意,冻结的资金不包括在内。", + "details_adv_import": "导入交易", + "details_adv_import_qr": "导入交易 (二维码)", + "details_amount_field_is_not_valid": "金额无效。", + "details_amount_field_is_less_than_minimum_amount_sat": "指定金额过小。请输入大于 500 聪的金额。", + "details_create": "创建发票", + "details_error_decode": "无法解析比特币地址", + "details_fee_field_is_not_valid": "矿工费无效。", + "details_frozen": "{amount} BTC 已被冻结。", "details_next": "下一步", - "details_no_signed_tx": "所选文件不包含可以导入的交易。", + "details_no_signed_tx": "所选文件不包含可导入的交易。", "details_note_placeholder": "给自己的备注", "details_scan": "扫描", - "details_total_exceeds_balance": "发送金额超过可用馀额", - "details_unrecognized_file_format": "不能辨认的档案格式", - "details_wallet_before_tx": "在创建一笔交易之前,您必须首先添加一个比特币钱包。", + "details_scan_hint": "双击以扫描或导入收款人地址", + "details_scan_error": "扫描错误", + "details_total_exceeds_balance": "发送金额超过可用余额。", + "details_total_exceeds_balance_frozen": "发送金额超过可用余额。请注意,冻结的比特币不包括在内。", + "details_unrecognized_file_format": "文件格式无法识别", + "details_wallet_before_tx": "在创建交易之前,您必须先添加一个比特币钱包。", "dynamic_init": "初始化中", "dynamic_next": "下一步", "dynamic_prev": "上一步", "dynamic_start": "开始", "dynamic_stop": "停止", - "fee_10m": "10分钟", - "fee_1d": "1日", - "fee_3h": "3小时", - "fee_custom": "自选", - "fee_fast": "快速", - "fee_medium": "中等", + "fee_10m": "10 分钟", + "fee_1d": "1 天", + "fee_3h": "3 个小时", + "fee_custom": "自定义", + "insert_custom_fee": "添加矿工费", + "fee_fast": "快", + "fee_medium": "中", + "fee_replace_minvb": "您设置的总矿工费率应高于 {min} 聪/字节。", + "fee_satvbyte": "以“聪/字节”为单位", "fee_slow": "慢", "header": "发送", "input_clear": "清除", "input_done": "完成", "input_paste": "粘贴", "input_total": "总计:", - "permission_camera_message": "我们需要您的授权许可才能使用您的相机。", - "psbt_sign": "签署一笔交易", + "permission_camera_message": "我们需要使用权限才能使用您的摄像头。", + "psbt_sign": "签署交易", + "invalid_psbt": "提供的部分签名比特币交易(PSBT)无效。", "open_settings": "打开设置", - "permission_storage_later": "等会问我", - "permission_storage_message": "BlueWallet需要您的授权许可才能访问您的存储空间以保存此文件。", + "permission_storage_denied_message": "BlueWallet 无法保存此文件。请打开设备设置并启用存储权限。", "permission_storage_title": "存储访问权限", "psbt_clipboard": "复制到剪贴板", - "psbt_this_is_psbt": "这是已部分签署的比特币交易(PSBT),请用您的硬体钱包完成签署。", + "psbt_this_is_psbt": "这是一个部分签名的比特币交易(PSBT)。请使用您的硬件钱包以完成签名。", "psbt_tx_export": "导出到文件", - "no_tx_signing_in_progress": "没有交易签署正在进行中。", - "psbt_tx_open": "打开已签署的交易", - "psbt_tx_scan": "扫描已签署的交易", + "no_tx_signing_in_progress": "当前没有正在进行的交易签名。", + "outdated_rate": "矿工费率最后更新于:{date}", + "psbt_tx_open": "打开已签名的交易", + "psbt_tx_scan": "扫描已签名的交易", + "qr_error_no_qrcode": "我们无法在所选图像中找到有效的二维码。请确保图像仅包含二维码,不含文本或按钮等其他内容。", + "reset_amount": "重置金额", + "reset_amount_confirm": "您是否要重置金额?", "success_done": "完成", - "txSaved": "交易文件({filePath})已保存在“下载”文件夹中。", - "problem_with_psbt": "PSBT的问题" + "txSaved": "交易文件({filePath})已保存。", + "file_saved_at_path": "文件({filePath})已保存。", + "cant_send_to_silentpayment_adress": "此钱包无法向 SilentPayment 地址发送交易。", + "cant_send_to_bip47": "此钱包无法向 BIP47 支付码发送交易。", + "cant_find_bip47_notification": "请先将此支付码添加到联系人。", + "problem_with_psbt": "PSBT(部分签名的比特币交易)出现问题" }, "settings": { "about": "关于", - "about_awesome": "从很棒的创立", - "about_backup": "始终备份您的密钥!", - "about_free": "BlueWallet是一个免费的开源项目,由比特币用户制作。", + "about_awesome": "由出色的技术打造", + "about_backup": "请务必备份您的密钥!", + "about_free": "BlueWallet 是一个自由且开源的项目,由比特币用户打造。", "about_license": "麻省理工学院许可证", - "about_release_notes": "发布说明", - "about_review": "给我们评论", + "about_release_notes": "更新日志", + "about_review": "给我们写个评价", + "performance_score": "性能评分:{num}", + "run_performance_test": "测试性能", "about_selftest": "运行自检", - "about_selftest_ok": "所有内部测试均已成功通过,钱包运作良好。", - "about_sm_github": "Github", - "about_sm_discord": "Discord 服务器", + "block_explorer_invalid_custom_url": "提供的网址 无效。请输入以 http:// 或 https:// 开头的有效网址。", + "about_selftest_electrum_disabled": "在 Electrum 离线模式下无法进行自我测试。请关闭离线模式后重试。", + "about_selftest_ok": "所有内部测试均已通过,钱包运行正常。", + "about_sm_github": "GitHub", "about_sm_telegram": "电报频道", - "about_sm_twitter": "在推特上关注我们", - "advanced_options": "进阶选项", - "biometrics": "生物识别", - "biom_10times": "您已尝试输入密码10次。 您想重置存储空间吗? 这将删除所有钱包并解密您的存储。", + "privacy_temporary_screenshots": "允许屏幕录制", + "privacy_temporary_screenshots_instructions": "屏幕录制保护将被暂时关闭,从而允许截图和屏幕录制。关闭并重新打开 BlueWallet 后,保护功能将自动恢复。", + "biometrics": "生物识别认证", + "biometrics_no_longer_available": "您的设备设置已更改,与应用程序中选择的安全设置不再匹配。请重新启用生物识别认证或密码,然后重启应用以应用这些更改。", + "biom_10times": "您已尝试输入密码 10 次。是否要重置存储?此操作将移除所有钱包并解密存储。", "biom_conf_identity": "请确认您的身份。", - "biom_no_passcode": "您的设备没有密码。 为了继续进行,请在“设置”应用中配置密码。", - "biom_remove_decrypt": "您的所有钱包将被删除,您的存储空间将被解密。您确定要继续吗?", - "currency": "货币", - "default_desc": "停用后,BlueWallet将在启动时立即打开选定的钱包。", - "default_info": "默认信息", + "biom_no_passcode": "您的设备未启用密码或生物识别认证。要继续操作,请在“设置”应用中配置密码或生物识别认证。", + "biom_remove_decrypt": "您的所有钱包将被移除,存储将被解密。您确定要继续吗?", + "currency": "币种", + "currency_source": "汇率来源于", + "currency_fetch_error": "获取所选货币的汇率时出错。", "default_title": "启动时", - "default_wallets": "查看所有钱包", + "donate": "打赏", + "donate_description": "请帮助我们保持 BlueWallet 独立!", "electrum_connected": "已连接", "electrum_connected_not": "未连接", - "electrum_error_connect": "无法连接到提供的Electrum服务器", - "lndhub_uri": "举例:{example}", - "electrum_host": "举例:{example}", + "electrum_error_connect": "无法连接到提供的 Electrum 服务器。", + "electrum_error_connect_tor": "无法连接到提供的 Electrum 服务器。请确保 Orbot 应用程序已连接并重试。", + "lndhub_uri": "例如:{example}", + "electrum_host": "例如:{example}", "electrum_offline_mode": "离线模式", - "electrum_saved": "您的更变已成功保存。要使更变生效,可能需要重新启动BlueWallet。", - "set_electrum_server_as_default": "将{server}设置为默认的Electrum服务器?", - "set_lndhub_as_default": "将{url}设置为默认的LNDHub服务器?", - "electrum_settings_server": "Electrum Server", + "electrum_offline_description": "启用后,您的比特币钱包将不会尝试获取余额或交易记录。", + "electrum_port": "端口,通常为 {example}", + "use_ssl": "使用 SSL", + "electrum_saved": "您的修改已成功保存。可能需要重启 BlueWallet 才能生效。", + "set_electrum_server_as_default": "是否将 {server} 设为默认 Electrum 服务器?", + "set_lndhub_as_default": "是否将 {url} 设置为默认 LNDhub(闪电网络守护进程集线器)", + "electrum_settings_server": "Electrum 服务器", "electrum_status": "状态", - "electrum_clear_alert_title": "清除历史记录?", - "electrum_clear_alert_message": "您是否要清除electrum 服务器的历史记录?", - "electrum_clear_alert_cancel": "取消", - "electrum_clear_alert_ok": "好的", - "electrum_select": "选择", + "electrum_preferred_server": "首选服务器", + "electrum_preferred_server_description": "请输入您希望钱包用于所有比特币操作的服务器。设置后,钱包将仅使用该服务器来查询余额、发送交易并获取网络数据。在设置前,请确保您信任该服务器。", + "electrum_unable_to_connect": "无法连接到 {server}。", + "electrum_history": "历史记录", + "electrum_reset_to_default": "这将允许 BlueWallet 从服务器列表中随机选择一个服务器。", "electrum_reset": "重置为默认", - "electrum_unable_to_connect": "无法连接至 {server}。", - "electrum_history": "服务器历史记录", - "electrum_reset_to_default": "您确定要重置您的Electrum设置为默认值吗?", - "electrum_clear": "清除", + "electrum_reset_to_default_and_clear_history": "重置为默认并清除历史记录", "encrypt_decrypt": "解密存储", - "encrypt_decrypt_q": "您确定要解密存储吗? 这样一来,无需密码即可访问您的钱包。", - "encrypt_enc_and_pass": "加密和密码保护的", + "encrypt_decrypt_q": "您确定要解密存储吗?这将允许在无需密码的情况下访问您的钱包。", + "encrypt_enc_and_pass": "密码保护", + "encrypt_storage_explanation_headline": "启用存储加密", + "encrypt_storage_explanation_description_line1": "启用“存储加密”可通过保护数据在设备上的存储方式,为应用增加额外的安全防护层。这使得未经许可的人员更难访问您的信息。", + "encrypt_storage_explanation_description_line2": "但是,需要注意的是,此加密仅保护设备钥匙链中存储的钱包的访问权限,并不会为钱包本身设置密码或提供额外保护。", + "i_understand": "我明白", + "block_explorer": "区块链浏览器", + "block_explorer_preferred": "使用首选区块链浏览器", + "block_explorer_error_saving_custom": "保存首选区块链浏览器时出错", "encrypt_title": "安全", "encrypt_tstorage": "存储", - "encrypt_use": "使用{type}", - "general": "一般的", - "general_adv_mode": "进阶模式", - "general_adv_mode_e": "启用后,您将看到进阶选项,例如不同的钱包类型、指定连接LNDHub进程的能力以及在创建钱包期间的自定义熵。", + "encrypt_use": "使用 {type}", + "set_as_preferred": "设为首选", + "set_as_preferred_electrum": "将 {host}:{port} 设置为首选服务器将禁止随机连接到推荐的服务器。", + "encrypted_feature_disabled": "启用加密存储时无法使用此功能。 ", + "encrypt_use_expl": "在进行交易、解锁、导出或删除钱包之前,{type} 将用于确认您的身份。", + "biometrics_fail": "如果未启用 {type} 或解锁失败,您可以使用设备密码作为替代。", + "general": "通用", "general_continuity": "连续性", - "general_continuity_e": "启用后,您将能够查看选定的钱包、交易及使用您其他Apple iCloud连接的设备。", - "groundcontrol_explanation": "GroundControl是一款免费的开源推送通知服务器,用于比特币钱包。 您可以安装自己的GroundControl服务器并将其URL放在此处,而不依赖BlueWallet的基础结构。 保留空白以使用GroundControl的默认服务器。", + "general_continuity_e": "启用后,您可以通过其他已连接 Apple iCloud 的设备查看所选钱包及交易记录。", + "groundcontrol_explanation": "GroundControl 是一个自由并开源的比特币钱包推送通知服务器。您可以自行搭建 GroundControl 服务器,并在此输入其网址,以无需依赖 BlueWallet 的基础设施。留空则使用 GroundControl 的默认服务器。", "header": "设置", "language": "语言", - "lightning_saved": "您的更变已成功保存。", + "last_updated": "最后更新", + "language_isRTL": "需要重启 BlueWallet 才能使语言设置生效。", + "license": "许可证", + "lightning_error_lndhub_uri": "无效的闪电网络守护进程集线器 (LNDhub) 统一资源标识符 (URI) ", + "lightning_error_lndhub_uri_tor": "无效的闪电网络守护进程集线器(LNDhub)统一资源标识符(URI)。请确保 Orbot 应用程序已连接,然后重试。", + "lightning_saved": "您的修改已成功保存。", "lightning_settings": "闪电网络设置", + "lightning_settings_explain": "要连接到您自己的闪电网络守护进程,请安装闪电网络守护进程集线器(LNDhub),并在设置中填写其网址。请注意,只有在保存更改后创建的钱包才会连接到指定的 LNDhub。", + "lndhub_github": "GitHub 存储库", "network": "网络", "network_broadcast": "广播交易", - "network_electrum": "Electrum服务器", - "notifications": "通知事项", - "open_link_in_explorer": "在资源管理器中打开链接", + "network_electrum": "Electrum 服务器", + "electrum_suggested_description": "当未设置首选服务器时,将随机选择一个推荐的服务器使用。", + "not_a_valid_uri": "无效的统一资源标识符(URI)", + "notifications": "通知", + "open_link_in_explorer": "在区块链浏览器中打开链接", "password": "密码", - "password_explain": "创建密码,您将用此密码来解密储存空间", - "passwords_do_not_match": "两个密码不同", - "plausible_deniability": "合理推诿", + "password_explain": "请输入用于解锁存储的密码。", + "plausible_deniability": "可合理否认", "privacy": "隐私", - "privacy_read_clipboard": "阅读剪贴板", + "privacy_read_clipboard": "读取剪贴板", "privacy_system_settings": "系统设置", - "privacy_quickactions": "钱包捷径", - "privacy_quickactions_explanation": "触碰并按住主屏幕上的BlueWallet应用图标,以快速查看您的钱包余额。", - "privacy_clipboard_explanation": "如果在剪贴板中找到地址或发票,请提供捷径。", - "push_notifications": "推送通知", - "retype_password": "再次输入密码", - "selfTest": "自行测试", + "privacy_quickactions": "钱包快捷方式", + "privacy_quickactions_explanation": "长按 BlueWallet 应用程序图标即可快速查看钱包余额。", + "privacy_clipboard_explanation": "如果在剪贴板中发现地址或发票,则提供快捷方式。", + "privacy_do_not_track": "禁用数据分析功能", + "privacy_do_not_track_explanation": "性能和可靠性信息将不会被提交用于数据分析。", + "rate": "汇率", + "push_notifications_explanation": "启用通知后,您的设备令牌将被发送到服务器,同时还会发送启用通知后所有钱包及交易的地址和交易 ID。设备令牌用于发送通知,而钱包信息则用于通知您比特币到账或交易确认情况。\n\n仅会传输您启用通知之后的信息——启用之前的信息不会被收集。\n\n禁用通知将从服务器中删除所有这些信息。此外,从应用中删除钱包也会删除该钱包在服务器上的相关信息。", + "selfTest": "自检", "save": "保存", "saved": "已保存", + "success_transaction_broadcasted": "您的交易已成功广播!", "total_balance": "总余额", - "total_balance_explanation": "在主屏幕小工具上显示您所有钱包的总余额。", - "widgets": "小工具", + "total_balance_explanation": "在主屏幕小组件上显示您所有钱包的总余额。", + "widgets": "小组件", "tools": "工具" }, "notifications": { - "would_you_like_to_receive_notifications": "您想在收到款项时得到通知吗?", - "no_and_dont_ask": "不,不要再问我", - "ask_me_later": "待会问我" + "would_you_like_to_receive_notifications": "您希望在收到付款时接收通知吗?", + "notifications_subtitle": "收到的付款以及交易确认", + "no_and_dont_ask": "不,不要再提醒我了。", + "permission_denied_message": "您已拒绝允许发送通知。如果您希望接收通知,请在设备设置中启用。" }, "transactions": { + "cancel_explain": "我们将使用一笔向您付款且矿工费更高的交易替换此交易。这实际上会取消当前交易。这称为 RBF(费用替换)。 ", "cancel_no": "此交易不可替换。", "cancel_title": "取消此交易(RBF)", - "confirmations_lowercase": "{confirmations}个确认", - "copy_link": "复制链接", - "expand_note": "打开备注", + "transaction_loading_error": "加载交易时出现问题。请稍后再试。", + "transaction_not_available": "交易不可用", + "confirmations_lowercase": "{confirmations} 次确认", + "expand_note": "展开备注", "cpfp_create": "创建", - "cpfp_exp": "我们将创建另一笔交易来花费您的未确认的交易。这个新总费用将高于原來交易的费用,令到其更快地被矿工放进区块链。 这称为CPFP-孩子为父母付费(Child Pays for Parent)。", - "cpfp_no_bump": "这项交易是不能碰撞的。", - "cpfp_title": "碰撞费用 (CPFP)", + "cpfp_exp": "我们将创建另一笔交易,用于花费您未确认的交易。总矿工费将高于原始矿工费,因此应更快被挖出并确认。这被称为 CPFP(子交易支付父交易)。", + "cpfp_no_bump": "此交易无法追加矿工费。", + "cpfp_title": "追加矿工费(CPFP)", "details_balance_hide": "隐藏余额", "details_balance_show": "显示余额", - "details_block": "区块高", "details_copy": "复制", - "details_copy_amount": "复制金额", - "details_copy_txid": "复制 Tx ID", - "details_from": "输入", + "details_copy_block_explorer_link": "复制区块链浏览器链接", + "details_copy_note": "复制备注", + "details_copy_txid": "复制交易 ID", "details_inputs": "输入", "details_outputs": "输出", - "details_received": "已收到", - "transaction_note_saved": "交易记录已成功保存。", - "details_show_in_block_explorer": "在区块浏览器查看", - "details_title": "转账", + "date": "日期", + "details_received": "已接收", + "details_view_in_browser": "在浏览器中查看", + "details_title": "交易", + "incoming_transaction": "收到的交易", + "outgoing_transaction": "发出的交易", + "expired_transaction": "过期的交易", + "pending_transaction": "待处理的交易", + "offchain": "链下", + "onchain": "链上", "details_to": "输出", - "enable_offline_signing": "此钱包未与线下签名结合使用。您想立即启用它吗?", - "list_conf": "Conf: {number}", - "pending": "待办", + "enable_offline_signing": "此钱包当前未与离线签名配合使用。您现在想启用吗?", + "list_conf": "确认次数:{number}", + "pending": "待处理", + "pending_with_amount": "待处理 {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "预计到达时间:大约 10 分钟", + "eta_3h": "预计到达时间:大约 3 个小时", + "eta_1d": "预计到达时间:大约 1 天", "list_title": "交易", - "rbf_title": "碰撞费用(RBF)", - "status_bump": "碰撞费用", + "list_title_sent": "已发送", + "list_title_received": "已接收", + "transaction": "交易", + "open_url_error": "无法使用默认浏览器打开链接。请更换默认浏览器并重试。", + "rbf_explain": "我们将使用一笔矿工费更高的交易替换当前交易,以便其能更快被矿工打包确认。这称为 RBF(费用替换)。", + "rbf_title": "追加矿工费(RBF)", + "status_bump": "追加矿工费", "status_cancel": "取消交易", - "transactions_count": "交易计数", - "txid": "交易ID", - "updating": "正在更新..." + "transactions_count": "交易数量", + "txid": "交易 ID", + "updating": "正在更新……", + "watchOnlyWarningTitle": "安全警告", + "watchOnlyWarningDescription": "请注意骗子,他们经常使用“观察钱包”来欺骗用户。这类钱包无法让您控制或发送资金,只能查看余额。 ", + "custom_fee_warning_title": " 警告", + "custom_fee_warning_description": "低于 1 聪/字节的矿工费是有效的,但可能由于节点政策而无法被转发。 ", + "details_eta_analyzing": "正在分析……", + "details_sent": "已发送", + "details_section": "详情", + "details_explorer": "浏览器", + "details_network_fee": "矿工费", + "details_to_address": "到", + "details_id": "ID", + "details_note": "备注", + "details_add_note": "添加", + "details_advanced": "高级", + "details_fee_rate": "费率", + "details_size": "大小", + "details_virtual_size": "虚拟大小", + "details_tx_hex": "交易十六进制", + "details_inputs_count": "输入({count})", + "details_outputs_count": "输出({count})" }, "wallets": { "add_bitcoin": "比特币", - "add_bitcoin_explain": "简单而强大的比特币钱包", + "add_bitcoin_explain": "简洁而强大的比特币钱包", "add_create": "创建", - "add_entropy_generated": "产生熵的{gen}个字节", + "total_balance": "总余额", + "add_entropy_reset_title": "重置熵", + "add_entropy_reset_message": "更改钱包类型将重置当前熵。您确定要继续吗?", + "add_entropy": "熵", + "add_entropy_bytes": "{bytes} 字节的熵", + "add_entropy_generated": "已生成 {gen} 字节熵", "add_entropy_provide": "通过掷骰子提供熵", - "add_entropy_remain": "产生熵的{gen}个字节。 剩余的{rem}字节将从系统随机数生成器中获得。", + "add_entropy_remain": "已生成 {gen} 字节熵。剩余 {rem} 字节将由系统随机数生成器提供。", "add_import_wallet": "导入钱包", - "add_lightning": "闪电", - "add_lightning_explain": "用于即时交易的花费", - "add_lndhub": "连接到您的LNDHub", + "add_lightning": "闪电网络", + "add_lightning_explain": "用于即时交易支出", + "add_lndhub": "连接到您的闪电网络守护进程集线器(LNDhub)", + "add_lndhub_error": "提供的地址不是有效的闪电网络守护进程集线器(LNDhub)服务器。", "add_lndhub_placeholder": "您的节点地址", + "add_placeholder": "我的第一个钱包", "add_title": "添加钱包", "add_wallet_name": "名称", "add_wallet_type": "类型", - "clipboard_bitcoin": "您的剪贴板上有一个比特币地址。您想使用它进行交易吗?", - "clipboard_lightning": "您的剪贴板上有一张闪电賬單。 您想使用它进行交易吗?", + "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_address": "地址", - "details_advanced": "进阶的", - "details_are_you_sure": "你确认么?", - "details_connected_to": "连接到", - "details_del_wb_err": "提供的余额与此钱包的余额不匹配,请再试一遍。", + "details_advanced": "高级设置", + "details_are_you_sure": "您确定吗?", + "details_connected_to": "已连接到", + "details_del_wb_err": "提供的余额与此钱包的余额不匹配。请重试。", + "details_del_wb_q": "此钱包有余额。在继续操作之前,请注意,如果没有此钱包的助记词,您将无法恢复资金。为了避免意外删除,请输入此钱包的余额:{balance} 聪。", "details_delete": "删除", "details_delete_wallet": "删除钱包", - "details_derivation_path": "推导路径", - "details_display": "在钱包清单中显示", + "details_derivation_path": "派生路径", + "details_display": "在主屏幕显示", "details_export_backup": "导出/备份", - "details_master_fingerprint": "主指纹", + "details_export_history": "将历史记录导出为 CSV 文件", + "details_master_fingerprint": "主密钥指纹", "details_multisig_type": "多重签名", - "details_no_cancel": "不了,请取消", - "details_save": "保存", - "details_show_xpub": "展示钱包公钥", - "details_show_addresses": "展示地址", + "details_show_xpub": "展示钱包扩展公钥(XPUB)", + "details_show_addresses": "显示地址", "details_title": "钱包", + "wallets": "钱包", "details_type": "类型", - "details_use_with_hardware_wallet": "与硬体钱包一起使用", - "details_wallet_updated": "钱包已更新", + "details_use_with_hardware_wallet": "与硬件钱包配合使用", "details_yes_delete": "是的,删除", - "enter_bip38_password": "输入密码进行解密", - "export_title": "钱包导出", + "enter_bip38_password": "输入密码以解密", + "export_title": "导出钱包", "import_do_import": "导入", - "import_passphrase": "密语", - "import_passphrase_title": "密语", - "import_error": "导入失败,请确认你提供的信息是有效的", - "import_explanation": "输入你的种子短语、公钥、WIF或者任何你拥有的东西。BlueWallet将尽可能猜测正确的格式并导入您的钱包", - "import_imported": "已经导入", - "import_scan_qr": "扫描或导入一个档案", - "import_success": "你的钱包已成功导入。", + "import_passphrase": "密码短语", + "import_passphrase_title": "密码短语", + "import_passphrase_message": "如果您使用过密码短语,请输入。", + "import_error": "导入失败。请确保提供的数据有效。", + "import_explanation": "请输入您的助记词、公钥、钱包导入格式或任何可用信息。BlueWallet 会尽力识别正确格式并导入您的钱包。", + "import_imported": "已导入", + "import_scan_qr": "扫描或导入文件", + "import_success": "您的钱包已成功导入。", + "import_success_watchonly": "您的钱包已成功导入。警告:这是一个“观察钱包”,您无法从中支出资金。", + "import_search_accounts": "搜索账户", "import_title": "导入", + "learn_more": "了解详情", + "import_discovery_title": "发现", + "import_discovery_subtitle": "选择一个已发现的钱包", + "import_discovery_derivation": "使用自定义派生路径", + "import_discovery_no_wallets": "未找到任何钱包。", + "import_discovery_offline": "BlueWallet 当前处于离线模式。在此模式下,它无法验证钱包的存在,因此您需要手动选择正确的钱包。", + "import_derivation_found": "已发现", + "import_derivation_found_not": "未找到", + "import_derivation_loading": "加载中……", + "import_derivation_subtitle": "请输入自定义派生路径,我们将尝试发现您的钱包。", + "import_derivation_title": "派生路径", "import_derivation_unknown": "未知", - "list_create_a_button": "现在添加", + "import_wrong_path": "派生路径错误", + "list_create_a_button": "立即添加", "list_create_a_wallet": "添加钱包", - "list_create_a_wallet_text": "这是免费的,您可以创建\n喜欢多少就多少。", - "list_empty_txs1": "你的交易将在这里展示", - "list_empty_txs1_lightning": "应使用闪电钱包进行日常交易。费用超便宜而且速度飞快。", - "list_empty_txs2": "从你的钱包开始。", - "list_empty_txs2_lightning": "\n要开始使用它,请点击“管理资金”并充值。", - "list_latest_transaction": "最近的交易", - "list_ln_browser": "LApp浏览器", + "list_create_a_wallet_text": "完全免费,您可以创建\n任意数量的钱包。", + "list_empty_txs1": "您的交易记录将显示在此处。", + "list_empty_txs1_lightning": "闪电网络钱包适合用于日常交易。手续费极低,速度非常快。", + "list_empty_txs2": "从您的钱包开始。", + "list_empty_txs2_lightning": "\n要开始使用,请点击“管理资金”并为余额充值。", + "list_latest_transaction": "最新交易", "list_long_choose": "选择图片", - "list_long_clipboard": "从剪贴板复制", + "paste_from_clipboard": "粘贴", + "import_file": "导入文件", "list_long_scan": "扫描二维码", "list_title": "钱包", - "list_tryagain": "再试一次", - "no_ln_wallet_error": "在支付闪电账单之前,必须先添加一个闪电钱包。", + "list_tryagain": "重试", + "no_ln_wallet_error": "在支付闪电网络发票之前,您必须先添加一个闪电网络钱包。", "looks_like_bip38": "这看起来像是受密码保护的私钥(BIP38)。", - "reorder_title": "重新排列钱包", + "manage_title": "管理钱包", + "no_results_found": "未找到结果。", "please_continue_scanning": "请继续扫描。", "select_no_bitcoin": "当前没有可用的比特币钱包。", - "select_no_bitcoin_exp": "需要一个比特币钱包来为闪电钱包充值。 请创建或导入一个。", + "select_no_bitcoin_exp": "要为闪电网络钱包充值,必须先拥有一个比特币钱包。请创建或导入一个。", "select_wallet": "选择钱包", - "xpub_copiedToClipboard": "复制到粘贴板。", - "pull_to_refresh": "拉动来刷新", - "warning_do_not_disclose": "警告! 不要透露。", - "add_ln_wallet_first": "您必须先添加一个闪电钱包。", + "pull_to_refresh": "下拉刷新", + "warning_do_not_disclose": "切勿泄露以下信息", + "scan_import": "扫描此二维码,将您的钱包导入到其他应用程序中。", + "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_title": "钱包扩展公钥(XPUB)", + "manage_wallets_search_placeholder": "搜索钱包、地址、交易和备注", + "more_info": "更多信息", + "details_delete_wallet_error_message": "确认此钱包是否已从通知中移除时出现问题——这可能是由于网络问题或连接不良。如果您继续操作,即使删除此钱包,您仍可能收到与该钱包相关的交易通知。", + "details_delete_anyway": "仍然删除", + "swipe_balance_hide": "隐藏", + "swipe_balance_show": "显示", + "drag_to_reorder": "拖动以重新排序", + "clear_search": "清除搜索" + }, + "total_balance_view": { + "display_in_bitcoin": "以比特币显示", + "hide": "隐藏", + "display_in_sats": "以“聪”单位显示", + "display_in_fiat": "以 {currency} 显示", + "title": "总余额", + "explanation": "在总览界面查看所有钱包的总余额。" }, "multisig": { - "multisig_vault": "保管库", - "default_label": "多重签名保管库", - "multisig_vault_explain": "大金额需要的最佳安全性", + "multisig_vault": "多重签名金库", + "default_label": "多重签名金库", + "multisig_vault_explain": "大额资金的最佳安全保障", "provide_signature": "提供签名", - "vault_key": "保管库密钥 {number}", - "required_keys_out_of_total": "总数中所需的密钥", - "fee": "费用:{number}", - "fee_btc": "{number} 比特币", + "provide_signature_details": "使用存储密钥的设备和钱包对交易进行签名 ", + "provide_signature_details_bluewallet": "在 BlueWallet 中,前往“发送”界面菜单并选择 ", + "provide_signature_next_steps": "扫描或导入已签署的交易", + "provide_signature_next_steps_details": "钱包成功签署交易后,请扫描提供的二维码或导入随附文件,然后在广播交易前仔细检查所有交易详情。", + "vault_key": "金库密钥 {number}", + "required_keys_out_of_total": "总密钥数量中所需的密钥数量", + "fee": "矿工费:{number}", + "fee_btc": "{number} BTC", "confirm": "确认", "header": "发送", - "share": "分享", + "share": "共享……", "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}", + "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": "共同签署此交易?", + "export_coordination_setup": "导出协作设置", + "cosign_this_transaction": "要对这笔交易进行联合签名吗?", "lets_start": "开始吧", "create": "创建", - "native_segwit_title": "最佳做法", + "native_segwit_title": "最佳实践", "wrapped_segwit_title": "最佳兼容性", - "legacy_title": "旧制式", - "co_sign_transaction": "签署一笔交易", - "what_is_vault": "保管库是", - "what_is_vault_numberOfWallets": "{m}-of-{n} 多重签名", + "legacy_title": "传统", + "co_sign_transaction": "签署交易", + "what_is_vault": "金库是一个", + "what_is_vault_numberOfWallets": "{m}/{n} 多重签名", "what_is_vault_wallet": "钱包。", - "vault_advanced_customize": "保管库设定", + "vault_advanced_customize": "金库设置", "needs": "它需要", - "what_is_vault_description_number_of_vault_keys": "{m}个保管库密钥", - "what_is_vault_description_to_spend": "花费,还有第三个\n可以用作备份的。", - "what_is_vault_description_to_spend_other": "花费。", - "quorum": "{m} of {n} 法定人数", - "quorum_header": "法定人数", - "of": "的", + "what_is_vault_description_number_of_vault_keys": "{m} 个金库密钥", + "what_is_vault_description_to_spend": "用于支出,第三个\n可作为备份使用。", + "what_is_vault_description_to_spend_other": "用于支出。", + "quorum": "{m}/{n} 法定数", + "quorum_header": "法定数", + "of": "/", "wallet_type": "钱包类型", - "invalid_mnemonics": "这个助记短语似乎无效。", - "not_a_multisignature_xpub": "这不是来自多重签名钱包的公钥!", - "invalid_cosigner_format": "不正确的签名人:这不是{format}格式的签名人。", + "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": "这是共同签名者的公钥,可以导入另一个钱包。 分享是安全的。", - "wallet_key_created": "您的保管库密钥已创建。花点时间安全地备份您的助记符种子。", - "are_you_sure_seed_will_be_lost": "你确定吗? 如果没有备份,助记符种子将丢失。", - "forget_this_seed": "忘记此种子,而是使用公钥。", - "view_edit_cosigners": "查看/编辑共同签名者", - "this_cosigner_is_already_imported": "此共同签名者已经被导入。", - "export_signed_psbt": "导出已签名的PSBT", + "i_have_mnemonics": "我有该密钥的助记词。", + "type_your_mnemonics": "输入助记词以导入您现有的金库密钥。", + "this_is_cosigners_xpub": "这是该联合签名者的扩展公钥(XPUB)——可导入到其他钱包使用。分享它是安全的。", + "this_is_cosigners_xpub_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_fp_explain": "跳过并使用默认值(00000000)", + "input_path": "输入派生路径", "input_path_explain": "跳过以使用默认值({default})", "ms_help": "帮助", - "ms_help_title": "多重签名保管库如何运作:提示和技巧", - "ms_help_text": "具有多重密钥的钱包,可提高安全性或共享保管", + "ms_help_title": "多重签名金库工作原理:技巧与提示", + "ms_help_text": "拥有多个密钥的钱包,用于增强安全性或共享托管", "ms_help_title1": "建议使用多个设备。", - "ms_help_1": "这个保管库将与其他BlueWallet应用程序和PSBT兼容的钱包配合使用,例如Electrum、Spectre、Coldcard、Cobo Vault等。", + "ms_help_1": "该金库可与其他 BlueWallet 应用及兼容 PSBT(部分签名比特币交易)的钱包配合使用,例如 Electrum、Specter、Coldcard、Cobo Vault 等。", "ms_help_title2": "编辑密钥", - "ms_help_title3": "保管库备份", - "ms_help_title4": "导入保管库", - "ms_help_4": "要导入多重签名,请使用您的备份文件和导入功能。 如果只有种子和公钥,则可以在创建保管库密钥时使用单独的“导入”按钮。", - "ms_help_title5": "进阶模式", - "ms_help_5": "默认情况下,BlueWallet 将生成 2-of-3 保管库。要创建不同的法定人数或更改地址类型,请在“设置”中激活“进阶模式”。" + "ms_help_2": "您可以在此设备上创建所有金库密钥,并可在之后进行删除或编辑。在同一设备上保存所有密钥,其安全性与普通比特币钱包相当。", + "ms_help_title3": "金库备份", + "ms_help_3": "在钱包选项中,您将找到金库备份和观察备份。此备份相当于钱包的地图,对于在丢失任一种助记词时进行钱包恢复至关重要。", + "ms_help_title4": "导入金库", + "ms_help_4": "要导入多重签名钱包,请使用备份文件和“导入”功能。如果您只有助记词和扩展公钥(XPUB),可以在创建金库密钥时使用单独的“导入”按钮。", + "ms_help_title5": "高级模式", + "ms_help_5": "默认情况下,BlueWallet 会生成一个 2/3 金库。若要创建不同的法定数或更改地址类型,请在“设置”中启用高级模式。" }, "is_it_my_address": { - "title": "是我的地址吗?", - "owns": "{label}拥有{address}", + "title": "这是我的地址吗?", + "owns": "{label} 拥有 {address}", "enter_address": "输入地址", "check_address": "检查地址", - "no_wallet_owns_address": "没有可用的钱包拥有提供的地址。", + "no_wallet_owns_address": "可用的钱包中没有拥有该地址的", "view_qrcode": "查看二维码" }, + "autofill_word": { + "title": "最终助记单词", + "enter": "输入您的部分助记词", + "generate_word": "生成最终助记单词", + "error": "输入的内容不是 11 或 23 个助记词的部分助记词,请重试。" + }, "cc": { - "change": "更变", - "coins_selected": "所选的币({number})", - "empty": "此钱包目前没有币。", + "change": "找零", + "coins_selected": "已选币数({number})", + "selected_summ": "已选择 {value} ", + "empty": "该钱包当前没有任何币(UTXO)", "freeze": "冻结", "freezeLabel": "冻结", "freezeLabel_un": "解冻", - "header": "币的控制", - "use_coin": "使用币", - "use_coins": "使用币", - "tip": "此功能使您可以查看、标记、冻结或选择币,以改善钱包管理。 您可以通过点击彩色圆圈选择多个币。" + "header": "选币功能", + "use_coin": "使用此币", + "use_coins": "使用所选币", + "tip": "此功能可让您查看、标记、冻结或选择币(UTXO),以便更好地管理钱包。您可以通过点击彩色圆圈选择多个币。", + "sort_asc": "升序", + "sort_desc": "降序", + "sort_height": "高度", + "sort_value": "金额", + "sort_label": "标签", + "sort_status": "状态", + "sort_by": "排序方式" }, "units": { - "BTC": "比特幣", + "BTC": "BTC", "MAX": "最大", + "sat_vbyte": "聪/字节", "sats": "聪" }, "addresses": { - "sign_sign": "签署", + "copy_private_key": "复制私钥", + "sensitive_private_key": "警告:私钥极其敏感。是否继续?", + "sign_title": "签署/验证消息", + "sign_help": "在此,您可以基于比特币地址创建或验证加密签名。", + "sign_sign": "签名", "sign_verify": "验证", + "sign_signature_correct": "验证成功!", + "sign_signature_incorrect": "验证失败!", "sign_placeholder_address": "地址", "sign_placeholder_message": "信息", "sign_placeholder_signature": "签名", "addresses_title": "地址", - "type_change": "改变", + "type_change": "找零", "type_receive": "接收", + "type_used": "已使用", "transactions": "交易" + }, + "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": "身份验证" + }, + "bip47": { + "payment_code": "支付码", + "contacts": "联系人", + "bip47_explain": "可重复使用并共享的代码", + "bip47_explain_subtitle": "BIP47", + "purpose": "可重复使用并共享的代码(BIP47)", + "pay_this_contact": "向此联系人付款", + "rename_contact": "重命名联系人", + "copy_payment_code": "复制支付码", + "hide_contact": "隐藏联系人", + "rename": "重命名", + "provide_name": "为此联系人提供新的名称", + "add_contact": "添加联系人", + "provide_payment_code": "提供支付码", + "invalid_pc": "无效的支付码", + "notification_tx_unconfirmed": "通知交易尚未确认,请稍等。", + "failed_create_notif_tx": "创建链上交易失败", + "onchain_tx_needed": "需要在链上交易", + "notif_tx_sent": "通知交易已发送。请等待确认", + "notif_tx": "通知交易", + "not_found": "未找到支付码" } } diff --git a/loc/zh_tw.json b/loc/zh_tw.json index a6d620aa5da..f1ba303a5ad 100644 --- a/loc/zh_tw.json +++ b/loc/zh_tw.json @@ -4,6 +4,7 @@ "cancel": "取消", "continue": "繼續", "clipboard": "剪貼簿", + "discard_changes": "放棄變更?", "enter_password": "輸入密碼", "never": "永不", "of": "{total}其中之{number}", @@ -11,26 +12,40 @@ "storage_is_encrypted": "你的儲存資料已經被加密, 請輸入密碼解密。", "yes": "是", "no": "否", - "save": "儲存", "seed": "種子", "success": "成功", "wallet_key": "錢包密鑰", - "invalid_animated_qr_code_fragment": "無效的動態二維碼,請重試。", - "downloads_folder": "下載資料夾" + "copied": "已複製!", + "discard_changes_explain": "您有未儲存的變更。確定要捨棄並離開此畫面嗎?", + "enter_url": "輸入網址", + "save": "儲存…", + "close": "關閉", + "change_input_currency": "變更輸入幣別", + "refresh": "重新整理", + "pick_image": "從圖庫選擇", + "pick_file": "選擇檔案", + "enter_amount": "輸入金額", + "qr_custom_input_button": "點擊10次以輸入自訂內容", + "unlock": "解鎖", + "port": "連接埠", + "ssl_port": "SSL 連接埠", + "suggested": "建議" }, "azteco": { - "codeIs": "您的優惠碼爲", + "codeIs": "您的優惠碼為", "errorBeforeRefeem": "在兌贖之前,你需先新增一個比特幣錢包。", "errorSomething": "出了些問題。 此優惠券仍然有效嗎?", "redeem": "兌贖到錢包", "redeemButton": "兌贖", "success": "成功", - "title": "兌贖Azte.co優惠券" + "title": "兌贖Azte.co優惠券", + "successMessage": "優惠券兌換成功!您的資金將很快抵達您的比特幣錢包。" }, "entropy": { "save": "儲存", "title": "熵", - "undo": "復原" + "undo": "復原", + "amountOfEntropy": "{bits} / {limit} 位元" }, "errors": { "broadcast": "廣播失敗", @@ -38,54 +53,63 @@ "network": "網路錯誤" }, "lnd": { - "errorInvoiceExpired": "賬單已過期", "expired": "已過期", "payButton": "支付", - "placeholder": "賬單", - "potentialFee": "潛在費用:{fee}", "refill": "增值", - "refill_create": "爲了繼續進行,請建立一個要增值的比特幣錢包。", + "refill_create": "為了繼續進行,請建立一個要增值的比特幣錢包。", "refill_external": "用外部錢包增值", "refill_lnd_balance": "給閃電錢包增值", - "sameWalletAsInvoiceError": "你不能用建立賬單的錢包去支付該賬單。", + "sameWalletAsInvoiceError": "你不能用建立帳單的錢包去支付該帳單", "title": "管理資金", - "can_send": "能傳送", - "can_receive": "能接收" + "errorInvoiceExpired": "發票已過期。", + "expiresIn": "{time} 分鐘後到期", + "payment": "支付", + "placeholder": "發票或地址", + "potentialFee": "預估手續費:{fee}" }, "lndViewInvoice": { - "additional_info": "附加信息", - "for": "为了:", - "lightning_invoice": "閃電賬單", - "open_direct_channel": "使用此節點來開啟直接頻道:", + "additional_info": "附加訊息", + "for": "為了:", + "lightning_invoice": "閃電帳單", "please_pay": "請付款", - "preimage": "原像", "sats": "聰", - "wasnt_paid_and_expired": "此賬單尚未付款,已過期。" + "wasnt_paid_and_expired": "此帳單尚未付款,已過期。", + "please_pay_between_and": "請支付介於 {min} 與 {max} 之間", + "preimage": "原像", + "date_time": "日期與時間" }, "plausibledeniability": { "create_fake_storage": "建立加密儲存", - "create_password": "建立密碼", "create_password_explanation": "虛假儲存空間密碼不能和主儲存空間密碼相同", - "help": "在某些情況下,你不得不透露密碼。爲了讓你的比特幣更加安全,BlueWallet可以建立另一個用不同密碼的加密空間,在壓力之下,你可能透露這個密碼。如果進入 BlueWallet,我們會解鎖一個新的虛假儲存空間。對第三方來說看上去是合理的,但會偷偷的幫你保證主錢包的安全,幣也就安全了。", + "help": "在某些情況下,你不得不透露密碼。為了讓你的比特幣更加安全,BlueWallet可以建立另一個用不同密碼的加密空間,在壓力之下,你可能透露這個密碼。如果進入 BlueWallet,我們會解鎖一個新的虛假錢包。這對第三方來說似乎是合理的,但對你不是,因為你知道你的主錢包是安全藏起來的。", "help2": "新的儲存空間具備完整的功能,你可以存入少量的金額在裏面。", "password_should_not_match": "此密碼已被使用,請用另一個密碼。", - "passwords_do_not_match": "密碼不匹配,請再試一遍。", - "retype_password": "重新輸入密碼", - "success": "成功", "title": "合理推諉" }, "pleasebackup": { - "ask": "您是否保存了錢包的備份短語? 如果您丟失了此設備,則需要此備份短語來訪問您的資金。沒有此備份短語,您的資金將永久丟失。", - "ask_no": "不,我還沒有", - "ask_yes": "是的,我完成了", - "text_lnd": "請儲存此錢包備份。這個備份可以在裝置遺失時用來恢複此錢包。" + "ask": "您是否已備份錢包的助記詞?若您遺失此裝置,將需要此助記詞來存取您的資金。沒有此助記詞,您的資金將永久遺失。", + "ok": "好,我把它寫下來了。", + "ok_lnd": "好的,我已經儲存了", + "text_lnd": "請儲存此錢包備份。這個備份可以在裝置遺失時用來恢復此錢包。", + "title": "你的錢包已被建立。", + "ask_no": "尚未,我還沒有。", + "ask_yes": "是的,我已經備份了。", + "text": "請花點時間將此助記詞寫在紙上。\n這是您的備份,可用來復原錢包。" }, "receive": { "details_create": "建立", "details_label": "描述", "details_setAmount": "收款金額", - "details_share": "分享", - "header": "收款" + "header": "收款", + "details_share": "分享…", + "address_not_found": "無法產生收款地址。", + "reset": "重設", + "maxSats": "最大金額為 {max} 聰", + "maxSatsFull": "最大金額為 {max} 聰或 {currency}", + "minSats": "最小金額為 {min} 聰", + "minSatsFull": "最小金額為 {min} 聰或 {currency}", + "qrcode_for_the_address": "地址的 QR 碼", + "bip47_explanation": "支付代碼是一個通用地址,可避免洩露您的錢包地址。並非所有服務都支援此功能。" }, "send": { "broadcastButton": "廣播", @@ -101,7 +125,7 @@ "create_details": "詳情", "create_fee": "手續費", "create_memo": "訊息", - "create_this_is_hex": "這個是您的交易的十六進制碼,已簽署並准備好廣播到網絡。", + "create_this_is_hex": "這個是您的交易的十六進制碼,已簽署並準備好廣播到網路。", "create_to": "到", "create_tx_size": "交易大小", "create_verify": "在coinb.in上驗證", @@ -119,7 +143,6 @@ "details_error_decode": "錯誤:無法解密比特幣地址", "details_fee_field_is_not_valid": "費用無效", "details_next": "下一個", - "details_no_signed_tx": "所選檔案不包含可以匯入的交易。", "details_note_placeholder": "給自己的訊息", "details_scan": "掃描", "details_total_exceeds_balance": "發送金額超過可用結餘", @@ -145,9 +168,7 @@ "permission_camera_message": "我們需要您的授權許可才能使用您的相機。", "psbt_sign": "簽署一單交易", "open_settings": "開啟設定", - "permission_storage_later": "稍後問我", - "permission_storage_message": "BlueWallet需要您的授權許可才能訪問您的儲存空間以儲存此檔案。", - "permission_storage_title": "儲存訪問權限", + "permission_storage_title": "儲存存取權限", "psbt_clipboard": "複製到剪貼簿", "psbt_this_is_psbt": "這是已部分簽署的比特幣交易(PSBT),請用您的硬體錢包完成簽署。", "psbt_tx_export": "匯出到檔案", @@ -155,170 +176,265 @@ "psbt_tx_open": "打開已簽署的交易", "psbt_tx_scan": "掃描已簽署的交易", "success_done": "完成", - "txSaved": "交易檔案({filePath})已儲存在“下載”文件夾中。", - "problem_with_psbt": "PSBT的問題" + "problem_with_psbt": "PSBT的問題", + "provided_address_is_invoice": "此地址似乎是 Lightning 發票。請前往您的 Lightning 錢包來支付此發票。", + "create_satoshi_per_vbyte": "聰每 vByte", + "details_insert_contact": "插入聯絡人", + "details_add_recc_rem_all_alert_description": "您確定要移除所有收件人嗎?", + "details_add_rec_rem_all": "移除所有收件人", + "details_recipients_title": "收件人", + "details_recipient_title": "收件人 #{number} / #{total}", + "please_complete_recipient_title": "收件人資料不完整", + "please_complete_recipient_details": "請先完成收件人 #{number} 的資料,再新增另一位收件人。", + "details_adv_full_sure_frozen": "您確定要將錢包的全部結餘用於此交易嗎?請注意,已凍結的幣將被排除。", + "details_adv_import_qr": "匯入交易(QR)", + "details_frozen": "{amount} BTC 已凍結。", + "details_no_signed_tx": "所選檔案不包含可匯入的交易。", + "details_scan_hint": "點按兩次以掃描或匯入目的地", + "details_scan_error": "掃描錯誤", + "details_total_exceeds_balance_frozen": "發送金額超過可用結餘。請注意,已凍結的幣將被排除。", + "insert_custom_fee": "輸入手續費", + "fee_replace_minvb": "您要支付的總手續費率(聰每 vByte)必須高於 {min} sat/vByte。", + "fee_satvbyte": "以 sat/vByte 為單位", + "invalid_psbt": "提供的 PSBT 無效。", + "permission_storage_denied_message": "BlueWallet 無法儲存此檔案。請開啟您的裝置設定並啟用儲存權限。", + "outdated_rate": "匯率上次更新:{date}", + "qr_error_no_qrcode": "我們在所選圖片中找不到有效的 QR 碼。請確認圖片只包含 QR 碼,沒有其他內容(例如文字或按鈕)。", + "reset_amount": "重設金額", + "reset_amount_confirm": "您要重設金額嗎?", + "txSaved": "交易檔案 ({filePath}) 已儲存。", + "file_saved_at_path": "檔案 ({filePath}) 已儲存。", + "cant_send_to_silentpayment_adress": "此錢包無法傳送至 Silent Payments 地址", + "cant_send_to_bip47": "此錢包無法傳送至 BIP47 支付代碼", + "cant_find_bip47_notification": "請先將此支付代碼加入聯絡人" }, "settings": { "about": "關於", "about_awesome": "從很棒的創立", "about_backup": "經常備份您的密鑰!", - "about_free": "BlueWallet是一個免費的開源項目,由比特幣用戶製作。", + "about_free": "BlueWallet是一個免費的開源專案,由比特幣使用者製作。", "about_license": "麻省理工學院許可證", "about_release_notes": "發佈說明", "about_review": "給我們評論", "about_selftest": "運行自我檢查", "about_selftest_ok": "所有內部測試均已成功通過,錢包運作良好。", "about_sm_github": "GitHub", - "about_sm_discord": "Discord 伺服器", - "about_sm_telegram": "電報(Telegram)頻道", - "about_sm_twitter": "在推特上追蹤我們", - "advanced_options": "進階選項", + "about_sm_telegram": "電報(Telegram)頻道", "biometrics": "生物識別", "biom_10times": "您已嘗試輸入密碼10次。 您想重設儲存空間嗎? 這將刪除所有錢包並解密您的儲存。", "biom_conf_identity": "請確認您的身份。", - "biom_no_passcode": "您的設備沒有密碼。 為了繼續進行,請在“設定”應用程式中配置密碼。", "biom_remove_decrypt": "您的所有錢包將被刪除,您的儲存空間將被解密。您確定要繼續嗎?", "currency": "貨幣", - "default_desc": "停用後,BlueWallet將在啟動時立即打開選定的錢包。", - "default_info": "預設信息", "default_title": "啟動時", - "default_wallets": "查看所有錢包", "electrum_connected": "已連接", "electrum_connected_not": "未連接", - "electrum_error_connect": "無法連接到提供的Electrum伺服器", - "electrum_saved": "您的更變已成功儲存。要使更變生效,可能需要重新啟動BlueWallet。", + "electrum_saved": "您的變更已成功儲存。要使變更生效,可能需要重新啟動BlueWallet。", "set_electrum_server_as_default": "將{server}設定為預設的Electrum伺服器?", - "set_lndhub_as_default": "將{url}設定為預設的LNDHub伺服器?", "electrum_settings_server": "Electrum伺服器", "electrum_status": "狀態", - "electrum_clear_alert_title": "清除歷史記錄?", - "electrum_clear_alert_message": "您是否要清除electrum 伺服器的歷史記錄?", - "electrum_clear_alert_cancel": "取消", - "electrum_clear_alert_ok": "好的", - "electrum_select": "選取", - "electrum_reset": "重設為預設值", "electrum_unable_to_connect": "無法連接至 {server}。", - "electrum_history": "伺服器歷史記錄", - "electrum_reset_to_default": "您確定要重設您的Electrum設定為預設值嗎?", - "electrum_clear": "清除", + "electrum_reset": "重設為預設值", "encrypt_decrypt": "解密儲存", - "encrypt_decrypt_q": "您確定要解密儲存嗎?這樣一來,無需密碼即可訪問您的錢包。", - "encrypt_enc_and_pass": "加密和密碼保護的", + "encrypt_decrypt_q": "您確定要解密儲存嗎?這樣一來,無需密碼即可存取您的錢包。", "encrypt_title": "安全", "encrypt_tstorage": "儲存", "encrypt_use": "使用{type}", "general": "一般的", - "general_adv_mode": "進階模式", - "general_adv_mode_e": "啟用後,您將看到進階選項,例如不同的錢包類型、指定連接LNDHub進程的能力以及在建立錢包期間的自定義熵。", "general_continuity": "連續性", "general_continuity_e": "啟用後,您將能夠查看選定的錢包、交易及使用您其他Apple iCloud連接的設備。", - "groundcontrol_explanation": "GroundControl是一款免費的開源推送通知服務器,用於比特幣錢包。 您可以安裝自己的GroundControl伺服器並將其URL放在此處,而不依賴BlueWallet的基礎結構。 保留空白以使用GroundControl的預設伺服器。", + "groundcontrol_explanation": "GroundControl是一款免費的開源推送通知伺服器,用於比特幣錢包。 您可以安裝自己的GroundControl伺服器並將其URL放在此處,而不依賴BlueWallet的基礎結構。 保留空白以使用預設值。", "header": "設定", "language": "語言", - "lightning_saved": "您的更變已成功儲存。", + "lightning_saved": "您的變更已成功儲存。", "lightning_settings": "閃電網路設定", - "network": "網絡", + "network": "網路", "network_broadcast": "廣播交易", "network_electrum": "Electrum伺服器", "notifications": "通知事項", - "open_link_in_explorer": "在資源管理器中打開鏈接", + "open_link_in_explorer": "在資源管理器中打開連結", "password": "密碼", - "password_explain": "建立密碼,您將用此密碼來解密儲存空間", - "passwords_do_not_match": "兩個密碼不同", "plausible_deniability": "合理推諉", "privacy": "私隱", "privacy_read_clipboard": "讀取剪貼板", "privacy_system_settings": "系統設定", "privacy_quickactions": "錢包捷徑", - "privacy_quickactions_explanation": "觸碰並按住主屏幕上的BlueWallet應用圖標,以快速查看您的錢包結餘。", - "privacy_clipboard_explanation": "如果在剪貼簿中找到地址或賬單,請提供捷徑。", - "push_notifications": "推送通知", - "retype_password": "再次輸入密碼", + "privacy_clipboard_explanation": "如果在剪貼簿中找到地址或帳單,請提供捷徑。", "selfTest": "自行測試", "save": "儲存", "saved": "已儲存", "total_balance": "結餘", - "total_balance_explanation": "在主屏幕小工具上顯示您所有錢包的結餘。", + "total_balance_explanation": "在主螢幕小工具上顯示您所有錢包的結餘。", "widgets": "小工具", - "tools": "工具" + "tools": "工具", + "performance_score": "效能分數:{num}", + "run_performance_test": "測試效能", + "block_explorer_invalid_custom_url": "提供的網址無效。請輸入以 http:// 或 https:// 開頭的有效網址。", + "about_selftest_electrum_disabled": "Electrum 離線模式下無法進行自我檢查。請停用離線模式後再試。", + "privacy_temporary_screenshots": "允許螢幕擷取", + "privacy_temporary_screenshots_instructions": "螢幕擷取保護將暫時關閉,允許截圖與螢幕錄影。當您關閉並重新開啟 BlueWallet 時,保護將自動重新啟用。", + "biometrics_no_longer_available": "您的裝置設定已變更,不再符合應用程式中所選的安全性設定。請重新啟用生物辨識或裝置密碼,然後重新啟動應用程式以套用變更。", + "biom_no_passcode": "您的裝置未啟用裝置密碼或生物辨識。若要繼續,請於「設定」應用程式中設定裝置密碼或生物辨識。", + "currency_source": "匯率來源", + "currency_fetch_error": "取得所選貨幣匯率時發生錯誤。", + "donate": "捐款", + "donate_description": "協助我們讓 Blue 保持免費!", + "electrum_error_connect": "無法連接至提供的 Electrum 伺服器", + "electrum_error_connect_tor": "無法連接至提供的 Electrum 伺服器。請確認 Orbot 應用程式已連接後再試。", + "lndhub_uri": "例如:{example}", + "electrum_host": "例如:{example}", + "electrum_offline_mode": "離線模式", + "electrum_offline_description": "啟用時,您的比特幣錢包將不會嘗試取得結餘或交易。", + "electrum_port": "連接埠,通常為 {example}", + "use_ssl": "使用 SSL", + "set_lndhub_as_default": "將 {url} 設為預設的 LNDhub 伺服器?", + "electrum_preferred_server": "偏好伺服器", + "electrum_preferred_server_description": "輸入您希望錢包用於所有比特幣活動的伺服器。設定後,您的錢包將只使用此伺服器來檢查結餘、傳送交易與取得網路資料。設定前請確保您信任此伺服器。", + "electrum_history": "紀錄", + "electrum_reset_to_default": "這將讓 BlueWallet 從伺服器清單中隨機選擇一個。", + "electrum_reset_to_default_and_clear_history": "重設為預設值並清除紀錄", + "encrypt_enc_and_pass": "密碼保護", + "encrypt_storage_explanation_headline": "啟用儲存空間加密", + "encrypt_storage_explanation_description_line1": "啟用儲存空間加密會為您的應用程式增加一層額外保護,加強資料在裝置上的儲存方式,使他人在未經許可的情況下更難存取您的資訊。", + "encrypt_storage_explanation_description_line2": "但請注意,此加密僅保護裝置鑰匙圈上錢包的存取權。它不會為錢包本身設定密碼或其他額外保護。", + "i_understand": "我了解", + "block_explorer": "區塊瀏覽器", + "block_explorer_preferred": "使用偏好的區塊瀏覽器", + "block_explorer_error_saving_custom": "儲存偏好區塊瀏覽器時發生錯誤", + "set_as_preferred": "設為偏好", + "set_as_preferred_electrum": "將 {host}:{port} 設為偏好伺服器後,將停用隨機連接到建議伺服器的功能。", + "encrypted_feature_disabled": "啟用儲存空間加密時無法使用此功能。", + "encrypt_use_expl": "在進行交易、解鎖、匯出或刪除錢包之前,將使用 {type} 來確認您的身分。", + "biometrics_fail": "若未啟用 {type},或解鎖失敗,您可改用裝置密碼。", + "last_updated": "最後更新", + "language_isRTL": "需要重新啟動 BlueWallet 才能讓語言方向生效。", + "license": "授權條款", + "lightning_error_lndhub_uri": "無效的 LNDhub URI", + "lightning_error_lndhub_uri_tor": "無效的 LNDhub URI。請確認 Orbot 應用程式已連接後再試。", + "lightning_settings_explain": "若要連接您自己的 LND 節點,請安裝 LNDhub 並將其網址填入此處的設定。請注意,僅在儲存變更後建立的錢包會連接到指定的 LNDhub。", + "lndhub_github": "GitHub 儲存庫", + "electrum_suggested_description": "未設定偏好伺服器時,系統將隨機選擇一個建議伺服器使用。", + "not_a_valid_uri": "無效的 URI", + "password_explain": "輸入您將用來解鎖儲存空間的密碼。", + "privacy_quickactions_explanation": "長按 BlueWallet 應用程式圖示以快速查看您錢包的結餘。", + "privacy_do_not_track": "停用分析", + "privacy_do_not_track_explanation": "效能與可靠性資訊將不會送出進行分析。", + "rate": "匯率", + "push_notifications_explanation": "啟用通知後,您的裝置權杖將會傳送至伺服器,連同啟用通知後所有錢包與所進行交易的錢包地址與交易 ID。裝置權杖用於傳送通知,而錢包資訊則讓我們能通知您比特幣到帳或交易確認。\n\n僅傳送啟用通知後的資訊—之前的資料不會被收集。\n\n停用通知將從伺服器移除所有上述資訊。此外,從應用程式刪除錢包也會從伺服器移除其相關資訊。", + "success_transaction_broadcasted": "您的交易已成功廣播!" }, "notifications": { "would_you_like_to_receive_notifications": "您想在收到款項時得到通知嗎?", - "no_and_dont_ask": "不,不要再問我", - "ask_me_later": "稍後問我" + "notifications_subtitle": "到帳付款與交易確認", + "no_and_dont_ask": "不,且不要再詢問我。", + "permission_denied_message": "您已拒絕傳送通知的權限。若要接收通知,請於裝置設定中啟用。" }, "transactions": { "cancel_no": "此交易不可替換。", "cancel_title": "取消此交易(RBF)", "confirmations_lowercase": "{confirmations}個確認", - "copy_link": "複製連結", "expand_note": "打開備註", "cpfp_create": "建立", - "cpfp_exp": "我們將建立另一筆交易來花費您的未確認的交易。這個新總費用將高於原來交易的費用,令到其更快地被礦工放進區塊鏈。這稱為CPFP-孩子為父母付費(Child Pays for Parent)。", + "cpfp_exp": "我們將建立另一筆交易來花費您的未確認的交易。這個新總費用將高於原來交易的費用,使其更快地被礦工放進區塊鏈。這稱為CPFP-孩子為父母付費(Child Pays for Parent)。", "cpfp_no_bump": "這項交易是不能對碰的。", "cpfp_title": "對碰費用 (CPFP)", "details_balance_hide": "隱藏結餘", "details_balance_show": "顯示結餘", - "details_block": "區塊高度", "details_copy": "複製", - "details_copy_amount": "複製數量", - "details_from": "輸入", "details_inputs": "輸入", "details_outputs": "輸出", "details_received": "已收到", - "transaction_note_saved": "交易記錄已成功儲存。", - "details_show_in_block_explorer": "區塊瀏覽器展示", - "details_title": "轉賬", - "details_to": "輸出", + "details_title": "轉帳", + "details_to": "至", "enable_offline_signing": "此錢包未與線下簽名結合使用。您想立即啟用它嗎?", - "list_conf": "Conf: {number}", + "list_conf": "確認數:{number}", "pending": "等待中", "list_title": "交易", + "list_title_received": "已收到", + "transaction": "轉帳", "rbf_title": "對碰費用(RBF)", "status_bump": "對碰費用", "status_cancel": "取消交易", "transactions_count": "交易記數", "txid": "交易ID", - "updating": "更新中..." + "updating": "更新中...", + "cancel_explain": "我們將以一筆付款給您本人且手續費較高的交易來取代此交易。這實際上會取消目前的交易。此功能稱為 RBF—以手續費取代。", + "transaction_loading_error": "載入交易時發生問題。請稍後再試。", + "transaction_not_available": "交易不可用", + "details_copy_block_explorer_link": "複製區塊瀏覽器連結", + "details_copy_note": "複製備註", + "details_copy_txid": "複製交易 ID", + "date": "日期", + "details_view_in_browser": "在瀏覽器中檢視", + "incoming_transaction": "收款交易", + "outgoing_transaction": "付款交易", + "expired_transaction": "已過期交易", + "pending_transaction": "待處理交易", + "offchain": "鏈下", + "onchain": "鏈上", + "pending_with_amount": "待處理 {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "eta_10m": "預計:約 10 分鐘內", + "eta_3h": "預計:約 3 小時內", + "eta_1d": "預計:約 1 天內", + "list_title_sent": "已傳送", + "open_url_error": "無法以預設瀏覽器開啟此連結。請變更您的預設瀏覽器後再試。", + "rbf_explain": "我們將以手續費較高的交易取代此交易,使其更快被礦工打包。此功能稱為 RBF—以手續費取代。", + "watchOnlyWarningTitle": "安全警告", + "watchOnlyWarningDescription": "請注意有些詐騙者會利用「僅觀察」錢包來欺騙使用者。這類錢包無法讓您控制或傳送資金;它們只能讓您檢視結餘。", + "custom_fee_warning_title": "警告", + "custom_fee_warning_description": "低於 1 sat/vB 的手續費仍為有效,但可能因節點政策而無法被轉發。", + "details_eta_analyzing": "分析中…", + "details_sent": "已傳送", + "details_section": "詳情", + "details_explorer": "瀏覽器", + "details_network_fee": "網路手續費", + "details_to_address": "至", + "details_id": "ID", + "details_note": "備註", + "details_add_note": "新增", + "details_advanced": "進階", + "details_fee_rate": "手續費率", + "details_size": "大小", + "details_virtual_size": "虛擬大小", + "details_tx_hex": "交易十六進位", + "details_inputs_count": "輸入 ({count})", + "details_outputs_count": "輸出 ({count})" }, "wallets": { "add_bitcoin": "比特幣", "add_bitcoin_explain": "簡單而強大的比特幣錢包", "add_create": "建立", + "total_balance": "結餘", + "add_entropy": "熵", "add_entropy_generated": "產生熵的{gen}個字節", "add_entropy_provide": "通過擲骰子提供熵", "add_entropy_remain": "產生熵的{gen}個字節。 剩餘的{rem}字節將從系統隨機數生成器中獲得。", "add_import_wallet": "匯入錢包", "add_lightning": "閃電", "add_lightning_explain": "用於即時交易的花費", - "add_lndhub": "連接到您的LNDHub", "add_lndhub_placeholder": "您的節點地址", "add_placeholder": "我的第一個錢包", "add_title": "新增錢包", "add_wallet_name": "名稱", "add_wallet_type": "類型", "clipboard_bitcoin": "您的剪貼簿上有一個比特幣地址。您想使用它來進行交易嗎?", - "clipboard_lightning": "您的剪貼簿上有一張閃電賬單。您想使用它進行交易嗎?", + "clipboard_lightning": "您的剪貼簿上有一張閃電帳單。您想使用它進行交易嗎?", "details_address": "地址", "details_advanced": "進階的", "details_are_you_sure": "你確定嗎?", "details_connected_to": "連接到", - "details_del_wb_err": "提供的結餘與此錢包的結餘不匹配,請再試一遍。", "details_delete": "移除", "details_delete_wallet": "移除錢包", - "details_derivation_path": "推導路徑", - "details_display": "在錢包清單中顯示", + "details_derivation_path": "衍生路徑", "details_export_backup": "匯出備份", "details_master_fingerprint": "主指紋", "details_multisig_type": "多重簽名", - "details_no_cancel": "不,取消", - "details_save": "儲存", "details_show_xpub": "展示錢包公鑰", "details_show_addresses": "顯示地址", "details_title": "錢包", + "wallets": "錢包", "details_type": "類型", "details_use_with_hardware_wallet": "與硬體錢包一起使用", - "details_wallet_updated": "錢包已更新", "details_yes_delete": "是的,刪除", "enter_bip38_password": "輸入密碼進行解密", "export_title": "錢包匯出", @@ -330,38 +446,89 @@ "import_success": "成功", "import_title": "匯入", "import_discovery_title": "探索", - "import_derivation_loading": "讀取中...", - "import_derivation_unknown": "未知", "list_create_a_button": "立即添加", "list_create_a_wallet": "建立一個錢包", - "list_create_a_wallet_text": "這是免費的,您可以建立\n喜歡多少就多少。", "list_empty_txs1": "你的交易將在這裡展示", "list_empty_txs1_lightning": "應使用閃電錢包進行日常交易。費用超便宜而且速度飛快。", "list_empty_txs2": "從你的錢包開始。", - "list_empty_txs2_lightning": "\n要開始使用它,請點擊“管理資金”並增值。", + "list_empty_txs2_lightning": "\n要開始使用它,請點擊「管理資金」並增值。", "list_latest_transaction": "最近的交易", - "list_ln_browser": "LApp瀏覽器", "list_long_choose": "選擇圖片", - "list_long_clipboard": "從剪貼簿複製", + "paste_from_clipboard": "貼上", + "import_file": "匯入檔案", "list_long_scan": "掃描二維碼", "list_title": "錢包", "list_tryagain": "再試一次", - "no_ln_wallet_error": "在繳付閃電賬單之前,必須先添加一個閃電錢包。", + "no_ln_wallet_error": "在繳付閃電帳單之前,必須先添加一個閃電錢包。", "looks_like_bip38": "這看起來像是受密碼保護的私鑰(BIP38)。", - "reorder_title": "重新排列錢包", "please_continue_scanning": "請繼續掃描。", "select_no_bitcoin": "當前沒有可用的比特幣錢包。", - "select_no_bitcoin_exp": "需要一個比特幣錢包來為閃電錢包增值,請建立或導入一個。", + "select_no_bitcoin_exp": "需要一個比特幣錢包來為閃電錢包增值,請建立或匯入一個。", "select_wallet": "選擇錢包", - "xpub_copiedToClipboard": "複製到貼上板.", "pull_to_refresh": "拉動來刷新", - "warning_do_not_disclose": "警告! 不要透露。", "add_ln_wallet_first": "您必須先添加一個閃電錢包。", "identity_pubkey": "身份公鑰", - "xpub_title": "錢包公鑰" + "xpub_title": "錢包公鑰", + "add_entropy_reset_title": "重設熵", + "add_entropy_reset_message": "變更錢包類型將會重設目前的熵。要繼續嗎?", + "add_entropy_bytes": "{bytes} 位元組的熵", + "add_lndhub": "連接到您的 LNDhub", + "add_lndhub_error": "提供的節點地址不是有效的 LNDhub 節點。", + "add_wallet_seed_length": "種子長度", + "add_wallet_seed_length_12": "12 個字", + "add_wallet_seed_length_24": "24 個字", + "clear_clipboard_on_import": "匯入後清除剪貼簿", + "details_del_wb_err": "提供的結餘金額與此錢包的結餘不符,請再試一次。", + "details_del_wb_q": "此錢包有結餘。在繼續之前,請注意若沒有此錢包的助記詞,您將無法復原資金。為避免意外移除,請輸入您錢包的結餘 {balance} 聰。", + "details_display": "顯示於主畫面", + "details_export_history": "匯出紀錄為 CSV", + "swipe_balance_hide": "隱藏", + "swipe_balance_show": "顯示", + "drag_to_reorder": "拖曳以重新排序", + "clear_search": "清除搜尋", + "import_passphrase": "密語", + "import_passphrase_title": "密語", + "import_passphrase_message": "若您有使用密語,請在此輸入", + "import_success_watchonly": "您的錢包已成功匯入。警告:這是僅觀察錢包,您無法從中花費。", + "import_search_accounts": "搜尋帳戶", + "learn_more": "深入了解", + "import_discovery_subtitle": "選擇已探索到的錢包", + "import_discovery_derivation": "使用自訂推導路徑", + "import_discovery_no_wallets": "找不到任何錢包。", + "import_discovery_offline": "BlueWallet 目前處於離線模式。在此模式下,它無法確認錢包是否存在,因此您需要手動選擇正確的錢包", + "import_derivation_found": "已找到", + "import_derivation_found_not": "未找到", + "import_derivation_loading": "載入中…", + "import_derivation_subtitle": "輸入自訂推導路徑,我們將嘗試探索您的錢包。", + "import_derivation_title": "推導路徑", + "import_derivation_unknown": "未知", + "import_wrong_path": "推導路徑錯誤", + "list_create_a_wallet_text": "免費,您可以建立\n任意數量的錢包。", + "manage_title": "管理錢包", + "no_results_found": "找不到結果。", + "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": "複製並安全保存此資訊,以便日後復原您的錢包。", + "manage_wallets_search_placeholder": "搜尋錢包、地址、交易與備註", + "more_info": "更多資訊", + "details_delete_wallet_error_message": "確認此錢包是否已自通知中移除時發生問題—可能是因為網路問題或連線不佳。若您繼續,刪除後仍可能收到與此錢包相關交易的通知。", + "details_delete_anyway": "仍要刪除" + }, + "total_balance_view": { + "title": "結餘", + "display_in_bitcoin": "以比特幣顯示", + "hide": "隱藏", + "display_in_sats": "以聰顯示", + "display_in_fiat": "以 {currency} 顯示", + "explanation": "在總覽畫面檢視您所有錢包的總結餘。" }, "multisig": { - "multisig_vault": "保管庫", + "multisig_vault": "多重簽名保管庫", "default_label": "多重簽名保管庫", "multisig_vault_explain": "大金額需要的最佳安全性", "provide_signature": "提供簽名", @@ -371,25 +538,24 @@ "fee_btc": "{number} 比特幣", "confirm": "確認", "header": "發送", - "share": "分享", "view": "查看", "manage_keys": "管理密鑰", "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": "建立", "native_segwit_title": "最佳做法", - "wrapped_segwit_title": "最佳兼容性", + "wrapped_segwit_title": "最佳相容性", "legacy_title": "舊制式", - "co_sign_transaction": "签署一單交易", - "what_is_vault": "保管库是", - "what_is_vault_numberOfWallets": "{m}-of-{n} 多重签名", + "co_sign_transaction": "簽署一單交易", + "what_is_vault": "保管庫是", + "what_is_vault_numberOfWallets": "{m}-of-{n} 多重簽名", "what_is_vault_wallet": "錢包。", - "vault_advanced_customize": "錢包。", + "vault_advanced_customize": "保管庫設定", "needs": "它需要", "what_is_vault_description_number_of_vault_keys": "{m}個保管庫密鑰", "what_is_vault_description_to_spend": "花費,還有第三個\n可以用作備份的。", @@ -398,35 +564,46 @@ "quorum_header": "法定人數", "of": "的", "wallet_type": "錢包類型", - "invalid_mnemonics": "這個助記短語似乎無效。", "not_a_multisignature_xpub": "這不是來自多重簽署錢包的公鑰!", - "invalid_cosigner_format": "不正確的簽名人:這不是{format}格式的簽名人。", "create_new_key": "建立新的", "scan_or_open_file": "掃描或開啟檔案", "i_have_mnemonics": "我有這個密鑰的種子。", "type_your_mnemonics": "插入種子以匯入現有的保管庫密鑰。", - "this_is_cosigners_xpub": "這是共同簽名者的公鑰,可以導入另一個錢包。 分享是安全的。", "wallet_key_created": "您的保管庫密鑰已建立,花點時間安全地備份您的助記符種子。", "are_you_sure_seed_will_be_lost": "你確定嗎? 如果沒有備份,助記符種子將丟失。", "forget_this_seed": "忘記此種子,而是使用公鑰。", - "view_edit_cosigners": "查看/編輯共同簽名者", - "this_cosigner_is_already_imported": "此共同簽名者已經被匯入。", "export_signed_psbt": "匯出已簽名的PSBT", "input_fp": "輸入指紋", - "input_fp_explain": "跳過以使用默認值(00000000)", - "input_path": "插入推導路徑", - "input_path_explain": "跳過以使用默認值({default})", + "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、Spectre、Coldcard、Cobo Vault等。", + "ms_help_1": "這個保管庫將與其他BlueWallet應用程式和PSBT相容的錢包配合使用,例如Electrum、Spectre、Coldcard、Cobo Vault等。", "ms_help_title2": "編輯密鑰", "ms_help_title3": "保管庫備份", "ms_help_title4": "匯入保管庫", - "ms_help_4": "要匯入多重簽名,請使用您的備份檔案和匯入功能。 如果只有種子和公鑰,則可以在建立保管庫密鑰時使用單獨的“匯入”按鈕。", + "ms_help_4": "要匯入多重簽名,請使用您的備份檔案和匯入功能。 如果只有種子和公鑰,則可以在建立保管庫密鑰時使用單獨的「匯入」按鈕。", "ms_help_title5": "進階模式", - "ms_help_5": "默認情況下,BlueWallet 將生成 2-of-3 保管庫。要建立不同的法定人數或更改地址類型,請在“設置”中激活“進階模式”。" + "ms_help_5": "預設情況下,BlueWallet 將生成 2-of-3 保管庫。要建立不同的法定人數或更改地址類型,請在「設定」中啟用「進階模式」。", + "provide_signature_details": "使用持有此密鑰的裝置與錢包來簽署此交易", + "provide_signature_details_bluewallet": "在 BlueWallet 中,前往「傳送」畫面選單並選擇 ", + "provide_signature_next_steps": "掃描或匯入已簽署的交易", + "provide_signature_next_steps_details": "當您的錢包成功簽署交易後,請掃描提供的 QR 碼或匯入隨附的檔案,然後在廣播之前檢視所有交易詳情。", + "share": "分享…", + "shared_key_detected": "共享共同簽署者", + "shared_key_detected_question": "有人與您共享了一位共同簽署者,您要匯入嗎?", + "invalid_mnemonics": "此助記詞似乎無效。", + "invalid_cosigner": "無效的共同簽署者資料", + "invalid_cosigner_format": "共同簽署者不正確:這不是 {format} 格式的共同簽署者。", + "this_is_cosigners_xpub": "這是共同簽署者的 XPUB—可匯入其他錢包。分享是安全的。", + "this_is_cosigners_xpub_airdrop": "若您以 AirDrop 分享,接收者必須位於協調畫面中。", + "view_edit_cosigners": "檢視/編輯共同簽署者", + "this_cosigner_is_already_imported": "此共同簽署者已匯入。", + "ms_help_2": "您可以在此裝置上建立所有保管庫密鑰,並於日後移除或編輯它們。將所有密鑰存放在同一裝置上的安全性等同於一般的比特幣錢包。", + "ms_help_3": "在錢包選項中,您可以找到保管庫備份與僅觀察備份。此備份就像通往您錢包的地圖,在您遺失其中一個種子時,對於復原錢包至關重要。" }, "is_it_my_address": { "title": "是我的地址嗎?", @@ -434,24 +611,33 @@ "enter_address": "輸入地址", "check_address": "檢查地址", "no_wallet_owns_address": "沒有可用的錢包擁有提供的地址。", - "view_qrcode": "檢視二維條碼" + "view_qrcode": "檢視 QR 碼" }, "cc": { - "change": "更變。", + "change": "找零", "coins_selected": "所選的幣({number})", - "empty": "此錢包目前沒有幣。", "freeze": "凍結", "freezeLabel": "凍結", "freezeLabel_un": "解凍", - "header": "币的控制", + "header": "幣的控制", "use_coin": "使用幣", "use_coins": "使用幣", - "tip": "此功能使您可以查看、標記、凍結或選擇幣,以改善錢包管理。 您可以通過點擊彩色圓圈選擇多個幣。" + "tip": "此功能使您可以查看、標記、凍結或選擇幣,以改善錢包管理。 您可以通過點擊彩色圓圈選擇多個幣。", + "sort_label": "標籤", + "sort_status": "狀態", + "selected_summ": "已選擇 {value}", + "empty": "此錢包目前沒有任何幣。", + "sort_asc": "升序", + "sort_desc": "降序", + "sort_height": "高度", + "sort_value": "金額", + "sort_by": "排序依據" }, "units": { "BTC": "比特幣", "MAX": "最大", - "sats": "聰" + "sats": "聰", + "sat_vbyte": "sat/vByte" }, "addresses": { "sign_sign": "簽署", @@ -460,8 +646,59 @@ "sign_placeholder_message": "訊息", "sign_placeholder_signature": "簽署", "addresses_title": "地址", - "type_change": "改變", + "type_change": "找零", "type_receive": "接收", - "transactions": "交易" + "transactions": "交易", + "copy_private_key": "複製私鑰", + "sensitive_private_key": "警告:私鑰極為敏感。是否繼續?", + "sign_title": "簽署/驗證訊息", + "sign_help": "您可以在此根據比特幣地址建立或驗證密碼學簽名。", + "sign_signature_correct": "驗證成功!", + "sign_signature_incorrect": "驗證失敗!", + "type_used": "已使用" + }, + "autofill_word": { + "title": "種子的最後一個字", + "enter": "輸入您不完整的助記詞", + "generate_word": "產生最後一個字", + "error": "輸入不是 11 字或 23 字的不完整助記詞。請再試一次。" + }, + "lnurl_auth": { + "register_question_part_1": "您要在", + "register_question_part_2": "使用您的 Lightning 錢包註冊帳號嗎?", + "register_answer": "您已成功於 {hostname} 註冊帳號!", + "login_question_part_1": "您要在", + "login_question_part_2": "使用您的 Lightning 錢包登入嗎?", + "login_answer": "您已成功登入 {hostname}!", + "link_question_part_1": "您要將您在", + "link_question_part_2": "的帳號連結到您的 Lightning 錢包嗎?", + "link_answer": "您的 Lightning 錢包已成功連結至您在 {hostname} 的帳號!", + "auth_question_part_1": "您要在", + "auth_question_part_2": "使用您的 Lightning 錢包進行驗證嗎?", + "auth_answer": "您已成功於 {hostname} 進行驗證!", + "could_not_auth": "我們無法在 {hostname} 為您進行驗證。", + "authenticate": "驗證" + }, + "bip47": { + "payment_code": "支付代碼", + "contacts": "聯絡人", + "bip47_explain": "可重複使用且可分享的代碼", + "bip47_explain_subtitle": "BIP47", + "purpose": "可重複使用且可分享的代碼(BIP47)", + "pay_this_contact": "支付給此聯絡人", + "rename_contact": "重新命名聯絡人", + "copy_payment_code": "複製支付代碼", + "hide_contact": "隱藏聯絡人", + "rename": "重新命名", + "provide_name": "為此聯絡人輸入新名稱", + "add_contact": "新增聯絡人", + "provide_payment_code": "提供支付代碼", + "invalid_pc": "無效的支付代碼", + "notification_tx_unconfirmed": "通知交易尚未確認,請稍候", + "failed_create_notif_tx": "無法建立鏈上交易", + "onchain_tx_needed": "需要鏈上交易", + "notif_tx_sent": "通知交易已送出,請等待確認", + "notif_tx": "通知交易", + "not_found": "找不到支付代碼" } } diff --git a/metro.config.js b/metro.config.js index bb53464d2c9..cee5b1e0f5e 100644 --- a/metro.config.js +++ b/metro.config.js @@ -1,27 +1,36 @@ +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); +const path = require('path'); + +const resolveAliases = { + '@arkade-os/sdk/adapters/expo': path.join(__dirname, 'node_modules/@arkade-os/sdk/dist/cjs/adapters/expo.js'), + 'expo/fetch': path.join(__dirname, 'util/expo-fetch.js'), +}; + /** - * Metro configuration for React Native - * https://github.com/facebook/react-native + * Metro configuration + * https://reactnative.dev/docs/metro * - * @format + * @type {import('@react-native/metro-config').MetroConfig} */ -const path = require('path'); -const exclusionList = require('metro-config/src/defaults/exclusionList'); - -module.exports = { +const config = { resolver: { - blockList: exclusionList([ - // This stops "react-native run-windows" from causing the metro server to crash if its already running - new RegExp(`${path.resolve(__dirname, 'windows').replace(/[/\\]/g, '/')}.*`), - // This prevents "react-native run-windows" from hitting: EBUSY: resource busy or locked, open msbuild.ProjectImports.zip - /.*\.ProjectImports\.zip/, - ]), - }, - transformer: { - getTransformOptions: async () => ({ - transform: { - experimentalImportSupport: false, - inlineRequires: true, - }, - }), + extraNodeModules: { + stream: require.resolve('stream-browserify'), + crypto: require.resolve('crypto-browserify'), + net: require.resolve('react-native-tcp-socket'), + tls: require.resolve('react-native-tcp-socket'), + }, + resolveRequest: (context, moduleName, platform) => { + if (resolveAliases[moduleName]) + return { + type: 'sourceFile', + filePath: resolveAliases[moduleName], + }; + + // Fall back to default resolution + return context.resolveRequest(context, moduleName, platform); + }, }, }; + +module.exports = mergeConfig(getDefaultConfig(__dirname), config); diff --git a/models/blockExplorer.ts b/models/blockExplorer.ts new file mode 100644 index 00000000000..7f2189c884f --- /dev/null +++ b/models/blockExplorer.ts @@ -0,0 +1,79 @@ +// blockExplorer.ts +import DefaultPreference from 'react-native-default-preference'; + +export interface BlockExplorer { + key: string; + name: string; + url: string; +} + +export const BLOCK_EXPLORERS: { [key: string]: BlockExplorer } = { + default: { key: 'default', name: 'Mempool.space', url: 'https://mempool.space' }, + blockchair: { key: 'blockchair', name: 'Blockchair', url: 'https://blockchair.com/bitcoin' }, + blockstream: { key: 'blockstream', name: 'Blockstream.info', url: 'https://blockstream.info' }, + custom: { key: 'custom', name: 'Custom', url: '' }, // Custom URL will be handled separately +}; + +export const getBlockExplorersList = (): BlockExplorer[] => { + return Object.values(BLOCK_EXPLORERS); +}; + +export const normalizeUrl = (url: string): string => { + return url.replace(/\/+$/, ''); +}; + +export const isValidUrl = (url: string): boolean => { + const pattern = /^(https?:\/\/)/; + return pattern.test(url); +}; + +export const findMatchingExplorerByDomain = (url: string): BlockExplorer | null => { + const domain = getDomain(url); + for (const explorer of Object.values(BLOCK_EXPLORERS)) { + if (getDomain(explorer.url) === domain) { + return explorer; + } + } + return null; +}; + +export const getDomain = (url: string): string => { + try { + const hostname = new URL(url).hostname; + return hostname.replace(/^www\./, ''); + } catch { + return ''; + } +}; + +const BLOCK_EXPLORER_STORAGE_KEY = 'blockExplorer'; + +export const saveBlockExplorer = async (url: string): Promise => { + try { + await DefaultPreference.set(BLOCK_EXPLORER_STORAGE_KEY, url); + return true; + } catch (error) { + console.error('Error saving block explorer:', error); + return false; + } +}; + +export const removeBlockExplorer = async (): Promise => { + try { + await DefaultPreference.clear(BLOCK_EXPLORER_STORAGE_KEY); + return true; + } catch (error) { + console.error('Error removing block explorer:', error); + return false; + } +}; + +export const getBlockExplorerUrl = async (): Promise => { + try { + const url = (await DefaultPreference.get(BLOCK_EXPLORER_STORAGE_KEY)) as string | null; + return url ?? BLOCK_EXPLORERS.default.url; + } catch (error) { + console.error('Error getting block explorer:', error); + return BLOCK_EXPLORERS.default.url; + } +}; diff --git a/models/fiatUnit.ts b/models/fiatUnit.ts index cb9b019412c..4ed580f86cc 100644 --- a/models/fiatUnit.ts +++ b/models/fiatUnit.ts @@ -1,138 +1,238 @@ +import { fetch } from '../util/fetch'; import untypedFiatUnit from './fiatUnits.json'; export const FiatUnitSource = { + Coinbase: 'Coinbase', CoinDesk: 'CoinDesk', CoinGecko: 'CoinGecko', + Kraken: 'Kraken', Yadio: 'Yadio', YadioConvert: 'YadioConvert', Exir: 'Exir', - wazirx: 'wazirx', + coinpaprika: 'coinpaprika', Bitstamp: 'Bitstamp', + BNR: 'BNR', } as const; +const handleError = (source: string, ticker: string, error: Error) => { + throw new Error( + `Could not update rate for ${ticker} from ${source}\n: ${error.message}. ` + + `\nMake sure the network you're on has access to ${source}.`, + ); +}; + +const fetchRate = async (url: string): Promise => { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); +}; + +interface CoinbaseResponse { + data: { + amount: string; + }; +} + +interface CoinDeskResponse { + [ticker: string]: number; +} + +interface CoinGeckoResponse { + bitcoin: { + [ticker: string]: number; + }; +} + +interface BitstampResponse { + last: string; +} + +interface KrakenResponse { + result: { + [pair: string]: { + c: [string]; + }; + }; +} + +interface YadioResponse { + [ticker: string]: { + price: number; + }; +} + +interface YadioConvertResponse { + rate: number; +} + +interface ExirResponse { + last: string; +} + +interface CoinpaprikaResponse { + quotes: { + [ticker: string]: { + price: number; + }; + }; +} + const RateExtractors = { - CoinDesk: async (ticker: string): Promise => { - let json; + Coinbase: async (ticker: string): Promise => { try { - const res = await fetch(`https://api.coindesk.com/v1/bpi/currentprice/${ticker}.json`); - json = await res.json(); - } catch (e: any) { - throw new Error(`Could not update rate for ${ticker}: ${e.message}`); + const json = (await fetchRate(`https://api.coinbase.com/v2/prices/BTC-${ticker.toUpperCase()}/buy`)) as CoinbaseResponse; + const rate = Number(json?.data?.amount); + if (!(rate >= 0)) throw new Error('Invalid data received'); + return rate; + } catch (error: any) { + handleError('Coinbase', ticker, error); + return undefined as never; } - let rate = json?.bpi?.[ticker]?.rate_float; - if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`); + }, - rate = Number(rate); - if (!(rate >= 0)) throw new Error(`Could not update rate for ${ticker}: data is wrong`); - return rate; + CoinDesk: async (ticker: string): Promise => { + try { + const json = (await fetchRate( + `https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=${ticker.toUpperCase()}`, + )) as CoinDeskResponse; + const rate = json?.[ticker.toUpperCase()]; + if (!(rate >= 0)) throw new Error('Invalid data received'); + return rate; + } catch (error: any) { + handleError('CoinDesk', ticker, error); + return undefined as never; + } }, + CoinGecko: async (ticker: string): Promise => { - let json; try { - const res = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=${ticker.toLowerCase()}`); - json = await res.json(); - } catch (e: any) { - throw new Error(`Could not update rate for ${ticker}: ${e.message}`); + const json = (await fetchRate( + `https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=${ticker.toLowerCase()}`, + )) as CoinGeckoResponse; + const rate = Number(json?.bitcoin?.[ticker.toLowerCase()]); + if (!(rate >= 0)) throw new Error('Invalid data received'); + return rate; + } catch (error: any) { + handleError('CoinGecko', ticker, error); + return undefined as never; } - const rate = json?.bitcoin?.[ticker] || json?.bitcoin?.[ticker.toLowerCase()]; - if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`); - return rate; }, + Bitstamp: async (ticker: string): Promise => { - let json; try { - const res = await fetch(`https://www.bitstamp.net/api/v2/ticker/btc${ticker.toLowerCase()}`); - json = await res.json(); - } catch (e: any) { - throw new Error(`Could not update rate from Bitstamp for ${ticker}: ${e.message}`); + const json = (await fetchRate(`https://www.bitstamp.net/api/v2/ticker/btc${ticker.toLowerCase()}`)) as BitstampResponse; + const rate = Number(json?.last); + if (!(rate >= 0)) throw new Error('Invalid data received'); + return rate; + } catch (error: any) { + handleError('Bitstamp', ticker, error); + return undefined as never; } + }, - if (Array.isArray(json)) { - throw new Error(`Unsupported ticker for Bitstamp: ${ticker}`); + Kraken: async (ticker: string): Promise => { + try { + const json = (await fetchRate(`https://api.kraken.com/0/public/Ticker?pair=XXBTZ${ticker.toUpperCase()}`)) as KrakenResponse; + const rate = Number(json?.result?.[`XXBTZ${ticker.toUpperCase()}`]?.c?.[0]); + if (!(rate >= 0)) throw new Error('Invalid data received'); + return rate; + } catch (error: any) { + handleError('Kraken', ticker, error); + return undefined as never; } + }, - let rate = +json?.last; - if (!rate) throw new Error(`Could not update rate from Bitstamp for ${ticker}: data is wrong`); - - rate = Number(rate); - if (!(rate >= 0)) throw new Error(`Could not update rate from Bitstamp for ${ticker}: data is wrong`); - return rate; + BNR: async (): Promise => { + try { + // Fetching USD to RON rate + + const xmlData = await (await fetch('https://www.bnr.ro/nbrfxrates.xml')).text(); + const matches = xmlData.match(/([\d.]+)<\/Rate>/); + if (matches && matches[1]) { + const usdToRonRate = parseFloat(matches[1]); + const btcToUsdRate = await RateExtractors.CoinGecko('USD'); + // Convert BTC to RON using the USD to RON exchange rate + return btcToUsdRate * usdToRonRate; + } + throw new Error('No valid USD to RON rate found'); + } catch (error: any) { + handleError('BNR', 'RON', error); + return undefined as never; + } }, Yadio: async (ticker: string): Promise => { - let json; try { - const res = await fetch(`https://api.yadio.io/json/${ticker}`); - json = await res.json(); - } catch (e: any) { - throw new Error(`Could not update rate for ${ticker}: ${e.message}`); + const json = (await fetchRate(`https://api.yadio.io/json/${ticker}`)) as YadioResponse; + const rate = Number(json?.[ticker]?.price); + if (!(rate >= 0)) throw new Error('Invalid data received'); + return rate; + } catch (error: any) { + handleError('Yadio', ticker, error); + return undefined as never; } - let rate = json?.[ticker]?.price; - if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`); - - rate = Number(rate); - if (!(rate >= 0)) throw new Error(`Could not update rate for ${ticker}: data is wrong`); - return rate; }, YadioConvert: async (ticker: string): Promise => { - let json; try { - const res = await fetch(`https://api.yadio.io/convert/1/BTC/${ticker}`); - json = await res.json(); - } catch (e: any) { - throw new Error(`Could not update rate for ${ticker}: ${e.message}`); + const json = (await fetchRate(`https://api.yadio.io/convert/1/BTC/${ticker}`)) as YadioConvertResponse; + const rate = Number(json?.rate); + if (!(rate >= 0)) throw new Error('Invalid data received'); + return rate; + } catch (error: any) { + handleError('YadioConvert', ticker, error); + return undefined as never; } - let rate = json?.rate; - if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`); - - rate = Number(rate); - if (!(rate >= 0)) throw new Error(`Could not update rate for ${ticker}: data is wrong`); - return rate; }, Exir: async (ticker: string): Promise => { - let json; try { - const res = await fetch('https://api.exir.io/v1/ticker?symbol=btc-irt'); - json = await res.json(); - } catch (e: any) { - throw new Error(`Could not update rate for ${ticker}: ${e.message}`); + const json = (await fetchRate('https://api.exir.io/v1/ticker?symbol=btc-irt')) as ExirResponse; + const rate = Number(json?.last); + if (!(rate >= 0)) throw new Error('Invalid data received'); + return rate; + } catch (error: any) { + handleError('Exir', ticker, error); + return undefined as never; } - let rate = json?.last; - if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`); - - rate = Number(rate); - if (!(rate >= 0)) throw new Error(`Could not update rate for ${ticker}: data is wrong`); - return rate; }, - wazirx: async (ticker: string): Promise => { - let json; + coinpaprika: async (ticker: string): Promise => { try { - const res = await fetch(`https://api.wazirx.com/api/v2/tickers/btcinr`); - json = await res.json(); - } catch (e: any) { - throw new Error(`Could not update rate for ${ticker}: ${e.message}`); + const json = (await fetchRate('https://api.coinpaprika.com/v1/tickers/btc-bitcoin?quotes=INR')) as CoinpaprikaResponse; + const rate = Number(json?.quotes?.INR?.price); + if (!(rate >= 0)) throw new Error('Invalid data received'); + return rate; + } catch (error: any) { + handleError('coinpaprika', ticker, error); + return undefined as never; } - let rate = json?.ticker?.buy; - if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`); - - rate = Number(rate); - if (!(rate >= 0)) throw new Error(`Could not update rate for ${ticker}: data is wrong`); - return rate; }, } as const; -type FiatUnit = { - [key: string]: { - endPointKey: string; - symbol: string; - locale: string; - source: 'CoinDesk' | 'Yadio' | 'Exir' | 'wazirx' | 'Bitstamp'; - }; +export type TFiatUnit = { + endPointKey: string; + symbol: string; + locale: string; + country: string; + source: 'Coinbase' | 'CoinDesk' | 'Yadio' | 'Exir' | 'coinpaprika' | 'Bitstamp' | 'Kraken'; +}; + +export type TFiatUnits = { + [key: string]: TFiatUnit; +}; + +export const FiatUnit = untypedFiatUnit as TFiatUnits; + +export type FiatUnitType = { + endPointKey: string; + symbol: string; + locale: string; + country: string; + source: keyof typeof FiatUnitSource; }; -export const FiatUnit = untypedFiatUnit as FiatUnit; export async function getFiatRate(ticker: string): Promise { return await RateExtractors[FiatUnit[ticker].source](ticker); diff --git a/models/fiatUnits.json b/models/fiatUnits.json index 312f2c45666..cb113540610 100644 --- a/models/fiatUnits.json +++ b/models/fiatUnits.json @@ -2,331 +2,442 @@ "USD": { "endPointKey": "USD", "locale": "en-US", - "source": "Bitstamp", - "symbol": "$" + "source": "Kraken", + "symbol": "$", + "country": "United States (US Dollar)" }, "AED": { "endPointKey": "AED", "locale": "ar-AE", "source": "CoinGecko", - "symbol": "د.إ." + "symbol": "د.إ.", + "country": "United Arab Emirates (UAE Dirham)" + }, + "AMD": { + "endPointKey": "AMD", + "locale": "hy-AM", + "source": "Coinbase", + "symbol": "֏", + "country": "Armenia (Armenian Dram)" }, "ANG": { "endPointKey": "ANG", "locale": "en-SX", - "source": "CoinDesk", - "symbol": "ƒ" + "source": "YadioConvert", + "symbol": "ƒ", + "country": "Sint Maarten (Netherlands Antillean Guilder)" }, "ARS": { "endPointKey": "ARS", "locale": "es-AR", "source": "Yadio", - "symbol": "$" + "symbol": "$", + "country": "Argentina (Argentine Peso)" }, "AUD": { "endPointKey": "AUD", "locale": "en-AU", "source": "CoinGecko", - "symbol": "$" + "symbol": "$", + "country": "Australia (Australian Dollar)" }, "AWG": { "endPointKey": "AWG", "locale": "nl-AW", - "source": "CoinDesk", - "symbol": "ƒ" + "source": "Coinbase", + "symbol": "ƒ", + "country": "Aruba (Aruban Florin)" }, "BHD": { "endPointKey": "BHD", "locale": "ar-BH", "source": "CoinGecko", - "symbol": "د.ب." + "symbol": "د.ب.", + "country": "Bahrain (Bahraini Dinar)" }, "BRL": { "endPointKey": "BRL", "locale": "pt-BR", "source": "CoinGecko", - "symbol": "R$" + "symbol": "R$", + "country": "Brazil (Brazilian Real)" }, "CAD": { "endPointKey": "CAD", "locale": "en-CA", "source": "CoinGecko", - "symbol": "$" + "symbol": "$", + "country": "Canada (Canadian Dollar)" }, "CHF": { "endPointKey": "CHF", "locale": "de-CH", "source": "CoinGecko", - "symbol": "CHF" + "symbol": "CHF", + "country": "Switzerland (Swiss Franc)" }, "CLP": { "endPointKey": "CLP", "locale": "es-CL", "source": "Yadio", - "symbol": "$" + "symbol": "$", + "country": "Chile (Chilean Peso)" }, "CNY": { "endPointKey": "CNY", "locale": "zh-CN", - "source": "CoinDesk", - "symbol": "¥" + "source": "Coinbase", + "symbol": "¥", + "country": "China (Chinese Yuan)" }, "COP": { "endPointKey": "COP", "locale": "es-CO", "source": "CoinDesk", - "symbol": "$" + "symbol": "$", + "country": "Colombia (Colombian Peso)" }, "CZK": { "endPointKey": "CZK", "locale": "cs-CZ", "source": "CoinGecko", - "symbol": "Kč" + "symbol": "Kč", + "country": "Czech Republic (Czech Koruna)" }, "DKK": { "endPointKey": "DKK", "locale": "da-DK", "source": "CoinGecko", - "symbol": "kr" + "symbol": "kr", + "country": "Denmark (Danish Krone)" + }, + "EGP": { + "endPointKey": "EGP", + "locale": "ar-EG", + "source": "YadioConvert", + "symbol": "ج.م.", + "country": "Egypt (Egyptian Pound)" }, "EUR": { "endPointKey": "EUR", "locale": "en-IE", - "source": "Bitstamp", - "symbol": "€" + "source": "Kraken", + "symbol": "€", + "country": "European Union (Euro)" }, "GBP": { "endPointKey": "GBP", "locale": "en-GB", - "source": "Bitstamp", - "symbol": "£" + "source": "Kraken", + "symbol": "£", + "country": "United Kingdom (British Pound)" + }, + "HKD": { + "endPointKey": "HKD", + "locale": "zh-HK", + "source": "CoinGecko", + "symbol": "HK$", + "country": "Hong Kong (Hong Kong Dollar)" }, "HRK": { "endPointKey": "HRK", "locale": "hr-HR", - "source": "CoinDesk", - "symbol": "HRK" + "source": "Coinbase", + "symbol": "HRK", + "country": "Croatia (Croatian Kuna)" }, "HUF": { "endPointKey": "HUF", "locale": "hu-HU", "source": "CoinGecko", - "symbol": "Ft" + "symbol": "Ft", + "country": "Hungary (Hungarian Forint)" }, "IDR": { "endPointKey": "IDR", "locale": "id-ID", "source": "CoinGecko", - "symbol": "Rp" + "symbol": "Rp", + "country": "Indonesia (Indonesian Rupiah)" }, "ILS": { "endPointKey": "ILS", "locale": "he-IL", "source": "CoinGecko", - "symbol": "₪" + "symbol": "₪", + "country": "Israel (Israeli New Shekel)" }, "INR": { "endPointKey": "INR", - "locale": "hi-HN", - "source": "wazirx", - "symbol": "₹" + "locale": "hi-IN", + "source": "coinpaprika", + "symbol": "₹", + "country": "India (Indian Rupee)" }, "IRR": { "endPointKey": "IRR", "locale": "fa-IR", "source": "Exir", - "symbol": "﷼" + "symbol": "﷼", + "country": "Iran (Iranian Rial)" }, "IRT": { "endPointKey": "IRT", "locale": "fa-IR", "source": "Exir", - "symbol": "تومان" + "symbol": "تومان", + "country": "Iran (Iranian Toman)" }, "ISK": { "endPointKey": "ISK", "locale": "is-IS", - "source": "CoinDesk", - "symbol": "kr" + "source": "Coinbase", + "symbol": "kr", + "country": "Iceland (Icelandic Króna)" }, "JPY": { "endPointKey": "JPY", "locale": "ja-JP", "source": "CoinGecko", - "symbol": "¥" + "symbol": "¥", + "country": "Japan (Japanese Yen)" }, "KES": { "endPointKey": "KES", "locale": "en-KE", "source": "CoinDesk", - "symbol": "Ksh" + "symbol": "Ksh", + "country": "Kenya (Kenyan Shilling)" }, "KRW": { "endPointKey": "KRW", "locale": "ko-KR", "source": "CoinGecko", - "symbol": "₩" + "symbol": "₩", + "country": "South Korea (South Korean Won)" }, "KWD": { "endPointKey": "KWD", "locale": "ar-KW", "source": "CoinGecko", - "symbol": "د.ك." + "symbol": "د.ك.", + "country": "Kuwait (Kuwaiti Dinar)" }, "LBP": { "endPointKey": "LBP", "locale": "ar-LB", "source": "YadioConvert", - "symbol": "ل.ل." + "symbol": "ل.ل.", + "country": "Lebanon (Lebanese Pound)" }, "LKR": { "endPointKey": "LKR", "locale": "si-LK", "source": "CoinGecko", - "symbol": "රු." + "symbol": "රු.", + "country": "Sri Lanka (Sri Lankan Rupee)" }, "MXN": { "endPointKey": "MXN", "locale": "es-MX", "source": "CoinGecko", - "symbol": "$" + "symbol": "$", + "country": "Mexico (Mexican Peso)" }, "MYR": { "endPointKey": "MYR", "locale": "ms-MY", "source": "CoinGecko", - "symbol": "RM" + "symbol": "RM", + "country": "Malaysia (Malaysian Ringgit)" }, "MZN": { "endPointKey": "MZN", "locale": "seh-MZ", - "source": "CoinDesk", - "symbol": "MTn" + "source": "Coinbase", + "symbol": "MTn", + "country": "Mozambique (Mozambican Metical)" }, "NGN": { "endPointKey": "NGN", "locale": "en-NG", "source": "CoinGecko", - "symbol": "₦" + "symbol": "₦", + "country": "Nigeria (Nigerian Naira)" }, "NOK": { "endPointKey": "NOK", "locale": "nb-NO", "source": "CoinGecko", - "symbol": "kr" + "symbol": "kr", + "country": "Norway (Norwegian Krone)" }, "NZD": { "endPointKey": "NZD", "locale": "en-NZ", "source": "CoinGecko", - "symbol": "$" + "symbol": "$", + "country": "New Zealand (New Zealand Dollar)" }, "OMR": { "endPointKey": "OMR", "locale": "ar-OM", - "source": "CoinDesk", - "symbol": "ر.ع." + "source": "Coinbase", + "symbol": "ر.ع.", + "country": "Oman (Omani Rial)" }, "PHP": { "endPointKey": "PHP", "locale": "en-PH", "source": "CoinGecko", - "symbol": "₱" + "symbol": "₱", + "country": "Philippines (Philippine Peso)" }, "PLN": { "endPointKey": "PLN", "locale": "pl-PL", "source": "CoinGecko", - "symbol": "zł" + "symbol": "zł", + "country": "Poland (Polish Zloty)" + }, + "PYG": { + "endPointKey": "PYG", + "locale": "es-PY", + "source": "Coinbase", + "symbol": "₲", + "country": "Paraguay (Paraguayan Guarani)" }, "QAR": { "endPointKey": "QAR", "locale": "ar-QA", - "source": "CoinDesk", - "symbol": "ر.ق." + "source": "Coinbase", + "symbol": "ر.ق.", + "country": "Qatar (Qatari Riyal)" + }, + "RON": { + "endPointKey": "RON", + "locale": "ro-RO", + "source": "BNR", + "symbol": "lei", + "country": "Romania (Romanian Leu)" + }, + "RSD": { + "endPointKey": "RSD", + "locale": "sr-RS", + "source": "Coinbase", + "symbol": "DIN", + "country": "Serbia (Serbian Dinar)" }, "RUB": { "endPointKey": "RUB", "locale": "ru-RU", "source": "CoinGecko", - "symbol": "₽" + "symbol": "₽", + "country": "Russia (Russian Ruble)" }, "SAR": { "endPointKey": "SAR", "locale": "ar-SA", "source": "CoinGecko", - "symbol": "ر.س." + "symbol": "ر.س.", + "country": "Saudi Arabia (Saudi Riyal)" }, "SEK": { "endPointKey": "SEK", "locale": "sv-SE", "source": "CoinGecko", - "symbol": "kr" + "symbol": "kr", + "country": "Sweden (Swedish Krona)" }, "SGD": { "endPointKey": "SGD", "locale": "zh-SG", "source": "CoinGecko", - "symbol": "S$" + "symbol": "S$", + "country": "Singapore (Singapore Dollar)" }, "THB": { "endPointKey": "THB", "locale": "th-TH", "source": "CoinGecko", - "symbol": "฿" + "symbol": "฿", + "country": "Thailand (Thai Baht)" }, "TRY": { "endPointKey": "TRY", "locale": "tr-TR", "source": "CoinGecko", - "symbol": "₺" + "symbol": "₺", + "country": "Turkey (Turkish Lira)" }, "TWD": { "endPointKey": "TWD", "locale": "zh-Hant-TW", "source": "CoinGecko", - "symbol": "NT$" + "symbol": "NT$", + "country": "Taiwan (New Taiwan Dollar)" }, "TZS": { "endPointKey": "TZS", "locale": "en-TZ", - "source": "CoinDesk", - "symbol": "TSh" + "source": "Coinbase", + "symbol": "TSh", + "country": "Tanzania (Tanzanian Shilling)" }, "UAH": { "endPointKey": "UAH", "locale": "uk-UA", "source": "CoinGecko", - "symbol": "₴" + "symbol": "₴", + "country": "Ukraine (Ukrainian Hryvnia)" }, "UGX": { "endPointKey": "UGX", "locale": "en-UG", - "source": "CoinDesk", - "symbol": "USh" + "source": "Coinbase", + "symbol": "USh", + "country": "Uganda (Ugandan Shilling)" }, "UYU": { "endPointKey": "UYU", "locale": "es-UY", - "source": "CoinDesk", - "symbol": "$" + "source": "Coinbase", + "symbol": "$", + "country": "Uruguay (Uruguayan Peso)" }, "VEF": { "endPointKey": "VEF", "locale": "es-VE", "source": "CoinGecko", - "symbol": "Bs." + "symbol": "Bs.", + "country": "Venezuela (Venezuelan Bolívar Fuerte)" }, "VES": { "endPointKey": "VES", "locale": "es-VE", "source": "Yadio", - "symbol": "Bs." + "symbol": "Bs.", + "country": "Venezuela (Venezuelan Bolívar Soberano)" + }, + "XAF": { + "endPointKey": "XAF", + "locale": "fr-CF", + "source": "Coinbase", + "symbol": "Fr", + "country": "Central African Republic (Central African Franc)" }, "ZAR": { "endPointKey": "ZAR", "locale": "en-ZA", "source": "CoinGecko", - "symbol": "R" + "symbol": "R", + "country": "South Africa (South African Rand)" + }, + "GHS": { + "endPointKey": "GHS", + "locale": "en-GH", + "source": "Coinbase", + "symbol": "₵", + "country": "Ghana (Ghanaian Cedi)" } -} +} \ No newline at end of file diff --git a/models/itemTypes.ts b/models/itemTypes.ts new file mode 100644 index 00000000000..5c0bcb19ca6 --- /dev/null +++ b/models/itemTypes.ts @@ -0,0 +1,13 @@ +export enum ItemType { + WalletSection = 'wallet', + TransactionSection = 'transaction', + AddressSection = 'address', + WalletGroupSection = 'walletGroup', +} + +export interface AddressItemData { + address: string; + walletID: string; + index: number; + isInternal: boolean; +} diff --git a/models/networkTransactionFees.js b/models/networkTransactionFees.js deleted file mode 100644 index ce1ee5a81bd..00000000000 --- a/models/networkTransactionFees.js +++ /dev/null @@ -1,44 +0,0 @@ -const BlueElectrum = require('../blue_modules/BlueElectrum'); - -export const NetworkTransactionFeeType = Object.freeze({ - FAST: 'Fast', - MEDIUM: 'MEDIUM', - SLOW: 'SLOW', - CUSTOM: 'CUSTOM', -}); - -export class NetworkTransactionFee { - static StorageKey = 'NetworkTransactionFee'; - - constructor(fastestFee = 2, mediumFee = 1, slowFee = 1) { - this.fastestFee = fastestFee; - this.mediumFee = mediumFee; - this.slowFee = slowFee; - } -} - -export default class NetworkTransactionFees { - static recommendedFees() { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async resolve => { - try { - const isDisabled = await BlueElectrum.isDisabled(); - if (isDisabled) { - throw new Error('Electrum is disabled. Dont attempt to fetch fees'); - } - const response = await BlueElectrum.estimateFees(); - if (typeof response === 'object') { - const networkFee = new NetworkTransactionFee(response.fast, response.medium, response.slow); - resolve(networkFee); - } else { - const networkFee = new NetworkTransactionFee(2, 1, 1); - resolve(networkFee); - } - } catch (err) { - console.warn(err); - const networkFee = new NetworkTransactionFee(2, 1, 1); - resolve(networkFee); - } - }); - } -} diff --git a/models/networkTransactionFees.ts b/models/networkTransactionFees.ts new file mode 100644 index 00000000000..1dd205ded3c --- /dev/null +++ b/models/networkTransactionFees.ts @@ -0,0 +1,42 @@ +import * as BlueElectrum from '../blue_modules/BlueElectrum'; + +export enum NetworkTransactionFeeType { + FAST = 'Fast', + MEDIUM = 'MEDIUM', + SLOW = 'SLOW', + CUSTOM = 'CUSTOM', +} + +export class NetworkTransactionFee { + static StorageKey = 'NetworkTransactionFee'; + + public fastestFee: number; + public mediumFee: number; + public slowFee: number; + + constructor(fastestFee = 2, mediumFee = 1, slowFee = 1) { + this.fastestFee = fastestFee; + this.mediumFee = mediumFee; + this.slowFee = slowFee; + } +} + +export default class NetworkTransactionFees { + static async recommendedFees(): Promise { + try { + const isDisabled = await BlueElectrum.isDisabled(); + if (isDisabled) { + throw new Error('Electrum is disabled. Dont attempt to fetch fees'); + } + const response = await BlueElectrum.estimateFees(); + if (response.fast === response.medium) { + // exception, if fees are equal lets bump priority fee + 1 so actual priority tx is above the rest + return new NetworkTransactionFee(response.fast + 1, response.medium, response.slow); + } + return new NetworkTransactionFee(response.fast, response.medium, response.slow); + } catch (err) { + console.warn(err); + return new NetworkTransactionFee(2, 1, 1); + } + } +} diff --git a/navigation/AddWalletStack.tsx b/navigation/AddWalletStack.tsx new file mode 100644 index 00000000000..e293843e180 --- /dev/null +++ b/navigation/AddWalletStack.tsx @@ -0,0 +1,255 @@ +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import React, { lazy } from 'react'; +import { Platform } from 'react-native'; + +import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { withLazySuspense } from './LazyLoadingIndicator'; +import { ScanQRCodeParamList } from './DetailViewStackParamList'; + +export type AddWalletStackParamList = { + AddWallet: { + entropy?: string; + words?: number; + }; + ImportWallet?: { + label?: string; + triggerImport?: boolean; + onBarScanned?: string; + }; + ImportWalletDiscovery: { + importText: string; + askPassphrase: boolean; + searchAccounts: boolean; + }; + ImportSpeed: undefined; + ImportCustomDerivationPath: { + importText: string; + password: string | undefined; + }; + PleaseBackup: { + walletID: string; + }; + PleaseBackupLNDHub: { + walletID: string; + }; + ProvideEntropy: { + words: number; + entropy?: string; + }; + WalletsAddMultisig: { + walletLabel: string; + }; + MultisigAdvanced: { + m: number; + n: number; + format: string; + onSave: (m: number, n: number, format: string) => void; + }; + WalletsAddMultisigStep2: { + m: number; + n: number; + walletLabel: string; + format: string; + onBarScanned?: string; + sheetAction?: string; + sheetImportText?: string; + sheetAskPassphrase?: boolean; + }; + WalletsAddMultisigVaultKeySheet: { + keyIndex: number; + seed: string; + }; + WalletsAddMultisigProvideMnemonicsSheet: { + importText: string; + askPassphrase: boolean; + }; + WalletsAddMultisigCosignerXpubSheet: { + cosignerXpub: string; + cosignerXpubURv2: string; + cosignerXpubFilename: string; + }; + WalletsAddMultisigHelp: undefined; + ScanQRCode: ScanQRCodeParamList; +}; + +const Stack = createNativeStackNavigator(); + +const WalletsAdd = lazy(() => import('../screen/wallets/Add')); +const ImportCustomDerivationPath = lazy(() => import('../screen/wallets/ImportCustomDerivationPath')); +const ImportWalletDiscovery = lazy(() => import('../screen/wallets/ImportWalletDiscovery')); +const ImportSpeed = lazy(() => import('../screen/wallets/ImportSpeed')); +const ImportWallet = lazy(() => import('../screen/wallets/ImportWallet')); +const PleaseBackup = lazy(() => import('../screen/wallets/PleaseBackup')); +const PleaseBackupLNDHub = lazy(() => import('../screen/wallets/pleaseBackupLNDHub')); +const ProvideEntropy = lazy(() => import('../screen/wallets/ProvideEntropy')); +const WalletsAddMultisig = lazy(() => import('../screen/wallets/WalletsAddMultisig')); +const MultisigAdvanced = lazy(() => import('../screen/wallets/MultisigAdvanced')); +const WalletsAddMultisigStep2 = lazy(() => import('../screen/wallets/addMultisigStep2')); +const WalletsAddMultisigHelp = lazy(() => import('../screen/wallets/addMultisigHelp')); +const WalletsAddMultisigVaultKeySheet = lazy(() => import('../screen/wallets/WalletsAddMultisigVaultKeySheet')); +const WalletsAddMultisigProvideMnemonicsSheet = lazy(() => import('../screen/wallets/WalletsAddMultisigProvideMnemonicsSheet')); +const WalletsAddMultisigCosignerXpubSheet = lazy(() => import('../screen/wallets/WalletsAddMultisigCosignerXpubSheet')); +const ScanQRCode = lazy(() => import('../screen/send/ScanQRCode')); + +const AddComponent = withLazySuspense(WalletsAdd); +const ImportWalletDiscoveryComponent = withLazySuspense(ImportWalletDiscovery); +const ImportCustomDerivationPathComponent = withLazySuspense(ImportCustomDerivationPath); +const ImportWalletComponent = withLazySuspense(ImportWallet); +const ImportSpeedComponent = withLazySuspense(ImportSpeed); +const PleaseBackupComponent = withLazySuspense(PleaseBackup); +const PleaseBackupLNDHubComponent = withLazySuspense(PleaseBackupLNDHub); +const ProvideEntropyComponent = withLazySuspense(ProvideEntropy); +const WalletsAddMultisigComponent = withLazySuspense(WalletsAddMultisig); +const MultisigAdvancedComponent = withLazySuspense(MultisigAdvanced); +const WalletsAddMultisigStep2Component = withLazySuspense(WalletsAddMultisigStep2); +const WalletsAddMultisigHelpComponent = withLazySuspense(WalletsAddMultisigHelp); +const WalletsAddMultisigVaultKeySheetComponent = withLazySuspense(WalletsAddMultisigVaultKeySheet); +const WalletsAddMultisigProvideMnemonicsSheetComponent = withLazySuspense(WalletsAddMultisigProvideMnemonicsSheet); +const WalletsAddMultisigCosignerXpubSheetComponent = withLazySuspense(WalletsAddMultisigCosignerXpubSheet); +const ScanQRCodeComponent = withLazySuspense(ScanQRCode); +const multisigSheetAllowedDetents = Platform.OS === 'ios' ? 'fitToContents' : [0.9]; + +const AddWalletStack = () => { + const theme = useTheme(); + return ( + + + + + + + + + + + + + + + + + + + ); +}; + +export default AddWalletStack; diff --git a/navigation/AztecoRedeemStack.tsx b/navigation/AztecoRedeemStack.tsx new file mode 100644 index 00000000000..237ffad9fa9 --- /dev/null +++ b/navigation/AztecoRedeemStack.tsx @@ -0,0 +1,39 @@ +import React, { lazy } from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import navigationStyle from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { withLazySuspense } from './LazyLoadingIndicator'; + +const Stack = createNativeStackNavigator(); + +const AztecoRedeem = lazy(() => import('../screen/receive/AztecoRedeem')); +const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet')); + +const AztecoRedeemComponent = withLazySuspense(AztecoRedeem); +const SelectWalletComponent = withLazySuspense(SelectWallet); + +const AztecoRedeemStackRoot = () => { + const theme = useTheme(); + + return ( + + + + + ); +}; + +export default AztecoRedeemStackRoot; diff --git a/navigation/ConnectionPollContext.ts b/navigation/ConnectionPollContext.ts new file mode 100644 index 00000000000..87229789283 --- /dev/null +++ b/navigation/ConnectionPollContext.ts @@ -0,0 +1,3 @@ +import React from 'react'; + +export const ConnectionPollContext = React.createContext<{ pollConnection: () => void } | null>(null); diff --git a/navigation/DetailViewScreensStack.tsx b/navigation/DetailViewScreensStack.tsx new file mode 100644 index 00000000000..c0cf78eae35 --- /dev/null +++ b/navigation/DetailViewScreensStack.tsx @@ -0,0 +1,574 @@ +import React, { lazy, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Animated, AppState, View, Platform, PlatformColor, Text, StyleSheet, Pressable } from 'react-native'; +import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; +import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import { useExtendedNavigation } from '../hooks/useExtendedNavigation'; +import loc from '../loc'; +import LNDViewAdditionalInvoicePreImage from '../screen/lnd/lndViewAdditionalInvoicePreImage'; +import LNDViewInvoice from '../screen/lnd/lndViewInvoice'; +import LnurlAuth from '../screen/lnd/lnurlAuth'; +import LnurlPay from '../screen/lnd/lnurlPay'; +import LnurlPaySuccess from '../screen/lnd/lnurlPaySuccess'; +import Broadcast from '../screen/send/Broadcast'; +import IsItMyAddress from '../screen/settings/IsItMyAddress'; +import Success from '../screen/send/success'; +import CPFP from '../screen/transactions/CPFP'; +import RBFBumpFee from '../screen/transactions/RBFBumpFee'; +import RBFCancel from '../screen/transactions/RBFCancel'; +import TransactionStatus from '../screen/transactions/TransactionStatus'; +import WalletAddresses from '../screen/wallets/WalletAddresses'; +import WalletDetails from '../screen/wallets/WalletDetails'; +import GenerateWord from '../screen/wallets/generateWord'; +import SelectWallet from '../screen/wallets/SelectWallet'; +import WalletsList from '../screen/wallets/WalletsList'; +import { DetailViewStack } from './index'; +import { withLazySuspense } from './LazyLoadingIndicator'; +import Icon from '../components/Icon'; +import SettingsButton from '../components/icons/SettingsButton'; +import { useSettings } from '../hooks/context/useSettings'; +import { useStorage } from '../hooks/context/useStorage'; +import { WalletTransactionsStatus } from '../components/Context/StorageProvider'; +import WalletTransactions from '../screen/wallets/WalletTransactions'; +import AddWalletButton from '../components/AddWalletButton'; +import Settings from '../screen/settings/Settings'; +import Currency from '../screen/settings/Currency'; +import GeneralSettings from '../screen/settings/GeneralSettings'; +import PlausibleDeniability from '../screen/PlausibleDeniability'; +import Licensing from '../screen/settings/Licensing'; +import NetworkSettings from '../screen/settings/NetworkSettings'; +import SettingsBlockExplorer from '../screen/settings/SettingsBlockExplorer'; +import About from '../screen/settings/About'; +// import DefaultView from '../screen/settings/DefaultView'; // Commented out - not accessible from UI +import ElectrumSettings from '../screen/settings/ElectrumSettings'; +import EncryptStorage from '../screen/settings/EncryptStorage'; +import Language from '../screen/settings/Language'; +import LightningSettings from '../screen/settings/LightningSettings'; +import NotificationSettings from '../screen/settings/NotificationSettings'; +import SelfTest from '../screen/settings/SelfTest'; +import ReleaseNotes from '../screen/settings/ReleaseNotes'; +import SettingsTools from '../screen/settings/SettingsTools'; +import PromptPasswordConfirmationSheet from '../screen/PromptPasswordConfirmationSheet'; +import { useSizeClass, SizeClass } from '../blue_modules/sizeClass'; +import getWalletTransactionsOptions from './helpers/getWalletTransactionsOptions'; +import { isDesktop } from '../blue_modules/environment'; +import * as BlueElectrum from '../blue_modules/BlueElectrum'; +import { ConnectionPollContext } from './ConnectionPollContext'; +import ManageWallets from '../screen/wallets/ManageWallets'; +import ReceiveDetails from '../screen/receive/ReceiveDetails'; +import ReceiveCustomAmountSheet from '../screen/receive/ReceiveCustomAmountSheet'; + +const PaymentCodesList = lazy(() => import('../screen/wallets/PaymentCodesList')); +const PaymentCodesListComponent = withLazySuspense(PaymentCodesList); + +const UpdatingLabel: React.FC<{ containerStyle: object; textStyle: object }> = ({ containerStyle, textStyle }) => { + const opacity = useRef(new Animated.Value(1)).current; + + useEffect(() => { + const pulse = Animated.loop( + Animated.sequence([ + Animated.timing(opacity, { + toValue: 0.55, + duration: 600, + useNativeDriver: true, + }), + Animated.timing(opacity, { + toValue: 1, + duration: 600, + useNativeDriver: true, + }), + ]), + ); + pulse.start(); + return () => pulse.stop(); + }, [opacity]); + + return ( + + {loc.transactions.updating} + + ); +}; + +const DetailViewStackScreensStack = () => { + const theme = useTheme(); + const navigation = useExtendedNavigation(); + const { walletTransactionUpdateStatus } = useStorage(); + const { isElectrumDisabled } = useSettings(); + const { sizeClass } = useSizeClass(); + const [electrumConnected, setElectrumConnected] = useState(null); + + const pollConnection = useCallback(async () => { + if (isElectrumDisabled) return; + const ok = await BlueElectrum.ping(); + setElectrumConnected(ok); + }, [isElectrumDisabled]); + + useEffect(() => { + if (isElectrumDisabled) { + setElectrumConnected(null); + return; + } + pollConnection(); + }, [isElectrumDisabled, pollConnection]); + + useEffect(() => { + if (isElectrumDisabled) return; + const subscription = AppState.addEventListener('change', nextState => { + if (nextState === 'active') { + pollConnection(); + } + }); + return () => subscription.remove(); + }, [isElectrumDisabled, pollConnection]); + // When starting up in an unknown state, we optimistically rely on ping() + // and the fast retry loop while disconnected. Slow health checks while connected + // run only from WalletsList when that screen is focused (saves idle battery). + + useEffect(() => { + if (isElectrumDisabled || electrumConnected !== false) return; + const interval = setInterval(pollConnection, 3000); + return () => clearInterval(interval); + }, [isElectrumDisabled, electrumConnected, pollConnection]); + + const connectionPollContextValue = useMemo(() => ({ pollConnection }), [pollConnection]); + + const navigateToAddWallet = useCallback(() => { + navigation.navigate('AddWalletRoot'); + }, [navigation]); + + const RightBarButtons = useMemo( + () => + sizeClass === SizeClass.Large ? ( + + ) : ( + <> + + + + + ), + [sizeClass, navigateToAddWallet], + ); + + const navigateToElectrumSettings = useCallback(() => { + const routeNames = navigation.getState()?.routeNames; + if (routeNames?.includes('ElectrumSettings')) { + navigation.navigate('ElectrumSettings'); + } else { + navigation.navigate('DetailViewStackScreensStack', { screen: 'ElectrumSettings' }); + } + }, [navigation]); + + const walletListScreenOptions = useMemo(() => { + const isUpdating = walletTransactionUpdateStatus !== WalletTransactionsStatus.NONE; + const showOffline = isElectrumDisabled; + // When the user explicitly pulls to refresh, we always prefer showing + // the "Updating..." pill over "Not connected" during that refresh. + const showNotConnected = !isElectrumDisabled && electrumConnected === false && !isUpdating; + const showUpdating = !isElectrumDisabled && isUpdating; + + const renderHeaderLeft = () => { + if (showOffline) { + const offlineBg = theme.dark ? theme.colors.darkGray : '#000000'; + return ( + + + {loc.settings.electrum_offline_mode} + + ); + } + if (showNotConnected) { + return ( + { + BlueElectrum.presentElectrumDisconnectedHelpAlert().catch(() => { + /* alert helper failed; ignore */ + }); + }} + style={[styles.updatingLabelContainer, { backgroundColor: theme.colors.redBG }]} + > + {loc.settings.electrum_connected_not} + + ); + } + if (showUpdating) { + return ( + + ); + } + return null; + }; + + return { + title: sizeClass === SizeClass.Large ? loc.wallets.list_title : '', + headerLargeTitle: false, + headerShadowVisible: false, + headerStyle: { + backgroundColor: theme.colors.customHeader, + }, + headerLeft: renderHeaderLeft, + headerRight: () => (isDesktop ? undefined : RightBarButtons), + }; + }, [ + RightBarButtons, + sizeClass, + theme.colors.customHeader, + theme.colors.foregroundColor, + theme.colors.lightButton, + theme.colors.redBG, + theme.colors.redText, + theme.colors.darkGray, + theme.dark, + electrumConnected, + isElectrumDisabled, + navigateToElectrumSettings, + walletTransactionUpdateStatus, + ]); + + const isIOSLightMode = Platform.OS === 'ios' && !theme.dark; + const settingsCardColor = theme.colors.lightButton ?? theme.colors.modal ?? theme.colors.elevated ?? theme.colors.background; + const settingsHeaderBackgroundColor = isIOSLightMode ? settingsCardColor : theme.colors.customHeader; + + // Consistent header configuration for all settings screens + const getSettingsHeaderOptions = (title: string) => { + // Use PlatformColor for iOS to match the Settings component, fallback to theme color + const titleColor = Platform.OS === 'ios' ? PlatformColor('label') : theme.colors.foregroundColor; + // Convert PlatformColor to string for TypeScript compatibility + const titleColorString = typeof titleColor === 'string' ? titleColor : String(titleColor); + return { + title, + headerBackButtonDisplayMode: 'default' as const, + headerBackVisible: true, // Show back button on Android + headerShadowVisible: false, + headerLargeTitle: false, + headerLargeTitleStyle: undefined, + headerTitleStyle: { + color: titleColorString, + }, + headerTransparent: false, + headerBlurEffect: undefined, + headerStyle: { + backgroundColor: settingsHeaderBackgroundColor, + }, + }; + }; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* */} + + + + + + + + + + + + + + + ); +}; + +export default DetailViewStackScreensStack; + +const styles = StyleSheet.create({ + width24: { + width: 24, + }, + updatingLabelContainer: { + borderRadius: 20, + paddingHorizontal: 12, + paddingVertical: 6, + justifyContent: 'center', + alignItems: 'center', + }, + offlineLabelRow: { + flexDirection: 'row', + }, + offlineLabelIcon: { + marginRight: 6, + }, + offlineLabelText: { + fontSize: 13, + fontWeight: '700', + color: '#ffffff', + }, + updatingLabelText: { + fontSize: 13, + fontWeight: '700', + }, +}); diff --git a/navigation/DetailViewStackParamList.ts b/navigation/DetailViewStackParamList.ts new file mode 100644 index 00000000000..a85619eb310 --- /dev/null +++ b/navigation/DetailViewStackParamList.ts @@ -0,0 +1,158 @@ +import { AztecoVoucher } from '../class/azteco'; +import { LightningTransaction, TWallet } from '../class/wallets/types'; +import { BitcoinUnit, Chain } from '../models/bitcoinUnits'; +import { PromptPasswordConfirmationParams } from '../screen/PromptPasswordConfirmationSheet.types'; +import { ElectrumServerItem } from '../screen/settings/ElectrumSettings'; +import { SendDetailsParams, TNavigationWrapper } from './SendDetailsStackParamList'; + +export type ScanQRCodeParamList = { + cameraStatusGranted?: boolean; + backdoorPressed?: boolean; + launchedBy?: string; + urTotal?: number; + urHave?: number; + backdoorText?: string; + onBarScanned?: (data: string, useBBQR: boolean) => void; + showFileImportButton?: boolean; + backdoorVisible?: boolean; + orientation?: 'portrait'; + animatedQRCodeData?: Record; +}; + +type VaultKeyData = { + keyIndex: number; + seed: string; + passphrase?: string; + xpub: string; + fp: string; + path: string; + cosignerXpubURv2: string; + exportFilename: string; + exportString?: string; +}; + +export type DetailViewStackParamList = { + DrawerRoot: undefined; + UnlockWithScreen: undefined; + WalletsList: { onBarScanned?: string }; + WalletTransactions: { isLoading?: boolean; walletID: string; walletType: string; onBarScanned?: string }; + WalletDetails: { walletID: string }; + // TODO: type tx properly once Transaction and ElectrumTransaction are unified + TransactionStatus: { hash: string; walletID: string; tx?: any }; + CPFP: { + wallet: TWallet | null; + txid: string; + }; + RBFBumpFee: { txid: string; wallet: TWallet | null }; + RBFCancel: { txid: string; wallet: TWallet | null }; + SelectWallet: { + chainType?: Chain; + onWalletSelect?: (wallet: TWallet, navigationWrapper: TNavigationWrapper) => void; + availableWallets?: TWallet[]; + noWalletExplanationText?: string; + onChainRequireSend?: boolean; + selectedWalletID?: string; // Add this parameter to scroll to a specific wallet + }; + LNDViewInvoice: { invoice: LightningTransaction; walletID: string }; + LNDViewAdditionalInvoiceInformation: { invoiceId: string }; + LNDViewAdditionalInvoicePreImage: { invoiceId: string }; + Broadcast: object; + IsItMyAddress: object; + GenerateWord: undefined; + LnurlPay: undefined; + LnurlPaySuccess: { + paymentHash: string; + justPaid: boolean; + fromWalletID: string; + }; + LnurlAuth: undefined; + Success: undefined; + WalletAddresses: { walletID: string }; + AddWalletRoot: undefined; + SendDetailsRoot: SendDetailsParams; + LNDCreateInvoiceRoot: undefined; + ScanLNDInvoiceRoot: { + screen: string; + params: { + paymentHash: string; + fromWalletID: string; + justPaid: boolean; + }; + }; + AztecoRedeemRoot: { + screen: string; + params: { + aztecoVoucher: AztecoVoucher; + }; + }; + AztecoRedeem: { aztecoVoucher: AztecoVoucher }; + WalletExport: undefined; + ExportMultisigCoordinationSetupRoot: undefined; + Settings: undefined; + Currency: undefined; + GeneralSettings: undefined; + Licensing: undefined; + NetworkSettings: undefined; + About: undefined; + // DefaultView: undefined; // Commented out - not accessible from UI + ElectrumSettings: { server?: ElectrumServerItem; onBarScanned?: string }; + SettingsBlockExplorer: undefined; + PlausibleDeniability: undefined; + EncryptStorage: undefined; + Language: undefined; + LightningSettings: { + url?: string; + onBarScanned?: string; + }; + NotificationSettings: undefined; + SelfTest: undefined; + ReleaseNotes: undefined; + SettingsTools: undefined; + ViewEditMultisigCosigners: { + walletID: string; + cosigners: string[]; + sheetAction?: string; + sheetImportText?: string; + sheetAskPassphrase?: boolean; + sheetCurrentlyEditingCosignerNum?: number; + }; + ViewEditMultisigCosignerViewSheet: { walletID: string; vaultKeyData: VaultKeyData }; + ViewEditMultisigProvideMnemonicsSheet: { + walletID: string; + currentlyEditingCosignerNum: number; + importText: string; + askPassphrase: boolean; + }; + ViewEditMultisigShareCosignerSheet: { + walletID: string; + cosignerXpub: string; + cosignerXpubURv2: string; + exportFilename: string; + }; + WalletXpub: { walletID: string; xpub: string }; + SignVerifyRoot: { + screen: 'SignVerify'; + params: { + walletID: string; + address: string; + }; + }; + ReceiveDetails: { + walletID?: string; + address: string; + }; + ReceiveCustomAmount: { + address: string; + currentLabel?: string; + currentAmount?: string; + currentUnit?: BitcoinUnit; + preferredUnit?: BitcoinUnit; + }; + ScanQRCode: ScanQRCodeParamList; + PaymentCodeList: { + paymentCode: string; + walletID: string; + }; + PromptPasswordConfirmationSheet: PromptPasswordConfirmationParams | undefined; + ManageWallets: undefined; +}; diff --git a/navigation/DrawerParamList.ts b/navigation/DrawerParamList.ts new file mode 100644 index 00000000000..a0d5007cabe --- /dev/null +++ b/navigation/DrawerParamList.ts @@ -0,0 +1,8 @@ +import { DetailViewStackParamList } from './DetailViewStackParamList'; + +export type DrawerParamList = { + DetailViewStackScreensStack: { + screen?: keyof DetailViewStackParamList; + params?: object; + }; +}; diff --git a/navigation/DrawerRoot.tsx b/navigation/DrawerRoot.tsx new file mode 100644 index 00000000000..011c4f24799 --- /dev/null +++ b/navigation/DrawerRoot.tsx @@ -0,0 +1,84 @@ +import { createDrawerNavigator, DrawerNavigationOptions, DrawerContentComponentProps } from '@react-navigation/drawer'; +import { useLocale } from '@react-navigation/native'; +import React, { useEffect, useMemo } from 'react'; +import { Animated, Easing } from 'react-native'; +import { useSizeClass, SizeClass } from '../blue_modules/sizeClass'; +import DrawerList from '../screen/wallets/DrawerList'; +import DetailViewStackScreensStack from './DetailViewScreensStack'; +import { DrawerParamList } from './DrawerParamList'; +import useCompanionListeners from '../hooks/useCompanionListeners'; + +const Drawer = createDrawerNavigator(); + +const DrawerContent = (props: DrawerContentComponentProps) => { + const { isLargeScreen } = useSizeClass(); + + if (!isLargeScreen) { + return null; + } + + return ; +}; + +const getAnimationConfig = (isDrawerTransitionConfigured: boolean) => { + if (!isDrawerTransitionConfigured) return {}; + + return { + config: { + timing: Animated.timing, + useNativeDriver: true, + duration: 250, + easing: Easing.inOut(Easing.cubic), + }, + }; +}; + +const DrawerRoot = () => { + const { sizeClass, isLargeScreen } = useSizeClass(); + const { direction } = useLocale(); + useCompanionListeners(); + + const getDrawerWidth = useMemo(() => { + switch (sizeClass) { + case SizeClass.Large: + return 320; + case SizeClass.Regular: + return 280; + default: + return 0; + } + }, [sizeClass]); + + const drawerStyle: DrawerNavigationOptions = useMemo( + () => ({ + drawerPosition: direction === 'rtl' ? 'right' : 'left', + drawerStyle: { + width: getDrawerWidth, + height: '100%', + }, + drawerType: isLargeScreen ? 'permanent' : 'front', + overlayColor: 'rgba(0,0,0,0.4)', + swipeEnabled: false, + + ...getAnimationConfig(true), + }), + [getDrawerWidth, isLargeScreen, direction], + ); + + useEffect(() => { + console.debug('[DrawerRoot] Size class changed:', SizeClass[sizeClass]); + }, [sizeClass]); + + return ( + + + + ); +}; + +export default DrawerRoot; diff --git a/navigation/ExportMultisigCoordinationSetupStack.tsx b/navigation/ExportMultisigCoordinationSetupStack.tsx new file mode 100644 index 00000000000..640b657e4ee --- /dev/null +++ b/navigation/ExportMultisigCoordinationSetupStack.tsx @@ -0,0 +1,38 @@ +import React, { lazy } from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { withLazySuspense } from './LazyLoadingIndicator'; + +export type ExportMultisigCoordinationSetupStackRootParamList = { + ExportMultisigCoordinationSetup: { + walletID: string; + }; +}; + +const Stack = createNativeStackNavigator(); + +const ExportMultisigCoordinationSetup = lazy(() => import('../screen/wallets/ExportMultisigCoordinationSetup')); +const ExportMultisigCoordinationSetupComponent = withLazySuspense(ExportMultisigCoordinationSetup); + +const ExportMultisigCoordinationSetupStack = () => { + const theme = useTheme(); + + return ( + + + + ); +}; + +export default ExportMultisigCoordinationSetupStack; diff --git a/navigation/LNDCreateInvoiceStack.tsx b/navigation/LNDCreateInvoiceStack.tsx new file mode 100644 index 00000000000..97f0726a3c1 --- /dev/null +++ b/navigation/LNDCreateInvoiceStack.tsx @@ -0,0 +1,71 @@ +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import React, { lazy } from 'react'; +import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { withLazySuspense } from './LazyLoadingIndicator'; + +const Stack = createNativeStackNavigator(); + +const LNDCreateInvoice = lazy(() => import('../screen/lnd/lndCreateInvoice')); +const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet')); +const LNDViewInvoice = lazy(() => import('../screen/lnd/lndViewInvoice')); +const LNDViewAdditionalInvoicePreImage = lazy(() => import('../screen/lnd/lndViewAdditionalInvoicePreImage')); +const ScanQRCode = lazy(() => import('../screen/send/ScanQRCode')); + +const LNDCreateInvoiceComponent = withLazySuspense(LNDCreateInvoice); +const SelectWalletComponent = withLazySuspense(SelectWallet); +const LNDViewInvoiceComponent = withLazySuspense(LNDViewInvoice); +const LNDViewAdditionalInvoicePreImageComponent = withLazySuspense(LNDViewAdditionalInvoicePreImage); +const ScanQRCodeComponent = withLazySuspense(ScanQRCode); + +const LNDCreateInvoiceRoot = () => { + const theme = useTheme(); + + return ( + + + + + + + + ); +}; + +export default LNDCreateInvoiceRoot; diff --git a/navigation/LNDStackParamsList.ts b/navigation/LNDStackParamsList.ts new file mode 100644 index 00000000000..ae357489e18 --- /dev/null +++ b/navigation/LNDStackParamsList.ts @@ -0,0 +1,33 @@ +import { TWallet } from '../class/wallets/types'; +import { BitcoinUnit, Chain } from '../models/bitcoinUnits'; +import { ScanQRCodeParamList } from './DetailViewStackParamList'; +import { TNavigationWrapper } from './SendDetailsStackParamList'; + +export type LNDStackParamsList = { + ScanLNDInvoice: { + walletID: string | undefined; + uri: string | undefined; + invoice: string | undefined; + onBarScanned: string | undefined; + }; + LnurlPay: { + lnurl: string; + walletID: string; + }; + LnurlPaySuccess: undefined; + ScanQRCode: ScanQRCodeParamList; + SelectWallet: { + chainType?: Chain; + onWalletSelect?: (wallet: TWallet, navigationWrapper: TNavigationWrapper) => void; + availableWallets?: TWallet[]; + noWalletExplanationText?: string; + onChainRequireSend?: boolean; + }; + Success: { + amount?: number; + fee?: number; + invoiceDescription?: string; + amountUnit: BitcoinUnit; + txid?: string; + }; +}; diff --git a/navigation/LazyLoadingIndicator.tsx b/navigation/LazyLoadingIndicator.tsx new file mode 100644 index 00000000000..3f4d2371209 --- /dev/null +++ b/navigation/LazyLoadingIndicator.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { ActivityIndicator, StyleSheet, View } from 'react-native'; + +export const LazyLoadingIndicator = () => ( + + + +); + +export const withLazySuspense =

(Component: React.ComponentType

) => { + const Wrapped = (props: P) => ( + }> + + + ); + Wrapped.displayName = `WithLazySuspense(${Component.displayName || Component.name || 'Component'})`; + return Wrapped; +}; + +const styles = StyleSheet.create({ + root: { flex: 1, justifyContent: 'center', alignItems: 'center' }, +}); diff --git a/navigation/MasterView.tsx b/navigation/MasterView.tsx new file mode 100644 index 00000000000..9cf7b4934c3 --- /dev/null +++ b/navigation/MasterView.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import DevMenu from '../components/DevMenu'; +import MainRoot from './index'; + +const MasterView = () => { + return ( + <> + + {__DEV__ && } + + ); +}; + +export default MasterView; diff --git a/navigation/PaymentCodeStack.tsx b/navigation/PaymentCodeStack.tsx new file mode 100644 index 00000000000..8f6afd8fee7 --- /dev/null +++ b/navigation/PaymentCodeStack.tsx @@ -0,0 +1,26 @@ +import React, { lazy } from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import navigationStyle from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; // Assuming 'loc' is used for localization +import { PaymentCodeStackParamList } from './PaymentCodeStackParamList'; +import { withLazySuspense } from './LazyLoadingIndicator'; + +const Stack = createNativeStackNavigator(); +const PaymentCodesList = lazy(() => import('../screen/wallets/PaymentCodesList')); +const PaymentCodesListComponent = withLazySuspense(PaymentCodesList); +const PaymentCodeStackRoot = () => { + const theme = useTheme(); + + return ( + + + + ); +}; + +export default PaymentCodeStackRoot; diff --git a/navigation/PaymentCodeStackParamList.ts b/navigation/PaymentCodeStackParamList.ts new file mode 100644 index 00000000000..e3bf6d5bbde --- /dev/null +++ b/navigation/PaymentCodeStackParamList.ts @@ -0,0 +1,17 @@ +import { BitcoinUnit } from '../models/bitcoinUnits'; + +export type PaymentCodeStackParamList = { + PaymentCode: { paymentCode: string }; + PaymentCodesList: { + memo: string; + address: string; + walletID: string; + amount: number; + amountSats: number; + unit: BitcoinUnit; + isTransactionReplaceable: boolean; + launchedBy: string; + isEditable: boolean; + uri: string /* payjoin uri */; + }; +}; diff --git a/navigation/PromptPasswordConfirmationStack.tsx b/navigation/PromptPasswordConfirmationStack.tsx new file mode 100644 index 00000000000..7d93fc73a21 --- /dev/null +++ b/navigation/PromptPasswordConfirmationStack.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import PromptPasswordConfirmationSheet from '../screen/PromptPasswordConfirmationSheet'; +import { PromptPasswordConfirmationStackParamList } from './PromptPasswordConfirmationStackParamList'; + +const Stack = createNativeStackNavigator(); + +const PromptPasswordConfirmationStack = () => { + const theme = useTheme(); + + return ( + + + + ); +}; + +export default PromptPasswordConfirmationStack; diff --git a/navigation/PromptPasswordConfirmationStackParamList.ts b/navigation/PromptPasswordConfirmationStackParamList.ts new file mode 100644 index 00000000000..59d44400990 --- /dev/null +++ b/navigation/PromptPasswordConfirmationStackParamList.ts @@ -0,0 +1,5 @@ +import { PromptPasswordConfirmationParams } from '../screen/PromptPasswordConfirmationSheet.types'; + +export type PromptPasswordConfirmationStackParamList = { + PromptPasswordConfirmationSheet: PromptPasswordConfirmationParams; +}; diff --git a/navigation/ReceiveDetailsStack.tsx b/navigation/ReceiveDetailsStack.tsx new file mode 100644 index 00000000000..c978f780cfb --- /dev/null +++ b/navigation/ReceiveDetailsStack.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import ReceiveDetails from '../screen/receive/ReceiveDetails'; +import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle'; +import { ReceiveDetailsStackParamList } from './ReceiveDetailsStackParamList'; +import ReceiveCustomAmountSheet from '../screen/receive/ReceiveCustomAmountSheet'; +import { Platform } from 'react-native'; + +const Stack = createNativeStackNavigator(); + +const ReceiveDetailsStack = () => { + const theme = useTheme(); + + return ( + + + + + ); +}; + +export default ReceiveDetailsStack; diff --git a/navigation/ReceiveDetailsStackParamList.ts b/navigation/ReceiveDetailsStackParamList.ts new file mode 100644 index 00000000000..4d3fb9bc4b5 --- /dev/null +++ b/navigation/ReceiveDetailsStackParamList.ts @@ -0,0 +1,18 @@ +export type ReceiveDetailsStackParamList = { + ReceiveDetails: { + walletID?: string; + address?: string; + customLabel?: string; + customAmount?: string; + customUnit?: import('../models/bitcoinUnits').BitcoinUnit; + bip21encoded?: string; + isCustom?: boolean; + }; + ReceiveCustomAmount: { + address: string; + currentLabel?: string; + currentAmount?: string; + currentUnit?: import('../models/bitcoinUnits').BitcoinUnit; + preferredUnit?: import('../models/bitcoinUnits').BitcoinUnit; + }; +}; diff --git a/navigation/ScanLNDInvoiceStack.tsx b/navigation/ScanLNDInvoiceStack.tsx new file mode 100644 index 00000000000..d30a7591efa --- /dev/null +++ b/navigation/ScanLNDInvoiceStack.tsx @@ -0,0 +1,80 @@ +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import React, { lazy } from 'react'; + +import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { withLazySuspense } from './LazyLoadingIndicator'; + +const Stack = createNativeStackNavigator(); + +const ScanLNDInvoice = lazy(() => import('../screen/lnd/ScanLNDInvoice')); +const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet')); +const Success = lazy(() => import('../screen/send/success')); +const LnurlPay = lazy(() => import('../screen/lnd/lnurlPay')); +const LnurlPaySuccess = lazy(() => import('../screen/lnd/lnurlPaySuccess')); +const ScanQRCode = lazy(() => import('../screen/send/ScanQRCode')); + +const ScanLNDInvoiceComponent = withLazySuspense(ScanLNDInvoice); +const SelectWalletComponent = withLazySuspense(SelectWallet); +const SuccessComponent = withLazySuspense(Success); +const LnurlPayComponent = withLazySuspense(LnurlPay); +const LnurlPaySuccessComponent = withLazySuspense(LnurlPaySuccess); +const ScanQRCodeComponent = withLazySuspense(ScanQRCode); + +const ScanLNDInvoiceRoot = () => { + const theme = useTheme(); + return ( + + + + + + + + + ); +}; + +export default ScanLNDInvoiceRoot; diff --git a/navigation/SendDetailsStack.tsx b/navigation/SendDetailsStack.tsx new file mode 100644 index 00000000000..c5f9b1ba859 --- /dev/null +++ b/navigation/SendDetailsStack.tsx @@ -0,0 +1,145 @@ +import React, { lazy, useMemo } from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Platform } from 'react-native'; +import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { withLazySuspense } from './LazyLoadingIndicator'; +import { SendDetailsStackParamList } from './SendDetailsStackParamList'; +import HeaderRightButton from '../components/HeaderRightButton'; +import { BitcoinUnit } from '../models/bitcoinUnits'; +import SelectFeeScreen from '../screen/SelectFeeScreen'; +import CoinControlOutputSheet from '../screen/send/CoinControlOutputSheet'; + +const Stack = createNativeStackNavigator(); + +const SendDetails = lazy(() => import('../screen/send/SendDetails')); +const Confirm = lazy(() => import('../screen/send/Confirm')); +const PsbtWithHardwareWallet = lazy(() => import('../screen/send/psbtWithHardwareWallet')); +const CreateTransaction = lazy(() => import('../screen/send/create')); +const PsbtMultisig = lazy(() => import('../screen/send/psbtMultisig')); +const PsbtMultisigQRCode = lazy(() => import('../screen/send/PsbtMultisigQRCode')); +const Success = lazy(() => import('../screen/send/success')); +const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet')); +const CoinControl = lazy(() => import('../screen/send/CoinControl')); +const PaymentCodesList = lazy(() => import('../screen/wallets/PaymentCodesList')); +const ScanQRCode = lazy(() => import('../screen/send/ScanQRCode')); + +const SendDetailsComponent = withLazySuspense(SendDetails); +const ConfirmComponent = withLazySuspense(Confirm); +const PsbtWithHardwareWalletComponent = withLazySuspense(PsbtWithHardwareWallet); +const CreateTransactionComponent = withLazySuspense(CreateTransaction); +const PsbtMultisigComponent = withLazySuspense(PsbtMultisig); +const PsbtMultisigQRCodeComponent = withLazySuspense(PsbtMultisigQRCode); +const SuccessComponent = withLazySuspense(Success); +const SelectWalletComponent = withLazySuspense(SelectWallet); +const CoinControlComponent = withLazySuspense(CoinControl); +const PaymentCodesListComponent = withLazySuspense(PaymentCodesList); +const ScanQRCodeComponent = withLazySuspense(ScanQRCode); + +const SendDetailsStack = () => { + const theme = useTheme(); + const DetailsButton = useMemo( + () => , + [], + ); + + return ( + + + + DetailsButton })(theme)} + /> + + + + + + + + + + + + ); +}; + +export default SendDetailsStack; diff --git a/navigation/SendDetailsStackParamList.ts b/navigation/SendDetailsStackParamList.ts new file mode 100644 index 00000000000..8a6da1da130 --- /dev/null +++ b/navigation/SendDetailsStackParamList.ts @@ -0,0 +1,131 @@ +import { Psbt } from 'bitcoinjs-lib'; +import { CreateTransactionTarget, CreateTransactionUtxo, TWallet, Utxo } from '../class/wallets/types'; +import { BitcoinUnit, Chain } from '../models/bitcoinUnits'; +import { ScanQRCodeParamList } from './DetailViewStackParamList'; +import { IFee } from '../screen/send/SendDetails'; +import { NetworkTransactionFeeType } from '../models/networkTransactionFees'; + +export type SendDetailsParams = { + transactionMemo?: string; + isTransactionReplaceable?: boolean; + payjoinUrl?: string; + feeUnit?: BitcoinUnit; + frozenBalance?: number; + amountUnit?: BitcoinUnit; + address?: string; + amount?: number; + amountSats?: number; + onBarScanned?: string; + unit?: BitcoinUnit; + noRbf?: boolean; + walletID: string; + launchedBy?: string; + utxos?: CreateTransactionUtxo[] | null; + isEditable?: boolean; + uri?: string; + paymentCode?: string; + selectedFeeRate?: string | undefined; + selectedFeeType?: NetworkTransactionFeeType; + addRecipientParams?: { + address: string; + amount?: number; + memo?: string; + }; +}; + +export type TNavigation = { + pop: () => void; + navigate: () => void; +}; + +export type TNavigationWrapper = { + navigation: TNavigation; +}; + +export type SendDetailsStackParamList = { + SendDetails: SendDetailsParams; + CoinControlOutput: { + walletID: string; + utxo: Utxo; + }; + SelectFee: { + networkTransactionFees: { + fastestFee: number; + mediumFee: number; + slowFee: number; + }; + feePrecalc: IFee; + feeRate: string; + feeUnit?: BitcoinUnit; + walletID: string; + customFee?: string | null; + }; + Confirm: { + fee: number; + memo?: string; + walletID: string; + tx: string; + targets?: CreateTransactionTarget[]; // needed to know if there were paymentCodes, which turned into addresses in `recipients` + recipients: CreateTransactionTarget[]; + satoshiPerByte: number; + payjoinUrl?: string | null; + psbt: Psbt; + }; + PsbtWithHardwareWallet: { + memo?: string; + walletID: string; + launchedBy?: string; + psbt?: Psbt; + txhex?: string; + deepLinkPSBT?: string; + onBarScanned?: string; + }; + CreateTransaction: { + memo?: string; + psbt?: Psbt; + txhex?: string; + tx: string; + fee: number; + showAnimatedQr?: boolean; + recipients: CreateTransactionTarget[]; + satoshiPerByte: number; + feeSatoshi?: number; + }; + PsbtMultisig: { + memo?: string; + psbtBase64: string; + walletID: string; + launchedBy?: string; + }; + PsbtMultisigQRCode: { + memo?: string; + psbtBase64: string; + walletID: string; + launchedBy?: string; + isShowOpenScanner?: boolean; + onBarScanned?: string; + }; + Success: { + fee?: number; + amount: number; + amountUnit?: BitcoinUnit; + txid?: string; + invoiceDescription?: string; + }; + SelectWallet: { + chainType?: Chain; + onWalletSelect?: (wallet: TWallet, navigationWrapper: TNavigationWrapper) => void; + availableWallets?: TWallet[]; + noWalletExplanationText?: string; + onChainRequireSend?: boolean; + selectedWalletID?: string; // Add this parameter to scroll to a specific wallet + }; + CoinControl: { + walletID: string; + }; + PaymentCodeList: { + walletID: string; + merge?: boolean; + }; + ScanQRCode: ScanQRCodeParamList; +}; diff --git a/navigation/SignVerifyStack.tsx b/navigation/SignVerifyStack.tsx new file mode 100644 index 00000000000..4210f95c103 --- /dev/null +++ b/navigation/SignVerifyStack.tsx @@ -0,0 +1,33 @@ +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import React, { lazy } from 'react'; + +import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { withLazySuspense } from './LazyLoadingIndicator'; + +const Stack = createNativeStackNavigator(); + +const SignVerify = lazy(() => import('../screen/wallets/signVerify')); +const SignVerifyComponent = withLazySuspense(SignVerify); + +const SignVerifyStackRoot = () => { + const theme = useTheme(); + + return ( + + + + ); +}; + +export default SignVerifyStackRoot; diff --git a/navigation/WalletExportStack.tsx b/navigation/WalletExportStack.tsx new file mode 100644 index 00000000000..b05bfa97e9d --- /dev/null +++ b/navigation/WalletExportStack.tsx @@ -0,0 +1,35 @@ +import React, { lazy } from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +import navigationStyle from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { withLazySuspense } from './LazyLoadingIndicator'; + +export type WalletExportStackParamList = { + WalletExport: { walletID: string }; +}; + +const Stack = createNativeStackNavigator(); + +const WalletExport = lazy(() => import('../screen/wallets/WalletExport')); +const WalletExportComponent = withLazySuspense(WalletExport); + +const WalletExportStack = () => { + const theme = useTheme(); + + return ( + + + + ); +}; + +export default WalletExportStack; diff --git a/navigation/goFromCoinControlToSendDetails.ts b/navigation/goFromCoinControlToSendDetails.ts new file mode 100644 index 00000000000..95da523d801 --- /dev/null +++ b/navigation/goFromCoinControlToSendDetails.ts @@ -0,0 +1,39 @@ +import { CommonActions, StackActions } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; + +import { Utxo } from '../class/wallets/types'; +import { BitcoinUnit } from '../models/bitcoinUnits'; +import { SendDetailsStackParamList } from './SendDetailsStackParamList'; + +/** + * After choosing UTXO(s) in Coin Control, open Send with those coins. + * Uses popTo when SendDetails is already in the stack (normal send → coin control). + * Resets the send stack when Coin Control was opened as the first screen (e.g. from wallet details). + */ +export function goFromCoinControlToSendDetails( + navigation: NativeStackNavigationProp, + walletID: string, + utxos: Utxo[], +): void { + const state = navigation.getState(); + const hasSendDetails = state.routes.some(r => r.name === 'SendDetails'); + + const params = { + walletID, + utxos, + isEditable: true as const, + feeUnit: BitcoinUnit.BTC, + amountUnit: BitcoinUnit.BTC, + }; + + if (hasSendDetails) { + navigation.dispatch(StackActions.popTo('SendDetails', params, { merge: true })); + } else { + navigation.dispatch( + CommonActions.reset({ + index: 0, + routes: [{ name: 'SendDetails', params }], + }), + ); + } +} diff --git a/navigation/helpers/getWalletTransactionsOptions.tsx b/navigation/helpers/getWalletTransactionsOptions.tsx new file mode 100644 index 00000000000..714876834d1 --- /dev/null +++ b/navigation/helpers/getWalletTransactionsOptions.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { TouchableOpacity, StyleSheet } from 'react-native'; +import Icon from '../../components/Icon'; +import WalletGradient from '../../class/wallet-gradient'; +import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; +import { DetailViewStackParamList } from '../DetailViewStackParamList'; +import { navigationRef } from '../../NavigationService'; +import { RouteProp } from '@react-navigation/native'; + +export type WalletTransactionsRouteProps = RouteProp; + +const getWalletTransactionsOptions = ({ route }: { route: WalletTransactionsRouteProps }): NativeStackNavigationOptions => { + const { isLoading = false, walletID, walletType } = route.params; + + const onPress = () => { + navigationRef.navigate('WalletDetails', { + walletID, + }); + }; + + const RightButton = ( + + + + ); + + const backgroundColor = WalletGradient.headerColorFor(walletType); + + return { + title: '', + headerBackTitleStyle: { fontSize: 0 }, + headerStyle: { + backgroundColor, + }, + headerBackButtonDisplayMode: 'minimal', + headerShadowVisible: false, + headerTintColor: '#FFFFFF', + statusBarStyle: 'light', + headerBackTitle: undefined, + headerRight: () => RightButton, + }; +}; + +const styles = StyleSheet.create({ + walletDetails: { + justifyContent: 'center', + alignItems: 'flex-end', + }, +}); + +export default getWalletTransactionsOptions; diff --git a/navigation/index.tsx b/navigation/index.tsx new file mode 100644 index 00000000000..0a01a855f62 --- /dev/null +++ b/navigation/index.tsx @@ -0,0 +1,178 @@ +import { createNativeStackNavigator, NativeStackNavigationOptions } from '@react-navigation/native-stack'; +import React, { lazy } from 'react'; +import { Platform } from 'react-native'; +import UnlockWith from '../screen/UnlockWith'; +import { withLazySuspense } from './LazyLoadingIndicator'; +import { DetailViewStackParamList } from './DetailViewStackParamList'; +import { useStorage } from '../hooks/context/useStorage'; +import loc from '../loc'; +import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import WalletXpub from '../screen/wallets/xpub'; +import WalletExport from '../screen/wallets/WalletExport'; +import ViewEditMultisigCosignerViewSheet from '../screen/wallets/ViewEditMultisigCosignerViewSheet'; +import ViewEditMultisigProvideMnemonicsSheet from '../screen/wallets/ViewEditMultisigProvideMnemonicsSheet'; +import ViewEditMultisigShareCosignerSheet from '../screen/wallets/ViewEditMultisigShareCosignerSheet'; + +// Lazy load all components except UnlockWith +const DrawerRoot = lazy(() => import('./DrawerRoot')); +const AddWalletStack = lazy(() => import('./AddWalletStack')); +const SendDetailsStack = lazy(() => import('./SendDetailsStack')); +const LNDCreateInvoiceRoot = lazy(() => import('./LNDCreateInvoiceStack')); +const ScanLNDInvoiceRoot = lazy(() => import('./ScanLNDInvoiceStack')); +const AztecoRedeemStackRoot = lazy(() => import('./AztecoRedeemStack')); +const ExportMultisigCoordinationSetupStack = lazy(() => import('./ExportMultisigCoordinationSetupStack')); +const SignVerifyStackRoot = lazy(() => import('./SignVerifyStack')); +const ScanQRCode = lazy(() => import('../screen/send/ScanQRCode')); +const ViewEditMultisigCosigners = lazy(() => import('../screen/wallets/ViewEditMultisigCosigners')); + +export const NavigationDefaultOptions: NativeStackNavigationOptions = { + headerShown: false, + presentation: 'modal', + headerShadowVisible: false, +}; +export const NavigationFormModalOptions: NativeStackNavigationOptions = { + headerShown: false, + presentation: 'formSheet', + sheetAllowedDetents: 'fitToContents', + sheetGrabberVisible: true, +}; + +export const NavigationFormNoSwipeDefaultOptions: NativeStackNavigationOptions = { + headerShown: false, + presentation: 'modal', + headerShadowVisible: false, + fullScreenGestureEnabled: false, +}; +export const StatusBarLightOptions: NativeStackNavigationOptions = { statusBarStyle: 'light' }; + +const DetailViewStack = createNativeStackNavigator(); + +const LazyDrawerRoot = withLazySuspense(DrawerRoot); +const LazyAddWalletStack = withLazySuspense(AddWalletStack); +const LazySendDetailsStack = withLazySuspense(SendDetailsStack); +const LazyLNDCreateInvoiceRoot = withLazySuspense(LNDCreateInvoiceRoot); +const LazyScanLNDInvoiceRoot = withLazySuspense(ScanLNDInvoiceRoot); +const LazyAztecoRedeemStackRoot = withLazySuspense(AztecoRedeemStackRoot); +const LazyExportMultisigCoordinationSetupStack = withLazySuspense(ExportMultisigCoordinationSetupStack); +const LazyViewEditMultisigCosigners = withLazySuspense(ViewEditMultisigCosigners); +const LazySignVerifyStackRoot = withLazySuspense(SignVerifyStackRoot); +const LazyScanQRCodeComponent = withLazySuspense(ScanQRCode); +const multisigSheetAllowedDetents = Platform.OS === 'ios' ? 'fitToContents' : [0.9]; + +const MainRoot = () => { + const { walletsInitialized } = useStorage(); + const theme = useTheme(); + + return ( + + {!walletsInitialized ? ( + + ) : ( + <> + + + {/* Modal stacks */} + + + + + + + + + + + + + + + + + + )} + + ); +}; + +export default MainRoot; +export { DetailViewStack }; diff --git a/package-lock.json b/package-lock.json index 82ffc40d290..5f03c68a8b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,218 +1,271 @@ { "name": "bluewallet", - "version": "6.4.9", - "lockfileVersion": 2, + "version": "8.0.0", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bluewallet", - "version": "6.4.9", + "version": "8.0.0", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@babel/preset-env": "^7.20.0", - "@bugsnag/react-native": "7.21.0", - "@bugsnag/source-maps": "2.3.1", - "@keystonehq/bc-ur-registry": "0.6.3", - "@ngraveio/bc-ur": "1.1.6", + "@arkade-os/boltz-swap": "0.2.19", + "@arkade-os/sdk": "0.3.12", + "@babel/preset-env": "7.29.5", + "@bugsnag/react-native": "8.8.1", + "@bugsnag/source-maps": "2.3.3", + "@keystonehq/bc-ur-registry": "0.7.1", + "@ngraveio/bc-ur": "1.1.13", + "@noble/hashes": "1.3.3", "@noble/secp256k1": "1.6.3", - "@react-native-async-storage/async-storage": "1.19.3", - "@react-native-clipboard/clipboard": "1.11.2", - "@react-native-community/push-notification-ios": "1.11.0", - "@react-navigation/drawer": "5.12.9", - "@react-navigation/native": "5.9.8", - "@remobile/react-native-qrcode-local-image": "https://github.com/BlueWallet/react-native-qrcode-local-image", - "@spsina/bip47": "github:BlueWallet/bip47#0a2f02c90350802f2ec93afa4e6c8843be2d687c", - "aez": "1.0.1", - "assert": "2.0.0", - "base-x": "3.0.9", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-clipboard/clipboard": "1.16.3", + "@react-native-community/cli": "20.1.3", + "@react-native-community/cli-platform-android": "20.1.3", + "@react-native-community/cli-platform-ios": "20.1.3", + "@react-native-documents/picker": "12.0.1", + "@react-native-vector-icons/entypo": "13.1.1", + "@react-native-vector-icons/fontawesome": "13.1.1", + "@react-native-vector-icons/fontawesome6": "13.1.1", + "@react-native-vector-icons/ionicons": "13.1.1", + "@react-native-vector-icons/material-design-icons": "13.1.1", + "@react-native-vector-icons/material-icons": "13.1.1", + "@react-native/babel-preset": "0.85.3", + "@react-native/codegen": "0.85.3", + "@react-native/gradle-plugin": "0.85.3", + "@react-native/metro-config": "0.85.3", + "@react-navigation/devtools": "7.0.58", + "@react-navigation/drawer": "7.10.2", + "@react-navigation/native": "7.2.4", + "@react-navigation/native-stack": "7.15.1", + "@scure/base": "2.0.0", + "@spsina/bip47": "github:BlueWallet/bip47#df82345", + "aezeed": "0.0.5", + "assert": "2.1.0", + "base-x": "4.0.1", "bc-bech32": "file:blue_modules/bc-bech32", "bech32": "2.0.0", - "bignumber.js": "9.1.1", + "bignumber.js": "9.3.1", "bip21": "2.0.3", - "bip32": "3.0.1", - "bip38": "github:BlueWallet/bip38", + "bip32": "5.0.1", + "bip38": "github:BlueWallet/bip38#7ec4b1932b98eaaff16c5a26765a26466958e6b4", "bip39": "3.1.0", - "bitcoinjs-lib": "6.1.1", + "bitcoinjs-lib": "7.0.1", "bitcoinjs-message": "2.2.0", "bolt11": "1.4.1", "buffer": "6.0.3", - "buffer-reverse": "1.0.1", - "coinselect": "3.1.13", - "crypto-js": "4.1.1", - "dayjs": "1.11.9", - "detox": "20.11.4", - "ecpair": "2.0.1", - "ecurve": "1.0.6", - "electrum-client": "https://github.com/BlueWallet/rn-electrum-client#76c0ea35e1a50c47f3a7f818d529ebd100161496", + "coinselect": "github:BlueWallet/coinselect#35f8038", + "crypto-browserify": "3.12.1", + "crypto-js": "4.2.0", + "dayjs": "1.11.20", + "detox": "20.51.1", + "ecpair": "3.0.1", + "electrum-client": "github:BlueWallet/rn-electrum-client#83420b8", "electrum-mnemonic": "2.0.0", "events": "3.3.0", - "frisbee": "3.1.4", - "junderw-crc32c": "1.2.0", - "lottie-ios": "3.4.4", - "lottie-react-native": "5.1.6", - "metro-react-native-babel-preset": "0.77.0", - "path-browserify": "1.0.1", + "lottie-react-native": "7.3.8", + "pako": "file:blue_modules/pako", "payjoin-client": "1.0.1", - "process": "0.11.10", "prop-types": "15.8.1", - "react": "18.2.0", + "qr": "0.5.5", + "react": "19.2.3", "react-localization": "github:BlueWallet/react-localization#ae7969a", - "react-native": "0.71.11", + "react-native": "0.85.3", + "react-native-biometrics": "3.0.1", "react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442", - "react-native-camera-kit": "13.0.0", - "react-native-crypto": "2.2.0", - "react-native-default-preference": "1.4.4", - "react-native-device-info": "8.7.1", - "react-native-document-picker": "https://github.com/BlueWallet/react-native-document-picker#857655cdddf17751c0fae1286a9121fda2a6d568", - "react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#ebfddc4", - "react-native-elements": "3.4.3", - "react-native-fingerprint-scanner": "https://github.com/BlueWallet/react-native-fingerprint-scanner#ce644673681716335d786727bab998f7e632ab5e", + "react-native-camera-kit-no-google": "github:BlueWallet/react-native-camera-kit-no-google#0ed049a62da29cf304019363ec9d9ef3a73652e6", + "react-native-capture-protection": "github:BlueWallet/react-native-capture-protection#b17b9ec", + "react-native-context-menu-view": "github:BlueWallet/react-native-context-menu-view#144110b02afdb11b431741aef5da95e91b942a9b", + "react-native-default-preference": "github:BlueWallet/react-native-default-preference#6338a1f1235e4130b8cfc2dd3b53015eeff2870c", + "react-native-device-info": "14.1.1", + "react-native-draggable-flatlist": "4.0.3", + "react-native-edge-to-edge": "1.8.1", "react-native-fs": "2.20.0", - "react-native-gesture-handler": "2.9.0", - "react-native-handoff": "https://github.com/BlueWallet/react-native-handoff#31d005f93d31099d0e564590a3bbd052b8a02b39", - "react-native-haptic-feedback": "2.0.3", - "react-native-idle-timer": "https://github.com/BlueWallet/react-native-idle-timer#8587876d68ab5920e79619726aeca9e672beaf2b", - "react-native-image-picker": "4.8.5", - "react-native-ios-context-menu": "github:BlueWallet/react-native-ios-context-menu#v1.15.3", - "react-native-keychain": "8.1.2", - "react-native-linear-gradient": "2.8.2", - "react-native-localize": "3.0.2", - "react-native-modal": "13.0.1", - "react-native-navigation-bar-color": "https://github.com/BlueWallet/react-native-navigation-bar-color#3b2894ae62fbce99a3bd24105f0921cebaef5c94", - "react-native-obscure": "https://github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb", - "react-native-passcode-auth": "https://github.com/BlueWallet/react-native-passcode-auth#a2ff977ba92b36f8d0a5567f59c05cc608e8bd12", - "react-native-privacy-snapshot": "https://github.com/BlueWallet/react-native-privacy-snapshot#529e4627d93f67752a27e82a040ff7b64dca0783", - "react-native-prompt-android": "https://github.com/BlueWallet/react-native-prompt-android#ed168d66fed556bc2ed07cf498770f058b78a376", - "react-native-push-notification": "8.1.1", - "react-native-qrcode-svg": "6.2.0", + "react-native-gesture-handler": "2.31.2", + "react-native-get-random-values": "1.11.0", + "react-native-handoff": "github:BlueWallet/react-native-handoff#v0.0.4", + "react-native-haptic-feedback": "2.3.4", + "react-native-image-picker": "8.2.1", + "react-native-keychain": "10.0.0", + "react-native-linear-gradient": "2.8.3", + "react-native-localize": "3.7.0", + "react-native-notifications": "5.2.2", + "react-native-permissions": "5.5.1", + "react-native-prompt-android": "github:BlueWallet/react-native-prompt-android#ed168d66fed556bc2ed07cf498770f058b78a376", "react-native-quick-actions": "0.3.13", - "react-native-randombytes": "3.6.1", - "react-native-rate": "1.2.12", - "react-native-reanimated": "2.17.0", - "react-native-safe-area-context": "3.4.1", - "react-native-screens": "3.20.0", - "react-native-secure-key-store": "https://github.com/BlueWallet/react-native-secure-key-store#2076b48", - "react-native-share": "8.2.2", - "react-native-svg": "13.13.0", - "react-native-tcp-socket": "5.6.2", - "react-native-tor": "0.1.8", - "react-native-vector-icons": "9.2.0", + "react-native-reanimated": "4.3.1", + "react-native-safe-area-context": "5.7.0", + "react-native-screens": "4.24.0", + "react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc", + "react-native-share": "12.2.6", + "react-native-svg": "15.15.5", + "react-native-tcp-socket": "6.4.1", "react-native-watch-connectivity": "1.1.0", - "react-native-webview": "12.4.0", - "react-native-widget-center": "https://github.com/BlueWallet/react-native-widget-center#a128c38", + "react-native-worklets": "0.8.1", "readable-stream": "3.6.2", - "realm": "12.0.0", - "rn-ldk": "github:BlueWallet/rn-ldk#v0.8.4", - "rn-nodeify": "10.3.0", - "scryptsy": "2.1.0", - "slip39": "https://github.com/BlueWallet/slip39-js", + "realm": "20.2.0", + "silent-payments": "github:BlueWallet/SilentPayments#59a037", + "slip39": "github:BlueWallet/slip39-js#d316ee6", "stream-browserify": "3.0.0", - "url": "0.11.1", + "text-encoding": "0.7.0", + "url": "0.11.4", "wif": "2.0.6" }, "devDependencies": { - "@babel/core": "^7.20.0", - "@babel/runtime": "^7.20.0", + "@babel/core": "^7.26.0", + "@babel/preset-env": "^7.26.0", + "@babel/runtime": "^7.26.0", "@jest/reporters": "^27.5.1", - "@react-native-community/eslint-config": "^3.2.0", - "@tsconfig/react-native": "^3.0.2", + "@react-native/eslint-config": "^0.85.3", + "@react-native/jest-preset": "0.85.3", + "@react-native/js-polyfills": "^0.85.3", + "@react-native/metro-babel-transformer": "^0.85.3", + "@react-native/typescript-config": "^0.85.3", + "@testing-library/react-native": "^13.0.1", + "@types/bip38": "^3.1.2", "@types/bs58check": "^2.1.0", "@types/create-hash": "^1.2.2", - "@types/jest": "^29.4.0", - "@types/react": "^18.2.16", - "@types/react-native": "^0.72.0", - "@types/react-test-renderer": "^18.0.0", - "@typescript-eslint/eslint-plugin": "^6.2.0", - "@typescript-eslint/parser": "^6.2.0", - "eslint": "^8.45.0", - "eslint-config-prettier": "^8.8.0", - "eslint-config-standard": "^17.0.0", + "@types/crypto-js": "^4.2.2", + "@types/jest": "^29.5.13", + "@types/react": "^19.2.0", + "@types/react-test-renderer": "^19.1.0", + "@types/wif": "^2.0.5", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-config-standard": "^17.1.0", "eslint-config-standard-jsx": "^11.0.0", "eslint-config-standard-react": "^13.0.0", - "eslint-plugin-import": "^2.27.0", - "eslint-plugin-n": "^16.0.1", - "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jest": "^28.7.0", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-react": "^7.33.0", - "eslint-plugin-react-native": "^4.0.0", - "jest": "^29.4.2", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-native": "^4.1.0", + "jest": "^29.6.3", + "jest-environment-node": "^29.7.0", "node-fetch": "^2.6.7", - "prettier": "^3.0.0", - "react-test-renderer": "18.2.0", + "patch-package": "^8.0.0", + "prettier": "^3.2.5", + "react-test-renderer": "19.2.3", "ts-jest": "^29.1.1", - "typescript": "^5.1.6" + "typescript": "^5.9.3" }, "engines": { - "node": ">=10.16.0", - "npm": ">=6.9.0" + "node": ">= 22.11.0" } }, "blue_modules/bc-bech32": { "version": "1.0.2", - "integrity": "sha512-lwAn5R4LUhcnyrZgNx3YdDPr5+nseM4kARANcv8i0YOMtnPJRTF7B7TZzS3DYgC6tff/aR2W/3jGoY/SJMs6MA==", "license": "MIT" }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "blue_modules/pako": { + "version": "2.1.0", + "license": "(MIT AND Zlib)", + "devDependencies": {} }, "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "version": "2.3.0", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, - "node_modules/@apocentre/alias-sampling": { - "version": "0.5.3", - "integrity": "sha512-7UDWIIF9hIeJqfKXkNIzkVandlwLf1FWTSdrb9iXvOP8oF544JRXQjCbiTmCv2c9n44n/FIWtehhBfNuAx2CZA==" + "node_modules/@arkade-os/boltz-swap": { + "version": "0.2.19", + "license": "MIT", + "dependencies": { + "@arkade-os/sdk": "0.3.12", + "@noble/hashes": "2.0.1", + "@scure/base": "2.0.0", + "@scure/btc-signer": "2.0.1", + "bip68": "^1.0.4", + "light-bolt11-decoder": "3.2.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@arkade-os/boltz-swap/node_modules/@noble/hashes": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@arkade-os/sdk": { + "version": "0.3.12", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@marcbachmann/cel-js": "7.0.0", + "@noble/curves": "2.0.0", + "@noble/secp256k1": "3.0.0", + "@scure/base": "2.0.0", + "@scure/btc-signer": "2.0.1", + "bip68": "1.0.4" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@arkade-os/sdk/node_modules/@noble/secp256k1": { + "version": "3.0.0", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.29.0", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.21.0", - "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.21.0", - "integrity": "sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==", + "version": "7.26.10", + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.0", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.21.0", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.0", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -223,97 +276,71 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.19.1", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", + "version": "7.27.0", "dev": true, + "license": "MIT", "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || >=14.0.0" }, "peerDependencies": { - "@babel/core": ">=7.11.0", - "eslint": "^7.5.0 || ^8.0.0" + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/@babel/generator": { - "version": "7.21.1", - "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", + "version": "7.29.1", + "license": "MIT", "dependencies": { - "@babel/types": "^7.21.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "version": "7.27.3", + "license": "MIT", "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.21.0", - "integrity": "sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==", + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-member-expression-to-functions": "^7.21.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/helper-split-export-declaration": "^7.18.6" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -323,11 +350,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.21.0", - "integrity": "sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==", + "version": "7.28.5", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.3.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -337,120 +365,89 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" + "resolve": "^1.22.11" }, "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dependencies": { - "@babel/types": "^7.18.6" - }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.21.0", - "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==", + "version": "7.28.5", + "license": "MIT", "dependencies": { - "@babel/types": "^7.21.0" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.21.2", - "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "version": "7.27.1", + "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "version": "7.28.6", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "version": "7.27.1", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -460,111 +457,81 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.20.7", - "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "dependencies": { - "@babel/types": "^7.20.2" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", - "dependencies": { - "@babel/types": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.27.1", + "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.27.1", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.28.5", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "version": "7.27.1", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.20.5", - "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "version": "7.27.1", + "license": "MIT", "dependencies": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.21.0", - "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "version": "7.27.0", + "license": "MIT", "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "node_modules/@babel/parser": { + "version": "7.29.0", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/types": "^7.29.0" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.21.2", - "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==", "bin": { "parser": "bin/babel-parser.js" }, @@ -572,11 +539,13 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -585,86 +554,87 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.20.7", - "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.13.0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.3.tgz", + "integrity": "sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.21.0", - "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.12.0" + "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.18.10", - "integrity": "sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow==", + "version": "7.25.9", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-default-from": "^7.18.6" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -673,13 +643,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" }, @@ -687,54 +654,45 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -743,29 +701,21 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "node_modules/@babel/plugin-syntax-export-default-from": { + "version": "7.25.9", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -774,13 +724,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", - "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -789,12 +737,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -803,14 +751,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0", - "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -819,23 +765,21 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -843,91 +787,89 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.18.6", - "integrity": "sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { + "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.18.6", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -936,118 +878,157 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-jsx": { + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "license": "MIT", "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.0", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1056,11 +1037,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1069,11 +1052,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.20.7", - "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1082,13 +1066,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.20.7", - "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1097,11 +1081,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1110,32 +1095,27 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.21.0", - "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.21.0", - "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1144,12 +1124,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.20.7", - "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/template": "^7.20.7" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1158,11 +1139,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.20.7", - "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1171,12 +1153,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1185,11 +1167,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.27.1", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-flow": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1198,12 +1181,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1212,12 +1195,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.21.0", - "integrity": "sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==", + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-flow": "^7.18.6" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1226,11 +1211,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.21.0", - "integrity": "sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==", + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1239,13 +1225,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1254,11 +1239,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1268,10 +1254,11 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1281,11 +1268,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.20.11", - "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1295,12 +1283,11 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.21.2", - "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==", + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-simple-access": "^7.20.2" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1310,13 +1297,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.20.11", - "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", + "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-identifier": "^7.19.1" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1326,11 +1316,12 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1340,11 +1331,11 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.20.5", - "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "version": "7.29.0", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1354,10 +1345,11 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1366,11 +1358,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-assign": { - "version": "7.18.6", - "integrity": "sha512-mQisZ3JfqWh2gVXvfqYCAAyRs6+7oev+myBsTwW5RnPhYXOTuCEw2oe3YgxlXMViXUS53lG8koulI7mJ+8JE+A==", + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1379,12 +1371,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1393,11 +1385,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.20.7", - "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1406,11 +1403,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1419,11 +1418,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1432,15 +1431,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.21.0", - "integrity": "sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==", + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.21.0" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1449,11 +1445,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.21.0", - "integrity": "sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==", + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1462,11 +1459,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.19.6", - "integrity": "sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==", + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1475,12 +1473,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.20.5", - "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "regenerator-transform": "^0.15.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1489,11 +1488,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1502,16 +1502,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.21.0", - "integrity": "sha512-ReY6pxwSzEU0b3r2/T/VhqMKg/AkceBT19X0UptA3/tYi5Pe2eXgEUH+NNMC5nok6c6XQz5tyVTUpuezRfSMSg==", + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.25.9", + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1520,11 +1515,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.25.9", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1533,12 +1532,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.20.7", - "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1547,11 +1545,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1560,11 +1558,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1573,11 +1571,27 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1586,13 +1600,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.21.0", - "integrity": "sha512-xo///XTPp3mDzTtrqXoBlK9eiAYW3wv9JXglcn/u1bi60RW11dEUxIgA8cbnDhutS1zacjMRmAwxE0gMklLnZg==", + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.26.10", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-typescript": "^7.20.0" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1601,11 +1618,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1614,12 +1631,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1628,85 +1646,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env": { - "version": "7.20.2", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", - "dependencies": { - "@babel/compat-data": "^7.20.1", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.20.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.20.2", - "@babel/plugin-transform-classes": "^7.20.2", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.20.2", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.19.6", - "@babel/plugin-transform-modules-commonjs": "^7.19.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.6", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.20.1", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1715,13 +1660,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-flow": { - "version": "7.18.6", - "integrity": "sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ==", + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-flow-strip-types": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1730,27 +1673,29 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-typescript": { - "version": "7.21.0", - "integrity": "sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg==", + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.21.0", - "@babel/plugin-transform-typescript": "^7.21.0" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1759,15 +1704,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/register": { - "version": "7.21.0", - "integrity": "sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw==", + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.5", - "source-map-support": "^0.5.16" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1776,91 +1718,224 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/register/node_modules/make-dir": { - "version": "2.1.0", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "dev": true, + "license": "MIT", "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { - "node": ">=6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/register/node_modules/pify": { - "version": "4.0.1", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, "engines": { - "node": ">=6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/register/node_modules/semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/register/node_modules/source-map-support": { - "version": "0.5.21", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "node_modules/@babel/preset-env": { + "version": "7.29.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.5.tgz", + "integrity": "sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==", + "dev": true, + "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "@babel/compat-data": "^7.29.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.4", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, "node_modules/@babel/runtime": { - "version": "7.21.0", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.27.0", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.20.7", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.28.6", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.2", - "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.1", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.2", - "@babel/types": "^7.21.2", - "debug": "^4.1.0", - "globals": "^11.1.0" + "version": "7.29.0", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.21.2", - "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", + "version": "7.29.0", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1868,50 +1943,53 @@ }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@bugsnag/core": { - "version": "7.19.0", - "integrity": "sha512-2KGwdaLD9PhR7Wk7xPi3jGuGsKTatc/28U4TOZIDU3CgC2QhGjubwiXSECel5gwxhZ3jACKcMKSV2ovHhv1NrA==", + "version": "8.8.0", + "license": "MIT", "dependencies": { "@bugsnag/cuid": "^3.0.0", "@bugsnag/safe-json-stringify": "^6.0.0", "error-stack-parser": "^2.0.3", - "iserror": "0.0.2", + "iserror": "^0.0.2", "stack-generator": "^2.0.3" } }, "node_modules/@bugsnag/cuid": { - "version": "3.0.2", - "integrity": "sha512-cIwzC93r3PQ/INeuwtZwkZIG2K8WWN0rRLZQhu+mr48Ay+i6sEki4GYfTsflse7hZ1BeDWrNb/Q9vgY3B31xHQ==" + "version": "3.2.1", + "license": "MIT" }, "node_modules/@bugsnag/delivery-react-native": { - "version": "7.19.0", - "integrity": "sha512-Zzl3VOwLDU4KHmf3VweyfNeJcQgL0NzbWG+OCxjCYen093Q4sxNTpWAVBCrYPRjQ2Sq3+D3+YbQg5UUrHL7kig==", + "version": "8.8.0", + "license": "MIT", "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@bugsnag/core": "^8.0.0" } }, "node_modules/@bugsnag/plugin-console-breadcrumbs": { - "version": "7.19.0", - "integrity": "sha512-ZHqPAK0WpbvWjj2wwSV8+C8+K9TOyQsfZnRJ7lIadbeUUJORmFRnG0vUHKBvwxMP7bqCj8fOe/S0kKF3dfMMKA==", + "version": "8.8.0", + "license": "MIT", "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@bugsnag/core": "^8.0.0" } }, "node_modules/@bugsnag/plugin-network-breadcrumbs": { - "version": "7.19.0", - "integrity": "sha512-Farc0XuUoxv10kJE65zfgZlqujR7TDu8QjwxA4YDxEE41kFM8TAw0CAK15WkQK1UTsNACiiAETZGyU279eB65Q==", + "version": "8.8.1", + "license": "MIT", + "dependencies": { + "@bugsnag/request-tracker": "^8.8.1" + }, "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@bugsnag/core": "^8.0.0" } }, "node_modules/@bugsnag/plugin-react": { - "version": "7.19.0", - "integrity": "sha512-owC4QXYJWGllMoOPcH5P7sbDIDuFLMCbjGAU6FwH5mBMObSQo+1ViSKImlTJQUFXATM8ySISTBVt7w3C6FFHng==", + "version": "8.8.0", + "license": "MIT", "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@bugsnag/core": "^8.0.0" }, "peerDependenciesMeta": { "@bugsnag/core": { @@ -1920,73 +1998,79 @@ } }, "node_modules/@bugsnag/plugin-react-native-client-sync": { - "version": "7.19.0", - "integrity": "sha512-WyK5pZuIzqVrY0h0HimwuODCo9ty9AyDY3q1pmwjrz2y8JTT21nnwUtHybLsp5Rl2oJR4tG06QkWmazgHDkWdA==", + "version": "8.8.0", + "license": "MIT", "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@bugsnag/core": "^8.0.0" } }, "node_modules/@bugsnag/plugin-react-native-event-sync": { - "version": "7.19.0", - "integrity": "sha512-OD73WFkDJAq8AheN2Jap+d17M1mPbEBc1Aulz9FCLs//QwlM2IOij8oarB1iF/wgK6FnIgLFEBPTZpGHuZUsyQ==", + "version": "8.8.0", + "license": "MIT", "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@bugsnag/core": "^8.0.0" } }, "node_modules/@bugsnag/plugin-react-native-global-error-handler": { - "version": "7.19.0", - "integrity": "sha512-zf+KIHqGEAs2ekAzJCTS0rM1nKrmpIfznBhn72xZJwyfYrh0wbvjZjClDEwxDZ24uNVUUHrIymzdqxpHqVb0lg==", + "version": "8.8.0", + "license": "MIT", "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@bugsnag/core": "^8.0.0" } }, "node_modules/@bugsnag/plugin-react-native-hermes": { - "version": "7.19.0", - "integrity": "sha512-6SGTSR6NMS2t8j02ZQ6FlA+K/nKkZqvGA+8A7WS/0M8HAShzyoMpZH10kGrU2dcCaiEtmD2T6OGBSbpF+385Dg==", + "version": "8.8.0", + "license": "MIT", "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@bugsnag/core": "^8.0.0" } }, "node_modules/@bugsnag/plugin-react-native-session": { - "version": "7.19.0", - "integrity": "sha512-PVwsUstedp9wTqJU/IKdCaMFKP2YrqHXoeBtqRTQ7FFyr0K8wsiW7nZP2jM31VS388hZWSWBlHQPA/3LZ49tNQ==", + "version": "8.8.0", + "license": "MIT", "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@bugsnag/core": "^8.0.0" } }, "node_modules/@bugsnag/plugin-react-native-unhandled-rejection": { - "version": "7.19.0", - "integrity": "sha512-+XDk0OoeM6MZhBh7kEalbRwFuhCZST6Y1jOostfz0fhrmT4FdgQYi1FWcPNsUTcjqv7M48pOFZNx8yWI0lGaYg==", + "version": "8.8.0", + "license": "MIT", "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@bugsnag/core": "^8.0.0" } }, "node_modules/@bugsnag/react-native": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@bugsnag/react-native/-/react-native-7.21.0.tgz", - "integrity": "sha512-NEm6QXY42SjYA3KdgDLTuGcPuIWHrX8/ma5Mza8RM/ELgActJutnty+1CWfXrzJwfXWcUVaYATB9nKITBcBlUg==", - "dependencies": { - "@bugsnag/core": "^7.19.0", - "@bugsnag/delivery-react-native": "^7.19.0", - "@bugsnag/plugin-console-breadcrumbs": "^7.19.0", - "@bugsnag/plugin-network-breadcrumbs": "^7.19.0", - "@bugsnag/plugin-react": "^7.19.0", - "@bugsnag/plugin-react-native-client-sync": "^7.19.0", - "@bugsnag/plugin-react-native-event-sync": "^7.19.0", - "@bugsnag/plugin-react-native-global-error-handler": "^7.19.0", - "@bugsnag/plugin-react-native-hermes": "^7.19.0", - "@bugsnag/plugin-react-native-session": "^7.19.0", - "@bugsnag/plugin-react-native-unhandled-rejection": "^7.19.0", + "version": "8.8.1", + "license": "MIT", + "dependencies": { + "@bugsnag/core": "^8.8.0", + "@bugsnag/delivery-react-native": "^8.8.0", + "@bugsnag/plugin-console-breadcrumbs": "^8.8.0", + "@bugsnag/plugin-network-breadcrumbs": "^8.8.1", + "@bugsnag/plugin-react": "^8.8.0", + "@bugsnag/plugin-react-native-client-sync": "^8.8.0", + "@bugsnag/plugin-react-native-event-sync": "^8.8.0", + "@bugsnag/plugin-react-native-global-error-handler": "^8.8.0", + "@bugsnag/plugin-react-native-hermes": "^8.8.0", + "@bugsnag/plugin-react-native-session": "^8.8.0", + "@bugsnag/plugin-react-native-unhandled-rejection": "^8.8.0", "iserror": "^0.0.2" } }, + "node_modules/@bugsnag/request-tracker": { + "version": "8.8.1", + "license": "MIT", + "dependencies": { + "@bugsnag/core": "^8.8.0" + } + }, "node_modules/@bugsnag/safe-json-stringify": { - "version": "6.0.0", - "integrity": "sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA==" + "version": "6.1.0", + "license": "MIT" }, "node_modules/@bugsnag/source-maps": { - "version": "2.3.1", - "integrity": "sha512-9xJTcf5+W7+y1fQBftSOste/3ORi+d5EeCCMdvaHSX69MKQP0lrDiSYpLwX/ErcXrTbvu7nimGGKJP2vBdF7zQ==", + "version": "2.3.3", + "license": "MIT", "dependencies": { "command-line-args": "^5.1.1", "command-line-usage": "^6.1.0", @@ -2000,13 +2084,25 @@ "bugsnag-source-maps": "bin/cli" } }, - "node_modules/@dominicstop/ts-event-emitter": { - "version": "1.1.0", - "integrity": "sha512-CcxmJIvUb1vsFheuGGVSQf4KdPZC44XolpUT34+vlal+LyQoBUOn31pjFET5M9ctOxEpt8xa0M3/2M7uUiAoJw==" + "node_modules/@colors/colors": { + "version": "1.6.0", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } }, "node_modules/@egjs/hammerjs": { "version": "2.0.17", - "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "license": "MIT", "dependencies": { "@types/hammerjs": "^2.0.36" }, @@ -2015,25 +2111,26 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.9.1", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "version": "3.4.3", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2042,19 +2139,17 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.1.tgz", - "integrity": "sha512-O7x6dMstWLn2ktjcoiNLDkAGG2EjveHL+Vvc+n0fXumkJYAcSqcVYKtwDU+hDZ0uDUsnUagSYaZrOLAYE8un1A==", + "version": "4.12.2", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", - "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", + "version": "2.1.4", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -2075,9 +2170,8 @@ }, "node_modules/@eslint/eslintrc/node_modules/ajv": { "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2091,15 +2185,22 @@ }, "node_modules/@eslint/eslintrc/node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.24.0", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -2112,9 +2213,8 @@ }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -2124,15 +2224,24 @@ }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -2141,43 +2250,69 @@ } }, "node_modules/@eslint/js": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", - "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", + "version": "8.57.1", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@flatten-js/interval-tree": { + "version": "1.1.3", + "license": "MIT" + }, "node_modules/@hapi/hoek": { "version": "9.3.0", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" }, "node_modules/@hapi/topo": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "version": "0.13.0", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -2187,15 +2322,23 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "version": "2.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, + "license": "ISC", "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -2207,125 +2350,109 @@ "node": ">=8" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "27.5.1", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=8" } }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "p-locate": "^4.1.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "p-try": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "p-limit": "^2.2.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/console": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0" }, "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/@jest/core": { - "version": "29.4.3", - "integrity": "sha512-56QvBq60fS4SPZCuM7T+7scNrkGIe7Mr6PVIXUpu48ouvRaWOFqRPV91eifvFM0ay2HmfswXiGf97NGUN5KofQ==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.4.3", - "@jest/reporters": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.4.3", - "jest-config": "^29.4.3", - "jest-haste-map": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-resolve-dependencies": "^29.4.3", - "jest-runner": "^29.4.3", - "jest-runtime": "^29.4.3", - "jest-snapshot": "^29.4.3", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "jest-watcher": "^29.4.3", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -2342,15 +2469,15 @@ } }, "node_modules/@jest/core/node_modules/@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -2358,16 +2485,16 @@ } }, "node_modules/@jest/core/node_modules/@jest/reporters": { - "version": "29.4.3", - "integrity": "sha512-sr2I7BmOjJhyqj9ANC6CTLsL4emMoka7HkQpcoMRlhCbQJjz2zsRzw0BDPiPyEFDXAbxKgGFYuQZiSJ1Y6YoTg==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -2375,13 +2502,13 @@ "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -2400,12 +2527,12 @@ } }, "node_modules/@jest/core/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -2414,21 +2541,21 @@ } }, "node_modules/@jest/core/node_modules/@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -2438,17 +2565,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/core/node_modules/@jest/transform/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, "node_modules/@jest/core/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -2460,105 +2582,42 @@ } }, "node_modules/@jest/core/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/core/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/core/node_modules/fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" } }, "node_modules/@jest/core/node_modules/jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -2570,17 +2629,17 @@ } }, "node_modules/@jest/core/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -2589,24 +2648,24 @@ } }, "node_modules/@jest/core/node_modules/jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "version": "29.6.3", "dev": true, + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/core/node_modules/jest-resolve": { - "version": "29.4.3", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -2616,11 +2675,11 @@ } }, "node_modules/@jest/core/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -2632,12 +2691,12 @@ } }, "node_modules/@jest/core/node_modules/jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", - "jest-util": "^29.4.3", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -2645,55 +2704,58 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/core/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/@jest/core/node_modules/resolve.exports": { + "version": "2.0.3", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@jest/core/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/@jest/core/node_modules/semver": { + "version": "7.7.1", "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/@jest/core/node_modules/resolve.exports": { - "version": "2.0.0", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", + "node_modules/@jest/core/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=10" + "node": ">=8" } }, "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/@jest/core/node_modules/v8-to-istanbul": { - "version": "9.1.0", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "version": "9.3.0", "dev": true, + "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" @@ -2701,8 +2763,8 @@ }, "node_modules/@jest/core/node_modules/write-file-atomic": { "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -2712,20 +2774,22 @@ } }, "node_modules/@jest/create-cache-key-function": { - "version": "29.4.3", - "integrity": "sha512-AJVFQTTy6jnZAQiAZrdOaTAPzJUrvAE/4IMe+Foav6WPhypFszqg7a4lOTyuzYQEEiT5RSrGYg9IY+/ivxiyXw==", + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3" + "@jest/types": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/create-cache-key-function/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "version": "29.6.3", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -2737,206 +2801,96 @@ } }, "node_modules/@jest/create-cache-key-function/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "version": "17.0.33", + "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/@jest/create-cache-key-function/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@jest/environment": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/create-cache-key-function/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/environment/node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/create-cache-key-function/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "29.4.3", - "integrity": "sha512-dq5S6408IxIa+lr54zeqce+QgI+CJT4nmmA+1yzFgtcsGK8c/EyiUb9XQOgz3BMKrRDfKseeOaxj2eO8LlD3lA==", - "dependencies": { - "@jest/fake-timers": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/node": "*", - "jest-mock": "^29.4.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/environment/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "version": "17.0.33", + "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/@jest/environment/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/environment/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/environment/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/expect": { - "version": "29.4.3", - "integrity": "sha512-iktRU/YsxEtumI9zsPctYUk7ptpC+AVLLk1Ax3AsA4g1C+8OOnKDkIQBDHtD5hA/+VtgMd5AWI5gNlcAlt2vxQ==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "expect": "^29.4.3", - "jest-snapshot": "^29.4.3" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.4.3", - "integrity": "sha512-/6JWbkxHOP8EoS8jeeTd9dTfc9Uawi+43oLKHfp6zzux3U2hqOOVnV3ai4RpDYHOccL6g+5nrxpoc8DmJxtXVQ==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "jest-get-type": "^29.4.3" + "jest-get-type": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.4.3", - "integrity": "sha512-4Hote2MGcCTWSD2gwl0dwbCpBRHhE6olYEuTj8FMowdg3oQWNKr2YuxenPQYZ7+PfqPY1k98wKDU4Z+Hvd4Tiw==", + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.4.3", - "jest-mock": "^29.4.3", - "jest-util": "^29.4.3" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "version": "29.6.3", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -2948,71 +2902,25 @@ } }, "node_modules/@jest/fake-timers/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "version": "17.0.33", + "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/@jest/fake-timers/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/fake-timers/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/fake-timers/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -3021,10 +2929,11 @@ } }, "node_modules/@jest/fake-timers/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -3035,36 +2944,26 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/globals": { - "version": "29.4.3", - "integrity": "sha512-8BQ/5EzfOLG7AaMcDh7yFCbfRLtsc+09E1RQmRBI4D6QQk4m6NSK/MXo+3bJrBN0yU8A2/VIcqhvsOLFmziioA==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/environment": "^29.4.3", - "@jest/expect": "^29.4.3", - "@jest/types": "^29.4.3", - "jest-mock": "^29.4.3" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -3076,81 +2975,17 @@ } }, "node_modules/@jest/globals/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/@jest/globals/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/globals/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/globals/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/reporters": { "version": "27.5.1", - "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", "dev": true, + "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^27.5.1", @@ -3190,144 +3025,80 @@ } } }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/source-map": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@jest/test-result": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@jest/console": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/test-sequencer/node_modules/@jest/console": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/schemas": { - "version": "29.4.3", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", - "dependencies": { - "@sinclair/typebox": "^0.25.16" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.4.3", - "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "27.5.1", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", - "dev": true, - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.4.3", - "integrity": "sha512-yi/t2nES4GB4G0mjLc0RInCq/cNr9dNwJxcGg8sslajua5Kb4kmozAc+qPLzplhBgfw1vLItbjyHzUN92UXicw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.4.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer/node_modules/@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", - "dev": true, - "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-sequencer/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -3336,11 +3107,11 @@ } }, "node_modules/@jest/test-sequencer/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -3352,105 +3123,27 @@ } }, "node_modules/@jest/test-sequencer/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/@jest/test-sequencer/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/test-sequencer/node_modules/anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@jest/test-sequencer/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/test-sequencer/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/test-sequencer/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/test-sequencer/node_modules/fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/@jest/test-sequencer/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/test-sequencer/node_modules/jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -3462,17 +3155,17 @@ } }, "node_modules/@jest/test-sequencer/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -3481,19 +3174,19 @@ } }, "node_modules/@jest/test-sequencer/node_modules/jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "version": "29.6.3", "dev": true, + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-sequencer/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -3505,12 +3198,12 @@ } }, "node_modules/@jest/test-sequencer/node_modules/jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", - "jest-util": "^29.4.3", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -3518,10 +3211,10 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-sequencer/node_modules/jest-worker/node_modules/supports-color": { + "node_modules/@jest/test-sequencer/node_modules/supports-color": { "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -3532,29 +3225,10 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@jest/test-sequencer/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/test-sequencer/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/transform": { "version": "27.5.1", - "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.1.0", "@jest/types": "^27.5.1", @@ -3576,258 +3250,150 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "1.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/types": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=6.0.0" } }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "license": "MIT" }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/@keystonehq/alias-sampling": { + "version": "0.1.2", + "license": "MIT" + }, + "node_modules/@keystonehq/bc-ur-registry": { + "version": "0.7.1", + "license": "Apache-2.0", "dependencies": { - "has-flag": "^4.0.0" - }, + "@ngraveio/bc-ur": "^1.1.5", + "bs58check": "^2.1.2", + "tslib": "^2.3.0" + } + }, + "node_modules/@marcbachmann/cel-js": { + "version": "7.0.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=20.19.0" } }, - "node_modules/@jest/types": { - "version": "27.5.1", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/@ngraveio/bc-ur": { + "version": "1.1.13", + "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "@keystonehq/alias-sampling": "^0.1.1", + "assert": "^2.0.0", + "bignumber.js": "^9.0.1", + "cbor-sync": "^1.0.4", + "crc": "^3.8.0", + "jsbi": "^3.1.5", + "sha.js": "^2.4.11" } }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "eslint-scope": "5.1.1" + } + }, + "node_modules/@noble/curves": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.0.0" }, "engines": { - "node": ">=8" + "node": ">= 20.19.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "2.0.0", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 20.19.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "node_modules/@noble/hashes": { + "version": "1.3.3", + "license": "MIT", "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "node": ">= 16" }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@keystonehq/bc-ur-registry": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@keystonehq/bc-ur-registry/-/bc-ur-registry-0.6.3.tgz", - "integrity": "sha512-xwOYrRgqfV5wANpHjmO1ilB2bloY2kMxf8xiZ4CG0U5Zkfwa/9wUaqN60xFaG862w/6wnMOHvLDxDm47xNSDlw==", - "dependencies": { - "@ngraveio/bc-ur": "^1.1.5", - "bs58check": "^2.1.2", - "tslib": "^2.3.0" - } - }, - "node_modules/@ngraveio/bc-ur": { - "version": "1.1.6", - "integrity": "sha512-G+2XgjXde2IOcEQeCwR250aS43/Swi7gw0FuETgJy2c3HqF8f88SXDMsIGgJlZ8jXd0GeHR4aX0MfjXf523UZg==", - "dependencies": { - "@apocentre/alias-sampling": "^0.5.3", - "assert": "^2.0.0", - "bignumber.js": "^9.0.1", - "cbor-sync": "^1.0.4", - "crc": "^3.8.0", - "jsbi": "^3.1.5", - "sha.js": "^2.4.11" - } - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "dependencies": { - "eslint-scope": "5.1.1" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@noble/hashes": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz", - "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, "node_modules/@noble/secp256k1": { "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", - "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==", "funding": [ { "type": "individual", "url": "https://paulmillr.com/funding/" } - ] + ], + "license": "MIT" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -3838,16 +3404,14 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -3856,19 +3420,10 @@ "node": ">= 8" } }, - "node_modules/@pkgr/utils": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", - "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "node_modules/@pkgr/core": { + "version": "0.2.0", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "fast-glob": "^3.3.0", - "is-glob": "^4.0.3", - "open": "^9.1.0", - "picocolors": "^1.0.0", - "tslib": "^2.6.0" - }, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -3876,1561 +3431,1722 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/@pkgr/utils/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgr/utils/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@pkgr/utils/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@pkgr/utils/node_modules/open": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", - "dev": true, - "dependencies": { - "default-browser": "^4.0.0", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@pkgr/utils/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@pkgr/utils/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@pkgr/utils/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@react-native-async-storage/async-storage": { - "version": "1.19.3", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.19.3.tgz", - "integrity": "sha512-CwGfoHCWdPOTPS+2fW6YRE1fFBpT9++ahLEroX5hkgwyoQ+TkmjOaUxixdEIoVua9Pz5EF2pGOIJzqOTMWfBlA==", + "version": "2.2.0", + "license": "MIT", "dependencies": { "merge-options": "^3.0.4" }, "peerDependencies": { - "react-native": "^0.0.0-0 || 0.60 - 0.72 || 1000.0.0" + "react-native": "^0.0.0-0 || >=0.65 <1.0" } }, "node_modules/@react-native-clipboard/clipboard": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.11.2.tgz", - "integrity": "sha512-bHyZVW62TuleiZsXNHS1Pv16fWc0fh8O9WvBzl4h2fykqZRW9a+Pv/RGTH56E3X2PqzHP38K5go8zmCZUoIsoQ==", + "version": "1.16.3", + "license": "MIT", + "workspaces": [ + "example" + ], "peerDependencies": { - "react": ">=16.0", - "react-native": ">=0.57.0" + "react": ">= 16.9.0", + "react-native": ">= 0.61.5", + "react-native-macos": ">= 0.61.0", + "react-native-windows": ">= 0.61.0" + }, + "peerDependenciesMeta": { + "react-native-macos": { + "optional": true + }, + "react-native-windows": { + "optional": true + } } }, "node_modules/@react-native-community/cli": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-10.2.4.tgz", - "integrity": "sha512-E9BUDHfLEsnjkjeJqECuCjl4E/1Ox9Nl6hkQBhEqjZm4AaQxgU7M6AyFfOgaXn5v3am16/R4ZOUTrJnGJWS3GA==", - "dependencies": { - "@react-native-community/cli-clean": "^10.1.1", - "@react-native-community/cli-config": "^10.1.1", - "@react-native-community/cli-debugger-ui": "^10.0.0", - "@react-native-community/cli-doctor": "^10.2.4", - "@react-native-community/cli-hermes": "^10.2.0", - "@react-native-community/cli-plugin-metro": "^10.2.3", - "@react-native-community/cli-server-api": "^10.1.1", - "@react-native-community/cli-tools": "^10.1.1", - "@react-native-community/cli-types": "^10.0.0", - "chalk": "^4.1.2", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.1.3.tgz", + "integrity": "sha512-sLo8cu9JyFNfuuF1C+8NJ4DHE/PEFaXGd4enkcxi/OJjGG8+sOQrdjNQ4i+cVh/2c+ah1mEMwsYjc3z0+/MqSg==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-clean": "20.1.3", + "@react-native-community/cli-config": "20.1.3", + "@react-native-community/cli-doctor": "20.1.3", + "@react-native-community/cli-server-api": "20.1.3", + "@react-native-community/cli-tools": "20.1.3", + "@react-native-community/cli-types": "20.1.3", "commander": "^9.4.1", - "execa": "^1.0.0", - "find-up": "^4.1.0", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^5.0.0", "fs-extra": "^8.1.0", "graceful-fs": "^4.1.3", - "prompts": "^2.4.0", - "semver": "^6.3.0" + "picocolors": "^1.1.1", + "prompts": "^2.4.2", + "semver": "^7.5.2" }, "bin": { - "react-native": "build/bin.js" + "rnc-cli": "build/bin.js" }, "engines": { - "node": ">=14" + "node": ">=20.19.4" } }, "node_modules/@react-native-community/cli-clean": { - "version": "10.1.1", - "integrity": "sha512-iNsrjzjIRv9yb5y309SWJ8NDHdwYtnCpmxZouQDyOljUdC9MwdZ4ChbtA4rwQyAwgOVfS9F/j56ML3Cslmvrxg==", + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-20.1.3.tgz", + "integrity": "sha512-sFLdLzapfC0scjgzBJJWYDY2RhHPjuuPkA5r6q0gc/UQH/izXpMpLrhh1DW84cMDraNACK0U62tU7ebNaQ1LMQ==", + "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "prompts": "^2.4.0" + "@react-native-community/cli-tools": "20.1.3", + "execa": "^5.0.0", + "fast-glob": "^3.3.2", + "picocolors": "^1.1.1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@react-native-community/cli-config": { + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-20.1.3.tgz", + "integrity": "sha512-n73nW0cG92oNF0r994pPqm0DjAShOm3F8LSffDYhJqNAno+h/csmv/37iL4NtSpmKIO8xqsG3uVTXz9X/hzNaQ==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "@react-native-community/cli-tools": "20.1.3", + "cosmiconfig": "^9.0.0", + "deepmerge": "^4.3.0", + "fast-glob": "^3.3.2", + "joi": "^17.2.1", + "picocolors": "^1.1.1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@react-native-community/cli-config-android": { + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-20.1.3.tgz", + "integrity": "sha512-DNHDP+OWLyhKShGciBqPcxhxfp1Z/7GQcb4F+TGyCeKQAr+JdnUjRXN3X+YCU/v+g2kbYYyRJKlGabzkVvdrAw==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "@react-native-community/cli-tools": "20.1.3", + "fast-glob": "^3.3.2", + "fast-xml-parser": "^5.3.6", + "picocolors": "^1.1.1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@react-native-community/cli-config-apple": { + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-apple/-/cli-config-apple-20.1.3.tgz", + "integrity": "sha512-QX9B83nAfCPs0KiaYz61kAEHWr9sttooxzRzNdQwvZTwnsIpvWOT9GvMMj/19OeXiQzMJBzZX0Pgt6+spiUsDQ==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@react-native-community/cli-tools": "20.1.3", + "execa": "^5.0.0", + "fast-glob": "^3.3.2", + "picocolors": "^1.1.1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@react-native-community/cli-clean/node_modules/cross-spawn": { - "version": "6.0.5", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "node_modules/@react-native-community/cli-doctor": { + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-20.1.3.tgz", + "integrity": "sha512-EI+mAPWn255/WZ4CQohy1I049yiaxVr41C3BeQ2BCyhxODIDR8XRsLzYb1t9MfqK/C3ZncUN2mPSRXFeKPPI1w==", + "license": "MIT", "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "@react-native-community/cli-config": "20.1.3", + "@react-native-community/cli-platform-android": "20.1.3", + "@react-native-community/cli-platform-apple": "20.1.3", + "@react-native-community/cli-platform-ios": "20.1.3", + "@react-native-community/cli-tools": "20.1.3", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.13.0", + "execa": "^5.0.0", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "picocolors": "^1.1.1", + "semver": "^7.5.2", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=4.8" + "node": ">=10" } }, - "node_modules/@react-native-community/cli-clean/node_modules/execa": { - "version": "1.0.0", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "node_modules/@react-native-community/cli-platform-android": { + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-20.1.3.tgz", + "integrity": "sha512-bzB9ELPOISuqgtDZXFPQlkuxx1YFkNx3cNgslc5ElCrk+5LeCLQLIBh/dmIuK8rwUrPcrramjeBj++Noc+TaAA==", + "license": "MIT", "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" + "@react-native-community/cli-config-android": "20.1.3", + "@react-native-community/cli-tools": "20.1.3", + "execa": "^5.0.0", + "logkitty": "^0.7.1", + "picocolors": "^1.1.1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/get-stream": { - "version": "4.1.0", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "node_modules/@react-native-community/cli-platform-apple": { + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-20.1.3.tgz", + "integrity": "sha512-XJ+DqAD4hkplWVXK5AMgN7pP9+4yRSe5KfZ/b42+ofkDBI55ALlUmX+9HWE3fMuRjcotTCoNZqX2ov97cFDXpQ==", + "license": "MIT", "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" + "@react-native-community/cli-config-apple": "20.1.3", + "@react-native-community/cli-tools": "20.1.3", + "execa": "^5.0.0", + "fast-xml-parser": "^5.3.6", + "picocolors": "^1.1.1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "node_modules/@react-native-community/cli-platform-ios": { + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-20.1.3.tgz", + "integrity": "sha512-2qL48SINotuHbZO73cgqSwqd/OWNx0xTbFSdujhpogV4p8BNwYYypfjh4vJY5qJEB5PxuoVkMXT+aCADpg9nBg==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-platform-apple": "20.1.3" } }, - "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { - "version": "1.1.0", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "engines": { - "node": ">=0.10.0" + "node_modules/@react-native-community/cli-server-api": { + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-20.1.3.tgz", + "integrity": "sha512-hsNsdUKZDd2T99OuNuiXz4VuvLa1UN0zcxefmPjXQgI0byrBLzzDr+o7p03sKuODSzKi2h+BMnUxiS07HACQLA==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "20.1.3", + "body-parser": "^2.2.2", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "open": "^6.2.0", + "pretty-format": "^29.7.0", + "serve-static": "^1.13.1", + "strict-url-sanitise": "0.0.1", + "ws": "^6.2.3" } }, - "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { - "version": "2.0.2", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "node_modules/@react-native-community/cli-tools": { + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.1.3.tgz", + "integrity": "sha512-EAn0vPCMxtHhfWk2UwLmSUfPfLUnFgC7NjiVJVTKJyVk5qGnkPfoT8te/1IUXFTysUB0F0RIi+NgDB4usFOLeA==", + "license": "MIT", "dependencies": { - "path-key": "^2.0.0" + "@vscode/sudo-prompt": "^9.0.0", + "appdirsjs": "^1.2.4", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "launch-editor": "^2.9.1", + "mime": "^2.4.1", + "ora": "^5.4.1", + "picocolors": "^1.1.1", + "prompts": "^2.4.2", + "semver": "^7.5.2" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/@react-native-community/cli-clean/node_modules/path-key": { - "version": "2.0.1", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "engines": { - "node": ">=4" + "node_modules/@react-native-community/cli-types": { + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-20.1.3.tgz", + "integrity": "sha512-IdAcegf0pH1hVraxWTG1ACLkYC0LDQfqtaEf42ESyLIF3Xap70JzL/9tAlxw7lSCPZPFWhrcgU0TBc4SkC/ecw==", + "license": "MIT", + "dependencies": { + "joi": "^17.2.1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/@react-native-community/cli/node_modules/semver": { + "version": "7.7.1", + "license": "ISC", "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@react-native-community/cli-clean/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@react-native-documents/picker": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@react-native-documents/picker/-/picker-12.0.1.tgz", + "integrity": "sha512-vpJKb4t/5bnxe9+gQl+plJfKrrIsmYwANGhNH2B9E1dS1+6FDBzg4Dwmcq4ueaGfkRKEPJ606mJttVEH1ZKZaA==", + "license": "MIT", + "funding": { + "url": "https://github.com/react-native-documents/document-picker?sponsor=1" + }, + "peerDependencies": { + "react": "*", + "react-native": ">=0.79.0" + } + }, + "node_modules/@react-native-vector-icons/common": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/common/-/common-13.0.0.tgz", + "integrity": "sha512-FJ0Ql5UTGVtK0ak4vLTxmhFHadb8NmTk4yOWoggh7UvC2pVQNyJK7L9nIZeIZ0IaVJtKfmKXtBWA0nKqqzQ/FQ==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "find-up": "^7.0.0", + "picocolors": "^1.1.1", + "plist": "^3.1.0" + }, + "bin": { + "rnvi-update-plist": "lib/commonjs/scripts/updatePlist.js" }, "engines": { - "node": ">=8" + "node": ">=20.19.0 <21.0.0 || >=22.0.0" + }, + "peerDependencies": { + "@react-native-vector-icons/get-image": "^13.0.0", + "@react-native/assets-registry": "*", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@react-native-vector-icons/get-image": { + "optional": true + }, + "@react-native/assets-registry": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-config": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-10.1.1.tgz", - "integrity": "sha512-p4mHrjC+s/ayiNVG6T35GdEGdP6TuyBUg5plVGRJfTl8WT6LBfLYLk+fz/iETrEZ/YkhQIsQcEUQC47MqLNHog==", + "node_modules/@react-native-vector-icons/common/node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "cosmiconfig": "^5.1.0", - "deepmerge": "^3.2.0", - "glob": "^7.1.3", - "joi": "^17.2.1" + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@react-native-vector-icons/common/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "p-locate": "^6.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@react-native-vector-icons/common/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "yocto-queue": "^1.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@react-native-vector-icons/common/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "p-limit": "^4.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@react-native-community/cli-config/node_modules/deepmerge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", - "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", + "node_modules/@react-native-vector-icons/common/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/@react-native-community/cli-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@react-native-vector-icons/common/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@react-native-vector-icons/entypo": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/entypo/-/entypo-13.1.1.tgz", + "integrity": "sha512-K3uZ/S0Nr0a/vuXw81tZDhKJaUfaGeTG+50vPHO60Ucl/L9b3O4KUtzMJa7zd0c400CO0vl5Lr97Wk266eXwLQ==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@react-native-vector-icons/common": "^13.0.0" }, "engines": { - "node": ">=8" + "node": ">= 18.0.0" + }, + "peerDependencies": { + "@expo/config-plugins": ">=10.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-debugger-ui": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-10.0.0.tgz", - "integrity": "sha512-8UKLcvpSNxnUTRy8CkCl27GGLqZunQ9ncGYhSrWyKrU9SWBJJGeZwi2k2KaoJi5FvF2+cD0t8z8cU6lsq2ZZmA==", + "node_modules/@react-native-vector-icons/fontawesome": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/fontawesome/-/fontawesome-13.1.1.tgz", + "integrity": "sha512-GD1eOt1YmkxbUmHZzxpCGMMC3WCif3edo8RKMnv0dlf07KNLktfQDh0mVYJhU4d203oyeTk1E5GWBjNDRw3zWg==", + "license": "MIT", "dependencies": { - "serve-static": "^1.13.1" + "@react-native-vector-icons/common": "^13.0.0" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "@expo/config-plugins": ">=10.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-doctor": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-10.2.4.tgz", - "integrity": "sha512-hEtgAqSyIASByhoZlv7WVvdoW4NBdn8vJh/X+dQBRBEXyZk1741/+CtiazwKkuliEhl7cdg4Mg99zgRLCXKAzg==", + "node_modules/@react-native-vector-icons/fontawesome6": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/fontawesome6/-/fontawesome6-13.1.1.tgz", + "integrity": "sha512-AwZSCk+2dakqzlBEEKwi/FBc6qg4TtGPPyj2OVt0HcA8sy+gMa0u5iW7hao/Fmq3ad0LQz9HTUYUeslH2jS0jA==", + "license": "MIT", "dependencies": { - "@react-native-community/cli-config": "^10.1.1", - "@react-native-community/cli-platform-ios": "^10.2.4", - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "envinfo": "^7.7.2", - "execa": "^1.0.0", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "prompts": "^2.4.0", - "semver": "^6.3.0", - "strip-ansi": "^5.2.0", - "sudo-prompt": "^9.0.0", - "wcwidth": "^1.0.1" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "@react-native-vector-icons/common": "^13.0.0" + }, "engines": { - "node": ">=6" + "node": ">= 18.0.0" + }, + "peerDependencies": { + "@expo/config-plugins": ">=10.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-doctor/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@react-native-vector-icons/ionicons": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/ionicons/-/ionicons-13.1.1.tgz", + "integrity": "sha512-OAIEf7HW5SnDi+YMRR1W/HBwzWmQiQ4msY8aSQRdVisPvbVFvO6vaWJdV33QI2aj1/5lVLh9oKJGcRsSaBzh2Q==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@react-native-vector-icons/common": "^13.0.0" }, "engines": { - "node": ">=8" + "node": ">= 18.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "@expo/config-plugins": ">=10.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-doctor/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@react-native-vector-icons/material-design-icons": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/material-design-icons/-/material-design-icons-13.1.1.tgz", + "integrity": "sha512-bKkai9GSMOrqIwKskHZuegejgO6bLp7xNgp7YdeLprkEK44/HsATjCpXhwvRPYq9RSHdOvrFFKBIKLZbkpijSw==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@react-native-vector-icons/common": "^13.0.0" }, "engines": { - "node": ">=10" + "node": ">= 18.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "@expo/config-plugins": ">=10.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-doctor/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@react-native-vector-icons/material-icons": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/material-icons/-/material-icons-13.1.1.tgz", + "integrity": "sha512-u13/5ITff+qGBZBnv3QQ+vLNCNgJzxUfXnMnZDK1rHgpUjH6lex3tSORX5XLYbCuaHDW7WFF0cqzoaephYZApg==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@react-native-vector-icons/common": "^13.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">= 18.0.0" + }, + "peerDependencies": { + "@expo/config-plugins": ">=10.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-doctor/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "node_modules/@react-native/assets-registry": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.85.3.tgz", + "integrity": "sha512-u9ZiYP23vA2IFtdFQFmetzSmk6SM0xgKIoiOsr1hXNHjHaLhOm+/Ph1ud57wX6+Dbwdzx8coJgnzSKL3W21PCg==", + "license": "MIT", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } }, - "node_modules/@react-native-community/cli-doctor/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.85.3.tgz", + "integrity": "sha512-Wc94zGfeFG8Njf9SHMPfYZP04kjigkOps6F1TYTvd7ZVXuGxqseCDgxc50LWcOhOCLypI9n3oVVqz81C3p44ZA==", + "license": "MIT", "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "@babel/traverse": "^7.29.0", + "@react-native/codegen": "0.85.3" }, "engines": { - "node": ">=4.8" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" + "node_modules/@react-native/babel-preset": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.85.3.tgz", + "integrity": "sha512-fD7fxEhkJB/aF57tWoXjaAWpklfrExYZS3k6aXPP3BQ77DZY7gvf/b7dbirwjID6NVnP1JDRJyTuPBGr0K/vlw==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@react-native/babel-plugin-codegen": "0.85.3", + "babel-plugin-syntax-hermes-parser": "0.33.3", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "node_modules/@react-native/codegen": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.85.3.tgz", + "integrity": "sha512-/JkS1lGLyzBWP1FbgDwaqEf7qShIC6pUC1M0a/YMAd/v4iqR24MRkQWe7jkYvcBQ2LpEhs5NGE9InhxSv21zCA==", + "license": "MIT", "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "@babel/core": "^7.25.2", + "@babel/parser": "^7.29.0", + "hermes-parser": "0.33.3", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", + "yargs": "^17.6.2" }, "engines": { - "node": ">=6" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "node_modules/@react-native/community-cli-plugin": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.85.3.tgz", + "integrity": "sha512-fs85dmbIqNmtzEixDb0g+q6R3Vt4H9eAt8/inIZdDKfjN76+sUJA2r1nxODQ76bU23MrIbz8sI7KFBPaWk/zQw==", + "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "@react-native/dev-middleware": "0.85.3", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "metro": "^0.84.3", + "metro-config": "^0.84.3", + "metro-core": "^0.84.3", + "semver": "^7.1.3" }, "engines": { - "node": ">=6" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@react-native-community/cli": "*", + "@react-native/metro-config": "0.85.3" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "optional": true + }, + "@react-native/metro-config": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-doctor/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@react-native/community-cli-plugin/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "node_modules/@react-native/debugger-frontend": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.85.3.tgz", + "integrity": "sha512-uAu7rM5o/Np1zgp6fi5zM1sP1aB8DcS7DdOLcj/TkSutOAjkMqqd2lWt1/+3S7qXexRHVK5XcP+o3VXo4L/V0A==", + "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "node_modules/@react-native/debugger-shell": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.85.3.tgz", + "integrity": "sha512-/jRAaT9boiCttIcEwS02WPwYkUihqsjSaK/TMtHz05vT6uMgac9PaQt5kzBQLIABv5aEIa5gtrMmKVz49MjkjQ==", + "license": "MIT", "dependencies": { - "path-key": "^2.0.0" + "cross-spawn": "^7.0.6", + "debug": "^4.4.0", + "fb-dotslash": "0.5.8" }, "engines": { - "node": ">=4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "node_modules/@react-native/dev-middleware": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.85.3.tgz", + "integrity": "sha512-JYzBiT4A8w+KQt+dOD5v+ti+tDrGoPnsSTuApq3Ls4RB5sfWbDlYMyz3dbc8qBIHz9tv0sQ5+eOu6Xwqzr5AQA==", + "license": "MIT", "dependencies": { - "ansi-regex": "^4.1.0" + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.85.3", + "@react-native/debugger-shell": "0.85.3", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.3.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "serve-static": "^1.16.2", + "ws": "^7.5.10" }, "engines": { - "node": ">=6" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@react-native/dev-middleware/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "is-docker": "^2.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/@react-native-community/cli-hermes": { - "version": "10.2.0", - "integrity": "sha512-urfmvNeR8IiO/Sd92UU3xPO+/qI2lwCWQnxOkWaU/i2EITFekE47MD6MZrfVulRVYRi5cuaFqKZO/ccOdOB/vQ==", - "dependencies": { - "@react-native-community/cli-platform-android": "^10.2.0", - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5" - } - }, - "node_modules/@react-native-community/cli-hermes/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@react-native/dev-middleware/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" }, "engines": { "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-hermes/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8.3.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-hermes/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@react-native/eslint-config": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.85.3.tgz", + "integrity": "sha512-CvE+1H4be7eZXpadoBDnz7B3ooK2Tl/tvbW2+odrsR22Afs2Q4m9fJtKD8lD8/LCufttsT5pnGIhP/ugO6x/mw==", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@babel/core": "^7.25.2", + "@babel/eslint-parser": "^7.25.1", + "@react-native/eslint-plugin": "0.85.3", + "@typescript-eslint/eslint-plugin": "^8.36.0", + "@typescript-eslint/parser": "^8.36.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-ft-flow": "^2.0.1", + "eslint-plugin-jest": "^29.0.1", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-native": "^5.0.0" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-hermes/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@react-native-community/cli-hermes/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.0 || ^9.0.0", + "prettier": ">=2" } }, - "node_modules/@react-native-community/cli-hermes/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-10.2.0.tgz", - "integrity": "sha512-CBenYwGxwFdObZTn1lgxWtMGA5ms2G/ALQhkS+XTAD7KHDrCxFF9yT/fnAjFZKM6vX/1TqGI1RflruXih3kAhw==", - "dependencies": { - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "glob": "^7.1.3", - "logkitty": "^0.7.1" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { - "node": ">=7.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, + "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=4.8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "dev": true, + "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "node_modules/@react-native/eslint-config/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": "18 || 20 || >=22" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "node_modules/@react-native/eslint-config/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^2.0.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "engines": { - "node": ">=4" + "node": "18 || 20 || >=22" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/@react-native/eslint-config/node_modules/eslint-config-prettier": { + "version": "8.10.0", + "dev": true, + "license": "MIT", "bin": { - "semver": "bin/semver" + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@react-native/eslint-config/node_modules/eslint-plugin-jest": { + "version": "29.15.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.0.tgz", + "integrity": "sha512-ZCGr7vTH2WSo2hrK5oM2RULFmMruQ7W3cX7YfwoTiPfzTGTFBMmrVIz45jZHd++cGKj/kWf02li/RhTGcANJSA==", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@typescript-eslint/utils": "^8.0.0" }, "engines": { - "node": ">=8" + "node": "^20.12.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "jest": "*", + "typescript": ">=4.8.4 <6.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + }, + "typescript": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-10.2.4.tgz", - "integrity": "sha512-/6K+jeRhcGojFIJMWMXV2eY5n/In+YUzBr/DKWQOeHBOHkESRNheG310xSAIjgB46YniSSUKhSyeuhalTbm9OQ==", + "node_modules/@react-native/eslint-config/node_modules/eslint-plugin-react-native": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-native/-/eslint-plugin-react-native-5.0.0.tgz", + "integrity": "sha512-VyWlyCC/7FC/aONibOwLkzmyKg4j9oI8fzrk9WYNs4I8/m436JuOTAFwLvEn1CVvc7La4cPfbCyspP4OYpP52Q==", + "dev": true, + "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "fast-xml-parser": "^4.0.12", - "glob": "^7.1.3", - "ora": "^5.4.1" + "eslint-plugin-react-native-globals": "^0.1.1" + }, + "peerDependencies": { + "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, + "node_modules/@react-native/eslint-config/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@react-native/eslint-config/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@react-native/eslint-config/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=10" + "node": "18 || 20 || >=22" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" + "node_modules/@react-native/eslint-config/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "node_modules/@react-native/eslint-config/node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/@react-native/eslint-plugin": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.85.3.tgz", + "integrity": "sha512-xUt6BZkIEPxNpsHsZc/FsjsyslrCW5NrGZDFIayyxQxg0zwwd0nXWFZ0qDfCeA75qYYTnboOwIuDIqykzJp61Q==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=4.8" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.85.3.tgz", + "integrity": "sha512-39dY2j50Q1pntejzwt3XL7vwXtrj8jcIfHq6E+gyu3jzYxZJVvMkMutQ39vSg6zinIQOX36oQDhidXUbCXzgoA==", + "license": "MIT", "engines": { - "node": ">=6" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "node_modules/@react-native/jest-preset": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/jest-preset/-/jest-preset-0.85.3.tgz", + "integrity": "sha512-ALPSrM0q2fU+5AXcOXzDKx7rxVKPMvygAZfsTWLdrGRVWIqf/HEfM0R8euQqIKUqmEuQ1TxMWN+px3h6gc4vow==", + "dev": true, + "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "@jest/create-cache-key-function": "^29.7.0", + "@react-native/js-polyfills": "0.85.3", + "babel-jest": "^29.7.0", + "jest-environment-node": "^29.7.0", + "regenerator-runtime": "^0.13.2" }, "engines": { - "node": ">=6" + "node": ">= 20.19.4" + }, + "peerDependencies": { + "react": "^19.2.3" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } + "node_modules/@react-native/jest-preset/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, + "license": "MIT" }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "node_modules/@react-native/js-polyfills": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.85.3.tgz", + "integrity": "sha512-U2+aMshIXf1uFn77tpBb/xhHWB9vkVrMpt7kkucAugF8hJKYTDGB587X7WwelHduK2KBfhl4giSv0rzZGoef9A==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.85.3.tgz", + "integrity": "sha512-omuKq+r7jM4XvCMIlNMPP7Up3SyB8o5EAdZtF7YXniKyq7UOMBqhYHFqgsdOXr0lT+3ADf7VCJG3sb82jlBrrQ==", + "license": "MIT", "dependencies": { - "path-key": "^2.0.0" + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.85.3", + "hermes-parser": "0.33.3", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">=4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "node_modules/@react-native/metro-config": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.85.3.tgz", + "integrity": "sha512-sVo6HepUmCcpdfozEf91lA0FjpLNNZYu/Zi9FiYiAQTK8pzATXDVTqhvdxpFrQn435p5eUTSbllvbH/KN+bnyA==", + "license": "MIT", + "dependencies": { + "@react-native/js-polyfills": "0.85.3", + "@react-native/metro-babel-transformer": "0.85.3", + "metro-config": "^0.84.3", + "metro-runtime": "^0.84.3" + }, "engines": { - "node": ">=4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } + "node_modules/@react-native/normalize-colors": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.85.3.tgz", + "integrity": "sha512-hj0PScZEhIbcOvQV5yMKX3ha4XEIOy/SVE1Rrpp0beW0dpNLOgSC7KDxGewmDnIHK9YdQUXGY9eMEfShUMIaZw==", + "license": "MIT" }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@react-native/typescript-config": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/typescript-config/-/typescript-config-0.85.3.tgz", + "integrity": "sha512-F2Ign3lv/99R5HMDiaQE6NpRdopn87VuXgfHABSk0iwzouLFk1fcwaMkJUmjhnxrQagsUwxOWp4WTPwEvRRazQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.85.3.tgz", + "integrity": "sha512-dsCjI//OIPEUJMyNHp4l7zNLVjCx7bcaRUceOCkU+IB17hkbtbGWvi7HjGFSzy7FJGmS/MOlcfpb72xXiy1Oig==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">=8" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@types/react": "^19.2.0", + "react": "*", + "react-native": "0.85.3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-plugin-metro": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-10.2.3.tgz", - "integrity": "sha512-jHi2oDuTePmW4NEyVT8JEGNlIYcnFXCSV2ZMp4rnDrUk4TzzyvS3IMvDlESEmG8Kry8rvP0KSUx/hTpy37Sbkw==", + "node_modules/@react-navigation/core": { + "version": "7.17.4", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.17.4.tgz", + "integrity": "sha512-Rv9E2oNNQEkPGpmu9q+vJwGJRSQR6LBg5L+Yo1QHjtwGbHUbjkIKOdYymDZoZYgNzX2OD4rAIlfuzbDKa3cCeA==", + "license": "MIT", "dependencies": { - "@react-native-community/cli-server-api": "^10.1.1", - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "metro": "0.73.10", - "metro-config": "0.73.10", - "metro-core": "0.73.10", - "metro-react-native-babel-transformer": "0.73.10", - "metro-resolver": "0.73.10", - "metro-runtime": "0.73.10", - "readline": "^1.3.0" + "@react-navigation/routers": "^7.5.5", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.3.11", + "query-string": "^7.1.3", + "react-is": "^19.1.0", + "use-latest-callback": "^0.2.4", + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "react": ">= 18.2.0" } }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@react-navigation/core/node_modules/react-is": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT" + }, + "node_modules/@react-navigation/devtools": { + "version": "7.0.58", + "resolved": "https://registry.npmjs.org/@react-navigation/devtools/-/devtools-7.0.58.tgz", + "integrity": "sha512-WpADcM0n+QHP1RMMmKZPc4reuvwTyX41gnJCdipjNUG0+VBNOkDyJZpAkeJqOJg2BIjSwsKcTAph3xkmXBjXVA==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.3.11", + "stacktrace-parser": "^0.1.11" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "react": ">= 18.2.0" } }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@react-navigation/drawer": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-7.10.2.tgz", + "integrity": "sha512-/ccYFvBPJNzOYioiMQsqjAR4dcQ+7+yjzcuMDTKgsMahLD7Jn7FdOFNtGwMaIQWhfK8KFVMH2KOXAlH/uAGZXw==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "@react-navigation/elements": "^2.9.18", + "color": "^4.2.3", + "react-native-drawer-layout": "^4.2.4", + "use-latest-callback": "^0.2.4" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "@react-navigation/native": "^7.2.4", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-gesture-handler": ">= 2.0.0", + "react-native-reanimated": ">= 2.0.0", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" } }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@react-navigation/elements": { + "version": "2.9.18", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.18.tgz", + "integrity": "sha512-mKEvDr6CkCVYZSb8W9WubNseihL+1c8M7ktZJCTCbMk8rQgdQfkdRNwpSUQKspdGpUHCb9cyzvaiuzl1NtjVgw==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "color": "^4.2.3", + "use-latest-callback": "^0.2.4", + "use-sync-external-store": "^1.5.0" }, - "engines": { - "node": ">=7.0.0" + "peerDependencies": { + "@react-native-masked-view/masked-view": ">= 0.2.0", + "@react-navigation/native": "^7.2.4", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0" + }, + "peerDependenciesMeta": { + "@react-native-masked-view/masked-view": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "node_modules/@react-navigation/native": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.2.4.tgz", + "integrity": "sha512-eWC2D3JjhYLId2fVTZhhCiUpWIaPhO9XyEb7Wq8ElmOHyIODlbOzgZ0rKia02OIsDKr9BzZl2sK1dL70yMxDaw==", + "license": "MIT", "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "@react-navigation/core": "^7.17.4", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.3.11", + "use-latest-callback": "^0.2.4" }, - "engines": { - "node": ">=4.8" + "peerDependencies": { + "react": ">= 18.2.0", + "react-native": "*" } }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "node_modules/@react-navigation/native-stack": { + "version": "7.15.1", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.15.1.tgz", + "integrity": "sha512-kNrJggwoB/onC0MpZIuZ6qaqeAziFchz+W9txBzhd6qbWmB1OkPVUnu6fWgc6BQc7MeMf59djVmqgX+6kJU1Ug==", + "license": "MIT", "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "@react-navigation/elements": "^2.9.18", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0", + "warn-once": "^0.1.1" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "@react-navigation/native": "^7.2.4", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" } }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "node_modules/@react-navigation/routers": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.5.tgz", + "integrity": "sha512-9/hhMte12Kgu+pMnLfA4EWJ0OQmIEAMVMX06FPH2yGkEQSQ3JhhCN/GkcRikzQhtEi97VYYQA15umptBUShcOQ==", + "license": "MIT", "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" + "nanoid": "^3.3.11" } }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@realm/fetch": { + "version": "0.1.1", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/is-stream": { + "node_modules/@rtsao/scc": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "engines": { - "node": ">=0.10.0" + "dev": true, + "license": "MIT" + }, + "node_modules/@scure/base": { + "version": "2.0.0", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "node_modules/@scure/btc-signer": { + "version": "2.0.1", + "license": "MIT", "dependencies": { - "path-key": "^2.0.0" + "@noble/curves": "~2.0.0", + "@noble/hashes": "~2.0.0", + "@scure/base": "~2.0.0", + "micro-packed": "~0.8.0" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/path-key": { + "node_modules/@scure/btc-signer/node_modules/@noble/hashes": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "license": "MIT", "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" + "node": ">= 20.19.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@react-native-community/cli-server-api": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-10.1.1.tgz", - "integrity": "sha512-NZDo/wh4zlm8as31UEBno2bui8+ufzsZV+KN7QjEJWEM0levzBtxaD+4je0OpfhRIIkhaRm2gl/vVf7OYAzg4g==", + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", "dependencies": { - "@react-native-community/cli-debugger-ui": "^10.0.0", - "@react-native-community/cli-tools": "^10.1.1", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.0", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^7.5.1" + "@hapi/hoek": "^9.0.0" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" + "type-detect": "4.0.8" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { - "version": "15.0.15", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz", - "integrity": "sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg==", + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@types/yargs-parser": "*" + "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@spsina/bip47": { + "version": "1.0.1", + "resolved": "git+ssh://git@github.com/BlueWallet/bip47.git#df823454092a9993edeea11d663f8eb9a522a174", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "bip32": "^3.0.1", + "bip39": "^3.0.4", + "bitcoinjs-lib": "^6.0.1", + "bs58check": "^2.1.1", + "create-hmac": "^1.1.7", + "ecpair": "^2.0.1", + "tiny-secp256k1": "^1.1.6" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=6.0.0" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@spsina/bip47/node_modules/bip32": { + "version": "3.1.0", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "bs58check": "^2.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.2", + "typeforce": "^1.11.5", + "wif": "^2.0.6" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=6.0.0" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@spsina/bip47/node_modules/bitcoinjs-lib": { + "version": "6.1.7", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@noble/hashes": "^1.2.0", + "bech32": "^2.0.0", + "bip174": "^2.1.1", + "bs58check": "^3.0.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2" }, "engines": { - "node": ">=7.0.0" + "node": ">=8.0.0" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@react-native-community/cli-server-api/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "node_modules/@spsina/bip47/node_modules/bitcoinjs-lib/node_modules/bs58check": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "node_modules/@spsina/bip47/node_modules/bs58": { + "version": "5.0.0", + "license": "MIT", "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" + "base-x": "^4.0.0" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "node_modules/@react-native-community/cli-server-api/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@spsina/bip47/node_modules/ecpair": { + "version": "2.1.0", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "randombytes": "^2.1.0", + "typeforce": "^1.18.0", + "wif": "^2.0.6" }, "engines": { - "node": ">=8" + "node": ">=8.0.0" } }, - "node_modules/@react-native-community/cli-tools": { - "version": "10.1.1", - "integrity": "sha512-+FlwOnZBV+ailEzXjcD8afY2ogFEBeHOw/8+XXzMgPaquU2Zly9B+8W089tnnohO3yfiQiZqkQlElP423MY74g==", + "node_modules/@testing-library/react-native": { + "version": "13.2.0", + "dev": true, + "license": "MIT", "dependencies": { - "appdirsjs": "^1.2.4", "chalk": "^4.1.2", - "find-up": "^5.0.0", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^6.3.0", - "shell-quote": "^1.7.3" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" + "jest-matcher-utils": "^29.7.0", + "pretty-format": "^29.7.0", + "redent": "^3.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "node": ">=18" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "jest": ">=29.0.0", + "react": ">=18.2.0", + "react-native": ">=0.71", + "react-test-renderer": ">=18.2.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependenciesMeta": { + "jest": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-tools/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/@react-native-community/cli-tools/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@react-native-community/cli-tools/node_modules/find-up": { - "version": "5.0.0", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, + "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "@babel/types": "^7.0.0" } }, - "node_modules/@react-native-community/cli-tools/node_modules/locate-path": { - "version": "6.0.0", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/@react-native-community/cli-tools/node_modules/p-locate": { - "version": "5.0.0", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@babel/types": "^7.20.7" } }, - "node_modules/@react-native-community/cli-tools/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@types/bip38": { + "version": "3.1.2", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@types/node": "*" } }, - "node_modules/@react-native-community/cli-types": { - "version": "10.0.0", - "integrity": "sha512-31oUM6/rFBZQfSmDQsT1DX/5fjqfxg7sf2u8kTPJK7rXVya5SRpAMaCXsPAG0omsmJxXt+J9HxUi3Ic+5Ux5Iw==", + "node_modules/@types/bn.js": { + "version": "4.11.6", + "license": "MIT", "dependencies": { - "joi": "^17.2.1" + "@types/node": "*" } }, - "node_modules/@react-native-community/cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@types/bs58check": { + "version": "2.1.2", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "@types/node": "*" } }, - "node_modules/@react-native-community/cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@types/create-hash": { + "version": "1.2.6", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "@types/node": "*" } }, - "node_modules/@react-native-community/cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@types/node": "*" } }, - "node_modules/@react-native-community/cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "license": "MIT" }, - "node_modules/@react-native-community/cli/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "engines": { - "node": "^12.20.0 || >=14" - } + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "license": "MIT" }, - "node_modules/@react-native-community/cli/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "license": "MIT", "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/@react-native-community/cli/node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/@react-native-community/cli/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "license": "MIT", "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" + "@types/istanbul-lib-report": "*" } }, - "node_modules/@react-native-community/cli/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "node_modules/@types/jest": { + "version": "29.5.14", + "dev": true, + "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" + "expect": "^29.0.0", + "pretty-format": "^29.0.0" } }, - "node_modules/@react-native-community/cli/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "node_modules/@types/json5": { + "version": "0.0.29", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.14.0", + "license": "MIT", "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" + "undici-types": "~6.21.0" } }, - "node_modules/@react-native-community/cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "license": "MIT" }, - "node_modules/@react-native-community/cli/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "engines": { - "node": ">=0.10.0" + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" } }, - "node_modules/@react-native-community/cli/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "node_modules/@types/react-test-renderer": { + "version": "19.1.0", + "license": "MIT", "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" + "@types/react": "*" } }, - "node_modules/@react-native-community/cli/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "engines": { - "node": ">=4" - } + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "dev": true, + "license": "MIT" }, - "node_modules/@react-native-community/cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "license": "MIT" + }, + "node_modules/@types/wif": { + "version": "2.0.5", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@types/node": "*" } }, - "node_modules/@react-native-community/eslint-config": { - "version": "3.2.0", - "integrity": "sha512-ZjGvoeiBtCbd506hQqwjKmkWPgynGUoJspG8/MuV/EfKnkjCtBmeJvq2n+sWbWEvL9LWXDp2GJmPzmvU5RSvKQ==", + "node_modules/@types/yargs": { + "version": "16.0.9", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.14.0", - "@babel/eslint-parser": "^7.18.2", - "@react-native-community/eslint-plugin": "^1.1.0", - "@typescript-eslint/eslint-plugin": "^5.30.5", - "@typescript-eslint/parser": "^5.30.5", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-ft-flow": "^2.0.1", - "eslint-plugin-jest": "^26.5.3", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-native": "^4.0.0" - }, - "peerDependencies": { - "eslint": ">=8", - "prettier": ">=2" + "@types/yargs-parser": "*" } }, - "node_modules/@react-native-community/eslint-config/node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5438,26 +5154,26 @@ } } }, - "node_modules/@react-native-community/eslint-config/node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5465,74 +5181,144 @@ } } }, - "node_modules/@react-native-community/eslint-config/node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@react-native-community/eslint-config/node_modules/eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", "dev": true, + "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { - "node": ">=12.0.0" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { - "eslint-config-prettier": { + "typescript": { "optional": true } } }, - "node_modules/@react-native-community/eslint-config/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@react-native-community/eslint-config/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "lru-cache": "^6.0.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -5540,2262 +5326,2216 @@ "node": ">=10" } }, - "node_modules/@react-native-community/eslint-config/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@react-native-community/eslint-plugin": { - "version": "1.3.0", - "integrity": "sha512-+zDZ20NUnSWghj7Ku5aFphMzuM9JulqCW+aPXT6IfIXFbb8tzYTTOSeRFOtuekJ99ibW2fUCSsjuKNlwDIbHFg==", - "dev": true - }, - "node_modules/@react-native-community/push-notification-ios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@react-native-community/push-notification-ios/-/push-notification-ios-1.11.0.tgz", - "integrity": "sha512-nfkUs8P2FeydOCR4r7BNmtGxAxI22YuGP6RmqWt6c8EEMUpqvIhNKWkRSFF3pHjkgJk2tpRb9wQhbezsqTyBvA==", + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "dev": true, + "license": "MIT", "dependencies": { - "invariant": "^2.2.4" + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "react": ">=16.6.3", - "react-native": ">=0.58.4" + "eslint": "^8.56.0" } }, - "node_modules/@react-native/assets": { - "version": "1.0.0", - "integrity": "sha512-KrwSpS1tKI70wuKl68DwJZYEvXktDHdZMG0k2AXD/rJVSlB23/X2CB2cutVR0HwNMJIal9HOUOBB2rVfa6UGtQ==" - }, - "node_modules/@react-native/normalize-color": { - "version": "2.1.0", - "integrity": "sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==" - }, - "node_modules/@react-native/polyfills": { - "version": "2.0.0", - "integrity": "sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ==" - }, - "node_modules/@react-native/virtualized-lists": { - "version": "0.72.4", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.72.4.tgz", - "integrity": "sha512-2t8WBVACkKEadtsiGYJaYTix575J/5VQJyqnyL7iDIsd3iG7ODjfMDsTGsVyAA2Av/xeVIuVQRUX0ZzV3cucug==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", "dev": true, + "license": "MIT", "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" }, - "peerDependencies": { - "react-native": "*", - "react-test-renderer": "18.2.0" + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@react-navigation/core": { - "version": "5.16.1", - "integrity": "sha512-3AToC7vPNeSNcHFLd1h71L6u34hfXoRAS1CxF9Fc4uC8uOrVqcNvphpeFbE0O9Bw6Zpl0BnMFl7E5gaL3KGzNA==", - "dependencies": { - "@react-navigation/routers": "^5.7.4", - "escape-string-regexp": "^4.0.0", - "nanoid": "^3.1.15", - "query-string": "^6.13.6", - "react-is": "^16.13.0" + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "react": "*" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@react-navigation/drawer": { - "version": "5.12.9", - "integrity": "sha512-SYb2BCEAn+BiEwC6WBfCzs1VlWD+ZdQbxmsim6vo1o+ndPW2e+kiq7FXKRs0vUXhQRZVl2oOB3vBn0c3YCllQg==", + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "dev": true, + "license": "ISC" + }, + "node_modules/@vscode/sudo-prompt": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@vscode/sudo-prompt/-/sudo-prompt-9.3.2.tgz", + "integrity": "sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw==", + "license": "MIT" + }, + "node_modules/@wix-pilot/core": { + "version": "3.4.2", + "license": "MIT", "dependencies": { - "color": "^3.1.3", - "react-native-iphone-x-helper": "^1.3.0" + "chalk": "^4.1.0", + "pngjs": "^7.0.0", + "winston": "^3.17.0" }, "peerDependencies": { - "@react-navigation/native": "^5.0.5", - "react": "*", - "react-native": "*", - "react-native-gesture-handler": ">= 1.0.0", - "react-native-reanimated": ">= 1.0.0", - "react-native-safe-area-context": ">= 0.6.0", - "react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0" + "expect": "*" + }, + "peerDependenciesMeta": { + "expect": { + "optional": true + } } }, - "node_modules/@react-navigation/native": { - "version": "5.9.8", - "integrity": "sha512-DNbcDHXQPSFDLn51kkVVJjT3V7jJy2GztNYZe/2bEg29mi5QEcHHcpifjMCtyFKntAOWzKlG88UicIQ17UEghg==", - "dependencies": { - "@react-navigation/core": "^5.16.1", - "escape-string-regexp": "^4.0.0", - "nanoid": "^3.1.15" - }, + "node_modules/@wix-pilot/core/node_modules/pngjs": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/@wix-pilot/detox": { + "version": "1.0.13", "peerDependencies": { - "react": "*", - "react-native": "*" + "@wix-pilot/core": "^3.4.1", + "detox": ">=20.33.0", + "expect": "29.x.x || 28.x.x || ^27.2.5" } }, - "node_modules/@react-navigation/routers": { - "version": "5.7.4", - "integrity": "sha512-0N202XAqsU/FlE53Nmh6GHyMtGm7g6TeC93mrFAFJOqGRKznT0/ail+cYlU6tNcPA9AHzZu1Modw1eoDINSliQ==", - "dependencies": { - "nanoid": "^3.1.15" + "node_modules/@xmldom/xmldom": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", + "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", + "license": "MIT", + "engines": { + "node": ">=14.6" } }, - "node_modules/@remobile/react-native-qrcode-local-image": { - "version": "1.0.4", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-qrcode-local-image.git#31b0113110fbafcf5a5f3ca4183a563550f5c352", - "license": "MIT" + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" }, - "node_modules/@sideway/address": { - "version": "4.1.4", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "node_modules/abort-controller": { + "version": "3.0.0", + "license": "MIT", "dependencies": { - "@hapi/hoek": "^9.0.0" + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" } }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, - "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" - }, - "node_modules/@sinonjs/commons": { - "version": "2.0.0", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { - "type-detect": "4.0.8" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.0.2", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", - "dependencies": { - "@sinonjs/commons": "^2.0.0" + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "node_modules/@spsina/bip47": { - "version": "1.0.1", - "resolved": "git+ssh://git@github.com/BlueWallet/bip47.git#0a2f02c90350802f2ec93afa4e6c8843be2d687c", - "integrity": "sha512-lsgEpiEMDgpiYOA2kizOwiSS3vjTeLe2VnkOTIGnJ7Eu7Mkgl9dLES7oSLAjY64aQXr0VolqCRciRDc2nAC++w==", + "node_modules/acorn": { + "version": "8.15.0", "license": "MIT", - "dependencies": { - "bip32": "^3.0.1", - "bip39": "^3.0.4", - "bitcoinjs-lib": "^6.0.1", - "bs58check": "^2.1.1", - "create-hmac": "^1.1.7", - "ecpair": "^2.0.1", - "tiny-secp256k1": "^1.1.6" + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.4.0" } }, - "node_modules/@tsconfig/react-native": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/react-native/-/react-native-3.0.2.tgz", - "integrity": "sha512-F7IoHEqf741lut4Z2K+IkWQRvXAhBiZMeY5L7BysG7Z2Z3MlIyFR+AagD8jQ/CqC1vowGnRwfLjeuwIpaeoJxA==", - "dev": true - }, - "node_modules/@types/async": { - "version": "3.2.18", - "integrity": "sha512-/IsuXp3B9R//uRLi40VlIYoMp7OzhkunPe2fDu7jGfQXI9y3CDCx6FC4juRLSqrpmLst3vgsiK536AAGJFl4Ww==" - }, - "node_modules/@types/babel__core": { - "version": "7.20.0", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "node_modules/acorn-jsx": { + "version": "5.3.2", "dev": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, + "node_modules/aez": { + "version": "1.0.1", + "license": "MIT", "dependencies": { - "@babel/types": "^7.0.0" + "blakejs": "^1.1.0", + "safe-buffer": "^5.1.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, + "node_modules/aezeed": { + "version": "0.0.5", + "license": "MIT", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "aez": "^1.0.1", + "crc-32": "^1.2.1", + "randombytes": "^2.1.0", + "scryptsy": "^2.1.0" } }, - "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" } }, - "node_modules/@types/bn.js": { - "version": "4.11.6", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "node_modules/ajv": { + "version": "8.17.1", + "license": "MIT", "dependencies": { - "@types/node": "*" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@types/bs58check": { - "version": "2.1.0", - "integrity": "sha512-OxsysnJQh82vy9DRbOcw9m2j/WiyqZLn0YBhKxdQ+aCwoHj+tWzyCgpwAkr79IfDXZKxc6h7k89T9pwS78CqTQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/create-hash": { - "version": "1.2.2", - "integrity": "sha512-Fg8/kfMJObbETFU/Tn+Y0jieYewryLrbKwLCEIwPyklZZVY2qB+64KFjhplGSw+cseZosfFXctXO+PyIYD8iZQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } + "node_modules/anser": { + "version": "1.4.10", + "license": "MIT" }, - "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "node_modules/ansi-escapes": { + "version": "4.3.2", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/hammerjs": { - "version": "2.0.41", - "integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dependencies": { - "@types/istanbul-lib-coverage": "*" + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "node_modules/ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "license": "MIT", "dependencies": { - "@types/istanbul-lib-report": "*" + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" } }, - "node_modules/@types/jest": { - "version": "29.4.0", - "integrity": "sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==", - "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" + "node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "dev": true - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.14.1", - "integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ==" - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" - }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, - "node_modules/@types/prop-types": { - "version": "15.7.5", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "node_modules/@types/react": { - "version": "18.2.16", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.16.tgz", - "integrity": "sha512-LLFWr12ZhBJ4YVw7neWLe6Pk7Ey5R9OCydfuMsz1L8bZxzaawJj2p06Q8/EFEHDeTBQNFLF62X+CG7B2zIyu0Q==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@types/react-native": { - "version": "0.72.0", - "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.72.0.tgz", - "integrity": "sha512-g1PJXUQ0SnYTimfTeN9dRqj8VfzvgJjt/eakEH7+tlm/ZiEPiL9xCool4iKmqalthwtM0/BkGhjwrKnJyg1JDA==", + "node_modules/anymatch": { + "version": "3.1.3", "dev": true, + "license": "ISC", "dependencies": { - "@react-native/virtualized-lists": "^0.72.4", - "@types/react": "*" - } - }, - "node_modules/@types/react-native-vector-icons": { - "version": "6.4.13", - "integrity": "sha512-1PqFoKuXTSzMHwGMAr+REdYJBQAbe9xrww3ecZR0FsHcD1K+vGS/rxuAriL4rsI6+p69sZQjDzpEVAbDQcjSwA==", - "dependencies": { - "@types/react": "*", - "@types/react-native": "^0.70" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@types/react-native-vector-icons/node_modules/@types/react-native": { - "version": "0.70.11", - "integrity": "sha512-FobPtzoNPNHugBKMfzs4Li0Q9ei4tgU8SI1M5Ayg7+t5/+noCm2sknI8uwij22wMkcHcefv8RFx4q28nNVJtCQ==", - "dependencies": { - "@types/react": "*" - } + "node_modules/appdirsjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", + "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", + "license": "MIT" }, - "node_modules/@types/react-test-renderer": { - "version": "18.0.0", - "integrity": "sha512-C7/5FBJ3g3sqUahguGi03O79b8afNeSD6T8/GU50oQrJCU0bVCCGQHaGKUbg2Ce8VQEEqTw8/HiS6lXHHdgkdQ==", + "node_modules/argparse": { + "version": "1.0.10", "dev": true, + "license": "MIT", "dependencies": { - "@types/react": "*" + "sprintf-js": "~1.0.2" } }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" - }, - "node_modules/@types/yargs": { - "version": "16.0.5", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", - "dependencies": { - "@types/yargs-parser": "*" + "node_modules/array-back": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.2.0.tgz", - "integrity": "sha512-rClGrMuyS/3j0ETa1Ui7s6GkLhfZGKZL3ZrChLeAiACBE/tRc1wq8SNZESUuluxhLj9FkUefRs2l6bCIArWBiQ==", + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.2.0", - "@typescript-eslint/type-utils": "6.2.0", - "@typescript-eslint/utils": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.2.0.tgz", - "integrity": "sha512-1ZMNVgm5nnHURU8ZSJ3snsHzpFeNK84rdZjluEVBGNu7jDymfqceB3kdIZ6A4xCfEFFhRIB6rF8q/JIqJd2R0Q==", + "node_modules/array-includes": { + "version": "3.1.8", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.2.0.tgz", - "integrity": "sha512-1nRRaDlp/XYJQLvkQJG5F3uBTno5SHPT7XVcJ5n1/k2WfNI28nJsvLakxwZRNY5spuatEKO7d5nZWsQpkqXwBA==", + "node_modules/array-union": { + "version": "2.1.0", "dev": true, + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=8" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.0.tgz", - "integrity": "sha512-Mts6+3HQMSM+LZCglsc2yMIny37IhUgp1Qe8yJUYVyO6rHP7/vN0vajKu3JvHCBIy8TSiKddJ/Zwu80jhnGj1w==", + "node_modules/array.prototype.findlast": { + "version": "1.2.5", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.2.0.tgz", - "integrity": "sha512-RCFrC1lXiX1qEZN8LmLrxYRhOkElEsPKTVSNout8DMzf8PeWoQG7Rxz2SadpJa3VSh5oYKGwt7j7X/VRg+Y3OQ==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.2.0", - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/typescript-estree": "6.2.0", - "semver": "^7.5.4" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.0.tgz", - "integrity": "sha512-QbaYUQVKKo9bgCzpjz45llCfwakyoxHetIy8CAvYCtd16Zu1KrpzNHofwF8kGkpPOxZB2o6kz+0nqH8ZkIzuoQ==", + "node_modules/array.prototype.flat": { + "version": "1.3.3", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "eslint-visitor-keys": "^3.4.1" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "node_modules/asap": { + "version": "2.0.6", + "license": "MIT" }, - "node_modules/@typescript-eslint/parser": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.2.0.tgz", - "integrity": "sha512-igVYOqtiK/UsvKAmmloQAruAdUHihsOCvplJpplPZ+3h4aDkC/UKZZNKgB6h93ayuYLuEymU3h8nF1xMRbh37g==", - "dev": true, + "node_modules/asn1.js": { + "version": "4.10.1", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "6.2.0", - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/typescript-estree": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.2.0.tgz", - "integrity": "sha512-1ZMNVgm5nnHURU8ZSJ3snsHzpFeNK84rdZjluEVBGNu7jDymfqceB3kdIZ6A4xCfEFFhRIB6rF8q/JIqJd2R0Q==", - "dev": true, + "node_modules/assert": { + "version": "2.1.0", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.2.0.tgz", - "integrity": "sha512-1nRRaDlp/XYJQLvkQJG5F3uBTno5SHPT7XVcJ5n1/k2WfNI28nJsvLakxwZRNY5spuatEKO7d5nZWsQpkqXwBA==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.0.tgz", - "integrity": "sha512-Mts6+3HQMSM+LZCglsc2yMIny37IhUgp1Qe8yJUYVyO6rHP7/vN0vajKu3JvHCBIy8TSiKddJ/Zwu80jhnGj1w==", + "node_modules/async": { + "version": "3.2.6", + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">= 0.4" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.0.tgz", - "integrity": "sha512-QbaYUQVKKo9bgCzpjz45llCfwakyoxHetIy8CAvYCtd16Zu1KrpzNHofwF8kGkpPOxZB2o6kz+0nqH8ZkIzuoQ==", - "dev": true, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "eslint-visitor-keys": "^3.4.1" + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript-eslint/parser/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/babel-jest": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/babel-jest/node_modules/@jest/transform": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "node_modules/babel-jest/node_modules/@jest/types": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.2.0.tgz", - "integrity": "sha512-DnGZuNU2JN3AYwddYIqrVkYW0uUQdv0AY+kz2M25euVNlujcN2u+rJgfJsBFlUEzBB6OQkUqSZPyuTLf2bP5mw==", + "node_modules/babel-jest/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.2.0", - "@typescript-eslint/utils": "6.2.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@types/yargs-parser": "*" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.2.0.tgz", - "integrity": "sha512-1ZMNVgm5nnHURU8ZSJ3snsHzpFeNK84rdZjluEVBGNu7jDymfqceB3kdIZ6A4xCfEFFhRIB6rF8q/JIqJd2R0Q==", + "node_modules/babel-jest/node_modules/jest-haste-map": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.2.0.tgz", - "integrity": "sha512-1nRRaDlp/XYJQLvkQJG5F3uBTno5SHPT7XVcJ5n1/k2WfNI28nJsvLakxwZRNY5spuatEKO7d5nZWsQpkqXwBA==", + "node_modules/babel-jest/node_modules/jest-regex-util": { + "version": "29.6.3", "dev": true, + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.0.tgz", - "integrity": "sha512-Mts6+3HQMSM+LZCglsc2yMIny37IhUgp1Qe8yJUYVyO6rHP7/vN0vajKu3JvHCBIy8TSiKddJ/Zwu80jhnGj1w==", + "node_modules/babel-jest/node_modules/jest-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.2.0.tgz", - "integrity": "sha512-RCFrC1lXiX1qEZN8LmLrxYRhOkElEsPKTVSNout8DMzf8PeWoQG7Rxz2SadpJa3VSh5oYKGwt7j7X/VRg+Y3OQ==", + "node_modules/babel-jest/node_modules/jest-worker": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.2.0", - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/typescript-estree": "6.2.0", - "semver": "^7.5.4" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.0.tgz", - "integrity": "sha512-QbaYUQVKKo9bgCzpjz45llCfwakyoxHetIy8CAvYCtd16Zu1KrpzNHofwF8kGkpPOxZB2o6kz+0nqH8ZkIzuoQ==", + "node_modules/babel-jest/node_modules/supports-color": { + "version": "8.1.1", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "eslint-visitor-keys": "^3.4.1" + "has-flag": "^4.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "node_modules/babel-jest/node_modules/write-file-atomic": { + "version": "4.0.2", "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "yallist": "^4.0.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.15", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.6", + "semver": "^6.3.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.6", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "@babel/helper-define-polyfill-provider": "^0.6.6" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, + "node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.33.3.tgz", + "integrity": "sha512-/Z9xYdaJ1lC0pT9do6TqCqhOSLfZ5Ot8D5za1p+feEfWYupCOfGbhhEXN9r2ZgJtDNUNRw/Z+T2CvAGKBqtqWA==", + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "hermes-parser": "0.33.3" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-flow": "^7.12.1" + } }, - "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@babel/core": "^7.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/babel-preset-jest": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.3.8", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "node_modules/balanced-match": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/base-64": { + "version": "0.1.0" + }, + "node_modules/base-x": { + "version": "4.0.1", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "license": "Apache-2.0", "bin": { - "semver": "bin/semver.js" + "baseline-browser-mapping": "dist/cli.cjs" }, "engines": { - "node": ">=10" + "node": ">=6.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { - "version": "4.0.0", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "node_modules/bc-bech32": { + "resolved": "blue_modules/bc-bech32", + "link": true }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, + "node_modules/bech32": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/bigi": { + "version": "1.4.2" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "*" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true, + "node_modules/bindings": { + "version": "1.5.0", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bip174": { + "version": "2.1.1", + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8.0.0" } }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" + "node_modules/bip21": { + "version": "2.0.3", + "license": "MIT", + "dependencies": { + "qs": "^6.3.0" + } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "node_modules/bip32": { + "version": "5.0.1", + "license": "MIT", "dependencies": { - "event-target-shim": "^5.0.0" + "@noble/hashes": "^1.2.0", + "@scure/base": "^1.1.1", + "uint8array-tools": "^0.0.8", + "valibot": "^1.2.0", + "wif": "^5.0.0" }, "engines": { - "node": ">=6.5" + "node": ">=18.0.0" } }, - "node_modules/abortcontroller-polyfill": { - "version": "1.7.5", - "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==" + "node_modules/bip32/node_modules/@scure/base": { + "version": "1.2.6", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } }, - "node_modules/absolute-path": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz", - "integrity": "sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA==" + "node_modules/bip32/node_modules/base-x": { + "version": "5.0.1", + "license": "MIT" }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/bip32/node_modules/bs58": { + "version": "6.0.0", + "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" + "base-x": "^5.0.0" } }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "node_modules/bip32/node_modules/bs58check": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^6.0.0" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "node_modules/bip32/node_modules/uint8array-tools": { + "version": "0.0.8", + "license": "MIT", + "engines": { + "node": ">=14.0.0" } }, - "node_modules/aez": { - "version": "1.0.1", - "integrity": "sha512-AtZEVcZcOLBAcNevz2e+Zu1zSLuPjtUA6CFY+ie8tF0IshKfcpJ0LiwnTqePOKaNidQQM/MfvtzUFOfAvHz5wQ==", + "node_modules/bip32/node_modules/wif": { + "version": "5.0.0", + "license": "MIT", "dependencies": { - "blakejs": "^1.1.0", - "safe-buffer": "^5.1.1" - }, - "engines": { - "node": ">=8.10.0" + "bs58check": "^4.0.0" } }, - "node_modules/ajv": { - "version": "8.12.0", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "node_modules/bip38": { + "version": "3.1.1", + "resolved": "git+ssh://git@github.com/BlueWallet/bip38.git#7ec4b1932b98eaaff16c5a26765a26466958e6b4", "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "bigi": "^1.2.0", + "browserify-aes": "^1.0.1", + "bs58check": "<3.0.0", + "buffer-xor": "^1.0.2", + "create-hash": "^1.1.1", + "ecurve": "^1.0.0", + "safe-buffer": "~5.1.1", + "scryptsy": "^2.1.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "peerDependencies": { + "react-native-blue-crypto": "*" } }, - "node_modules/anser": { - "version": "1.4.10", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==" + "node_modules/bip38/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, + "node_modules/bip39": { + "version": "3.1.0", + "license": "ISC", "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@noble/hashes": "^1.2.0" } }, - "node_modules/ansi-fragments": { - "version": "0.2.1", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "node_modules/bip66": { + "version": "1.1.5", + "license": "MIT", "dependencies": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" + "safe-buffer": "^5.0.1" } }, - "node_modules/ansi-fragments/node_modules/ansi-regex": { - "version": "4.1.1", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "node_modules/bip68": { + "version": "1.0.4", + "license": "ISC", "engines": { - "node": ">=6" + "node": ">=4.5.0" } }, - "node_modules/ansi-fragments/node_modules/strip-ansi": { - "version": "5.2.0", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "node_modules/bitcoin-ops": { + "version": "1.4.1", + "license": "MIT" + }, + "node_modules/bitcoinjs-lib": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-7.0.1.tgz", + "integrity": "sha512-vwEmpL5Tpj0I0RBdNkcDMXePoaYSTeKY6mL6/l5esbnTs+jGdPDuLp4NY1hSh6Zk5wSgePygZ4Wx5JJao30Pww==", + "license": "MIT", "dependencies": { - "ansi-regex": "^4.1.0" + "@noble/hashes": "^1.2.0", + "bech32": "^2.0.0", + "bip174": "^3.0.0", + "bs58check": "^4.0.0", + "uint8array-tools": "^0.0.9", + "valibot": "^1.2.0", + "varuint-bitcoin": "^2.0.0" }, "engines": { - "node": ">=6" + "node": ">=18.0.0" } }, - "node_modules/ansi-regex": { + "node_modules/bitcoinjs-lib/node_modules/base-x": { "version": "5.0.1", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/bitcoinjs-lib/node_modules/bip174": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-3.0.0.tgz", + "integrity": "sha512-N3vz3rqikLEu0d6yQL8GTrSkpYb35NQKWMR7Hlza0lOj6ZOlvQ3Xr7N9Y+JPebaCVoEUHdBeBSuLxcHr71r+Lw==", + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "uint8array-tools": "^0.0.9", + "varuint-bitcoin": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=18.0.0" } }, - "node_modules/appdirsjs": { - "version": "1.2.7", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==" - }, - "node_modules/argparse": { - "version": "1.0.10", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/bitcoinjs-lib/node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" + "base-x": "^5.0.0" } }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "engines": { - "node": ">=0.10.0" + "node_modules/bitcoinjs-lib/node_modules/bs58check": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-4.0.0.tgz", + "integrity": "sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^6.0.0" } }, - "node_modules/arr-union": { - "version": "3.1.0", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "engines": { - "node": ">=0.10.0" + "node_modules/bitcoinjs-lib/node_modules/varuint-bitcoin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-2.0.0.tgz", + "integrity": "sha512-6QZbU/rHO2ZQYpWFDALCDSRsXbAs1VOEmXAxtbtjLtKuMJ/FQ8YbhfxlaiKv5nklci0M6lZtlZyxo9Q+qNnyog==", + "license": "MIT", + "dependencies": { + "uint8array-tools": "^0.0.8" } }, - "node_modules/array-back": { - "version": "3.1.0", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "node_modules/bitcoinjs-lib/node_modules/varuint-bitcoin/node_modules/uint8array-tools": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.8.tgz", + "integrity": "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=14.0.0" } }, - "node_modules/array-includes": { - "version": "3.1.6", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dev": true, + "node_modules/bitcoinjs-message": { + "version": "2.2.0", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" + "bech32": "^1.1.3", + "bs58check": "^2.1.2", + "buffer-equals": "^1.0.3", + "create-hash": "^1.1.2", + "secp256k1": "^3.0.1", + "varuint-bitcoin": "^1.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10" } }, - "node_modules/array-union": { - "version": "2.1.0", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" + "node_modules/bitcoinjs-message/node_modules/bech32": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, + "node_modules/blakejs": { + "version": "1.2.1", + "license": "MIT" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "4.12.1", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.1", - "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", - "dev": true, + "node_modules/bolt11": { + "version": "1.4.1", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.1.3" + "@types/bn.js": "^4.11.3", + "bech32": "^1.1.2", + "bitcoinjs-lib": "^6.0.0", + "bn.js": "^4.11.8", + "create-hash": "^1.2.0", + "lodash": "^4.17.11", + "safe-buffer": "^5.1.1", + "secp256k1": "^4.0.2" } }, - "node_modules/asap": { - "version": "2.0.6", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + "node_modules/bolt11/node_modules/bech32": { + "version": "1.1.4", + "license": "MIT" }, - "node_modules/asn1.js": { - "version": "5.4.1", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "node_modules/bolt11/node_modules/bitcoinjs-lib": { + "version": "6.1.7", + "license": "MIT", "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" + "@noble/hashes": "^1.2.0", + "bech32": "^2.0.0", + "bip174": "^2.1.1", + "bs58check": "^3.0.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" } }, - "node_modules/assert": { + "node_modules/bolt11/node_modules/bitcoinjs-lib/node_modules/bech32": { "version": "2.0.0", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "license": "MIT" + }, + "node_modules/bolt11/node_modules/bs58": { + "version": "5.0.0", + "license": "MIT", "dependencies": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" + "base-x": "^4.0.0" } }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "engines": { - "node": ">=0.10.0" + "node_modules/bolt11/node_modules/bs58check": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" } }, - "node_modules/ast-types": { - "version": "0.14.2", - "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", + "node_modules/bolt11/node_modules/secp256k1": { + "version": "4.0.4", + "hasInstallScript": true, + "license": "MIT", "dependencies": { - "tslib": "^2.0.1" + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" }, "engines": { - "node": ">=4" + "node": ">=18.0.0" } }, - "node_modules/astral-regex": { + "node_modules/boolbase": { "version": "1.0.0", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/async": { - "version": "3.2.4", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + "node_modules/brorand": { + "version": "1.1.0", + "license": "MIT" }, - "node_modules/async-limiter": { - "version": "1.0.1", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "license": "BSD-2-Clause" }, - "node_modules/asynckit": { - "version": "0.4.0", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "node_modules/browserify-aes": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } }, - "node_modules/atob": { - "version": "2.1.2", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" + "node_modules/browserify-cipher": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node_modules/browserify-des": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, - "node_modules/babel-jest": { - "version": "29.4.3", - "integrity": "sha512-o45Wyn32svZE+LnMVWv/Z4x0SwtLbh4FyGcYtR20kIWd+rdrDZ9Fzq8Ml3MYLD+mZvEdzCjZsCnYZ2jpJyQ+Nw==", - "dev": true, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "license": "MIT", "dependencies": { - "@jest/transform": "^29.4.3", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.4.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" + "node": ">= 0.10" } }, - "node_modules/babel-jest/node_modules/@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "dev": true, + "node_modules/browserify-rsa/node_modules/bn.js": { + "version": "5.2.2", + "license": "MIT" + }, + "node_modules/browserify-sign": { + "version": "4.2.3", + "license": "ISC", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.5", + "hash-base": "~3.0", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.12" } }, - "node_modules/babel-jest/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, + "node_modules/browserify-sign/node_modules/bn.js": { + "version": "5.2.2", + "license": "MIT" + }, + "node_modules/browserify-sign/node_modules/hash-base": { + "version": "3.0.5", + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.10" } }, - "node_modules/babel-jest/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, + "node_modules/browserify-sign/node_modules/isarray": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "2.3.8", + "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "safe-buffer": "~5.1.0" } }, - "node_modules/babel-jest/node_modules/anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, + "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/browserslist": { + "version": "4.28.1", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">= 8" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/bs-logger": { + "version": "0.2.6", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "fast-json-stable-stringify": "2.x" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 6" } }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/bs58": { + "version": "4.0.1", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "base-x": "^3.0.2" } }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/babel-jest/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "node_modules/bs58/node_modules/base-x": { + "version": "3.0.11", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } }, - "node_modules/babel-jest/node_modules/fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node_modules/bs58check": { + "version": "2.1.2", + "license": "MIT", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" } }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "node_modules/bser": { + "version": "2.1.1", + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" } }, - "node_modules/babel-jest/node_modules/jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", - "dev": true, + "node_modules/bson": { + "version": "4.7.2", + "license": "Apache-2.0", "dependencies": { - "@jest/types": "^29.4.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" + "buffer": "^5.6.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": ">=6.9.0" } }, - "node_modules/babel-jest/node_modules/jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node_modules/bson/node_modules/buffer": { + "version": "5.7.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "node_modules/babel-jest/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, + "node_modules/buffer": { + "version": "6.0.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/babel-jest/node_modules/jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, + "node_modules/buffer-equals": { + "version": "1.0.4", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/babel-jest/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/buffer-from": { + "version": "1.1.2", + "license": "MIT" + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/babel-jest/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/builtins": { + "version": "5.1.0", "dev": true, - "engines": { - "node": ">=0.10.0" + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" } }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/builtins/node_modules/semver": { + "version": "7.7.1", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/babel-jest/node_modules/write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, + "node_modules/bunyamin": { + "version": "1.6.3", + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "@flatten-js/interval-tree": "^1.1.2", + "multi-sort-stream": "^1.0.4", + "stream-json": "^1.7.5", + "trace-event-lib": "^1.3.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=14.18.2" + }, + "peerDependencies": { + "@types/bunyan": "^1.8.8", + "bunyan": "^1.8.15 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@types/bunyan": { + "optional": true + }, + "bunyan": { + "optional": true + } } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" + "node_modules/bunyan": { + "version": "1.8.15", + "engines": [ + "node >=0.10.0" + ], + "license": "MIT", + "bin": { + "bunyan": "bin/bunyan" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" } }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.4.3", - "integrity": "sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q==", - "dev": true, + "node_modules/bunyan-debug-stream": { + "version": "3.1.1", + "license": "MIT", "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" + "node": ">=0.12.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" + "bunyan": "*" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "peerDependenciesMeta": { + "bunyan": { + "optional": true + } } }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "node_modules/babel-plugin-syntax-trailing-function-commas": { - "version": "7.0.0-beta.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", - "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==" - }, - "node_modules/babel-plugin-transform-flow-enums": { - "version": "0.0.2", - "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", - "dependencies": { - "@babel/plugin-syntax-flow": "^7.12.1" - } + "node_modules/caf": { + "version": "15.0.1", + "license": "MIT" }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, + "node_modules/call-bind": { + "version": "1.0.8", + "license": "MIT", "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-fbjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", - "integrity": "sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==", - "dependencies": { - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-syntax-class-properties": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-block-scoped-functions": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-for-of": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-member-expression-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-object-super": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-property-literals": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0" + "engines": { + "node": ">= 0.4" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-preset-jest": { - "version": "29.4.3", - "integrity": "sha512-gWx6COtSuma6n9bw+8/F+2PCXrIgxV/D1TJFnp6OyBK2cxPWg0K9p/sriNYeifKjpUkMViWQ09DSWtzJQRETsw==", - "dev": true, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^29.4.3", - "babel-preset-current-node-syntax": "^1.0.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">= 0.4" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base": { - "version": "0.11.2", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "node_modules/call-bound": { + "version": "1.0.4", + "license": "MIT", "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/base-64": { - "version": "0.1.0", - "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" - }, - "node_modules/base-x": { - "version": "3.0.9", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dependencies": { - "safe-buffer": "^5.0.1" + "node_modules/callsites": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dependencies": { - "is-descriptor": "^1.0.0" - }, + "node_modules/camelcase": { + "version": "5.3.1", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "node_modules/caniuse-lite": { + "version": "1.0.30001772", "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/feross" + "type": "opencollective", + "url": "https://opencollective.com/browserslist" }, { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "github", + "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, - "node_modules/bc-bech32": { - "resolved": "blue_modules/bc-bech32", - "link": true + "node_modules/cbor-sync": { + "version": "1.0.4", + "license": "MIT" }, - "node_modules/bech32": { - "version": "2.0.0", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + "node_modules/chalk": { + "version": "4.1.2", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, - "node_modules/big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "node_modules/char-regex": { + "version": "1.0.2", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.6" + "node": ">=10" } }, - "node_modules/bigi": { - "version": "1.4.2", - "integrity": "sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw==" + "node_modules/chownr": { + "version": "1.1.4", + "license": "ISC" }, - "node_modules/bignumber.js": { - "version": "9.1.1", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, "engines": { - "node": "*" + "node": ">=12.13.0" } }, - "node_modules/bindings": { - "version": "1.5.0", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "node_modules/chrome-launcher/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bip174": { - "version": "2.1.0", - "integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==", + "is-docker": "^2.0.0" + }, "engines": { - "node": ">=8.0.0" + "node": ">=8" } }, - "node_modules/bip21": { - "version": "2.0.3", - "integrity": "sha512-L4ODmASLjsHYAU+TG7xffkYNMvHzAe4mkVX7mcvOUyKAr/MDBPrsRgqUhE8EmKdeEKHk5SYpX1Aexzvm/6WdbQ==", + "node_modules/chromium-edge-launcher": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", + "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", + "license": "Apache-2.0", "dependencies": { - "qs": "^6.3.0" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4" } }, - "node_modules/bip32": { - "version": "3.0.1", - "integrity": "sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==", + "node_modules/chromium-edge-launcher/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", "dependencies": { - "@types/node": "10.12.18", - "bs58check": "^2.1.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "typeforce": "^1.11.5", - "wif": "^2.0.6" + "is-docker": "^2.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=8" } }, - "node_modules/bip32/node_modules/@types/node": { - "version": "10.12.18", - "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" - }, - "node_modules/bip38": { - "version": "3.1.1", - "resolved": "git+ssh://git@github.com/BlueWallet/bip38.git#60018f7c61e70584647d724b9b9264a7ebc8182e", - "dependencies": { - "bigi": "^1.2.0", - "browserify-aes": "^1.0.1", - "bs58check": "<3.0.0", - "buffer-xor": "^1.0.2", - "create-hash": "^1.1.1", - "ecurve": "^1.0.0", - "safe-buffer": "~5.1.1", - "scryptsy": "^2.1.0" + "node_modules/chromium-edge-launcher/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" }, - "peerDependencies": { - "react-native-blue-crypto": "*" + "engines": { + "node": ">=10" } }, - "node_modules/bip38/node_modules/safe-buffer": { - "version": "5.1.2", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/ci-info": { + "version": "3.9.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/bip39": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", - "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "node_modules/cipher-base": { + "version": "1.0.6", + "license": "MIT", "dependencies": { - "@noble/hashes": "^1.2.0" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" } }, - "node_modules/bip66": { - "version": "1.1.5", - "integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==", + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "license": "MIT", "dependencies": { - "safe-buffer": "^5.0.1" + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/bitcoin-ops": { - "version": "1.4.1", - "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==" + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/bitcoinjs-lib": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.1.tgz", - "integrity": "sha512-FYihfgTk29lt1eK2y48OtuarEDUnTprNBW3ctT8yHiOhvmeS3DzAVG6gI0VCvMkydz6UdlXlYNWIPqGD0SUYRQ==", + "node_modules/cliui": { + "version": "8.0.1", + "license": "ISC", "dependencies": { - "@noble/hashes": "^1.2.0", - "bech32": "^2.0.0", - "bip174": "^2.1.0", - "bs58check": "^3.0.1", - "typeforce": "^1.11.3", - "varuint-bitcoin": "^1.1.2" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=12" } }, - "node_modules/bitcoinjs-lib/node_modules/base-x": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", - "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" - }, - "node_modules/bitcoinjs-lib/node_modules/bs58": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", - "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", "dependencies": { - "base-x": "^4.0.0" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/bitcoinjs-lib/node_modules/bs58check": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", - "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/coinselect": { + "version": "3.1.13", + "resolved": "git+ssh://git@github.com/BlueWallet/coinselect.git#35f803875f37473e95e4207dd7ac8991c4ac7812", + "license": "MIT" + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/color": { + "version": "4.2.3", + "license": "MIT", "dependencies": { - "@noble/hashes": "^1.2.0", - "bs58": "^5.0.0" + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" } }, - "node_modules/bitcoinjs-message": { - "version": "2.2.0", - "integrity": "sha512-103Wy3xg8Y9o+pdhGP4M3/mtQQuUWs6sPuOp1mYphSUoSMHjHTlkj32K4zxU8qMH0Ckv23emfkGlFWtoWZ7YFA==", + "node_modules/color-convert": { + "version": "2.0.1", + "license": "MIT", "dependencies": { - "bech32": "^1.1.3", - "bs58check": "^2.1.2", - "buffer-equals": "^1.0.3", - "create-hash": "^1.1.2", - "secp256k1": "^3.0.1", - "varuint-bitcoin": "^1.0.1" + "color-name": "~1.1.4" }, "engines": { - "node": ">=0.10" + "node": ">=7.0.0" } }, - "node_modules/bitcoinjs-message/node_modules/bech32": { + "node_modules/color-name": { "version": "1.1.4", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + "license": "MIT" }, - "node_modules/bl": { - "version": "4.1.0", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/color-string": { + "version": "1.9.1", + "license": "MIT", "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" } }, - "node_modules/bl/node_modules/buffer": { - "version": "5.7.1", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "license": "MIT" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "color": "^3.1.3", + "text-hex": "1.0.x" } }, - "node_modules/blakejs": { - "version": "1.2.1", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" - }, - "node_modules/bluebird": { - "version": "3.7.2", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/bn.js": { - "version": "4.12.0", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/bolt11": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/bolt11/-/bolt11-1.4.1.tgz", - "integrity": "sha512-jR0Y+MO+CK2at1Cg5mltLJ+6tdOwNKoTS/DJOBDdzVkQ+R9D6UgZMayTWOsuzY7OgV1gEqlyT5Tzk6t6r4XcNQ==", + "node_modules/colorspace/node_modules/color": { + "version": "3.2.1", + "license": "MIT", "dependencies": { - "@types/bn.js": "^4.11.3", - "bech32": "^1.1.2", - "bitcoinjs-lib": "^6.0.0", - "bn.js": "^4.11.8", - "create-hash": "^1.2.0", - "lodash": "^4.17.11", - "safe-buffer": "^5.1.1", - "secp256k1": "^4.0.2" + "color-convert": "^1.9.3", + "color-string": "^1.6.0" } }, - "node_modules/bolt11/node_modules/bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + "node_modules/colorspace/node_modules/color-convert": { + "version": "1.9.3", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } }, - "node_modules/bolt11/node_modules/node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + "node_modules/colorspace/node_modules/color-name": { + "version": "1.1.3", + "license": "MIT" }, - "node_modules/bolt11/node_modules/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", - "hasInstallScript": true, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", "dependencies": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">=10.0.0" + "node": ">= 0.8" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, - "node_modules/boolean": { - "version": "3.2.0", - "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==" + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "license": "MIT" }, - "node_modules/bplist-parser": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", - "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", - "dev": true, + "node_modules/command-line-args": { + "version": "5.2.1", + "license": "MIT", "dependencies": { - "big-integer": "^1.6.44" + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" }, "engines": { - "node": ">= 5.10.0" + "node": ">=4.0.0" } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/command-line-usage": { + "version": "6.1.3", + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "array-back": "^4.0.2", + "chalk": "^2.4.2", + "table-layout": "^1.0.2", + "typical": "^5.2.0" + }, + "engines": { + "node": ">=8.0.0" } }, - "node_modules/braces": { - "version": "3.0.2", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/command-line-usage/node_modules/ansi-styles": { + "version": "3.2.1", + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/brorand": { - "version": "1.1.0", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + "node_modules/command-line-usage/node_modules/array-back": { + "version": "4.0.2", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "node_modules/command-line-usage/node_modules/chalk": { + "version": "2.4.2", + "license": "MIT", "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "node_modules/command-line-usage/node_modules/color-convert": { + "version": "1.9.3", + "license": "MIT", "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" + "color-name": "1.1.3" } }, - "node_modules/browserify-des": { - "version": "1.0.2", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "node_modules/command-line-usage/node_modules/color-name": { + "version": "1.1.3", + "license": "MIT" + }, + "node_modules/command-line-usage/node_modules/escape-string-regexp": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/command-line-usage/node_modules/has-flag": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/command-line-usage/node_modules/supports-color": { + "version": "5.5.0", + "license": "MIT", "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/browserify-rsa": { - "version": "4.1.0", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "node_modules/command-line-usage/node_modules/typical": { + "version": "5.2.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/commander": { + "version": "9.5.0", + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/browserify-rsa/node_modules/bn.js": { - "version": "5.2.1", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } }, - "node_modules/browserify-sign": { - "version": "4.2.1", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "ms": "2.0.0" } }, - "node_modules/browserify-sign/node_modules/bn.js": { - "version": "5.2.1", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, - "node_modules/browserslist": { - "version": "4.21.5", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } + "node_modules/concat-map": { + "version": "0.0.1", + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "engines": [ + "node >= 6.0" ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - }, - "bin": { - "browserslist": "cli.js" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">= 0.10.0" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { - "fast-json-stable-stringify": "2.x" - }, + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/consola": { + "version": "2.15.3", + "license": "MIT" + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">= 0.6" } }, - "node_modules/bs58": { - "version": "4.0.1", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "node_modules/convert-source-map": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.48.0", + "license": "MIT", "dependencies": { - "base-x": "^3.0.2" + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/bs58check": { - "version": "2.1.2", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "node_modules/core-util-is": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "license": "MIT", "dependencies": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/bser": { - "version": "2.1.1", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", "dependencies": { - "node-int64": "^0.4.0" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", + "node_modules/crc": { + "version": "3.8.0", + "license": "MIT", "dependencies": { - "buffer": "^5.6.0" + "buffer": "^5.1.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" }, "engines": { - "node": ">=6.9.0" + "node": ">=0.8" } }, - "node_modules/bson/node_modules/buffer": { + "node_modules/crc/node_modules/buffer": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "funding": [ { "type": "github", @@ -7810,1470 +7550,1613 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, - "node_modules/buffer": { - "version": "6.0.3", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/create-ecdh": { + "version": "4.0.4", + "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" } }, - "node_modules/buffer-equals": { - "version": "1.0.4", - "integrity": "sha512-99MsCq0j5+RhubVEtKQgKaD6EM+UP3xJgIvQqwJ3SOLDUekzxMX1ylXBng+Wa2sh7mGT0W6RUly8ojjr1Tt6nA==", - "engines": { - "node": ">=0.10.0" + "node_modules/create-hash": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/buffer-reverse": { - "version": "1.0.1", - "integrity": "sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg==" - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" - }, - "node_modules/builtins": { - "version": "5.0.1", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", - "dev": true, + "node_modules/create-hmac": { + "version": "1.1.7", + "license": "MIT", "dependencies": { - "semver": "^7.0.0" + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, - "node_modules/builtins/node_modules/lru-cache": { - "version": "6.0.0", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/create-jest": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/builtins/node_modules/semver": { - "version": "7.3.8", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "node_modules/create-jest/node_modules/@jest/types": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/builtins/node_modules/yallist": { - "version": "4.0.0", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/bundle-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", - "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "node_modules/create-jest/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { - "run-applescript": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/yargs-parser": "*" } }, - "node_modules/bunyan": { - "version": "1.8.15", - "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", - "engines": [ - "node >=0.10.0" - ], - "bin": { - "bunyan": "bin/bunyan" + "node_modules/create-jest/node_modules/jest-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, - "optionalDependencies": { - "dtrace-provider": "~0.8", - "moment": "^2.19.3", - "mv": "~2", - "safe-json-stringify": "~1" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/bunyan-debug-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bunyan-debug-stream/-/bunyan-debug-stream-3.1.0.tgz", - "integrity": "sha512-VaFYbDVdiSn3ZpdozrjZ8mFpxHXl26t11C1DKRQtbo0EgffqeFNrRLOGIESKVeGEvVu4qMxMSSxzNlSw7oTj7w==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "license": "MIT", "dependencies": { - "chalk": "^4.1.2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=0.12.0" - }, - "peerDependencies": { - "bunyan": "*" + "node": ">= 8" } }, - "node_modules/bunyan-debug-stream/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "license": "ISC", "dependencies": { - "color-convert": "^2.0.1" + "isexe": "^2.0.0" }, - "engines": { - "node": ">=8" + "bin": { + "node-which": "bin/node-which" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">= 8" } }, - "node_modules/bunyan-debug-stream/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/crypto-browserify": { + "version": "3.12.1", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" }, "engines": { - "node": ">=10" + "node": ">= 0.10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/bunyan-debug-stream/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/crypto-browserify/node_modules/hash-base": { + "version": "3.0.5", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.10" } }, - "node_modules/bunyan-debug-stream/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "node_modules/crypto-js": { + "version": "4.2.0", + "license": "MIT" }, - "node_modules/bunyan-debug-stream/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "node_modules/css-select": { + "version": "5.1.0", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/bunyan-debug-stream/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/css-tree": { + "version": "1.1.3", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "mdn-data": "2.0.14", + "source-map": "^0.6.1" }, "engines": { - "node": ">=8" + "node": ">=8.0.0" } }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "node_modules/css-what": { + "version": "6.1.0", + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cache-base": { - "version": "1.0.1", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "node": ">= 6" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/caf": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/caf/-/caf-15.0.1.tgz", - "integrity": "sha512-Xp/IK6vMwujxWZXra7djdYzPdPnEQKa7Mudu2wZgDQ3TJry1I0TgtjEgwZHpoBcMp68j4fb0/FZ1SJyMEgJrXQ==" + "node_modules/csstype": { + "version": "3.2.3", + "license": "MIT" }, - "node_modules/call-bind": { + "node_modules/data-view-buffer": { "version": "1.0.2", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", "dependencies": { - "callsites": "^2.0.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" } }, - "node_modules/caller-callsite/node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "license": "MIT", "dependencies": { - "caller-callsite": "^2.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=4" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/callsites": { - "version": "3.1.0", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, + "node_modules/decamelize": { + "version": "4.0.0", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001457", - "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/caseless": { - "version": "0.12.0", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" - }, - "node_modules/cbor-sync": { - "version": "1.0.4", - "integrity": "sha512-GWlXN4wiz0vdWWXBU71Dvc1q3aBo0HytqwAZnXF1wOwjqNnDWA1vZ1gDMFLlqohak31VQzmhiYfiCX5QSSfagA==" - }, - "node_modules/chalk": { - "version": "2.4.2", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/decompress-response": { + "version": "6.0.0", + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "mimic-response": "^3.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" + "node_modules/dedent": { + "version": "1.5.3", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/char-regex": { - "version": "1.0.2", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, + "node_modules/deep-extend": { + "version": "0.6.0", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=4.0.0" } }, - "node_modules/child-process-promise": { - "version": "2.2.1", - "integrity": "sha512-Fi4aNdqBsr0mv+jgWxcZ/7rAIC2mgihrptyVI4foh/rrjY/3BNjfP9+oaiFx/fzim+1ZyCNBae0DlyfQhSugog==", - "dependencies": { - "cross-spawn": "^4.0.2", - "node-version": "^1.0.0", - "promise-polyfill": "^6.0.1" - } + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" }, - "node_modules/ci-info": { - "version": "3.8.0", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], + "node_modules/deepmerge": { + "version": "4.3.1", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/cipher-base": { + "node_modules/defaults": { "version": "1.0.4", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "node_modules/class-utils": { - "version": "0.3.6", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" + "clone": "^1.0.2" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "node_modules/define-data-property": { + "version": "1.1.4", + "license": "MIT", "dependencies": { - "is-descriptor": "^0.1.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "node_modules/define-properties": { + "version": "1.2.1", + "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=0.4.0" } }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dependencies": { - "kind-of": "^3.0.2" - }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/des.js": { + "version": "1.1.0", + "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/class-utils/node_modules/kind-of": { - "version": "5.1.0", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "node_modules/detect-libc": { + "version": "2.0.3", + "license": "Apache-2.0", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/cli-cursor": { + "node_modules/detect-newline": { "version": "3.1.0", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" - }, + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/cli-spinners": { - "version": "2.7.0", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "node_modules/detox": { + "version": "20.51.1", + "resolved": "https://registry.npmjs.org/detox/-/detox-20.51.1.tgz", + "integrity": "sha512-SKHM445qJDSaCO0vQHZnzfStpU1nQtBgWPjMZh/R+1q/ra9F2WyvtNSQcDTb2SPC/jN4FNWaj9ZUcJTkaLEB/g==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@wix-pilot/core": "^3.4.2", + "@wix-pilot/detox": "^1.0.13", + "ajv": "^8.6.3", + "bunyan": "^1.8.12", + "bunyan-debug-stream": "^3.1.0", + "caf": "^15.0.1", + "chalk": "^4.0.0", + "execa": "^5.1.1", + "find-up": "^5.0.0", + "fs-extra": "^11.0.0", + "funpermaproxy": "^1.1.0", + "glob": "^8.0.3", + "ini": "^1.3.4", + "jest-environment-emit": "^1.2.0", + "json-cycle": "^1.3.0", + "lodash": "^4.17.11", + "multi-sort-stream": "^1.0.3", + "multipipe": "^4.0.0", + "node-ipc": "9.2.1", + "promisify-child-process": "^4.1.2", + "proper-lockfile": "^3.0.2", + "resolve-from": "^5.0.0", + "sanitize-filename": "^1.6.1", + "semver": "^7.0.0", + "serialize-error": "^8.0.1", + "shell-quote": "^1.7.2", + "signal-exit": "^3.0.3", + "stream-json": "^1.7.4", + "strip-ansi": "^6.0.1", + "telnet-client": "1.2.8", + "tmp": "^0.2.1", + "trace-event-lib": "^1.3.1", + "which": "^1.3.1", + "ws": "^7.0.0", + "yargs": "^17.0.0", + "yargs-parser": "^21.0.0", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "detox": "local-cli/cli.js" + }, "engines": { - "node": ">=6" + "node": ">=14" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "jest": "30.x.x || 29.x.x || 28.x.x || ^27.2.5" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + } } }, - "node_modules/cliui": { - "version": "7.0.4", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/detox/node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, "engines": { - "node": ">=0.8" + "node": ">=14.14" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "node_modules/detox/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/co": { - "version": "4.6.0", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" + "node_modules/detox/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/coinselect": { - "version": "3.1.13", - "integrity": "sha512-iJOrKH/7N9gX0jRkxgOHuGjvzvoxUMSeylDhH1sHn+CjLjdin5R0Hz2WEBu/jrZV5OrHcm+6DMzxwu9zb5mSZg==" - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "node_modules/detox/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/color": { - "version": "3.2.1", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/color-string": { - "version": "1.9.1", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/colorette": { - "version": "1.4.0", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" + "node_modules/detox/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">= 0.8" + "node": ">=10" } }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" - }, - "node_modules/command-line-args": { - "version": "5.2.1", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "node_modules/detox/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=4.0.0" + "node": ">=8" } }, - "node_modules/command-line-usage": { - "version": "6.1.3", - "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", - "dependencies": { - "array-back": "^4.0.2", - "chalk": "^2.4.2", - "table-layout": "^1.0.2", - "typical": "^5.2.0" - }, + "node_modules/detox/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">= 10.0.0" } }, - "node_modules/command-line-usage/node_modules/array-back": { - "version": "4.0.2", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "node_modules/detox/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/command-line-usage/node_modules/typical": { - "version": "5.2.0", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "node_modules/diff-sequences": { + "version": "29.6.3", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/commander": { - "version": "2.20.3", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/common-tags": { - "version": "1.8.2", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "engines": { - "node": ">=4.0.0" + "node_modules/diffie-hellman": { + "version": "5.0.3", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" } }, - "node_modules/commondir": { - "version": "1.0.1", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "mime-db": ">= 1.43.0 < 2" + "path-type": "^4.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" + "esutils": "^2.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">=6.0.0" } }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/dom-serializer": { + "version": "2.0.0", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/concat-stream": { - "version": "2.0.0", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" + "node_modules/domelementtype": { + "version": "2.3.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } + "license": "BSD-2-Clause" }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "node_modules/domhandler": { + "version": "5.0.3", + "license": "BSD-2-Clause", "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" + "domelementtype": "^2.3.0" }, "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/consola": { - "version": "2.15.3", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "engines": { - "node": ">=0.10.0" + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/core-js-compat": { - "version": "3.28.0", - "integrity": "sha512-myzPgE7QodMg4nnd3K1TDoES/nADRStM8Gpz0D6nhkwbmwEnE0ZGJgoWsvQ722FR8D7xS0n0LV556RcEicjTyg==", + "node_modules/domutils": { + "version": "3.2.2", + "license": "BSD-2-Clause", "dependencies": { - "browserslist": "^4.21.5" + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/core-util-is": { - "version": "1.0.2", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - }, - "node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "node_modules/drbg.js": { + "version": "1.0.1", + "license": "MIT", "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" + "browserify-aes": "^1.0.6", + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4" }, "engines": { - "node": ">=4" + "node": ">=0.10" } }, - "node_modules/cosmiconfig/node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "node_modules/dtrace-provider": { + "version": "0.8.8", + "hasInstallScript": true, + "license": "BSD-2-Clause", + "optional": true, "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "nan": "^2.14.0" }, "engines": { - "node": ">=4" + "node": ">=0.10" } }, - "node_modules/cosmiconfig/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/cosmiconfig/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "engines": { - "node": ">=4" + "node": ">= 0.4" } }, - "node_modules/crc": { - "version": "3.8.0", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "node_modules/duplexer2": { + "version": "0.1.4", + "license": "BSD-3-Clause", "dependencies": { - "buffer": "^5.1.0" + "readable-stream": "^2.0.2" } }, - "node_modules/crc/node_modules/buffer": { - "version": "5.7.1", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/duplexer2/node_modules/isarray": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "license": "MIT", "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" + "safe-buffer": "~5.1.0" } }, - "node_modules/create-hash": { - "version": "1.2.0", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" + "node_modules/easy-stack": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=6.0.0" } }, - "node_modules/create-hmac": { - "version": "1.1.7", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "node_modules/ecpair": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-3.0.1.tgz", + "integrity": "sha512-uz8wMFvtdr58TLrXnAesBsoMEyY8UudLOfApcyg40XfZjP+gt1xO4cuZSIkZ8hTMTQ8+ETgt7xSIV4eM7M6VNw==", + "license": "MIT", "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "uint8array-tools": "^0.0.8", + "valibot": "^1.2.0", + "wif": "^5.0.0" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "node_modules/ecpair/node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, + "node_modules/ecpair/node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", "dependencies": { - "node-fetch": "2.6.7" + "base-x": "^5.0.0" } }, - "node_modules/cross-fetch/node_modules/node-fetch": { - "version": "2.6.7", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "node_modules/ecpair/node_modules/bs58check": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-4.0.0.tgz", + "integrity": "sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==", + "license": "MIT", "dependencies": { - "whatwg-url": "^5.0.0" - }, + "@noble/hashes": "^1.2.0", + "bs58": "^6.0.0" + } + }, + "node_modules/ecpair/node_modules/uint8array-tools": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.8.tgz", + "integrity": "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==", + "license": "MIT", "engines": { - "node": "4.x || >=6.0.0" - }, + "node": ">=14.0.0" + } + }, + "node_modules/ecpair/node_modules/valibot": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.0.tgz", + "integrity": "sha512-iC/x7fVcSyOwlm/VSt7RlHnzNGLGvR9GnxdifUeWoCJo0q4ZZvrVkIHC6faTlkxG47I2Y4UrFquPuVHCrOnrLg==", + "license": "MIT", "peerDependencies": { - "encoding": "^0.1.0" + "typescript": ">=5" }, "peerDependenciesMeta": { - "encoding": { + "typescript": { "optional": true } } }, - "node_modules/cross-spawn": { - "version": "4.0.2", - "integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==", + "node_modules/ecpair/node_modules/wif": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/wif/-/wif-5.0.0.tgz", + "integrity": "sha512-iFzrC/9ne740qFbNjTZ2FciSRJlHIXoxqk/Y5EnE08QOXu1WjJyCCswwDTYbohAOEnlCtLaAAQBhyaLRFh2hMA==", + "license": "MIT", "dependencies": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" + "bs58check": "^4.0.0" } }, - "node_modules/cross-spawn/node_modules/lru-cache": { - "version": "4.1.5", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "node_modules/ecurve": { + "version": "1.0.6", + "license": "MIT", "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "bigi": "^1.1.0", + "safe-buffer": "^5.0.1" } }, - "node_modules/cross-spawn/node_modules/yallist": { - "version": "2.1.2", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" - }, - "node_modules/crypto-js": { - "version": "4.1.1", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, - "node_modules/css-select": { - "version": "5.1.0", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "node_modules/ejs": { + "version": "3.1.10", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" + "jake": "^10.8.5" }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/css-tree": { - "version": "1.1.3", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, + "node_modules/electron-to-chromium": { + "version": "1.5.302", + "license": "ISC" + }, + "node_modules/electrum-client": { + "version": "3.1.1", + "resolved": "git+ssh://git@github.com/BlueWallet/rn-electrum-client.git#83420b861bac2c0ea343f1d8503104a49e9654a3", + "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">=18" } }, - "node_modules/css-what": { - "version": "6.1.0", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "node_modules/electrum-mnemonic": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "create-hmac": "^1.1.7", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0" + } + }, + "node_modules/elliptic": { + "version": "6.6.1", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/emittery": { + "version": "0.13.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/fb55" + "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/csstype": { - "version": "3.1.1", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + "node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" }, - "node_modules/dayjs": { - "version": "1.11.9", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", - "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" + "node_modules/enabled": { + "version": "2.0.0", + "license": "MIT" }, - "node_modules/debug": { - "version": "4.3.4", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 0.8" } }, - "node_modules/decamelize": { - "version": "4.0.0", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "node_modules/end-of-stream": { + "version": "1.4.4", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "license": "BSD-2-Clause", "engines": { - "node": ">=10" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", "engines": { - "node": ">=0.10" + "node": ">=6" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "license": "MIT", "dependencies": { - "mimic-response": "^3.1.0" + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/errorhandler": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.2.tgz", + "integrity": "sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "escape-html": "~1.0.3" }, "engines": { - "node": ">=10" + "node": ">= 0.8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/dedent": { - "version": "0.7.0", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "node_modules/deep-equal": { - "version": "1.1.1", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "node_modules/es-abstract": { + "version": "1.23.9", + "dev": true, + "license": "MIT", "dependencies": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", "engines": { - "node": ">=4.0.0" + "node": ">= 0.4" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.0", - "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/default-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", - "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "node_modules/es-iterator-helpers": { + "version": "1.2.1", "dev": true, + "license": "MIT", "dependencies": { - "bundle-name": "^3.0.0", - "default-browser-id": "^3.0.0", - "execa": "^7.1.1", - "titleize": "^3.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.4" } }, - "node_modules/default-browser-id": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", - "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", - "dev": true, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", "dependencies": { - "bplist-parser": "^0.2.0", - "untildify": "^4.0.0" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=12" + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.4" } }, - "node_modules/default-browser/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/es-shim-unscopables": { + "version": "1.1.0", "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { - "node": ">= 8" + "node": ">= 0.4" } }, - "node_modules/default-browser/node_modules/execa": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", - "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", + "node_modules/es-to-primitive": { + "version": "1.3.0", "dev": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/default-browser/node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true, + "node_modules/escalade": { + "version": "3.2.0", + "license": "MIT", "engines": { - "node": ">=14.18.0" + "node": ">=6" } }, - "node_modules/default-browser/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, - "node_modules/default-browser/node_modules/mimic-fn": { + "node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/default-browser/node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "node_modules/eslint": { + "version": "8.57.1", "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^4.0.0" + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "bin": { + "eslint": "bin/eslint.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/default-browser/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/eslint-compat-utils": { + "version": "0.5.1", "dev": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^4.0.0" + "semver": "^7.5.4" }, "engines": { "node": ">=12" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "eslint": ">=6.0.0" } }, - "node_modules/default-browser/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/eslint-compat-utils/node_modules/semver": { + "version": "7.7.1", "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/default-browser/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/eslint-config-prettier": { + "version": "9.1.0", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/default-browser/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "node_modules/eslint-config-standard": { + "version": "17.1.0", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" } }, - "node_modules/default-browser/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/eslint-config-standard-jsx": { + "version": "11.0.0", "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peerDependencies": { + "eslint": "^8.8.0", + "eslint-plugin-react": "^7.28.0" } }, - "node_modules/defaults": { - "version": "1.0.4", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "node_modules/eslint-config-standard-react": { + "version": "13.0.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peerDependencies": { + "eslint": "^8.8.0", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.6.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "dev": true, + "license": "MIT", "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/define-properties": { - "version": "1.2.0", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "dev": true, + "license": "MIT", "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "debug": "^3.2.7" }, "engines": { - "node": ">= 0.4" + "node": ">=4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/define-property": { - "version": "2.0.2", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "dev": true, + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "license": "MIT", "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" }, "engines": { - "node": ">=0.10.0" + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/eslint-plugin-eslint-comments": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5", + "ignore": "^5.0.5" + }, "engines": { - "node": ">=0.4.0" + "node": ">=6.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" } }, - "node_modules/denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==" + "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/eslint-plugin-ft-flow": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, "engines": { - "node": ">= 0.8" + "node": ">=12.22.0" + }, + "peerDependencies": { + "@babel/eslint-parser": "^7.12.0", + "eslint": "^8.1.0" } }, - "node_modules/deprecated-react-native-prop-types": { - "version": "3.0.1", - "integrity": "sha512-J0jCJcsk4hMlIb7xwOZKLfMpuJn6l8UtrPEzzQV5ewz5gvKNYakhBuq9h2rWX7YwHHJZFhU5W8ye7dB9oN8VcQ==", + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "dev": true, + "license": "MIT", "dependencies": { - "@react-native/normalize-color": "*", - "invariant": "*", - "prop-types": "*" + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "node_modules/des.js": { - "version": "1.0.1", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/detect-libc": { - "version": "2.0.1", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/detox": { - "version": "20.11.4", - "resolved": "https://registry.npmjs.org/detox/-/detox-20.11.4.tgz", - "integrity": "sha512-P48KAtK8qIDOxJKUl4q/syPkuHz67kAeFlNodBZg5aO4hJiH+RsbEkQfJSYkTCeZV800EcmUQwZK2M5amLoYaw==", - "hasInstallScript": true, + "node_modules/eslint-plugin-jest": { + "version": "28.11.0", + "dev": true, + "license": "MIT", "dependencies": { - "ajv": "^8.6.3", - "bunyan": "^1.8.12", - "bunyan-debug-stream": "^3.1.0", - "caf": "^15.0.1", - "chalk": "^4.0.0", - "child-process-promise": "^2.2.0", - "execa": "^5.1.1", - "find-up": "^5.0.0", - "fs-extra": "^11.0.0", - "funpermaproxy": "^1.1.0", - "glob": "^8.0.3", - "ini": "^1.3.4", - "json-cycle": "^1.3.0", - "lodash": "^4.17.11", - "multi-sort-stream": "^1.0.3", - "multipipe": "^4.0.0", - "node-ipc": "9.2.1", - "proper-lockfile": "^3.0.2", - "resolve-from": "^5.0.0", - "sanitize-filename": "^1.6.1", - "semver": "^7.0.0", - "serialize-error": "^8.0.1", - "shell-quote": "^1.7.2", - "signal-exit": "^3.0.3", - "stream-json": "^1.7.4", - "strip-ansi": "^6.0.1", - "telnet-client": "1.2.8", - "tempfile": "^2.0.0", - "trace-event-lib": "^1.3.1", - "which": "^1.3.1", - "ws": "^7.0.0", - "yargs": "^17.0.0", - "yargs-parser": "^21.0.0", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "detox": "local-cli/cli.js" + "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "engines": { - "node": ">=14.5.0" + "node": "^16.10.0 || ^18.12.0 || >=20.0.0" }, "peerDependencies": { - "jest": "29.x.x || 28.x.x || ^27.2.5" + "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", + "jest": "*" }, "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, "jest": { "optional": true } } }, - "node_modules/detox/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/eslint-plugin-n": { + "version": "16.6.2", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@eslint-community/eslint-utils": "^4.4.0", + "builtins": "^5.0.1", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^13.24.0", + "ignore": "^5.2.4", + "is-builtin-module": "^3.2.1", + "is-core-module": "^2.12.1", + "minimatch": "^3.1.2", + "resolve": "^1.22.2", + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/detox/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/detox/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/eslint-plugin-n/node_modules/globals": { + "version": "13.24.0", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/detox/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=12" + "node": "*" } }, - "node_modules/detox/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "node_modules/detox/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/detox/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, + "node_modules/eslint-plugin-n/node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -9281,1224 +9164,1108 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/detox/node_modules/fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "node_modules/eslint-plugin-prettier": { + "version": "5.2.6", + "dev": true, + "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.0" }, "engines": { - "node": ">=14.14" - } - }, - "node_modules/detox/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } } }, - "node_modules/detox/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/eslint-plugin-promise": { + "version": "6.6.0", + "dev": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/detox/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "dev": true, + "license": "MIT", "dependencies": { - "universalify": "^2.0.0" + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, - "node_modules/detox/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, - "node_modules/detox/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "hermes-estree": "0.25.1" } }, - "node_modules/detox/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/eslint-plugin-react-native": { + "version": "4.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "eslint-plugin-react-native-globals": "^0.1.1" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8" } }, - "node_modules/detox/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/eslint-plugin-react-native-globals": { + "version": "0.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/detox/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "esutils": "^2.0.2" }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/detox/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/detox/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/detox/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/detox/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/eslint-scope": { + "version": "5.1.1", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, "engines": { - "node": ">=12" + "node": ">=8.0.0" } }, - "node_modules/detox/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=12" + "node": ">=4.0" } }, - "node_modules/diff-sequences": { - "version": "29.4.3", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/dijkstrajs": { - "version": "1.0.2", - "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==" + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" }, - "node_modules/dir-glob": { - "version": "3.0.1", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", "dev": true, + "license": "MIT", "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "esutils": "^2.0.2" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=6.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/domelementtype": { - "version": "2.3.0", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "5.0.3", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "dev": true, + "license": "MIT", "dependencies": { - "domelementtype": "^2.3.0" + "type-fest": "^0.20.2" }, "engines": { - "node": ">= 4" + "node": ">=8" }, "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/domutils": { - "version": "3.0.1", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.1" + "argparse": "^2.0.1" }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/drbg.js": { - "version": "1.0.1", - "integrity": "sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g==", + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", "dependencies": { - "browserify-aes": "^1.0.6", - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=0.10" + "node": "*" } }, - "node_modules/dtrace-provider": { - "version": "0.8.8", - "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", - "hasInstallScript": true, - "optional": true, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "nan": "^2.14.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, - "node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "dependencies": { - "readable-stream": "^2.0.2" + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/duplexer2/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/espree": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/duplexer2/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/duplexer2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/easy-stack": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", - "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==", + "node_modules/esprima": { + "version": "4.0.1", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, "engines": { - "node": ">=6.0.0" + "node": ">=4" } }, - "node_modules/ecpair": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.0.1.tgz", - "integrity": "sha512-iT3wztQMeE/nDTlfnAg8dAFUfBS7Tq2BXzq3ae6L+pWgFU0fQ3l0woTzdTBrJV3OxBjxbzjq8EQhAbEmJNWFSw==", + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "randombytes": "^2.1.0", - "typeforce": "^1.18.0", - "wif": "^2.0.6" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=0.10" } }, - "node_modules/ecurve": { - "version": "1.0.6", - "integrity": "sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w==", + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "bigi": "^1.1.0", - "safe-buffer": "^5.0.1" + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/electron-to-chromium": { - "version": "1.4.309", - "integrity": "sha512-U7DTiKe4h+irqBG6h4EZ0XXaZuJj4md3xIXXaGSYhwiumPZ4BSc6rgf9UD0hVUMaeP/jB0q5pKWCPxvhO8fvZA==" - }, - "node_modules/electrum-client": { - "version": "2.0.0", - "resolved": "git+ssh://git@github.com/BlueWallet/rn-electrum-client.git#76c0ea35e1a50c47f3a7f818d529ebd100161496", - "integrity": "sha512-w9LHCQYUlCddBRGrDmgo1EUNp+zmzcyQSKLFOeO1XPITiAAFQDBZLwORVbBPywhMXf4PUk1dOphhHzJBJYG0vA==", - "license": "MIT", + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=6" + "node": ">=4.0" } }, - "node_modules/electrum-mnemonic": { - "version": "2.0.0", - "integrity": "sha512-egooI/RRX31y1LUbvv2OJf0eptrJjc5/lFv6txgDZx91g6JdZrQeQp+5AqlcfDUdsl2aDkeHk1a79J6bt3v8SA==", - "dependencies": { - "create-hmac": "^1.1.7", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0" + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/elliptic": { - "version": "6.5.4", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "node_modules/emittery": { - "version": "0.13.1", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, + "node_modules/event-pubsub": { + "version": "4.3.0", + "license": "Unlicense", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "node": ">=4.0.0" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "node_modules/event-target-shim": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "node_modules/encode-utf8": { - "version": "1.0.3", - "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==" + "node_modules/eventemitter3": { + "version": "4.0.7", + "license": "MIT" }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/events": { + "version": "3.3.0", + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=0.8.x" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "license": "MIT", "dependencies": { - "once": "^1.4.0" + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" } }, - "node_modules/entities": { - "version": "4.4.0", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "node_modules/execa": { + "version": "5.1.1", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, "engines": { - "node": ">=0.12" + "node": ">=10" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "bin": { - "envinfo": "dist/cli.js" - }, + "node_modules/exeunt": { + "version": "1.1.0", + "license": "MPL-2.0", "engines": { - "node": ">=4" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" + "node": ">=0.10" } }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "dependencies": { - "stackframe": "^1.3.4" + "node_modules/exit": { + "version": "0.1.2", + "dev": true, + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", - "dependencies": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" - }, + "node_modules/expand-template": { + "version": "2.0.3", + "license": "(MIT OR WTFPL)", "engines": { - "node": ">= 0.8" + "node": ">=6" } }, - "node_modules/es-abstract": { - "version": "1.21.1", - "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", + "node_modules/expect": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.4", - "is-array-buffer": "^3.0.1", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "node_modules/expect/node_modules/@jest/types": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "node_modules/expect/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "@types/yargs-parser": "*" } }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "node_modules/expect/node_modules/jest-message-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/es6-object-assign": { - "version": "1.1.0", - "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==" - }, - "node_modules/escalade": { - "version": "3.1.1", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/expect/node_modules/jest-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0" }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/fast-base64-decode": { + "version": "1.0.0", + "license": "MIT" }, - "node_modules/eslint": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz", - "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.1.0", - "@eslint/js": "8.44.0", - "@humanwhocodes/config-array": "^0.11.10", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.6.0", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8.6.0" } }, - "node_modules/eslint-config-prettier": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", - "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" }, - "peerDependencies": { - "eslint": ">=7.0.0" + "engines": { + "node": ">= 6" } }, - "node_modules/eslint-config-standard": { - "version": "17.0.0", - "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "url": "https://github.com/sponsors/fastify" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "opencollective", + "url": "https://opencollective.com/fastify" } ], - "peerDependencies": { - "eslint": "^8.0.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0", - "eslint-plugin-promise": "^6.0.0" - } + "license": "BSD-3-Clause" }, - "node_modules/eslint-config-standard-jsx": { - "version": "11.0.0", - "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==", - "dev": true, + "node_modules/fast-xml-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" + "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "peerDependencies": { - "eslint": "^8.8.0", - "eslint-plugin-react": "^7.28.0" + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" } }, - "node_modules/eslint-config-standard-react": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard-react/-/eslint-config-standard-react-13.0.0.tgz", - "integrity": "sha512-HrVPGj8UncHfV+BsdJTuJpVsomn6AIrke3Af2Fh4XFvQQDU+iO6N2ZL+UsC+scExft4fU3uf7fJwj7PKWnXJDA==", - "dev": true, + "node_modules/fast-xml-parser": { + "version": "5.5.9", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz", + "integrity": "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" + "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "peerDependencies": { - "eslint": "^8.8.0", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.6.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, + "license": "MIT", "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.2" + }, + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, + "node_modules/fastq": { + "version": "1.19.1", + "license": "ISC", "dependencies": { - "ms": "^2.1.1" + "reusify": "^1.0.4" } }, - "node_modules/eslint-module-utils": { - "version": "2.7.4", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "dev": true, - "dependencies": { - "debug": "^3.2.7" + "node_modules/fb-dotslash": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", + "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "dotslash": "bin/dotslash" }, "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "node": ">=20" } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, + "node_modules/fb-watchman": { + "version": "2.0.2", + "license": "Apache-2.0", "dependencies": { - "ms": "^2.1.1" + "bser": "2.1.1" } }, - "node_modules/eslint-plugin-es-x": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.2.0.tgz", - "integrity": "sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.6.0" - }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" + "node": ">=12.0.0" }, "peerDependencies": { - "eslint": ">=8" + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/eslint-plugin-eslint-comments": { - "version": "3.2.0", - "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", + "node_modules/fecha": { + "version": "4.2.3", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", "dev": true, + "license": "MIT", "dependencies": { - "escape-string-regexp": "^1.0.5", - "ignore": "^5.0.5" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=6.5.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": { - "version": "1.0.5", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/filelist": { + "version": "1.0.4", "dev": true, - "engines": { - "node": ">=0.8.0" + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" } }, - "node_modules/eslint-plugin-ft-flow": { - "version": "2.0.3", - "integrity": "sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg==", + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", "dev": true, + "license": "ISC", "dependencies": { - "lodash": "^4.17.21", - "string-natural-compare": "^3.0.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=12.22.0" - }, - "peerDependencies": { - "@babel/eslint-parser": "^7.12.0", - "eslint": "^8.1.0" + "node": ">=10" } }, - "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, + "node_modules/fill-range": { + "version": "7.1.1", + "license": "MIT", "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "node": ">=8" } }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/eslint-plugin-jest": { - "version": "26.9.0", - "integrity": "sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==", - "dev": true, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^5.10.0" + "ee-first": "1.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } + "node": ">= 0.8" } }, - "node_modules/eslint-plugin-n": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.0.1.tgz", - "integrity": "sha512-CDmHegJN0OF3L5cz5tATH84RPQm9kG+Yx39wIqIwPR2C0uhBGMWfbbOtetR83PQjjidA5aXMu+LEFw1jaSwvTA==", - "dev": true, + "node_modules/find-replace": { + "version": "3.0.0", + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "builtins": "^5.0.1", - "eslint-plugin-es-x": "^7.1.0", - "ignore": "^5.2.4", - "is-core-module": "^2.12.1", - "minimatch": "^3.1.2", - "resolve": "^1.22.2", - "semver": "^7.5.3" + "array-back": "^3.0.1" }, "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=7.0.0" + "node": ">=4.0.0" } }, - "node_modules/eslint-plugin-n/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "node_modules/find-up": { + "version": "5.0.0", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-n/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "micromatch": "^4.0.2" } }, - "node_modules/eslint-plugin-n/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "node_modules/flat": { + "version": "5.0.2", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } }, - "node_modules/eslint-plugin-prettier": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", - "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", + "node_modules/flat-cache": { + "version": "3.2.0", "dev": true, + "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/eslint-plugin-promise": { - "version": "6.1.1", - "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "node_modules/flatted": { + "version": "3.3.3", "dev": true, + "license": "ISC" + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "license": "MIT" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/for-each": { + "version": "0.3.5", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-react": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.0.tgz", - "integrity": "sha512-qewL/8P34WkY8jAqdQxsiL82pDUeT7nhs8IsuXgfgnsEloKCT4miAV9N9kGtx7/KM9NH/NCGUE7Edt9iGxLXFw==", - "dev": true, + "node_modules/form-data": { + "version": "3.0.4", + "license": "MIT", "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.8" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" }, "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "node": ">= 6" } }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "node": ">= 0.6" } }, - "node_modules/eslint-plugin-react-native": { - "version": "4.0.0", - "integrity": "sha512-kMmdxrSY7A1WgdqaGC+rY/28rh7kBGNBRsk48ovqkQmdg5j4K+DaFmegENDzMrdLkoufKGRNkKX6bgSwQTCAxQ==", - "dev": true, + "node_modules/fs-constants": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.7.4", - "eslint-plugin-react-native-globals": "^0.1.1" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, - "peerDependencies": { - "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8" + "engines": { + "node": ">=6 <7 || >=8" } }, - "node_modules/eslint-plugin-react-native-globals": { - "version": "0.1.2", - "integrity": "sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==", - "dev": true + "node_modules/fs.realpath": { + "version": "1.0.0", + "license": "ISC" }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.10.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, - "bin": { - "resolve": "bin/resolve" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/functions-have-names": { + "version": "1.2.3", "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "4.3.0", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, + "node_modules/funpermaproxy": { + "version": "1.1.0", + "license": "Apache-2.0", "engines": { - "node": ">=4.0" + "node": ">=8.3.0" } }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=6.9.0" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "node_modules/get-caller-file": { + "version": "2.0.5", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/get-package-type": { + "version": "0.1.0", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8.0.0" } }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.4" } }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/cross-spawn": { - "version": "7.0.3", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, + "node_modules/get-stream": { + "version": "6.0.1", + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz", - "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==", + "node_modules/get-symbol-description": { + "version": "1.1.0", "dev": true, + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "node_modules/get-tsconfig": { + "version": "4.10.0", "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "node_modules/github-from-package": { + "version": "0.0.0", + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "license": "ISC", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=10" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint/node_modules/glob-parent": { + "node_modules/glob-parent": { "version": "6.0.2", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -10506,64 +10273,50 @@ "node": ">=10.13.0" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.20.0", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "license": "MIT", "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "license": "ISC", "dependencies": { - "argparse": "^2.0.1" + "brace-expansion": "^1.1.7" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": "*" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/globalthis": { + "version": "1.0.4", "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/globby": { + "version": "11.1.0", "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" }, "engines": { "node": ">=10" @@ -10572,817 +10325,740 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/shebang-command": { - "version": "2.0.0", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/shebang-regex": { - "version": "3.0.0", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } + "node_modules/graceful-fs": { + "version": "4.2.11", + "license": "ISC" }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/graphemer": { + "version": "1.4.0", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/has-bigints": { + "version": "1.1.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/which": { - "version": "2.0.2", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, + "node_modules/has-flag": { + "version": "4.0.0", + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "es-define-property": "^1.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "node_modules/has-proto": { + "version": "1.2.0", "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esprima": { - "version": "4.0.1", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esquery": { - "version": "1.4.2", - "integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==", - "dev": true, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "estraverse": "^5.1.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=0.10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, + "node_modules/hash-base": { + "version": "3.1.0", + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" }, "engines": { - "node": ">=4.0" + "node": ">=4" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" + "node_modules/hash.js": { + "version": "1.1.7", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" } }, - "node_modules/esutils": { - "version": "2.0.3", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } + "node_modules/hermes-compiler": { + "version": "250829098.0.10", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.10.tgz", + "integrity": "sha512-TcRlZ0/TlyfJqquRFAWoyElVNnkdYRi/sEp4/Qy8/GYxjg8j2cS9D4MjuaQ+qimkmLN7AmO+44IznRf06mAr0w==", + "license": "MIT" }, - "node_modules/event-pubsub": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", - "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", - "engines": { - "node": ">=4.0.0" - } + "node_modules/hermes-estree": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", + "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", + "license": "MIT" }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" + "node_modules/hermes-parser": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", + "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.33.3" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/events": { - "version": "3.3.0", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" + "node_modules/hmac-drbg": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "license": "BSD-3-Clause", "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" + "react-is": "^16.7.0" } }, - "node_modules/execa": { - "version": "5.1.1", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "license": "MIT" + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "license": "ISC" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">=10" + "node": ">= 0.8" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "7.0.3", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">= 0.8" } }, - "node_modules/execa/node_modules/shebang-command": { - "version": "2.0.0", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=8" + "node": ">= 14" } }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "3.0.0", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/human-signals": { + "version": "2.1.0", + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": ">=10.17.0" } }, - "node_modules/execa/node_modules/which": { - "version": "2.0.2", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">= 8" + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/exit": { - "version": "0.1.2", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">= 4" } }, - "node_modules/exit-on-epipe": { - "version": "1.0.1", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "license": "MIT", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, "engines": { - "node": ">=0.8" + "node": ">=16.x" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "node_modules/import-fresh": { + "version": "3.3.1", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expect": { - "version": "29.4.3", - "integrity": "sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.4.3", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3" - }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=4" } }, - "node_modules/expect/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/import-local": { + "version": "3.2.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expect/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "node_modules/imurmurhash": { + "version": "0.1.4", "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "license": "MIT", + "engines": { + "node": ">=0.8.19" } }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/indent-string": { + "version": "4.0.0", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/expect/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/inflight": { + "version": "1.0.6", + "license": "ISC", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/expect/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.4" } }, - "node_modules/expect/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/expect/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "node_modules/invariant": { + "version": "2.2.4", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" } }, - "node_modules/expect/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, + "node_modules/is-arguments": { + "version": "1.2.0", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/expect/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/is-array-buffer": { + "version": "3.0.5", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/expect/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "node": ">= 0.4" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "node_modules/is-bigint": { + "version": "1.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "is-plain-object": "^2.0.4" + "has-bigints": "^1.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-diff": { - "version": "1.2.0", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "node_modules/is-boolean-object": { + "version": "1.2.2", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=8.6.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/is-builtin-module": { + "version": "3.2.1", "dev": true, + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "builtin-modules": "^3.3.0" }, "engines": { - "node": ">= 6" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fast-xml-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.4.tgz", - "integrity": "sha512-fbfMDvgBNIdDJLdLOwacjFAPYt67tr31H9ZhWSm45CDAxvd0I6WTlSOUo7K2P/K5sA5JgMKG64PI3DMcaFdWpQ==", - "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "dependencies": { - "strnum": "^1.0.5" + "node_modules/is-callable": { + "version": "1.2.7", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, - "bin": { - "fxparser": "src/cli/cli.js" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fastq": { - "version": "1.15.0", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, + "node_modules/is-core-module": { + "version": "2.16.1", + "license": "MIT", "dependencies": { - "reusify": "^1.0.4" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", "dependencies": { - "bser": "2.1.1" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/is-date-object": { + "version": "1.1.0", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "node_modules/fill-range": { - "version": "7.0.1", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dependencies": { - "to-regex-range": "^5.0.1" + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/filter-obj": { - "version": "1.1.0", - "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "node_modules/is-extglob": { + "version": "2.1.1", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "call-bound": "^1.0.3" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/finalhandler/node_modules/ms": { + "node_modules/is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "node_modules/find-cache-dir": { + "node_modules/is-generator-fn": { "version": "2.1.0", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, + "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/find-cache-dir/node_modules/find-up": { - "version": "3.0.0", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "node_modules/is-generator-function": { + "version": "1.1.0", + "license": "MIT", "dependencies": { - "locate-path": "^3.0.0" + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=6" - } + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/find-cache-dir/node_modules/locate-path": { - "version": "3.0.0", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "node_modules/is-glob": { + "version": "4.0.3", + "license": "MIT", "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/find-cache-dir/node_modules/make-dir": { - "version": "2.1.0", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/find-cache-dir/node_modules/p-limit": { - "version": "2.3.0", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, + "node_modules/is-map": { + "version": "2.0.3", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-cache-dir/node_modules/p-locate": { - "version": "3.0.0", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "node_modules/is-nan": { + "version": "1.3.2", + "license": "MIT", "dependencies": { - "p-limit": "^2.0.0" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" }, "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/path-exists": { - "version": "3.0.0", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-cache-dir/node_modules/pify": { - "version": "4.0.1", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.12.0" } }, - "node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "3.0.0", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "node_modules/is-number-object": { + "version": "1.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "find-up": "^3.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/find-replace": { - "version": "3.0.0", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "dependencies": { - "array-back": "^3.0.1" + "node": ">= 0.4" }, - "engines": { - "node": ">=4.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-up": { - "version": "4.1.0", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/findit": { - "version": "2.0.0", - "integrity": "sha512-ENZS237/Hr8bjczn5eKuBohLgaD0JyUd0arxretR1f9RO46vZHA1b2y0VorgGV3WaOT3c+78P8h7v4JGJ1i/rg==" - }, - "node_modules/flat": { - "version": "5.0.2", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "bin": { - "flat": "cli.js" + "node_modules/is-plain-obj": { + "version": "2.1.0", + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/flat-cache": { - "version": "3.0.4", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, + "node_modules/is-regex": { + "version": "1.2.1", + "license": "MIT", "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/flow-parser": { - "version": "0.185.2", - "integrity": "sha512-2hJ5ACYeJCzNtiVULov6pljKOLygy0zddoqSI1fFetM+XRPpRshFdGEijtqlamA1XwyZ+7rhryI6FQFzvtLWUQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dependencies": { - "is-callable": "^1.1.3" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/for-in": { - "version": "1.0.2", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "node_modules/is-set": { + "version": "2.0.3", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/form-data": { - "version": "3.0.1", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "node": ">= 0.4" }, - "engines": { - "node": ">= 6" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "dev": true, + "license": "MIT", "dependencies": { - "map-cache": "^0.2.2" + "call-bound": "^1.0.3" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "node_modules/is-stream": { + "version": "2.0.1", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/frisbee": { - "version": "3.1.4", - "integrity": "sha512-LoGzXyYWuGSwUUDKdlbYbokGf08UT37wzdDtVPtOeMWHgzeKiAceIPRrAn7Vn9aYaS+uMJkq7aze6pbfj9hnBA==", + "node_modules/is-string": { + "version": "1.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.10.2", - "abortcontroller-polyfill": "^1.4.0", - "boolean": "^3.0.1", - "caseless": "^0.12.0", - "common-tags": "^1.8.0", - "cross-fetch": "^3.0.4", - "debug": "^4.1.1", - "qs": "6.9.4", - "url-join": "^4.0.1", - "url-parse": "^1.4.7" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=8.9.4" - } - }, - "node_modules/frisbee/node_modules/qs": { - "version": "6.9.4", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", - "engines": { - "node": ">=0.6" + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/function-bind": { + "node_modules/is-symbol": { "version": "1.1.1", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -11391,71 +11067,68 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/funpermaproxy": { - "version": "1.1.0", - "integrity": "sha512-2Sp1hWuO8m5fqeFDusyhKqYPT+7rGLw34N3qonDcdRP8+n7M7Gl/yKp/q7oCxnnJ6pWCectOmLFJpsMU/++KrQ==", - "engines": { - "node": ">=8.3.0" - } + "node_modules/is-typedarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "dev": true, + "license": "MIT", "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "node_modules/is-weakref": { + "version": "1.1.1", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "node_modules/is-weakset": { + "version": "2.0.4", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -11464,1057 +11137,1177 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-value": { - "version": "2.0.6", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + "node_modules/isarray": { + "version": "2.0.5", + "license": "MIT" }, - "node_modules/glob": { - "version": "7.2.3", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "node_modules/iserror": { + "version": "0.0.2", + "license": "MIT" }, - "node_modules/globals": { - "version": "11.12.0", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/globalthis": { - "version": "1.0.3", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "define-properties": "^1.1.3" + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/globby": { - "version": "11.1.0", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gopd": { - "version": "1.0.1", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "get-intrinsic": "^1.1.3" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=10" } }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/istanbul-reports": { + "version": "3.1.7", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "function-bind": "^1.1.1" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": ">= 0.4.0" + "node": ">=8" } }, - "node_modules/has-bigints": { - "version": "1.0.2", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "node_modules/iterator.prototype": { + "version": "1.1.5", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "node_modules/jake": { + "version": "10.9.2", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "get-intrinsic": "^1.1.1" + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/has-proto": { - "version": "1.0.1", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.11", "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "*" } }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "node_modules/jest": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/has-value": { - "version": "1.0.0", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "node_modules/jest-changed-files": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/has-values": { - "version": "1.0.0", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "node_modules/jest-changed-files/node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, + "license": "MIT", "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "node_modules/jest-changed-files/node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" + "@types/yargs-parser": "*" } }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/jest-changed-files/node_modules/jest-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "node_modules/jest-circus": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/hash-base": { - "version": "3.1.0", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "node_modules/jest-circus/node_modules/@jest/console": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/hermes-estree": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.8.0.tgz", - "integrity": "sha512-W6JDAOLZ5pMPMjEiQGLCXSSV7pIBEgRR5zGkxgmzGSXHOxqV5dC/M1Zevqpbm9TZDE5tu358qZf8Vkzmsc+u7Q==" - }, - "node_modules/hermes-parser": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.8.0.tgz", - "integrity": "sha512-yZKalg1fTYG5eOiToLUaw69rQfZq/fi+/NtEXRU7N87K/XobNRhRWorh80oSge2lWUiZfTgUvRJH+XgZWrhoqA==", + "node_modules/jest-circus/node_modules/@jest/test-result": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "hermes-estree": "0.8.0" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/hermes-profile-transformer": { - "version": "0.0.6", - "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", + "node_modules/jest-circus/node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, + "license": "MIT", "dependencies": { - "source-map": "^0.7.3" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/hermes-profile-transformer/node_modules/source-map": { - "version": "0.7.4", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" + "node_modules/jest-circus/node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" } }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "node_modules/jest-circus/node_modules/jest-message-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "engines": { - "node": ">=10.17.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "node_modules/jest-circus/node_modules/jest-util": { + "version": "29.7.0", "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/image-size": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", - "integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==", - "bin": { - "image-size": "bin/image-size.js" + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=4.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/jest-cli": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/jest-cli/node_modules/@jest/console": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/import-local": { - "version": "3.1.0", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "node_modules/jest-cli/node_modules/@jest/test-result": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/jest-cli/node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, "engines": { - "node": ">=0.8.19" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/inflight": { - "version": "1.0.6", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "node_modules/jest-cli/node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, + "license": "MIT", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "@types/yargs-parser": "*" } }, - "node_modules/inherits": { - "version": "2.0.4", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/internal-slot": { - "version": "1.0.5", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "node_modules/jest-cli/node_modules/jest-message-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ip": { - "version": "1.1.8", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "node_modules/jest-cli/node_modules/jest-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^6.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-arguments": { - "version": "1.1.1", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "node_modules/jest-config": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/is-array-buffer": { - "version": "3.0.1", - "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "node_modules/jest-config/node_modules/@jest/types": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-typed-array": "^1.1.10" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "node_modules/jest-config/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@types/yargs-parser": "*" } }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "node_modules/jest-config/node_modules/jest-haste-map": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/is-callable": { - "version": "1.2.7", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node_modules/jest-config/node_modules/jest-regex-util": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "node_modules/jest-config/node_modules/jest-resolve": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^6.0.0" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-date-object": { - "version": "1.0.5", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "node_modules/jest-config/node_modules/jest-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "node_modules/jest-config/node_modules/jest-worker": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "node_modules/jest-config/node_modules/resolve.exports": { + "version": "2.0.3", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "node_modules/jest-config/node_modules/supports-color": { + "version": "8.1.1", "dev": true, - "bin": { - "is-docker": "cli.js" + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/is-extendable": { - "version": "0.1.1", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "node_modules/jest-diff": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/jest-docblock": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "node_modules/jest-each": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "node_modules/jest-each/node_modules/@jest/types": { + "version": "29.6.3", "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "node_modules/jest-each/node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-each/node_modules/jest-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "node_modules/jest-environment-emit": { + "version": "1.2.0", + "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "bunyamin": "^1.5.2", + "bunyan": "^2.0.5", + "bunyan-debug-stream": "^3.1.0", + "funpermaproxy": "^1.1.0", + "lodash.merge": "^4.6.2", + "node-ipc": "9.2.1", + "strip-ansi": "^6.0.0", + "tslib": "^2.5.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=16.14.0" + }, + "peerDependencies": { + "@jest/environment": ">=27.2.5", + "@jest/types": ">=27.2.5", + "jest": ">=27.2.5", + "jest-environment-jsdom": ">=27.2.5", + "jest-environment-node": ">=27.2.5" + }, + "peerDependenciesMeta": { + "@jest/environment": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "jest": { + "optional": true + }, + "jest-environment-jsdom": { + "optional": true + }, + "jest-environment-node": { + "optional": true + } } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, + "node_modules/jest-environment-emit/node_modules/bunyan": { + "version": "2.0.5", + "engines": [ + "node >=0.10.0" + ], + "license": "MIT", "dependencies": { - "is-docker": "^3.0.0" + "exeunt": "1.1.0" }, "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" + "bunyan": "bin/bunyan" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "node_modules/jest-environment-emit/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { "node": ">=8" } }, - "node_modules/is-nan": { - "version": "1.3.2", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "node_modules/jest-environment-node/node_modules/@jest/types": { + "version": "29.6.3", "dev": true, - "engines": { - "node": ">= 0.4" + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" + "node_modules/jest-environment-node/node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "node_modules/jest-environment-node/node_modules/jest-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, + "node_modules/jest-get-type": { + "version": "29.6.3", + "license": "MIT", "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/jest-haste-map": { + "version": "27.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + }, "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "isobject": "^3.0.1" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-regex": { - "version": "1.1.4", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "node_modules/jest-message-util": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-string": { - "version": "1.0.7", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/is-symbol": { - "version": "1.0.4", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "node_modules/jest-message-util/node_modules/react-is": { + "version": "17.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-mock": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "node_modules/jest-mock/node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/jest-mock/node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" } }, - "node_modules/is-weakref": { - "version": "1.0.2", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "node_modules/jest-mock/node_modules/jest-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "1.1.0", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/isarray": { - "version": "1.0.0", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/iserror": { - "version": "0.0.2", - "integrity": "sha512-oKGGrFVaWwETimP3SiWwjDeY27ovZoyZPHtxblC4hCq9fXxed/jasx+ATWFFjCVSRZng8VTMsN1nDnGo6zMBSw==" - }, - "node_modules/isexe": { - "version": "2.0.0", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/isobject": { - "version": "3.0.1", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "node_modules/jest-regex-util": { + "version": "27.5.1", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "node_modules/jest-resolve": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-resolve-dependencies/node_modules/jest-regex-util": { + "version": "29.6.3", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "5.2.0", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "node_modules/jest-resolve/node_modules/camelcase": { + "version": "6.3.0", "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, + "license": "MIT", "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "node_modules/jest-resolve/node_modules/jest-get-type": { + "version": "27.5.1", "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest": { - "version": "29.4.3", - "integrity": "sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA==", + "node_modules/jest-resolve/node_modules/jest-validate": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "@jest/core": "^29.4.3", - "@jest/types": "^29.4.3", - "import-local": "^3.0.2", - "jest-cli": "^29.4.3" - }, - "bin": { - "jest": "bin/jest.js" + "@jest/types": "^27.5.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "leven": "^3.1.0", + "pretty-format": "^27.5.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-changed-files": { - "version": "29.4.3", - "integrity": "sha512-Vn5cLuWuwmi2GNNbokPOEcvrXGSGrqVnPEZV7rC6P7ck07Dyw9RFnvWglnupSh+hGys0ajGtw/bc2ZgweljQoQ==", + "node_modules/jest-resolve/node_modules/pretty-format": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-circus": { - "version": "29.4.3", - "integrity": "sha512-Vw/bVvcexmdJ7MLmgdT3ZjkJ3LKu8IlpefYokxiqoZy6OCQ2VAm6Vk3t/qHiAGUXbdbJKJWnc8gH3ypTbB/OBw==", + "node_modules/jest-resolve/node_modules/react-is": { + "version": "17.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-runner": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/environment": "^29.4.3", - "@jest/expect": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.4.3", - "jest-matcher-utils": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-runtime": "^29.4.3", - "jest-snapshot": "^29.4.3", - "jest-util": "^29.4.3", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", "p-limit": "^3.1.0", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "source-map-support": "0.5.13" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", + "node_modules/jest-runner/node_modules/@jest/console": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", + "node_modules/jest-runner/node_modules/@jest/test-result": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -12522,12 +12315,37 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/jest-runner/node_modules/@jest/transform": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -12538,92 +12356,90 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "node_modules/jest-runner/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-runner/node_modules/jest-haste-map": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-runner/node_modules/jest-message-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-runner/node_modules/jest-regex-util": { + "version": "29.6.3", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", + "node_modules/jest-runner/node_modules/jest-resolve": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/jest-runner/node_modules/jest-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -12634,73 +12450,109 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-runner/node_modules/jest-worker": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/resolve.exports": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "8.1.1", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-cli": { - "version": "29.4.3", - "integrity": "sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg==", + "node_modules/jest-runner/node_modules/write-file-atomic": { + "version": "4.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/core": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", "chalk": "^4.0.0", - "exit": "^0.1.2", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.4.3", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } } }, - "node_modules/jest-cli/node_modules/@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", + "node_modules/jest-runtime/node_modules/@jest/console": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-cli/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", + "node_modules/jest-runtime/node_modules/@jest/test-result": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -12708,12 +12560,37 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-cli/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/jest-runtime/node_modules/@jest/transform": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/types": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -12724,105 +12601,90 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-cli/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "node_modules/jest-runtime/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-runtime/node_modules/jest-haste-map": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/cliui": { - "version": "8.0.1", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "engines": { - "node": ">=12" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-runtime/node_modules/jest-message-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-cli/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-cli/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-runtime/node_modules/jest-regex-util": { + "version": "29.6.3", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-cli/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", + "node_modules/jest-runtime/node_modules/jest-resolve": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-cli/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/jest-runtime/node_modules/jest-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -12833,202 +12695,159 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-cli/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/yargs": { - "version": "17.7.1", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "node_modules/jest-runtime/node_modules/jest-worker": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=12" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-cli/node_modules/yargs-parser": { - "version": "21.1.1", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "node_modules/jest-runtime/node_modules/resolve.exports": { + "version": "2.0.3", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/jest-config": { - "version": "29.4.3", - "integrity": "sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==", + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "8.1.1", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.4.3", - "@jest/types": "^29.4.3", - "babel-jest": "^29.4.3", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.4.3", - "jest-environment-node": "^29.4.3", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-runner": "^29.4.3", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" + "has-flag": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-config/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/jest-runtime/node_modules/write-file-atomic": { + "version": "4.0.2", "dev": true, + "license": "ISC", "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-config/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-serializer": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@types/node": "*", + "graceful-fs": "^4.2.9" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-config/node_modules/anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/jest-snapshot": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" }, "engines": { - "node": ">= 8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-snapshot/node_modules/@jest/transform": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-snapshot/node_modules/@jest/types": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-config/node_modules/fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-snapshot/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" } }, - "node_modules/jest-config/node_modules/jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", + "node_modules/jest-snapshot/node_modules/jest-haste-map": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -13039,39 +12858,39 @@ "fsevents": "^2.3.2" } }, - "node_modules/jest-config/node_modules/jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "node_modules/jest-snapshot/node_modules/jest-message-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/jest-resolve": { - "version": "29.4.3", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", + "node_modules/jest-snapshot/node_modules/jest-regex-util": { + "version": "29.6.3", "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/jest-snapshot/node_modules/jest-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -13082,13 +12901,13 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", + "node_modules/jest-snapshot/node_modules/jest-worker": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", - "jest-util": "^29.4.3", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -13096,157 +12915,160 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.1", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-config/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-config/node_modules/resolve.exports": { - "version": "2.0.0", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "8.1.1", "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-snapshot/node_modules/write-file-atomic": { + "version": "4.0.2", "dev": true, + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/jest-diff": { - "version": "29.4.3", - "integrity": "sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA==", + "node_modules/jest-util": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/jest-validate": { + "version": "29.7.0", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/jest-validate/node_modules/@jest/types": { + "version": "29.6.3", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/jest-validate/node_modules/@types/yargs": { + "version": "17.0.33", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@types/yargs-parser": "*" } }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-watcher": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-docblock": { - "version": "29.4.3", - "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "node_modules/jest-watcher/node_modules/@jest/console": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "detect-newline": "^3.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each": { - "version": "29.4.3", - "integrity": "sha512-1ElHNAnKcbJb/b+L+7j0/w7bDvljw4gTv1wL9fYOczeJrbTbkMGQ5iQPFJ3eFQH19VPTx1IyfePdqSpePKss7Q==", + "node_modules/jest-watcher/node_modules/@jest/test-result": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.4.3", - "pretty-format": "^29.4.3" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/jest-watcher/node_modules/@jest/types": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -13257,73 +13079,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "node_modules/jest-watcher/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-watcher/node_modules/jest-message-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/jest-watcher/node_modules/jest-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -13334,37 +13122,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-worker": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=8" + "node": ">= 10.13.0" } }, - "node_modules/jest-environment-node": { - "version": "29.4.3", - "integrity": "sha512-gAiEnSKF104fsGDXNkwk49jD/0N0Bqu2K9+aMQXA6avzsA9H3Fiv1PW2D+gzbOSR705bWd2wJZRFEFpV0tXISg==", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/environment": "^29.4.3", - "@jest/fake-timers": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/node": "*", - "jest-mock": "^29.4.3", - "jest-util": "^29.4.3" + "has-flag": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/jest/node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -13375,744 +13165,816 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-node/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "node_modules/jest/node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/jest-environment-node/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" } }, - "node_modules/jest-environment-node/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/js-message": { + "version": "1.0.7", + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=0.6.0" } }, - "node_modules/jest-environment-node/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/js-queue": { + "version": "2.0.2", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "easy-stack": "^1.0.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=1.0.0" } }, - "node_modules/jest-environment-node/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-environment-node/node_modules/has-flag": { + "node_modules/js-tokens": { "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/jest-environment-node/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/js-yaml": { + "version": "3.14.1", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jest-environment-node/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" + "node_modules/jsbi": { + "version": "3.2.5", + "license": "Apache-2.0" + }, + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "license": "0BSD" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/jest-get-type": { - "version": "29.4.3", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "node_modules/json-buffer": { + "version": "3.0.1", "dev": true, + "license": "MIT" + }, + "node_modules/json-cycle": { + "version": "1.5.0", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 4" } }, - "node_modules/jest-haste-map": { - "version": "27.5.1", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">= 0.4" }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-haste-map/node_modules/anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" }, "engines": { - "node": ">= 8" + "node": ">=6" } }, - "node_modules/jest-haste-map/node_modules/fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node_modules/jsonfile": { + "version": "4.0.0", + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/jest-haste-map/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", "dev": true, - "engines": { - "node": ">=0.10.0" + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-leak-detector": { - "version": "29.4.3", - "integrity": "sha512-9yw4VC1v2NspMMeV3daQ1yXPNxMgCzwq9BocCwYrRgXe4uaEJPAN0ZK37nFBhcy3cUwEVstFecFLaTHpF7NiGA==", + "node_modules/jsx-ast-utils": { + "version": "3.3.5", "dev": true, + "license": "MIT", "dependencies": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=4.0" } }, - "node_modules/jest-matcher-utils": { - "version": "29.4.3", - "integrity": "sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg==", + "node_modules/keyv": { + "version": "4.5.4", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "json-buffer": "3.0.1" } }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "graceful-fs": "^4.1.11" } }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/kleur": { + "version": "3.0.3", + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=6" } }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/kuler": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/launch-editor": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", + "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" } }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/leven": { + "version": "3.1.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/levn": { + "version": "0.4.1", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/jest-message-util": { - "version": "27.5.1", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dev": true, + "node_modules/light-bolt11-decoder": { + "version": "3.2.0", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "@scure/base": "1.1.1" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "node_modules/light-bolt11-decoder/node_modules/@scure/base": { + "version": "1.1.1", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "license": "Apache-2.0", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "debug": "^2.6.9", + "marky": "^1.2.2" } }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "ms": "2.0.0" } }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } + "node_modules/lines-and-columns": { + "version": "1.2.4", + "license": "MIT" }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "27.5.1", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, + "node_modules/localized-strings": { + "version": "0.2.4", + "resolved": "git+ssh://git@github.com/BlueWallet/localized-strings.git#178351a7297336618d9ee87277f8e3af9ab7285d", + "integrity": "sha512-cyATnmtgLe5i5qEuAZ052eGA46RHFvmwzR8ubOZZjkk8SyTow5XYjagrl2kUjbfzbTBIGG7A3ZfjGHfAtEbTvQ==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "p-locate": "^5.0.0" }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "17.0.2", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "license": "MIT" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "license": "MIT" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-mock": { - "version": "29.4.3", - "integrity": "sha512-LjFgMg+xed9BdkPMyIJh+r3KeHt1klXPJYBULXVVAkbTaaKjPX1o1uVCAZADMEp/kOxGTwy/Ot8XbvgItOrHEg==", + "node_modules/logform": { + "version": "2.7.0", + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "jest-util": "^29.4.3" + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 12.0.0" } }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/logkitty": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", + "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "ansi-fragments": "^0.2.1", + "dayjs": "^1.8.15", + "yargs": "^15.1.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "bin": { + "logkitty": "bin/logkitty.js" } }, - "node_modules/jest-mock/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "node_modules/logkitty/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", "dependencies": { - "@types/yargs-parser": "*" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, - "node_modules/jest-mock/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/logkitty/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/logkitty/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">=8" } }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/logkitty/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "p-try": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-mock/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/logkitty/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "p-limit": "^2.2.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "node_modules/jest-mock/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-mock/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/logkitty/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { "node": ">=8" } }, - "node_modules/jest-mock/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/logkitty/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-mock/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/logkitty/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/logkitty/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" }, "engines": { "node": ">=8" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, + "node_modules/logkitty/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, "engines": { "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lottie-react-native": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-7.3.8.tgz", + "integrity": "sha512-GAOl99TKi0c6xCcB1AJ+o70mgOPIAI/7K2G+Gs+o4po/qBhgvWijPNCKH8h6yNmlwFTg+RN3DmzRvHNFGxZMKQ==", + "license": "Apache-2.0", "peerDependencies": { - "jest-resolve": "*" + "@lottiefiles/dotlottie-react": "^0.13.5", + "react": "*", + "react-native": ">=0.46", + "react-native-windows": ">=0.63.x" }, "peerDependenciesMeta": { - "jest-resolve": { + "@lottiefiles/dotlottie-react": { + "optional": true + }, + "react-native-windows": { "optional": true } } }, - "node_modules/jest-regex-util": { - "version": "27.5.1", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node_modules/lru-cache": { + "version": "5.1.1", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" } }, - "node_modules/jest-resolve": { - "version": "27.5.1", - "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", + "node_modules/make-dir": { + "version": "4.0.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" + "semver": "^7.5.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-resolve-dependencies": { - "version": "29.4.3", - "integrity": "sha512-uvKMZAQ3nmXLH7O8WAOhS5l0iWyT3WmnJBdmIHiV5tBbdaDZ1wqtNX04FONGoaFvSOSHBJxnwAVnSn1WHdGVaw==", + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.1", "dev": true, - "dependencies": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.4.3" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/jest-resolve-dependencies/node_modules/jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "node_modules/make-error": { + "version": "1.3.6", "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "license": "ISC" }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/makeerror": { + "version": "1.0.12", + "license": "BSD-3-Clause", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "tmpl": "1.0.5" } }, - "node_modules/jest-resolve/node_modules/camelcase": { - "version": "6.3.0", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, + "node_modules/marky": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", + "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", + "license": "Apache-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.4" } }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/md5.js": { + "version": "1.3.5", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 0.8" } }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/memoize-one": { + "version": "5.2.1", + "license": "MIT" + }, + "node_modules/merge-options": { + "version": "3.0.4", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "is-plain-obj": "^2.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/merge-stream": { + "version": "2.0.0", + "license": "MIT" }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/merge2": { + "version": "1.4.1", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/jest-resolve/node_modules/jest-get-type": { - "version": "27.5.1", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } + "node_modules/merkle-lib": { + "version": "2.0.10", + "license": "MIT" }, - "node_modules/jest-resolve/node_modules/jest-validate": { - "version": "27.5.1", - "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", - "dev": true, + "node_modules/metro": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.84.3.tgz", + "integrity": "sha512-1h3lbVrE6hGf1e/764HfhPGg/bGrWMJDDh7G2rc4gFYZboVuI40BlG/y+UhtbhQDNlO/csMvrcnK0YrTlHUVew==", + "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", - "camelcase": "^6.2.0", + "@babel/code-frame": "^7.29.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "accepts": "^2.0.0", "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "leven": "^3.1.0", - "pretty-format": "^27.5.1" + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.35.0", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.84.3", + "metro-cache": "0.84.3", + "metro-cache-key": "0.84.3", + "metro-config": "0.84.3", + "metro-core": "0.84.3", + "metro-file-map": "0.84.3", + "metro-resolver": "0.84.3", + "metro-runtime": "0.84.3", + "metro-source-map": "0.84.3", + "metro-symbolicate": "0.84.3", + "metro-transform-plugins": "0.84.3", + "metro-transform-worker": "0.84.3", + "mime-types": "^3.0.1", + "nullthrows": "^1.1.1", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-resolve/node_modules/pretty-format": { - "version": "27.5.1", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, + "node_modules/metro-babel-transformer": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.3.tgz", + "integrity": "sha512-svAA+yMLpeMiGcz/jKJs4oHpIGEx4nBqNEJ5AGj4CYIg1efvK+A0TjR6tgIuc6tKO5e8JmN/1lglpN2+f3/z/w==", + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.35.0", + "metro-cache-key": "0.84.3", + "nullthrows": "^1.1.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-resolve/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "node_modules/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "license": "MIT" }, - "node_modules/jest-resolve/node_modules/react-is": { - "version": "17.0.2", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "node_modules/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.35.0" + } }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/metro-cache": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.3.tgz", + "integrity": "sha512-0QElxwLaHqLZf+Xqio8QrjVbuXP/8sJfQBGSPiITlKDVXrVLefuzYVSH9Sj+QL6lrPj2gYZd/iwQh1yZuVKnLA==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "https-proxy-agent": "^7.0.5", + "metro-core": "0.84.3" }, "engines": { - "node": ">=8" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-runner": { - "version": "29.4.3", - "integrity": "sha512-GWPTEiGmtHZv1KKeWlTX9SIFuK19uLXlRQU43ceOQ2hIfA5yPEJC7AMkvFKpdCHx6pNEdOD+2+8zbniEi3v3gA==", - "dev": true, + "node_modules/metro-cache-key": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.3.tgz", + "integrity": "sha512-TnSL1Fdvrw+2glTdBSRmA5TL8l/i16ECjsrUdf3E5HncA+sNx8KcwDG8r+3ct1UhfYcusJypzZqTN55FZZcwGg==", + "license": "MIT", "dependencies": { - "@jest/console": "^29.4.3", - "@jest/environment": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.4.3", - "jest-haste-map": "^29.4.3", - "jest-leak-detector": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-runtime": "^29.4.3", - "jest-util": "^29.4.3", - "jest-watcher": "^29.4.3", - "jest-worker": "^29.4.3", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", - "dev": true, + "node_modules/metro-config": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.84.3.tgz", + "integrity": "sha512-JmCzZWOETR+O22q8oPBWyQppx3roU9EbkbGzD8Gf1jukQ4b5T1fTzqqHruu6K4sTiNq5zVQySmKF6bp4kVARew==", + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0" + "connect": "^3.6.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.84.3", + "metro-cache": "0.84.3", + "metro-core": "0.84.3", + "metro-runtime": "0.84.3", + "yaml": "^2.6.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", - "dev": true, + "node_modules/metro-core": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.84.3.tgz", + "integrity": "sha512-cc0pvAa80ai1nDmqqz0P59a+0ZqCZ/YHU/3jEekZL6spFnYDfX8iDLdn9FR6kX+67rmzKxHNrbrSRFLX2AYocw==", + "license": "MIT", "dependencies": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.84.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "dev": true, + "node_modules/metro-file-map": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.3.tgz", + "integrity": "sha512-1cL4m4Jv1yRUt9RJExZQLfccscdlMNOcRG6LHLtmJhf3BG9j3MujPVc7CIpKYdFl+KUl+sdjge6oO3+meKCHQA==", + "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", + "debug": "^4.4.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "nullthrows": "^1.1.1", + "walker": "^1.0.7" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, + "node_modules/metro-file-map/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -14123,2232 +13985,2071 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, + "node_modules/metro-file-map/node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/metro-file-map/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, + "node_modules/metro-file-map/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">= 8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/metro-file-map/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "has-flag": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/metro-minify-terser": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.3.tgz", + "integrity": "sha512-3ofrG2OQyJbO9RNhCfOcl8QU7EE2WrSsnN5dFkuZaJO5+4Imujr9bUXmspeNlXRsOVk0F/rVRbEFH98lFSCkBQ==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" }, "engines": { - "node": ">=7.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runner/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/jest-runner/node_modules/fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "node_modules/metro-resolver": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.3.tgz", + "integrity": "sha512-pjEzGDtoM8DTHAIPK/9u9ZxszEiuRohYUVImWvgbnB91V4gqYJpQcoEYUugf2NIm1lrX5HNu0OvNqWmPBnGYjA==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/metro-runtime": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.3.tgz", + "integrity": "sha512-o7HLRfMyVk9N2dUZ9VjQfB6xxUItL9Pi9WcqxURE7MEKOH6wbGt9/E92YdYLluTOtkzYAEVfdC6h6lcxqA+hMQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, "engines": { - "node": ">=8" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-runner/node_modules/jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", - "dev": true, + "node_modules/metro-source-map": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.3.tgz", + "integrity": "sha512-jS48CeSzw78M8y6VE0f9uy3lVmfbOS677j2VCxnlmlYmnahcXuC6IhoN9K6LynNvos9517yUadcfgioju38xYQ==", + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.84.3", + "nullthrows": "^1.1.1", + "ob1": "0.84.3", + "source-map": "^0.5.6", + "vlq": "^1.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-runner/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "node_modules/metro-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-symbolicate": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.3.tgz", + "integrity": "sha512-J9Tpo8NCycYrozRvBIUyOwGAu4xkawOsAppmTscFiaegK0WvuDGwIM53GbzVSnytCHjVAF0io5GQxpkrKTuc7g==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.84.3", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-runner/node_modules/jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true, + "node_modules/metro-symbolicate/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-runner/node_modules/jest-resolve": { - "version": "29.4.3", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", - "dev": true, + "node_modules/metro-transform-plugins": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.3.tgz", + "integrity": "sha512-8S3baq2XhBaafHEH5Q8sJW6tmzsEJk80qKc3RU/nZV1MsnYq94RdjTUR6AyKjQd6Rfsk1BtBxhtiNnk7mgslCg==", + "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-runner/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, + "node_modules/metro-transform-worker": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.3.tgz", + "integrity": "sha512-Wjba7PyYktNRsHbPmkx2J2UX32rAzcDXjCu49zPHeF/viJlYJhwRaNePQcHaCRqQ+kmgQT4ThprsnJfDj71ZMA==", + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "metro": "0.84.3", + "metro-babel-transformer": "0.84.3", + "metro-cache": "0.84.3", + "metro-cache-key": "0.84.3", + "metro-minify-terser": "0.84.3", + "metro-source-map": "0.84.3", + "metro-transform-plugins": "0.84.3", + "nullthrows": "^1.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-runner/node_modules/jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, + "node_modules/metro/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "node_modules/metro/node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "@types/yargs-parser": "*" } }, - "node_modules/jest-runner/node_modules/resolve.exports": { + "node_modules/metro/node_modules/accepts": { "version": "2.0.0", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/jest-runner/node_modules/write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, + "node_modules/metro/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "license": "MIT" + }, + "node_modules/metro/node_modules/hermes-estree": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "license": "MIT" + }, + "node_modules/metro/node_modules/hermes-parser": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "hermes-estree": "0.35.0" } }, - "node_modules/jest-runtime": { - "version": "29.4.3", - "integrity": "sha512-F5bHvxSH+LvLV24vVB3L8K467dt3y3dio6V3W89dUz9nzvTpqd/HcT9zfYKL2aZPvD63vQFgLvaUX/UpUhrP6Q==", - "dev": true, + "node_modules/metro/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", "dependencies": { - "@jest/environment": "^29.4.3", - "@jest/fake-timers": "^29.4.3", - "@jest/globals": "^29.4.3", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-mock": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-snapshot": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" + "picomatch": "^2.2.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", - "dev": true, + "node_modules/metro/node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/metro/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0" + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", - "dev": true, + "node_modules/metro/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", "dependencies": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "mime-db": "^1.54.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/jest-runtime/node_modules/@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, + "node_modules/metro/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.6" } }, - "node_modules/jest-runtime/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, + "node_modules/metro/node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-runtime/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "node_modules/metro/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/metro/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-runtime/node_modules/anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, + "node_modules/metro/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/micro-packed": { + "version": "0.8.0", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@scure/base": "2.0.0" }, "engines": { - "node": ">=10" + "node": ">= 20.19.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/micromatch": { + "version": "4.0.8", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=8.6" } }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runtime/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/jest-runtime/node_modules/fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", - "dev": true, + "node_modules/miller-rabin": { + "version": "4.0.1", + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "bn.js": "^4.0.0", + "brorand": "^1.0.1" }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "bin": { + "miller-rabin": "bin/miller-rabin" } }, - "node_modules/jest-runtime/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "license": "MIT", + "bin": { + "mime": "cli.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=4.0.0" } }, - "node_modules/jest-runtime/node_modules/jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.6" } }, - "node_modules/jest-runtime/node_modules/jest-resolve": { - "version": "29.4.3", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", - "dev": true, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" + "mime-db": "1.52.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.6" } }, - "node_modules/jest-runtime/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.6" } }, - "node_modules/jest-runtime/node_modules/jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" } }, - "node_modules/jest-runtime/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "node_modules/mimic-response": { + "version": "3.1.0", + "license": "MIT", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-runtime/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/min-indent": { + "version": "1.0.1", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/jest-runtime/node_modules/resolve.exports": { - "version": "2.0.0", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", - "dev": true, - "engines": { - "node": ">=10" - } + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "license": "ISC" }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "9.0.5", "dev": true, + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-runtime/node_modules/write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node_modules/minimist": { + "version": "1.2.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-serializer": { - "version": "27.5.1", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", + "node_modules/mkdirp": { + "version": "0.5.6", + "license": "MIT", + "optional": true, "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.9" + "minimist": "^1.2.6" }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "bin": { + "mkdirp": "bin/cmd.js" } }, - "node_modules/jest-snapshot": { - "version": "29.4.3", - "integrity": "sha512-NGlsqL0jLPDW91dz304QTM/SNO99lpcSYYAjNiX0Ou+sSGgkanKBcSjCfp/pqmiiO1nQaOyLp6XQddAzRcx3Xw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.4.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.4.3", - "jest-get-type": "^29.4.3", - "jest-haste-map": "^29.4.3", - "jest-matcher-utils": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "natural-compare": "^1.4.0", - "pretty-format": "^29.4.3", - "semver": "^7.3.5" - }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "license": "MIT" + }, + "node_modules/moment": { + "version": "2.30.1", + "license": "MIT", + "optional": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "*" } }, - "node_modules/jest-snapshot/node_modules/@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "dev": true, + "node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/multi-sort-stream": { + "version": "1.0.4", + "license": "bsd" + }, + "node_modules/multipipe": { + "version": "4.0.0", + "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "duplexer2": "^0.1.2", + "object-assign": "^4.1.0" } }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, + "node_modules/mv": { + "version": "2.1.1", + "license": "MIT", + "optional": true, "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.8.0" } }, - "node_modules/jest-snapshot/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, + "node_modules/mv/node_modules/brace-expansion": { + "version": "1.1.11", + "license": "MIT", + "optional": true, "dependencies": { - "@types/yargs-parser": "*" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/mv/node_modules/glob": { + "version": "6.0.4", + "license": "ISC", + "optional": true, "dependencies": { - "color-convert": "^2.0.1" + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "*" } }, - "node_modules/jest-snapshot/node_modules/anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, + "node_modules/mv/node_modules/minimatch": { + "version": "3.1.2", + "license": "ISC", + "optional": true, "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 8" + "node": "*" } }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/mv/node_modules/rimraf": { + "version": "2.4.5", + "license": "ISC", + "optional": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "glob": "^6.0.1" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "bin": { + "rimraf": "bin.js" } }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" + "node_modules/nan": { + "version": "2.22.2", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=7.0.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/convert-source-map": { + "node_modules/napi-build-utils": { "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "license": "MIT" }, - "node_modules/jest-snapshot/node_modules/fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "node_modules/natural-compare": { + "version": "1.4.0", "dev": true, - "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/ncp": { + "version": "2.0.0", + "license": "MIT", "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "bin": { + "ncp": "bin/ncp" } }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/jest-snapshot/node_modules/jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.4.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, + "node_modules/nocache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", + "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": ">=12.0.0" } }, - "node_modules/jest-snapshot/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, + "node_modules/node-abi": { + "version": "3.74.0", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "semver": "^7.3.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/jest-snapshot/node_modules/jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/jest-snapshot/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/node-addon-api": { + "version": "5.1.0", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "whatwg-url": "^5.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/jest-snapshot/node_modules/jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/node-ipc": { + "version": "9.2.1", + "license": "MIT", "dependencies": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "event-pubsub": "4.3.0", + "js-message": "1.0.7", + "js-queue": "2.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/jest-snapshot/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "node_modules/node-machine-id": { + "version": "1.1.12", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "license": "MIT" + }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=0.12.0" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/antelle" } }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "license": "BSD-2-Clause", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "license": "ISC", + "bin": { + "semver": "bin/semver" } }, - "node_modules/jest-snapshot/node_modules/normalize-path": { + "node_modules/normalize-path": { "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.8", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, + "node_modules/npm-run-path": { + "version": "4.0.1", + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "path-key": "^3.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/nth-check": { + "version": "2.1.1", + "license": "BSD-2-Clause", "dependencies": { - "has-flag": "^4.0.0" + "boolbase": "^1.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/jest-snapshot/node_modules/write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, + "node_modules/nullthrows": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/ob1": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.3.tgz", + "integrity": "sha512-J7554Ef8bzmKaDY365Afq6PF+qtdnY/d5PKUQFrsKlZHV/N3OGZewVrvDrQDyX5V5NJjTpcAKtlrFZcDr+HvpQ==", + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/jest-util": { - "version": "27.5.1", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, + "node_modules/object-inspect": { + "version": "1.13.4", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/object-is": { + "version": "1.1.6", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/object-keys": { + "version": "1.1.1", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/object.assign": { + "version": "4.1.7", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" }, "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "29.4.3", - "integrity": "sha512-J3u5v7aPQoXPzaar6GndAVhdQcZr/3osWSgTeKg5v574I9ybX/dTyH0AJFb5XgXIB7faVhf+rS7t4p3lL9qFaw==", - "dev": true, - "dependencies": { - "@jest/types": "^29.4.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "leven": "^3.1.0", - "pretty-format": "^29.4.3" + "node": ">= 0.4" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/object.entries": { + "version": "1.1.9", "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "node": ">= 0.4" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/object.fromentries": { + "version": "2.0.8", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/object.groupby": { + "version": "1.0.3", "dev": true, - "engines": { - "node": ">=10" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.4" } }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/object.values": { + "version": "1.2.1", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "ee-first": "1.1.1" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.8" } }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/once": { + "version": "1.4.0", + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "wrappy": "1" } }, - "node_modules/jest-watcher": { - "version": "29.4.3", - "integrity": "sha512-zwlXH3DN3iksoIZNk73etl1HzKyi5FuQdYLnkQKm5BW4n8HpoG59xSwpVdFrnh60iRRaRBGw0gcymIxjJENPcA==", - "dev": true, + "node_modules/one-time": { + "version": "1.0.0", + "license": "MIT", "dependencies": { - "@jest/test-result": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.4.3", - "string-length": "^4.0.1" + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-watcher/node_modules/@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", - "dev": true, + "node_modules/open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0" + "is-wsl": "^1.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-watcher/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", + "node_modules/optionator": { + "version": "0.9.4", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.8.0" } }, - "node_modules/jest-watcher/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-watcher/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/own-keys": { + "version": "1.0.1", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/p-limit": { + "version": "3.1.0", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "yocto-queue": "^0.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/p-locate": { + "version": "5.0.0", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=7.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/p-try": { + "version": "2.2.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/jest-watcher/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, + "node_modules/pako": { + "resolved": "blue_modules/pako", + "link": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "callsites": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" } }, - "node_modules/jest-watcher/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, + "node_modules/parse-asn1": { + "version": "5.1.7", + "license": "ISC", "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.10" } }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/parse-asn1/node_modules/hash-base": { + "version": "3.0.5", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" }, "engines": { - "node": ">=8" + "node": ">= 0.10" } }, - "node_modules/jest-worker": { - "version": "27.5.1", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "node_modules/parse-json": { + "version": "5.2.0", + "license": "MIT", "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, "engines": { - "node": ">= 10.13.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/patch-package": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", + "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^10.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.2.4", + "yaml": "^2.2.2" }, - "engines": { - "node": ">=10" + "bin": { + "patch-package": "index.js" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "engines": { + "node": ">=14", + "npm": ">5" } }, - "node_modules/jest/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/patch-package/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/jest/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "node_modules/patch-package/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, + "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/jest/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/patch-package/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "universalify": "^2.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/jest/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/patch-package/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/patch-package/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "node_modules/jest/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/jest/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/patch-package/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 10.0.0" } }, - "node_modules/joi": { - "version": "17.8.3", - "integrity": "sha512-q5Fn6Tj/jR8PfrLrx4fpGH4v9qM6o+vDUfD4/3vxxyg34OmKcNqYZ1qn2mpLza96S8tL0p0rIw2gOZX+/cTg9w==", - "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } + "node_modules/path-browserify": { + "version": "1.0.1", + "license": "MIT" }, - "node_modules/js-message": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", - "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==", + "node_modules/path-exists": { + "version": "4.0.0", + "license": "MIT", "engines": { - "node": ">=0.6.0" + "node": ">=8" } }, - "node_modules/js-queue": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.2.tgz", - "integrity": "sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==", - "dependencies": { - "easy-stack": "^1.0.1" - }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", "engines": { - "node": ">=1.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "node": ">=14.0.0" } }, - "node_modules/jsbi": { - "version": "3.2.5", - "integrity": "sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ==" - }, - "node_modules/jsc-android": { - "version": "250231.0.0", - "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==" - }, - "node_modules/jsc-safe-url": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", - "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==" - }, - "node_modules/jscodeshift": { - "version": "0.13.1", - "integrity": "sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ==", - "dependencies": { - "@babel/core": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/preset-flow": "^7.13.13", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.13.16", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^3.1.10", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.20.4", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "bin": { - "jscodeshift": "bin/jscodeshift.js" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" + "node_modules/path-is-absolute": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/jscodeshift/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, + "node_modules/path-key": { + "version": "3.1.1", + "license": "MIT", "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jscodeshift/node_modules/arr-diff": { + "node_modules/path-parse": { + "version": "1.0.7", + "license": "MIT" + }, + "node_modules/path-type": { "version": "4.0.0", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/jscodeshift/node_modules/array-unique": { - "version": "0.3.2", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "engines": { - "node": ">=0.10.0" + "node_modules/payjoin-client": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "bitcoinjs-lib": "^5.2.0" } }, - "node_modules/jscodeshift/node_modules/babel-core": { - "version": "7.0.0-bridge.0", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "node_modules/payjoin-client/node_modules/@types/node": { + "version": "10.12.18", + "license": "MIT" + }, + "node_modules/payjoin-client/node_modules/bech32": { + "version": "1.1.4", + "license": "MIT" }, - "node_modules/jscodeshift/node_modules/braces": { - "version": "2.3.2", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "node_modules/payjoin-client/node_modules/bip32": { + "version": "2.0.6", + "license": "MIT", "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "@types/node": "10.12.18", + "bs58check": "^2.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "tiny-secp256k1": "^1.1.3", + "typeforce": "^1.11.5", + "wif": "^2.0.6" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0.0" } }, - "node_modules/jscodeshift/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "node_modules/payjoin-client/node_modules/bitcoinjs-lib": { + "version": "5.2.0", + "license": "MIT", "dependencies": { - "is-extendable": "^0.1.0" + "bech32": "^1.1.2", + "bip174": "^2.0.1", + "bip32": "^2.0.4", + "bip66": "^1.1.0", + "bitcoin-ops": "^1.4.0", + "bs58check": "^2.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.3", + "merkle-lib": "^2.0.10", + "pushdata-bitcoin": "^1.0.1", + "randombytes": "^2.0.1", + "tiny-secp256k1": "^1.1.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.0.4", + "wif": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.0.0" } }, - "node_modules/jscodeshift/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/pbkdf2": { + "version": "3.1.3", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "create-hash": "~1.1.3", + "create-hmac": "^1.1.7", + "ripemd160": "=2.0.1", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.11", + "to-buffer": "^1.2.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=0.12" } }, - "node_modules/jscodeshift/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/pbkdf2/node_modules/create-hash": { + "version": "1.1.3", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "sha.js": "^2.4.0" } }, - "node_modules/jscodeshift/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jscodeshift/node_modules/debug": { - "version": "2.6.9", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/pbkdf2/node_modules/hash-base": { + "version": "2.0.2", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "inherits": "^2.0.1" } }, - "node_modules/jscodeshift/node_modules/expand-brackets": { - "version": "2.1.4", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "node_modules/pbkdf2/node_modules/ripemd160": { + "version": "2.0.1", + "license": "MIT", "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" + "hash-base": "^2.0.0", + "inherits": "^2.0.1" } }, - "node_modules/jscodeshift/node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dependencies": { - "is-descriptor": "^0.1.0" - }, + "node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/jscodeshift/node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dependencies": { - "is-extendable": "^0.1.0" - }, + "node_modules/pirates": { + "version": "4.0.7", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 6" } }, - "node_modules/jscodeshift/node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "node_modules/pkg-dir": { + "version": "4.2.0", + "dev": true, + "license": "MIT", "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "find-up": "^4.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/expand-brackets/node_modules/kind-of": { - "version": "5.1.0", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/jscodeshift/node_modules/extglob": { - "version": "2.0.4", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/jscodeshift/node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "is-descriptor": "^1.0.0" + "p-locate": "^4.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/jscodeshift/node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", "dependencies": { - "is-extendable": "^0.1.0" + "p-try": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jscodeshift/node_modules/fill-range": { - "version": "4.0.0", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "p-limit": "^2.2.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/jscodeshift/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "node_modules/plist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", + "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", + "license": "MIT", "dependencies": { - "is-extendable": "^0.1.0" + "@xmldom/xmldom": "^0.9.10", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.4.0" } }, - "node_modules/jscodeshift/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/jscodeshift/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "node_modules/prebuild-install": { + "version": "7.1.3", + "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" + "bin": { + "prebuild-install": "bin.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/jscodeshift/node_modules/is-data-descriptor": { - "version": "0.1.4", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dependencies": { - "kind-of": "^3.0.2" - }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8.0" } }, - "node_modules/jscodeshift/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" + "node_modules/prettier": { + "version": "3.5.3", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/jscodeshift/node_modules/is-number": { - "version": "3.0.0", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" + "fast-diff": "^1.1.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0.0" } }, - "node_modules/jscodeshift/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/pretty-format": { + "version": "29.7.0", + "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jscodeshift/node_modules/micromatch": { - "version": "3.1.10", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jscodeshift/node_modules/ms": { - "version": "2.0.0", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "node_modules/process-nextick-args": { + "version": "2.0.1", + "license": "MIT" }, - "node_modules/jscodeshift/node_modules/rimraf": { - "version": "2.6.3", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "node_modules/promise": { + "version": "8.3.0", + "license": "MIT", "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "asap": "~2.0.6" } }, - "node_modules/jscodeshift/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, + "node_modules/promisify-child-process": { + "version": "4.1.2", + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/jscodeshift/node_modules/temp": { - "version": "0.8.4", - "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "node_modules/prompts": { + "version": "2.4.2", + "license": "MIT", "dependencies": { - "rimraf": "~2.6.2" + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" }, "engines": { - "node": ">=6.0.0" + "node": ">= 6" } }, - "node_modules/jscodeshift/node_modules/to-regex-range": { - "version": "2.1.1", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "node_modules/prop-types": { + "version": "15.8.1", + "license": "MIT", "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" } }, - "node_modules/jscodeshift/node_modules/write-file-atomic": { - "version": "2.4.3", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "license": "MIT" + }, + "node_modules/proper-lockfile": { + "version": "3.2.0", + "license": "MIT", "dependencies": { "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", + "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, - "node_modules/jsesc": { - "version": "2.5.2", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" + "node_modules/public-encrypt": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" } }, - "node_modules/json-cycle": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/json-cycle/-/json-cycle-1.3.0.tgz", - "integrity": "sha512-FD/SedD78LCdSvJaOUQAXseT8oQBb5z6IVYaQaCrVUlu9zOAr1BDdKyVYQaSD/GDsAMrXpKcOyBD4LIl8nfjHw==", - "engines": { - "node": ">= 4" + "node_modules/pump": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "node_modules/punycode": { + "version": "1.4.1", + "license": "MIT" }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "node_modules/pure-rand": { + "version": "6.1.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" }, - "node_modules/json-stable-stringify-without-jsonify": { + "node_modules/pushdata-bitcoin": { "version": "1.0.1", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" + "license": "MIT", + "dependencies": { + "bitcoin-ops": "^1.3.0" } }, - "node_modules/jsonfile": { - "version": "4.0.0", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "node_modules/qr": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/qr/-/qr-0.5.5.tgz", + "integrity": "sha512-iQBvKj7MRKO+co+MY0IZpyLO+ezvttxsmV86WywrgPuAmgBkv0pytyi03wourniSoPgzffeBW6cBgIkpqcvjTg==", + "license": "(MIT OR Apache-2.0)", + "engines": { + "node": ">= 20.19.0" } }, - "node_modules/jsx-ast-utils": { - "version": "3.3.3", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", - "dev": true, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", "dependencies": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" + "side-channel": "^1.1.0" }, "engines": { - "node": ">=4.0" + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/junderw-crc32c": { - "version": "1.2.0", - "integrity": "sha512-tP0w5QOrunUS/XgsDBoZfw2jKNFhnUrdM96IXzuJtCyuXd19Hj47Hfd5+WFd81QDQFosiPffpc/jnSrZ35IV+A==", + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", "dependencies": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - }, - "bin": { - "crc32": "bin/crc32.njs" + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" + "inherits": "~2.0.3" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "node_modules/localized-strings": { - "version": "0.2.4", - "resolved": "git+ssh://git@github.com/BlueWallet/localized-strings.git#178351a7297336618d9ee87277f8e3af9ab7285d", - "integrity": "sha512-E6ncf+f2l/1Rv2C5DMxkfSbK0wjYxyD6g3dcUqM5TB/tGv0QZlJ96MFTjhmIshMa2bM6P06hjj6V8QjC/Zqhlg==", + "node_modules/queue-microtask": { + "version": "1.2.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT" }, - "node_modules/locate-path": { - "version": "5.0.0", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/randombytes": { + "version": "2.1.0", + "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" + "safe-buffer": "^5.1.0" } }, - "node_modules/lodash": { - "version": "4.17.21", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "node_modules/randomfill": { + "version": "1.0.4", + "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 0.10" } }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/rc": { + "version": "1.2.8", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "bin": { + "rc": "cli.js" } }, - "node_modules/log-symbols/node_modules/color-convert": { + "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, + "license": "MIT", "engines": { - "node": ">=7.0.0" + "node": ">=0.10.0" } }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/react-devtools-core": { + "version": "6.1.5", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "shell-quote": "^1.6.1", + "ws": "^7" } }, - "node_modules/logkitty": { - "version": "0.7.1", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "dependencies": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" + "node_modules/react-devtools-core/node_modules/ws": { + "version": "7.5.10", + "license": "MIT", + "engines": { + "node": ">=8.3.0" }, - "bin": { - "logkitty": "bin/logkitty.js" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/logkitty/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, + "node_modules/react-freeze": { + "version": "1.0.4", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "react": ">=17.0.0" } }, - "node_modules/logkitty/node_modules/cliui": { - "version": "6.0.0", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } + "node_modules/react-is": { + "version": "18.3.1", + "license": "MIT" }, - "node_modules/logkitty/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/react-localization": { + "version": "1.0.19", + "resolved": "git+ssh://git@github.com/BlueWallet/react-localization.git#ae7969a8998128aebf1169f931fb22587dc5f874", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "localized-strings": "github:BlueWallet/localized-strings#178351a7297336618d9ee87277f8e3af9ab7285d" + }, + "peerDependencies": { + "react": "^18.0.0 || ^17.0.0 || ^16.0.0 || ^15.6.0" + } + }, + "node_modules/react-native": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.85.3.tgz", + "integrity": "sha512-HN/fGC+3nZVcDNcw7gfbM/DuqZAvI9Mz+/SxuhODaua4JY0BPzhfTzWXRyTR4mRgMHmShTPpH2PYMTxvZrsdZA==", + "license": "MIT", + "dependencies": { + "@react-native/assets-registry": "0.85.3", + "@react-native/codegen": "0.85.3", + "@react-native/community-cli-plugin": "0.85.3", + "@react-native/gradle-plugin": "0.85.3", + "@react-native/js-polyfills": "0.85.3", + "@react-native/normalize-colors": "0.85.3", + "@react-native/virtualized-lists": "0.85.3", + "abort-controller": "^3.0.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "babel-plugin-syntax-hermes-parser": "0.33.3", + "base64-js": "^1.5.1", + "commander": "^12.0.0", + "flow-enums-runtime": "^0.0.6", + "hermes-compiler": "250829098.0.10", + "invariant": "^2.2.4", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.84.3", + "metro-source-map": "^0.84.3", + "nullthrows": "^1.1.1", + "pretty-format": "^29.7.0", + "promise": "^8.3.0", + "react-devtools-core": "^6.1.5", + "react-refresh": "^0.14.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.27.0", + "semver": "^7.1.3", + "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.15", + "whatwg-fetch": "^3.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "react-native": "cli.js" }, "engines": { - "node": ">=7.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@react-native/jest-preset": "0.85.3", + "@types/react": "^19.1.1", + "react": "^19.2.3" + }, + "peerDependenciesMeta": { + "@react-native/jest-preset": { + "optional": true + }, + "@types/react": { + "optional": true + } } }, - "node_modules/logkitty/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "node_modules/react-native-biometrics": { + "version": "3.0.1", + "license": "MIT", + "peerDependencies": { + "react-native": ">=0.60.0" + } }, - "node_modules/logkitty/node_modules/decamelize": { - "version": "1.2.0", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "engines": { - "node": ">=0.10.0" + "node_modules/react-native-blue-crypto": { + "version": "1.0.0", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-blue-crypto.git#3cb5442425bd835e185284fbc62e84b7155bc441", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/logkitty/node_modules/wrap-ansi": { - "version": "6.2.0", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "node_modules/react-native-camera-kit-no-google": { + "version": "17.0.4", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-camera-kit-no-google.git#0ed049a62da29cf304019363ec9d9ef3a73652e6", + "integrity": "sha512-w/8o4w5QGxTqrlUVjm/HunWCUi2BIh10xnUX/WB76YOhK1duJCF97lrGGQMGgkokFvjGQrjUTBDkYu6s4r0kSw==", + "license": "MIT", + "engines": { + "node": ">=18" }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-capture-protection": { + "version": "2.4.1", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-capture-protection.git#b17b9ecfe5582b338e371856f7a749d2e35731bc", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 16.0.0" + }, + "peerDependencies": { + "expo": ">=47.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } } }, - "node_modules/logkitty/node_modules/y18n": { - "version": "4.0.3", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + "node_modules/react-native-context-menu-view": { + "version": "1.21.0", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-context-menu-view.git#144110b02afdb11b431741aef5da95e91b942a9b", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-native": ">=0.60.0-rc.0 <1.0.x" + } }, - "node_modules/logkitty/node_modules/yargs": { - "version": "15.4.1", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" + "node_modules/react-native-default-preference": { + "version": "1.5.1", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-default-preference.git#6338a1f1235e4130b8cfc2dd3b53015eeff2870c", + "integrity": "sha512-sUZHfSskPQUZ5E+9Xu1pNIoEk76ZZTS48wpEqDy0tZM7skzk0CooWIwY/eiuZYZqJOtAB83pDLXkQzu8msQ8gw==", + "license": "MIT", + "peerDependencies": { + "react-native": ">=0.47.0" } }, - "node_modules/logkitty/node_modules/yargs-parser": { - "version": "18.1.3", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "node_modules/react-native-device-info": { + "version": "14.1.1", + "license": "MIT", + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/react-native-draggable-flatlist": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/react-native-draggable-flatlist/-/react-native-draggable-flatlist-4.0.3.tgz", + "integrity": "sha512-2F4x5BFieWdGq9SetD2nSAR7s7oQCSgNllYgERRXXtNfSOuAGAVbDb/3H3lP0y5f7rEyNwabKorZAD/SyyNbDw==", + "license": "MIT", "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "@babel/preset-typescript": "^7.17.12" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "react-native": ">=0.64.0", + "react-native-gesture-handler": ">=2.0.0", + "react-native-reanimated": ">=2.8.0" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "node_modules/react-native-drawer-layout": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-4.2.4.tgz", + "integrity": "sha512-l1Le5HcVidobnJm8xqFZo46Rs8FDHdxbTZhkjxpNSRgU+QMoQXilOfzTHAeNjEGiKVGgIs9cW3ctXeHqgp5jJg==", + "license": "MIT", "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "color": "^4.2.3", + "use-latest-callback": "^0.2.4" }, - "bin": { - "loose-envify": "cli.js" + "peerDependencies": { + "react": ">= 18.2.0", + "react-native": "*", + "react-native-gesture-handler": ">= 2.0.0", + "react-native-reanimated": ">= 2.0.0" } }, - "node_modules/lottie-ios": { - "version": "3.4.4", - "integrity": "sha512-ikj8VNuClItRDZ8C9MfRMDxN0U/UIX3HRF2aei3H44F9Hkhwv0EIVRkBwG+SwS/WSoGmBzkcVG8O3BjPk5hW7Q==" + "node_modules/react-native-edge-to-edge": { + "version": "1.8.1", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } }, - "node_modules/lottie-react-native": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-5.1.6.tgz", - "integrity": "sha512-vhdeZstXMfuVKwnddYWjJgQ/1whGL58IJEJu/iSf0XQ5gAb4pp/+vy91mdYQLezlb8Aw4Vu3fKnqErJL2hwchg==", + "node_modules/react-native-fs": { + "version": "2.20.0", + "license": "MIT", "dependencies": { - "invariant": "^2.2.2", - "react-native-safe-modules": "^1.0.3" + "base-64": "^0.1.0", + "utf8": "^3.0.0" }, "peerDependencies": { - "lottie-ios": "^3.4.0", - "react": "*", - "react-native": ">=0.46", - "react-native-windows": ">=0.63.x" + "react-native": "*", + "react-native-windows": "*" }, "peerDependenciesMeta": { "react-native-windows": { @@ -16356,21564 +16057,2641 @@ } } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "node_modules/react-native-gesture-handler": { + "version": "2.31.2", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.31.2.tgz", + "integrity": "sha512-rw5q74i2AfS7YGYdbxQDhOU7xqgY6WRM1132/CCm3erqjblhECZDZFHIm0tteHoC9ih24wogVBVVzcTBQtZ+5A==", + "license": "MIT", "dependencies": { - "yallist": "^3.0.2" + "@egjs/hammerjs": "^2.0.17", + "@types/react-test-renderer": "^19.1.0", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, + "node_modules/react-native-get-random-values": { + "version": "1.11.0", + "license": "MIT", "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" + "fast-base64-decode": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "react-native": ">=0.56" } }, - "node_modules/make-error": { - "version": "1.3.6", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dependencies": { - "tmpl": "1.0.5" + "node_modules/react-native-handoff": { + "version": "0.0.3", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-handoff.git#31d005f93d31099d0e564590a3bbd052b8a02b39", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/map-cache": { - "version": "0.2.2", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "engines": { - "node": ">=0.10.0" + "node_modules/react-native-haptic-feedback": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/react-native-haptic-feedback/-/react-native-haptic-feedback-2.3.4.tgz", + "integrity": "sha512-3nHECsi+pL1c2TKNpOD5aPtAfFzrkjpABjioi6OUBb6OVeuUObQlx9idoXSbj1h/L7TCaC13LqKT5ILl0xxHjQ==", + "license": "MIT", + "workspaces": [ + "example" + ], + "peerDependencies": { + "react-native": ">=0.60.0" } }, - "node_modules/map-visit": { - "version": "1.0.0", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" + "node_modules/react-native-image-picker": { + "version": "8.2.1", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/md5.js": { - "version": "1.3.5", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "node_modules/react-native-is-edge-to-edge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz", + "integrity": "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/mdn-data": { - "version": "2.0.14", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "node_modules/memoize-one": { - "version": "5.2.1", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, - "node_modules/merge-options": { - "version": "3.0.4", - "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", - "dependencies": { - "is-plain-obj": "^2.1.0" - }, + "node_modules/react-native-keychain": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-10.0.0.tgz", + "integrity": "sha512-YzPKSAnSzGEJ12IK6CctNLU79T1W15WDrElRQ+1/FsOazGX9ucFPTQwgYe8Dy8jiSEDJKM4wkVa3g4lD2Z+Pnw==", + "license": "MIT", + "workspaces": [ + "KeychainExample", + "website" + ], "engines": { - "node": ">=10" + "node": ">=16" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "node_modules/merge2": { - "version": "1.4.1", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" + "node_modules/react-native-linear-gradient": { + "version": "2.8.3", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/merkle-lib": { - "version": "2.0.10", - "integrity": "sha512-XrNQvUbn1DL5hKNe46Ccs+Tu3/PYOlrcZILuGUhb95oKBPjc/nmIC8D462PQkipVDGKRvwhn+QFg2cCdIvmDJA==" - }, - "node_modules/metro": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.73.10.tgz", - "integrity": "sha512-J2gBhNHFtc/Z48ysF0B/bfTwUwaRDLjNv7egfhQCc+934dpXcjJG2KZFeuybF+CvA9vo4QUi56G2U+RSAJ5tsA==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", - "absolute-path": "^0.0.0", - "accepts": "^1.3.7", - "async": "^3.2.2", - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "denodeify": "^1.2.1", - "error-stack-parser": "^2.0.6", - "graceful-fs": "^4.2.4", - "hermes-parser": "0.8.0", - "image-size": "^0.6.0", - "invariant": "^2.2.4", - "jest-worker": "^27.2.0", - "jsc-safe-url": "^0.2.2", - "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.73.10", - "metro-cache": "0.73.10", - "metro-cache-key": "0.73.10", - "metro-config": "0.73.10", - "metro-core": "0.73.10", - "metro-file-map": "0.73.10", - "metro-hermes-compiler": "0.73.10", - "metro-inspector-proxy": "0.73.10", - "metro-minify-terser": "0.73.10", - "metro-minify-uglify": "0.73.10", - "metro-react-native-babel-preset": "0.73.10", - "metro-resolver": "0.73.10", - "metro-runtime": "0.73.10", - "metro-source-map": "0.73.10", - "metro-symbolicate": "0.73.10", - "metro-transform-plugins": "0.73.10", - "metro-transform-worker": "0.73.10", - "mime-types": "^2.1.27", - "node-fetch": "^2.2.0", - "nullthrows": "^1.1.1", - "rimraf": "^3.0.2", - "serialize-error": "^2.1.0", - "source-map": "^0.5.6", - "strip-ansi": "^6.0.0", - "temp": "0.8.3", - "throat": "^5.0.0", - "ws": "^7.5.1", - "yargs": "^17.5.1" + "node_modules/react-native-localize": { + "version": "3.7.0", + "license": "MIT", + "peerDependencies": { + "@expo/config-plugins": "*", + "react": "*", + "react-native": "*", + "react-native-macos": "*" }, - "bin": { - "metro": "src/cli.js" + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "react-native-macos": { + "optional": true + } } }, - "node_modules/metro-babel-transformer": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.73.10.tgz", - "integrity": "sha512-Yv2myTSnpzt/lTyurLvqYbBkytvUJcLHN8XD3t7W6rGiLTQPzmf1zypHQLphvcAXtCWBOXFtH7KLOSi2/qMg+A==", - "dependencies": { - "@babel/core": "^7.20.0", - "hermes-parser": "0.8.0", - "metro-source-map": "0.73.10", - "nullthrows": "^1.1.1" + "node_modules/react-native-notifications": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/react-native-notifications/-/react-native-notifications-5.2.2.tgz", + "integrity": "sha512-So4o7KfQfmNtpXDgzxPCRbuzVr3Mc6Hiz7IrdiMm0lxsMGgKgitQV4vO51DKIA5J//CzagJED6NBrTERSnZBFw==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/metro-cache": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.73.10.tgz", - "integrity": "sha512-wPGlQZpdVlM404m7MxJqJ+hTReDr5epvfPbt2LerUAHY9RN99w61FeeAe25BMZBwgUgDtAsfGlJ51MBHg8MAqw==", - "dependencies": { - "metro-core": "0.73.10", - "rimraf": "^3.0.2" + "node_modules/react-native-permissions": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-5.5.1.tgz", + "integrity": "sha512-nTKFoj47b6EXNqbbg+8VFwBWMpxF1/UTbrNBLpXkWpt005pH4BeFv/NwpcC1iNhToKBrxQD+5kI0z6+kTYoYWA==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-windows": "*" + }, + "peerDependenciesMeta": { + "react-native-windows": { + "optional": true + } } }, - "node_modules/metro-cache-key": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.73.10.tgz", - "integrity": "sha512-JMVDl/EREDiUW//cIcUzRjKSwE2AFxVWk47cFBer+KA4ohXIG2CQPEquT56hOw1Y1s6gKNxxs1OlAOEsubrFjw==" + "node_modules/react-native-prompt-android": { + "version": "1.0.0", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-prompt-android.git#ed168d66fed556bc2ed07cf498770f058b78a376", + "integrity": "sha512-vTyVQW/EjOYxKdXmq/MWE97MNuQzoqSzvlsIBFMQV9dVH3X9+zPfR7osPO6EM8ATASrcntMSepKyKd7W6DwHPA==", + "license": "MIT" }, - "node_modules/metro-config": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.73.10.tgz", - "integrity": "sha512-wIlybd1Z9I8K2KcStTiJxTB7OK529dxFgogNpKCTU/3DxkgAASqSkgXnZP6kVyqjh5EOWAKFe5U6IPic7kXDdQ==", + "node_modules/react-native-quick-actions": { + "version": "0.3.13", + "license": "MIT" + }, + "node_modules/react-native-reanimated": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.3.1.tgz", + "integrity": "sha512-KhGsS0YkCA+gusgyzlf9hnqzVPIR398KTpqXyqq/+yYJJPAvyEEPKcxlB0xtOOXSMrR2A9uRKVARVQhZwrOh+Q==", + "license": "MIT", "dependencies": { - "cosmiconfig": "^5.0.5", - "jest-validate": "^26.5.2", - "metro": "0.73.10", - "metro-cache": "0.73.10", - "metro-core": "0.73.10", - "metro-runtime": "0.73.10" + "react-native-is-edge-to-edge": "^1.3.1", + "semver": "^7.7.3" + }, + "peerDependencies": { + "react": "*", + "react-native": "0.81 - 0.85", + "react-native-worklets": "0.8.x" } }, - "node_modules/metro-config/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" + "node_modules/react-native-reanimated/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">= 10.14.2" + "node": ">=10" } }, - "node_modules/metro-config/node_modules/@types/yargs": { - "version": "15.0.15", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz", - "integrity": "sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg==", - "dependencies": { - "@types/yargs-parser": "*" + "node_modules/react-native-safe-area-context": { + "version": "5.7.0", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/metro-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/react-native-screens": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.24.0.tgz", + "integrity": "sha512-SyoiGaDofiyGPFrUkn1oGsAzkRuX1JUvTD9YQQK3G1JGQ5VWkvHgYSsc1K9OrLsDQxN7NmV71O0sHCAh8cBetA==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "react-freeze": "^1.0.0", + "warn-once": "^0.1.0" }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-secure-key-store": { + "version": "2.0.10", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-secure-key-store.git#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc", + "integrity": "sha512-Xyppk+Qd8DzxpUYmVTfXSr42eHw1Xjxg0g6Cc/iqXUGwc2MIm78+877sisAM0S/L8aZ9qBFfRdPpZfej3O+iHg==", + "license": "ISC" + }, + "node_modules/react-native-share": { + "version": "12.2.6", + "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-12.2.6.tgz", + "integrity": "sha512-K9jZCQaTIqSNG37kMVygU1rflVMJm2g0ikslnbbmQ7EgsckYpw7ipePyp01E64hG+HrWNl+z9ZnFWLCC6H+Tiw==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=16" + } + }, + "node_modules/react-native-svg": { + "version": "15.15.5", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.15.5.tgz", + "integrity": "sha512-L4go5jA+GWutdJ/JucuN20cjAbMg1HmMtAP+wZ+3JLCf6Jd0bhXQHxciRP/AQm/FlrIEZwkMcHNZP+FXAiic0w==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/metro-config/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "engines": { - "node": ">=10" + "node_modules/react-native-tcp-socket": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/react-native-tcp-socket/-/react-native-tcp-socket-6.4.1.tgz", + "integrity": "sha512-2+zya9ielB8nWfMYVULB64ZqLzQD32qIzTnDef7NM1BChaJiA1btkJTBL+8WnyBrujjlCBVxBC3gAkXtG5hmvQ==", + "license": "MIT", + "dependencies": { + "buffer": "^5.4.3", + "eventemitter3": "^4.0.7" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "individual", + "url": "https://github.com/sponsors/Rapsssito" + }, + "peerDependencies": { + "react-native": ">=0.60.0" } }, - "node_modules/metro-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/react-native-tcp-socket/node_modules/buffer": { + "version": "5.7.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/react-native-watch-connectivity": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "react": ">=15.1", + "react-native": ">=0.40" } }, - "node_modules/metro-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/react-native-worklets": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.8.1.tgz", + "integrity": "sha512-oWP/lStsAHU6oYCaWDXrda/wOHVdhusQJz1e6x9gPnXdFf4ndNDAOtWCmk2zGrAnlapfyA3rM6PCQq94mPg9cw==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-classes": "^7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/preset-typescript": "^7.27.1", + "convert-source-map": "^2.0.0", + "semver": "^7.7.3" }, - "engines": { - "node": ">=7.0.0" + "peerDependencies": { + "@babel/core": "*", + "@react-native/metro-config": "*", + "react": "*", + "react-native": "0.81 - 0.85" } }, - "node_modules/metro-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "node_modules/react-native-worklets/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/metro-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/react-native/node_modules/commander": { + "version": "12.1.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/metro-config/node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "node_modules/react-native/node_modules/regenerator-runtime": { + "version": "0.13.11", + "license": "MIT" + }, + "node_modules/react-native/node_modules/semver": { + "version": "7.7.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">= 10.14.2" + "node": ">=10" } }, - "node_modules/metro-config/node_modules/jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dependencies": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" + "node_modules/react-native/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "license": "MIT", "engines": { - "node": ">= 10.14.2" + "node": ">=0.10.0" } }, - "node_modules/metro-config/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "node_modules/react-test-renderer": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.2.3.tgz", + "integrity": "sha512-TMR1LnSFiWZMJkCgNf5ATSvAheTT2NvKIwiVwdBPHxjBI7n/JbWd4gaZ16DVd9foAXdvDz+sB5yxZTwMjPRxpw==", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" + "react-is": "^19.2.3", + "scheduler": "^0.27.0" }, - "engines": { - "node": ">= 10" + "peerDependencies": { + "react": "^19.2.3" } }, - "node_modules/metro-config/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "node_modules/react-test-renderer/node_modules/react-is": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", + "dev": true, + "license": "MIT" }, - "node_modules/metro-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/read-pkg": { + "version": "5.2.0", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" }, "engines": { "node": ">=8" } }, - "node_modules/metro-core": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.73.10.tgz", - "integrity": "sha512-5uYkajIxKyL6W45iz/ftNnYPe1l92CvF2QJeon1CHsMXkEiOJxEjo41l+iSnO/YodBGrmMCyupSO4wOQGUc0lw==", + "node_modules/read-pkg-up": { + "version": "7.0.1", + "license": "MIT", "dependencies": { - "lodash.throttle": "^4.1.1", - "metro-resolver": "0.73.10" + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/metro-file-map": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.73.10.tgz", - "integrity": "sha512-XOMWAybeaXyD6zmVZPnoCCL2oO3rp4ta76oUlqWP0skBzhFxVtkE/UtDwApEMUY361JeBBago647gnKiARs+1g==", + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "license": "MIT", "dependencies": { - "abort-controller": "^3.0.0", - "anymatch": "^3.0.3", - "debug": "^2.2.0", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "invariant": "^2.2.4", - "jest-regex-util": "^27.0.6", - "jest-serializer": "^27.0.6", - "jest-util": "^27.2.0", - "jest-worker": "^27.2.0", - "micromatch": "^4.0.4", - "nullthrows": "^1.1.1", - "walker": "^1.0.7" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "engines": { + "node": ">=8" } }, - "node_modules/metro-file-map/node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "p-locate": "^4.1.0" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/metro-file-map/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/metro-file-map/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=8" } }, - "node_modules/metro-file-map/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/metro-file-map/node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/metro-hermes-compiler": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-hermes-compiler/-/metro-hermes-compiler-0.73.10.tgz", - "integrity": "sha512-rTRWEzkVrwtQLiYkOXhSdsKkIObnL+Jqo+IXHI7VEK2aSLWRAbtGNqECBs44kbOUypDYTFFE+WLtoqvUWqYkWg==" - }, - "node_modules/metro-inspector-proxy": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.73.10.tgz", - "integrity": "sha512-CEEvocYc5xCCZBtGSIggMCiRiXTrnBbh8pmjKQqm9TtJZALeOGyt5pXUaEkKGnhrXETrexsg6yIbsQHhEvVfvQ==", - "dependencies": { - "connect": "^3.6.5", - "debug": "^2.2.0", - "ws": "^7.5.1", - "yargs": "^17.5.1" - }, - "bin": { - "metro-inspector-proxy": "src/cli.js" + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" } }, - "node_modules/metro-inspector-proxy/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/readable-stream": { + "version": "3.6.2", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=12" + "node": ">= 6" } }, - "node_modules/metro-inspector-proxy/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/realm": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/realm/-/realm-20.2.0.tgz", + "integrity": "sha512-xkfczMrxnZK1KlJ5JBWGMe2Cs48+p1D8T9r8TO6FrZhnm2KUUhBJPOpOIoJ4h1O20fCxlfJ2pIg3mplJTUgF6g==", + "hasInstallScript": true, + "license": "apache-2.0", "dependencies": { - "ms": "2.0.0" + "@realm/fetch": "^0.1.1", + "bson": "^4.7.2", + "debug": "^4.3.4", + "node-machine-id": "^1.1.12", + "path-browserify": "^1.0.1", + "prebuild-install": "^7.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react-native": ">=0.71.0" + }, + "peerDependenciesMeta": { + "react-native": { + "optional": true + } } }, - "node_modules/metro-inspector-proxy/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/metro-inspector-proxy/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/redent": { + "version": "3.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/metro-inspector-proxy/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "node_modules/reduce-flatten": { + "version": "2.0.0", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/metro-minify-terser": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.73.10.tgz", - "integrity": "sha512-uG7TSKQ/i0p9kM1qXrwbmY3v+6BrMItsOcEXcSP8Z+68bb+t9HeVK0T/hIfUu1v1PEnonhkhfzVsaP8QyTd5lQ==", + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "dev": true, + "license": "MIT", "dependencies": { - "terser": "^5.15.0" - } - }, - "node_modules/metro-minify-uglify": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.73.10.tgz", - "integrity": "sha512-eocnSeJKnLz/UoYntVFhCJffED7SLSgbCHgNvI6ju6hFb6EFHGJT9OLbkJWeXaWBWD3Zw5mYLS8GGqGn/CHZPA==", - "dependencies": { - "uglify-es": "^3.1.9" - } - }, - "node_modules/metro-react-native-babel-preset": { - "version": "0.77.0", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.77.0.tgz", - "integrity": "sha512-HPPD+bTxADtoE4y/4t1txgTQ1LVR6imOBy7RMHUsqMVTbekoi8Ph5YI9vKX2VMPtVWeFt0w9YnCSLPa76GcXsA==", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.4.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" }, "engines": { - "node": ">=18" + "node": ">= 0.4" }, - "peerDependencies": { - "@babel/core": "*" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/metro-react-native-babel-transformer": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.73.10.tgz", - "integrity": "sha512-4G/upwqKdmKEjmsNa92/NEgsOxUWOygBVs+FXWfXWKgybrmcjh3NoqdRYrROo9ZRA/sB9Y/ZXKVkWOGKHtGzgg==", - "dependencies": { - "@babel/core": "^7.20.0", - "babel-preset-fbjs": "^3.4.0", - "hermes-parser": "0.8.0", - "metro-babel-transformer": "0.73.10", - "metro-react-native-babel-preset": "0.73.10", - "metro-source-map": "0.73.10", - "nullthrows": "^1.1.1" - }, - "peerDependencies": { - "@babel/core": "*" - } + "node_modules/regenerate": { + "version": "1.4.2", + "license": "MIT" }, - "node_modules/metro-react-native-babel-transformer/node_modules/metro-react-native-babel-preset": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.73.10.tgz", - "integrity": "sha512-1/dnH4EHwFb2RKEKx34vVDpUS3urt2WEeR8FYim+ogqALg4sTpG7yeQPxWpbgKATezt4rNfqAANpIyH19MS4BQ==", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "react-refresh": "^0.4.0" + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" }, - "peerDependencies": { - "@babel/core": "*" + "engines": { + "node": ">=4" } }, - "node_modules/metro-resolver": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.73.10.tgz", - "integrity": "sha512-HeXbs+0wjakaaVQ5BI7eT7uqxlZTc9rnyw6cdBWWMgUWB++KpoI0Ge7Hi6eQAOoVAzXC3m26mPFYLejpzTWjng==", - "dependencies": { - "absolute-path": "^0.0.0" - } + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "license": "MIT" }, - "node_modules/metro-runtime": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.73.10.tgz", - "integrity": "sha512-EpVKm4eN0Fgx2PEWpJ5NiMArV8zVoOin866jIIvzFLpmkZz1UEqgjf2JAfUJnjgv3fjSV3JqeGG2vZCaGQBTow==", + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.0.0", - "react-refresh": "^0.4.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/metro-source-map": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.73.10.tgz", - "integrity": "sha512-NAGv14701p/YaFZ76KzyPkacBw/QlEJF1f8elfs23N1tC33YyKLDKvPAzFJiYqjdcFvuuuDCA8JCXd2TgLxNPw==", + "node_modules/regexpu-core": { + "version": "6.4.0", + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", - "invariant": "^2.2.4", - "metro-symbolicate": "0.73.10", - "nullthrows": "^1.1.1", - "ob1": "0.73.10", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - } - }, - "node_modules/metro-source-map/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/metro-symbolicate": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.73.10.tgz", - "integrity": "sha512-PmCe3TOe1c/NVwMlB+B17me951kfkB3Wve5RqJn+ErPAj93od1nxicp6OJe7JT4QBRnpUP8p9tw2sHKqceIzkA==", + "node_modules/regjsgen": { + "version": "0.8.0", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "license": "BSD-2-Clause", "dependencies": { - "invariant": "^2.2.4", - "metro-source-map": "0.73.10", - "nullthrows": "^1.1.1", - "source-map": "^0.5.6", - "through2": "^2.0.1", - "vlq": "^1.0.0" + "jsesc": "~3.1.0" }, "bin": { - "metro-symbolicate": "src/index.js" - }, - "engines": { - "node": ">=8.3" + "regjsparser": "bin/parser" } }, - "node_modules/metro-symbolicate/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "node_modules/require-directory": { + "version": "2.1.1", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/metro-transform-plugins": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.73.10.tgz", - "integrity": "sha512-D4AgD3Vsrac+4YksaPmxs/0ocT67bvwTkFSIgWWeDvWwIG0U1iHzTS9f8Bvb4PITnXryDoFtjI6OWF7uOpGxpA==", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", - "nullthrows": "^1.1.1" + "node_modules/require-from-string": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/metro-transform-worker": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.73.10.tgz", - "integrity": "sha512-IySvVubudFxahxOljWtP0QIMMpgUrCP0bW16cz2Enof0PdumwmR7uU3dTbNq6S+XTzuMHR+076aIe4VhPAWsIQ==", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/types": "^7.20.0", - "babel-preset-fbjs": "^3.4.0", - "metro": "0.73.10", - "metro-babel-transformer": "0.73.10", - "metro-cache": "0.73.10", - "metro-cache-key": "0.73.10", - "metro-hermes-compiler": "0.73.10", - "metro-source-map": "0.73.10", - "metro-transform-plugins": "0.73.10", - "nullthrows": "^1.1.1" - } + "node_modules/require-main-filename": { + "version": "2.0.0", + "license": "ISC" }, - "node_modules/metro/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/resolve": { + "version": "1.22.11", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/metro/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/resolve-cwd": { + "version": "3.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/metro/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, - "node_modules/metro/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, + "node_modules/resolve-from": { + "version": "5.0.0", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/metro/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/metro/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/metro/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/metro/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/resolve.exports": { + "version": "1.1.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/metro/node_modules/metro-react-native-babel-preset": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.73.10.tgz", - "integrity": "sha512-1/dnH4EHwFb2RKEKx34vVDpUS3urt2WEeR8FYim+ogqALg4sTpG7yeQPxWpbgKATezt4rNfqAANpIyH19MS4BQ==", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "react-refresh": "^0.4.0" + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" }, - "peerDependencies": { - "@babel/core": "*" + "engines": { + "node": ">=8" } }, - "node_modules/metro/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/metro/node_modules/serialize-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "node_modules/retry": { + "version": "0.12.0", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 4" } }, - "node_modules/metro/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "node_modules/reusify": { + "version": "1.1.0", + "license": "MIT", "engines": { + "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/metro/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "glob": "^7.1.3" }, - "engines": { - "node": ">=8" + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/metro/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/ripemd160": { + "version": "2.0.2", + "license": "MIT", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" + "hash-base": "^3.0.0", + "inherits": "^2.0.1" } }, - "node_modules/metro/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" + "node_modules/run-parallel": { + "version": "1.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" } }, - "node_modules/micromatch": { - "version": "4.0.5", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "node_modules/safe-array-concat": { + "version": "1.1.3", + "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" }, "engines": { - "node": ">=8.6" - } - }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" + "node": ">=0.4" }, - "bin": { - "miller-rabin": "bin/miller-rabin" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mime": { - "version": "2.6.0", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } + "node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "node_modules/mime-db": { - "version": "1.52.0", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } + "node_modules/safe-json-stringify": { + "version": "1.2.0", + "license": "MIT", + "optional": true }, - "node_modules/mime-types": { - "version": "2.1.35", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/safe-push-apply": { + "version": "1.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "es-errors": "^1.3.0", + "isarray": "^2.0.5" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/safe-regex-test": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "license": "MIT", "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + "node_modules/sanitize-filename": { + "version": "1.6.3", + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } }, - "node_modules/minimatch": { - "version": "3.1.2", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/scryptsy": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/secp256k1": { + "version": "3.8.1", + "hasInstallScript": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "bindings": "^1.5.0", + "bip66": "^1.1.5", + "bn.js": "^4.11.8", + "create-hash": "^1.2.0", + "drbg.js": "^1.0.1", + "elliptic": "^6.5.7", + "nan": "^2.14.0", + "safe-buffer": "^5.1.2" }, "engines": { - "node": "*" + "node": ">=4.0.0" } }, - "node_modules/minimist": { - "version": "1.2.8", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node_modules/semver": { + "version": "6.3.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8.0" } }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" + "ms": "2.0.0" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, - "node_modules/moment": { - "version": "2.29.4", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "optional": true, + "node_modules/send/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/multi-sort-stream": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/multi-sort-stream/-/multi-sort-stream-1.0.4.tgz", - "integrity": "sha512-hAZ8JOEQFbgdLe8HWZbb7gdZg0/yAIHF00Qfo3kd0rXFv96nXe+/bPTrKHZ2QMHugGX4FiAyET1Lt+jiB+7Qlg==" - }, - "node_modules/multipipe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-4.0.0.tgz", - "integrity": "sha512-jzcEAzFXoWwWwUbvHCNPwBlTz3WCWe/jPcXSmTfbo/VjRwRTfvLZ/bdvtiTdqCe8d4otCSsPCbhGYcX+eggpKQ==", - "dependencies": { - "duplexer2": "^0.1.2", - "object-assign": "^4.1.0" + "node": ">= 0.8" } }, - "node_modules/mv": { - "version": "2.1.1", - "integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==", - "optional": true, - "dependencies": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" }, "engines": { - "node": ">=0.8.0" + "node": ">=4" } }, - "node_modules/mv/node_modules/glob": { - "version": "6.0.4", - "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", - "optional": true, - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { - "node": "*" + "node": ">= 0.8" } }, - "node_modules/mv/node_modules/rimraf": { - "version": "2.4.5", - "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", - "optional": true, + "node_modules/serialize-error": { + "version": "8.1.0", + "license": "MIT", "dependencies": { - "glob": "^6.0.1" + "type-fest": "^0.20.2" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nan": { - "version": "2.17.0", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==" - }, - "node_modules/nanoid": { - "version": "3.3.4", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.20.2", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nanomatch": { - "version": "1.2.13", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/arr-diff": { - "version": "4.0.0", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "engines": { - "node": ">=0.10.0" + "node": ">= 0.8.0" } }, - "node_modules/nanomatch/node_modules/array-unique": { - "version": "0.3.2", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "node_modules/ncp": { + "node_modules/set-blocking": { "version": "2.0.0", - "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", - "optional": true, - "bin": { - "ncp": "bin/ncp" - } + "license": "ISC" }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/set-function-length": { + "version": "1.2.2", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "node_modules/nice-try": { - "version": "1.0.5", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "node_modules/nocache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", - "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", + "node_modules/set-function-name": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, "engines": { - "node": ">=12.0.0" + "node": ">= 0.4" } }, - "node_modules/node-abi": { - "version": "3.33.0", - "integrity": "sha512-7GGVawqyHF4pfd0YFybhv/eM9JwTtPqx0mAanQ146O3FlSh3pA24zf9IRQTOsfTSqXTNzPSP5iagAJ94jjuVog==", + "node_modules/set-proto": { + "version": "1.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "semver": "^7.3.5" + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/node-abi/node_modules/lru-cache": { - "version": "6.0.0", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sf-symbols-typescript": { + "version": "2.2.0", + "license": "MIT", "engines": { "node": ">=10" } }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.3.8", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "node_modules/sha.js": { + "version": "2.4.12", + "license": "(MIT AND BSD-3-Clause)", "dependencies": { - "lru-cache": "^6.0.0" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" }, "bin": { - "semver": "bin/semver.js" + "sha.js": "bin.js" }, "engines": { - "node": ">=10" + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-abi/node_modules/yallist": { - "version": "4.0.0", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/node-dir": { - "version": "0.1.17", - "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", + "node_modules/shebang-command": { + "version": "2.0.0", + "license": "MIT", "dependencies": { - "minimatch": "^3.0.2" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">= 0.10.5" + "node": ">=8" } }, - "node_modules/node-fetch": { - "version": "2.6.9", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "license": "MIT", "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">=8" } }, - "node_modules/node-gyp-build": { - "version": "4.6.0", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" + "node_modules/shell-quote": { + "version": "1.8.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-int64": { - "version": "0.4.0", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" - }, - "node_modules/node-ipc": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-9.2.1.tgz", - "integrity": "sha512-mJzaM6O3xHf9VT8BULvJSbdVbmHUKRNOH7zDDkCrA1/T+CVjq2WVIDfLt0azZRXpgArJtl3rtmEozrbXPZ9GaQ==", + "node_modules/side-channel": { + "version": "1.1.0", + "license": "MIT", "dependencies": { - "event-pubsub": "4.3.0", - "js-message": "1.0.7", - "js-queue": "2.0.2" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { - "node": ">=8.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-machine-id": { - "version": "1.1.12", - "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==" - }, - "node_modules/node-releases": { - "version": "2.0.10", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" - }, - "node_modules/node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "node_modules/side-channel-list": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.4" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/antelle" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-version": { - "version": "1.2.0", - "integrity": "sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, "engines": { - "node": ">=6.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dependencies": { - "boolbase": "^1.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/nullthrows": { - "version": "1.1.1", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" + "node_modules/signal-exit": { + "version": "3.0.7", + "license": "ISC" }, - "node_modules/ob1": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.73.10.tgz", - "integrity": "sha512-aO6EYC+QRRCkZxVJhCWhLKgVjhNuD6Gu1riGjxrIm89CqLsmKgxzYDDEsktmKsoDeRdWGQM5EdMzXDl5xcVfsw==" + "node_modules/silent-payments": { + "version": "2.2.1", + "resolved": "git+ssh://git@github.com/BlueWallet/SilentPayments.git#59a0373254695dc2d1be2246aeeb4b1b83f91b28", + "license": "MIT", + "dependencies": { + "@noble/secp256k1": "1.6.3", + "bip32": "5.0.0", + "bip39": "3.1.0", + "bitcoinjs-lib": "7.0.0", + "create-hash": "1.2.0", + "ecpair": "3.0.0" + } }, - "node_modules/object-assign": { - "version": "4.1.1", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" + "node_modules/silent-payments/node_modules/@scure/base": { + "version": "1.2.6", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/object-copy": { - "version": "0.1.0", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "node_modules/silent-payments/node_modules/base-x": { + "version": "5.0.1", + "license": "MIT" + }, + "node_modules/silent-payments/node_modules/bip174": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-3.0.0.tgz", + "integrity": "sha512-N3vz3rqikLEu0d6yQL8GTrSkpYb35NQKWMR7Hlza0lOj6ZOlvQ3Xr7N9Y+JPebaCVoEUHdBeBSuLxcHr71r+Lw==", + "license": "MIT", "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "uint8array-tools": "^0.0.9", + "varuint-bitcoin": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=18.0.0" } }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dependencies": { - "is-descriptor": "^0.1.0" - }, + "node_modules/silent-payments/node_modules/bip174/node_modules/uint8array-tools": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.9.tgz", + "integrity": "sha512-9vqDWmoSXOoi+K14zNaf6LBV51Q8MayF0/IiQs3GlygIKUYtog603e6virExkjjFosfJUBI4LhbQK1iq8IG11A==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=14.0.0" } }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "node_modules/silent-payments/node_modules/bip32": { + "version": "5.0.0", + "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" + "@noble/hashes": "^1.2.0", + "@scure/base": "^1.1.1", + "uint8array-tools": "^0.0.8", + "valibot": "^0.37.0", + "wif": "^5.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=18.0.0" } }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "node_modules/silent-payments/node_modules/bitcoinjs-lib": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-7.0.0.tgz", + "integrity": "sha512-2W6dGXFd1KG3Bs90Bzb5+ViCeSKNIYkCUWZ4cvUzUgwnneiNNZ6Sk8twGNcjlesmxC0JyLc/958QycfpvXLg7A==", + "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" + "@noble/hashes": "^1.2.0", + "bech32": "^2.0.0", + "bip174": "^3.0.0", + "bs58check": "^4.0.0", + "uint8array-tools": "^0.0.9", + "valibot": "^0.38.0", + "varuint-bitcoin": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=18.0.0" } }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, + "node_modules/silent-payments/node_modules/bitcoinjs-lib/node_modules/uint8array-tools": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.9.tgz", + "integrity": "sha512-9vqDWmoSXOoi+K14zNaf6LBV51Q8MayF0/IiQs3GlygIKUYtog603e6virExkjjFosfJUBI4LhbQK1iq8IG11A==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=14.0.0" } }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" + "node_modules/silent-payments/node_modules/bitcoinjs-lib/node_modules/valibot": { + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.38.0.tgz", + "integrity": "sha512-RCJa0fetnzp+h+KN9BdgYOgtsMAG9bfoJ9JSjIhFHobKWVWyzM3jjaeNTdpFK9tQtf3q1sguXeERJ/LcmdFE7w==", + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/silent-payments/node_modules/bs58": { + "version": "6.0.0", + "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" + "base-x": "^5.0.0" } }, - "node_modules/object-inspect": { - "version": "1.12.3", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node_modules/silent-payments/node_modules/bs58check": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^6.0.0" } }, - "node_modules/object-is": { - "version": "1.1.5", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "node_modules/silent-payments/node_modules/ecpair": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-3.0.0.tgz", + "integrity": "sha512-kf4JxjsRQoD4EBzpYjGAcR0t9i/4oAeRPtyCpKvSwyotgkc6oA4E4M0/e+kep7cXe+mgxAvoeh/jdgH9h5+Wxw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "uint8array-tools": "^0.0.8", + "valibot": "^0.37.0", + "wif": "^5.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=20.0.0" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "node_modules/silent-payments/node_modules/uint8array-tools": { + "version": "0.0.8", + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=14.0.0" } }, - "node_modules/object-visit": { - "version": "1.0.1", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "dependencies": { - "isobject": "^3.0.0" + "node_modules/silent-payments/node_modules/valibot": { + "version": "0.37.0", + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" }, - "engines": { - "node": ">=0.10.0" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/object.assign": { - "version": "4.1.4", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, + "node_modules/silent-payments/node_modules/varuint-bitcoin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-2.0.0.tgz", + "integrity": "sha512-6QZbU/rHO2ZQYpWFDALCDSRsXbAs1VOEmXAxtbtjLtKuMJ/FQ8YbhfxlaiKv5nklci0M6lZtlZyxo9Q+qNnyog==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "uint8array-tools": "^0.0.8" } }, - "node_modules/object.entries": { - "version": "1.1.6", - "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", - "dev": true, + "node_modules/silent-payments/node_modules/wif": { + "version": "5.0.0", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" + "bs58check": "^4.0.0" } }, - "node_modules/object.fromentries": { - "version": "2.0.6", - "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", - "dev": true, + "node_modules/simple-concat": { + "version": "1.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" } }, - "node_modules/object.hasown": { - "version": "1.1.2", - "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", - "dev": true, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "license": "MIT", "dependencies": { - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "is-arrayish": "^0.3.1" } }, - "node_modules/object.pick": { - "version": "1.3.0", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dependencies": { - "isobject": "^3.0.1" - }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "license": "MIT" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/object.values": { - "version": "1.1.6", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "color-convert": "^1.9.0" }, "engines": { - "node": ">= 0.8" + "node": ">=4" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "engines": { - "node": ">= 0.8" + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" } }, - "node_modules/once": { - "version": "1.4.0", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/slip39": { + "version": "0.1.9", + "resolved": "git+ssh://git@github.com/BlueWallet/slip39-js.git#d316ee6a929ab645fe5462ef1c91720eb66889c8", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "dev": true, + "license": "MIT", "dependencies": { - "wrappy": "1" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/onetime": { - "version": "5.1.2", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/spdx-correct": { + "version": "3.2.0", + "license": "Apache-2.0", "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/open": { - "version": "6.4.0", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "license": "MIT", "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=8" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/opencollective-postinstall": { - "version": "2.0.3", - "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", - "bin": { - "opencollective-postinstall": "index.js" + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "license": "CC0-1.0" + }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "node_modules/sprintf-js": { + "version": "1.0.3", "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-generator": { + "version": "2.0.10", + "license": "MIT", "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, + "stackframe": "^1.3.4" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": "*" } }, - "node_modules/ora": { - "version": "5.4.1", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "node_modules/stack-utils": { + "version": "2.0.6", + "dev": true, + "license": "MIT", "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" + "escape-string-regexp": "^2.0.0" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/stackframe": { + "version": "1.3.4", + "license": "MIT" + }, + "node_modules/stacktrace-parser": { + "version": "0.1.11", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "type-fest": "^0.7.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=6" } }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/stream-browserify": { + "version": "3.0.0", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "engines": { - "node": ">=0.10.0" + "node_modules/stream-chain": { + "version": "2.2.5", + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.9.1", + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" } }, - "node_modules/p-finally": { - "version": "1.0.0", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/strict-url-sanitise": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/strict-url-sanitise/-/strict-url-sanitise-0.0.1.tgz", + "integrity": "sha512-nuFtF539K8jZg3FjaWH/L8eocCR6gegz5RDOsaWxfdbF5Jqr2VXWxZayjTwUzsWJDC91k2EbnJXp6FuWW+Z4hg==", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "4.1.0", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/string-natural-compare": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/p-try": { - "version": "2.2.0", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", "dev": true, + "license": "MIT", "dependencies": { - "callsites": "^3.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/parse-asn1": { - "version": "5.1.6", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pascalcase": { - "version": "0.1.1", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/path-browserify": { - "version": "1.0.1", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/path-key": { - "version": "3.1.1", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-type": { + "node_modules/strip-bom": { "version": "4.0.0", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/payjoin-client": { - "version": "1.0.1", - "integrity": "sha512-Z9JmTcO3KBDX/w2V8W7L2juC0ItMSVCKp9YL/uaJLG9OS0ReT2Y2q1HfDgK9o5lSOCt1pPsHXo+1qJO2CApMxQ==", - "dependencies": { - "bitcoinjs-lib": "^5.2.0" + "node_modules/strip-final-newline": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/payjoin-client/node_modules/@types/node": { - "version": "10.12.18", - "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" - }, - "node_modules/payjoin-client/node_modules/bech32": { - "version": "1.1.4", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "node_modules/payjoin-client/node_modules/bip32": { - "version": "2.0.6", - "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==", + "node_modules/strip-indent": { + "version": "3.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "10.12.18", - "bs58check": "^2.1.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "tiny-secp256k1": "^1.1.3", - "typeforce": "^1.11.5", - "wif": "^2.0.6" + "min-indent": "^1.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=8" } }, - "node_modules/payjoin-client/node_modules/bitcoinjs-lib": { - "version": "5.2.0", - "integrity": "sha512-5DcLxGUDejgNBYcieMIUfjORtUeNWl828VWLHJGVKZCb4zIS1oOySTUr0LGmcqJBQgTBz3bGbRQla4FgrdQEIQ==", + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", + "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "license": "MIT", "dependencies": { - "bech32": "^1.1.2", - "bip174": "^2.0.1", - "bip32": "^2.0.4", - "bip66": "^1.1.0", - "bitcoin-ops": "^1.4.0", - "bs58check": "^2.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.3", - "merkle-lib": "^2.0.10", - "pushdata-bitcoin": "^1.0.1", - "randombytes": "^2.0.1", - "tiny-secp256k1": "^1.1.1", - "typeforce": "^1.11.3", - "varuint-bitcoin": "^1.0.4", - "wif": "^2.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=8" } }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "dev": true, + "license": "MIT", "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" }, "engines": { - "node": ">=0.12" + "node": ">=8" } }, - "node_modules/picocolors": { + "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pirates": { - "version": "4.0.5", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "node_modules/synckit": { + "version": "0.11.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.0", + "tslib": "^2.8.1" + }, "engines": { - "node": ">= 6" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, + "node_modules/table-layout": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "find-up": "^4.0.0" + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=8.0.0" } }, - "node_modules/pngjs": { - "version": "5.0.0", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "node_modules/table-layout/node_modules/array-back": { + "version": "4.0.2", + "license": "MIT", "engines": { - "node": ">=10.13.0" + "node": ">=8" } }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "node_modules/table-layout/node_modules/typical": { + "version": "5.2.0", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/prebuild-install": { - "version": "7.1.1", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "node_modules/tar-fs": { + "version": "2.1.2", + "license": "MIT", "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" + "tar-stream": "^2.1.4" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, + "node_modules/tar-stream": { + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, "engines": { - "node": ">= 0.8.0" + "node": ">=6" } }, - "node_modules/prettier": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", - "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" + "node_modules/telnet-client": { + "version": "1.2.8", + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.4" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "type": "paypal", + "url": "https://paypal.me/kozjak" } }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "node_modules/terminal-link": { + "version": "2.1.1", "dev": true, + "license": "MIT", "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/pretty-format": { - "version": "29.4.3", - "integrity": "sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA==", - "dependencies": { - "@jest/schemas": "^29.4.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "18.2.0", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, - "node_modules/printj": { - "version": "1.1.2", - "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "node_modules/terser": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.2.tgz", + "integrity": "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, "bin": { - "printj": "bin/printj.njs" + "terser": "bin/terser" }, "engines": { - "node": ">=0.8" - } - }, - "node_modules/process": { - "version": "0.11.10", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "engines": { - "node": ">= 0.6.0" + "node": ">=10" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" }, - "node_modules/promise": { - "version": "8.3.0", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", "dependencies": { - "asap": "~2.0.6" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/promise-polyfill": { - "version": "6.1.0", - "integrity": "sha512-g0LWaH0gFsxovsU7R5LrrhHhWAWiHRnh1GPrhXnPgYsDkIqjRYUYSZEsej/wtleDrz5xVSIDbeKfidztp2XHFQ==" - }, - "node_modules/prompts": { - "version": "2.4.2", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "node_modules/test-exclude": { + "version": "6.0.0", + "dev": true, + "license": "ISC", "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/proper-lockfile": { - "version": "3.2.0", - "integrity": "sha512-iMghHHXv2bsxl6NchhEaFck8tvX3F9cknEEh1SUpguUOBjN7PAAW9BLzmbc1g/mCD1gY3EE2EABBHPJfFdHFmA==", + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", "dependencies": { - "graceful-fs": "^4.1.11", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/pseudomap": { - "version": "1.0.2", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } + "node_modules/text-encoding": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", + "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", + "deprecated": "no longer maintained", + "license": "(Unlicense OR Apache-2.0)" }, - "node_modules/pump": { - "version": "3.0.0", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } + "node_modules/text-hex": { + "version": "1.0.0", + "license": "MIT" }, - "node_modules/punycode": { - "version": "2.3.0", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "engines": { - "node": ">=6" - } + "node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "license": "MIT" }, - "node_modules/pushdata-bitcoin": { - "version": "1.0.1", - "integrity": "sha512-hw7rcYTJRAl4olM8Owe8x0fBuJJ+WGbMhQuLWOXEMN3PxPCKQHRkhfL+XG0+iXUmSHjkMmb3Ba55Mt21cZc9kQ==", - "dependencies": { - "bitcoin-ops": "^1.3.0" - } + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "license": "MIT" }, - "node_modules/qrcode": { - "version": "1.5.1", - "integrity": "sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==", + "node_modules/tiny-secp256k1": { + "version": "1.1.7", + "hasInstallScript": true, + "license": "MIT", "dependencies": { - "dijkstrajs": "^1.0.1", - "encode-utf8": "^1.0.3", - "pngjs": "^5.0.0", - "yargs": "^15.3.1" - }, - "bin": { - "qrcode": "bin/qrcode" + "bindings": "^1.3.0", + "bn.js": "^4.11.8", + "create-hmac": "^1.1.7", + "elliptic": "^6.4.0", + "nan": "^2.13.2" }, "engines": { - "node": ">=10.13.0" + "node": ">=6.0.0" } }, - "node_modules/qrcode/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=8" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/qrcode/node_modules/cliui": { - "version": "6.0.0", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/qrcode/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, + "node_modules/tmp": { + "version": "0.2.5", + "license": "MIT", "engines": { - "node": ">=7.0.0" + "node": ">=14.14" } }, - "node_modules/qrcode/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "node_modules/tmpl": { + "version": "1.0.5", + "license": "BSD-3-Clause" }, - "node_modules/qrcode/node_modules/decamelize": { - "version": "1.2.0", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "node_modules/to-buffer": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/qrcode/node_modules/wrap-ansi": { - "version": "6.2.0", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/to-regex-range": { + "version": "5.0.1", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "is-number": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=8.0" } }, - "node_modules/qrcode/node_modules/y18n": { - "version": "4.0.3", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "node_modules/qrcode/node_modules/yargs": { - "version": "15.4.1", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.6" } }, - "node_modules/qrcode/node_modules/yargs-parser": { - "version": "18.1.3", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "node_modules/tr46": { + "version": "0.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/trace-event-lib": { + "version": "1.4.1", + "license": "MIT", "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "browser-process-hrtime": "^1.0.0" }, "engines": { - "node": ">=6" + "node": ">=12.0.0" } }, - "node_modules/qs": { - "version": "6.11.0", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, + "node_modules/triple-beam": { + "version": "1.4.1", + "license": "MIT", "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 14.0.0" } }, - "node_modules/query-string": { - "version": "6.14.1", - "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "license": "WTFPL", "dependencies": { - "decode-uri-component": "^0.2.0", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - }, + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=16" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "typescript": ">=4.2.0" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/randomfill": { - "version": "1.0.4", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "node_modules/ts-jest": { + "version": "29.3.1", + "dev": true, + "license": "MIT", "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.1", + "type-fest": "^4.38.0", + "yargs-parser": "^21.1.1" }, "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react": { - "version": "18.2.0", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" + "ts-jest": "cli.js" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-devtools-core": { - "version": "4.27.2", - "integrity": "sha512-8SzmIkpO87alD7Xr6gWIEa1jHkMjawOZ+6egjazlnjB4UUcbnzGDf/vBJ4BzGuWWEM+pzrxuzsPpcMqlQkYK2g==", - "dependencies": { - "shell-quote": "^1.6.1", - "ws": "^7" - } - }, - "node_modules/react-freeze": { - "version": "1.0.3", - "integrity": "sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g==", - "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { - "react": ">=17.0.0" + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } } }, - "node_modules/react-is": { - "version": "16.13.1", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-localization": { - "version": "1.0.19", - "resolved": "git+ssh://git@github.com/BlueWallet/react-localization.git#ae7969a8998128aebf1169f931fb22587dc5f874", + "node_modules/ts-jest/node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, "license": "MIT", "dependencies": { - "localized-strings": "github:BlueWallet/localized-strings#178351a7297336618d9ee87277f8e3af9ab7285d" - }, - "peerDependencies": { - "react": "^18.0.0 || ^17.0.0 || ^16.0.0 || ^15.6.0" - } - }, - "node_modules/react-native": { - "version": "0.71.11", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.71.11.tgz", - "integrity": "sha512-++8IxgUe4Ev+bTiFlLfJCdSoE5cReVP1DTpVJ8f/QtzaxA3h1008Y3Xah1Q5vsR4rZcYMO7Pn3af+wOshdQFug==", - "dependencies": { - "@jest/create-cache-key-function": "^29.2.1", - "@react-native-community/cli": "10.2.4", - "@react-native-community/cli-platform-android": "10.2.0", - "@react-native-community/cli-platform-ios": "10.2.4", - "@react-native/assets": "1.0.0", - "@react-native/normalize-color": "2.1.0", - "@react-native/polyfills": "2.0.0", - "abort-controller": "^3.0.0", - "anser": "^1.4.9", - "base64-js": "^1.1.2", - "deprecated-react-native-prop-types": "^3.0.1", - "event-target-shim": "^5.0.1", - "invariant": "^2.2.4", - "jest-environment-node": "^29.2.1", - "jsc-android": "^250231.0.0", - "memoize-one": "^5.0.0", - "metro-react-native-babel-transformer": "0.73.10", - "metro-runtime": "0.73.10", - "metro-source-map": "0.73.10", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1", - "pretty-format": "^26.5.2", - "promise": "^8.3.0", - "react-devtools-core": "^4.26.1", - "react-native-codegen": "^0.71.5", - "react-native-gradle-plugin": "^0.71.19", - "react-refresh": "^0.4.0", - "react-shallow-renderer": "^16.15.0", - "regenerator-runtime": "^0.13.2", - "scheduler": "^0.23.0", - "stacktrace-parser": "^0.1.3", - "use-sync-external-store": "^1.0.0", - "whatwg-fetch": "^3.0.0", - "ws": "^6.2.2" - }, - "bin": { - "react-native": "cli.js" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": "18.2.0" - } - }, - "node_modules/react-native-animatable": { - "version": "1.3.3", - "integrity": "sha512-2ckIxZQAsvWn25Ho+DK3d1mXIgj7tITkrS4pYDvx96WyOttSvzzFeQnM2od0+FUMzILbdHDsDEqZvnz1DYNQ1w==", - "dependencies": { - "prop-types": "^15.7.2" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/react-native-blue-crypto": { - "version": "1.0.0", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-blue-crypto.git#3cb5442425bd835e185284fbc62e84b7155bc441", + "node_modules/ts-jest/node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-camera-kit": { - "version": "13.0.0", - "integrity": "sha512-fnkyivCG2xzS+14/doP8pCAYNafYaTyg5J0t+JJltJdgKSHf328OG44Rd+fnbbEOydZxgy/bcuLB24R0kCbynw==", - "dependencies": { - "lodash": "^4.14.2" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-codegen": { - "version": "0.71.5", - "integrity": "sha512-rfsuc0zkuUuMjFnrT55I1mDZ+pBRp2zAiRwxck3m6qeGJBGK5OV5JH66eDQ4aa+3m0of316CqrJDRzVlYufzIg==", "dependencies": { - "@babel/parser": "^7.14.0", - "flow-parser": "^0.185.0", - "jscodeshift": "^0.13.1", - "nullthrows": "^1.1.1" + "@types/yargs-parser": "*" } }, - "node_modules/react-native-crypto": { - "version": "2.2.0", - "integrity": "sha512-eZu9Y8pa8BN9FU2pIex7MLRAi+Cd1Y6bsxfiufKh7sfraAACJvjQTeW7/zcQAT93WMfM+D0OVk+bubvkrbrUkw==", + "node_modules/ts-jest/node_modules/jest-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.4", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "3.0.8", - "public-encrypt": "^4.0.0", - "randomfill": "^1.0.3" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "*" - }, - "peerDependencies": { - "react-native-randombytes": ">=2.0.0 <4.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/react-native-crypto/node_modules/pbkdf2": { - "version": "3.0.8", - "integrity": "sha512-Bf7yBd61ChnMqPqf+PxHm34Iiq9M9Bkd/+JqzosPOqwG6FiTixtkpCs4PNd38+6/VYRvAxGe/GgPb4Q4GktFzg==", - "dependencies": { - "create-hmac": "^1.1.2" + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=0.12" + "node": ">=10" } }, - "node_modules/react-native-default-preference": { - "version": "1.4.4", - "integrity": "sha512-h0vtgiSKws3UmMRJykXAVM4ne1SgfoocUcoBD19ewRpQd6wqurE0HJRQGrSxcHK5LdKE7QPSIB1VX3YGIVS8Jg==", - "peerDependencies": { - "react-native": ">=0.47.0" + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.39.1", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-native-device-info": { - "version": "8.7.1", - "integrity": "sha512-cVMZztFa2Qn6qpQa601W61CtUwZQ1KXfqCOeltejAWEXmgIWivC692WGSdtGudj4upSi1UgMSaGcvKjfcpdGjg==", - "peerDependencies": { - "react-native": "*" + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" } }, - "node_modules/react-native-document-picker": { - "version": "9.0.1", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-document-picker.git#857655cdddf17751c0fae1286a9121fda2a6d568", - "integrity": "sha512-1HRak1rmZlf9ixWvtU7JzOsseMQNUsf1YTliFJiXEjNOn3+b5Mx9IdZRx9Nf1jIuqwZHeOJtPjm/PX/rN6kqTg==", + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "dev": true, "license": "MIT", "dependencies": { - "invariant": "^2.2.4" - }, - "peerDependencies": { - "react": "*", - "react-native": "*", - "react-native-windows": "*" + "minimist": "^1.2.0" }, - "peerDependenciesMeta": { - "react-native-windows": { - "optional": true - } + "bin": { + "json5": "lib/cli.js" } }, - "node_modules/react-native-draggable-flatlist": { - "version": "4.0.1", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-draggable-flatlist.git#ebfddc4877e8f65d5391a748db61b9cd030430ba", + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "dev": true, "license": "MIT", - "dependencies": { - "@babel/preset-typescript": "^7.17.12" - }, - "peerDependencies": { - "react-native": ">=0.64.0", - "react-native-gesture-handler": ">=2.0.0", - "react-native-reanimated": ">=2.8.0" + "engines": { + "node": ">=4" } }, - "node_modules/react-native-elements": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-3.4.3.tgz", - "integrity": "sha512-VtZc25EecPZyUBER85zFK9ZbY6kkUdcm1ZwJ9hdoGSCr1R/GFgxor4jngOcSYeMvQ+qimd5No44OVJW3rSJECA==", - "hasInstallScript": true, + "node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "license": "Apache-2.0", "dependencies": { - "@types/react-native-vector-icons": "^6.4.6", - "color": "^3.1.2", - "deepmerge": "^4.2.2", - "hoist-non-react-statics": "^3.3.2", - "lodash.isequal": "^4.5.0", - "opencollective-postinstall": "^2.0.3", - "react-native-ratings": "8.0.4", - "react-native-size-matters": "^0.3.1" + "safe-buffer": "^5.0.1" }, - "peerDependencies": { - "react-native-safe-area-context": ">= 3.0.0", - "react-native-vector-icons": ">7.0.0" + "engines": { + "node": "*" } }, - "node_modules/react-native-fingerprint-scanner": { - "version": "6.0.0", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-fingerprint-scanner.git#ce644673681716335d786727bab998f7e632ab5e", - "integrity": "sha512-bS/wNTeah8vACOOuYWRdAi0yKdAqcYqz611PoAtPFs/EaYD/Y8vw+/1jas5DUvTKnAoveCcO1ICVgCsT7XTdKA==", + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, "license": "MIT", - "peerDependencies": { - "react-native": ">=0.60 <1.0.0" - } - }, - "node_modules/react-native-fs": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.20.0.tgz", - "integrity": "sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ==", "dependencies": { - "base-64": "^0.1.0", - "utf8": "^3.0.0" - }, - "peerDependencies": { - "react-native": "*", - "react-native-windows": "*" + "prelude-ls": "^1.2.1" }, - "peerDependenciesMeta": { - "react-native-windows": { - "optional": true - } - } - }, - "node_modules/react-native-gesture-handler": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.9.0.tgz", - "integrity": "sha512-a0BcH3Qb1tgVqUutc6d3VuWQkI1AM3+fJx8dkxzZs9t06qA27QgURYFoklpabuWpsUTzuKRpxleykp25E8m7tg==", - "dependencies": { - "@egjs/hammerjs": "^2.0.17", - "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4", - "lodash": "^4.17.21", - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-gradle-plugin": { - "version": "0.71.19", - "resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.71.19.tgz", - "integrity": "sha512-1dVk9NwhoyKHCSxcrM6vY6cxmojeATsBobDicX0ZKr7DgUF2cBQRTKsimQFvzH8XhOVXyH8p4HyDSZNIFI8OlQ==" - }, - "node_modules/react-native-handoff": { - "version": "0.0.3", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-handoff.git#31d005f93d31099d0e564590a3bbd052b8a02b39", - "integrity": "sha512-kyJxMjkMvlebreExkmxRuId6w+pQJlblXKd0tpDyWGQzcL9RmFyEsRTn3ZUoRzu/XKNGhs/uKUgZWzgc/qBkpQ==", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-haptic-feedback": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/react-native-haptic-feedback/-/react-native-haptic-feedback-2.0.3.tgz", - "integrity": "sha512-7+qvcxXZts/hA+HOOIFyM1x9m9fn/TJVSTgXaoQ8uT4gLc97IMvqHQ559tDmnlth+hHMzd3HRMpmRLWoKPL0DA==", - "peerDependencies": { - "react-native": ">=0.60.0" - } - }, - "node_modules/react-native-idle-timer": { - "version": "2.1.6", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-idle-timer.git#8587876d68ab5920e79619726aeca9e672beaf2b", - "integrity": "sha512-d24QXgHUkwu+BTp676Pb7ng7u01iiw3YBBY+DYvurkMAHRoB9c+wj32oB4sLq07W+LbETyMEuDlLUxTOasqd3Q==", - "license": "MIT" - }, - "node_modules/react-native-image-picker": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-4.8.5.tgz", - "integrity": "sha512-+pQxkjO8cKv4RKTHOFS0fSHQ11HkWgb+imUPSOS8mwoChkR33aSuzV/6P4t9JCJgsus4qLAlB6BUosdIxw7GTA==", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-ios-context-menu": { - "version": "1.15.3", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-ios-context-menu.git#e5c1217cd220bfab6e6d9a7c65838545082e3f8e", - "license": "MIT", - "dependencies": { - "@dominicstop/ts-event-emitter": "^1.1.0" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-iphone-x-helper": { - "version": "1.3.1", - "integrity": "sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==", - "peerDependencies": { - "react-native": ">=0.42.0" - } - }, - "node_modules/react-native-keychain": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-8.1.2.tgz", - "integrity": "sha512-bhHEui+yMp3Us41NMoRGtnWEJiBE0g8tw5VFpq4mpmXAx6XJYahuM6K3WN5CsUeUl83hYysSL9oFZNKSTPSvYw==" - }, - "node_modules/react-native-linear-gradient": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.2.tgz", - "integrity": "sha512-hgmCsgzd58WNcDCyPtKrvxsaoETjb/jLGxis/dmU3Aqm2u4ICIduj4ECjbil7B7pm9OnuTkmpwXu08XV2mpg8g==", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-localize": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-3.0.2.tgz", - "integrity": "sha512-/l/oE1LVNgIRRhLbhmfFMHiWV0xhUn0A0iz1ytLVRYywL7FTp8Rx2vkJS/q/RpExDvV7yLw2493XZBYIM1dnLQ==", - "peerDependencies": { - "react": ">=18.1.0", - "react-native": ">=0.70.0", - "react-native-macos": ">=0.70.0" - }, - "peerDependenciesMeta": { - "react-native-macos": { - "optional": true - } - } - }, - "node_modules/react-native-modal": { - "version": "13.0.1", - "integrity": "sha512-UB+mjmUtf+miaG/sDhOikRfBOv0gJdBU2ZE1HtFWp6UixW9jCk/bhGdHUgmZljbPpp0RaO/6YiMmQSSK3kkMaw==", - "dependencies": { - "prop-types": "^15.6.2", - "react-native-animatable": "1.3.3" - }, - "peerDependencies": { - "react": "*", - "react-native": ">=0.65.0" - } - }, - "node_modules/react-native-navigation-bar-color": { - "version": "2.0.1", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-navigation-bar-color.git#3b2894ae62fbce99a3bd24105f0921cebaef5c94", - "integrity": "sha512-tFVsXyfvEQ8FJM9r5k7buLAMC1QMHEo3uFdXYDw86Y3iQiHA4QnE2KCiJxjF7NhkL6HFVRSDSVv0r2qEhs1pMg==", - "license": "MIT" - }, - "node_modules/react-native-obscure": { - "name": "@talaikis/react-native-obscure", - "version": "0.0.3", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb", - "integrity": "sha512-bmzbnlXII8hW7steqwouzQW9cJ+mdA8HJ8pWkb0KD60jC5dYgJ0e66wgUiSTDNFPrvlEKriE4eEanoJFj7Q9pg==", - "license": "MIT", - "peerDependencies": { - "react-native": "^0.60.5" - } - }, - "node_modules/react-native-passcode-auth": { - "version": "1.0.0", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-passcode-auth.git#a2ff977ba92b36f8d0a5567f59c05cc608e8bd12", - "integrity": "sha512-8FL4NDMZZVrbHr1f4555dV+GY3PLpmSbJ1wIbdW1r6zSaFe59g9ns4sdLliisjO+RvyDJP7UDPDaeu+2iJ26Bg==", - "license": "ISC" - }, - "node_modules/react-native-privacy-snapshot": { - "version": "1.0.0", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-privacy-snapshot.git#529e4627d93f67752a27e82a040ff7b64dca0783", - "integrity": "sha512-bO73UmkI0KpAzk3766z77qBdArwOaBCr58q5H0qDfn0jf5HonYoEkJRDRwHr7SpDxrSu2Nuoz2AokxLyb3/KXw==", - "license": "MIT", - "peerDependencies": { - "react": "^16.13.1", - "react-native": "^0.62.0" - } - }, - "node_modules/react-native-prompt-android": { - "version": "1.0.0", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-prompt-android.git#ed168d66fed556bc2ed07cf498770f058b78a376", - "integrity": "sha512-DcHZ4mMVF0HlMui8qq81kJcY+SNx2dXa9YuKDBoENCJxrPbjc++/XsSHpqQOOLV2wR+zItDDtvNkJRqI8g5JRQ==", - "license": "MIT" - }, - "node_modules/react-native-push-notification": { - "version": "8.1.1", - "integrity": "sha512-XpBtG/w+a6WXTxu6l1dNYyTiHnbgnvjoc3KxPTxYkaIABRmvuJZkFxqruyGvfCw7ELAlZEAJO+dthdTabCe1XA==", - "peerDependencies": { - "@react-native-community/push-notification-ios": "^1.10.1", - "react-native": ">=0.33" - } - }, - "node_modules/react-native-qrcode-svg": { - "version": "6.2.0", - "integrity": "sha512-rb2PgUwT8QpQyReVYNvzRY84AHsMh81354Tnkfp6MfqRbcdJURhnBWLBOO11pLMS6eXiwlq4SkcQxy88hRq+Dw==", - "dependencies": { - "prop-types": "^15.8.0", - "qrcode": "^1.5.1" - }, - "peerDependencies": { - "react": "*", - "react-native": ">=0.63.4", - "react-native-svg": "^13.2.0" - } - }, - "node_modules/react-native-quick-actions": { - "version": "0.3.13", - "integrity": "sha512-Vz13a0+NV0mzCh/29tNt0qDzWPh8i2srTQW8eCSzGFDArnVm1COTOhTD0FY0hWHlxRY0ahvX+BlezTDvsyAuMA==" - }, - "node_modules/react-native-randombytes": { - "version": "3.6.1", - "integrity": "sha512-qxkwMbOZ0Hff1V7VqpaWrR6ItkA+oF6bnI79Qp9F3Tk8WBsdKDi6m1mi3dEdFWePoRLrhJ2L03rU0yabst1tVw==", - "dependencies": { - "buffer": "^4.9.1", - "sjcl": "^1.0.3" - } - }, - "node_modules/react-native-randombytes/node_modules/buffer": { - "version": "4.9.2", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/react-native-rate": { - "version": "1.2.12", - "integrity": "sha512-A/z3s7Zth08aXcJnru6S4p71NG8acx2w5LhIfItwTJUbQruNJugk8WrN51dLBCSDv8W33kbS5YoUT4M9jOP5gA==" - }, - "node_modules/react-native-ratings": { - "version": "8.0.4", - "integrity": "sha512-Xczu5lskIIRD6BEdz9A0jDRpEck/SFxRqiglkXi0u67yAtI1/pcJC76P4MukCbT8K4BPVl+42w83YqXBoBRl7A==", - "dependencies": { - "lodash": "^4.17.15" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-reanimated": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-2.17.0.tgz", - "integrity": "sha512-bVy+FUEaHXq4i+aPPqzGeor1rG4scgVNBbBz21ohvC7iMpB9IIgvGsmy1FAoodZhZ5sa3EPF67Rcec76F1PXlQ==", - "dependencies": { - "@babel/plugin-transform-object-assign": "^7.16.7", - "@babel/preset-typescript": "^7.16.7", - "invariant": "^2.2.4", - "lodash.isequal": "^4.5.0", - "setimmediate": "^1.0.5", - "string-hash-64": "^1.0.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0", - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-safe-area-context": { - "version": "3.4.1", - "integrity": "sha512-xfpVd0CiZR7oBhuwJ2HcZMehg5bjha1Ohu1XHpcT+9ykula0TgovH2BNU0R5Krzf/jBR1LMjR6VabxdlUjqxcA==", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-safe-modules": { - "version": "1.0.3", - "integrity": "sha512-DUxti4Z+AgJ/ZsO5U7p3uSCUBko8JT8GvFlCeOXk9bMd+4qjpoDvMYpfbixXKgL88M+HwmU/KI1YFN6gsQZyBA==", - "dependencies": { - "dedent": "^0.6.0" - }, - "peerDependencies": { - "react-native": "*" - } - }, - "node_modules/react-native-safe-modules/node_modules/dedent": { - "version": "0.6.0", - "integrity": "sha512-cSfRWjXJtZQeRuZGVvDrJroCR5V2UvBNUMHsPCdNYzuAG8b9V8aAy3KUcdQrGQPXs17Y+ojbPh1aOCplg9YR9g==" - }, - "node_modules/react-native-screens": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.20.0.tgz", - "integrity": "sha512-joWUKWAVHxymP3mL9gYApFHAsbd9L6ZcmpoZa6Sl3W/82bvvNVMqcfP7MeNqVCg73qZ8yL4fW+J/syusHleUgg==", - "dependencies": { - "react-freeze": "^1.0.0", - "warn-once": "^0.1.0" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-secure-key-store": { - "version": "2.0.10", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-secure-key-store.git#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc", - "license": "ISC" - }, - "node_modules/react-native-share": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-8.2.2.tgz", - "integrity": "sha512-kVCI/cT0GnuYUTXe6mAimrjrnt4VWoRfrWqJZjFeoYFqAyOEfos84RC4eZlZnOT5eVtmTXRIkor5vgSkKOlZhw==" - }, - "node_modules/react-native-size-matters": { - "version": "0.3.1", - "integrity": "sha512-mKOfBLIBFBcs9br1rlZDvxD5+mAl8Gfr5CounwJtxI6Z82rGrMO+Kgl9EIg3RMVf3G855a85YVqHJL2f5EDRlw==", - "peerDependencies": { - "react-native": "*" - } - }, - "node_modules/react-native-svg": { - "version": "13.13.0", - "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.13.0.tgz", - "integrity": "sha512-L8y8uEiMG0Tr++Nb2+24wlMuv18+bmq/CMoFFtTUlEqVvGCoK2ea8WamPl/9bV8gjL+Rngg5NqEBvKS23sbYoA==", - "dependencies": { - "css-select": "^5.1.0", - "css-tree": "^1.1.3" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-tcp-socket": { - "version": "5.6.2", - "integrity": "sha512-doijFOAJd9p8KmduhfbZaPfqRVd3CZuTLAimJx0yxIqFWy/EDPGHeFVrOEOqRZ3lWBVDcssiCIQJhV0baKu5Pg==", - "dependencies": { - "buffer": "^5.4.3", - "eventemitter3": "^4.0.7" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/Rapsssito" - }, - "peerDependencies": { - "react-native": ">=0.60.0" - } - }, - "node_modules/react-native-tcp-socket/node_modules/buffer": { - "version": "5.7.1", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/react-native-tor": { - "version": "0.1.8", - "integrity": "sha512-rRArwpqTQoUrQ3WQxc1WLkk1Eg/yjiwU775AxnSt6E7Nfy1V9H6+R9reMD5PJ/39qcuDYv3F7i3MR8Rjy7lOZA==", - "dependencies": { - "@types/async": "^3.2.6", - "async": "^3.2.0" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-vector-icons": { - "version": "9.2.0", - "integrity": "sha512-wKYLaFuQST/chH3AJRjmOLoLy3JEs1JR6zMNgTaemFpNoXs0ztRnTxcxFD9xhX7cJe1/zoN5BpQYe7kL0m5yyA==", - "dependencies": { - "prop-types": "^15.7.2", - "yargs": "^16.1.1" - }, - "bin": { - "fa5-upgrade": "bin/fa5-upgrade.sh", - "generate-icon": "bin/generate-icon.js" - } - }, - "node_modules/react-native-watch-connectivity": { - "version": "1.1.0", - "integrity": "sha512-s+zlKOVENRXgkVQvJt5f73CyqpC6ZhKlRsXLybtaFIsR1KR3ARzExm0yTm3DAb5K9AqtCpYX+1SOd4d0Af2ZNQ==", - "dependencies": { - "lodash.sortby": "^4.7.0" - }, - "peerDependencies": { - "react": ">=15.1", - "react-native": ">=0.40" - } - }, - "node_modules/react-native-webview": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-12.4.0.tgz", - "integrity": "sha512-wYzTfNADidmqv6bY+x6NUfX8+uBR9mmF1CO1NOvY4oD2vv+D4rA0XwcwAe2D7RevXUy3fmuTT93kFQcgo8fEhg==", - "dependencies": { - "escape-string-regexp": "2.0.0", - "invariant": "2.2.4" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-webview/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "engines": { - "node": ">=8" - } - }, - "node_modules/react-native-widget-center": { - "version": "0.0.9", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-widget-center.git#a128c389526d55afdd67937494f2fec224dd0009", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.1", - "react-native": ">=0.60.0-rc.0 <1.0.x" + "node": ">= 0.8.0" } }, - "node_modules/react-native/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/react-native/node_modules/@types/yargs": { - "version": "15.0.15", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz", - "integrity": "sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/react-native/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/react-native/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/react-native/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/react-native/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/react-native/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/react-native/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/react-native/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "node_modules/react-native/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-native/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/react-refresh": { - "version": "0.4.3", - "integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-shallow-renderer": { - "version": "16.15.0", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "dependencies": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-test-renderer": { - "version": "18.2.0", - "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", - "dev": true, - "dependencies": { - "react-is": "^18.2.0", - "react-shallow-renderer": "^16.15.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/react-test-renderer/node_modules/react-is": { - "version": "18.2.0", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readline": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==" - }, - "node_modules/realm": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/realm/-/realm-12.0.0.tgz", - "integrity": "sha512-QaFnn92eCwpWCvbnxE26N0mAHhuXRwtM23JF0tA5fKtTA+djj1+LhuT/iGJryCrGVbN8m++EOBsvWgGG8hpsuw==", - "hasInstallScript": true, - "dependencies": { - "bson": "^4.7.2", - "debug": "^4.3.4", - "node-fetch": "^2.6.9", - "node-machine-id": "^1.1.12", - "prebuild-install": "^7.1.1" - }, - "peerDependencies": { - "react-native": ">=0.71.0" - }, - "peerDependenciesMeta": { - "react-native": { - "optional": true - } - } - }, - "node_modules/recast": { - "version": "0.20.5", - "integrity": "sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==", - "dependencies": { - "ast-types": "0.14.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/reduce-flatten": { - "version": "2.0.0", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", - "engines": { - "node": ">=6" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "node_modules/regenerator-transform": { - "version": "0.15.1", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpu-core": { - "version": "5.3.1", - "integrity": "sha512-nCOzW2V/X15XpLsK2rlgdwrysrBq+AauCn+omItIz4R1pIcmeot5zvjdmOBRLzEH/CkC6IxMJVmxDe3QcMuNVQ==", - "dependencies": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsparser": { - "version": "0.9.1", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "node_modules/requires-port": { - "version": "1.0.0", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, - "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dependencies": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "deprecated": "https://github.com/lydell/resolve-url#deprecated" - }, - "node_modules/resolve.exports": { - "version": "1.1.1", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "engines": { - "node": ">=0.12" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/rn-ldk": { - "version": "0.8.4", - "resolved": "git+ssh://git@github.com/BlueWallet/rn-ldk.git#ab0ce31e4ec8c208fe16954ecbcaba5df60f56c6", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/rn-nodeify": { - "version": "10.3.0", - "integrity": "sha512-EZB3M4M5i8yySCWF7AAZ31xU7cpdLuIKMlVxXji9t0aY8Ojy3BAyRt1sTp0OwBgy1ejShmlIu2L4f8mToJ+uvg==", - "dependencies": { - "@yarnpkg/lockfile": "^1.0.0", - "deep-equal": "^1.0.0", - "findit": "^2.0.0", - "fs-extra": "^0.22.1", - "minimist": "^1.1.2", - "object.pick": "^1.1.1", - "run-parallel": "^1.1.2", - "semver": "^5.0.1", - "xtend": "^4.0.0" - }, - "bin": { - "rn-nodeify": "cmd.js" - } - }, - "node_modules/rn-nodeify/node_modules/fs-extra": { - "version": "0.22.1", - "integrity": "sha512-M7CuxS2f9k/5il8ufmLiCtT7B2O2JLoTZi83ZtyEJMG67cTn87fNULYWtno5Vm31TxmSRE0nkA9GxaRR+y3XTA==", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "rimraf": "^2.2.8" - } - }, - "node_modules/rn-nodeify/node_modules/jsonfile": { - "version": "2.4.0", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/rn-nodeify/node_modules/rimraf": { - "version": "2.7.1", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/rn-nodeify/node_modules/semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/run-applescript": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", - "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", - "dev": true, - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-json-stringify": { - "version": "1.2.0", - "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", - "optional": true - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sanitize-filename": { - "version": "1.6.3", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", - "dependencies": { - "truncate-utf8-bytes": "^1.0.0" - } - }, - "node_modules/scheduler": { - "version": "0.23.0", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/scryptsy": { - "version": "2.1.0", - "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==" - }, - "node_modules/secp256k1": { - "version": "3.8.0", - "integrity": "sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw==", - "hasInstallScript": true, - "dependencies": { - "bindings": "^1.5.0", - "bip66": "^1.1.5", - "bn.js": "^4.11.8", - "create-hash": "^1.2.0", - "drbg.js": "^1.0.1", - "elliptic": "^6.5.2", - "nan": "^2.14.0", - "safe-buffer": "^5.1.2" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/send/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serialize-error": { - "version": "8.1.0", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, - "node_modules/set-value": { - "version": "2.0.1", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/sha.js": { - "version": "2.4.11", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "1.2.0", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-regex": { - "version": "1.0.0", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shell-quote": { - "version": "1.8.0", - "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/simple-get": { - "version": "4.0.1", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, - "node_modules/sjcl": { - "version": "1.0.8", - "integrity": "sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ==", - "engines": { - "node": "*" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slip39": { - "version": "0.1.7", - "resolved": "git+ssh://git@github.com/BlueWallet/slip39-js.git#35619ed112fa022de1f5a3b6e2996dd3025472b2", - "license": "MIT" - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/kind-of": { - "version": "5.1.0", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated" - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.12", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==" - }, - "node_modules/split-on-first": { - "version": "1.1.0", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/split-string": { - "version": "3.1.0", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, - "node_modules/stack-generator": { - "version": "2.0.10", - "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", - "dependencies": { - "stackframe": "^1.3.4" - } - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/stackframe": { - "version": "1.3.4", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" - }, - "node_modules/stacktrace-parser": { - "version": "0.1.10", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "dependencies": { - "type-fest": "^0.7.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/stacktrace-parser/node_modules/type-fest": { - "version": "0.7.1", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/kind-of": { - "version": "5.1.0", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stream-browserify": { - "version": "3.0.0", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "dependencies": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "node_modules/stream-chain": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", - "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==" - }, - "node_modules/stream-json": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.7.5.tgz", - "integrity": "sha512-NSkoVduGakxZ8a+pTPUlcGEeAGQpWL9rKJhOFCV+J/QtdQUEU5vtBgVg6eJXn8JB8RZvpbJWZGvXkhz70MLWoA==", - "dependencies": { - "stream-chain": "^2.2.5" - } - }, - "node_modules/strict-uri-encode": { - "version": "2.0.0", - "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-hash-64": { - "version": "1.0.3", - "integrity": "sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==" - }, - "node_modules/string-length": { - "version": "4.0.2", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-natural-compare": { - "version": "3.0.1", - "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", - "dev": true - }, - "node_modules/string-width": { - "version": "4.2.3", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.8", - "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" - }, - "node_modules/sudo-prompt": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==" - }, - "node_modules/supports-color": { - "version": "5.5.0", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/synckit": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", - "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", - "dev": true, - "dependencies": { - "@pkgr/utils": "^2.3.1", - "tslib": "^2.5.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/table-layout": { - "version": "1.0.2", - "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", - "dependencies": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/table-layout/node_modules/array-back": { - "version": "4.0.2", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/table-layout/node_modules/typical": { - "version": "5.2.0", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/tar-fs": { - "version": "2.1.1", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/telnet-client": { - "version": "1.2.8", - "integrity": "sha512-W+w4k3QAmULVNhBVT2Fei369kGZCh/TH25M7caJAXW+hLxwoQRuw0di3cX4l0S9fgH3Mvq7u+IFMoBDpEw/eIg==", - "dependencies": { - "bluebird": "^3.5.4" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/kozjak" - } - }, - "node_modules/temp": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha512-jtnWJs6B1cZlHs9wPG7BrowKxZw/rf6+UpGAkr8AaYmiTyTO7zQlLoST8zx/8TcUPnZmeBoB+H8ARuHZaSijVw==", - "engines": [ - "node >=0.8.0" - ], - "dependencies": { - "os-tmpdir": "^1.0.0", - "rimraf": "~2.2.6" - } - }, - "node_modules/temp-dir": { - "version": "1.0.0", - "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/temp/node_modules/rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==", - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/tempfile": { - "version": "2.0.0", - "integrity": "sha512-ZOn6nJUgvgC09+doCEF3oB+r3ag7kUvlsXEGX069QRD60p+P3uP7XG9N2/at+EyIRGSN//ZY3LyEotA1YpmjuA==", - "dependencies": { - "temp-dir": "^1.0.0", - "uuid": "^3.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.0.tgz", - "integrity": "sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/through2/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/through2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/tiny-secp256k1": { - "version": "1.1.6", - "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", - "hasInstallScript": true, - "dependencies": { - "bindings": "^1.3.0", - "bn.js": "^4.11.8", - "create-hmac": "^1.1.7", - "elliptic": "^6.4.0", - "nan": "^2.13.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/titleize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", - "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/trace-event-lib": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/trace-event-lib/-/trace-event-lib-1.3.1.tgz", - "integrity": "sha512-RO/TD5E9RNqU6MhOfi/njFWKYhrzOJCpRXlEQHgXwM+6boLSrQnOZ9xbHwOXzC+Luyixc7LNNSiTsqTVeF7I1g==", - "dependencies": { - "browser-process-hrtime": "^1.0.0", - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/truncate-utf8-bytes": { - "version": "1.0.2", - "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", - "dependencies": { - "utf8-byte-length": "^1.0.1" - } - }, - "node_modules/ts-api-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", - "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", - "dev": true, - "engines": { - "node": ">=16.13.0" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/ts-jest": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", - "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/ts-jest/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/ts-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ts-jest/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ts-jest/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ts-jest/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/ts-jest/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-jest/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "dependencies": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/ts-jest/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-jest/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/ts-jest/node_modules/yargs-parser": { - "version": "21.1.1", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/tsconfig-paths": { - "version": "3.14.1", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typeforce": { - "version": "1.18.0", - "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" - }, - "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typical": { - "version": "4.0.0", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "deprecated": "support for ECMAScript is superseded by `uglify-js` as of v3.13.0", - "dependencies": { - "commander": "~2.13.0", - "source-map": "~0.6.1" - }, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/uglify-es/node_modules/commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "engines": { - "node": ">=4" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.10", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated" - }, - "node_modules/url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", - "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.11.0" - } - }, - "node_modules/url-join": { - "version": "4.0.1", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" - }, - "node_modules/url-parse": { - "version": "1.5.10", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, - "node_modules/use": { - "version": "3.1.1", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.2.0", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/utf8": { - "version": "3.0.0", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" - }, - "node_modules/utf8-byte-length": { - "version": "1.0.4", - "integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==" - }, - "node_modules/util": { - "version": "0.12.5", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "3.4.0", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/v8-to-istanbul": { - "version": "8.1.1", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.4", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/varuint-bitcoin": { - "version": "1.1.2", - "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", - "dependencies": { - "safe-buffer": "^5.1.1" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vlq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", - "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==" - }, - "node_modules/walker": { - "version": "1.0.8", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/warn-once": { - "version": "0.1.1", - "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==" - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-fetch": { - "version": "3.6.2", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "1.3.1", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==" - }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wif": { - "version": "2.0.6", - "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", - "dependencies": { - "bs58check": "<3.0.0" - } - }, - "node_modules/wordwrapjs": { - "version": "4.0.1", - "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", - "dependencies": { - "reduce-flatten": "^2.0.0", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/wordwrapjs/node_modules/typical": { - "version": "5.2.0", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.9", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "node_modules/yargs": { - "version": "16.2.0", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.3.0", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true - }, - "@ampproject/remapping": { - "version": "2.2.0", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@apocentre/alias-sampling": { - "version": "0.5.3", - "integrity": "sha512-7UDWIIF9hIeJqfKXkNIzkVandlwLf1FWTSdrb9iXvOP8oF544JRXQjCbiTmCv2c9n44n/FIWtehhBfNuAx2CZA==" - }, - "@babel/code-frame": { - "version": "7.18.6", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/compat-data": { - "version": "7.21.0", - "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==" - }, - "@babel/core": { - "version": "7.21.0", - "integrity": "sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==", - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.0", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.21.0", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.0", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" - } - }, - "@babel/eslint-parser": { - "version": "7.19.1", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", - "dev": true, - "requires": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - } - }, - "@babel/generator": { - "version": "7.21.1", - "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", - "requires": { - "@babel/types": "^7.21.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "requires": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.20.7", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", - "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.21.0", - "integrity": "sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-member-expression-to-functions": "^7.21.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/helper-split-export-declaration": "^7.18.6" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.21.0", - "integrity": "sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.3.1" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "requires": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-function-name": { - "version": "7.21.0", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", - "requires": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.21.0", - "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==", - "requires": { - "@babel/types": "^7.21.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.21.2", - "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.20.2", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==" - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-replace-supers": { - "version": "7.20.7", - "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" - } - }, - "@babel/helper-simple-access": { - "version": "7.20.2", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "requires": { - "@babel/types": "^7.20.2" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", - "requires": { - "@babel/types": "^7.20.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.19.4", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" - }, - "@babel/helper-validator-option": { - "version": "7.21.0", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==" - }, - "@babel/helper-wrap-function": { - "version": "7.20.5", - "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", - "requires": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" - } - }, - "@babel/helpers": { - "version": "7.21.0", - "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", - "requires": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" - } - }, - "@babel/highlight": { - "version": "7.18.6", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.21.2", - "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==" - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.20.7", - "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.7" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.21.0", - "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-default-from": { - "version": "7.18.10", - "integrity": "sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-default-from": "^7.18.6" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", - "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0", - "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-default-from": { - "version": "7.18.6", - "integrity": "sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-flow": { - "version": "7.18.6", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.20.7", - "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.20.7", - "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.21.0", - "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.21.0", - "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.20.7", - "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/template": "^7.20.7" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.20.7", - "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-flow-strip-types": { - "version": "7.21.0", - "integrity": "sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-flow": "^7.18.6" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.21.0", - "integrity": "sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.18.9", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "requires": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.18.9", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.20.11", - "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", - "requires": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.21.2", - "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==", - "requires": { - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-simple-access": "^7.20.2" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.20.11", - "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", - "requires": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-identifier": "^7.19.1" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.20.5", - "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.18.6", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-object-assign": { - "version": "7.18.6", - "integrity": "sha512-mQisZ3JfqWh2gVXvfqYCAAyRs6+7oev+myBsTwW5RnPhYXOTuCEw2oe3YgxlXMViXUS53lG8koulI7mJ+8JE+A==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.18.6", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.20.7", - "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.21.0", - "integrity": "sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.21.0" - } - }, - "@babel/plugin-transform-react-jsx-self": { - "version": "7.21.0", - "integrity": "sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.19.6", - "integrity": "sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.20.5", - "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "regenerator-transform": "^0.15.1" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.21.0", - "integrity": "sha512-ReY6pxwSzEU0b3r2/T/VhqMKg/AkceBT19X0UptA3/tYi5Pe2eXgEUH+NNMC5nok6c6XQz5tyVTUpuezRfSMSg==", - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.20.7", - "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.21.0", - "integrity": "sha512-xo///XTPp3mDzTtrqXoBlK9eiAYW3wv9JXglcn/u1bi60RW11dEUxIgA8cbnDhutS1zacjMRmAwxE0gMklLnZg==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-typescript": "^7.20.0" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/preset-env": { - "version": "7.20.2", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", - "requires": { - "@babel/compat-data": "^7.20.1", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.20.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.20.2", - "@babel/plugin-transform-classes": "^7.20.2", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.20.2", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.19.6", - "@babel/plugin-transform-modules-commonjs": "^7.19.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.6", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.20.1", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - } - }, - "@babel/preset-flow": { - "version": "7.18.6", - "integrity": "sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-flow-strip-types": "^7.18.6" - } - }, - "@babel/preset-modules": { - "version": "0.1.5", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-typescript": { - "version": "7.21.0", - "integrity": "sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg==", - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.21.0", - "@babel/plugin-transform-typescript": "^7.21.0" - } - }, - "@babel/register": { - "version": "7.21.0", - "integrity": "sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw==", - "requires": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.5", - "source-map-support": "^0.5.16" - }, - "dependencies": { - "make-dir": { - "version": "2.1.0", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "source-map-support": { - "version": "0.5.21", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "@babel/regjsgen": { - "version": "0.8.0", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" - }, - "@babel/runtime": { - "version": "7.21.0", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", - "requires": { - "regenerator-runtime": "^0.13.11" - } - }, - "@babel/template": { - "version": "7.20.7", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" - } - }, - "@babel/traverse": { - "version": "7.21.2", - "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==", - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.1", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.2", - "@babel/types": "^7.21.2", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.21.2", - "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", - "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@bugsnag/core": { - "version": "7.19.0", - "integrity": "sha512-2KGwdaLD9PhR7Wk7xPi3jGuGsKTatc/28U4TOZIDU3CgC2QhGjubwiXSECel5gwxhZ3jACKcMKSV2ovHhv1NrA==", - "requires": { - "@bugsnag/cuid": "^3.0.0", - "@bugsnag/safe-json-stringify": "^6.0.0", - "error-stack-parser": "^2.0.3", - "iserror": "0.0.2", - "stack-generator": "^2.0.3" - } - }, - "@bugsnag/cuid": { - "version": "3.0.2", - "integrity": "sha512-cIwzC93r3PQ/INeuwtZwkZIG2K8WWN0rRLZQhu+mr48Ay+i6sEki4GYfTsflse7hZ1BeDWrNb/Q9vgY3B31xHQ==" - }, - "@bugsnag/delivery-react-native": { - "version": "7.19.0", - "integrity": "sha512-Zzl3VOwLDU4KHmf3VweyfNeJcQgL0NzbWG+OCxjCYen093Q4sxNTpWAVBCrYPRjQ2Sq3+D3+YbQg5UUrHL7kig==" - }, - "@bugsnag/plugin-console-breadcrumbs": { - "version": "7.19.0", - "integrity": "sha512-ZHqPAK0WpbvWjj2wwSV8+C8+K9TOyQsfZnRJ7lIadbeUUJORmFRnG0vUHKBvwxMP7bqCj8fOe/S0kKF3dfMMKA==" - }, - "@bugsnag/plugin-network-breadcrumbs": { - "version": "7.19.0", - "integrity": "sha512-Farc0XuUoxv10kJE65zfgZlqujR7TDu8QjwxA4YDxEE41kFM8TAw0CAK15WkQK1UTsNACiiAETZGyU279eB65Q==" - }, - "@bugsnag/plugin-react": { - "version": "7.19.0", - "integrity": "sha512-owC4QXYJWGllMoOPcH5P7sbDIDuFLMCbjGAU6FwH5mBMObSQo+1ViSKImlTJQUFXATM8ySISTBVt7w3C6FFHng==" - }, - "@bugsnag/plugin-react-native-client-sync": { - "version": "7.19.0", - "integrity": "sha512-WyK5pZuIzqVrY0h0HimwuODCo9ty9AyDY3q1pmwjrz2y8JTT21nnwUtHybLsp5Rl2oJR4tG06QkWmazgHDkWdA==" - }, - "@bugsnag/plugin-react-native-event-sync": { - "version": "7.19.0", - "integrity": "sha512-OD73WFkDJAq8AheN2Jap+d17M1mPbEBc1Aulz9FCLs//QwlM2IOij8oarB1iF/wgK6FnIgLFEBPTZpGHuZUsyQ==" - }, - "@bugsnag/plugin-react-native-global-error-handler": { - "version": "7.19.0", - "integrity": "sha512-zf+KIHqGEAs2ekAzJCTS0rM1nKrmpIfznBhn72xZJwyfYrh0wbvjZjClDEwxDZ24uNVUUHrIymzdqxpHqVb0lg==" - }, - "@bugsnag/plugin-react-native-hermes": { - "version": "7.19.0", - "integrity": "sha512-6SGTSR6NMS2t8j02ZQ6FlA+K/nKkZqvGA+8A7WS/0M8HAShzyoMpZH10kGrU2dcCaiEtmD2T6OGBSbpF+385Dg==" - }, - "@bugsnag/plugin-react-native-session": { - "version": "7.19.0", - "integrity": "sha512-PVwsUstedp9wTqJU/IKdCaMFKP2YrqHXoeBtqRTQ7FFyr0K8wsiW7nZP2jM31VS388hZWSWBlHQPA/3LZ49tNQ==" - }, - "@bugsnag/plugin-react-native-unhandled-rejection": { - "version": "7.19.0", - "integrity": "sha512-+XDk0OoeM6MZhBh7kEalbRwFuhCZST6Y1jOostfz0fhrmT4FdgQYi1FWcPNsUTcjqv7M48pOFZNx8yWI0lGaYg==" - }, - "@bugsnag/react-native": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@bugsnag/react-native/-/react-native-7.21.0.tgz", - "integrity": "sha512-NEm6QXY42SjYA3KdgDLTuGcPuIWHrX8/ma5Mza8RM/ELgActJutnty+1CWfXrzJwfXWcUVaYATB9nKITBcBlUg==", - "requires": { - "@bugsnag/core": "^7.19.0", - "@bugsnag/delivery-react-native": "^7.19.0", - "@bugsnag/plugin-console-breadcrumbs": "^7.19.0", - "@bugsnag/plugin-network-breadcrumbs": "^7.19.0", - "@bugsnag/plugin-react": "^7.19.0", - "@bugsnag/plugin-react-native-client-sync": "^7.19.0", - "@bugsnag/plugin-react-native-event-sync": "^7.19.0", - "@bugsnag/plugin-react-native-global-error-handler": "^7.19.0", - "@bugsnag/plugin-react-native-hermes": "^7.19.0", - "@bugsnag/plugin-react-native-session": "^7.19.0", - "@bugsnag/plugin-react-native-unhandled-rejection": "^7.19.0", - "iserror": "^0.0.2" - } - }, - "@bugsnag/safe-json-stringify": { - "version": "6.0.0", - "integrity": "sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA==" - }, - "@bugsnag/source-maps": { - "version": "2.3.1", - "integrity": "sha512-9xJTcf5+W7+y1fQBftSOste/3ORi+d5EeCCMdvaHSX69MKQP0lrDiSYpLwX/ErcXrTbvu7nimGGKJP2vBdF7zQ==", - "requires": { - "command-line-args": "^5.1.1", - "command-line-usage": "^6.1.0", - "concat-stream": "^2.0.0", - "consola": "^2.15.0", - "form-data": "^3.0.0", - "glob": "^7.1.6", - "read-pkg-up": "^7.0.1" - } - }, - "@dominicstop/ts-event-emitter": { - "version": "1.1.0", - "integrity": "sha512-CcxmJIvUb1vsFheuGGVSQf4KdPZC44XolpUT34+vlal+LyQoBUOn31pjFET5M9ctOxEpt8xa0M3/2M7uUiAoJw==" - }, - "@egjs/hammerjs": { - "version": "2.0.17", - "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", - "requires": { - "@types/hammerjs": "^2.0.36" - } - }, - "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true - } - } - }, - "@eslint-community/regexpp": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.1.tgz", - "integrity": "sha512-O7x6dMstWLn2ktjcoiNLDkAGG2EjveHL+Vvc+n0fXumkJYAcSqcVYKtwDU+hDZ0uDUsnUagSYaZrOLAYE8un1A==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", - "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@eslint/js": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", - "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", - "dev": true - }, - "@hapi/hoek": { - "version": "9.3.0", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "@hapi/topo": { - "version": "5.1.0", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "27.5.1", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "29.4.3", - "integrity": "sha512-56QvBq60fS4SPZCuM7T+7scNrkGIe7Mr6PVIXUpu48ouvRaWOFqRPV91eifvFM0ay2HmfswXiGf97NGUN5KofQ==", - "dev": true, - "requires": { - "@jest/console": "^29.4.3", - "@jest/reporters": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.4.3", - "jest-config": "^29.4.3", - "jest-haste-map": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-resolve-dependencies": "^29.4.3", - "jest-runner": "^29.4.3", - "jest-runtime": "^29.4.3", - "jest-snapshot": "^29.4.3", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "jest-watcher": "^29.4.3", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0" - } - }, - "@jest/reporters": { - "version": "29.4.3", - "integrity": "sha512-sr2I7BmOjJhyqj9ANC6CTLsL4emMoka7HkQpcoMRlhCbQJjz2zsRzw0BDPiPyEFDXAbxKgGFYuQZiSJ1Y6YoTg==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - } - }, - "@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", - "dev": true, - "requires": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "dependencies": { - "convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - } - } - }, - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true - }, - "jest-resolve": { - "version": "29.4.3", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - } - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "resolve.exports": { - "version": "2.0.0", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "v8-to-istanbul": { - "version": "9.1.0", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - } - }, - "write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - } - } - }, - "@jest/create-cache-key-function": { - "version": "29.4.3", - "integrity": "sha512-AJVFQTTy6jnZAQiAZrdOaTAPzJUrvAE/4IMe+Foav6WPhypFszqg7a4lOTyuzYQEEiT5RSrGYg9IY+/ivxiyXw==", - "requires": { - "@jest/types": "^29.4.3" - }, - "dependencies": { - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/environment": { - "version": "29.4.3", - "integrity": "sha512-dq5S6408IxIa+lr54zeqce+QgI+CJT4nmmA+1yzFgtcsGK8c/EyiUb9XQOgz3BMKrRDfKseeOaxj2eO8LlD3lA==", - "requires": { - "@jest/fake-timers": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/node": "*", - "jest-mock": "^29.4.3" - }, - "dependencies": { - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/expect": { - "version": "29.4.3", - "integrity": "sha512-iktRU/YsxEtumI9zsPctYUk7ptpC+AVLLk1Ax3AsA4g1C+8OOnKDkIQBDHtD5hA/+VtgMd5AWI5gNlcAlt2vxQ==", - "dev": true, - "requires": { - "expect": "^29.4.3", - "jest-snapshot": "^29.4.3" - } - }, - "@jest/expect-utils": { - "version": "29.4.3", - "integrity": "sha512-/6JWbkxHOP8EoS8jeeTd9dTfc9Uawi+43oLKHfp6zzux3U2hqOOVnV3ai4RpDYHOccL6g+5nrxpoc8DmJxtXVQ==", - "dev": true, - "requires": { - "jest-get-type": "^29.4.3" - } - }, - "@jest/fake-timers": { - "version": "29.4.3", - "integrity": "sha512-4Hote2MGcCTWSD2gwl0dwbCpBRHhE6olYEuTj8FMowdg3oQWNKr2YuxenPQYZ7+PfqPY1k98wKDU4Z+Hvd4Tiw==", - "requires": { - "@jest/types": "^29.4.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.4.3", - "jest-mock": "^29.4.3", - "jest-util": "^29.4.3" - }, - "dependencies": { - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/globals": { - "version": "29.4.3", - "integrity": "sha512-8BQ/5EzfOLG7AaMcDh7yFCbfRLtsc+09E1RQmRBI4D6QQk4m6NSK/MXo+3bJrBN0yU8A2/VIcqhvsOLFmziioA==", - "dev": true, - "requires": { - "@jest/environment": "^29.4.3", - "@jest/expect": "^29.4.3", - "@jest/types": "^29.4.3", - "jest-mock": "^29.4.3" - }, - "dependencies": { - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/reporters": { - "version": "27.5.1", - "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/schemas": { - "version": "29.4.3", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", - "requires": { - "@sinclair/typebox": "^0.25.16" - } - }, - "@jest/source-map": { - "version": "29.4.3", - "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "27.5.1", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", - "dev": true, - "requires": { - "@jest/console": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "29.4.3", - "integrity": "sha512-yi/t2nES4GB4G0mjLc0RInCq/cNr9dNwJxcGg8sslajua5Kb4kmozAc+qPLzplhBgfw1vLItbjyHzUN92UXicw==", - "dev": true, - "requires": { - "@jest/test-result": "^29.4.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0" - } - }, - "@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", - "dev": true, - "requires": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/transform": { - "version": "27.5.1", - "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.5.1", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-util": "^27.5.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/types": { - "version": "27.5.1", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" - }, - "@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.17", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "@keystonehq/bc-ur-registry": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@keystonehq/bc-ur-registry/-/bc-ur-registry-0.6.3.tgz", - "integrity": "sha512-xwOYrRgqfV5wANpHjmO1ilB2bloY2kMxf8xiZ4CG0U5Zkfwa/9wUaqN60xFaG862w/6wnMOHvLDxDm47xNSDlw==", - "requires": { - "@ngraveio/bc-ur": "^1.1.5", - "bs58check": "^2.1.2", - "tslib": "^2.3.0" - } - }, - "@ngraveio/bc-ur": { - "version": "1.1.6", - "integrity": "sha512-G+2XgjXde2IOcEQeCwR250aS43/Swi7gw0FuETgJy2c3HqF8f88SXDMsIGgJlZ8jXd0GeHR4aX0MfjXf523UZg==", - "requires": { - "@apocentre/alias-sampling": "^0.5.3", - "assert": "^2.0.0", - "bignumber.js": "^9.0.1", - "cbor-sync": "^1.0.4", - "crc": "^3.8.0", - "jsbi": "^3.1.5", - "sha.js": "^2.4.11" - } - }, - "@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "requires": { - "eslint-scope": "5.1.1" - } - }, - "@noble/hashes": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz", - "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==" - }, - "@noble/secp256k1": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", - "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==" - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@pkgr/utils": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", - "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "fast-glob": "^3.3.0", - "is-glob": "^4.0.3", - "open": "^9.1.0", - "picocolors": "^1.0.0", - "tslib": "^2.6.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "open": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", - "dev": true, - "requires": { - "default-browser": "^4.0.0", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "@react-native-async-storage/async-storage": { - "version": "1.19.3", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.19.3.tgz", - "integrity": "sha512-CwGfoHCWdPOTPS+2fW6YRE1fFBpT9++ahLEroX5hkgwyoQ+TkmjOaUxixdEIoVua9Pz5EF2pGOIJzqOTMWfBlA==", - "requires": { - "merge-options": "^3.0.4" - } - }, - "@react-native-clipboard/clipboard": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.11.2.tgz", - "integrity": "sha512-bHyZVW62TuleiZsXNHS1Pv16fWc0fh8O9WvBzl4h2fykqZRW9a+Pv/RGTH56E3X2PqzHP38K5go8zmCZUoIsoQ==" - }, - "@react-native-community/cli": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-10.2.4.tgz", - "integrity": "sha512-E9BUDHfLEsnjkjeJqECuCjl4E/1Ox9Nl6hkQBhEqjZm4AaQxgU7M6AyFfOgaXn5v3am16/R4ZOUTrJnGJWS3GA==", - "requires": { - "@react-native-community/cli-clean": "^10.1.1", - "@react-native-community/cli-config": "^10.1.1", - "@react-native-community/cli-debugger-ui": "^10.0.0", - "@react-native-community/cli-doctor": "^10.2.4", - "@react-native-community/cli-hermes": "^10.2.0", - "@react-native-community/cli-plugin-metro": "^10.2.3", - "@react-native-community/cli-server-api": "^10.1.1", - "@react-native-community/cli-tools": "^10.1.1", - "@react-native-community/cli-types": "^10.0.0", - "chalk": "^4.1.2", - "commander": "^9.4.1", - "execa": "^1.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "prompts": "^2.4.0", - "semver": "^6.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==" - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-clean": { - "version": "10.1.1", - "integrity": "sha512-iNsrjzjIRv9yb5y309SWJ8NDHdwYtnCpmxZouQDyOljUdC9MwdZ4ChbtA4rwQyAwgOVfS9F/j56ML3Cslmvrxg==", - "requires": { - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "prompts": "^2.4.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "cross-spawn": { - "version": "6.0.5", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "is-stream": { - "version": "1.1.0", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" - }, - "npm-run-path": { - "version": "2.0.2", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" - }, - "semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-config": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-10.1.1.tgz", - "integrity": "sha512-p4mHrjC+s/ayiNVG6T35GdEGdP6TuyBUg5plVGRJfTl8WT6LBfLYLk+fz/iETrEZ/YkhQIsQcEUQC47MqLNHog==", - "requires": { - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "cosmiconfig": "^5.1.0", - "deepmerge": "^3.2.0", - "glob": "^7.1.3", - "joi": "^17.2.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "deepmerge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", - "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-debugger-ui": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-10.0.0.tgz", - "integrity": "sha512-8UKLcvpSNxnUTRy8CkCl27GGLqZunQ9ncGYhSrWyKrU9SWBJJGeZwi2k2KaoJi5FvF2+cD0t8z8cU6lsq2ZZmA==", - "requires": { - "serve-static": "^1.13.1" - } - }, - "@react-native-community/cli-doctor": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-10.2.4.tgz", - "integrity": "sha512-hEtgAqSyIASByhoZlv7WVvdoW4NBdn8vJh/X+dQBRBEXyZk1741/+CtiazwKkuliEhl7cdg4Mg99zgRLCXKAzg==", - "requires": { - "@react-native-community/cli-config": "^10.1.1", - "@react-native-community/cli-platform-ios": "^10.2.4", - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "envinfo": "^7.7.2", - "execa": "^1.0.0", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "prompts": "^2.4.0", - "semver": "^6.3.0", - "strip-ansi": "^5.2.0", - "sudo-prompt": "^9.0.0", - "wcwidth": "^1.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-hermes": { - "version": "10.2.0", - "integrity": "sha512-urfmvNeR8IiO/Sd92UU3xPO+/qI2lwCWQnxOkWaU/i2EITFekE47MD6MZrfVulRVYRi5cuaFqKZO/ccOdOB/vQ==", - "requires": { - "@react-native-community/cli-platform-android": "^10.2.0", - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-platform-android": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-10.2.0.tgz", - "integrity": "sha512-CBenYwGxwFdObZTn1lgxWtMGA5ms2G/ALQhkS+XTAD7KHDrCxFF9yT/fnAjFZKM6vX/1TqGI1RflruXih3kAhw==", - "requires": { - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "glob": "^7.1.3", - "logkitty": "^0.7.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-platform-ios": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-10.2.4.tgz", - "integrity": "sha512-/6K+jeRhcGojFIJMWMXV2eY5n/In+YUzBr/DKWQOeHBOHkESRNheG310xSAIjgB46YniSSUKhSyeuhalTbm9OQ==", - "requires": { - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "fast-xml-parser": "^4.0.12", - "glob": "^7.1.3", - "ora": "^5.4.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-plugin-metro": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-10.2.3.tgz", - "integrity": "sha512-jHi2oDuTePmW4NEyVT8JEGNlIYcnFXCSV2ZMp4rnDrUk4TzzyvS3IMvDlESEmG8Kry8rvP0KSUx/hTpy37Sbkw==", - "requires": { - "@react-native-community/cli-server-api": "^10.1.1", - "@react-native-community/cli-tools": "^10.1.1", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "metro": "0.73.10", - "metro-config": "0.73.10", - "metro-core": "0.73.10", - "metro-react-native-babel-transformer": "0.73.10", - "metro-resolver": "0.73.10", - "metro-runtime": "0.73.10", - "readline": "^1.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-server-api": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-10.1.1.tgz", - "integrity": "sha512-NZDo/wh4zlm8as31UEBno2bui8+ufzsZV+KN7QjEJWEM0levzBtxaD+4je0OpfhRIIkhaRm2gl/vVf7OYAzg4g==", - "requires": { - "@react-native-community/cli-debugger-ui": "^10.0.0", - "@react-native-community/cli-tools": "^10.1.1", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.0", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^7.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "15.0.15", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz", - "integrity": "sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-tools": { - "version": "10.1.1", - "integrity": "sha512-+FlwOnZBV+ailEzXjcD8afY2ogFEBeHOw/8+XXzMgPaquU2Zly9B+8W089tnnohO3yfiQiZqkQlElP423MY74g==", - "requires": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "find-up": "^5.0.0", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^6.3.0", - "shell-quote": "^1.7.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "find-up": { - "version": "5.0.0", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "locate-path": { - "version": "6.0.0", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-locate": { - "version": "5.0.0", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "requires": { - "p-limit": "^3.0.2" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-types": { - "version": "10.0.0", - "integrity": "sha512-31oUM6/rFBZQfSmDQsT1DX/5fjqfxg7sf2u8kTPJK7rXVya5SRpAMaCXsPAG0omsmJxXt+J9HxUi3Ic+5Ux5Iw==", - "requires": { - "joi": "^17.2.1" - } - }, - "@react-native-community/eslint-config": { - "version": "3.2.0", - "integrity": "sha512-ZjGvoeiBtCbd506hQqwjKmkWPgynGUoJspG8/MuV/EfKnkjCtBmeJvq2n+sWbWEvL9LWXDp2GJmPzmvU5RSvKQ==", - "dev": true, - "requires": { - "@babel/core": "^7.14.0", - "@babel/eslint-parser": "^7.18.2", - "@react-native-community/eslint-plugin": "^1.1.0", - "@typescript-eslint/eslint-plugin": "^5.30.5", - "@typescript-eslint/parser": "^5.30.5", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-ft-flow": "^2.0.1", - "eslint-plugin-jest": "^26.5.3", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-native": "^4.0.0" - }, - "dependencies": { - "@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "dev": true, - "requires": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@react-native-community/eslint-plugin": { - "version": "1.3.0", - "integrity": "sha512-+zDZ20NUnSWghj7Ku5aFphMzuM9JulqCW+aPXT6IfIXFbb8tzYTTOSeRFOtuekJ99ibW2fUCSsjuKNlwDIbHFg==", - "dev": true - }, - "@react-native-community/push-notification-ios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@react-native-community/push-notification-ios/-/push-notification-ios-1.11.0.tgz", - "integrity": "sha512-nfkUs8P2FeydOCR4r7BNmtGxAxI22YuGP6RmqWt6c8EEMUpqvIhNKWkRSFF3pHjkgJk2tpRb9wQhbezsqTyBvA==", - "requires": { - "invariant": "^2.2.4" - } - }, - "@react-native/assets": { - "version": "1.0.0", - "integrity": "sha512-KrwSpS1tKI70wuKl68DwJZYEvXktDHdZMG0k2AXD/rJVSlB23/X2CB2cutVR0HwNMJIal9HOUOBB2rVfa6UGtQ==" - }, - "@react-native/normalize-color": { - "version": "2.1.0", - "integrity": "sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==" - }, - "@react-native/polyfills": { - "version": "2.0.0", - "integrity": "sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ==" - }, - "@react-native/virtualized-lists": { - "version": "0.72.4", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.72.4.tgz", - "integrity": "sha512-2t8WBVACkKEadtsiGYJaYTix575J/5VQJyqnyL7iDIsd3iG7ODjfMDsTGsVyAA2Av/xeVIuVQRUX0ZzV3cucug==", - "dev": true, - "requires": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" - } - }, - "@react-navigation/core": { - "version": "5.16.1", - "integrity": "sha512-3AToC7vPNeSNcHFLd1h71L6u34hfXoRAS1CxF9Fc4uC8uOrVqcNvphpeFbE0O9Bw6Zpl0BnMFl7E5gaL3KGzNA==", - "requires": { - "@react-navigation/routers": "^5.7.4", - "escape-string-regexp": "^4.0.0", - "nanoid": "^3.1.15", - "query-string": "^6.13.6", - "react-is": "^16.13.0" - } - }, - "@react-navigation/drawer": { - "version": "5.12.9", - "integrity": "sha512-SYb2BCEAn+BiEwC6WBfCzs1VlWD+ZdQbxmsim6vo1o+ndPW2e+kiq7FXKRs0vUXhQRZVl2oOB3vBn0c3YCllQg==", - "requires": { - "color": "^3.1.3", - "react-native-iphone-x-helper": "^1.3.0" - } - }, - "@react-navigation/native": { - "version": "5.9.8", - "integrity": "sha512-DNbcDHXQPSFDLn51kkVVJjT3V7jJy2GztNYZe/2bEg29mi5QEcHHcpifjMCtyFKntAOWzKlG88UicIQ17UEghg==", - "requires": { - "@react-navigation/core": "^5.16.1", - "escape-string-regexp": "^4.0.0", - "nanoid": "^3.1.15" - } - }, - "@react-navigation/routers": { - "version": "5.7.4", - "integrity": "sha512-0N202XAqsU/FlE53Nmh6GHyMtGm7g6TeC93mrFAFJOqGRKznT0/ail+cYlU6tNcPA9AHzZu1Modw1eoDINSliQ==", - "requires": { - "nanoid": "^3.1.15" - } - }, - "@remobile/react-native-qrcode-local-image": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-qrcode-local-image.git#31b0113110fbafcf5a5f3ca4183a563550f5c352", - "from": "@remobile/react-native-qrcode-local-image@https://github.com/BlueWallet/react-native-qrcode-local-image" - }, - "@sideway/address": { - "version": "4.1.4", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@sideway/formula": { - "version": "3.0.1", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" - }, - "@sideway/pinpoint": { - "version": "2.0.0", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, - "@sinclair/typebox": { - "version": "0.25.24", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" - }, - "@sinonjs/commons": { - "version": "2.0.0", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "10.0.2", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", - "requires": { - "@sinonjs/commons": "^2.0.0" - } - }, - "@spsina/bip47": { - "version": "git+ssh://git@github.com/BlueWallet/bip47.git#0a2f02c90350802f2ec93afa4e6c8843be2d687c", - "integrity": "sha512-lsgEpiEMDgpiYOA2kizOwiSS3vjTeLe2VnkOTIGnJ7Eu7Mkgl9dLES7oSLAjY64aQXr0VolqCRciRDc2nAC++w==", - "from": "@spsina/bip47@github:BlueWallet/bip47#0a2f02c90350802f2ec93afa4e6c8843be2d687c", - "requires": { - "bip32": "^3.0.1", - "bip39": "^3.0.4", - "bitcoinjs-lib": "^6.0.1", - "bs58check": "^2.1.1", - "create-hmac": "^1.1.7", - "ecpair": "^2.0.1", - "tiny-secp256k1": "^1.1.6" - } - }, - "@tsconfig/react-native": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/react-native/-/react-native-3.0.2.tgz", - "integrity": "sha512-F7IoHEqf741lut4Z2K+IkWQRvXAhBiZMeY5L7BysG7Z2Z3MlIyFR+AagD8jQ/CqC1vowGnRwfLjeuwIpaeoJxA==", - "dev": true - }, - "@types/async": { - "version": "3.2.18", - "integrity": "sha512-/IsuXp3B9R//uRLi40VlIYoMp7OzhkunPe2fDu7jGfQXI9y3CDCx6FC4juRLSqrpmLst3vgsiK536AAGJFl4Ww==" - }, - "@types/babel__core": { - "version": "7.20.0", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.3", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/bn.js": { - "version": "4.11.6", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "requires": { - "@types/node": "*" - } - }, - "@types/bs58check": { - "version": "2.1.0", - "integrity": "sha512-OxsysnJQh82vy9DRbOcw9m2j/WiyqZLn0YBhKxdQ+aCwoHj+tWzyCgpwAkr79IfDXZKxc6h7k89T9pwS78CqTQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/create-hash": { - "version": "1.2.2", - "integrity": "sha512-Fg8/kfMJObbETFU/Tn+Y0jieYewryLrbKwLCEIwPyklZZVY2qB+64KFjhplGSw+cseZosfFXctXO+PyIYD8iZQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/graceful-fs": { - "version": "4.1.6", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/hammerjs": { - "version": "2.0.41", - "integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==" - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "29.4.0", - "integrity": "sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==", - "dev": true, - "requires": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "@types/node": { - "version": "18.14.1", - "integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ==" - }, - "@types/normalize-package-data": { - "version": "2.4.1", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" - }, - "@types/prettier": { - "version": "2.7.2", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, - "@types/prop-types": { - "version": "15.7.5", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "@types/react": { - "version": "18.2.16", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.16.tgz", - "integrity": "sha512-LLFWr12ZhBJ4YVw7neWLe6Pk7Ey5R9OCydfuMsz1L8bZxzaawJj2p06Q8/EFEHDeTBQNFLF62X+CG7B2zIyu0Q==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-native": { - "version": "0.72.0", - "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.72.0.tgz", - "integrity": "sha512-g1PJXUQ0SnYTimfTeN9dRqj8VfzvgJjt/eakEH7+tlm/ZiEPiL9xCool4iKmqalthwtM0/BkGhjwrKnJyg1JDA==", - "dev": true, - "requires": { - "@react-native/virtualized-lists": "^0.72.4", - "@types/react": "*" - } - }, - "@types/react-native-vector-icons": { - "version": "6.4.13", - "integrity": "sha512-1PqFoKuXTSzMHwGMAr+REdYJBQAbe9xrww3ecZR0FsHcD1K+vGS/rxuAriL4rsI6+p69sZQjDzpEVAbDQcjSwA==", - "requires": { - "@types/react": "*", - "@types/react-native": "^0.70" - }, - "dependencies": { - "@types/react-native": { - "version": "0.70.11", - "integrity": "sha512-FobPtzoNPNHugBKMfzs4Li0Q9ei4tgU8SI1M5Ayg7+t5/+noCm2sknI8uwij22wMkcHcefv8RFx4q28nNVJtCQ==", - "requires": { - "@types/react": "*" - } - } - } - }, - "@types/react-test-renderer": { - "version": "18.0.0", - "integrity": "sha512-C7/5FBJ3g3sqUahguGi03O79b8afNeSD6T8/GU50oQrJCU0bVCCGQHaGKUbg2Ce8VQEEqTw8/HiS6lXHHdgkdQ==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/scheduler": { - "version": "0.16.2", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" - }, - "@types/yargs": { - "version": "16.0.5", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" - }, - "@typescript-eslint/eslint-plugin": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.2.0.tgz", - "integrity": "sha512-rClGrMuyS/3j0ETa1Ui7s6GkLhfZGKZL3ZrChLeAiACBE/tRc1wq8SNZESUuluxhLj9FkUefRs2l6bCIArWBiQ==", - "dev": true, - "requires": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.2.0", - "@typescript-eslint/type-utils": "6.2.0", - "@typescript-eslint/utils": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "dependencies": { - "@typescript-eslint/scope-manager": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.2.0.tgz", - "integrity": "sha512-1ZMNVgm5nnHURU8ZSJ3snsHzpFeNK84rdZjluEVBGNu7jDymfqceB3kdIZ6A4xCfEFFhRIB6rF8q/JIqJd2R0Q==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0" - } - }, - "@typescript-eslint/types": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.2.0.tgz", - "integrity": "sha512-1nRRaDlp/XYJQLvkQJG5F3uBTno5SHPT7XVcJ5n1/k2WfNI28nJsvLakxwZRNY5spuatEKO7d5nZWsQpkqXwBA==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.0.tgz", - "integrity": "sha512-Mts6+3HQMSM+LZCglsc2yMIny37IhUgp1Qe8yJUYVyO6rHP7/vN0vajKu3JvHCBIy8TSiKddJ/Zwu80jhnGj1w==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - } - }, - "@typescript-eslint/utils": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.2.0.tgz", - "integrity": "sha512-RCFrC1lXiX1qEZN8LmLrxYRhOkElEsPKTVSNout8DMzf8PeWoQG7Rxz2SadpJa3VSh5oYKGwt7j7X/VRg+Y3OQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.2.0", - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/typescript-estree": "6.2.0", - "semver": "^7.5.4" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.0.tgz", - "integrity": "sha512-QbaYUQVKKo9bgCzpjz45llCfwakyoxHetIy8CAvYCtd16Zu1KrpzNHofwF8kGkpPOxZB2o6kz+0nqH8ZkIzuoQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.2.0", - "eslint-visitor-keys": "^3.4.1" - } - }, - "eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/parser": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.2.0.tgz", - "integrity": "sha512-igVYOqtiK/UsvKAmmloQAruAdUHihsOCvplJpplPZ+3h4aDkC/UKZZNKgB6h93ayuYLuEymU3h8nF1xMRbh37g==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "6.2.0", - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/typescript-estree": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0", - "debug": "^4.3.4" - }, - "dependencies": { - "@typescript-eslint/scope-manager": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.2.0.tgz", - "integrity": "sha512-1ZMNVgm5nnHURU8ZSJ3snsHzpFeNK84rdZjluEVBGNu7jDymfqceB3kdIZ6A4xCfEFFhRIB6rF8q/JIqJd2R0Q==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0" - } - }, - "@typescript-eslint/types": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.2.0.tgz", - "integrity": "sha512-1nRRaDlp/XYJQLvkQJG5F3uBTno5SHPT7XVcJ5n1/k2WfNI28nJsvLakxwZRNY5spuatEKO7d5nZWsQpkqXwBA==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.0.tgz", - "integrity": "sha512-Mts6+3HQMSM+LZCglsc2yMIny37IhUgp1Qe8yJUYVyO6rHP7/vN0vajKu3JvHCBIy8TSiKddJ/Zwu80jhnGj1w==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.0.tgz", - "integrity": "sha512-QbaYUQVKKo9bgCzpjz45llCfwakyoxHetIy8CAvYCtd16Zu1KrpzNHofwF8kGkpPOxZB2o6kz+0nqH8ZkIzuoQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.2.0", - "eslint-visitor-keys": "^3.4.1" - } - }, - "eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.2.0.tgz", - "integrity": "sha512-DnGZuNU2JN3AYwddYIqrVkYW0uUQdv0AY+kz2M25euVNlujcN2u+rJgfJsBFlUEzBB6OQkUqSZPyuTLf2bP5mw==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "6.2.0", - "@typescript-eslint/utils": "6.2.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "dependencies": { - "@typescript-eslint/scope-manager": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.2.0.tgz", - "integrity": "sha512-1ZMNVgm5nnHURU8ZSJ3snsHzpFeNK84rdZjluEVBGNu7jDymfqceB3kdIZ6A4xCfEFFhRIB6rF8q/JIqJd2R0Q==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0" - } - }, - "@typescript-eslint/types": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.2.0.tgz", - "integrity": "sha512-1nRRaDlp/XYJQLvkQJG5F3uBTno5SHPT7XVcJ5n1/k2WfNI28nJsvLakxwZRNY5spuatEKO7d5nZWsQpkqXwBA==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.0.tgz", - "integrity": "sha512-Mts6+3HQMSM+LZCglsc2yMIny37IhUgp1Qe8yJUYVyO6rHP7/vN0vajKu3JvHCBIy8TSiKddJ/Zwu80jhnGj1w==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - } - }, - "@typescript-eslint/utils": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.2.0.tgz", - "integrity": "sha512-RCFrC1lXiX1qEZN8LmLrxYRhOkElEsPKTVSNout8DMzf8PeWoQG7Rxz2SadpJa3VSh5oYKGwt7j7X/VRg+Y3OQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.2.0", - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/typescript-estree": "6.2.0", - "semver": "^7.5.4" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.0.tgz", - "integrity": "sha512-QbaYUQVKKo9bgCzpjz45llCfwakyoxHetIy8CAvYCtd16Zu1KrpzNHofwF8kGkpPOxZB2o6kz+0nqH8ZkIzuoQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.2.0", - "eslint-visitor-keys": "^3.4.1" - } - }, - "eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true - } - } - }, - "@yarnpkg/lockfile": { - "version": "1.1.0", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" - }, - "abort-controller": { - "version": "3.0.0", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "abortcontroller-polyfill": { - "version": "1.7.5", - "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==" - }, - "absolute-path": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz", - "integrity": "sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA==" - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==" - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true - }, - "aez": { - "version": "1.0.1", - "integrity": "sha512-AtZEVcZcOLBAcNevz2e+Zu1zSLuPjtUA6CFY+ie8tF0IshKfcpJ0LiwnTqePOKaNidQQM/MfvtzUFOfAvHz5wQ==", - "requires": { - "blakejs": "^1.1.0", - "safe-buffer": "^5.1.1" - } - }, - "ajv": { - "version": "8.12.0", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "anser": { - "version": "1.4.10", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==" - }, - "ansi-escapes": { - "version": "4.3.2", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-fragments": { - "version": "0.2.1", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "requires": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" - }, - "strip-ansi": { - "version": "5.2.0", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "3.2.1", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "appdirsjs": { - "version": "1.2.7", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==" - }, - "argparse": { - "version": "1.0.10", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-flatten": { - "version": "1.1.0", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" - }, - "arr-union": { - "version": "3.1.0", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==" - }, - "array-back": { - "version": "3.1.0", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==" - }, - "array-includes": { - "version": "3.1.6", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" - } - }, - "array-union": { - "version": "2.1.0", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array.prototype.flat": { - "version": "1.3.1", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.flatmap": { - "version": "1.3.1", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.tosorted": { - "version": "1.1.1", - "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.1.3" - } - }, - "asap": { - "version": "2.0.6", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, - "asn1.js": { - "version": "5.4.1", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "assert": { - "version": "2.0.0", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", - "requires": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" - } - }, - "assign-symbols": { - "version": "1.0.0", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==" - }, - "ast-types": { - "version": "0.14.2", - "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", - "requires": { - "tslib": "^2.0.1" - } - }, - "astral-regex": { - "version": "1.0.0", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==" - }, - "async": { - "version": "3.2.4", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "async-limiter": { - "version": "1.0.1", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, - "asynckit": { - "version": "0.4.0", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "atob": { - "version": "2.1.2", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" - }, - "available-typed-arrays": { - "version": "1.0.5", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" - }, - "babel-jest": { - "version": "29.4.3", - "integrity": "sha512-o45Wyn32svZE+LnMVWv/Z4x0SwtLbh4FyGcYtR20kIWd+rdrDZ9Fzq8Ml3MYLD+mZvEdzCjZsCnYZ2jpJyQ+Nw==", - "dev": true, - "requires": { - "@jest/transform": "^29.4.3", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.4.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "29.4.3", - "integrity": "sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "requires": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - } - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "7.0.0-beta.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", - "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==" - }, - "babel-plugin-transform-flow-enums": { - "version": "0.0.2", - "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", - "requires": { - "@babel/plugin-syntax-flow": "^7.12.1" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-fbjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", - "integrity": "sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==", - "requires": { - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-syntax-class-properties": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-block-scoped-functions": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-for-of": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-member-expression-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-object-super": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-property-literals": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0" - } - }, - "babel-preset-jest": { - "version": "29.4.3", - "integrity": "sha512-gWx6COtSuma6n9bw+8/F+2PCXrIgxV/D1TJFnp6OyBK2cxPWg0K9p/sriNYeifKjpUkMViWQ09DSWtzJQRETsw==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.4.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base": { - "version": "0.11.2", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "base-64": { - "version": "0.1.0", - "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" - }, - "base-x": { - "version": "3.0.9", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "base64-js": { - "version": "1.5.1", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bc-bech32": { - "version": "file:blue_modules/bc-bech32" - }, - "bech32": { - "version": "2.0.0", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" - }, - "big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true - }, - "bigi": { - "version": "1.4.2", - "integrity": "sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw==" - }, - "bignumber.js": { - "version": "9.1.1", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==" - }, - "bindings": { - "version": "1.5.0", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bip174": { - "version": "2.1.0", - "integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==" - }, - "bip21": { - "version": "2.0.3", - "integrity": "sha512-L4ODmASLjsHYAU+TG7xffkYNMvHzAe4mkVX7mcvOUyKAr/MDBPrsRgqUhE8EmKdeEKHk5SYpX1Aexzvm/6WdbQ==", - "requires": { - "qs": "^6.3.0" - } - }, - "bip32": { - "version": "3.0.1", - "integrity": "sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==", - "requires": { - "@types/node": "10.12.18", - "bs58check": "^2.1.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "typeforce": "^1.11.5", - "wif": "^2.0.6" - }, - "dependencies": { - "@types/node": { - "version": "10.12.18", - "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" - } - } - }, - "bip38": { - "version": "git+ssh://git@github.com/BlueWallet/bip38.git#60018f7c61e70584647d724b9b9264a7ebc8182e", - "from": "bip38@github:BlueWallet/bip38", - "requires": { - "bigi": "^1.2.0", - "browserify-aes": "^1.0.1", - "bs58check": "<3.0.0", - "buffer-xor": "^1.0.2", - "create-hash": "^1.1.1", - "ecurve": "^1.0.0", - "safe-buffer": "~5.1.1", - "scryptsy": "^2.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "bip39": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", - "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", - "requires": { - "@noble/hashes": "^1.2.0" - } - }, - "bip66": { - "version": "1.1.5", - "integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "bitcoin-ops": { - "version": "1.4.1", - "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==" - }, - "bitcoinjs-lib": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.1.tgz", - "integrity": "sha512-FYihfgTk29lt1eK2y48OtuarEDUnTprNBW3ctT8yHiOhvmeS3DzAVG6gI0VCvMkydz6UdlXlYNWIPqGD0SUYRQ==", - "requires": { - "@noble/hashes": "^1.2.0", - "bech32": "^2.0.0", - "bip174": "^2.1.0", - "bs58check": "^3.0.1", - "typeforce": "^1.11.3", - "varuint-bitcoin": "^1.1.2" - }, - "dependencies": { - "base-x": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", - "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" - }, - "bs58": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", - "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", - "requires": { - "base-x": "^4.0.0" - } - }, - "bs58check": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", - "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", - "requires": { - "@noble/hashes": "^1.2.0", - "bs58": "^5.0.0" - } - } - } - }, - "bitcoinjs-message": { - "version": "2.2.0", - "integrity": "sha512-103Wy3xg8Y9o+pdhGP4M3/mtQQuUWs6sPuOp1mYphSUoSMHjHTlkj32K4zxU8qMH0Ckv23emfkGlFWtoWZ7YFA==", - "requires": { - "bech32": "^1.1.3", - "bs58check": "^2.1.2", - "buffer-equals": "^1.0.3", - "create-hash": "^1.1.2", - "secp256k1": "^3.0.1", - "varuint-bitcoin": "^1.0.1" - }, - "dependencies": { - "bech32": { - "version": "1.1.4", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - } - } - }, - "bl": { - "version": "4.1.0", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, - "blakejs": { - "version": "1.2.1", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" - }, - "bluebird": { - "version": "3.7.2", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "bn.js": { - "version": "4.12.0", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "bolt11": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/bolt11/-/bolt11-1.4.1.tgz", - "integrity": "sha512-jR0Y+MO+CK2at1Cg5mltLJ+6tdOwNKoTS/DJOBDdzVkQ+R9D6UgZMayTWOsuzY7OgV1gEqlyT5Tzk6t6r4XcNQ==", - "requires": { - "@types/bn.js": "^4.11.3", - "bech32": "^1.1.2", - "bitcoinjs-lib": "^6.0.0", - "bn.js": "^4.11.8", - "create-hash": "^1.2.0", - "lodash": "^4.17.11", - "safe-buffer": "^5.1.1", - "secp256k1": "^4.0.2" - }, - "dependencies": { - "bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" - }, - "secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", - "requires": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - } - } - } - }, - "boolbase": { - "version": "1.0.0", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, - "boolean": { - "version": "3.2.0", - "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==" - }, - "bplist-parser": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", - "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", - "dev": true, - "requires": { - "big-integer": "^1.6.44" - } - }, - "brace-expansion": { - "version": "1.1.11", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { - "fill-range": "^7.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" - }, - "browserify-aes": { - "version": "1.2.0", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.1.0", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "requires": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - }, - "dependencies": { - "bn.js": { - "version": "5.2.1", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - } - } - }, - "browserify-sign": { - "version": "4.2.1", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "bn.js": { - "version": "5.2.1", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - } - } - }, - "browserslist": { - "version": "4.21.5", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "requires": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - } - }, - "bs-logger": { - "version": "0.2.6", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bs58": { - "version": "4.0.1", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "requires": { - "base-x": "^3.0.2" - } - }, - "bs58check": { - "version": "2.1.2", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "requires": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, - "bser": { - "version": "2.1.1", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "requires": { - "node-int64": "^0.4.0" - } - }, - "bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "requires": { - "buffer": "^5.6.0" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, - "buffer": { - "version": "6.0.3", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-equals": { - "version": "1.0.4", - "integrity": "sha512-99MsCq0j5+RhubVEtKQgKaD6EM+UP3xJgIvQqwJ3SOLDUekzxMX1ylXBng+Wa2sh7mGT0W6RUly8ojjr1Tt6nA==" - }, - "buffer-from": { - "version": "1.1.2", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "buffer-reverse": { - "version": "1.0.1", - "integrity": "sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg==" - }, - "buffer-xor": { - "version": "1.0.3", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" - }, - "builtins": { - "version": "5.0.1", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", - "dev": true, - "requires": { - "semver": "^7.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "bundle-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", - "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", - "dev": true, - "requires": { - "run-applescript": "^5.0.0" - } - }, - "bunyan": { - "version": "1.8.15", - "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", - "requires": { - "dtrace-provider": "~0.8", - "moment": "^2.19.3", - "mv": "~2", - "safe-json-stringify": "~1" - } - }, - "bunyan-debug-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bunyan-debug-stream/-/bunyan-debug-stream-3.1.0.tgz", - "integrity": "sha512-VaFYbDVdiSn3ZpdozrjZ8mFpxHXl26t11C1DKRQtbo0EgffqeFNrRLOGIESKVeGEvVu4qMxMSSxzNlSw7oTj7w==", - "requires": { - "chalk": "^4.1.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==" - }, - "cache-base": { - "version": "1.0.1", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "caf": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/caf/-/caf-15.0.1.tgz", - "integrity": "sha512-Xp/IK6vMwujxWZXra7djdYzPdPnEQKa7Mudu2wZgDQ3TJry1I0TgtjEgwZHpoBcMp68j4fb0/FZ1SJyMEgJrXQ==" - }, - "call-bind": { - "version": "1.0.2", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "requires": { - "callsites": "^2.0.0" - }, - "dependencies": { - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==" - } - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsites": { - "version": "3.1.0", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "caniuse-lite": { - "version": "1.0.30001457", - "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==" - }, - "caseless": { - "version": "0.12.0", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" - }, - "cbor-sync": { - "version": "1.0.4", - "integrity": "sha512-GWlXN4wiz0vdWWXBU71Dvc1q3aBo0HytqwAZnXF1wOwjqNnDWA1vZ1gDMFLlqohak31VQzmhiYfiCX5QSSfagA==" - }, - "chalk": { - "version": "2.4.2", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "1.0.5", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - } - } - }, - "char-regex": { - "version": "1.0.2", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "child-process-promise": { - "version": "2.2.1", - "integrity": "sha512-Fi4aNdqBsr0mv+jgWxcZ/7rAIC2mgihrptyVI4foh/rrjY/3BNjfP9+oaiFx/fzim+1ZyCNBae0DlyfQhSugog==", - "requires": { - "cross-spawn": "^4.0.2", - "node-version": "^1.0.0", - "promise-polyfill": "^6.0.1" - } - }, - "ci-info": { - "version": "3.8.0", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==" - }, - "cipher-base": { - "version": "1.0.4", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "cjs-module-lexer": { - "version": "1.2.2", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "cli-cursor": { - "version": "3.1.0", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-spinners": { - "version": "2.7.0", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==" - }, - "cliui": { - "version": "7.0.4", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clone": { - "version": "1.0.4", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" - }, - "clone-deep": { - "version": "4.0.1", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "co": { - "version": "4.6.0", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "coinselect": { - "version": "3.1.13", - "integrity": "sha512-iJOrKH/7N9gX0jRkxgOHuGjvzvoxUMSeylDhH1sHn+CjLjdin5R0Hz2WEBu/jrZV5OrHcm+6DMzxwu9zb5mSZg==" - }, - "collect-v8-coverage": { - "version": "1.0.1", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color": { - "version": "3.2.1", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "requires": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "color-convert": { - "version": "1.9.3", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "color-string": { - "version": "1.9.1", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "colorette": { - "version": "1.4.0", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" - }, - "combined-stream": { - "version": "1.0.8", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" - }, - "command-line-args": { - "version": "5.2.1", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", - "requires": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" - } - }, - "command-line-usage": { - "version": "6.1.3", - "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", - "requires": { - "array-back": "^4.0.2", - "chalk": "^2.4.2", - "table-layout": "^1.0.2", - "typical": "^5.2.0" - }, - "dependencies": { - "array-back": { - "version": "4.0.2", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==" - }, - "typical": { - "version": "5.2.0", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==" - } - } - }, - "commander": { - "version": "2.20.3", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "common-tags": { - "version": "1.8.2", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==" - }, - "commondir": { - "version": "1.0.1", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, - "component-emitter": { - "version": "1.3.0", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "concat-map": { - "version": "0.0.1", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "concat-stream": { - "version": "2.0.0", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "consola": { - "version": "2.15.3", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" - }, - "convert-source-map": { - "version": "1.9.0", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "copy-descriptor": { - "version": "0.1.1", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==" - }, - "core-js-compat": { - "version": "3.28.0", - "integrity": "sha512-myzPgE7QodMg4nnd3K1TDoES/nADRStM8Gpz0D6nhkwbmwEnE0ZGJgoWsvQ722FR8D7xS0n0LV556RcEicjTyg==", - "requires": { - "browserslist": "^4.21.5" - } - }, - "core-util-is": { - "version": "1.0.2", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - }, - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "dependencies": { - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==" - } - } - }, - "crc": { - "version": "3.8.0", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "requires": { - "buffer": "^5.1.0" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, - "create-ecdh": { - "version": "4.0.4", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "create-hash": { - "version": "1.2.0", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "cross-fetch": { - "version": "3.1.5", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "requires": { - "node-fetch": "2.6.7" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.7", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - } - } - }, - "cross-spawn": { - "version": "4.0.2", - "integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==", - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.5", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "yallist": { - "version": "2.1.2", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" - } - } - }, - "crypto-js": { - "version": "4.1.1", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" - }, - "css-select": { - "version": "5.1.0", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - } - }, - "css-tree": { - "version": "1.1.3", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - } - }, - "css-what": { - "version": "6.1.0", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" - }, - "csstype": { - "version": "3.1.1", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" - }, - "dayjs": { - "version": "1.11.9", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", - "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" - }, - "debug": { - "version": "4.3.4", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "4.0.0", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==" - }, - "decode-uri-component": { - "version": "0.2.2", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==" - }, - "decompress-response": { - "version": "6.0.0", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - } - }, - "dedent": { - "version": "0.7.0", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "deep-equal": { - "version": "1.1.1", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.3.0", - "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==" - }, - "default-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", - "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", - "dev": true, - "requires": { - "bundle-name": "^3.0.0", - "default-browser-id": "^3.0.0", - "execa": "^7.1.1", - "titleize": "^3.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "execa": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", - "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - } - }, - "human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true - }, - "is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true - }, - "mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true - }, - "npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "requires": { - "path-key": "^4.0.0" - }, - "dependencies": { - "path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true - } - } - }, - "onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "requires": { - "mimic-fn": "^4.0.0" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "default-browser-id": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", - "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", - "dev": true, - "requires": { - "bplist-parser": "^0.2.0", - "untildify": "^4.0.0" - } - }, - "defaults": { - "version": "1.0.4", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "requires": { - "clone": "^1.0.2" - } - }, - "define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true - }, - "define-properties": { - "version": "1.2.0", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "define-property": { - "version": "2.0.2", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "deprecated-react-native-prop-types": { - "version": "3.0.1", - "integrity": "sha512-J0jCJcsk4hMlIb7xwOZKLfMpuJn6l8UtrPEzzQV5ewz5gvKNYakhBuq9h2rWX7YwHHJZFhU5W8ye7dB9oN8VcQ==", - "requires": { - "@react-native/normalize-color": "*", - "invariant": "*", - "prop-types": "*" - } - }, - "des.js": { - "version": "1.0.1", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "detect-libc": { - "version": "2.0.1", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" - }, - "detect-newline": { - "version": "3.1.0", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "detox": { - "version": "20.11.4", - "resolved": "https://registry.npmjs.org/detox/-/detox-20.11.4.tgz", - "integrity": "sha512-P48KAtK8qIDOxJKUl4q/syPkuHz67kAeFlNodBZg5aO4hJiH+RsbEkQfJSYkTCeZV800EcmUQwZK2M5amLoYaw==", - "requires": { - "ajv": "^8.6.3", - "bunyan": "^1.8.12", - "bunyan-debug-stream": "^3.1.0", - "caf": "^15.0.1", - "chalk": "^4.0.0", - "child-process-promise": "^2.2.0", - "execa": "^5.1.1", - "find-up": "^5.0.0", - "fs-extra": "^11.0.0", - "funpermaproxy": "^1.1.0", - "glob": "^8.0.3", - "ini": "^1.3.4", - "json-cycle": "^1.3.0", - "lodash": "^4.17.11", - "multi-sort-stream": "^1.0.3", - "multipipe": "^4.0.0", - "node-ipc": "9.2.1", - "proper-lockfile": "^3.0.2", - "resolve-from": "^5.0.0", - "sanitize-filename": "^1.6.1", - "semver": "^7.0.0", - "serialize-error": "^8.0.1", - "shell-quote": "^1.7.2", - "signal-exit": "^3.0.3", - "stream-json": "^1.7.4", - "strip-ansi": "^6.0.1", - "telnet-client": "1.2.8", - "tempfile": "^2.0.0", - "trace-event-lib": "^1.3.1", - "which": "^1.3.1", - "ws": "^7.0.0", - "yargs": "^17.0.0", - "yargs-parser": "^21.0.0", - "yargs-unparser": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "requires": { - "p-locate": "^5.0.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "requires": { - "p-limit": "^3.0.2" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" - } - } - }, - "diff-sequences": { - "version": "29.4.3", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", - "dev": true - }, - "diffie-hellman": { - "version": "5.0.3", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "dijkstrajs": { - "version": "1.0.2", - "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==" - }, - "dir-glob": { - "version": "3.0.1", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serializer": { - "version": "2.0.0", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - } - }, - "domelementtype": { - "version": "2.3.0", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" - }, - "domhandler": { - "version": "5.0.3", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "requires": { - "domelementtype": "^2.3.0" - } - }, - "domutils": { - "version": "3.0.1", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", - "requires": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.1" - } - }, - "drbg.js": { - "version": "1.0.1", - "integrity": "sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g==", - "requires": { - "browserify-aes": "^1.0.6", - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4" - } - }, - "dtrace-provider": { - "version": "0.8.8", - "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", - "optional": true, - "requires": { - "nan": "^2.14.0" - } - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "easy-stack": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", - "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==" - }, - "ecpair": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.0.1.tgz", - "integrity": "sha512-iT3wztQMeE/nDTlfnAg8dAFUfBS7Tq2BXzq3ae6L+pWgFU0fQ3l0woTzdTBrJV3OxBjxbzjq8EQhAbEmJNWFSw==", - "requires": { - "randombytes": "^2.1.0", - "typeforce": "^1.18.0", - "wif": "^2.0.6" - } - }, - "ecurve": { - "version": "1.0.6", - "integrity": "sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w==", - "requires": { - "bigi": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "electron-to-chromium": { - "version": "1.4.309", - "integrity": "sha512-U7DTiKe4h+irqBG6h4EZ0XXaZuJj4md3xIXXaGSYhwiumPZ4BSc6rgf9UD0hVUMaeP/jB0q5pKWCPxvhO8fvZA==" - }, - "electrum-client": { - "version": "git+ssh://git@github.com/BlueWallet/rn-electrum-client.git#76c0ea35e1a50c47f3a7f818d529ebd100161496", - "integrity": "sha512-w9LHCQYUlCddBRGrDmgo1EUNp+zmzcyQSKLFOeO1XPITiAAFQDBZLwORVbBPywhMXf4PUk1dOphhHzJBJYG0vA==", - "from": "electrum-client@https://github.com/BlueWallet/rn-electrum-client#76c0ea35e1a50c47f3a7f818d529ebd100161496" - }, - "electrum-mnemonic": { - "version": "2.0.0", - "integrity": "sha512-egooI/RRX31y1LUbvv2OJf0eptrJjc5/lFv6txgDZx91g6JdZrQeQp+5AqlcfDUdsl2aDkeHk1a79J6bt3v8SA==", - "requires": { - "create-hmac": "^1.1.7", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0" - } - }, - "elliptic": { - "version": "6.5.4", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "emittery": { - "version": "0.13.1", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "encode-utf8": { - "version": "1.0.3", - "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "end-of-stream": { - "version": "1.4.4", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "entities": { - "version": "4.4.0", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==" - }, - "envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==" - }, - "error-ex": { - "version": "1.3.2", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "error-stack-parser": { - "version": "2.1.4", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "requires": { - "stackframe": "^1.3.4" - } - }, - "errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", - "requires": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" - } - }, - "es-abstract": { - "version": "1.21.1", - "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.4", - "is-array-buffer": "^3.0.1", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - } - }, - "es-set-tostringtag": { - "version": "2.0.1", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - } - }, - "es-shim-unscopables": { - "version": "1.0.0", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-object-assign": { - "version": "1.1.0", - "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==" - }, - "escalade": { - "version": "3.1.1", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "escape-string-regexp": { - "version": "4.0.0", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - }, - "eslint": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz", - "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.1.0", - "@eslint/js": "8.44.0", - "@humanwhocodes/config-array": "^0.11.10", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.6.0", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "eslint-scope": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz", - "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true - }, - "find-up": { - "version": "5.0.0", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "13.20.0", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-locate": { - "version": "5.0.0", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "which": { - "version": "2.0.2", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "eslint-config-prettier": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", - "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", - "dev": true - }, - "eslint-config-standard": { - "version": "17.0.0", - "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", - "dev": true - }, - "eslint-config-standard-jsx": { - "version": "11.0.0", - "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==", - "dev": true - }, - "eslint-config-standard-react": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard-react/-/eslint-config-standard-react-13.0.0.tgz", - "integrity": "sha512-HrVPGj8UncHfV+BsdJTuJpVsomn6AIrke3Af2Fh4XFvQQDU+iO6N2ZL+UsC+scExft4fU3uf7fJwj7PKWnXJDA==", - "dev": true - }, - "eslint-import-resolver-node": { - "version": "0.3.7", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.7.4", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "dev": true, - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-es-x": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.2.0.tgz", - "integrity": "sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.6.0" - } - }, - "eslint-plugin-eslint-comments": { - "version": "3.2.0", - "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "ignore": "^5.0.5" - }, - "dependencies": { - "escape-string-regexp": { - "version": "1.0.5", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - } - } - }, - "eslint-plugin-ft-flow": { - "version": "2.0.3", - "integrity": "sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg==", - "dev": true, - "requires": { - "lodash": "^4.17.21", - "string-natural-compare": "^3.0.1" - } - }, - "eslint-plugin-import": { - "version": "2.27.5", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, - "requires": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "doctrine": { - "version": "2.1.0", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - } - } - }, - "eslint-plugin-jest": { - "version": "26.9.0", - "integrity": "sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==", - "dev": true, - "requires": { - "@typescript-eslint/utils": "^5.10.0" - } - }, - "eslint-plugin-n": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.0.1.tgz", - "integrity": "sha512-CDmHegJN0OF3L5cz5tATH84RPQm9kG+Yx39wIqIwPR2C0uhBGMWfbbOtetR83PQjjidA5aXMu+LEFw1jaSwvTA==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.4.0", - "builtins": "^5.0.1", - "eslint-plugin-es-x": "^7.1.0", - "ignore": "^5.2.4", - "is-core-module": "^2.12.1", - "minimatch": "^3.1.2", - "resolve": "^1.22.2", - "semver": "^7.5.3" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "eslint-plugin-prettier": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", - "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" - } - }, - "eslint-plugin-promise": { - "version": "6.1.1", - "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", - "dev": true - }, - "eslint-plugin-react": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.0.tgz", - "integrity": "sha512-qewL/8P34WkY8jAqdQxsiL82pDUeT7nhs8IsuXgfgnsEloKCT4miAV9N9kGtx7/KM9NH/NCGUE7Edt9iGxLXFw==", - "dev": true, - "requires": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.8" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "resolve": { - "version": "2.0.0-next.4", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - } - } - }, - "eslint-plugin-react-hooks": { - "version": "4.6.0", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true - }, - "eslint-plugin-react-native": { - "version": "4.0.0", - "integrity": "sha512-kMmdxrSY7A1WgdqaGC+rY/28rh7kBGNBRsk48ovqkQmdg5j4K+DaFmegENDzMrdLkoufKGRNkKX6bgSwQTCAxQ==", - "dev": true, - "requires": { - "@babel/traverse": "^7.7.4", - "eslint-plugin-react-native-globals": "^0.1.1" - } - }, - "eslint-plugin-react-native-globals": { - "version": "0.1.2", - "integrity": "sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==", - "dev": true - }, - "eslint-scope": { - "version": "5.1.1", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "dependencies": { - "estraverse": { - "version": "4.3.0", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "requires": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esquery": { - "version": "1.4.2", - "integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "event-pubsub": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", - "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==" - }, - "event-target-shim": { - "version": "5.0.1", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" - }, - "eventemitter3": { - "version": "4.0.7", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "events": { - "version": "3.3.0", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "evp_bytestokey": { - "version": "1.0.3", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "execa": { - "version": "5.1.1", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "shebang-command": { - "version": "2.0.0", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" - }, - "which": { - "version": "2.0.2", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "exit": { - "version": "0.1.2", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "exit-on-epipe": { - "version": "1.0.1", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" - }, - "expand-template": { - "version": "2.0.3", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" - }, - "expect": { - "version": "29.4.3", - "integrity": "sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.4.3", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3" - }, - "dependencies": { - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-diff": { - "version": "1.2.0", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fast-xml-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.4.tgz", - "integrity": "sha512-fbfMDvgBNIdDJLdLOwacjFAPYt67tr31H9ZhWSm45CDAxvd0I6WTlSOUo7K2P/K5sA5JgMKG64PI3DMcaFdWpQ==", - "requires": { - "strnum": "^1.0.5" - } - }, - "fastq": { - "version": "1.15.0", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.2", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "fill-range": { - "version": "7.0.1", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "filter-obj": { - "version": "1.1.0", - "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==" - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "find-cache-dir": { - "version": "2.1.0", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "make-dir": { - "version": "2.1.0", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "p-limit": { - "version": "2.3.0", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" - }, - "pify": { - "version": "4.0.1", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "pkg-dir": { - "version": "3.0.0", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "requires": { - "find-up": "^3.0.0" - } - }, - "semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "find-replace": { - "version": "3.0.0", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "requires": { - "array-back": "^3.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "findit": { - "version": "2.0.0", - "integrity": "sha512-ENZS237/Hr8bjczn5eKuBohLgaD0JyUd0arxretR1f9RO46vZHA1b2y0VorgGV3WaOT3c+78P8h7v4JGJ1i/rg==" - }, - "flat": { - "version": "5.0.2", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" - }, - "flat-cache": { - "version": "3.0.4", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "flow-parser": { - "version": "0.185.2", - "integrity": "sha512-2hJ5ACYeJCzNtiVULov6pljKOLygy0zddoqSI1fFetM+XRPpRshFdGEijtqlamA1XwyZ+7rhryI6FQFzvtLWUQ==" - }, - "for-each": { - "version": "0.3.3", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "requires": { - "is-callable": "^1.1.3" - } - }, - "for-in": { - "version": "1.0.2", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==" - }, - "form-data": { - "version": "3.0.1", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "frisbee": { - "version": "3.1.4", - "integrity": "sha512-LoGzXyYWuGSwUUDKdlbYbokGf08UT37wzdDtVPtOeMWHgzeKiAceIPRrAn7Vn9aYaS+uMJkq7aze6pbfj9hnBA==", - "requires": { - "@babel/runtime": "^7.10.2", - "abortcontroller-polyfill": "^1.4.0", - "boolean": "^3.0.1", - "caseless": "^0.12.0", - "common-tags": "^1.8.0", - "cross-fetch": "^3.0.4", - "debug": "^4.1.1", - "qs": "6.9.4", - "url-join": "^4.0.1", - "url-parse": "^1.4.7" - }, - "dependencies": { - "qs": { - "version": "6.9.4", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" - } - } - }, - "fs-constants": { - "version": "1.0.0", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "fs.realpath": { - "version": "1.0.0", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "function-bind": { - "version": "1.1.1", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "function.prototype.name": { - "version": "1.1.5", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "functions-have-names": { - "version": "1.2.3", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" - }, - "funpermaproxy": { - "version": "1.1.0", - "integrity": "sha512-2Sp1hWuO8m5fqeFDusyhKqYPT+7rGLw34N3qonDcdRP8+n7M7Gl/yKp/q7oCxnnJ6pWCectOmLFJpsMU/++KrQ==" - }, - "gensync": { - "version": "1.0.0-beta.2", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" - }, - "get-caller-file": { - "version": "2.0.5", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-intrinsic": { - "version": "1.2.0", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-package-type": { - "version": "0.1.0", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" - }, - "get-symbol-description": { - "version": "1.0.0", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "get-value": { - "version": "2.0.6", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==" - }, - "github-from-package": { - "version": "0.0.0", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" - }, - "glob": { - "version": "7.2.3", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globals": { - "version": "11.12.0", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "globalthis": { - "version": "1.0.3", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3" - } - }, - "globby": { - "version": "11.1.0", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "gopd": { - "version": "1.0.1", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "graceful-fs": { - "version": "4.2.10", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "has": { - "version": "1.0.3", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "has-property-descriptors": { - "version": "1.0.0", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-proto": { - "version": "1.0.1", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "has-value": { - "version": "1.0.0", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hash-base": { - "version": "3.1.0", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "hash.js": { - "version": "1.1.7", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "hermes-estree": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.8.0.tgz", - "integrity": "sha512-W6JDAOLZ5pMPMjEiQGLCXSSV7pIBEgRR5zGkxgmzGSXHOxqV5dC/M1Zevqpbm9TZDE5tu358qZf8Vkzmsc+u7Q==" - }, - "hermes-parser": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.8.0.tgz", - "integrity": "sha512-yZKalg1fTYG5eOiToLUaw69rQfZq/fi+/NtEXRU7N87K/XobNRhRWorh80oSge2lWUiZfTgUvRJH+XgZWrhoqA==", - "requires": { - "hermes-estree": "0.8.0" - } - }, - "hermes-profile-transformer": { - "version": "0.0.6", - "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", - "requires": { - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" - } - } - }, - "hmac-drbg": { - "version": "1.0.1", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - } - }, - "hosted-git-info": { - "version": "2.8.9", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "html-escaper": { - "version": "2.0.2", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "dependencies": { - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - } - } - }, - "human-signals": { - "version": "2.1.0", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" - }, - "ieee754": { - "version": "1.2.1", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ignore": { - "version": "5.2.4", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "image-size": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", - "integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==" - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-local": { - "version": "3.1.0", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" - }, - "inflight": { - "version": "1.0.6", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.8", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "internal-slot": { - "version": "1.0.5", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "invariant": { - "version": "2.2.4", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "ip": { - "version": "1.1.8", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-arguments": { - "version": "1.1.1", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-array-buffer": { - "version": "3.0.1", - "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-typed-array": "^1.1.10" - } - }, - "is-arrayish": { - "version": "0.2.1", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "is-bigint": { - "version": "1.0.4", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-callable": { - "version": "1.2.7", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" - }, - "is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-date-object": { - "version": "1.0.5", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==" - }, - "is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true - }, - "is-extendable": { - "version": "0.1.1", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==" - }, - "is-extglob": { - "version": "2.1.1", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" - }, - "is-generator-fn": { - "version": "2.1.0", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-generator-function": { - "version": "1.0.10", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-glob": { - "version": "4.0.3", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "requires": { - "is-docker": "^3.0.0" - } - }, - "is-interactive": { - "version": "1.0.0", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" - }, - "is-nan": { - "version": "1.3.2", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - } - }, - "is-negative-zero": { - "version": "2.0.2", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "is-number-object": { - "version": "1.0.7", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-path-inside": { - "version": "3.0.3", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" - }, - "is-plain-object": { - "version": "2.0.4", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.1.4", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-stream": { - "version": "2.0.1", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, - "is-string": { - "version": "1.0.7", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.10", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" - }, - "is-weakref": { - "version": "1.0.2", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-windows": { - "version": "1.0.2", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" - }, - "is-wsl": { - "version": "1.1.0", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==" - }, - "isarray": { - "version": "1.0.0", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "iserror": { - "version": "0.0.2", - "integrity": "sha512-oKGGrFVaWwETimP3SiWwjDeY27ovZoyZPHtxblC4hCq9fXxed/jasx+ATWFFjCVSRZng8VTMsN1nDnGo6zMBSw==" - }, - "isexe": { - "version": "2.0.0", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "isobject": { - "version": "3.0.1", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.1", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.5", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "29.4.3", - "integrity": "sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA==", - "dev": true, - "requires": { - "@jest/core": "^29.4.3", - "@jest/types": "^29.4.3", - "import-local": "^3.0.2", - "jest-cli": "^29.4.3" - }, - "dependencies": { - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-changed-files": { - "version": "29.4.3", - "integrity": "sha512-Vn5cLuWuwmi2GNNbokPOEcvrXGSGrqVnPEZV7rC6P7ck07Dyw9RFnvWglnupSh+hGys0ajGtw/bc2ZgweljQoQ==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "29.4.3", - "integrity": "sha512-Vw/bVvcexmdJ7MLmgdT3ZjkJ3LKu8IlpefYokxiqoZy6OCQ2VAm6Vk3t/qHiAGUXbdbJKJWnc8gH3ypTbB/OBw==", - "dev": true, - "requires": { - "@jest/environment": "^29.4.3", - "@jest/expect": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.4.3", - "jest-matcher-utils": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-runtime": "^29.4.3", - "jest-snapshot": "^29.4.3", - "jest-util": "^29.4.3", - "p-limit": "^3.1.0", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0" - } - }, - "@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", - "dev": true, - "requires": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-cli": { - "version": "29.4.3", - "integrity": "sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg==", - "dev": true, - "requires": { - "@jest/core": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/types": "^29.4.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.4.3", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "dependencies": { - "@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0" - } - }, - "@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", - "dev": true, - "requires": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "cliui": { - "version": "8.0.1", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs": { - "version": "17.7.1", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } - } - }, - "jest-config": { - "version": "29.4.3", - "integrity": "sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.4.3", - "@jest/types": "^29.4.3", - "babel-jest": "^29.4.3", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.4.3", - "jest-environment-node": "^29.4.3", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-runner": "^29.4.3", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true - }, - "jest-resolve": { - "version": "29.4.3", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - } - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "resolve.exports": { - "version": "2.0.0", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "29.4.3", - "integrity": "sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "29.4.3", - "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "29.4.3", - "integrity": "sha512-1ElHNAnKcbJb/b+L+7j0/w7bDvljw4gTv1wL9fYOczeJrbTbkMGQ5iQPFJ3eFQH19VPTx1IyfePdqSpePKss7Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.4.3", - "pretty-format": "^29.4.3" - }, - "dependencies": { - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-node": { - "version": "29.4.3", - "integrity": "sha512-gAiEnSKF104fsGDXNkwk49jD/0N0Bqu2K9+aMQXA6avzsA9H3Fiv1PW2D+gzbOSR705bWd2wJZRFEFpV0tXISg==", - "requires": { - "@jest/environment": "^29.4.3", - "@jest/fake-timers": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/node": "*", - "jest-mock": "^29.4.3", - "jest-util": "^29.4.3" - }, - "dependencies": { - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-get-type": { - "version": "29.4.3", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", - "dev": true - }, - "jest-haste-map": { - "version": "27.5.1", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "dependencies": { - "anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - } - } - }, - "jest-leak-detector": { - "version": "29.4.3", - "integrity": "sha512-9yw4VC1v2NspMMeV3daQ1yXPNxMgCzwq9BocCwYrRgXe4uaEJPAN0ZK37nFBhcy3cUwEVstFecFLaTHpF7NiGA==", - "dev": true, - "requires": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" - } - }, - "jest-matcher-utils": { - "version": "29.4.3", - "integrity": "sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "27.5.1", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "27.5.1", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "17.0.2", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-mock": { - "version": "29.4.3", - "integrity": "sha512-LjFgMg+xed9BdkPMyIJh+r3KeHt1klXPJYBULXVVAkbTaaKjPX1o1uVCAZADMEp/kOxGTwy/Ot8XbvgItOrHEg==", - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "jest-util": "^29.4.3" - }, - "dependencies": { - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-pnp-resolver": { - "version": "1.2.3", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true - }, - "jest-regex-util": { - "version": "27.5.1", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==" - }, - "jest-resolve": { - "version": "27.5.1", - "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.3.0", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "27.5.1", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "dev": true - }, - "jest-validate": { - "version": "27.5.1", - "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "leven": "^3.1.0", - "pretty-format": "^27.5.1" - } - }, - "pretty-format": { - "version": "27.5.1", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "17.0.2", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "29.4.3", - "integrity": "sha512-uvKMZAQ3nmXLH7O8WAOhS5l0iWyT3WmnJBdmIHiV5tBbdaDZ1wqtNX04FONGoaFvSOSHBJxnwAVnSn1WHdGVaw==", - "dev": true, - "requires": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.4.3" - }, - "dependencies": { - "jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true - } - } - }, - "jest-runner": { - "version": "29.4.3", - "integrity": "sha512-GWPTEiGmtHZv1KKeWlTX9SIFuK19uLXlRQU43ceOQ2hIfA5yPEJC7AMkvFKpdCHx6pNEdOD+2+8zbniEi3v3gA==", - "dev": true, - "requires": { - "@jest/console": "^29.4.3", - "@jest/environment": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.4.3", - "jest-haste-map": "^29.4.3", - "jest-leak-detector": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-runtime": "^29.4.3", - "jest-util": "^29.4.3", - "jest-watcher": "^29.4.3", - "jest-worker": "^29.4.3", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0" - } - }, - "@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", - "dev": true, - "requires": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true - }, - "jest-resolve": { - "version": "29.4.3", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - } - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "resolve.exports": { - "version": "2.0.0", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", - "dev": true - }, - "source-map-support": { - "version": "0.5.13", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - } - } - }, - "jest-runtime": { - "version": "29.4.3", - "integrity": "sha512-F5bHvxSH+LvLV24vVB3L8K467dt3y3dio6V3W89dUz9nzvTpqd/HcT9zfYKL2aZPvD63vQFgLvaUX/UpUhrP6Q==", - "dev": true, - "requires": { - "@jest/environment": "^29.4.3", - "@jest/fake-timers": "^29.4.3", - "@jest/globals": "^29.4.3", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-mock": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-snapshot": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0" - } - }, - "@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", - "dev": true, - "requires": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true - }, - "jest-resolve": { - "version": "29.4.3", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - } - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "resolve.exports": { - "version": "2.0.0", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - } - } - }, - "jest-serializer": { - "version": "27.5.1", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - } - }, - "jest-snapshot": { - "version": "29.4.3", - "integrity": "sha512-NGlsqL0jLPDW91dz304QTM/SNO99lpcSYYAjNiX0Ou+sSGgkanKBcSjCfp/pqmiiO1nQaOyLp6XQddAzRcx3Xw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.4.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.4.3", - "jest-get-type": "^29.4.3", - "jest-haste-map": "^29.4.3", - "jest-matcher-utils": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "natural-compare": "^1.4.0", - "pretty-format": "^29.4.3", - "semver": "^7.3.5" - }, - "dependencies": { - "@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "lru-cache": { - "version": "6.0.0", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "semver": { - "version": "7.3.8", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "yallist": { - "version": "4.0.0", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "jest-util": { - "version": "27.5.1", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-validate": { - "version": "29.4.3", - "integrity": "sha512-J3u5v7aPQoXPzaar6GndAVhdQcZr/3osWSgTeKg5v574I9ybX/dTyH0AJFb5XgXIB7faVhf+rS7t4p3lL9qFaw==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "leven": "^3.1.0", - "pretty-format": "^29.4.3" - }, - "dependencies": { - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.3.0", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "29.4.3", - "integrity": "sha512-zwlXH3DN3iksoIZNk73etl1HzKyi5FuQdYLnkQKm5BW4n8HpoG59xSwpVdFrnh60iRRaRBGw0gcymIxjJENPcA==", - "dev": true, - "requires": { - "@jest/test-result": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.4.3", - "string-length": "^4.0.1" - }, - "dependencies": { - "@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "slash": "^3.0.0" - } - }, - "@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", - "dev": true, - "requires": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "27.5.1", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "joi": { - "version": "17.8.3", - "integrity": "sha512-q5Fn6Tj/jR8PfrLrx4fpGH4v9qM6o+vDUfD4/3vxxyg34OmKcNqYZ1qn2mpLza96S8tL0p0rIw2gOZX+/cTg9w==", - "requires": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "js-message": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", - "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==" - }, - "js-queue": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.2.tgz", - "integrity": "sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==", - "requires": { - "easy-stack": "^1.0.1" - } - }, - "js-tokens": { - "version": "4.0.0", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.14.1", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbi": { - "version": "3.2.5", - "integrity": "sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ==" - }, - "jsc-android": { - "version": "250231.0.0", - "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==" - }, - "jsc-safe-url": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", - "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==" - }, - "jscodeshift": { - "version": "0.13.1", - "integrity": "sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ==", - "requires": { - "@babel/core": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/preset-flow": "^7.13.13", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.13.16", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^3.1.10", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.20.4", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "arr-diff": { - "version": "4.0.0", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==" - }, - "array-unique": { - "version": "0.3.2", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==" - }, - "babel-core": { - "version": "7.0.0-bridge.0", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==" - }, - "braces": { - "version": "2.3.2", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "debug": { - "version": "2.6.9", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-descriptor": { - "version": "0.1.6", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "extglob": { - "version": "2.0.4", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "ms": { - "version": "2.0.0", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "rimraf": { - "version": "2.6.3", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "requires": { - "glob": "^7.1.3" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "temp": { - "version": "0.8.4", - "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", - "requires": { - "rimraf": "~2.6.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "write-file-atomic": { - "version": "2.4.3", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - } - } - }, - "jsesc": { - "version": "2.5.2", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-cycle": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/json-cycle/-/json-cycle-1.3.0.tgz", - "integrity": "sha512-FD/SedD78LCdSvJaOUQAXseT8oQBb5z6IVYaQaCrVUlu9zOAr1BDdKyVYQaSD/GDsAMrXpKcOyBD4LIl8nfjHw==" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema-traverse": { - "version": "1.0.0", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" - }, - "jsonfile": { - "version": "4.0.0", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsx-ast-utils": { - "version": "3.3.3", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", - "dev": true, - "requires": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" - } - }, - "junderw-crc32c": { - "version": "1.2.0", - "integrity": "sha512-tP0w5QOrunUS/XgsDBoZfw2jKNFhnUrdM96IXzuJtCyuXd19Hj47Hfd5+WFd81QDQFosiPffpc/jnSrZ35IV+A==", - "requires": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - } - }, - "kind-of": { - "version": "6.0.3", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - }, - "kleur": { - "version": "3.0.3", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" - }, - "leven": { - "version": "3.1.0", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "localized-strings": { - "version": "git+ssh://git@github.com/BlueWallet/localized-strings.git#178351a7297336618d9ee87277f8e3af9ab7285d", - "integrity": "sha512-E6ncf+f2l/1Rv2C5DMxkfSbK0wjYxyD6g3dcUqM5TB/tGv0QZlJ96MFTjhmIshMa2bM6P06hjj6V8QjC/Zqhlg==", - "from": "localized-strings@github:BlueWallet/localized-strings#178351a7297336618d9ee87277f8e3af9ab7285d" - }, - "locate-path": { - "version": "5.0.0", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.camelcase": { - "version": "4.3.0", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, - "lodash.debounce": { - "version": "4.0.8", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "lodash.isequal": { - "version": "4.5.0", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" - }, - "lodash.memoize": { - "version": "4.1.2", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.sortby": { - "version": "4.7.0", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" - }, - "lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" - }, - "log-symbols": { - "version": "4.1.0", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "logkitty": { - "version": "0.7.1", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "requires": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "6.0.0", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "decamelize": { - "version": "1.2.0", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" - }, - "wrap-ansi": { - "version": "6.2.0", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "yargs": { - "version": "15.4.1", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "loose-envify": { - "version": "1.4.0", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lottie-ios": { - "version": "3.4.4", - "integrity": "sha512-ikj8VNuClItRDZ8C9MfRMDxN0U/UIX3HRF2aei3H44F9Hkhwv0EIVRkBwG+SwS/WSoGmBzkcVG8O3BjPk5hW7Q==" - }, - "lottie-react-native": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-5.1.6.tgz", - "integrity": "sha512-vhdeZstXMfuVKwnddYWjJgQ/1whGL58IJEJu/iSf0XQ5gAb4pp/+vy91mdYQLezlb8Aw4Vu3fKnqErJL2hwchg==", - "requires": { - "invariant": "^2.2.2", - "react-native-safe-modules": "^1.0.3" - } - }, - "lru-cache": { - "version": "5.1.1", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "3.1.0", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "requires": { - "tmpl": "1.0.5" - } - }, - "map-cache": { - "version": "0.2.2", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==" - }, - "map-visit": { - "version": "1.0.0", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "requires": { - "object-visit": "^1.0.0" - } - }, - "md5.js": { - "version": "1.3.5", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "mdn-data": { - "version": "2.0.14", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "memoize-one": { - "version": "5.2.1", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, - "merge-options": { - "version": "3.0.4", - "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", - "requires": { - "is-plain-obj": "^2.1.0" - } - }, - "merge-stream": { - "version": "2.0.0", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "merge2": { - "version": "1.4.1", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "merkle-lib": { - "version": "2.0.10", - "integrity": "sha512-XrNQvUbn1DL5hKNe46Ccs+Tu3/PYOlrcZILuGUhb95oKBPjc/nmIC8D462PQkipVDGKRvwhn+QFg2cCdIvmDJA==" - }, - "metro": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.73.10.tgz", - "integrity": "sha512-J2gBhNHFtc/Z48ysF0B/bfTwUwaRDLjNv7egfhQCc+934dpXcjJG2KZFeuybF+CvA9vo4QUi56G2U+RSAJ5tsA==", - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", - "absolute-path": "^0.0.0", - "accepts": "^1.3.7", - "async": "^3.2.2", - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "denodeify": "^1.2.1", - "error-stack-parser": "^2.0.6", - "graceful-fs": "^4.2.4", - "hermes-parser": "0.8.0", - "image-size": "^0.6.0", - "invariant": "^2.2.4", - "jest-worker": "^27.2.0", - "jsc-safe-url": "^0.2.2", - "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.73.10", - "metro-cache": "0.73.10", - "metro-cache-key": "0.73.10", - "metro-config": "0.73.10", - "metro-core": "0.73.10", - "metro-file-map": "0.73.10", - "metro-hermes-compiler": "0.73.10", - "metro-inspector-proxy": "0.73.10", - "metro-minify-terser": "0.73.10", - "metro-minify-uglify": "0.73.10", - "metro-react-native-babel-preset": "0.73.10", - "metro-resolver": "0.73.10", - "metro-runtime": "0.73.10", - "metro-source-map": "0.73.10", - "metro-symbolicate": "0.73.10", - "metro-transform-plugins": "0.73.10", - "metro-transform-worker": "0.73.10", - "mime-types": "^2.1.27", - "node-fetch": "^2.2.0", - "nullthrows": "^1.1.1", - "rimraf": "^3.0.2", - "serialize-error": "^2.1.0", - "source-map": "^0.5.6", - "strip-ansi": "^6.0.0", - "temp": "0.8.3", - "throat": "^5.0.0", - "ws": "^7.5.1", - "yargs": "^17.5.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "metro-react-native-babel-preset": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.73.10.tgz", - "integrity": "sha512-1/dnH4EHwFb2RKEKx34vVDpUS3urt2WEeR8FYim+ogqALg4sTpG7yeQPxWpbgKATezt4rNfqAANpIyH19MS4BQ==", - "requires": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "react-refresh": "^0.4.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "serialize-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" - } - } - }, - "metro-babel-transformer": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.73.10.tgz", - "integrity": "sha512-Yv2myTSnpzt/lTyurLvqYbBkytvUJcLHN8XD3t7W6rGiLTQPzmf1zypHQLphvcAXtCWBOXFtH7KLOSi2/qMg+A==", - "requires": { - "@babel/core": "^7.20.0", - "hermes-parser": "0.8.0", - "metro-source-map": "0.73.10", - "nullthrows": "^1.1.1" - } - }, - "metro-cache": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.73.10.tgz", - "integrity": "sha512-wPGlQZpdVlM404m7MxJqJ+hTReDr5epvfPbt2LerUAHY9RN99w61FeeAe25BMZBwgUgDtAsfGlJ51MBHg8MAqw==", - "requires": { - "metro-core": "0.73.10", - "rimraf": "^3.0.2" - } - }, - "metro-cache-key": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.73.10.tgz", - "integrity": "sha512-JMVDl/EREDiUW//cIcUzRjKSwE2AFxVWk47cFBer+KA4ohXIG2CQPEquT56hOw1Y1s6gKNxxs1OlAOEsubrFjw==" - }, - "metro-config": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.73.10.tgz", - "integrity": "sha512-wIlybd1Z9I8K2KcStTiJxTB7OK529dxFgogNpKCTU/3DxkgAASqSkgXnZP6kVyqjh5EOWAKFe5U6IPic7kXDdQ==", - "requires": { - "cosmiconfig": "^5.0.5", - "jest-validate": "^26.5.2", - "metro": "0.73.10", - "metro-cache": "0.73.10", - "metro-core": "0.73.10", - "metro-runtime": "0.73.10" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "15.0.15", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz", - "integrity": "sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" - }, - "jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "requires": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - } - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "metro-core": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.73.10.tgz", - "integrity": "sha512-5uYkajIxKyL6W45iz/ftNnYPe1l92CvF2QJeon1CHsMXkEiOJxEjo41l+iSnO/YodBGrmMCyupSO4wOQGUc0lw==", - "requires": { - "lodash.throttle": "^4.1.1", - "metro-resolver": "0.73.10" - } - }, - "metro-file-map": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.73.10.tgz", - "integrity": "sha512-XOMWAybeaXyD6zmVZPnoCCL2oO3rp4ta76oUlqWP0skBzhFxVtkE/UtDwApEMUY361JeBBago647gnKiARs+1g==", - "requires": { - "abort-controller": "^3.0.0", - "anymatch": "^3.0.3", - "debug": "^2.2.0", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.4", - "invariant": "^2.2.4", - "jest-regex-util": "^27.0.6", - "jest-serializer": "^27.0.6", - "jest-util": "^27.2.0", - "jest-worker": "^27.2.0", - "micromatch": "^4.0.4", - "nullthrows": "^1.1.1", - "walker": "^1.0.7" - }, - "dependencies": { - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - } - } - }, - "metro-hermes-compiler": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-hermes-compiler/-/metro-hermes-compiler-0.73.10.tgz", - "integrity": "sha512-rTRWEzkVrwtQLiYkOXhSdsKkIObnL+Jqo+IXHI7VEK2aSLWRAbtGNqECBs44kbOUypDYTFFE+WLtoqvUWqYkWg==" - }, - "metro-inspector-proxy": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.73.10.tgz", - "integrity": "sha512-CEEvocYc5xCCZBtGSIggMCiRiXTrnBbh8pmjKQqm9TtJZALeOGyt5pXUaEkKGnhrXETrexsg6yIbsQHhEvVfvQ==", - "requires": { - "connect": "^3.6.5", - "debug": "^2.2.0", - "ws": "^7.5.1", - "yargs": "^17.5.1" - }, - "dependencies": { - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" - } - } - }, - "metro-minify-terser": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.73.10.tgz", - "integrity": "sha512-uG7TSKQ/i0p9kM1qXrwbmY3v+6BrMItsOcEXcSP8Z+68bb+t9HeVK0T/hIfUu1v1PEnonhkhfzVsaP8QyTd5lQ==", - "requires": { - "terser": "^5.15.0" - } - }, - "metro-minify-uglify": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.73.10.tgz", - "integrity": "sha512-eocnSeJKnLz/UoYntVFhCJffED7SLSgbCHgNvI6ju6hFb6EFHGJT9OLbkJWeXaWBWD3Zw5mYLS8GGqGn/CHZPA==", - "requires": { - "uglify-es": "^3.1.9" - } - }, - "metro-react-native-babel-preset": { - "version": "0.77.0", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.77.0.tgz", - "integrity": "sha512-HPPD+bTxADtoE4y/4t1txgTQ1LVR6imOBy7RMHUsqMVTbekoi8Ph5YI9vKX2VMPtVWeFt0w9YnCSLPa76GcXsA==", - "requires": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.4.0" - } - }, - "metro-react-native-babel-transformer": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.73.10.tgz", - "integrity": "sha512-4G/upwqKdmKEjmsNa92/NEgsOxUWOygBVs+FXWfXWKgybrmcjh3NoqdRYrROo9ZRA/sB9Y/ZXKVkWOGKHtGzgg==", - "requires": { - "@babel/core": "^7.20.0", - "babel-preset-fbjs": "^3.4.0", - "hermes-parser": "0.8.0", - "metro-babel-transformer": "0.73.10", - "metro-react-native-babel-preset": "0.73.10", - "metro-source-map": "0.73.10", - "nullthrows": "^1.1.1" - }, - "dependencies": { - "metro-react-native-babel-preset": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.73.10.tgz", - "integrity": "sha512-1/dnH4EHwFb2RKEKx34vVDpUS3urt2WEeR8FYim+ogqALg4sTpG7yeQPxWpbgKATezt4rNfqAANpIyH19MS4BQ==", - "requires": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "react-refresh": "^0.4.0" - } - } - } - }, - "metro-resolver": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.73.10.tgz", - "integrity": "sha512-HeXbs+0wjakaaVQ5BI7eT7uqxlZTc9rnyw6cdBWWMgUWB++KpoI0Ge7Hi6eQAOoVAzXC3m26mPFYLejpzTWjng==", - "requires": { - "absolute-path": "^0.0.0" - } - }, - "metro-runtime": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.73.10.tgz", - "integrity": "sha512-EpVKm4eN0Fgx2PEWpJ5NiMArV8zVoOin866jIIvzFLpmkZz1UEqgjf2JAfUJnjgv3fjSV3JqeGG2vZCaGQBTow==", - "requires": { - "@babel/runtime": "^7.0.0", - "react-refresh": "^0.4.0" - } - }, - "metro-source-map": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.73.10.tgz", - "integrity": "sha512-NAGv14701p/YaFZ76KzyPkacBw/QlEJF1f8elfs23N1tC33YyKLDKvPAzFJiYqjdcFvuuuDCA8JCXd2TgLxNPw==", - "requires": { - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", - "invariant": "^2.2.4", - "metro-symbolicate": "0.73.10", - "nullthrows": "^1.1.1", - "ob1": "0.73.10", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - } - } - }, - "metro-symbolicate": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.73.10.tgz", - "integrity": "sha512-PmCe3TOe1c/NVwMlB+B17me951kfkB3Wve5RqJn+ErPAj93od1nxicp6OJe7JT4QBRnpUP8p9tw2sHKqceIzkA==", - "requires": { - "invariant": "^2.2.4", - "metro-source-map": "0.73.10", - "nullthrows": "^1.1.1", - "source-map": "^0.5.6", - "through2": "^2.0.1", - "vlq": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - } - } - }, - "metro-transform-plugins": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.73.10.tgz", - "integrity": "sha512-D4AgD3Vsrac+4YksaPmxs/0ocT67bvwTkFSIgWWeDvWwIG0U1iHzTS9f8Bvb4PITnXryDoFtjI6OWF7uOpGxpA==", - "requires": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", - "nullthrows": "^1.1.1" - } - }, - "metro-transform-worker": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.73.10.tgz", - "integrity": "sha512-IySvVubudFxahxOljWtP0QIMMpgUrCP0bW16cz2Enof0PdumwmR7uU3dTbNq6S+XTzuMHR+076aIe4VhPAWsIQ==", - "requires": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/types": "^7.20.0", - "babel-preset-fbjs": "^3.4.0", - "metro": "0.73.10", - "metro-babel-transformer": "0.73.10", - "metro-cache": "0.73.10", - "metro-cache-key": "0.73.10", - "metro-hermes-compiler": "0.73.10", - "metro-source-map": "0.73.10", - "metro-transform-plugins": "0.73.10", - "nullthrows": "^1.1.1" - } - }, - "micromatch": { - "version": "4.0.5", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "miller-rabin": { - "version": "4.0.1", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - } - }, - "mime": { - "version": "2.6.0", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" - }, - "mime-db": { - "version": "1.52.0", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, - "mimic-response": { - "version": "3.1.0", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - }, - "minimalistic-assert": { - "version": "1.0.1", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" - }, - "minimatch": { - "version": "3.1.2", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" - }, - "mixin-deep": { - "version": "1.3.2", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.6", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "requires": { - "minimist": "^1.2.6" - } - }, - "mkdirp-classic": { - "version": "0.5.3", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" - }, - "moment": { - "version": "2.29.4", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "optional": true - }, - "ms": { - "version": "2.1.2", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "multi-sort-stream": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/multi-sort-stream/-/multi-sort-stream-1.0.4.tgz", - "integrity": "sha512-hAZ8JOEQFbgdLe8HWZbb7gdZg0/yAIHF00Qfo3kd0rXFv96nXe+/bPTrKHZ2QMHugGX4FiAyET1Lt+jiB+7Qlg==" - }, - "multipipe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-4.0.0.tgz", - "integrity": "sha512-jzcEAzFXoWwWwUbvHCNPwBlTz3WCWe/jPcXSmTfbo/VjRwRTfvLZ/bdvtiTdqCe8d4otCSsPCbhGYcX+eggpKQ==", - "requires": { - "duplexer2": "^0.1.2", - "object-assign": "^4.1.0" - } - }, - "mv": { - "version": "2.1.1", - "integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==", - "optional": true, - "requires": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", - "optional": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.4.5", - "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", - "optional": true, - "requires": { - "glob": "^6.0.1" - } - } - } - }, - "nan": { - "version": "2.17.0", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==" - }, - "nanoid": { - "version": "3.3.4", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" - }, - "nanomatch": { - "version": "1.2.13", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==" - }, - "array-unique": { - "version": "0.3.2", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==" - } - } - }, - "napi-build-utils": { - "version": "1.0.2", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" - }, - "natural-compare": { - "version": "1.4.0", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "natural-compare-lite": { - "version": "1.4.0", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "ncp": { - "version": "2.0.0", - "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", - "optional": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "neo-async": { - "version": "2.6.2", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "nice-try": { - "version": "1.0.5", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "nocache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", - "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==" - }, - "node-abi": { - "version": "3.33.0", - "integrity": "sha512-7GGVawqyHF4pfd0YFybhv/eM9JwTtPqx0mAanQ146O3FlSh3pA24zf9IRQTOsfTSqXTNzPSP5iagAJ94jjuVog==", - "requires": { - "semver": "^7.3.5" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.8", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, - "node-dir": { - "version": "0.1.17", - "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", - "requires": { - "minimatch": "^3.0.2" - } - }, - "node-fetch": { - "version": "2.6.9", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-gyp-build": { - "version": "4.6.0", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==" - }, - "node-int64": { - "version": "0.4.0", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" - }, - "node-ipc": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-9.2.1.tgz", - "integrity": "sha512-mJzaM6O3xHf9VT8BULvJSbdVbmHUKRNOH7zDDkCrA1/T+CVjq2WVIDfLt0azZRXpgArJtl3rtmEozrbXPZ9GaQ==", - "requires": { - "event-pubsub": "4.3.0", - "js-message": "1.0.7", - "js-queue": "2.0.2" - } - }, - "node-machine-id": { - "version": "1.1.12", - "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==" - }, - "node-releases": { - "version": "2.0.10", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" - }, - "node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==" - }, - "node-version": { - "version": "1.2.0", - "integrity": "sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ==" - }, - "normalize-package-data": { - "version": "2.5.0", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "npm-run-path": { - "version": "4.0.1", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "requires": { - "path-key": "^3.0.0" - } - }, - "nth-check": { - "version": "2.1.1", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "requires": { - "boolbase": "^1.0.0" - } - }, - "nullthrows": { - "version": "1.1.1", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" - }, - "ob1": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.73.10.tgz", - "integrity": "sha512-aO6EYC+QRRCkZxVJhCWhLKgVjhNuD6Gu1riGjxrIm89CqLsmKgxzYDDEsktmKsoDeRdWGQM5EdMzXDl5xcVfsw==" - }, - "object-assign": { - "version": "4.1.1", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "object-copy": { - "version": "0.1.0", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.12.3", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" - }, - "object-is": { - "version": "1.1.5", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object-visit": { - "version": "1.0.1", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.4", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "object.entries": { - "version": "1.1.6", - "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "object.fromentries": { - "version": "2.0.6", - "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "object.hasown": { - "version": "1.1.2", - "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", - "dev": true, - "requires": { - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "object.pick": { - "version": "1.3.0", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "requires": { - "isobject": "^3.0.1" - } - }, - "object.values": { - "version": "1.1.6", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "once": { - "version": "1.4.0", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "open": { - "version": "6.4.0", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "requires": { - "is-wsl": "^1.1.0" - } - }, - "opencollective-postinstall": { - "version": "2.0.3", - "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==" - }, - "optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "requires": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - } - }, - "ora": { - "version": "5.4.1", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "requires": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "os-tmpdir": { - "version": "1.0.2", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" - }, - "p-finally": { - "version": "1.0.0", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==" - }, - "p-limit": { - "version": "3.1.0", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "p-try": { - "version": "2.2.0", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-asn1": { - "version": "5.1.6", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "parse-json": { - "version": "5.2.0", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "pascalcase": { - "version": "0.1.1", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==" - }, - "path-browserify": { - "version": "1.0.1", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" - }, - "path-exists": { - "version": "4.0.0", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "path-is-absolute": { - "version": "1.0.1", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "path-key": { - "version": "3.1.1", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" - }, - "path-parse": { - "version": "1.0.7", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-type": { - "version": "4.0.0", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "payjoin-client": { - "version": "1.0.1", - "integrity": "sha512-Z9JmTcO3KBDX/w2V8W7L2juC0ItMSVCKp9YL/uaJLG9OS0ReT2Y2q1HfDgK9o5lSOCt1pPsHXo+1qJO2CApMxQ==", - "requires": { - "bitcoinjs-lib": "^5.2.0" - }, - "dependencies": { - "@types/node": { - "version": "10.12.18", - "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" - }, - "bech32": { - "version": "1.1.4", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "bip32": { - "version": "2.0.6", - "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==", - "requires": { - "@types/node": "10.12.18", - "bs58check": "^2.1.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "tiny-secp256k1": "^1.1.3", - "typeforce": "^1.11.5", - "wif": "^2.0.6" - } - }, - "bitcoinjs-lib": { - "version": "5.2.0", - "integrity": "sha512-5DcLxGUDejgNBYcieMIUfjORtUeNWl828VWLHJGVKZCb4zIS1oOySTUr0LGmcqJBQgTBz3bGbRQla4FgrdQEIQ==", - "requires": { - "bech32": "^1.1.2", - "bip174": "^2.0.1", - "bip32": "^2.0.4", - "bip66": "^1.1.0", - "bitcoin-ops": "^1.4.0", - "bs58check": "^2.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.3", - "merkle-lib": "^2.0.10", - "pushdata-bitcoin": "^1.0.1", - "randombytes": "^2.0.1", - "tiny-secp256k1": "^1.1.1", - "typeforce": "^1.11.3", - "varuint-bitcoin": "^1.0.4", - "wif": "^2.0.1" - } - } - } - }, - "pbkdf2": { - "version": "3.1.2", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "picocolors": { - "version": "1.0.0", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "picomatch": { - "version": "2.3.1", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" - }, - "pirates": { - "version": "4.0.5", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==" - }, - "pkg-dir": { - "version": "4.2.0", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "pngjs": { - "version": "5.0.0", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" - }, - "posix-character-classes": { - "version": "0.1.1", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==" - }, - "prebuild-install": { - "version": "7.1.1", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", - "requires": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", - "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "pretty-format": { - "version": "29.4.3", - "integrity": "sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA==", - "requires": { - "@jest/schemas": "^29.4.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" - }, - "react-is": { - "version": "18.2.0", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - } - } - }, - "printj": { - "version": "1.1.2", - "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" - }, - "process": { - "version": "0.11.10", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" - }, - "process-nextick-args": { - "version": "2.0.1", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "promise": { - "version": "8.3.0", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "requires": { - "asap": "~2.0.6" - } - }, - "promise-polyfill": { - "version": "6.1.0", - "integrity": "sha512-g0LWaH0gFsxovsU7R5LrrhHhWAWiHRnh1GPrhXnPgYsDkIqjRYUYSZEsej/wtleDrz5xVSIDbeKfidztp2XHFQ==" - }, - "prompts": { - "version": "2.4.2", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "prop-types": { - "version": "15.8.1", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "proper-lockfile": { - "version": "3.2.0", - "integrity": "sha512-iMghHHXv2bsxl6NchhEaFck8tvX3F9cknEEh1SUpguUOBjN7PAAW9BLzmbc1g/mCD1gY3EE2EABBHPJfFdHFmA==", - "requires": { - "graceful-fs": "^4.1.11", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "pseudomap": { - "version": "1.0.2", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" - }, - "public-encrypt": { - "version": "4.0.3", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "pump": { - "version": "3.0.0", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.3.0", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" - }, - "pushdata-bitcoin": { - "version": "1.0.1", - "integrity": "sha512-hw7rcYTJRAl4olM8Owe8x0fBuJJ+WGbMhQuLWOXEMN3PxPCKQHRkhfL+XG0+iXUmSHjkMmb3Ba55Mt21cZc9kQ==", - "requires": { - "bitcoin-ops": "^1.3.0" - } - }, - "qrcode": { - "version": "1.5.1", - "integrity": "sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==", - "requires": { - "dijkstrajs": "^1.0.1", - "encode-utf8": "^1.0.3", - "pngjs": "^5.0.0", - "yargs": "^15.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "6.0.0", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "decamelize": { - "version": "1.2.0", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" - }, - "wrap-ansi": { - "version": "6.2.0", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "yargs": { - "version": "15.4.1", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "qs": { - "version": "6.11.0", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "query-string": { - "version": "6.14.1", - "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", - "requires": { - "decode-uri-component": "^0.2.0", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - } - }, - "querystringify": { - "version": "2.2.0", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "queue-microtask": { - "version": "1.2.3", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "randombytes": { - "version": "2.1.0", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "rc": { - "version": "1.2.8", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" - } - } - }, - "react": { - "version": "18.2.0", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-devtools-core": { - "version": "4.27.2", - "integrity": "sha512-8SzmIkpO87alD7Xr6gWIEa1jHkMjawOZ+6egjazlnjB4UUcbnzGDf/vBJ4BzGuWWEM+pzrxuzsPpcMqlQkYK2g==", - "requires": { - "shell-quote": "^1.6.1", - "ws": "^7" - } - }, - "react-freeze": { - "version": "1.0.3", - "integrity": "sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g==" - }, - "react-is": { - "version": "16.13.1", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "react-localization": { - "version": "git+ssh://git@github.com/BlueWallet/react-localization.git#ae7969a8998128aebf1169f931fb22587dc5f874", - "from": "react-localization@github:BlueWallet/react-localization#ae7969a", - "requires": { - "localized-strings": "github:BlueWallet/localized-strings#178351a7297336618d9ee87277f8e3af9ab7285d" - } - }, - "react-native": { - "version": "0.71.11", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.71.11.tgz", - "integrity": "sha512-++8IxgUe4Ev+bTiFlLfJCdSoE5cReVP1DTpVJ8f/QtzaxA3h1008Y3Xah1Q5vsR4rZcYMO7Pn3af+wOshdQFug==", - "requires": { - "@jest/create-cache-key-function": "^29.2.1", - "@react-native-community/cli": "10.2.4", - "@react-native-community/cli-platform-android": "10.2.0", - "@react-native-community/cli-platform-ios": "10.2.4", - "@react-native/assets": "1.0.0", - "@react-native/normalize-color": "2.1.0", - "@react-native/polyfills": "2.0.0", - "abort-controller": "^3.0.0", - "anser": "^1.4.9", - "base64-js": "^1.1.2", - "deprecated-react-native-prop-types": "^3.0.1", - "event-target-shim": "^5.0.1", - "invariant": "^2.2.4", - "jest-environment-node": "^29.2.1", - "jsc-android": "^250231.0.0", - "memoize-one": "^5.0.0", - "metro-react-native-babel-transformer": "0.73.10", - "metro-runtime": "0.73.10", - "metro-source-map": "0.73.10", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1", - "pretty-format": "^26.5.2", - "promise": "^8.3.0", - "react-devtools-core": "^4.26.1", - "react-native-codegen": "^0.71.5", - "react-native-gradle-plugin": "^0.71.19", - "react-refresh": "^0.4.0", - "react-shallow-renderer": "^16.15.0", - "regenerator-runtime": "^0.13.2", - "scheduler": "^0.23.0", - "stacktrace-parser": "^0.1.3", - "use-sync-external-store": "^1.0.0", - "whatwg-fetch": "^3.0.0", - "ws": "^6.2.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "15.0.15", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz", - "integrity": "sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "requires": { - "async-limiter": "~1.0.0" - } - } - } - }, - "react-native-animatable": { - "version": "1.3.3", - "integrity": "sha512-2ckIxZQAsvWn25Ho+DK3d1mXIgj7tITkrS4pYDvx96WyOttSvzzFeQnM2od0+FUMzILbdHDsDEqZvnz1DYNQ1w==", - "requires": { - "prop-types": "^15.7.2" - } - }, - "react-native-blue-crypto": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-blue-crypto.git#3cb5442425bd835e185284fbc62e84b7155bc441", - "from": "react-native-blue-crypto@github:BlueWallet/react-native-blue-crypto#3cb5442" - }, - "react-native-camera-kit": { - "version": "13.0.0", - "integrity": "sha512-fnkyivCG2xzS+14/doP8pCAYNafYaTyg5J0t+JJltJdgKSHf328OG44Rd+fnbbEOydZxgy/bcuLB24R0kCbynw==", - "requires": { - "lodash": "^4.14.2" - } - }, - "react-native-codegen": { - "version": "0.71.5", - "integrity": "sha512-rfsuc0zkuUuMjFnrT55I1mDZ+pBRp2zAiRwxck3m6qeGJBGK5OV5JH66eDQ4aa+3m0of316CqrJDRzVlYufzIg==", - "requires": { - "@babel/parser": "^7.14.0", - "flow-parser": "^0.185.0", - "jscodeshift": "^0.13.1", - "nullthrows": "^1.1.1" - } - }, - "react-native-crypto": { - "version": "2.2.0", - "integrity": "sha512-eZu9Y8pa8BN9FU2pIex7MLRAi+Cd1Y6bsxfiufKh7sfraAACJvjQTeW7/zcQAT93WMfM+D0OVk+bubvkrbrUkw==", - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.4", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "3.0.8", - "public-encrypt": "^4.0.0", - "randomfill": "^1.0.3" - }, - "dependencies": { - "pbkdf2": { - "version": "3.0.8", - "integrity": "sha512-Bf7yBd61ChnMqPqf+PxHm34Iiq9M9Bkd/+JqzosPOqwG6FiTixtkpCs4PNd38+6/VYRvAxGe/GgPb4Q4GktFzg==", - "requires": { - "create-hmac": "^1.1.2" - } - } - } - }, - "react-native-default-preference": { - "version": "1.4.4", - "integrity": "sha512-h0vtgiSKws3UmMRJykXAVM4ne1SgfoocUcoBD19ewRpQd6wqurE0HJRQGrSxcHK5LdKE7QPSIB1VX3YGIVS8Jg==" - }, - "react-native-device-info": { - "version": "8.7.1", - "integrity": "sha512-cVMZztFa2Qn6qpQa601W61CtUwZQ1KXfqCOeltejAWEXmgIWivC692WGSdtGudj4upSi1UgMSaGcvKjfcpdGjg==" - }, - "react-native-document-picker": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-document-picker.git#857655cdddf17751c0fae1286a9121fda2a6d568", - "integrity": "sha512-1HRak1rmZlf9ixWvtU7JzOsseMQNUsf1YTliFJiXEjNOn3+b5Mx9IdZRx9Nf1jIuqwZHeOJtPjm/PX/rN6kqTg==", - "from": "react-native-document-picker@https://github.com/BlueWallet/react-native-document-picker#857655cdddf17751c0fae1286a9121fda2a6d568", - "requires": { - "invariant": "^2.2.4" - } - }, - "react-native-draggable-flatlist": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-draggable-flatlist.git#ebfddc4877e8f65d5391a748db61b9cd030430ba", - "from": "react-native-draggable-flatlist@github:BlueWallet/react-native-draggable-flatlist#ebfddc4", - "requires": { - "@babel/preset-typescript": "^7.17.12" - } - }, - "react-native-elements": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-3.4.3.tgz", - "integrity": "sha512-VtZc25EecPZyUBER85zFK9ZbY6kkUdcm1ZwJ9hdoGSCr1R/GFgxor4jngOcSYeMvQ+qimd5No44OVJW3rSJECA==", - "requires": { - "@types/react-native-vector-icons": "^6.4.6", - "color": "^3.1.2", - "deepmerge": "^4.2.2", - "hoist-non-react-statics": "^3.3.2", - "lodash.isequal": "^4.5.0", - "opencollective-postinstall": "^2.0.3", - "react-native-ratings": "8.0.4", - "react-native-size-matters": "^0.3.1" - } - }, - "react-native-fingerprint-scanner": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-fingerprint-scanner.git#ce644673681716335d786727bab998f7e632ab5e", - "integrity": "sha512-bS/wNTeah8vACOOuYWRdAi0yKdAqcYqz611PoAtPFs/EaYD/Y8vw+/1jas5DUvTKnAoveCcO1ICVgCsT7XTdKA==", - "from": "react-native-fingerprint-scanner@https://github.com/BlueWallet/react-native-fingerprint-scanner#ce644673681716335d786727bab998f7e632ab5e" - }, - "react-native-fs": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.20.0.tgz", - "integrity": "sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ==", - "requires": { - "base-64": "^0.1.0", - "utf8": "^3.0.0" - } - }, - "react-native-gesture-handler": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.9.0.tgz", - "integrity": "sha512-a0BcH3Qb1tgVqUutc6d3VuWQkI1AM3+fJx8dkxzZs9t06qA27QgURYFoklpabuWpsUTzuKRpxleykp25E8m7tg==", - "requires": { - "@egjs/hammerjs": "^2.0.17", - "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4", - "lodash": "^4.17.21", - "prop-types": "^15.7.2" - } - }, - "react-native-gradle-plugin": { - "version": "0.71.19", - "resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.71.19.tgz", - "integrity": "sha512-1dVk9NwhoyKHCSxcrM6vY6cxmojeATsBobDicX0ZKr7DgUF2cBQRTKsimQFvzH8XhOVXyH8p4HyDSZNIFI8OlQ==" - }, - "react-native-handoff": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-handoff.git#31d005f93d31099d0e564590a3bbd052b8a02b39", - "integrity": "sha512-kyJxMjkMvlebreExkmxRuId6w+pQJlblXKd0tpDyWGQzcL9RmFyEsRTn3ZUoRzu/XKNGhs/uKUgZWzgc/qBkpQ==", - "from": "react-native-handoff@https://github.com/BlueWallet/react-native-handoff#31d005f93d31099d0e564590a3bbd052b8a02b39" - }, - "react-native-haptic-feedback": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/react-native-haptic-feedback/-/react-native-haptic-feedback-2.0.3.tgz", - "integrity": "sha512-7+qvcxXZts/hA+HOOIFyM1x9m9fn/TJVSTgXaoQ8uT4gLc97IMvqHQ559tDmnlth+hHMzd3HRMpmRLWoKPL0DA==" - }, - "react-native-idle-timer": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-idle-timer.git#8587876d68ab5920e79619726aeca9e672beaf2b", - "integrity": "sha512-d24QXgHUkwu+BTp676Pb7ng7u01iiw3YBBY+DYvurkMAHRoB9c+wj32oB4sLq07W+LbETyMEuDlLUxTOasqd3Q==", - "from": "react-native-idle-timer@https://github.com/BlueWallet/react-native-idle-timer#8587876d68ab5920e79619726aeca9e672beaf2b" - }, - "react-native-image-picker": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-4.8.5.tgz", - "integrity": "sha512-+pQxkjO8cKv4RKTHOFS0fSHQ11HkWgb+imUPSOS8mwoChkR33aSuzV/6P4t9JCJgsus4qLAlB6BUosdIxw7GTA==" - }, - "react-native-ios-context-menu": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-ios-context-menu.git#e5c1217cd220bfab6e6d9a7c65838545082e3f8e", - "from": "react-native-ios-context-menu@github:BlueWallet/react-native-ios-context-menu#v1.15.3", - "requires": { - "@dominicstop/ts-event-emitter": "^1.1.0" - } - }, - "react-native-iphone-x-helper": { - "version": "1.3.1", - "integrity": "sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==" - }, - "react-native-keychain": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-8.1.2.tgz", - "integrity": "sha512-bhHEui+yMp3Us41NMoRGtnWEJiBE0g8tw5VFpq4mpmXAx6XJYahuM6K3WN5CsUeUl83hYysSL9oFZNKSTPSvYw==" - }, - "react-native-linear-gradient": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.2.tgz", - "integrity": "sha512-hgmCsgzd58WNcDCyPtKrvxsaoETjb/jLGxis/dmU3Aqm2u4ICIduj4ECjbil7B7pm9OnuTkmpwXu08XV2mpg8g==" - }, - "react-native-localize": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-3.0.2.tgz", - "integrity": "sha512-/l/oE1LVNgIRRhLbhmfFMHiWV0xhUn0A0iz1ytLVRYywL7FTp8Rx2vkJS/q/RpExDvV7yLw2493XZBYIM1dnLQ==" - }, - "react-native-modal": { - "version": "13.0.1", - "integrity": "sha512-UB+mjmUtf+miaG/sDhOikRfBOv0gJdBU2ZE1HtFWp6UixW9jCk/bhGdHUgmZljbPpp0RaO/6YiMmQSSK3kkMaw==", - "requires": { - "prop-types": "^15.6.2", - "react-native-animatable": "1.3.3" - } - }, - "react-native-navigation-bar-color": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-navigation-bar-color.git#3b2894ae62fbce99a3bd24105f0921cebaef5c94", - "integrity": "sha512-tFVsXyfvEQ8FJM9r5k7buLAMC1QMHEo3uFdXYDw86Y3iQiHA4QnE2KCiJxjF7NhkL6HFVRSDSVv0r2qEhs1pMg==", - "from": "react-native-navigation-bar-color@https://github.com/BlueWallet/react-native-navigation-bar-color#3b2894ae62fbce99a3bd24105f0921cebaef5c94" - }, - "react-native-obscure": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb", - "integrity": "sha512-bmzbnlXII8hW7steqwouzQW9cJ+mdA8HJ8pWkb0KD60jC5dYgJ0e66wgUiSTDNFPrvlEKriE4eEanoJFj7Q9pg==", - "from": "react-native-obscure@https://github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb" - }, - "react-native-passcode-auth": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-passcode-auth.git#a2ff977ba92b36f8d0a5567f59c05cc608e8bd12", - "integrity": "sha512-8FL4NDMZZVrbHr1f4555dV+GY3PLpmSbJ1wIbdW1r6zSaFe59g9ns4sdLliisjO+RvyDJP7UDPDaeu+2iJ26Bg==", - "from": "react-native-passcode-auth@https://github.com/BlueWallet/react-native-passcode-auth#a2ff977ba92b36f8d0a5567f59c05cc608e8bd12" - }, - "react-native-privacy-snapshot": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-privacy-snapshot.git#529e4627d93f67752a27e82a040ff7b64dca0783", - "integrity": "sha512-bO73UmkI0KpAzk3766z77qBdArwOaBCr58q5H0qDfn0jf5HonYoEkJRDRwHr7SpDxrSu2Nuoz2AokxLyb3/KXw==", - "from": "react-native-privacy-snapshot@https://github.com/BlueWallet/react-native-privacy-snapshot#529e4627d93f67752a27e82a040ff7b64dca0783" - }, - "react-native-prompt-android": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-prompt-android.git#ed168d66fed556bc2ed07cf498770f058b78a376", - "integrity": "sha512-DcHZ4mMVF0HlMui8qq81kJcY+SNx2dXa9YuKDBoENCJxrPbjc++/XsSHpqQOOLV2wR+zItDDtvNkJRqI8g5JRQ==", - "from": "react-native-prompt-android@https://github.com/BlueWallet/react-native-prompt-android#ed168d66fed556bc2ed07cf498770f058b78a376" - }, - "react-native-push-notification": { - "version": "8.1.1", - "integrity": "sha512-XpBtG/w+a6WXTxu6l1dNYyTiHnbgnvjoc3KxPTxYkaIABRmvuJZkFxqruyGvfCw7ELAlZEAJO+dthdTabCe1XA==" - }, - "react-native-qrcode-svg": { - "version": "6.2.0", - "integrity": "sha512-rb2PgUwT8QpQyReVYNvzRY84AHsMh81354Tnkfp6MfqRbcdJURhnBWLBOO11pLMS6eXiwlq4SkcQxy88hRq+Dw==", - "requires": { - "prop-types": "^15.8.0", - "qrcode": "^1.5.1" - } - }, - "react-native-quick-actions": { - "version": "0.3.13", - "integrity": "sha512-Vz13a0+NV0mzCh/29tNt0qDzWPh8i2srTQW8eCSzGFDArnVm1COTOhTD0FY0hWHlxRY0ahvX+BlezTDvsyAuMA==" - }, - "react-native-randombytes": { - "version": "3.6.1", - "integrity": "sha512-qxkwMbOZ0Hff1V7VqpaWrR6ItkA+oF6bnI79Qp9F3Tk8WBsdKDi6m1mi3dEdFWePoRLrhJ2L03rU0yabst1tVw==", - "requires": { - "buffer": "^4.9.1", - "sjcl": "^1.0.3" - }, - "dependencies": { - "buffer": { - "version": "4.9.2", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - } - } - }, - "react-native-rate": { - "version": "1.2.12", - "integrity": "sha512-A/z3s7Zth08aXcJnru6S4p71NG8acx2w5LhIfItwTJUbQruNJugk8WrN51dLBCSDv8W33kbS5YoUT4M9jOP5gA==" - }, - "react-native-ratings": { - "version": "8.0.4", - "integrity": "sha512-Xczu5lskIIRD6BEdz9A0jDRpEck/SFxRqiglkXi0u67yAtI1/pcJC76P4MukCbT8K4BPVl+42w83YqXBoBRl7A==", - "requires": { - "lodash": "^4.17.15" - } - }, - "react-native-reanimated": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-2.17.0.tgz", - "integrity": "sha512-bVy+FUEaHXq4i+aPPqzGeor1rG4scgVNBbBz21ohvC7iMpB9IIgvGsmy1FAoodZhZ5sa3EPF67Rcec76F1PXlQ==", - "requires": { - "@babel/plugin-transform-object-assign": "^7.16.7", - "@babel/preset-typescript": "^7.16.7", - "invariant": "^2.2.4", - "lodash.isequal": "^4.5.0", - "setimmediate": "^1.0.5", - "string-hash-64": "^1.0.3" - } - }, - "react-native-safe-area-context": { - "version": "3.4.1", - "integrity": "sha512-xfpVd0CiZR7oBhuwJ2HcZMehg5bjha1Ohu1XHpcT+9ykula0TgovH2BNU0R5Krzf/jBR1LMjR6VabxdlUjqxcA==" - }, - "react-native-safe-modules": { - "version": "1.0.3", - "integrity": "sha512-DUxti4Z+AgJ/ZsO5U7p3uSCUBko8JT8GvFlCeOXk9bMd+4qjpoDvMYpfbixXKgL88M+HwmU/KI1YFN6gsQZyBA==", - "requires": { - "dedent": "^0.6.0" - }, - "dependencies": { - "dedent": { - "version": "0.6.0", - "integrity": "sha512-cSfRWjXJtZQeRuZGVvDrJroCR5V2UvBNUMHsPCdNYzuAG8b9V8aAy3KUcdQrGQPXs17Y+ojbPh1aOCplg9YR9g==" - } - } - }, - "react-native-screens": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.20.0.tgz", - "integrity": "sha512-joWUKWAVHxymP3mL9gYApFHAsbd9L6ZcmpoZa6Sl3W/82bvvNVMqcfP7MeNqVCg73qZ8yL4fW+J/syusHleUgg==", - "requires": { - "react-freeze": "^1.0.0", - "warn-once": "^0.1.0" - } - }, - "react-native-secure-key-store": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-secure-key-store.git#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc", - "from": "react-native-secure-key-store@https://github.com/BlueWallet/react-native-secure-key-store#2076b48" - }, - "react-native-share": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-8.2.2.tgz", - "integrity": "sha512-kVCI/cT0GnuYUTXe6mAimrjrnt4VWoRfrWqJZjFeoYFqAyOEfos84RC4eZlZnOT5eVtmTXRIkor5vgSkKOlZhw==" - }, - "react-native-size-matters": { - "version": "0.3.1", - "integrity": "sha512-mKOfBLIBFBcs9br1rlZDvxD5+mAl8Gfr5CounwJtxI6Z82rGrMO+Kgl9EIg3RMVf3G855a85YVqHJL2f5EDRlw==" - }, - "react-native-svg": { - "version": "13.13.0", - "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.13.0.tgz", - "integrity": "sha512-L8y8uEiMG0Tr++Nb2+24wlMuv18+bmq/CMoFFtTUlEqVvGCoK2ea8WamPl/9bV8gjL+Rngg5NqEBvKS23sbYoA==", - "requires": { - "css-select": "^5.1.0", - "css-tree": "^1.1.3" - } - }, - "react-native-tcp-socket": { - "version": "5.6.2", - "integrity": "sha512-doijFOAJd9p8KmduhfbZaPfqRVd3CZuTLAimJx0yxIqFWy/EDPGHeFVrOEOqRZ3lWBVDcssiCIQJhV0baKu5Pg==", - "requires": { - "buffer": "^5.4.3", - "eventemitter3": "^4.0.7" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, - "react-native-tor": { - "version": "0.1.8", - "integrity": "sha512-rRArwpqTQoUrQ3WQxc1WLkk1Eg/yjiwU775AxnSt6E7Nfy1V9H6+R9reMD5PJ/39qcuDYv3F7i3MR8Rjy7lOZA==", - "requires": { - "@types/async": "^3.2.6", - "async": "^3.2.0" - } - }, - "react-native-vector-icons": { - "version": "9.2.0", - "integrity": "sha512-wKYLaFuQST/chH3AJRjmOLoLy3JEs1JR6zMNgTaemFpNoXs0ztRnTxcxFD9xhX7cJe1/zoN5BpQYe7kL0m5yyA==", - "requires": { - "prop-types": "^15.7.2", - "yargs": "^16.1.1" - } - }, - "react-native-watch-connectivity": { - "version": "1.1.0", - "integrity": "sha512-s+zlKOVENRXgkVQvJt5f73CyqpC6ZhKlRsXLybtaFIsR1KR3ARzExm0yTm3DAb5K9AqtCpYX+1SOd4d0Af2ZNQ==", - "requires": { - "lodash.sortby": "^4.7.0" - } - }, - "react-native-webview": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-12.4.0.tgz", - "integrity": "sha512-wYzTfNADidmqv6bY+x6NUfX8+uBR9mmF1CO1NOvY4oD2vv+D4rA0XwcwAe2D7RevXUy3fmuTT93kFQcgo8fEhg==", - "requires": { - "escape-string-regexp": "2.0.0", - "invariant": "2.2.4" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - } - } - }, - "react-native-widget-center": { - "version": "git+ssh://git@github.com/BlueWallet/react-native-widget-center.git#a128c389526d55afdd67937494f2fec224dd0009", - "from": "react-native-widget-center@https://github.com/BlueWallet/react-native-widget-center#a128c38" - }, - "react-refresh": { - "version": "0.4.3", - "integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==" - }, - "react-shallow-renderer": { - "version": "16.15.0", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "requires": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - } - }, - "react-test-renderer": { - "version": "18.2.0", - "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", - "dev": true, - "requires": { - "react-is": "^18.2.0", - "react-shallow-renderer": "^16.15.0", - "scheduler": "^0.23.0" - }, - "dependencies": { - "react-is": { - "version": "18.2.0", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - } - } - }, - "read-pkg": { - "version": "5.2.0", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" - } - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readline": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==" - }, - "realm": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/realm/-/realm-12.0.0.tgz", - "integrity": "sha512-QaFnn92eCwpWCvbnxE26N0mAHhuXRwtM23JF0tA5fKtTA+djj1+LhuT/iGJryCrGVbN8m++EOBsvWgGG8hpsuw==", - "requires": { - "bson": "^4.7.2", - "debug": "^4.3.4", - "node-fetch": "^2.6.9", - "node-machine-id": "^1.1.12", - "prebuild-install": "^7.1.1" - } - }, - "recast": { - "version": "0.20.5", - "integrity": "sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==", - "requires": { - "ast-types": "0.14.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" - } - }, - "reduce-flatten": { - "version": "2.0.0", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==" - }, - "regenerate": { - "version": "1.4.2", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "regenerate-unicode-properties": { - "version": "10.1.0", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.11", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "regenerator-transform": { - "version": "0.15.1", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regex-not": { - "version": "1.0.2", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexp.prototype.flags": { - "version": "1.4.3", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, - "regexpu-core": { - "version": "5.3.1", - "integrity": "sha512-nCOzW2V/X15XpLsK2rlgdwrysrBq+AauCn+omItIz4R1pIcmeot5zvjdmOBRLzEH/CkC6IxMJVmxDe3QcMuNVQ==", - "requires": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - } - }, - "regjsparser": { - "version": "0.9.1", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" - } - } - }, - "repeat-element": { - "version": "1.1.4", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==" - }, - "repeat-string": { - "version": "1.6.1", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==" - }, - "require-directory": { - "version": "2.1.1", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - }, - "require-from-string": { - "version": "2.0.2", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" - }, - "require-main-filename": { - "version": "2.0.0", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "requires-port": { - "version": "1.0.0", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, - "resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "requires": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" - }, - "resolve-url": { - "version": "0.2.1", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==" - }, - "resolve.exports": { - "version": "1.1.1", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", - "dev": true - }, - "restore-cursor": { - "version": "3.1.0", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" - }, - "retry": { - "version": "0.12.0", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==" - }, - "reusify": { - "version": "1.0.4", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "rn-ldk": { - "version": "git+ssh://git@github.com/BlueWallet/rn-ldk.git#ab0ce31e4ec8c208fe16954ecbcaba5df60f56c6", - "from": "rn-ldk@github:BlueWallet/rn-ldk#v0.8.4" - }, - "rn-nodeify": { - "version": "10.3.0", - "integrity": "sha512-EZB3M4M5i8yySCWF7AAZ31xU7cpdLuIKMlVxXji9t0aY8Ojy3BAyRt1sTp0OwBgy1ejShmlIu2L4f8mToJ+uvg==", - "requires": { - "@yarnpkg/lockfile": "^1.0.0", - "deep-equal": "^1.0.0", - "findit": "^2.0.0", - "fs-extra": "^0.22.1", - "minimist": "^1.1.2", - "object.pick": "^1.1.1", - "run-parallel": "^1.1.2", - "semver": "^5.0.1", - "xtend": "^4.0.0" - }, - "dependencies": { - "fs-extra": { - "version": "0.22.1", - "integrity": "sha512-M7CuxS2f9k/5il8ufmLiCtT7B2O2JLoTZi83ZtyEJMG67cTn87fNULYWtno5Vm31TxmSRE0nkA9GxaRR+y3XTA==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "rimraf": "^2.2.8" - } - }, - "jsonfile": { - "version": "2.4.0", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "rimraf": { - "version": "2.7.1", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "run-applescript": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", - "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", - "dev": true, - "requires": { - "execa": "^5.0.0" - } - }, - "run-parallel": { - "version": "1.2.0", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safe-json-stringify": { - "version": "1.2.0", - "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", - "optional": true - }, - "safe-regex": { - "version": "1.1.0", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "requires": { - "ret": "~0.1.10" - } - }, - "safe-regex-test": { - "version": "1.0.0", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - } - }, - "safer-buffer": { - "version": "2.1.2", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sanitize-filename": { - "version": "1.6.3", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", - "requires": { - "truncate-utf8-bytes": "^1.0.0" - } - }, - "scheduler": { - "version": "0.23.0", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "scryptsy": { - "version": "2.1.0", - "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==" - }, - "secp256k1": { - "version": "3.8.0", - "integrity": "sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw==", - "requires": { - "bindings": "^1.5.0", - "bip66": "^1.1.5", - "bn.js": "^4.11.8", - "create-hash": "^1.2.0", - "drbg.js": "^1.0.1", - "elliptic": "^6.5.2", - "nan": "^2.14.0", - "safe-buffer": "^5.1.2" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - } - } - }, - "serialize-error": { - "version": "8.1.0", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", - "requires": { - "type-fest": "^0.20.2" - }, - "dependencies": { - "type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" - } - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, - "set-value": { - "version": "2.0.1", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "setimmediate": { - "version": "1.0.5", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "sha.js": { - "version": "2.4.11", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shallow-clone": { - "version": "3.0.1", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "1.2.0", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==" - }, - "shell-quote": { - "version": "1.8.0", - "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==" - }, - "side-channel": { - "version": "1.0.4", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "simple-concat": { - "version": "1.0.1", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" - }, - "simple-get": { - "version": "4.0.1", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "requires": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "simple-swizzle": { - "version": "0.2.2", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - } - } - }, - "sisteransi": { - "version": "1.0.5", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, - "sjcl": { - "version": "1.0.8", - "integrity": "sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ==" - }, - "slash": { - "version": "3.0.0", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - }, - "slice-ansi": { - "version": "2.1.0", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - } - }, - "slip39": { - "version": "git+ssh://git@github.com/BlueWallet/slip39-js.git#35619ed112fa022de1f5a3b6e2996dd3025472b2", - "from": "slip39@https://github.com/BlueWallet/slip39-js" - }, - "snapdragon": { - "version": "0.8.2", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - }, - "ms": { - "version": "2.0.0", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "source-map": { - "version": "0.5.7", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "source-map": { - "version": "0.6.1", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-resolve": { - "version": "0.5.3", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-url": { - "version": "0.4.1", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" - }, - "spdx-correct": { - "version": "3.1.1", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "spdx-expression-parse": { - "version": "3.0.1", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.12", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==" - }, - "split-on-first": { - "version": "1.1.0", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" - }, - "split-string": { - "version": "3.1.0", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, - "stack-generator": { - "version": "2.0.10", - "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", - "requires": { - "stackframe": "^1.3.4" - } - }, - "stack-utils": { - "version": "2.0.6", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - } - } - }, - "stackframe": { - "version": "1.3.4", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" - }, - "stacktrace-parser": { - "version": "0.1.10", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "requires": { - "type-fest": "^0.7.1" - }, - "dependencies": { - "type-fest": { - "version": "0.7.1", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==" - } - } - }, - "static-extend": { - "version": "0.1.2", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" - }, - "stream-browserify": { - "version": "3.0.0", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "requires": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "stream-chain": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", - "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==" - }, - "stream-json": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.7.5.tgz", - "integrity": "sha512-NSkoVduGakxZ8a+pTPUlcGEeAGQpWL9rKJhOFCV+J/QtdQUEU5vtBgVg6eJXn8JB8RZvpbJWZGvXkhz70MLWoA==", - "requires": { - "stream-chain": "^2.2.5" - } - }, - "strict-uri-encode": { - "version": "2.0.0", - "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==" - }, - "string_decoder": { - "version": "1.3.0", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-hash-64": { - "version": "1.0.3", - "integrity": "sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==" - }, - "string-length": { - "version": "4.0.2", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-natural-compare": { - "version": "3.0.1", - "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "3.0.0", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - } - } - }, - "string.prototype.matchall": { - "version": "4.0.8", - "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4" - } - }, - "string.prototype.trimend": { - "version": "1.0.6", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "string.prototype.trimstart": { - "version": "1.0.6", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "strip-ansi": { - "version": "6.0.1", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==" - }, - "strip-final-newline": { - "version": "2.0.0", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" - }, - "strip-json-comments": { - "version": "3.1.1", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" - }, - "sudo-prompt": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==" - }, - "supports-color": { - "version": "5.5.0", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.3.0", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "synckit": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", - "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", - "dev": true, - "requires": { - "@pkgr/utils": "^2.3.1", - "tslib": "^2.5.0" - } - }, - "table-layout": { - "version": "1.0.2", - "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", - "requires": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" - }, - "dependencies": { - "array-back": { - "version": "4.0.2", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==" - }, - "typical": { - "version": "5.2.0", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==" - } - } - }, - "tar-fs": { - "version": "2.1.1", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - }, - "dependencies": { - "chownr": { - "version": "1.1.4", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - } - } - }, - "tar-stream": { - "version": "2.2.0", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "telnet-client": { - "version": "1.2.8", - "integrity": "sha512-W+w4k3QAmULVNhBVT2Fei369kGZCh/TH25M7caJAXW+hLxwoQRuw0di3cX4l0S9fgH3Mvq7u+IFMoBDpEw/eIg==", - "requires": { - "bluebird": "^3.5.4" - } - }, - "temp": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha512-jtnWJs6B1cZlHs9wPG7BrowKxZw/rf6+UpGAkr8AaYmiTyTO7zQlLoST8zx/8TcUPnZmeBoB+H8ARuHZaSijVw==", - "requires": { - "os-tmpdir": "^1.0.0", - "rimraf": "~2.2.6" - }, - "dependencies": { - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==" - } - } - }, - "temp-dir": { - "version": "1.0.0", - "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==" - }, - "tempfile": { - "version": "2.0.0", - "integrity": "sha512-ZOn6nJUgvgC09+doCEF3oB+r3ag7kUvlsXEGX069QRD60p+P3uP7XG9N2/at+EyIRGSN//ZY3LyEotA1YpmjuA==", - "requires": { - "temp-dir": "^1.0.0", - "uuid": "^3.0.1" - } - }, - "terminal-link": { - "version": "2.1.1", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "terser": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.0.tgz", - "integrity": "sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==", - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "test-exclude": { - "version": "6.0.0", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "tiny-secp256k1": { - "version": "1.1.6", - "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", - "requires": { - "bindings": "^1.3.0", - "bn.js": "^4.11.8", - "create-hmac": "^1.1.7", - "elliptic": "^6.4.0", - "nan": "^2.13.2" - } - }, - "titleize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", - "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" - }, - "to-fast-properties": { - "version": "2.0.0", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" - }, - "to-object-path": { - "version": "0.3.0", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "tr46": { - "version": "0.0.3", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "trace-event-lib": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/trace-event-lib/-/trace-event-lib-1.3.1.tgz", - "integrity": "sha512-RO/TD5E9RNqU6MhOfi/njFWKYhrzOJCpRXlEQHgXwM+6boLSrQnOZ9xbHwOXzC+Luyixc7LNNSiTsqTVeF7I1g==", - "requires": { - "browser-process-hrtime": "^1.0.0", - "lodash": "^4.17.21" - } - }, - "truncate-utf8-bytes": { - "version": "1.0.2", - "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", - "requires": { - "utf8-byte-length": "^1.0.1" - } - }, - "ts-api-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", - "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", - "dev": true - }, - "ts-jest": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", - "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, - "requires": { - "@jest/types": "^29.4.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs-parser": { - "version": "21.1.1", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } + "node_modules/type-detect": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "tsconfig-paths": { - "version": "3.14.1", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "node_modules/type-fest": { + "version": "0.21.3", "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" }, - "dependencies": { - "json5": { - "version": "1.0.2", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" - }, - "tsutils": { - "version": "3.21.0", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", "dependencies": { - "tslib": { - "version": "1.14.1", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "tunnel-agent": { - "version": "0.6.0", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "requires": { - "safe-buffer": "^5.0.1" + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" } }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/typed-array-byte-length": { + "version": "1.0.3", "dev": true, - "requires": { - "prelude-ls": "^1.2.1" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "type-detect": { - "version": "4.0.8", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" - }, - "type-fest": { - "version": "0.21.3", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "typed-array-length": { + "node_modules/typed-array-byte-offset": { "version": "1.0.4", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", "dev": true, - "requires": { - "call-bind": "^1.0.2", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "typedarray": { + "node_modules/typedarray": { "version": "0.0.6", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + "license": "MIT" }, - "typedarray-to-buffer": { + "node_modules/typedarray-to-buffer": { "version": "3.1.5", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "is-typedarray": "^1.0.0" } }, - "typeforce": { + "node_modules/typeforce": { "version": "1.18.0", - "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + "license": "MIT" }, - "typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true + "node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } }, - "typical": { + "node_modules/typical": { "version": "4.0.0", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "requires": { - "commander": "~2.13.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" - } + "node_modules/uint8array-tools": { + "version": "0.0.9", + "license": "MIT", + "engines": { + "node": ">=14.0.0" } }, - "unbox-primitive": { - "version": "1.0.2", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "node_modules/unbox-primitive": { + "version": "1.1.0", "dev": true, - "requires": { - "call-bind": "^1.0.2", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" + "node_modules/undici-types": { + "version": "6.21.0", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "unicode-match-property-ecmascript": { + "node_modules/unicode-match-property-ecmascript": { "version": "2.0.0", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "requires": { + "license": "MIT", + "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==" + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "union-value": { - "version": "1.0.1", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "universalify": { + "node_modules/universalify": { "version": "0.1.2", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } }, - "unpipe": { + "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "unset-value": { - "version": "1.0.0", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "requires": { - "isarray": "1.0.0" - } - } - } + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" }, - "has-values": { - "version": "0.1.4", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==" + { + "type": "github", + "url": "https://github.com/sponsors/ai" } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.10", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { + "node_modules/uri-js": { "version": "4.4.1", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==" + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", - "requires": { + "node_modules/url": { + "version": "0.11.4", + "license": "MIT", + "dependencies": { "punycode": "^1.4.1", - "qs": "^6.11.0" + "qs": "^6.12.3" }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - } + "engines": { + "node": ">= 0.4" } }, - "url-join": { - "version": "4.0.1", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" - }, - "url-parse": { - "version": "1.5.10", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" + "node_modules/use-latest-callback": { + "version": "0.2.6", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8" } }, - "use": { - "version": "3.1.1", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" - }, - "use-sync-external-store": { - "version": "1.2.0", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } }, - "utf8": { + "node_modules/utf8": { "version": "3.0.0", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" + "license": "MIT" }, - "utf8-byte-length": { - "version": "1.0.4", - "integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==" + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "license": "(WTFPL OR MIT)" }, - "util": { + "node_modules/util": { "version": "0.12.5", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "requires": { + "license": "MIT", + "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", @@ -37921,247 +18699,463 @@ "which-typed-array": "^1.1.2" } }, - "util-deprecate": { + "node_modules/util-deprecate": { "version": "1.0.2", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "license": "MIT" }, - "utils-merge": { + "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "8.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=10.12.0" + } }, - "uuid": { - "version": "3.4.0", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "dev": true, + "license": "MIT" }, - "v8-to-istanbul": { - "version": "8.1.1", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "node_modules/v8-to-istanbul/node_modules/source-map": { + "version": "0.7.4", "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/valibot": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.3.1.tgz", + "integrity": "sha512-sfdRir/QFM0JaF22hqTroPc5xy4DimuGQVKFrzF1YfGwaS1nJot3Y8VqMdLO2Lg27fMzat2yD3pY5PbAYO39Gg==", + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true + "peerDependenciesMeta": { + "typescript": { + "optional": true } } }, - "validate-npm-package-license": { + "node_modules/validate-npm-package-license": { "version": "3.0.4", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { + "license": "Apache-2.0", + "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, - "varuint-bitcoin": { + "node_modules/varuint-bitcoin": { "version": "1.1.2", - "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", - "requires": { + "license": "MIT", + "dependencies": { "safe-buffer": "^5.1.1" } }, - "vary": { + "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "vlq": { + "node_modules/vlq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", - "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==" + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "license": "MIT" }, - "walker": { + "node_modules/walker": { "version": "1.0.8", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "requires": { + "license": "Apache-2.0", + "dependencies": { "makeerror": "1.0.12" } }, - "warn-once": { + "node_modules/warn-once": { "version": "0.1.1", - "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==" + "license": "MIT" }, - "wcwidth": { + "node_modules/wcwidth": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "requires": { + "license": "MIT", + "dependencies": { "defaults": "^1.0.3" } }, - "webidl-conversions": { + "node_modules/webidl-conversions": { "version": "3.0.1", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "dev": true, + "license": "BSD-2-Clause" }, - "whatwg-fetch": { - "version": "3.6.2", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "license": "MIT" }, - "whatwg-url": { + "node_modules/whatwg-url": { "version": "5.0.0", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { + "dev": true, + "license": "MIT", + "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, - "which": { + "node_modules/which": { "version": "1.3.1", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { + "license": "ISC", + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "which-boxed-primitive": { + "node_modules/which-collection": { "version": "1.0.2", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "which-module": { - "version": "2.0.0", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==" + "node_modules/which-module": { + "version": "2.0.1", + "license": "ISC" }, - "which-typed-array": { - "version": "1.1.9", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "node_modules/which-typed-array": { + "version": "1.1.19", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "wif": { + "node_modules/wif": { "version": "2.0.6", - "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", - "requires": { + "license": "MIT", + "dependencies": { "bs58check": "<3.0.0" } }, - "wordwrapjs": { + "node_modules/winston": { + "version": "3.17.0", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrapjs": { "version": "4.0.1", - "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", - "requires": { + "license": "MIT", + "dependencies": { "reduce-flatten": "^2.0.0", "typical": "^5.2.0" }, - "dependencies": { - "typical": { - "version": "5.2.0", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==" - } + "engines": { + "node": ">=8.0.0" } }, - "wrap-ansi": { + "node_modules/wordwrapjs/node_modules/typical": { + "version": "5.2.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { "version": "7.0.0", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { + "license": "MIT", + "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - } + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "license": "ISC" }, - "write-file-atomic": { + "node_modules/write-file-atomic": { "version": "3.0.3", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", "signal-exit": "^3.0.2", "typedarray-to-buffer": "^3.1.5" } }, - "ws": { - "version": "7.5.9", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" + "node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "license": "MIT", + "dependencies": { + "async-limiter": "~1.0.0" + } }, - "xtend": { - "version": "4.0.2", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "license": "MIT", + "engines": { + "node": ">=8.0" + } }, - "y18n": { + "node_modules/y18n": { "version": "5.0.8", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + "license": "ISC", + "engines": { + "node": ">=10" + } }, - "yallist": { + "node_modules/yallist": { "version": "3.1.1", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } }, - "yargs": { - "version": "16.2.0", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "requires": { - "cliui": "^7.0.2", + "node_modules/yargs": { + "version": "17.7.2", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, - "yargs-parser": { - "version": "20.2.9", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + "node_modules/yargs-parser": { + "version": "21.1.1", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "yargs-unparser": { + "node_modules/yargs-unparser": { "version": "2.0.0", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "requires": { + "license": "MIT", + "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" - } + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "yocto-queue": { + "node_modules/yocto-queue": { "version": "0.1.0", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } } } } diff --git a/package.json b/package.json index b5a4aed3207..c94478828c0 100644 --- a/package.json +++ b/package.json @@ -1,219 +1,199 @@ { "name": "bluewallet", - "version": "6.4.9", + "version": "8.0.0", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/BlueWallet/BlueWallet.git" }, + "codegenConfig": { + "name": "BlueWalletSpec", + "type": "all", + "jsSrcsDir": "./codegen", + "android": { + "javaPackageName": "io.bluewallet.bluewallet" + } + }, "devDependencies": { - "@babel/core": "^7.20.0", - "@babel/runtime": "^7.20.0", + "@babel/core": "^7.26.0", + "@babel/preset-env": "^7.26.0", + "@babel/runtime": "^7.26.0", "@jest/reporters": "^27.5.1", - "@react-native-community/eslint-config": "^3.2.0", - "@tsconfig/react-native": "^3.0.2", + "@react-native/eslint-config": "^0.85.3", + "@react-native/jest-preset": "0.85.3", + "@react-native/js-polyfills": "^0.85.3", + "@react-native/metro-babel-transformer": "^0.85.3", + "@react-native/typescript-config": "^0.85.3", + "@testing-library/react-native": "^13.0.1", + "@types/bip38": "^3.1.2", "@types/bs58check": "^2.1.0", "@types/create-hash": "^1.2.2", - "@types/jest": "^29.4.0", - "@types/react": "^18.2.16", - "@types/react-native": "^0.72.0", - "@types/react-test-renderer": "^18.0.0", - "@typescript-eslint/eslint-plugin": "^6.2.0", - "@typescript-eslint/parser": "^6.2.0", - "eslint": "^8.45.0", - "eslint-config-prettier": "^8.8.0", - "eslint-config-standard": "^17.0.0", + "@types/crypto-js": "^4.2.2", + "@types/jest": "^29.5.13", + "@types/react": "^19.2.0", + "@types/react-test-renderer": "^19.1.0", + "@types/wif": "^2.0.5", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-config-standard": "^17.1.0", "eslint-config-standard-jsx": "^11.0.0", "eslint-config-standard-react": "^13.0.0", - "eslint-plugin-import": "^2.27.0", - "eslint-plugin-n": "^16.0.1", - "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jest": "^28.7.0", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-react": "^7.33.0", - "eslint-plugin-react-native": "^4.0.0", - "jest": "^29.4.2", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-native": "^4.1.0", + "jest": "^29.6.3", + "jest-environment-node": "^29.7.0", "node-fetch": "^2.6.7", - "prettier": "^3.0.0", - "react-test-renderer": "18.2.0", + "patch-package": "^8.0.0", + "prettier": "^3.2.5", + "react-test-renderer": "19.2.3", "ts-jest": "^29.1.1", - "typescript": "^5.1.6" + "typescript": "^5.9.3" }, "engines": { - "node": ">=10.16.0", - "npm": ">=6.9.0" + "node": ">= 22.11.0" }, "scripts": { - "clean": "cd android/; ./gradlew clean; cd ..; rm -r -f /tmp/metro-cache/; rm -r -f node_modules/; npm cache clean --force; npm i; npm start -- --reset-cache", + "clean": "cd android/; ./gradlew clean; cd ..; rm -r -f android/app/.cxx android/.cxx android/build; rm -r -f /tmp/metro-cache/; rm -r -f node_modules/; npm cache clean --force; npm i", + "cleanstart": "npm run clean && npm start -- --reset-cache", "clean:ios": "rm -fr node_modules && rm -fr ios/Pods && npm i && cd ios && pod update && cd ..; npm start -- --reset-cache", "releasenotes2json": "./scripts/release-notes.sh > release-notes.txt; node -e 'console.log(JSON.stringify(require(\"fs\").readFileSync(\"release-notes.txt\", \"utf8\")));' > release-notes.json", "branch2json": "./scripts/current-branch.sh > current-branch.json", - "podinstall": "./scripts/podinstall.sh", - "start": "node node_modules/react-native/local-cli/cli.js start", + "start": "react-native start", "android": "react-native run-android", - "android:clean": "cd android; ./gradlew clean ; cd .. ; npm run android", + "android:relaunch": "adb shell am force-stop io.bluewallet.bluewallet; adb shell monkey -p io.bluewallet.bluewallet -c android.intent.category.LAUNCHER 1", + "android:uninstall": "adb uninstall io.bluewallet.bluewallet", + "adb": "adb reverse tcp:8081 tcp:8081", + "android:clean": "cd android; ./gradlew clean ; cd .. ; rm -r -f android/app/.cxx; npm run android", + "android:restart": "adb shell am force-stop io.bluewallet.bluewallet; adb shell monkey -p io.bluewallet.bluewallet -c android.intent.category.LAUNCHER 1", "ios": "react-native run-ios", - "postinstall": "rn-nodeify --install buffer,events,process,stream,inherits,path,assert,crypto --hack; npm run releasenotes2json; npm run branch2json; npm run podinstall; npm run patches", - "patches": "patch -p1 < scripts/react-native-tor.patch; patch -p1 < scripts/rn-ldk.patch", - "test": "npm run tslint && npm run lint && npm run unit && npm run jest", - "jest": "jest -b tests/integration/*", - "windowspatches": "./scripts/windows-patches.sh", - "maccatalystpatches": "./scripts/maccatalystpatches/applypatchesformaccatalyst.sh", + "postinstall": "npm run releasenotes2json; npm run branch2json; npm run patches", + "patches": "npx patch-package", + "test": "npm run lint && npm run unit && npm run integration", + "integration": "jest tests/integration/*", "e2e:debug-build": "detox build -c android.debug", - "e2e:debug-test": "detox test -c android.debug -d 200000 -l info", + "e2e:debug-test": "detox test -c android.debug -d 200000 --loglevel error --reuse", + "e2e:debug-test-device": "detox test -c android.debug.device -d 200000 --loglevel info --reuse", "e2e:debug": "(test -f android/app/build/outputs/apk/debug/app-debug.apk && test -f android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk) || npm run e2e:debug-build; npm run e2e:debug-test", "e2e:release-build": "detox build -c android.release", - "e2e:release-test": "detox test -c android.release --record-videos all --record-logs all --take-screenshots all --headless -d 200000 -R 3", + "e2e:release-build-device": "E2E_ANDROID_ARCHS=armeabi-v7a,arm64-v8a detox build -c android.release", + "e2e:release-test": "detox test -c android.release --loglevel error", + "e2e:release-test-device": "detox test -c android.release.device --loglevel info --record-videos all", + "e2e:build:ios-release": "detox build -c ios.release", + "e2e:test:ios-release": "detox test -c ios.release", "tslint": "tsc", - "lint": " npm run tslint && node scripts/find-unused-loc.js && eslint --ext .js,.ts,.tsx '*.@(js|ts|tsx)' screen 'blue_modules/*.@(js|ts|tsx)' class models loc tests components", + "lint": " npm run tslint && node scripts/find-unused-loc.js && node scripts/find-english-leftovers.js && eslint --ext .js,.ts,.tsx '*.@(js|ts|tsx)' screen 'blue_modules/*.@(js|ts|tsx)' class models loc tests components navigation typings", "lint:fix": "npm run lint -- --fix", "lint:quickfix": "git status --porcelain | grep -v '\\.json' | grep -E '\\.js|\\.ts' --color=never | awk '{print $2}' | xargs eslint --fix; exit 0", - "unit": "jest -b -i tests/unit/*", - "windows": "react-native run-windows" - }, - "jest": { - "preset": "react-native", - "transform": { - "^.+\\.(ts|tsx)$": "ts-jest" - }, - "moduleFileExtensions": [ - "js", - "json", - "ts", - "tsx" - ], - "transformIgnorePatterns": [ - "node_modules/(?!((jest-)?react-native(-.*)?|@react-native(-community)?)/)" - ], - "setupFiles": [ - "./tests/setup.js" - ], - "watchPathIgnorePatterns": [ - "/node_modules" - ], - "setupFilesAfterEnv": [ - "./tests/setupAfterEnv.js" - ] + "unit": "jest -b -w tests/unit/*" }, "dependencies": { - "@babel/preset-env": "^7.20.0", - "@bugsnag/react-native": "7.21.0", - "@bugsnag/source-maps": "2.3.1", - "@keystonehq/bc-ur-registry": "0.6.3", - "@ngraveio/bc-ur": "1.1.6", + "@arkade-os/boltz-swap": "0.2.19", + "@arkade-os/sdk": "0.3.12", + "@babel/preset-env": "7.29.5", + "@bugsnag/react-native": "8.8.1", + "@bugsnag/source-maps": "2.3.3", + "@keystonehq/bc-ur-registry": "0.7.1", + "@ngraveio/bc-ur": "1.1.13", + "@noble/hashes": "1.3.3", "@noble/secp256k1": "1.6.3", - "@react-native-async-storage/async-storage": "1.19.3", - "@react-native-clipboard/clipboard": "1.11.2", - "@react-native-community/push-notification-ios": "1.11.0", - "@react-navigation/drawer": "5.12.9", - "@react-navigation/native": "5.9.8", - "@remobile/react-native-qrcode-local-image": "https://github.com/BlueWallet/react-native-qrcode-local-image", - "@spsina/bip47": "github:BlueWallet/bip47#0a2f02c90350802f2ec93afa4e6c8843be2d687c", - "aez": "1.0.1", - "assert": "2.0.0", - "base-x": "3.0.9", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-clipboard/clipboard": "1.16.3", + "@react-native-community/cli": "20.1.3", + "@react-native-community/cli-platform-android": "20.1.3", + "@react-native-community/cli-platform-ios": "20.1.3", + "@react-native-documents/picker": "12.0.1", + "@react-native-vector-icons/entypo": "13.1.1", + "@react-native-vector-icons/fontawesome": "13.1.1", + "@react-native-vector-icons/fontawesome6": "13.1.1", + "@react-native-vector-icons/ionicons": "13.1.1", + "@react-native-vector-icons/material-design-icons": "13.1.1", + "@react-native-vector-icons/material-icons": "13.1.1", + "@react-native/babel-preset": "0.85.3", + "@react-native/codegen": "0.85.3", + "@react-native/gradle-plugin": "0.85.3", + "@react-native/metro-config": "0.85.3", + "@react-navigation/devtools": "7.0.58", + "@react-navigation/drawer": "7.10.2", + "@react-navigation/native": "7.2.4", + "@react-navigation/native-stack": "7.15.1", + "@scure/base": "2.0.0", + "@spsina/bip47": "github:BlueWallet/bip47#df82345", + "aezeed": "0.0.5", + "assert": "2.1.0", + "base-x": "4.0.1", "bc-bech32": "file:blue_modules/bc-bech32", "bech32": "2.0.0", - "bignumber.js": "9.1.1", + "bignumber.js": "9.3.1", "bip21": "2.0.3", - "bip32": "3.0.1", - "bip38": "github:BlueWallet/bip38", + "bip32": "5.0.1", + "bip38": "github:BlueWallet/bip38#7ec4b1932b98eaaff16c5a26765a26466958e6b4", "bip39": "3.1.0", - "bitcoinjs-lib": "6.1.1", + "bitcoinjs-lib": "7.0.1", "bitcoinjs-message": "2.2.0", "bolt11": "1.4.1", "buffer": "6.0.3", - "buffer-reverse": "1.0.1", - "coinselect": "3.1.13", - "crypto-js": "4.1.1", - "dayjs": "1.11.9", - "detox": "20.11.4", - "ecpair": "2.0.1", - "ecurve": "1.0.6", - "electrum-client": "https://github.com/BlueWallet/rn-electrum-client#76c0ea35e1a50c47f3a7f818d529ebd100161496", + "coinselect": "github:BlueWallet/coinselect#35f8038", + "crypto-browserify": "3.12.1", + "crypto-js": "4.2.0", + "dayjs": "1.11.20", + "detox": "20.51.1", + "ecpair": "3.0.1", + "electrum-client": "github:BlueWallet/rn-electrum-client#83420b8", "electrum-mnemonic": "2.0.0", "events": "3.3.0", - "frisbee": "3.1.4", - "junderw-crc32c": "1.2.0", - "lottie-ios": "3.4.4", - "lottie-react-native": "5.1.6", - "metro-react-native-babel-preset": "0.77.0", - "path-browserify": "1.0.1", + "lottie-react-native": "7.3.8", + "pako": "file:blue_modules/pako", "payjoin-client": "1.0.1", - "process": "0.11.10", "prop-types": "15.8.1", - "react": "18.2.0", + "qr": "0.5.5", + "react": "19.2.3", "react-localization": "github:BlueWallet/react-localization#ae7969a", - "react-native": "0.71.11", + "react-native": "0.85.3", + "react-native-biometrics": "3.0.1", "react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442", - "react-native-camera-kit": "13.0.0", - "react-native-crypto": "2.2.0", - "react-native-default-preference": "1.4.4", - "react-native-device-info": "8.7.1", - "react-native-document-picker": "https://github.com/BlueWallet/react-native-document-picker#857655cdddf17751c0fae1286a9121fda2a6d568", - "react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#ebfddc4", - "react-native-elements": "3.4.3", - "react-native-fingerprint-scanner": "https://github.com/BlueWallet/react-native-fingerprint-scanner#ce644673681716335d786727bab998f7e632ab5e", + "react-native-camera-kit-no-google": "github:BlueWallet/react-native-camera-kit-no-google#0ed049a62da29cf304019363ec9d9ef3a73652e6", + "react-native-capture-protection": "github:BlueWallet/react-native-capture-protection#b17b9ec", + "react-native-context-menu-view": "github:BlueWallet/react-native-context-menu-view#144110b02afdb11b431741aef5da95e91b942a9b", + "react-native-default-preference": "github:BlueWallet/react-native-default-preference#6338a1f1235e4130b8cfc2dd3b53015eeff2870c", + "react-native-device-info": "14.1.1", + "react-native-draggable-flatlist": "4.0.3", + "react-native-edge-to-edge": "1.8.1", "react-native-fs": "2.20.0", - "react-native-gesture-handler": "2.9.0", - "react-native-handoff": "https://github.com/BlueWallet/react-native-handoff#31d005f93d31099d0e564590a3bbd052b8a02b39", - "react-native-haptic-feedback": "2.0.3", - "react-native-idle-timer": "https://github.com/BlueWallet/react-native-idle-timer#8587876d68ab5920e79619726aeca9e672beaf2b", - "react-native-image-picker": "4.8.5", - "react-native-ios-context-menu": "github:BlueWallet/react-native-ios-context-menu#v1.15.3", - "react-native-keychain": "8.1.2", - "react-native-linear-gradient": "2.8.2", - "react-native-localize": "3.0.2", - "react-native-modal": "13.0.1", - "react-native-navigation-bar-color": "https://github.com/BlueWallet/react-native-navigation-bar-color#3b2894ae62fbce99a3bd24105f0921cebaef5c94", - "react-native-obscure": "https://github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb", - "react-native-passcode-auth": "https://github.com/BlueWallet/react-native-passcode-auth#a2ff977ba92b36f8d0a5567f59c05cc608e8bd12", - "react-native-privacy-snapshot": "https://github.com/BlueWallet/react-native-privacy-snapshot#529e4627d93f67752a27e82a040ff7b64dca0783", - "react-native-prompt-android": "https://github.com/BlueWallet/react-native-prompt-android#ed168d66fed556bc2ed07cf498770f058b78a376", - "react-native-push-notification": "8.1.1", - "react-native-qrcode-svg": "6.2.0", + "react-native-gesture-handler": "2.31.2", + "react-native-get-random-values": "1.11.0", + "react-native-handoff": "github:BlueWallet/react-native-handoff#v0.0.4", + "react-native-haptic-feedback": "2.3.4", + "react-native-image-picker": "8.2.1", + "react-native-keychain": "10.0.0", + "react-native-linear-gradient": "2.8.3", + "react-native-localize": "3.7.0", + "react-native-notifications": "5.2.2", + "react-native-permissions": "5.5.1", + "react-native-prompt-android": "github:BlueWallet/react-native-prompt-android#ed168d66fed556bc2ed07cf498770f058b78a376", "react-native-quick-actions": "0.3.13", - "react-native-randombytes": "3.6.1", - "react-native-rate": "1.2.12", - "react-native-reanimated": "2.17.0", - "react-native-safe-area-context": "3.4.1", - "react-native-screens": "3.20.0", - "react-native-secure-key-store": "https://github.com/BlueWallet/react-native-secure-key-store#2076b48", - "react-native-share": "8.2.2", - "react-native-svg": "13.13.0", - "react-native-tcp-socket": "5.6.2", - "react-native-tor": "0.1.8", - "react-native-vector-icons": "9.2.0", + "react-native-reanimated": "4.3.1", + "react-native-safe-area-context": "5.7.0", + "react-native-screens": "4.24.0", + "react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc", + "react-native-share": "12.2.6", + "react-native-svg": "15.15.5", + "react-native-tcp-socket": "6.4.1", "react-native-watch-connectivity": "1.1.0", - "react-native-webview": "12.4.0", - "react-native-widget-center": "https://github.com/BlueWallet/react-native-widget-center#a128c38", + "react-native-worklets": "0.8.1", "readable-stream": "3.6.2", - "realm": "12.0.0", - "rn-ldk": "github:BlueWallet/rn-ldk#v0.8.4", - "rn-nodeify": "10.3.0", - "scryptsy": "2.1.0", - "slip39": "https://github.com/BlueWallet/slip39-js", + "realm": "20.2.0", + "silent-payments": "github:BlueWallet/SilentPayments#59a037", + "slip39": "github:BlueWallet/slip39-js#d316ee6", "stream-browserify": "3.0.0", - "url": "0.11.1", + "text-encoding": "0.7.0", + "url": "0.11.4", "wif": "2.0.6" - }, - "react-native": { - "crypto": "react-native-crypto", - "path": "path-browserify", - "_stream_transform": "readable-stream/transform", - "_stream_readable": "readable-stream/readable", - "_stream_writable": "readable-stream/writable", - "_stream_duplex": "readable-stream/duplex", - "_stream_passthrough": "readable-stream/passthrough", - "stream": "stream-browserify" - }, - "browser": { - "crypto": "react-native-crypto", - "path": "path-browserify", - "_stream_transform": "readable-stream/transform", - "_stream_readable": "readable-stream/readable", - "_stream_writable": "readable-stream/writable", - "_stream_duplex": "readable-stream/duplex", - "_stream_passthrough": "readable-stream/passthrough", - "stream": "stream-browserify" } } diff --git a/patches/README.md b/patches/README.md new file mode 100644 index 00000000000..34fba002b01 --- /dev/null +++ b/patches/README.md @@ -0,0 +1,66 @@ +# patches + +Local patches applied to `node_modules` by [`patch-package`](https://github.com/ds300/patch-package) +on `postinstall` (see `package.json` → `scripts.patches`). + +When upstream ships an equivalent fix, drop the patch here and bump the dependency. + +--- + +## `react-native-tcp-socket+6.4.1.patch` + +**What:** in `TcpSockets.m onConnect:`, read the socket addresses once and +emit `connect` only when both are valid; otherwise emit an `error` event +for that client. + +**Why:** `onConnect:` builds an `NSDictionary` literal from +`[socket localHost]` / `[socket connectedHost]`. The socket can disconnect +between this callback being queued and run, in which case those getters +return `nil`; a dictionary literal with a `nil` value throws +`NSInvalidArgumentException`, which is uncaught and aborts the whole app +(SIGABRT). It is intermittent and was seen against Electrum TLS +connections, but is not TLS-specific. + +``` +NSInvalidArgumentException — attempt to insert nil object from objects[0] + -[TcpSockets onConnect:] -> -[TcpSocketClient socketDidSecure:] +→ Signal 6 (abort) +``` + +The `error`-event path (rather than just skipping the event) is +deliberate: skipping silently leaves the JS side waiting forever for a +`connect` callback that never arrives. Emitting `error` lets the JS +connection fail fast so the caller can retry. + +**Upstream:** +- Bug: https://github.com/Rapsssito/react-native-tcp-socket/issues/197 (open) +- https://github.com/Rapsssito/react-native-tcp-socket/pull/225 (open) — + proposes the same nil guard but skips the event, which the maintainer + noted would hang JS; this patch emits `error` instead. +- https://github.com/Rapsssito/react-native-tcp-socket/pull/172 (closed) — + earlier attempt with the same error-event structure. + +**Remove this patch once an upstream fix is merged and +`react-native-tcp-socket` is bumped past 6.4.1.** + +--- + +## `react-native-notifications+5.2.2.patch` + +**What:** rewrites `FcmToken.sendTokenToJS()` (Android) to obtain the +`ReactContext` from `ReactHost` first (bridgeless / New Architecture), +falling back to `ReactInstanceManager` only if that fails — and wraps +both lookups in `try/catch`. + +**Why:** under the New Architecture (bridgeless, RN 0.76+) there is no +`ReactInstanceManager`. The stock code calls +`getReactNativeHost().getReactInstanceManager()` first, which throws +`UnsupportedOperationException: ReactInstanceManager.createReactContext +is unsupported` and crashes the app when the FCM push token is +delivered. + +**Upstream:** https://github.com/wix/react-native-notifications/issues/1071 (open) + +Added in BlueWallet PR https://github.com/BlueWallet/BlueWallet/pull/8424 +during a React Native bump. Remove once `react-native-notifications` +ships New-Architecture-safe token delivery. diff --git a/patches/react-native-notifications+5.2.2.patch b/patches/react-native-notifications+5.2.2.patch new file mode 100644 index 00000000000..a0b9277c7a1 --- /dev/null +++ b/patches/react-native-notifications+5.2.2.patch @@ -0,0 +1,36 @@ +diff --git a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/fcm/FcmToken.java b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/fcm/FcmToken.java +index c7bf71c..08022ff 100644 +--- a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/fcm/FcmToken.java ++++ b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/fcm/FcmToken.java +@@ -88,15 +88,26 @@ public class FcmToken implements IFcmToken { + } + + protected void sendTokenToJS() { +- final ReactInstanceManager instanceManager = ((ReactApplication) mAppContext).getReactNativeHost().getReactInstanceManager(); +- ReactContext reactContext = instanceManager.getCurrentReactContext(); ++ final ReactApplication reactApplication = (ReactApplication) mAppContext; ++ ReactContext reactContext = null; + +- if (reactContext == null) { +- // If the react context is not available, try to get the current context from the react host (RN0.76). +- reactContext = ((ReactApplication) mAppContext).getReactHost().getCurrentReactContext(); ++ try { ++ // Bridgeless/new architecture apps expose the current context from ReactHost. ++ reactContext = reactApplication.getReactHost().getCurrentReactContext(); ++ } catch (Throwable ignored) { ++ // Older React Native versions may not expose ReactHost. ++ } ++ ++ if (reactContext == null) { ++ try { ++ final ReactInstanceManager instanceManager = reactApplication.getReactNativeHost().getReactInstanceManager(); ++ reactContext = instanceManager.getCurrentReactContext(); ++ } catch (Throwable ignored) { ++ // Bridgeless mode or other configurations may not support ReactInstanceManager access. ++ } + } + // Note: Cannot assume react-context exists cause this is an async dispatched service. + if (reactContext != null && reactContext.hasActiveReactInstance()) { + Bundle tokenMap = new Bundle(); + tokenMap.putString("deviceToken", sToken); + mJsIOHelper.sendEventToJS(TOKEN_RECEIVED_EVENT_NAME, tokenMap, reactContext); diff --git a/patches/react-native-tcp-socket+6.4.1.patch b/patches/react-native-tcp-socket+6.4.1.patch new file mode 100644 index 00000000000..12167bc8a42 --- /dev/null +++ b/patches/react-native-tcp-socket+6.4.1.patch @@ -0,0 +1,54 @@ +diff --git a/node_modules/react-native-tcp-socket/ios/TcpSockets.m b/node_modules/react-native-tcp-socket/ios/TcpSockets.m +index 0fe15d2..a5553c6 100644 +--- a/node_modules/react-native-tcp-socket/ios/TcpSockets.m ++++ b/node_modules/react-native-tcp-socket/ios/TcpSockets.m +@@ -219,20 +219,35 @@ - (void)onWrittenData:(TcpSocketClient *)client msgId:(NSNumber *)msgId { + + - (void)onConnect:(TcpSocketClient *)client { + GCDAsyncSocket *socket = [client getSocket]; +- [self sendEventWithName:@"connect" +- body:@{ +- @"id" : client.id, +- @"connection" : @{ +- @"localAddress" : [socket localHost], +- @"localPort" : +- [NSNumber numberWithInt:[socket localPort]], +- @"remoteAddress" : [socket connectedHost], +- @"remotePort" : [NSNumber +- numberWithInt:[socket connectedPort]], +- @"remoteFamily" : [socket isIPv4] ? @"IPv4" +- : @"IPv6" +- } +- }]; ++ NSString *localAddress = [socket localHost]; ++ NSString *remoteAddress = [socket connectedHost]; ++ // The socket can disconnect between this callback being queued and run, ++ // in which case the address getters return nil. Building the connection ++ // dictionary with a nil value throws NSInvalidArgumentException and ++ // crashes the app, so emit an error instead of the connect event - ++ // skipping silently would hang the JS side waiting for a callback. ++ if (localAddress != nil && remoteAddress != nil) { ++ [self sendEventWithName:@"connect" ++ body:@{ ++ @"id" : client.id, ++ @"connection" : @{ ++ @"localAddress" : localAddress, ++ @"localPort" : ++ [NSNumber numberWithInt:[socket localPort]], ++ @"remoteAddress" : remoteAddress, ++ @"remotePort" : [NSNumber ++ numberWithInt:[socket connectedPort]], ++ @"remoteFamily" : [socket isIPv4] ? @"IPv4" ++ : @"IPv6" ++ } ++ }]; ++ } else { ++ [self sendEventWithName:@"error" ++ body:@{ ++ @"id" : client.id, ++ @"error" : @"Socket disconnected before connect could be reported" ++ }]; ++ } + } + + - (void)onListen:(TcpSocketClient *)server { diff --git a/react-native.config.js b/react-native.config.js new file mode 100644 index 00000000000..2fbdadc407d --- /dev/null +++ b/react-native.config.js @@ -0,0 +1,29 @@ +const path = require('path'); + +module.exports = { + project: { + android: {}, + ios: {}, + }, + dependencies: { + 'react-native-context-menu-view': { + root: path.resolve(__dirname, 'node_modules/react-native-context-menu-view'), + platforms: { + ios: { + podspecPath: path.resolve(__dirname, 'node_modules/react-native-context-menu-view/react-native-context-menu-view.podspec'), + }, + android: { + sourceDir: path.resolve(__dirname, 'node_modules/react-native-context-menu-view/android'), + }, + }, + }, + }, + codegenConfig: { + name: 'BlueWalletSpec', + type: 'all', + jsSrcsDir: './codegen', + android: { + javaPackageName: 'io.bluewallet.bluewallet', + }, + }, +}; diff --git a/renovate.json b/renovate.json index c586b3b43d3..df61c0898a6 100644 --- a/renovate.json +++ b/renovate.json @@ -1,7 +1,8 @@ { "extends": [ - "config:base", + "config:best-practices", ":disableMajorUpdates", ":preserveSemverRanges" - ] + ], + "ignoreDeps": ["react-native"] } diff --git a/screen/ActionSheet.common.ts b/screen/ActionSheet.common.ts new file mode 100644 index 00000000000..46e4155be9c --- /dev/null +++ b/screen/ActionSheet.common.ts @@ -0,0 +1,12 @@ +// ActionSheet.common.ts +export interface ActionSheetOptions { + title?: string; + message?: string; + options: string[]; // Array of button labels. + destructiveButtonIndex?: number; + cancelButtonIndex?: number; + confirmButtonIndex?: number; + anchor?: number; +} + +export type CompletionCallback = (buttonIndex: number) => void; diff --git a/screen/ActionSheet.ios.js b/screen/ActionSheet.ios.js deleted file mode 100644 index 30d570d186b..00000000000 --- a/screen/ActionSheet.ios.js +++ /dev/null @@ -1,7 +0,0 @@ -import { ActionSheetIOS } from 'react-native'; - -export default class ActionSheet { - static showActionSheetWithOptions(options, completion) { - ActionSheetIOS.showActionSheetWithOptions(options, completion); - } -} diff --git a/screen/ActionSheet.ios.ts b/screen/ActionSheet.ios.ts new file mode 100644 index 00000000000..ade5dac043b --- /dev/null +++ b/screen/ActionSheet.ios.ts @@ -0,0 +1,16 @@ +// ActionSheet.ios.ts +import { ActionSheetIOS } from 'react-native'; + +import { ActionSheetOptions, CompletionCallback } from './ActionSheet.common'; + +export default class ActionSheet { + static showActionSheetWithOptions(options: ActionSheetOptions, completion: CompletionCallback): void { + const iosOptions = { + ...options, + }; + if (options.anchor) { + iosOptions.anchor = options.anchor; + } + ActionSheetIOS.showActionSheetWithOptions(iosOptions, completion); + } +} diff --git a/screen/ActionSheet.js b/screen/ActionSheet.js deleted file mode 100644 index 02bbfe44d31..00000000000 --- a/screen/ActionSheet.js +++ /dev/null @@ -1,7 +0,0 @@ -import { Alert } from 'react-native'; - -export default class ActionSheet { - static showActionSheetWithOptions(options) { - Alert.alert(options.title, options.message, options.buttons, { cancelable: true }); - } -} diff --git a/screen/ActionSheet.ts b/screen/ActionSheet.ts new file mode 100644 index 00000000000..8aff2161193 --- /dev/null +++ b/screen/ActionSheet.ts @@ -0,0 +1,25 @@ +// ActionSheet.ts +import { Alert } from 'react-native'; + +import { ActionSheetOptions, CompletionCallback } from './ActionSheet.common'; + +export default class ActionSheet { + static showActionSheetWithOptions(options: ActionSheetOptions, completion: CompletionCallback): void { + const alertOptions = options.options.map((option, index) => { + let style: 'default' | 'cancel' | 'destructive' = 'default'; + if (index === options.destructiveButtonIndex) { + style = 'destructive'; + } else if (index === options.cancelButtonIndex) { + style = 'cancel'; + } + + return { + text: option, + onPress: () => completion(index), + style, + }; + }); + + Alert.alert(options.title || '', options.message || '', alertOptions, { cancelable: !!options.cancelButtonIndex }); + } +} diff --git a/screen/PlausibleDeniability.tsx b/screen/PlausibleDeniability.tsx new file mode 100644 index 00000000000..cd0772c006f --- /dev/null +++ b/screen/PlausibleDeniability.tsx @@ -0,0 +1,88 @@ +import React, { useCallback, useReducer } from 'react'; +import { useFocusEffect } from '@react-navigation/native'; +import BlueCard from '../components/BlueCard'; +import BlueText from '../components/BlueText'; +import Button from '../components/Button'; +import loc from '../loc'; +import { MODAL_TYPES } from './PromptPasswordConfirmationSheet.types'; +import { useExtendedNavigation } from '../hooks/useExtendedNavigation'; +import SafeAreaScrollView from '../components/SafeAreaScrollView'; +import { BlueSpacing20 } from '../components/BlueSpacing'; +import { BlueLoading } from '../components/BlueLoading'; +import { useStorage } from '../hooks/context/useStorage'; + +// Action Types +const SET_LOADING = 'SET_LOADING'; +const SET_MODAL_TYPE = 'SET_MODAL_TYPE'; + +// Defining State and Action Types +type State = { + isLoading: boolean; + modalType: keyof typeof MODAL_TYPES; +}; + +type Action = { type: typeof SET_LOADING; payload: boolean } | { type: typeof SET_MODAL_TYPE; payload: keyof typeof MODAL_TYPES }; + +// Initial State +const initialState: State = { + isLoading: false, + modalType: MODAL_TYPES.CREATE_FAKE_STORAGE, +}; + +// Reducer Function +function reducer(state: State, action: Action): State { + switch (action.type) { + case SET_LOADING: + return { ...state, isLoading: action.payload }; + case SET_MODAL_TYPE: + return { ...state, modalType: action.payload }; + default: + return state; + } +} + +// Component +const PlausibleDeniability: React.FC = () => { + useStorage(); + const [state, dispatch] = useReducer(reducer, initialState); + const navigation = useExtendedNavigation(); + + const handleOnCreateFakeStorageButtonPressed = async () => { + dispatch({ type: SET_LOADING, payload: true }); + dispatch({ type: SET_MODAL_TYPE, payload: MODAL_TYPES.CREATE_FAKE_STORAGE }); + navigation.navigate('PromptPasswordConfirmationSheet', { + modalType: MODAL_TYPES.CREATE_FAKE_STORAGE, + returnTo: 'PlausibleDeniability', + }); + }; + + useFocusEffect( + useCallback(() => { + dispatch({ type: SET_LOADING, payload: false }); + dispatch({ type: SET_MODAL_TYPE, payload: MODAL_TYPES.CREATE_FAKE_STORAGE }); + }, []), + ); + + return ( + + {state.isLoading ? ( + + ) : ( + + {loc.plausibledeniability.help} + + {loc.plausibledeniability.help2} + +