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..b446c450331 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 16" } }, "emulator": { @@ -39,14 +44,32 @@ } }, "configurations": { - "ios": { + "ios.release": { "device": "simulator", - "app": "ios" + "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..cd5a4bb7b2d 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,6 +19,7 @@ "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", "prettier/prettier": [ "warn", @@ -49,7 +50,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/workflows/build-ios-release-pullrequest.yml b/.github/workflows/build-ios-release-pullrequest.yml new file mode 100644 index 00000000000..19dc0b4970f --- /dev/null +++ b/.github/workflows/build-ios-release-pullrequest.yml @@ -0,0 +1,549 @@ +name: Build Release and Upload to TestFlight (iOS) + +on: + push: + branches: + - master + pull_request: + types: [opened, reopened, synchronize, labeled] + branches: + - master + workflow_dispatch: + +jobs: + build: + runs-on: macos-15 + 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@v6 + 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@v6 + with: + node-version: 24 + cache: 'npm' + cache-dependency-path: package-lock.json + + - uses: maxim-lobanov/setup-xcode@v1 + 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@v1 + with: + ruby-version: 3.1.6 + + - 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: Install CocoaPods Dependencies + 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@v6 + 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@v6 + 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-15 + 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@v6 + + - name: Set Up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.1.6 + + - 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@v5 + 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@v7 + 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-release-apk.yml b/.github/workflows/build-release-apk.yml index e7526962929..dce0ed1bbe4 100644 --- a/.github/workflows/build-release-apk.yml +++ b/.github/workflows/build-release-apk.yml @@ -1,47 +1,130 @@ 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-latest steps: - name: Checkout project - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: fetch-depth: "0" - name: Specify node version - uses: actions/setup-node@v2-beta - with: - node-version: 16 - - - name: Use npm caches - uses: actions/cache@v2 + uses: actions/setup-node@v6 with: - path: ~/.npm - key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm- + node-version: 24 + cache: 'npm' + cache-dependency-path: package-lock.json - name: Use specific Java version for sdkmanager to work - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: distribution: 'temurin' - java-version: '11' + java-version: '17' cache: 'gradle' - name: Install node_modules - run: npm install --production + run: npm ci --omit=dev --yes - - name: Build + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.1.6 + bundler-cache: true + + - name: Cache Ruby Gems + uses: actions/cache@v5 + with: + path: vendor/bundle + key: ${{ runner.os }}-ruby-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-ruby- + + - 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 + + - name: Prepare Keystore + run: bundle exec fastlane android prepare_keystore env: KEYSTORE_FILE_HEX: ${{ secrets.KEYSTORE_FILE_HEX }} + + - name: Update Version Code, Build, and Sign APK + id: build_and_sign_apk + run: | + bundle exec fastlane android update_version_build_and_sign_apk + env: + BUILD_NUMBER: ${{ env.NEW_BUILD_NUMBER }} KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} - run: ./scripts/build-release-apk.sh - - uses: actions/upload-artifact@v2 - if: success() + - name: Determine APK Filename and Path + id: determine_apk_path + run: | + 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}-${NEW_BUILD_NUMBER}-${BRANCH_NAME}.apk" + else + EXPECTED_FILENAME="BlueWallet-${VERSION_NAME}-${NEW_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@v6 + with: + name: signed-apk + path: ${{ env.APK_PATH }} + if-no-files-found: error + + browserstack: + runs-on: ubuntu-latest + needs: buildReleaseApk + if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'browserstack') }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 with: - name: apk - path: ./android/app/build/outputs/apk/release/app-release.apk + ruby-version: 3.1.6 + bundler-cache: true + + - name: Install dependencies with Bundler + run: bundle install --jobs 4 --retry 3 + + - name: Download APK artifact + uses: actions/download-artifact@v5 + 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 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ecd1930413..f3635262569 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,41 +3,54 @@ 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@v6 + with: + fetch-depth: 0 - name: Specify node version - uses: actions/setup-node@v2-beta + uses: actions/setup-node@v6 with: - node-version: 16 + node-version: 24 + cache: 'npm' + cache-dependency-path: package-lock.json + + - 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@v6 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@v6 with: - path: node_modules - key: ${{ runner.os }}-nm-${{ hashFiles('package-lock.json') }} + node-version: 24 + cache: 'npm' + cache-dependency-path: package-lock.json - 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 +62,34 @@ 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@v6 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@v6 with: - path: ~/.npm - key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm- + node-version: 24 + cache: 'npm' + cache-dependency-path: package-lock.json - 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.yml b/.github/workflows/e2e.yml new file mode 100644 index 00000000000..d3aa2767499 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,251 @@ +name: Tests e2e + +on: [pull_request] + +permissions: + contents: read + +jobs: + ios: + runs-on: macos-15 + env: + BUILD_CONFIGURATION: Release + HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }} + HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: 'npm' + cache-dependency-path: package-lock.json + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.1.6" + + - name: Cache Ruby gems + uses: actions/cache@v5 + with: + path: vendor/bundle + key: ${{ runner.os }}-ruby-${{ hashFiles('Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-ruby- + + - name: Install Ruby gems + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 --quiet + + - name: Install Node dependencies + run: npm ci || npm ci + + - name: Cache CocoaPods + uses: actions/cache@v5 + with: + path: ios/Pods + key: ${{ runner.os }}-pods-${{ hashFiles('ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + + - name: Install CocoaPods dependencies + run: bundle exec fastlane ios install_pods || bundle exec fastlane ios install_pods + + - name: Delete Apple Watch target + run: | + bundle exec ruby <<'RUBY' + require 'xcodeproj' + + project_path = 'ios/BlueWallet.xcodeproj' + project = Xcodeproj::Project.open(project_path) + + target_names = %w[BlueWalletWatch] + 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| phase.display_name == 'Embed Watch Content' }.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 'Apple Watch target references removed' + else + puts 'No Apple Watch targets found' + end + RUBY + + - name: Remove Watch schemes + run: rm -f ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWalletWatch.xcscheme + + - name: Build iOS simulator app + working-directory: ios + env: + RCT_NO_LAUNCH_PACKAGER: "1" + run: | + set -eo pipefail + build() { + xcodebuild \ + -workspace BlueWallet.xcworkspace \ + -scheme BlueWallet \ + -configuration "${BUILD_CONFIGURATION}" \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + -derivedDataPath build \ + build + } + build || build + + - 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 + OUTPUT_DIR="BlueWallet-simulator" + rm -rf "$OUTPUT_DIR" + mkdir -p "$OUTPUT_DIR" + cp -R "$APP_DIR" "$OUTPUT_DIR/BlueWallet.app" + echo "APP_EXPORT_PATH=$OUTPUT_DIR" >> "$GITHUB_ENV" + + - name: Install applesimutils + run: | + brew tap wix/brew + brew install applesimutils + + - name: Run detox tests + 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@v6 + if: failure() + with: + name: e2e-ios-videos + path: ./artifacts/ + + + android: + runs-on: ubuntu-latest + env: + HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }} + HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Free disk space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + android: false + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: true + + - name: npm and gradle caches in /mnt + run: | + rm -rf ~/.npm + rm -rf ~/.gradle + sudo mkdir -p /mnt/.npm + sudo mkdir -p /mnt/.gradle + sudo chown -R runner /mnt/.npm + sudo chown -R runner /mnt/.gradle + ln -s /mnt/.npm /home/runner/ + ln -s /mnt/.gradle /home/runner/ + + - name: Create artifacts directory on /mnt + run: | + sudo mkdir -p /mnt/artifacts + sudo chown -R runner /mnt/artifacts + + - name: Specify node version + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: 'npm' + cache-dependency-path: package-lock.json + + - name: Use gradle caches + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Install node_modules + run: npm ci --omit=dev --yes || npm ci --omit=dev --yes + + - name: Use specific Java version for sdkmanager to work + uses: actions/setup-java@v5 + 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: Build + run: npm run e2e:release-build || npm run e2e:release-build + + - name: Install dev deps needed for tests + run: npm i || npm i + + - name: Run tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 31 + avd-name: Pixel_API_29_AOSP + force-avd-creation: false + 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 3 --reuse --artifacts-location /mnt/artifacts + + - uses: actions/upload-artifact@v6 + if: failure() + with: + name: e2e-android-videos + path: /mnt/artifacts/ diff --git a/.github/workflows/lockfiles_update.yml b/.github/workflows/lockfiles_update.yml new file mode 100644 index 00000000000..a56d5bff6cb --- /dev/null +++ b/.github/workflows/lockfiles_update.yml @@ -0,0 +1,99 @@ +name: Lock Files Update + +on: + workflow_dispatch: + push: + branches: + - master + +jobs: + pod-update: + runs-on: macos-15 + permissions: + contents: write + steps: + + - name: Checkout master branch + uses: actions/checkout@v6 + with: + ref: master # Ensures we're checking out the master branch + fetch-depth: 0 # Ensures full history to enable branch deletion and recreation + + - name: Delete existing branch + run: | + git push origin --delete pod-update-branch || echo "Branch does not exist, continuing..." + git branch -D pod-update-branch || echo "Local branch does not exist, continuing..." + + - name: Create new branch from master + run: git checkout -b pod-update-branch # Create a new branch from the master branch + + - name: Specify node version + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: 'npm' + cache-dependency-path: package-lock.json + + - name: Install node modules + run: npm ci + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.1.6 + bundler-cache: true + + - name: Install and update Ruby Gems + run: | + bundle install + + - name: Install CocoaPods Dependencies + run: | + cd ios + pod install + pod update + + - name: Check for changes + id: check-changes + run: | + git diff --quiet package-lock.json ios/Podfile.lock || echo "Changes detected" + continue-on-error: true + + - name: Stop job if no changes + if: steps.check-changes.outcome == 'success' + run: | + echo "No changes detected in package-lock.json or Podfile.lock. Stopping the job." + exit 0 + + - name: Commit changes + if: steps.check-changes.outcome != 'success' + run: | + git add package-lock.json ios/Podfile.lock + git commit -m "Update lock files" + + # Step 10: Get the list of changed files for PR description + - name: Get changed files for PR description + id: get-changes + if: steps.check-changes.outcome != 'success' + run: | + git diff --name-only HEAD^ HEAD > changed_files.txt + echo "CHANGES=$(cat changed_files.txt)" >> $GITHUB_ENV + + # Step 11: Push the changes and create the PR using the LockFiles PAT + - name: Push and create PR + if: steps.check-changes.outcome != 'success' + run: | + git push origin pod-update-branch + gh pr create --title "Lock Files Updates" --body "The following lock files were updated:\n\n${{ env.CHANGES }}" --base master + env: + GITHUB_TOKEN: ${{ secrets.LOCKFILES_WORKFLOW }} # Use the LockFiles PAT for PR creation + + cleanup: + runs-on: macos-15 + if: github.event.pull_request.merged == true || github.event.pull_request.state == 'closed' + needs: pod-update + steps: + + - name: Delete branch after PR merge/close + run: | + git push origin --delete pod-update-branch \ No newline at end of file diff --git a/.gitignore b/.gitignore index d802dd90b55..f7936041fa9 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,17 @@ artifacts/ # Editors .vscode/ /.vs +.claude *.mx *.realm -*.realm.lock \ No newline at end of file +*.realm.lock +android/app/.project +android/app/.classpath +android/.settings/org.eclipse.buildship.core.prefs +android/.project +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..8a4b2758ef0 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.6 +3.1.6 \ No newline at end of file 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..5c14e079e24 --- /dev/null +++ b/App.tsx @@ -0,0 +1,33 @@ +import { NavigationContainer } 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); + + return ( + + + + + + + + + + + + ); +}; + +export default App; diff --git a/BlueComponents.js b/BlueComponents.js index bb49e91b87c..96c5447162c 100644 --- a/BlueComponents.js +++ b/BlueComponents.js @@ -1,33 +1,9 @@ /* 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'; +import React, { forwardRef } from 'react'; +import { Dimensions, Platform, Pressable, StyleSheet, TextInput, View } from 'react-native'; +import { Icon, Text } from '@rneui/themed'; +import { useTheme } from './components/themes'; +import { useLocale } from '@react-navigation/native'; const { height, width } = Dimensions.get('window'); const aspectRatio = height / width; @@ -38,212 +14,6 @@ if (aspectRatio > 1.6) { 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. * @@ -252,133 +22,27 @@ export const LightningButton = props => { export const BlueButtonLink = forwardRef((props, ref) => { const { colors } = useTheme(); return ( - + [styles.blueButtonLink, pressed && styles.pressed]} {...props} ref={ref}> {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 => { +export const BlueText = ({ bold = false, ...props }) => { const { colors } = useTheme(); - const style = StyleSheet.compose({ color: colors.foregroundColor, writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr' }, props.style); + const { direction } = useLocale(); + const style = StyleSheet.compose( + { + color: colors.foregroundColor, + writingDirection: direction, + fontWeight: bold ? 'bold' : 'normal', + }, + props.style, + ); return ; }; @@ -387,75 +51,9 @@ export const BlueTextCentered = props => { 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(); + const { direction } = useLocale(); return ( { color: colors.foregroundColor, fontWeight: '400', marginHorizontal: 20, - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + writingDirection: direction, }} /> ); @@ -478,6 +76,7 @@ export const BlueFormMultiInput = props => { multiline underlineColorAndroid="transparent" numberOfLines={4} + editable={!props.editable} style={{ paddingHorizontal: 8, paddingVertical: 16, @@ -503,317 +102,13 @@ export const BlueFormMultiInput = props => { ); }; -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 }) { +export function BlueBigCheckmark({ style = {} }) { const defaultStyles = { backgroundColor: '#ccddf9', width: 120, @@ -832,39 +127,13 @@ export function BlueBigCheckmark({ style }) { ); } -const tabsStyles = StyleSheet.create({ - root: { - flexDirection: 'row', - height: 50, - borderColor: '#e3e3e3', - borderBottomWidth: 1, - }, - tabRoot: { - flex: 1, +const styles = StyleSheet.create({ + blueButtonLink: { + minWidth: 100, + minHeight: 36, justifyContent: 'center', - alignItems: 'center', - borderColor: 'white', - borderBottomWidth: 2, + }, + pressed: { + opacity: 0.6, }, }); - -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..b895a5b0c69 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,74 @@ +# 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. + +**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. + +## 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..496abe302c4 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,10 @@ 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. diff --git a/Gemfile b/Gemfile index 67a72298669..05cb8219b1d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,16 @@ -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.1.6" +gem "fastlane", "~> 2.228.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 +# 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..3714faedb9b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,30 +1,62 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.5) + CFPropertyList (3.0.7) + base64 + nkf rexml - activesupport (6.1.4.4) - concurrent-ruby (~> 1.0, >= 1.0.2) + activesupport (7.2.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.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.1144.0) + aws-sdk-core (3.229.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.110.0) + aws-sdk-core (~> 3, >= 3.228.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.196.1) + aws-sdk-core (~> 3, >= 3.228.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.4.1) + bigdecimal (3.2.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 +64,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 +77,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 +85,254 @@ 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 (2.5.3) + 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.16.0) ffi (>= 1.15.0) - ffi (1.15.5) + excon (0.112.0) + faraday (1.10.4) + 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.7) + 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.1.1) + 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.3) + faraday_middleware (1.2.1) + faraday (~> 1.0) + fastimage (2.4.0) + fastlane (2.228.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored (~> 1.2) + commander (~> 4.6) + 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.0.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.0.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + naturally (~> 2.2) + optparse (>= 0.1.1, < 1.0.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 (2.3.1) + git + xml-simple + fastlane-plugin-bugsnag_sourcemaps_upload (0.2.0) + fastlane-sirp (1.0.0) + sysrandom (~> 1.0) + ffi (1.17.2) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - httpclient (2.8.3) - i18n (1.9.1) + git (3.1.1) + activesupport (>= 5.0) + addressable (~> 2.8) + process_executer (~> 1.3) + rchardet (~> 1.9) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.8.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.5.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + 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.7) concurrent-ruby (~> 1.0) - json (2.6.1) - minitest (5.15.0) + jmespath (1.6.2) + json (2.13.2) + 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.2025.0805) + mini_magick (4.13.2) + mini_mime (1.1.5) + minitest (5.25.5) molinillo (0.8.0) + multi_json (1.17.0) + 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.6.0) + os (1.1.4) + plist (3.7.2) + process_executer (1.3.0) + public_suffix (4.0.7) + rake (13.3.0) + rchardet (1.9.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.1.2) + rexml (3.4.1) + rouge (3.28.0) ruby-macho (2.5.1) - typhoeus (1.4.0) + ruby2_keywords (0.0.5) + rubyzip (2.4.1) + securerandom (0.4.1) + security (0.1.5) + signet (0.20.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + sysrandom (1.0.5) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + 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.4.1) ethon (>= 0.9.0) - tzinfo (2.0.4) + 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) + cocoapods (>= 1.13, != 1.15.1, != 1.15.0) + concurrent-ruby (< 1.3.4) + fastlane (~> 2.228.0) + fastlane-plugin-browserstack + fastlane-plugin-bugsnag + fastlane-plugin-bugsnag_sourcemaps_upload + jwt + xcodeproj (< 1.26.0) RUBY VERSION - ruby 2.7.4p191 + ruby 3.1.6p260 BUNDLED WITH - 2.2.27 + 2.3.27 diff --git a/LICENSE b/LICENSE index 9d6b974ffcd..7c6a630f99e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 BlueWallet developers +Copyright (c) 2024 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..f950b26f28c 100644 --- a/README.md +++ b/README.md @@ -76,14 +76,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,7 +101,7 @@ 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 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.ts similarity index 87% rename from __mocks__/react-native-image-picker.js rename to __mocks__/react-native-image-picker.ts index 3392a0e6b3e..c401aabe35d 100644 --- a/__mocks__/react-native-image-picker.js +++ b/__mocks__/react-native-image-picker.ts @@ -1,4 +1,4 @@ -import {NativeModules} from 'react-native'; +import { NativeModules } from 'react-native'; // Mock the ImagePickerManager native module to allow us to unit test the JavaScript code NativeModules.ImagePickerManager = { 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 index e8895216fd3..9d2efc8e780 100644 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ b/android/.settings/org.eclipse.buildship.core.prefs @@ -1,2 +1,2 @@ connection.project.dir= -eclipse.preferences.version=1 +eclipse.preferences.version=1 \ No newline at end of file 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.buildship.core.prefs b/android/app/.settings/org.eclipse.buildship.core.prefs index b1886adb46c..7e818ac1097 100644 --- a/android/app/.settings/org.eclipse.buildship.core.prefs +++ b/android/app/.settings/org.eclipse.buildship.core.prefs @@ -1,2 +1,2 @@ connection.project.dir=.. -eclipse.preferences.version=1 +eclipse.preferences.version=1 \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index a1b77db54d2..48cbe123714 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,23 +1,21 @@ 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 @@ -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,48 +63,46 @@ 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 "7.2.4" testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } - splits { - abi { - reset() - enable enableSeparateBuildPerCPUArchitecture - universalApk false // If true, also generate a universal APK - include (*reactNativeArchitectures()) + lintOptions { + abortOnError false + checkReleaseBuilds false + } + + sourceSets { + main { + assets.srcDirs = ['src/main/assets', 'src/main/res/assets'] } } + buildTypes { release { // Caution! In production, you need to generate your own keystore file. @@ -122,38 +113,35 @@ android { } } - // 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) + 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.16.0' + implementation 'androidx.work:work-runtime-ktx:2.10.5' + implementation 'com.google.android.material:material:1.12.0' + implementation 'androidx.compose.ui:ui:1.9.4' + 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' + androidTestImplementation('com.wix:detox:0.1.1') + implementation 'androidx.appcompat:appcompat:1.7.1' implementation fileTree(dir: "libs", include: ["*.jar"]) + 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..2e3a3075f36 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,115 +1,214 @@ + xmlns:tools="http://schemas.android.com/tools" + android:installLocation="auto"> + + - - - + + + + + + + + + + + - - - - - - - - - - + android:name=".MainApplication" + android:label="@string/app_name" + android:icon="@mipmap/ic_launcher" + android:roundIcon="@mipmap/ic_launcher_round" + android:allowBackup="false" + android:largeHeap="true" + android:extractNativeLibs="true" + android:usesCleartextTraffic="true" + android:supportsRtl="true" + android:theme="@style/AppTheme" + android:networkSecurityConfig="@xml/network_security_config" + android:configChanges="uiMode"> + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService" + android:exported="false"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + \ 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/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..66100d6b0b2 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.kt @@ -0,0 +1,117 @@ +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) + + if (cachedPrice != null) { + // Show cached data immediately + val preferredCurrency = sharedPref.getString("preferredCurrency", "USD") + val preferredCurrencyLocale = sharedPref.getString("preferredCurrencyLocale", "en-US") + + try { + val localeParts = preferredCurrencyLocale?.split("-") ?: listOf("en", "US") + val locale = if (localeParts.size == 2) { + java.util.Locale(localeParts[0], localeParts[1]) + } else { + 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 + + views.setViewVisibility(R.id.loading_indicator, View.GONE) + 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(cachedPrice.toDouble().toInt())) + views.setTextViewText(R.id.last_updated_time, java.text.SimpleDateFormat("hh:mm a", java.util.Locale.getDefault()).format(java.util.Date())) + views.setViewVisibility(R.id.price_arrow_container, View.GONE) + } 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..7528e061503 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.kt @@ -0,0 +1,226 @@ +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.ReactPackage +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost +import com.facebook.react.defaults.DefaultReactNativeHost +import com.facebook.react.soloader.OpenSourceMergedSoMapping +import com.facebook.soloader.SoLoader +import com.facebook.react.modules.i18nmanager.I18nUtil +import io.bluewallet.bluewallet.components.segmentedcontrol.CustomSegmentedControlPackage + +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 = + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + add(CustomSegmentedControlPackage()) + add(SettingsPackage()) + } + + override fun getJSMainModuleName(): String = "index" + + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED + } + + override val reactHost: ReactHost + get() = 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) + SoLoader.init(this, OpenSourceMergedSoMapping) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + load() + } + + 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..99e4636cbd3 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/SettingsModule.kt @@ -0,0 +1,213 @@ +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.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.Promise +import java.util.UUID + +class SettingsModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(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" + } + + override fun getName(): String { + return "SettingsModule" + } + + /** + * Initialize device UID if not exists + * Uses the same Android ID as react-native-device-info's getUniqueId() + */ + @ReactMethod + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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..82bbfc6fe45 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/SettingsPackage.kt @@ -0,0 +1,16 @@ +package io.bluewallet.bluewallet + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class SettingsPackage : ReactPackage { + override fun createNativeModules(reactContext: ReactApplicationContext): List { + return listOf(SettingsModule(reactContext)) + } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return 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..9a183fdb184 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.kt @@ -0,0 +1,279 @@ +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 currencyFormat = getCurrencyFormat(preferredCurrency, preferredCurrencyLocale) + + views.apply { + setViewVisibility(R.id.loading_indicator, View.GONE) + setTextViewText(R.id.price_value, currencyFormat.format(previousPrice?.toDouble()?.toInt())) + 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) + setViewVisibility(R.id.price_arrow_container, View.GONE) + } + } + + private fun displayFetchedPrice( + views: RemoteViews, + fetchedPrice: String, + previousPrice: String?, + currentTime: String, + preferredCurrency: String?, + preferredCurrencyLocale: String? + ) { + val currentPrice = fetchedPrice.toDouble().toInt() + val currencyFormat = getCurrencyFormat(preferredCurrency, preferredCurrencyLocale) + + views.apply { + setViewVisibility(R.id.loading_indicator, View.GONE) + 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) + + if (previousPrice != null) { + setViewVisibility(R.id.price_arrow_container, View.VISIBLE) + setTextViewText(R.id.previous_price, currencyFormat.format(previousPrice.toDouble().toInt())) + setImageViewResource( + R.id.price_arrow, + if (currentPrice > previousPrice.toDouble().toInt()) android.R.drawable.arrow_up_float else android.R.drawable.arrow_down_float + ) + } else { + setViewVisibility(R.id.price_arrow_container, View.GONE) + } + } + } + + private fun getCurrencyFormat(currencyCode: String?, localeString: String?): NumberFormat { + val localeParts = localeString?.split("-") ?: listOf("en", "US") + val locale = if (localeParts.size == 2) { + Locale(localeParts[0], localeParts[1]) + } else { + 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/java/io/bluewallet/bluewallet/components/segmentedcontrol/CustomSegmentedControl.kt b/android/app/src/main/java/io/bluewallet/bluewallet/components/segmentedcontrol/CustomSegmentedControl.kt new file mode 100644 index 00000000000..cf41bdb06a6 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/components/segmentedcontrol/CustomSegmentedControl.kt @@ -0,0 +1,213 @@ +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.events.Event +import com.facebook.react.uimanager.UIManagerHelper +import com.google.android.material.button.MaterialButton +import com.google.android.material.button.MaterialButtonToggleGroup +import io.bluewallet.bluewallet.R + +class CustomSegmentedControl @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 onChangeEvent: ((WritableMap) -> Unit)? = null + + 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) + } + } + } + } + + 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 + + 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 = ContextCompat.getColor(context, R.color.button_background_color) + val unselectedBgColor = ContextCompat.getColor(context, R.color.button_disabled_background_color) + val selectedTextColor = ContextCompat.getColor(context, R.color.button_text_color) + val unselectedTextColor = 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 + } + } + + 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 inner class ChangeEvent( + surfaceId: Int, + viewId: Int, + private val eventData: WritableMap + ) : Event(surfaceId, viewId) { + + override fun getEventName(): String = "onChangeEvent" + + override fun getEventData(): WritableMap = eventData + } + + fun setOnChangeEvent(callback: ((WritableMap) -> Unit)?) { + onChangeEvent = callback + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/components/segmentedcontrol/CustomSegmentedControlManager.kt b/android/app/src/main/java/io/bluewallet/bluewallet/components/segmentedcontrol/CustomSegmentedControlManager.kt new file mode 100644 index 00000000000..6725ba2ab63 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/components/segmentedcontrol/CustomSegmentedControlManager.kt @@ -0,0 +1,47 @@ +package io.bluewallet.bluewallet.components.segmentedcontrol + +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.common.MapBuilder +import com.facebook.react.uimanager.SimpleViewManager +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.annotations.ReactProp + +class CustomSegmentedControlManager : SimpleViewManager() { + + companion object { + const val REACT_CLASS = "CustomSegmentedControl" + private const val ON_CHANGE_EVENT = "onChangeEvent" + } + + override fun getName(): String = REACT_CLASS + + override fun createViewInstance(reactContext: ThemedReactContext): CustomSegmentedControl { + return CustomSegmentedControl(reactContext) + } + + @ReactProp(name = "values") + fun setValues(view: CustomSegmentedControl, values: ReadableArray?) { + val valuesArray = values?.let { array -> + Array(array.size()) { index -> + array.getString(index) ?: "" + } + } ?: emptyArray() + + view.values = valuesArray + } + + @ReactProp(name = "selectedIndex", defaultInt = 0) + fun setSelectedIndex(view: CustomSegmentedControl, selectedIndex: Int) { + view.selectedIndex = selectedIndex + } + + override fun getExportedCustomDirectEventTypeConstants(): Map? { + return MapBuilder.builder() + .put(ON_CHANGE_EVENT, MapBuilder.of("registrationName", ON_CHANGE_EVENT)) + .build() + } + + override fun onAfterUpdateTransaction(view: CustomSegmentedControl) { + super.onAfterUpdateTransaction(view) + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/components/segmentedcontrol/CustomSegmentedControlPackage.kt b/android/app/src/main/java/io/bluewallet/bluewallet/components/segmentedcontrol/CustomSegmentedControlPackage.kt new file mode 100644 index 00000000000..dc94e8c22d3 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/components/segmentedcontrol/CustomSegmentedControlPackage.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 CustomSegmentedControlPackage : ReactPackage { + + override fun createNativeModules(reactContext: ReactApplicationContext): List { + return emptyList() + } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return listOf(CustomSegmentedControlManager()) + } +} 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/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..f55e37a5131 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,33 +2,28 @@ buildscript { ext { - minSdkVersion = 28 - supportLibVersion = "28.0.0" - buildToolsVersion = "33.0.0" - compileSdkVersion = 33 - targetSdkVersion = 33 + minSdkVersion = 24 + buildToolsVersion = "35.0.0" + compileSdkVersion = 35 + targetSdkVersion = 35 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' - + ndkVersion = "27.1.12297006" + kotlin_version = '2.0.21' + kotlinVersion = '2.0.21' } 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("com.android.tools.build:gradle") + classpath("com.facebook.react:react-native-gradle-plugin") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") - + classpath 'com.google.gms:google-services:4.4.4' // Google Services plugin + classpath("com.bugsnag:bugsnag-android-gradle-plugin:8.2.0") } } @@ -37,23 +32,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. @@ -79,18 +58,22 @@ subprojects { afterEvaluate {project -> if (project.hasProperty("android")) { android { - buildToolsVersion '30.0.3' - compileSdkVersion 31 + buildToolsVersion "35.0.0" + compileSdkVersion 35 defaultConfig { - minSdkVersion 28 + minSdkVersion 24 } } } - } -} - -subprojects { subproject -> - if(project['name'] == 'react-native-widget-center') { - project.configurations { compile { } } + 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 (project.plugins.hasPlugin("com.android.application") || project.plugins.hasPlugin("com.android.library")) { + kotlinOptions.jvmTarget = android.compileOptions.sourceCompatibility + } else { + kotlinOptions.jvmTarget = sourceCompatibility + } } -} + + } +} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties index cc86a726d93..bf403032b1a 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,7 @@ 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 diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b..a4b76b9530d 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..e0fd02028bc 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-8.12-all.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..f3b75f3b0d4 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 @@ -133,22 +133,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 @@ -193,11 +200,15 @@ 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" \ @@ -205,6 +216,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# 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 +248,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..9d21a21834d 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,34 @@ 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 %* + :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..3687ce3b772 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,4 +1,4 @@ module.exports = { - presets: ['module:metro-react-native-babel-preset'], + presets: ['module:@react-native/babel-preset'], plugins: ['react-native-reanimated/plugin'], // required by react-native-reanimated v2 https://docs.swmansion.com/react-native-reanimated/docs/installation/ }; 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.ts similarity index 56% rename from blue_modules/BlueElectrum.js rename to blue_modules/BlueElectrum.ts index 79ede14583b..bdff3999f20 100644 --- a/blue_modules/BlueElectrum.js +++ b/blue_modules/BlueElectrum.ts @@ -1,32 +1,125 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { Alert } from 'react-native'; -import { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet, TaprootWallet } from '../class'; +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 { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet, TaprootWallet } from '../class'; +import presentAlert from '../components/Alert'; import loc from '../loc'; -import WidgetCommunication from './WidgetCommunication'; -import { isTorDaemonDisabled } from './environment'; -import alert from '../components/Alert'; -const bitcoin = require('bitcoinjs-lib'); +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 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 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 }, + { host: 'electrum.bitaroo.net', 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 = Math.floor(Math.random() * hardcodedPeers.length); +let latestBlock: { height: number; time: number } | { height: undefined; time: undefined } = { height: undefined, time: undefined }; +const txhashHeightCache: Record = {}; +let _realm: Realm | undefined; + +function bitcoinjs_crypto_sha256(buffer: Uint8Array): Uint8Array { + return _sha256(buffer); +} -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 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 = 'electrumcache.realm'; + const path = `${cacheFolderPath}/electrumcache.realm`; // Use cache folder path const schema = [ { @@ -38,105 +131,146 @@ async function _getRealm() { }, }, ]; + + // @ts-ignore schema doesn't match Realm's schema type _realm = await Realm.open({ schema, path, encryptionKey, + excludeFromIcloudBackup: true, }); + 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' }, -]; +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); -/** @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); + console.log('Getting preferred server:', { host, tcpPort, sslPort }); -let latestBlockheight = false; -let latestBlockheightTimestamp = false; + if (!host) { + console.warn('Preferred server host is undefined'); + return; + } -const txhashHeightCache = {}; + return { + host, + tcp: tcpPort ? Number(tcpPort) : undefined, + ssl: sslPort ? Number(sslPort) : undefined, + }; + } catch (error) { + console.error('Error in getPreferredServer:', error); + return undefined; + } +}; -async function isDisabled() { +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 { - const savedValue = await AsyncStorage.getItem(ELECTRUM_CONNECTION_DISABLED); + 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 { + } catch (error) { + console.error('Error getting Electrum connection disabled state:', error); result = false; } return !!result; } -async function setDisabled(disabled = true) { - return AsyncStorage.setItem(ELECTRUM_CONNECTION_DISABLED, disabled ? '1' : ''); +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' : ''); } -async function connectMain() { +function getCurrentPeer() { + return hardcodedPeers[currentPeerIndex]; +} + +/** + * Returns NEXT hardcoded electrum server (increments index after use) + */ +function getNextPeer() { + const peer = getCurrentPeer(); + currentPeerIndex++; + if (currentPeerIndex + 1 >= 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 = await getNextPeer(); + let usingPeer = 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); - } + console.log('Using peer:', JSON.stringify(usingPeer)); 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 = new ElectrumClient(net, tls, usingPeer.ssl || usingPeer.tcp, usingPeer.host, usingPeer.ssl ? 'tls' : 'tcp'); - mainClient.onError = function (e) { + 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?.close(); + mainClient = undefined; mainConnected = false; // dropping `mainConnected` flag ensures there wont be reconnection race condition if several // errors triggered @@ -174,20 +308,25 @@ async function connectMain() { } const header = await mainClient.blockchainHeaders_subscribe(); if (header && header.height) { - latestBlockheight = header.height; - latestBlockheightTimestamp = Math.floor(+new Date() / 1000); + 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.close(); + mainClient?.close(); + mainClient = undefined; if (connectionAttempt >= 5) { presentNetworkErrorAlert(usingPeer); } else { @@ -198,25 +337,89 @@ async function connectMain() { } } -async function presentNetworkErrorAlert(usingPeer) { +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 }, + }); + }); +} + +const presentNetworkErrorAlert = async (usingPeer?: Peer) => { 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( + + presentAlert({ + allowRepeat: false, + 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.close(); + mainClient?.close(); + mainClient = undefined; setTimeout(connectMain, 500); }, style: 'default', @@ -224,41 +427,14 @@ async function presentNetworkErrorAlert(usingPeer) { { 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(); + presentResetToDefaultsAlert().then(result => { + if (result) { + connectionAttempt = 0; + mainClient?.close(); + mainClient = undefined; + setTimeout(connectMain, 500); + } + }); }, style: 'destructive', }, @@ -266,54 +442,37 @@ async function presentNetworkErrorAlert(usingPeer) { text: loc._.cancel, onPress: () => { connectionAttempt = 0; - mainClient.close() && mainClient.close(); + mainClient?.close(); + mainClient = undefined; }, 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 }; -} + options: { cancelable: false }, + }); +}; /** * 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() { +async function getRandomDynamicPeer(): Promise { try { - let peers = JSON.parse(await AsyncStorage.getItem(storageKey)); + 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 = {}; + 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', ''); @@ -328,22 +487,22 @@ async function getRandomDynamicPeer() { } } -/** - * - * @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; +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; + } }; -module.exports.getConfig = async function () { +export const getConfig = async function () { if (!mainClient) throw new Error('Electrum client is not connected'); return { host: mainClient.host, @@ -353,21 +512,16 @@ module.exports.getConfig = async function () { }; }; -module.exports.getSecondsSinceLastRequest = function () { +export const getSecondsSinceLastRequest = function () { return mainClient && mainClient.timeLastCall ? (+new Date() - mainClient.timeLastCall) / 1000 : -1; }; -/** - * - * @param address {String} - * @returns {Promise} - */ -module.exports.getTransactionsByAddress = async function (address) { +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 = bitcoin.crypto.sha256(script); - const reversedHash = Buffer.from(reverse(hash)); - const history = await mainClient.blockchainScripthash_getHistory(reversedHash.toString('hex')); + 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 } @@ -375,37 +529,118 @@ module.exports.getTransactionsByAddress = async function (address) { return history; }; -/** - * - * @param address {String} - * @returns {Promise} - */ -module.exports.getMempoolTransactionsByAddress = async function (address) { +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 = bitcoin.crypto.sha256(script); - const reversedHash = Buffer.from(reverse(hash)); - return mainClient.blockchainScripthash_getMempool(reversedHash.toString('hex')); + const hash = bitcoinjs_crypto_sha256(script); + const reversedHash = new Uint8Array(hash).reverse(); + return mainClient.blockchainScripthash_getMempool(uint8ArrayToHex(reversedHash)); }; -module.exports.ping = async function () { +export const ping = async function () { try { await mainClient.server_ping(); - } catch (_) { - mainConnected = false; - return false; - } - return true; + return true; + } catch (_) {} + + mainConnected = false; + return false; }; -module.exports.getTransactionsFullByAddress = async function (address) { - const txs = await this.getTransactionsByAddress(address); - const ret = []; +// 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; + + 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) { + } 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 @@ -422,7 +657,7 @@ module.exports.getTransactionsFullByAddress = async function (address) { let prevTxForVin; try { prevTxForVin = await mainClient.blockchainTransaction_get(input.txid, true); - } catch (error) { + } 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 @@ -447,7 +682,7 @@ module.exports.getTransactionsFullByAddress = async function (address) { } for (const output of full.vout) { - if (output.scriptPubKey && output.scriptPubKey.addresses) output.addresses = output.scriptPubKey.addresses; + 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]; } @@ -463,26 +698,28 @@ module.exports.getTransactionsFullByAddress = async function (address) { 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; +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: {} }; + const ret = { + balance: 0, + unconfirmed_balance: 0, + addresses: {} as Record, + }; const chunks = splitIntoChunks(addresses, batchsize); for (const chunk of chunks) { const scripthashes = []; - const scripthash2addr = {}; + const scripthash2addr: Record = {}; 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'); + const hash = bitcoinjs_crypto_sha256(script); + const reversedHash = uint8ArrayToHex(new Uint8Array(hash).reverse()); scripthashes.push(reversedHash); scripthash2addr[reversedHash] = addr; } @@ -491,7 +728,7 @@ module.exports.multiGetBalanceByAddress = async function (addresses, batchsize) if (disableBatching) { const promises = []; - const index2scripthash = {}; + const index2scripthash: Record = {}; for (let promiseIndex = 0; promiseIndex < scripthashes.length; promiseIndex++) { promises.push(mainClient.blockchainScripthash_getBalance(scripthashes[promiseIndex])); index2scripthash[promiseIndex] = scripthashes[promiseIndex]; @@ -515,20 +752,18 @@ module.exports.multiGetBalanceByAddress = async function (addresses, batchsize) return ret; }; -module.exports.multiGetUtxoByAddress = async function (addresses, batchsize) { - batchsize = batchsize || 100; +export const multiGetUtxoByAddress = async function (addresses: string[], batchsize: number = 100): Promise> { if (!mainClient) throw new Error('Electrum client is not connected'); - const ret = {}; + const ret: Record = {}; const chunks = splitIntoChunks(addresses, batchsize); for (const chunk of chunks) { const scripthashes = []; - const scripthash2addr = {}; + const scripthash2addr: Record = {}; 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'); + const hash = bitcoinjs_crypto_sha256(script); + const reversedHash = uint8ArrayToHex(new Uint8Array(hash).reverse()); scripthashes.push(reversedHash); scripthash2addr[reversedHash] = addr; } @@ -547,7 +782,7 @@ module.exports.multiGetUtxoByAddress = async function (addresses, batchsize) { 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.txid = utxo.tx_hash; utxo.vout = utxo.tx_pos; delete utxo.tx_pos; delete utxo.tx_hash; @@ -558,20 +793,27 @@ module.exports.multiGetUtxoByAddress = async function (addresses, batchsize) { return ret; }; -module.exports.multiGetHistoryByAddress = async function (addresses, batchsize) { - batchsize = batchsize || 100; +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 = {}; + const ret: Record = {}; const chunks = splitIntoChunks(addresses, batchsize); for (const chunk of chunks) { const scripthashes = []; - const scripthash2addr = {}; + const scripthash2addr: Record = {}; 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'); + const hash = bitcoinjs_crypto_sha256(script); + const reversedHash = uint8ArrayToHex(new Uint8Array(hash).reverse()); scripthashes.push(reversedHash); scripthash2addr[reversedHash] = addr; } @@ -580,7 +822,7 @@ module.exports.multiGetHistoryByAddress = async function (addresses, batchsize) if (disableBatching) { const promises = []; - const index2scripthash = {}; + const index2scripthash: Record = {}; for (let promiseIndex = 0; promiseIndex < scripthashes.length; promiseIndex++) { index2scripthash[promiseIndex] = scripthashes[promiseIndex]; promises.push(mainClient.blockchainScripthash_getHistory(scripthashes[promiseIndex])); @@ -609,12 +851,20 @@ module.exports.multiGetHistoryByAddress = async function (addresses, batchsize) return ret; }; -module.exports.multiGetTransactionByTxid = async function (txids, batchsize, verbose = true) { - batchsize = batchsize || 45; +// 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 = {}; + const ret: MultiGetTransactionByTxidResult = {}; txids = [...new Set(txids)]; // deduplicate just for any case // lets try cache first: @@ -625,7 +875,7 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver 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); + ret[txid] = JSON.parse(jsonString.cache_value as string); } catch (error) { console.log(error, 'cache failed to parse', jsonString.cache_value); } @@ -650,7 +900,7 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver // 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 = {}; + const index2txid: Record = {}; for (let promiseIndex = 0; promiseIndex < chunk.length; promiseIndex++) { const txid = chunk[promiseIndex]; index2txid[promiseIndex] = txid; @@ -668,16 +918,16 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver const txid = index2txid[resultIndex]; results.push({ result: tx, param: txid }); } - } catch (_) { - if (String(_?.message ?? _).startsWith('verbose transactions are currently unsupported')) { + } 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 (error) { - console.log(error); + } catch (err) { + console.log(err); } } } else { @@ -692,8 +942,8 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver tx = txhexToElectrumTransaction(tx); } results.push({ result: tx, param: txid }); - } catch (error) { - console.log(error); + } catch (err) { + console.log(err); } } } @@ -711,50 +961,59 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver 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) ?? []) { - for (const vout of ret[txid]?.vout ?? []) { + 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: - 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, - ); - } - }); + 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; -}; +} /** * 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; +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; + return false; } return new Promise(function (resolve, reject) { waitTillConnectedInterval = setInterval(() => { @@ -763,12 +1022,6 @@ module.exports.waitTillConnected = async function () { 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 @@ -782,7 +1035,7 @@ module.exports.waitTillConnected = async function () { // Returns the value at a given percentile in a sorted numeric array. // "Linear interpolation between closest ranks" method -function percentile(arr, p) { +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]; @@ -800,12 +1053,8 @@ function percentile(arr, p) { /** * 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) { +export const calcEstimateFeeFromFeeHistorgam = function (numberOfBlocks: number, feeHistorgram: number[][]) { // first, transforming histogram: let totalVsize = 0; const histogramToUse = []; @@ -825,7 +1074,7 @@ module.exports.calcEstimateFeeFromFeeHistorgam = function (numberOfBlocks, feeHi // now we have histogram of precisely size for numberOfBlocks. // lets spread it into flat array so its easier to calculate percentile: - let histogramFlat = []; + 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 @@ -838,7 +1087,7 @@ module.exports.calcEstimateFeeFromFeeHistorgam = function (numberOfBlocks, feeHi return Math.round(percentile(histogramFlat, 0.5) || 1); }; -module.exports.estimateFees = async function () { +export const estimateFees = async function (): Promise<{ fast: number; medium: number; slow: number }> { let histogram; let timeoutId; try { @@ -850,15 +1099,20 @@ module.exports.estimateFees = async function () { 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); + 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, module.exports.calcEstimateFeeFromFeeHistorgam(1, histogram)); + 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)); @@ -872,7 +1126,7 @@ module.exports.estimateFees = async function () { * @param numberOfBlocks {number} The number of blocks to target for confirmation * @returns {Promise} Satoshis per byte */ -module.exports.estimateFee = async function (numberOfBlocks) { +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); @@ -880,31 +1134,31 @@ module.exports.estimateFee = async function (numberOfBlocks) { return Math.round(new BigNumber(coinUnitsPerKilobyte).dividedBy(1024).multipliedBy(100000000).toNumber()); }; -module.exports.serverFeatures = async function () { +export const serverFeatures = async function () { if (!mainClient) throw new Error('Electrum client is not connected'); return mainClient.server_features(); }; -module.exports.broadcast = async function (hex) { +export const broadcast = async function (hex: string) { if (!mainClient) throw new Error('Electrum client is not connected'); try { - const broadcast = await mainClient.blockchainTransaction_broadcast(hex); - return broadcast; + const res = await mainClient.blockchainTransaction_broadcast(hex); + return res; } catch (error) { return error; } }; -module.exports.broadcastV2 = async function (hex) { +export const broadcastV2 = async function (hex: string): Promise { 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; +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 latestBlockheight + extraBlocks; + return latestBlock.height + extraBlocks; } const baseTs = 1587570465609; // uS @@ -912,14 +1166,9 @@ module.exports.estimateCurrentBlockheight = function () { 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); +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 @@ -928,28 +1177,17 @@ module.exports.calculateBlockTime = function (height) { }; /** - * - * @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', - ); +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 = false; + let timeoutId: NodeJS.Timeout | undefined; try { const rez = await Promise.race([ new Promise(resolve => { - timeoutId = setTimeout(() => resolve('timeout'), host.endsWith('.onion') && !isTorDisabled ? 21000 : 5000); + timeoutId = setTimeout(() => resolve('timeout'), 5000); }), client.connect(), ]); @@ -967,27 +1205,23 @@ module.exports.testConnection = async function (host, tcpPort, sslPort) { return false; }; -module.exports.forceDisconnect = () => { - mainClient.close(); +export const forceDisconnect = (): void => { + mainClient?.close(); }; -module.exports.setBatchingDisabled = () => { +export const setBatchingDisabled = () => { disableBatching = true; }; -module.exports.setBatchingEnabled = () => { +export const 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) { + +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) { @@ -996,97 +1230,13 @@ const splitIntoChunks = function (arr, chunkSize) { return groups; }; -const semVerToInt = function (semver) { +const semVerToInt = function (semver: string): number { 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; + const ret = Number(semver.split('.')[0]) * 1000000 + Number(semver.split('.')[1]) * 1000 + Number(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/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..488e73057c5 --- /dev/null +++ b/blue_modules/SettingsModule.ts @@ -0,0 +1,51 @@ +import { NativeModules, Platform } from 'react-native'; + +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 SettingsModule: SettingsModuleInterface | null = Platform.OS === 'android' ? NativeModules.SettingsModule : null; + +export default SettingsModule; 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..a9a49c0c918 --- /dev/null +++ b/blue_modules/analytics.ts @@ -0,0 +1,46 @@ +import Bugsnag from '@bugsnag/react-native'; +import { getUniqueId } from 'react-native-device-info'; + +import { BlueApp as BlueAppClass } from '../class'; + +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 () => { + // Don't try to start Bugsnag again as it's already initialized in native code + // Just configure the existing instance if tracking is allowed + const uniqueID = await getUniqueId(); + const doNotTrack = await BlueApp.isDoNotTrackEnabled(); + + if (doNotTrack) { + userHasOptedOut = true; + return; + } + + // Configure the existing Bugsnag instance instead of starting a new one + Bugsnag.setUser(uniqueID); + + // Add additional configuration if needed + Bugsnag.addOnError(function (event) { + return !userHasOptedOut; + }); +})(); + +const A = async (event: string) => {}; + +A.setOptOut = (value: boolean) => { + if (value) userHasOptedOut = true; +}; + +A.logError = (errorString: string) => { + console.error(errorString); + Bugsnag.notify(new Error(String(errorString))); +}; + +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.ts similarity index 84% rename from blue_modules/bip39.js rename to blue_modules/bip39.ts index afcffd37872..347a0d461c9 100644 --- a/blue_modules/bip39.js +++ b/blue_modules/bip39.ts @@ -1,6 +1,6 @@ import * as bip39 from 'bip39'; -const WORDLISTS = [ +const WORDLISTS: string[][] = [ bip39.wordlists.english, bip39.wordlists.french, bip39.wordlists.spanish, @@ -13,7 +13,7 @@ const WORDLISTS = [ bip39.wordlists.portuguese, ]; -export function validateMnemonic(mnemonic) { +export function validateMnemonic(mnemonic: string) { for (const wordlist of WORDLISTS) { const valid = bip39.validateMnemonic(mnemonic, wordlist); if (valid) return true; 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..3ef2df3e68c --- /dev/null +++ b/blue_modules/fs.ts @@ -0,0 +1,253 @@ +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 RNQRGenerator from 'rn-qr-generator'; +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, + }); + + if (response.didCancel) { + return undefined; + } else if (response.errorCode) { + throw new Error(response.errorMessage); + } else if (response.assets) { + try { + const uri = response.assets[0].uri; + if (uri) { + const result = await RNQRGenerator.detect({ uri: decodeURI(uri.toString()) }); + if (result?.values.length > 0) { + return result?.values[0]; + } + } + throw new Error(loc.send.qr_error_no_qrcode); + } catch (error) { + console.error(error); + presentAlert({ message: 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 handleImageFile = async (fileCopyUri: string): Promise<{ data: string | false; uri: string | false }> => { + try { + const exists = await RNFS.exists(fileCopyUri); + if (!exists) { + presentAlert({ message: 'File does not exist' }); + return { data: false, uri: false }; + } + // First attempt: use original URI + let result = await RNQRGenerator.detect({ uri: decodeURI(fileCopyUri) }); + if (result?.values && result.values.length > 0) { + return { data: result.values[0], uri: fileCopyUri }; + } + // Second attempt: remove file:// prefix and try again + const altUri = fileCopyUri.replace(/^file:\/\//, ''); + result = await RNQRGenerator.detect({ uri: decodeURI(altUri) }); + if (result?.values && result.values.length > 0) { + return { data: result.values[0], uri: fileCopyUri }; + } + presentAlert({ message: loc.send.qr_error_no_qrcode }); + return { data: false, uri: false }; + } catch (error: any) { + console.error(error); + presentAlert({ message: loc.send.qr_error_no_qrcode }); + return { data: false, uri: false }; + } +}; + +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..23e9a8a5dc2 --- /dev/null +++ b/blue_modules/notifications.ts @@ -0,0 +1,664 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import PushNotificationIOS from '@react-native-community/push-notification-ios'; +import { AppState, AppStateStatus, Platform } from 'react-native'; +import { getApplicationName, getSystemName, getSystemVersion, getVersion, hasGmsSync, hasHmsSync } from 'react-native-device-info'; +import { checkNotifications, requestNotifications, RESULTS } from 'react-native-permissions'; +import PushNotification, { ReceivedNotification } from 'react-native-push-notification'; +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'; +export const NOTIFICATIONS_NO_AND_DONT_ASK_FLAG = 'NOTIFICATIONS_NO_AND_DONT_ASK_FLAG'; +let alreadyConfigured = false; +let baseURI = groundControlUri; + +type TPushToken = { + token: string; + os: string; // its actually ('ios' | 'android'), but types for the lib are a bit more generic... +}; + +// thats unwrapped `ReceivedNotification`, withall `data` fields inline +type TPayload = { + // inherited from `ReceivedNotification`: + subText?: string; + message?: string | object; + foreground: boolean; + userInteraction: boolean; + // hopefully stuffed in `data` and uwrapped when received: + address: string; + txid: string; + type: number; + hash: string; +}; + +function deepClone(obj: T): T { + return JSON.parse(JSON.stringify(obj)); +} + +const checkAndroidNotificationPermission = async () => { + try { + const { status } = await checkNotifications(); + console.debug('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) => { + 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(); + } + } + } + } +}; + +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. + * 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 + */ +/** + * Attempts to obtain permissions and configure notifications. + * Shows a rationale on Android if permissions are needed. + * + * @returns {Promise} + */ +export const tryToObtainPermissions = async () => { + console.debug('tryToObtainPermissions: Starting user-triggered permission request'); + + if (!isNotificationsCapable) { + console.debug('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.debug('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.debug('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.debug('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.debug('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 { + return new Promise(function (resolve) { + PushNotification.checkPermissions((result: any) => { + resolve(result); + }); + }); + } 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.debug('Disabling notifications as user opted out...'); + PushNotification.removeAllDeliveredNotifications(); + PushNotification.setApplicationIconBadgeNumber(0); + PushNotification.cancelAllLocalNotifications(); + await AsyncStorage.setItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG, 'true'); + console.debug('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.debug('postTokenConfig: Starting token configuration'); + const pushToken = await getPushToken(); + console.debug('postTokenConfig: Retrieved push token:', !!pushToken); + + if (!pushToken || !pushToken.token || !pushToken.os) { + console.debug('postTokenConfig: Invalid token or missing OS info'); + return; + } + + try { + const lang = (await AsyncStorage.getItem('lang')) || 'en'; + const appVersion = getSystemName() + ' ' + getSystemVersion() + ';' + getApplicationName() + ' ' + getVersion(); + console.debug('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} + */ +export const configureNotifications = async (onProcessNotifications?: () => void) => { + if (alreadyConfigured) { + console.debug('configureNotifications: Already configured, skipping'); + return true; + } + + return new Promise(resolve => { + const handleRegistration = async (token: TPushToken) => { + if (__DEV__) { + console.debug('configureNotifications: Token received:', token); + } + alreadyConfigured = true; + await _setPushToken(token); + resolve(true); + }; + + // const handleNotification = async (notification: TPushNotification & { data: any }) => { + const handleNotification = async (notification: Omit) => { + // Deep clone to avoid modifying the original object + // @ts-ignore some missing properties hopefully will be unwrapped from `.data` + const payload: TPayload = deepClone({ + ...notification, + ...notification.data, + }); + + if (notification.data?.data) { + const validData = Object.fromEntries(Object.entries(notification.data.data).filter(([_, value]) => value != null)); + Object.assign(payload, validData); + } + + // @ts-ignore stfu ts, its cleanup + payload.data = undefined; + + if (!payload.subText && !payload.message) { + console.warn('Notification missing required fields:', payload); + return; + } + + await addNotification(payload); + notification.finish(PushNotificationIOS.FetchResult.NoData); + + if (payload.foreground && onProcessNotifications) { + await onProcessNotifications(); + } + }; + + const configure = async () => { + try { + const { status } = await checkNotifications(); + if (status !== RESULTS.GRANTED) { + console.debug('configureNotifications: Permissions not granted'); + return resolve(false); + } + + const existingToken = await getPushToken(); + if (existingToken) { + alreadyConfigured = true; + console.debug('Notifications already configured with existing token'); + return resolve(true); + } + + PushNotification.configure({ + onRegister: handleRegistration, + onNotification: handleNotification, + onRegistrationError: (error: any) => { + console.error('Registration error:', error); + resolve(false); + }, + permissions: { alert: true, badge: true, sound: true }, + popInitialNotification: true, + }); + } catch (error) { + console.error('Error in configure:', error); + resolve(false); + } + }; + + configure(); + }); +}; + +/** + * 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 { + return new Promise(resolve => { + PushNotification.getDeliveredNotifications((notifications: Record[]) => resolve(notifications)); + }); + } catch (error) { + console.error('Error getting delivered notifications:', error); + throw error; + } +}; + +export const removeDeliveredNotifications = (identifiers = []) => { + PushNotification.removeDeliveredNotifications(identifiers); +}; + +export const setApplicationIconBadgeNumber = (badges: number) => { + PushNotification.setApplicationIconBadgeNumber(badges); +}; + +export const removeAllDeliveredNotifications = () => { + PushNotification.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.debug('initializeNotifications: Starting initialization'); + try { + const noAndDontAskFlag = await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG); + console.debug('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.debug('Base URI set to:', baseURI); + + setApplicationIconBadgeNumber(0); + + // Only check permissions, never request + currentPermissionStatus = await checkNotificationPermissionStatus(); + console.debug('initializeNotifications: Permission status:', currentPermissionStatus); + + // Handle Android 13+ permissions differently + const canProceed = + Platform.OS === 'android' + ? isNotificationsCapable && (await checkAndroidNotificationPermission()) + : currentPermissionStatus === 'granted'; + + if (canProceed) { + console.debug('initializeNotifications: Can proceed with notification setup'); + const token = await getPushToken(); + + if (token) { + console.debug('initializeNotifications: Existing token found, configuring'); + await configureNotifications(onProcessNotifications); + await postTokenConfig(); + } else { + console.debug('initializeNotifications: No token found, will request permissions'); + await tryToObtainPermissions(); + } + } else { + console.debug('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/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..629e79300b5 --- /dev/null +++ b/blue_modules/sizeClass.ts @@ -0,0 +1,141 @@ +import { Dimensions, Platform, AppState, AppStateStatus } from 'react-native'; +import { useState, useEffect } from 'react'; +import { isDesktop } from './environment'; + +// 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; +} + +/** + * Get current size class information based on device dimensions + */ +export function getSizeClass(): SizeClassInfo { + // Get device dimensions + const { width, height } = Dimensions.get('window'); + const isLandscape = width > height; + const orientation = isLandscape ? 'landscape' : 'portrait'; + + // Determine horizontal size class (following iOS conventions) + let horizontalSizeClass: SizeClass; + + if (Platform.OS === 'ios' && Platform.isPad) { + // iPads always have Regular width + horizontalSizeClass = SizeClass.Regular; + } else if (isDesktop) { + // Desktop systems get Large width + horizontalSizeClass = SizeClass.Large; + } else if (isLandscape && width >= 667) { + // iPhone Plus models (and modern equivalent sizes) in landscape: Regular width + // 667 points corresponds roughly to iPhone Plus models + horizontalSizeClass = SizeClass.Regular; + } else { + // Regular iPhones: Compact width + horizontalSizeClass = SizeClass.Compact; + } + + // Determine vertical size class (following iOS conventions) + let verticalSizeClass: SizeClass; + + if (Platform.OS === 'ios' && Platform.isPad) { + // iPads always have Regular height + verticalSizeClass = SizeClass.Regular; + } else if (isDesktop) { + // Desktop systems get Large height + verticalSizeClass = SizeClass.Large; + } else if (isLandscape) { + // All iPhones in landscape: Compact height + verticalSizeClass = SizeClass.Compact; + } else { + // iPhones in portrait: Regular height + verticalSizeClass = SizeClass.Regular; + } + + // Derive overall size class - simplified logic to avoid redundant comparisons + let sizeClass: SizeClass; + + if (horizontalSizeClass === SizeClass.Compact) { + // If width is compact, overall is compact + sizeClass = SizeClass.Compact; + } else { + // Otherwise, width is Regular or Large, so overall is Large + // (per requirements that any non-Compact width device is considered Large) + sizeClass = SizeClass.Large; + } + + // Determine isLargeScreen property (true for Regular and Large widths) + const isLargeScreen = horizontalSizeClass !== SizeClass.Compact; + + return { + horizontalSizeClass, + verticalSizeClass, + sizeClass, + orientation, + isCompact: sizeClass === SizeClass.Compact, + isLarge: sizeClass === SizeClass.Large, + isLargeScreen, + }; +} + +/** + * React hook to use size classes in components + */ +export function useSizeClass(): SizeClassInfo { + const [sizeClassInfo, setSizeClassInfo] = useState(getSizeClass()); + + useEffect(() => { + // Update size class when dimensions change + const updateSizeClass = () => { + const newInfo = getSizeClass(); + setSizeClassInfo(newInfo); + console.debug( + `[SizeClass] Updated:`, + `horizontal=${SizeClass[newInfo.horizontalSizeClass]}`, + `vertical=${SizeClass[newInfo.verticalSizeClass]}`, + `orientation=${newInfo.orientation}`, + `isLargeScreen=${newInfo.isLargeScreen}`, + ); + }; + + const dimensionSubscription = Dimensions.addEventListener('change', updateSizeClass); + + // Also update when app becomes active + const handleAppStateChange = (nextAppState: AppStateStatus) => { + if (nextAppState === 'active') { + updateSizeClass(); + } + }; + + const appStateSubscription = AppState.addEventListener('change', handleAppStateChange); + + // Clean up + return () => { + dimensionSubscription.remove(); + appStateSubscription.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..e8a46d9af96 --- /dev/null +++ b/blue_modules/start-and-decrypt.ts @@ -0,0 +1,75 @@ +import { Platform } from 'react-native'; + +import { BlueApp as BlueAppClass } from '../class/'; +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, 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..80374bb4295 100644 --- a/blue_modules/ur/index.js +++ b/blue_modules/ur/index.js @@ -1,23 +1,36 @@ -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, MultisigHDWallet } from '../../class'; +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 +38,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,12 +62,25 @@ 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) { +function encodeUR(arg1, arg2, walletID) { + if (walletID && useBBQRWalletIDs.includes(walletID)) { + // assume arg1 is hex of a PSBT, arg2 is capacity per frame + const minSplit = Math.max(1, Math.ceil(arg1.length / 2 / arg2)); + const ret = splitQRs(hexToUint8Array(arg1), 'P', { minSplit }); + return ret.parts; + } + return useURv1 ? encodeURv1(arg1, arg2) : encodeURv2(arg1, arg2); } @@ -60,7 +97,7 @@ function encodeURv1(arg1, arg2) { /** * * @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 +162,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 +222,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 +272,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 +292,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 +340,49 @@ 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 }; 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/BlueApp.js b/class/blue-app.ts similarity index 56% rename from BlueApp.js rename to class/blue-app.ts index c0ee48a831d..070b8b9ad89 100644 --- a/BlueApp.js +++ b/class/blue-app.ts @@ -1,60 +1,110 @@ -import Biometric from './class/biometrics'; -import { Platform } from 'react-native'; -import loc from './loc'; 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 * 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; +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 -const prompt = require('./helpers/prompt'); -const currency = require('./blue_modules/currency'); -const BlueElectrum = require('./blue_modules/BlueElectrum'); -BlueElectrum.connectMain(); -class AppStorage { +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 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]; + 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() { - /** {Array.} */ 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() { - if (!(typeof navigator !== 'undefined' && navigator.product === 'ReactNative')) return; - for (const key of this.constructor.keys2migrate) { + // 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) { @@ -68,13 +118,9 @@ class AppStorage { /** * 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') { + 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); @@ -84,28 +130,20 @@ class AppStorage { /** * 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') { + getItem = (key: string): Promise => { + if (isReactNative) { return RNSecureKeyStore.get(key); } else { return AsyncStorage.getItem(key); } }; - /** - * @throws Error - * @param key {string} - * @returns {Promise<*>|null} - */ - getItemWithFallbackToRealm = async key => { + getItemWithFallbackToRealm = async (key: string): Promise => { let value; try { return await this.getItem(key); - } catch (error) { + } catch (error: any) { console.warn('error reading', key, error.message); console.warn('fallback to realm'); const realmKeyValue = await this.openRealmKeyValue(); @@ -113,6 +151,7 @@ class AppStorage { 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; } @@ -120,23 +159,23 @@ class AppStorage { } }; - storageIsEncrypted = async () => { + storageIsEncrypted = async (): Promise => { let data; try { - data = await this.getItemWithFallbackToRealm(AppStorage.FLAG_ENCRYPTED); - } catch (error) { - console.warn('error reading `' + AppStorage.FLAG_ENCRYPTED + '` key:', error.message); + data = await this.getItemWithFallbackToRealm(BlueApp.FLAG_ENCRYPTED); + } catch (error: any) { + console.warn('error reading `' + BlueApp.FLAG_ENCRYPTED + '` key:', error.message); return false; } - return !!data; + return Boolean(data); }; - isPasswordInUse = async password => { + isPasswordInUse = async (password: string) => { try { let data = await this.getItem('data'); data = this.decryptData(data, password); - return !!data; + return Boolean(data); } catch (_e) { return false; } @@ -145,12 +184,8 @@ class AppStorage { /** * 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) { + decryptData(data: string, password: string): boolean | string { data = JSON.parse(data); let decrypted; let num = 0; @@ -167,19 +202,20 @@ class AppStorage { return false; } - decryptStorage = async password => { + decryptStorage = async (password: string): Promise => { if (password === this.cachedPassword) { this.cachedPassword = undefined; await this.saveToDisk(); this.wallets = []; - this.tx_metadata = []; + this.tx_metadata = {}; + this.counterparty_metadata = {}; return this.loadFromDisk(); } else { throw new Error('Incorrect password. Please, try again.'); } }; - encryptStorage = async password => { + encryptStorage = async (password: string): Promise => { // assuming the storage is not yet encrypted await this.saveToDisk(); let data = await this.getItem('data'); @@ -191,23 +227,23 @@ class AppStorage { data = JSON.stringify(data); this.cachedPassword = password; await this.setItem('data', data); - await this.setItem(AppStorage.FLAG_ENCRYPTED, '1'); + await this.setItem(BlueApp.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 => { + 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 = { + const data: TBucketStorage = { wallets: [], tx_metadata: {}, + counterparty_metadata: {}, }; let buckets = await this.getItem('data'); @@ -219,21 +255,21 @@ class AppStorage { return (await this.getItem('data')) === bucketsString; }; - hashIt = s => { - return createHash('sha256').update(s).digest().toString('hex'); + 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. - * - * @returns {Promise} */ - async getRealm() { + async getRealmForTransactions() { + const cacheFolderPath = RNFS.CachesDirectoryPath; // Path to cache folder const password = this.hashIt(this.cachedPassword || 'fyegjitkyf[eqjnc.lf'); - const buf = Buffer.from(this.hashIt(password) + this.hashIt(password), 'hex'); + const buf = hexToUint8Array(this.hashIt(password) + this.hashIt(password)); const encryptionKey = Int8Array.from(buf); - const path = this.hashIt(this.hashIt(password)) + '-wallettransactions.realm'; + const fileName = this.hashIt(this.hashIt(password)) + '-wallettransactions.realm'; + const path = `${cacheFolderPath}/${fileName}`; // Use cache folder path const schema = [ { @@ -246,20 +282,24 @@ class AppStorage { }, }, ]; + // @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 device unique id + * Returns instace of the Realm database, which is encrypted by random bytes stored in keychain. * Database file is static. * * @returns {Promise} */ - async openRealmKeyValue() { + async openRealmKeyValue(): Promise { + const cacheFolderPath = RNFS.CachesDirectoryPath; // Path to cache folder const service = 'realm_encryption_key'; let password; const credentials = await Keychain.getGenericPassword({ service }); @@ -267,13 +307,13 @@ class AppStorage { password = credentials.password; } else { const buf = await randomBytes(64); - password = buf.toString('hex'); + password = uint8ArrayToHex(buf); await Keychain.setGenericPassword(service, password, { service }); } - const buf = Buffer.from(password, 'hex'); + const buf = hexToUint8Array(password); const encryptionKey = Int8Array.from(buf); - const path = 'keyvalue.realm'; + const path = `${cacheFolderPath}/keyvalue.realm`; // Use cache folder path const schema = [ { @@ -285,14 +325,17 @@ class AppStorage { }, }, ]; + // @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, key, value) { + saveToRealmKeyValue(realmkeyValue: Realm, key: string, value: any) { realmkeyValue.write(() => { realmkeyValue.create( 'KeyValue', @@ -312,66 +355,75 @@ class AppStorage { * @param password If present means storage must be decrypted before usage * @returns {Promise.} */ - async loadFromDisk(password) { - let data = await this.getItemWithFallbackToRealm('data'); + 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) { - data = this.decryptData(data, password); - if (data) { + dataRaw = this.decryptData(dataRaw, password); + if (dataRaw) { // password is good, cache it this.cachedPassword = password; } } - if (data !== null) { + if (dataRaw !== null) { let realm; try { - realm = await this.getRealm(); - } catch (error) { - alert(error.message); + realm = await this.getRealmForTransactions(); + } catch (error: any) { + presentAlert({ message: error.message }); } - data = JSON.parse(data); + 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 instatiating correct object + // deciding which type is wallet and instantiating correct object const tempObj = JSON.parse(key); - let unserializedWallet; + let unserializedWallet: TWallet; switch (tempObj.type) { case SegwitBech32Wallet.type: - unserializedWallet = SegwitBech32Wallet.fromJson(key); + unserializedWallet = SegwitBech32Wallet.fromJson(key) as unknown as SegwitBech32Wallet; break; case SegwitP2SHWallet.type: - unserializedWallet = SegwitP2SHWallet.fromJson(key); + unserializedWallet = SegwitP2SHWallet.fromJson(key) as unknown as SegwitP2SHWallet; break; case WatchOnlyWallet.type: - unserializedWallet = WatchOnlyWallet.fromJson(key); + unserializedWallet = WatchOnlyWallet.fromJson(key) as unknown as WatchOnlyWallet; unserializedWallet.init(); if (unserializedWallet.isHd() && !unserializedWallet.isXpubValid()) { continue; } break; case HDLegacyP2PKHWallet.type: - unserializedWallet = HDLegacyP2PKHWallet.fromJson(key); + unserializedWallet = HDLegacyP2PKHWallet.fromJson(key) as unknown as HDLegacyP2PKHWallet; break; case HDSegwitP2SHWallet.type: - unserializedWallet = HDSegwitP2SHWallet.fromJson(key); + unserializedWallet = HDSegwitP2SHWallet.fromJson(key) as unknown as HDSegwitP2SHWallet; break; case HDSegwitBech32Wallet.type: - unserializedWallet = HDSegwitBech32Wallet.fromJson(key); + 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); + unserializedWallet = HDLegacyBreadwalletWallet.fromJson(key) as unknown as HDLegacyBreadwalletWallet; break; case HDLegacyElectrumSeedP2PKHWallet.type: - unserializedWallet = HDLegacyElectrumSeedP2PKHWallet.fromJson(key); + unserializedWallet = HDLegacyElectrumSeedP2PKHWallet.fromJson(key) as unknown as HDLegacyElectrumSeedP2PKHWallet; break; case HDSegwitElectrumSeedP2WPKHWallet.type: - unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key); + unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key) as unknown as HDSegwitElectrumSeedP2WPKHWallet; break; case MultisigHDWallet.type: - unserializedWallet = MultisigHDWallet.fromJson(key); + unserializedWallet = MultisigHDWallet.fromJson(key) as unknown as MultisigHDWallet; break; case HDAezeedWallet.type: - unserializedWallet = HDAezeedWallet.fromJson(key); + 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(':')) { @@ -380,25 +432,24 @@ class AppStorage { unserializedWallet.passphrase = passphrase; } - break; - case LightningLdkWallet.type: - unserializedWallet = LightningLdkWallet.fromJson(key); break; case SLIP39SegwitP2SHWallet.type: - unserializedWallet = SLIP39SegwitP2SHWallet.fromJson(key); + unserializedWallet = SLIP39SegwitP2SHWallet.fromJson(key) as unknown as SLIP39SegwitP2SHWallet; break; case SLIP39LegacyP2PKHWallet.type: - unserializedWallet = SLIP39LegacyP2PKHWallet.fromJson(key); + unserializedWallet = SLIP39LegacyP2PKHWallet.fromJson(key) as unknown as SLIP39LegacyP2PKHWallet; break; case SLIP39SegwitBech32Wallet.type: - unserializedWallet = SLIP39SegwitBech32Wallet.fromJson(key); + unserializedWallet = SLIP39SegwitBech32Wallet.fromJson(key) as unknown as SLIP39SegwitBech32Wallet; + break; + case LightningArkWallet.type: + unserializedWallet = LightningArkWallet.fromJson(key) as unknown as LightningArkWallet; break; case LightningCustodianWallet.type: { - /** @type {LightningCustodianWallet} */ - unserializedWallet = LightningCustodianWallet.fromJson(key); - let lndhub = false; + unserializedWallet = LightningCustodianWallet.fromJson(key) as unknown as LightningCustodianWallet; + let lndhub: false | any = false; try { - lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB); + lndhub = await getLNDHub(); } catch (error) { console.warn(error); } @@ -415,16 +466,21 @@ class AppStorage { 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); + unserializedWallet = LegacyWallet.fromJson(key) as unknown as LegacyWallet; break; } try { if (realm) this.inflateWalletFromRealm(realm, unserializedWallet); - } catch (error) { - alert(error.message); + } catch (error: any) { + presentAlert({ message: error.message }); } // done @@ -432,6 +488,7 @@ class AppStorage { 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(); @@ -447,16 +504,10 @@ class AppStorage { * * @param wallet {AbstractWallet} */ - deleteWallet = wallet => { + deleteWallet = (wallet: TWallet): void => { 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 @@ -469,39 +520,44 @@ class AppStorage { this.wallets = tempWallets; }; - inflateWalletFromRealm(realm, walletToInflate) { + inflateWalletFromRealm(realm: Realm, walletToInflate: TWallet) { const transactions = realm.objects('WalletTransactions'); - const transactionsForWallet = transactions.filtered(`walletid = "${walletToInflate.getID()}"`); + const transactionsForWallet = transactions.filtered(`walletid = "${walletToInflate.getID()}"`) as unknown as TRealmTransaction[]; 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)); + 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] || []; - walletToInflate._txs_by_external_index[tx.index].push(JSON.parse(tx.tx)); + const transaction = JSON.parse(tx.tx); + (walletToInflate._txs_by_external_index[tx.index] as Transaction[]).push(transaction); } } 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)); + 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] || []; - walletToInflate._txs_by_internal_index[tx.index].push(JSON.parse(tx.tx)); + 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 || []; - walletToInflate._txs_by_external_index.push(JSON.parse(tx.tx)); + const transaction = JSON.parse(tx.tx); + (walletToInflate._txs_by_external_index as Transaction[]).push(transaction); } } } - offloadWalletToRealm(realm, wallet) { + offloadWalletToRealm(realm: Realm, wallet: TWallet): void { const id = wallet.getID(); - const walletToSave = wallet._hdWalletInstance ?? wallet; + 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 @@ -511,6 +567,7 @@ class AppStorage { 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', @@ -536,6 +593,7 @@ class AppStorage { // 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( @@ -552,6 +610,7 @@ class AppStorage { } 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( @@ -577,57 +636,61 @@ class AppStorage { * * @returns {Promise} Result of storage save */ - async saveToDisk() { + async saveToDisk(): Promise { if (savingInProgress) { console.warn('saveToDisk is in progress'); - if (++savingInProgress > 10) alert('Critical error. Last actions were not saved'); // should never happen + 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 = []; + const walletsToSave: string[] = []; // serialized wallets let realm; try { - realm = await this.getRealm(); - } catch (error) { - alert(error.message); + 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 (key._hdWalletInstance) keyCloned._hdWalletInstance = Object.assign({}, key._hdWalletInstance); + 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 (key._hdWalletInstance) { - keyCloned._hdWalletInstance._txs_by_external_index = {}; - keyCloned._hdWalletInstance._txs_by_internal_index = {}; - } - if (keyCloned._bip47_instance) { + 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 = { + + 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 = []; + const newData: string[] = []; // serialized buckets let num = 0; for (const bucket of buckets) { let decrypted; @@ -653,20 +716,21 @@ class AppStorage { 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' : ''); + 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, AppStorage.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); + this.saveToRealmKeyValue(realmkeyValue, BlueApp.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); realmkeyValue.close(); - } catch (error) { + } catch (error: any) { console.error('save to disk exception:', error.message); - alert('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(); @@ -681,10 +745,8 @@ class AppStorage { * 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 => { + fetchWalletBalances = async (index?: number): Promise => { console.log('fetchWalletBalances for wallet#', typeof index === 'undefined' ? '(all)' : index); if (index || index === 0) { let c = 0; @@ -711,17 +773,16 @@ class AppStorage { * blank to fetch from all wallets * @return {Promise.} */ - fetchWalletTransactions = async index => { + 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 (wallet.fetchPendingTransactions) { + + if ('fetchPendingTransactions' in wallet) { await wallet.fetchPendingTransactions(); - } - if (wallet.fetchUserInvoices) { await wallet.fetchUserInvoices(); } } @@ -729,29 +790,28 @@ class AppStorage { } else { for (const wallet of this.wallets) { await wallet.fetchTransactions(); - if (wallet.fetchPendingTransactions) { + if ('fetchPendingTransactions' in wallet) { await wallet.fetchPendingTransactions(); - } - if (wallet.fetchUserInvoices) { await wallet.fetchUserInvoices(); } } } }; - fetchSenderPaymentCodes = async index => { + 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 (!(this.wallets[index].allowBIP47() && this.wallets[index].isBIP47Enabled())) return; - await this.wallets[index].fetchBIP47SenderPaymentCodes(); + 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 { for (const wallet of this.wallets) { try { - if (!(wallet.allowBIP47() && wallet.isBIP47Enabled())) continue; + if (!(wallet.allowBIP47() && wallet.isBIP47Enabled() && 'fetchBIP47SenderPaymentCodes' in wallet)) continue; await wallet.fetchBIP47SenderPaymentCodes(); } catch (error) { console.error('Failed to fetch sender payment codes for wallet', wallet.label, error); @@ -760,11 +820,7 @@ class AppStorage { } }; - /** - * - * @returns {Array.} - */ - getWallets = () => { + getWallets = (): TWallet[] => { return this.wallets; }; @@ -772,51 +828,62 @@ class AppStorage { * 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} + * @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, limit = Infinity, includeWalletsWithHideTransactionsEnabled = false) => { + getTransactions = ( + index?: number, + limit: number = Infinity, + includeWalletsWithHideTransactionsEnabled: boolean = false, + ): ExtendedTransaction[] => { if (index || index === 0) { - let txs = []; + 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; } } - return txs; } - let txs = []; + const txs: ExtendedTransaction[] = []; for (const wallet of this.wallets.filter(w => includeWalletsWithHideTransactionsEnabled || !w.getHideTransactionsInWalletsList())) { - const walletTransactions = wallet.getTransactions(); + const walletTransactions: Transaction[] = wallet.getTransactions(); const walletID = wallet.getID(); + const walletPreferredBalanceUnit = wallet.getPreferredBalanceUnit(); for (const t of walletTransactions) { - t.walletPreferredBalanceUnit = wallet.getPreferredBalanceUnit(); - t.walletID = walletID; + txs.push({ + ...t, + walletID, + walletPreferredBalanceUnit, + }); } - 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; + .sort((a, b) => { + return b.timestamp - a.timestamp; }) .slice(0, limit); }; /** * Getter for a sum of all balances of all wallets - * - * @return {number} */ - getBalance = () => { + getBalance = (): number => { let finalBalance = 0; for (const wal of this.wallets) { finalBalance += wal.getBalance(); @@ -824,46 +891,46 @@ class AppStorage { return finalBalance; }; - isAdvancedModeEnabled = async () => { + isHandoffEnabled = async (): Promise => { try { - return !!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED)); + return !!(await AsyncStorage.getItem(BlueApp.HANDOFF_STORAGE_KEY)); } catch (_) {} return false; }; - setIsAdvancedModeEnabled = async value => { - await AsyncStorage.setItem(AppStorage.ADVANCED_MODE_ENABLED, value ? '1' : ''); + setIsHandoffEnabled = async (value: boolean): Promise => { + await AsyncStorage.setItem(BlueApp.HANDOFF_STORAGE_KEY, value ? '1' : ''); }; - isHandoffEnabled = async () => { + isDoNotTrackEnabled = async (): Promise => { 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)); + 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 (_) {} - return false; + const doNotTrackValue = await DefaultPreference.get(BlueApp.DO_NOT_TRACK); + return doNotTrackValue === '1' || false; }; - setDoNotTrack = async value => { - await AsyncStorage.setItem(AppStorage.DO_NOT_TRACK, value ? '1' : ''); + 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 - * - * @param ms {number} Milliseconds to sleep - * @returns {Promise | Promise<*>>} */ - sleep = ms => { + sleep = (ms: number): Promise => { return new Promise(resolve => setTimeout(resolve, ms)); }; @@ -873,72 +940,38 @@ class AppStorage { 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 + async moveRealmFilesToCacheDirectory() { + const documentPath = RNFS.DocumentDirectoryPath; // Path to documentPath folder + const cachePath = RNFS.CachesDirectoryPath; // Path to cachePath folder 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 (!(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'), + ); - if (success) { - console.log('loaded from disk'); - // We want to return true to let the UnlockWith screen that its ok to proceed. - return true; - } + 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 (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; + 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}`); } - } 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/class/camera.js b/class/camera.ts similarity index 72% rename from class/camera.js rename to class/camera.ts index d6baab18041..e9c5116796f 100644 --- a/class/camera.js +++ b/class/camera.ts @@ -1,8 +1,7 @@ -import { Linking, Alert } from 'react-native'; -import { getSystemName } from 'react-native-device-info'; -import loc from '../loc'; +import { Alert, Linking } from 'react-native'; -const isDesktop = getSystemName() === 'Mac OS X'; +import { isDesktop } from '../blue_modules/environment'; +import loc from '../loc'; export const openPrivacyDesktopSettings = () => { if (isDesktop) { @@ -12,7 +11,7 @@ export const openPrivacyDesktopSettings = () => { } }; -export const presentCameraNotAuthorizedAlert = error => { +export const presentCameraNotAuthorizedAlert = (error: string) => { Alert.alert( loc.errors.error, error, 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.ts similarity index 66% rename from class/deeplink-schema-match.js rename to class/deeplink-schema-match.ts index eb1fd38e76b..c3dfd82d8e3 100644 --- a/class/deeplink-schema-match.js +++ b/class/deeplink-schema-match.ts @@ -1,17 +1,25 @@ -import { LightningCustodianWallet, WatchOnlyWallet } from './'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import RNFS from 'react-native-fs'; +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 Lnurl from './lnurl'; +import { WatchOnlyWallet } from './'; import Azteco from './azteco'; -const bitcoin = require('bitcoinjs-lib'); -const bip21 = require('bip21'); -const BlueApp = require('../BlueApp'); -const AppStorage = BlueApp.AppStorage; +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) { + static hasSchema(schemaString: string): boolean { if (typeof schemaString !== 'string' || schemaString.length <= 0) return false; const lowercaseString = schemaString.trim().toLowerCase(); return ( @@ -31,7 +39,11 @@ class DeeplinkSchemaMatch { * @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: () => {} }) { + static navigationRouteFor( + event: { url: string }, + completionHandler: (args: TCompletionHandlerParams) => void, + context: TContext = { wallets: [], saveToDisk: () => {}, addWallet: () => {}, setSharedCosigner: () => {} }, + ) { if (event.url === null) { return; } @@ -62,7 +74,7 @@ class DeeplinkSchemaMatch { ]); } else if (action === 'openReceive') { completionHandler([ - 'ReceiveDetailsRoot', + 'DetailViewStackScreensStack', { screen: 'ReceiveDetails', params: { @@ -74,9 +86,9 @@ class DeeplinkSchemaMatch { } else if (wallet.chain === Chain.OFFCHAIN) { if (action === 'openSend') { completionHandler([ - 'ScanLndInvoiceRoot', + 'ScanLNDInvoiceRoot', { - screen: 'ScanLndInvoice', + screen: 'ScanLNDInvoice', params: { walletID: wallet.getID(), }, @@ -87,8 +99,8 @@ class DeeplinkSchemaMatch { } } } - } else if (DeeplinkSchemaMatch.isPossiblySignedPSBTFile(event.url)) { - RNFS.readFile(decodeURI(event.url)) + } else if (DeeplinkSchemaMatch.isPossiblyPSBTFile(event.url)) { + readFileOutsideSandbox(decodeURI(event.url)) .then(file => { if (file) { completionHandler([ @@ -104,8 +116,19 @@ class DeeplinkSchemaMatch { }) .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; + let isBothBitcoinAndLightning: TBothBitcoinAndLightning; try { isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(event.url); } catch (e) { @@ -115,7 +138,7 @@ class DeeplinkSchemaMatch { completionHandler([ 'SelectWallet', { - onWalletSelect: (wallet, { navigation }) => { + onWalletSelect: (wallet: TWallet, { navigation }: any) => { navigation.pop(); // close select wallet screen navigation.navigate(...DeeplinkSchemaMatch.isBothBitcoinAndLightningOnWalletSelect(wallet, isBothBitcoinAndLightning)); }, @@ -133,9 +156,9 @@ class DeeplinkSchemaMatch { ]); } else if (DeeplinkSchemaMatch.isLightningInvoice(event.url)) { completionHandler([ - 'ScanLndInvoiceRoot', + 'ScanLNDInvoiceRoot', { - screen: 'ScanLndInvoice', + screen: 'ScanLNDInvoice', params: { uri: event.url.replace('://', ':'), }, @@ -155,12 +178,12 @@ class DeeplinkSchemaMatch { }, ]); } else if (Lnurl.isLightningAddress(event.url)) { - // this might be not just an email but a lightning addres + // this might be not just an email but a lightning address // @see https://lightningaddress.com completionHandler([ - 'ScanLndInvoiceRoot', + 'ScanLNDInvoiceRoot', { - screen: 'ScanLndInvoice', + screen: 'ScanLNDInvoice', params: { uri: event.url, }, @@ -190,64 +213,6 @@ class DeeplinkSchemaMatch { (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', @@ -277,7 +242,7 @@ class DeeplinkSchemaMatch { * @param url {string} * @return {string|boolean} */ - static getServerFromSetElectrumServerAction(url) { + 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]); @@ -291,35 +256,26 @@ class DeeplinkSchemaMatch { * @param url {string} * @return {string|boolean} */ - static getUrlFromSetLndhubUrlAction(url) { + 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) { - return ( - (filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) && - filePath.toLowerCase().endsWith('.txn') - ); + static isTXNFile(filePath: string): boolean { + return filePath.toLowerCase().endsWith('.txn'); } - static isPossiblySignedPSBTFile(filePath) { - return ( - (filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) && - filePath.toLowerCase().endsWith('-signed.psbt') - ); + static isPossiblyPSBTFile(filePath: string): boolean { + return filePath.toLowerCase().endsWith('.psbt'); } - static isPossiblyPSBTFile(filePath) { - return ( - (filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) && - filePath.toLowerCase().endsWith('.psbt') - ); + static isPossiblyCosignerFile(filePath: string): boolean { + return filePath.toLowerCase().endsWith('.bwcosigner'); } - static isBothBitcoinAndLightningOnWalletSelect(wallet, uri) { + static isBothBitcoinAndLightningOnWalletSelect(wallet: TWallet, uri: any): TCompletionHandlerParams { if (wallet.chain === Chain.ONCHAIN) { return [ 'SendDetailsRoot', @@ -331,11 +287,11 @@ class DeeplinkSchemaMatch { }, }, ]; - } else if (wallet.chain === Chain.OFFCHAIN) { + } else { return [ - 'ScanLndInvoiceRoot', + 'ScanLNDInvoiceRoot', { - screen: 'ScanLndInvoice', + screen: 'ScanLNDInvoice', params: { uri: uri.lndInvoice, walletID: wallet.getID(), @@ -345,7 +301,7 @@ class DeeplinkSchemaMatch { } } - static isBitcoinAddress(address) { + static isBitcoinAddress(address: string): boolean { address = address.replace('://', ':').replace('bitcoin:', '').replace('BITCOIN:', '').replace('bitcoin=', '').split('?')[0]; let isValidBitcoinAddress = false; try { @@ -357,7 +313,7 @@ class DeeplinkSchemaMatch { return isValidBitcoinAddress; } - static isLightningInvoice(invoice) { + static isLightningInvoice(invoice: string): boolean { let isValidLightningInvoice = false; if ( invoice.toLowerCase().startsWith('lightning:lnb') || @@ -369,19 +325,33 @@ class DeeplinkSchemaMatch { return isValidLightningInvoice; } - static isLnUrl(text) { + static isLnUrl(text: string): boolean { return Lnurl.isLnurl(text); } - static isWidgetAction(text) { + static isWidgetAction(text: string): boolean { return text.startsWith('widget?action='); } - static isBothBitcoinAndLightning(url) { + 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; - let lndInvoice; + 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. @@ -413,8 +383,10 @@ class DeeplinkSchemaMatch { return undefined; } - static bip21decode(uri) { - if (!uri) return {}; + 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:'); @@ -423,37 +395,40 @@ class DeeplinkSchemaMatch { 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; + 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 (!(Number(argument.amount) > 0)) { - delete argument.amount; + if (key === 'amount' && !(Number(options[key]) > 0)) { + delete options[key]; } } - return bip21.encode.apply(bip21, argumentsArray); + return bip21.encode(address, options); } - static decodeBitcoinUri(uri) { - let amount = ''; - let parsedBitcoinUri = null; + static decodeBitcoinUri(uri: string) { + let amount; let address = uri || ''; let memo = ''; let payjoinUrl = ''; try { - parsedBitcoinUri = DeeplinkSchemaMatch.bip21decode(uri); - address = 'address' in parsedBitcoinUri ? parsedBitcoinUri.address : address; + const parsedBitcoinUri = DeeplinkSchemaMatch.bip21decode(uri); + address = parsedBitcoinUri.address ? parsedBitcoinUri.address.toString() : address; if ('options' in parsedBitcoinUri) { - if ('amount' in parsedBitcoinUri.options) { - amount = parsedBitcoinUri.options.amount.toString(); - amount = parsedBitcoinUri.options.amount; + if (parsedBitcoinUri.options.amount) { + amount = Number(parsedBitcoinUri.options.amount); } - if ('label' in parsedBitcoinUri.options) { - memo = parsedBitcoinUri.options.label || memo; + if (parsedBitcoinUri.options.label) { + memo = parsedBitcoinUri.options.label; } - if ('pj' in parsedBitcoinUri.options) { + if (parsedBitcoinUri.options.pj) { payjoinUrl = parsedBitcoinUri.options.pj; } } diff --git a/class/hd-segwit-bech32-transaction.js b/class/hd-segwit-bech32-transaction.ts similarity index 79% rename from class/hd-segwit-bech32-transaction.js rename to class/hd-segwit-bech32-transaction.ts index 758a120a9ab..adb2b99fdaf 100644 --- a/class/hd-segwit-bech32-transaction.js +++ b/class/hd-segwit-bech32-transaction.ts @@ -1,21 +1,31 @@ +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'; -const bitcoin = require('bitcoinjs-lib'); -const BlueElectrum = require('../blue_modules/BlueElectrum'); -const reverse = require('buffer-reverse'); -const BigNumber = require('bignumber.js'); +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, txid, wallet) { + constructor(txhex: string | null, txid: string | null, wallet: HDSegwitBech32Wallet | null) { if (!txhex && !txid) throw new Error('Bad arguments'); this._txhex = txhex; this._txid = txid; @@ -40,7 +50,8 @@ export class HDSegwitBech32Transaction { * @private */ async _fetchTxhexAndDecode() { - const hexes = await BlueElectrum.multiGetTransactionByTxid([this._txid], 10, false); + 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); @@ -54,6 +65,7 @@ export class HDSegwitBech32Transaction { */ 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) { @@ -81,7 +93,7 @@ export class HDSegwitBech32Transaction { * @private */ async _fetchRemoteTx() { - const result = await BlueElectrum.multiGetTransactionByTxid([this._txid || this._txDecoded.getId()]); + const result = await BlueElectrum.multiGetTransactionByTxid([this._txid || this._txDecoded!.getId()], true); this._remoteTx = Object.values(result)[0]; } @@ -98,7 +110,7 @@ export class HDSegwitBech32Transaction { /** * 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. + * definitely initiated by us, can be RBF'ed. * * @returns {Promise} */ @@ -106,9 +118,9 @@ export class HDSegwitBech32Transaction { 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.txid === (this._txid || this._txDecoded!.getId())) { // its our transaction, and its spending transaction, which means we initiated it - if (tx.value < 0) found = true; + if (tx.value && tx.value < 0) found = true; } } return found; @@ -125,8 +137,8 @@ export class HDSegwitBech32Transaction { 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; + if (tx.txid === (this._txid || this._txDecoded!.getId())) { + if (tx.value && tx.value > 0) found = true; } } return found; @@ -147,28 +159,27 @@ export class HDSegwitBech32Transaction { 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) { - let reversedHash = Buffer.from(reverse(inp.hash)); - reversedHash = reversedHash.toString('hex'); - prevInputs.push(reversedHash); + prevInputs.push(uint8ArrayToHex(new Uint8Array(inp.hash).reverse())); } - const prevTransactions = await BlueElectrum.multiGetTransactionByTxid(prevInputs); + const prevTransactions = await BlueElectrum.multiGetTransactionByTxid(prevInputs, true); // fetched, now lets count how much satoshis went in let wentIn = 0; - const utxos = []; + const utxos: CreateTransactionUtxo[] = []; for (const inp of this._txDecoded.ins) { - let reversedHash = Buffer.from(reverse(inp.hash)); - reversedHash = reversedHash.toString('hex'); + 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 address = SegwitBech32Wallet.witnessToAddress(inp.witness[inp.witness.length - 1]); - utxos.push({ vout: inp.index, value, txId: reversedHash, address }); + 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 }); } } @@ -176,7 +187,7 @@ export class HDSegwitBech32Transaction { let wasSpent = 0; for (const outp of this._txDecoded.outs) { - wasSpent += +outp.value; + wasSpent += Number(outp.value); } const fee = wentIn - wasSpent; @@ -185,7 +196,7 @@ export class HDSegwitBech32Transaction { // lets take a look at change let changeAmount = 0; - const targets = []; + 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(); @@ -206,7 +217,7 @@ export class HDSegwitBech32Transaction { unconfirmedUtxos.push({ vout: outp.n, value, - txId: this._txid || this._txDecoded.getId(), + txid: this._txid || this._txDecoded.getId(), address, }); } @@ -225,10 +236,11 @@ export class HDSegwitBech32Transaction { 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 = reverse(inp.hash).toString('hex'); + const txidInUtxo = uint8ArrayToHex(new Uint8Array(inp.hash).reverse()); let found = false; for (const spentU of spentUtxos) { @@ -250,12 +262,19 @@ export class HDSegwitBech32Transaction { 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) { - if (!this._wallet.weOwnAddress(SegwitBech32Wallet.scriptPubKeyToAddress(outp.script))) return true; + const outpScript = outp.script; + if ( + !this._wallet.weOwnAddress( + String(SegwitBech32Wallet.scriptPubKeyToAddress(isUint8Array(outpScript) ? uint8ArrayToHex(outpScript) : outpScript)), + ) + ) + return true; } return false; @@ -278,7 +297,7 @@ export class HDSegwitBech32Transaction { * @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) { + async createRBFcancelTx(newFeerate: any) { if (!this._wallet) throw new Error('Wallet required for this method'); if (!this._remoteTx) await this._fetchRemoteTx(); @@ -303,7 +322,7 @@ export class HDSegwitBech32Transaction { * @param newFeerate {number} Sat/byte * @returns {Promise<{outputs: Array, tx: Transaction, inputs: Array, fee: Number}>} */ - async createRBFbumpFee(newFeerate) { + async createRBFbumpFee(newFeerate: number) { if (!this._wallet) throw new Error('Wallet required for this method'); if (!this._remoteTx) await this._fetchRemoteTx(); @@ -333,7 +352,7 @@ export class HDSegwitBech32Transaction { * @param newFeerate {number} sat/byte * @returns {Promise<{outputs: Array, tx: Transaction, inputs: Array, fee: Number}>} */ - async createCPFPbumpFee(newFeerate) { + async createCPFPbumpFee(newFeerate: number) { if (!this._wallet) throw new Error('Wallet required for this method'); if (!this._remoteTx) await this._fetchRemoteTx(); @@ -347,16 +366,21 @@ export class HDSegwitBech32Transaction { const targetFeeRate = 2 * newFeerate - feeRate; let add = 0; + let tx: bitcoin.Transaction | undefined, inputs: CoinSelectReturnInput[], outputs: CoinSelectOutput[], fee: number; while (add <= 128) { - // eslint-disable-next-line no-var - var { tx, inputs, outputs, fee } = this._wallet.createTransaction( + const createdTx = this._wallet.createTransaction( unconfirmedUtxos, [{ address: myAddress }], targetFeeRate + add, myAddress, HDSegwitBech32Wallet.defaultRBFSequence, ); - const combinedFeeRate = (oldFee + fee) / (this._txDecoded.virtualSize() + tx.virtualSize()); // avg + 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; @@ -366,6 +390,7 @@ export class HDSegwitBech32Transaction { } } + // @ts-ignore stfu return { tx, inputs, outputs, fee }; } } diff --git a/class/index.js b/class/index.ts similarity index 88% rename from class/index.js rename to class/index.ts index 9436a9faec9..30257370d1f 100644 --- a/class/index.js +++ b/class/index.ts @@ -1,20 +1,22 @@ +export * from './blue-app'; +export * from './hd-segwit-bech32-transaction'; +export * from './multisig-cosigner'; +export * from './wallets/abstract-hd-wallet'; 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-aezeed-wallet'; export * from './wallets/hd-legacy-breadwallet-wallet'; +export * from './wallets/hd-legacy-electrum-seed-p2pkh-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/hd-segwit-p2sh-wallet'; +export * from './wallets/hd-taproot-wallet'; +export * from './wallets/legacy-wallet'; +export * from './wallets/lightning-custodian-wallet'; +export * from './wallets/lightning-ark-wallet'; export * from './wallets/multisig-hd-wallet'; +export * from './wallets/segwit-bech32-wallet'; +export * from './wallets/segwit-p2sh-wallet'; export * from './wallets/slip39-wallets'; -export * from './hd-segwit-bech32-transaction'; -export * from './multisig-cosigner'; +export * from './wallets/taproot-wallet'; +export * from './wallets/watch-only-wallet'; diff --git a/class/lnurl.js b/class/lnurl.ts similarity index 52% rename from class/lnurl.js rename to class/lnurl.ts index 6e23959e44f..346b75c39e3 100644 --- a/class/lnurl.js +++ b/class/lnurl.ts @@ -1,14 +1,54 @@ import { bech32 } from 'bech32'; import bolt11 from 'bolt11'; -import { isTorDaemonDisabled } from '../blue_modules/environment'; +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 { createHmac } from 'crypto'; -import secp256k1 from 'secp256k1'; -const CryptoJS = require('crypto-js'); -const createHash = require('create-hash'); -const torrific = require('../blue_modules/torrific'); +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 */ @@ -17,15 +57,21 @@ export default class Lnurl { static TAG_WITHDRAW_REQUEST = 'withdrawRequest'; // type of LNURL static TAG_LOGIN_REQUEST = 'login'; // type of LNURL - constructor(url, AsyncStorage) { - this._lnurl = url; + 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) { + 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]; @@ -33,7 +79,7 @@ export default class Lnurl { return null; } - static getUrlFromLnurl(lnurlExample) { + static getUrlFromLnurl(lnurlExample: string): string | false { const found = Lnurl.findlnurl(lnurlExample); if (!found) { if (Lnurl.isLightningAddress(lnurlExample)) { @@ -47,30 +93,25 @@ export default class Lnurl { } const decoded = bech32.decode(found, 10000); - return Buffer.from(bech32.fromWords(decoded.words)).toString(); + return uint8ArrayToString(new Uint8Array(bech32.fromWords(decoded.words))); } - static isLnurl(url) { + static isLnurl(url: string): boolean { return Lnurl.findlnurl(url) !== null; } - static isOnionUrl(url) { + static isOnionUrl(url: string): boolean { return Lnurl.parseOnionUrl(url) !== null; } - static parseOnionUrl(url) { + 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) { - const parsedOnionUrl = Lnurl.parseOnionUrl(url); - if (parsedOnionUrl) { - return _fetchGetTor(parsedOnionUrl); - } - + async fetchGet(url: string): Promise { const resp = await fetch(url, { method: 'GET' }); if (resp.status >= 300) { throw new Error('Bad response from server'); @@ -82,14 +123,14 @@ export default class Lnurl { return reply; } - decodeInvoice(invoice) { + decodeInvoice(invoice: string): DecodedInvoice { const { payeeNodeKey, tags, satoshis, millisatoshis, timestamp } = bolt11.decode(invoice); - const decoded = { - destination: payeeNodeKey, + const decoded: DecodedInvoice = { + destination: payeeNodeKey ?? '', num_satoshis: satoshis ? satoshis.toString() : '0', num_millisatoshis: millisatoshis ? millisatoshis.toString() : '0', - timestamp: timestamp.toString(), + timestamp: timestamp?.toString() ?? '', fallback_addr: '', route_hints: [], }; @@ -98,10 +139,10 @@ export default class Lnurl { const { tagName, data } = tags[i]; switch (tagName) { case 'payment_hash': - decoded.payment_hash = data; + decoded.payment_hash = String(data); break; case 'purpose_commit_hash': - decoded.description_hash = data; + decoded.description_hash = String(data); break; case 'min_final_cltv_expiry': decoded.cltv_expiry = data.toString(); @@ -110,21 +151,21 @@ export default class Lnurl { decoded.expiry = data.toString(); break; case 'description': - decoded.description = data; + decoded.description = String(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(); + 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, comment = '') { + 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) @@ -138,21 +179,19 @@ export default class Lnurl { ); 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 (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); - if (this._lnurlPayServiceBolt11Payload.status === 'ERROR') - throw new Error(this._lnurlPayServiceBolt11Payload.reason || 'requestBolt11FromLnurlPayService() error'); + this._lnurlPayServiceBolt11Payload = (await this.fetchGet(urlToFetch)) as LnurlPayServiceBolt11Payload; // check pr description_hash, amount etc: const decoded = this.decodeInvoice(this._lnurlPayServiceBolt11Payload.pr); - const metadataHash = createHash('sha256').update(this._lnurlPayServicePayload.metadata).digest('hex'); + const metadataHash = uint8ArrayToHex(sha256(this._lnurlPayServicePayload.metadata)); if (metadataHash !== decoded.description_hash) { - throw new Error(`Invoice description_hash doesn't match metadata.`); + 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)}`); @@ -161,11 +200,12 @@ export default class Lnurl { return this._lnurlPayServiceBolt11Payload; } - async callLnurlPayService() { + 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); + 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); @@ -174,8 +214,8 @@ export default class Lnurl { const data = reply; // parse metadata and extract things from it - let image; - let description; + 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]; @@ -191,14 +231,15 @@ export default class Lnurl { } // setting the payment screen with the parameters - const min = Math.ceil((data.minSendable || 0) / 1000); - const max = Math.floor(data.maxSendable / 1000); + 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, @@ -210,7 +251,7 @@ export default class Lnurl { return this._lnurlPayServicePayload; } - async loadSuccessfulPayment(paymentHash) { + async loadSuccessfulPayment(paymentHash: string): Promise { if (!paymentHash) throw new Error('No paymentHash provided'); let data; try { @@ -230,9 +271,9 @@ export default class Lnurl { return true; } - async storeSuccess(paymentHash, preimage) { + async storeSuccess(paymentHash: string, preimage: string | { data: Buffer }): Promise { if (typeof preimage === 'object') { - preimage = Buffer.from(preimage.data).toString('hex'); + preimage = uint8ArrayToHex(new Uint8Array(preimage.data)); } this._preimage = preimage; @@ -247,120 +288,95 @@ export default class Lnurl { ); } - getSuccessAction() { - return this._lnurlPayServiceBolt11Payload.successAction; + getSuccessAction(): any | undefined { + return this._lnurlPayServiceBolt11Payload && 'successAction' in this._lnurlPayServiceBolt11Payload + ? this._lnurlPayServiceBolt11Payload.successAction + : undefined; } - getDomain() { - return this._lnurlPayServicePayload.domain; + getDomain(): string | undefined { + return this._lnurlPayServicePayload ? this._lnurlPayServicePayload.domain : undefined; } - getDescription() { - return this._lnurlPayServicePayload.description; + getDescription(): string | undefined { + return this._lnurlPayServicePayload ? this._lnurlPayServicePayload.description : undefined; } - getImage() { - return this._lnurlPayServicePayload.image; + getImage(): string | undefined { + return this._lnurlPayServicePayload ? this._lnurlPayServicePayload.image : undefined; } - getLnurl() { + getLnurl(): string { return this._lnurl; } - getDisposable() { - return this._lnurlPayServiceBolt11Payload.disposable; + getDisposable(): boolean | undefined { + return this._lnurlPayServiceBolt11Payload && 'disposable' in this._lnurlPayServiceBolt11Payload + ? this._lnurlPayServiceBolt11Payload.disposable + : undefined; } - getPreimage() { + getPreimage(): string | false { return this._preimage; } - static decipherAES(ciphertextBase64, preimageHex, ivBase64) { + 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(Buffer.from(ciphertextBase64, 'base64').toString('hex'), key, { + return CryptoJS.AES.decrypt(uint8ArrayToHex(base64ToUint8Array(ciphertextBase64)), 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; + getCommentAllowed(): number | false { + if (!this._lnurlPayServicePayload) return false; + return this._lnurlPayServicePayload.commentAllowed ? parseInt(this._lnurlPayServicePayload.commentAllowed.toString(), 10) : false; } - getMin() { - return this?._lnurlPayServicePayload?.min ? parseInt(this._lnurlPayServicePayload.min, 10) : false; + getMin(): number | false { + if (!this._lnurlPayServicePayload) return false; + return this._lnurlPayServicePayload.min ? parseInt(this._lnurlPayServicePayload.min.toString(), 10) : false; } - getMax() { - return this?._lnurlPayServicePayload?.max ? parseInt(this._lnurlPayServicePayload.max, 10) : false; + getMax(): number | false { + if (!this._lnurlPayServicePayload) return false; + return this._lnurlPayServicePayload.max ? parseInt(this._lnurlPayServicePayload.max.toString(), 10) : false; } - getAmount() { + getAmount(): number | false { 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(); - }); + 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) { + 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(); } } - -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/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.ts similarity index 93% rename from class/multisig-cosigner.js rename to class/multisig-cosigner.ts index bf15b24f070..64eb6dc3216 100644 --- a/class/multisig-cosigner.js +++ b/class/multisig-cosigner.ts @@ -1,16 +1,21 @@ -import b58 from 'bs58check'; -import { MultisigHDWallet } from './wallets/multisig-hd-wallet'; 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 { - constructor(data) { + 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._fp = false; - this._xpub = false; - this._path = false; - this._valid = false; this._cosigners = []; // is it plain simple Zpub/Ypub/xpub? @@ -69,6 +74,7 @@ export class MultisigCosigner { // 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) @@ -125,7 +131,7 @@ export class MultisigCosigner { } } - static isXpubValid(key) { + static isXpubValid(key: string) { let xpub; try { @@ -138,7 +144,7 @@ export class MultisigCosigner { return false; } - static exportToJson(xfp, xpub, path) { + static exportToJson(xfp: string, xpub: string, path: string) { return JSON.stringify({ xfp, xpub, 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.ts similarity index 50% rename from class/payjoin-transaction.js rename to class/payjoin-transaction.ts index 33362be9254..358ecaa1ac0 100644 --- a/class/payjoin-transaction.js +++ b/class/payjoin-transaction.ts @@ -1,16 +1,25 @@ import * as bitcoin from 'bitcoinjs-lib'; -import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; -import alert from '../components/Alert'; 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 => new Promise(resolve => setTimeout(resolve, milliseconds)); +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 { - constructor(psbt, broadcast, wallet) { + 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; @@ -23,6 +32,7 @@ export default class PayjoinTransaction { 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); @@ -30,22 +40,43 @@ export default class PayjoinTransaction { 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 {boolean|Psbt} + * @returns {Psbt} */ getPayjoinPsbt() { return this._payjoinPsbt; } - async signPsbt(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); @@ -57,32 +88,26 @@ export default class PayjoinTransaction { return this._payjoinPsbt; } - async broadcastTx(txHex) { + async broadcastTx(txHex: string) { try { const result = await this._broadcast(txHex); if (!result) { throw new Error(`Broadcast failed`); } return ''; - } catch (e) { + } catch (e: any) { return 'Error: ' + e.message; } } - async scheduleBroadcastTx(txHex, milliseconds) { + 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 - ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); - alert('Something was wrong with the payjoin transaction, the original transaction sucessfully broadcast.'); + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); + presentAlert({ message: 'Something was wrong with the payjoin transaction, the original transaction successfully broadcast.' }); } }); } - - async isOwnOutputScript(outputScript) { - const address = bitcoin.address.fromOutputScript(outputScript); - - return this._wallet.weOwnAddress(address); - } } 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 index dee8059812f..e9144e75714 100644 --- a/class/synced-async-storage.ts +++ b/class/synced-async-storage.ts @@ -1,9 +1,8 @@ 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'); +import AES from 'crypto-js/aes'; +import ENCHEX from 'crypto-js/enc-hex'; +import ENCUTF8 from 'crypto-js/enc-utf8'; +import SHA256 from 'crypto-js/sha256'; export default class SyncedAsyncStorage { defaultBaseUrl = 'https://bytes-store.herokuapp.com'; @@ -86,7 +85,7 @@ export default class SyncedAsyncStorage { console.log('saved, seq num:', text); resolve(text); }) - .catch(reason => reject(reason)); + .catch((reason: Error) => reject(reason)); }); } 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..e4663801c78 --- /dev/null +++ b/class/wallet-gradient.ts @@ -0,0 +1,89 @@ +import { useTheme } from '../components/themes'; +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 = () => { + const { colors } = useTheme(); + return colors.lightButton; + }; + + 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.ts similarity index 64% rename from class/wallet-import.js rename to class/wallet-import.ts index 5367980df41..05a6f9a44ee 100644 --- a/class/wallet-import.js +++ b/class/wallet-import.ts @@ -1,6 +1,7 @@ -import wif from 'wif'; import bip38 from 'bip38'; +import wif from 'wif'; +import loc from '../loc'; import { HDAezeedWallet, HDLegacyBreadwalletWallet, @@ -9,54 +10,95 @@ import { HDSegwitBech32Wallet, HDSegwitElectrumSeedP2WPKHWallet, HDSegwitP2SHWallet, + HDTaprootWallet, LegacyWallet, LightningCustodianWallet, - LightningLdkWallet, + LightningArkWallet, MultisigHDWallet, + SegwitBech32Wallet, + SegwitP2SHWallet, SLIP39LegacyP2PKHWallet, SLIP39SegwitBech32Wallet, SLIP39SegwitP2SHWallet, - SegwitBech32Wallet, - SegwitP2SHWallet, + TaprootWallet, 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 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 => path.match(/^(m\/)?(\d+'?\/)*\d+'?$/) !== null; +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 {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 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, askPassphrase = false, searchAccounts = false, onProgress, onWallet, onPassword) => { +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; - let promiseReject; + let promiseResolve: (arg: TStatus) => void; + let promiseReject: (reason?: any) => void; let running = true; // if you put it to false, internal generator stops - const wallets = []; - const promise = new Promise((resolve, reject) => { + 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 => { + const reportProgress = (name: string) => { onProgress(name); }; - const reportFinish = (cancelled, stopped) => { + const reportFinish = (cancelled: boolean = false, stopped: boolean = false) => { promiseResolve({ cancelled, stopped, wallets }); }; - const reportWallet = wallet => { + const reportWallet = (wallet: TWallet) => { if (wallets.some(w => w.getID() === wallet.getID())) return; // do not add duplicates wallets.push(wallet); onWallet(wallet); @@ -76,7 +118,9 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal // 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 @@ -134,7 +178,7 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal } // is it bip38 encrypted - if (text.startsWith('6P')) { + if (text.startsWith('6P') && password) { const decryptedKey = await bip38.decryptAsync(text, password); if (decryptedKey) { @@ -147,7 +191,7 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal const ms = new MultisigHDWallet(); ms.setSecret(text); if (ms.getN() > 0 && ms.getM() > 0) { - await ms.fetchBalance(); + await fetch(ms, true, false); yield { wallet: ms }; } @@ -161,34 +205,40 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal lnd.setSecret(split[0]); } await lnd.init(); - await lnd.authorize(); - await lnd.fetchTransactions(); - await lnd.fetchUserInvoices(); - await lnd.fetchPendingTransactions(); - await lnd.fetchBalance(); + if (!offline) { + 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 }; + // 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); - hd2.setPassphrase(password); + 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 ? bip39WalletFormats : bip39WalletFormatsBlueWallet; + 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; @@ -207,6 +257,9 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal case 'p2wpkh-p2sh': WalletClass = HDSegwitP2SHWallet; break; + case 'p2tr': + WalletClass = HDTaprootWallet; + break; default: // p2wpkh WalletClass = HDSegwitBech32Wallet; @@ -214,10 +267,12 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal for (const path of paths) { const wallet = new WalletClass(); wallet.setSecret(text); - wallet.setPassphrase(password); + if (password) { + wallet.setPassphrase(password); + } wallet.setDerivationPath(path); yield { progress: `bip39 ${i.script_type} ${path}` }; - if (await wallet.wasEverUsed()) { + if (await wasUsed(wallet)) { yield { wallet }; walletFound = true; } else { @@ -230,15 +285,18 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal // to decide which one is it let's compare number of transactions const m0Legacy = new HDLegacyP2PKHWallet(); m0Legacy.setSecret(text); - m0Legacy.setPassphrase(password); + 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 - if (!password && text.split(' ').length === 12) { + // 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 m0Legacy.wasEverUsed()) { + if (await wasUsed(m0Legacy)) { await m0Legacy.fetchBalance(); await m0Legacy.fetchTransactions(); yield { progress: 'BRD' }; @@ -252,7 +310,7 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal walletFound = true; } } else { - if (await m0Legacy.wasEverUsed()) { + if (await wasUsed(m0Legacy)) { yield { wallet: m0Legacy }; walletFound = true; } @@ -262,7 +320,6 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal if (!walletFound) { yield { wallet: hd2 }; } - // return; } yield { progress: 'wif' }; @@ -275,17 +332,27 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal yield { progress: 'wif p2wpkh' }; const segwitBech32Wallet = new SegwitBech32Wallet(); segwitBech32Wallet.setSecret(text); - if (await segwitBech32Wallet.wasEverUsed()) { + if (await wasUsed(segwitBech32Wallet)) { // yep, its single-address bech32 wallet - await segwitBech32Wallet.fetchBalance(); + 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 segwitWallet.wasEverUsed()) { + if (await wasUsed(segwitWallet)) { // yep, its single-address p2wpkh wallet - await segwitWallet.fetchBalance(); + await fetch(segwitWallet, true); walletFound = true; yield { wallet: segwitWallet }; } @@ -294,9 +361,9 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal yield { progress: 'wif p2pkh' }; const legacyWallet = new LegacyWallet(); legacyWallet.setSecret(text); - if (await legacyWallet.wasEverUsed()) { + if (await wasUsed(legacyWallet)) { // yep, its single-address legacy wallet - await legacyWallet.fetchBalance(); + await fetch(legacyWallet, true); walletFound = true; yield { wallet: legacyWallet }; } @@ -306,6 +373,7 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal yield { wallet: segwitBech32Wallet }; yield { wallet: segwitWallet }; yield { wallet: legacyWallet }; + yield { wallet: taprootWallet }; } } @@ -314,25 +382,46 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal const legacyWallet = new LegacyWallet(); legacyWallet.setSecret(text); if (legacyWallet.getAddress()) { - await legacyWallet.fetchBalance(); - await legacyWallet.fetchTransactions(); + await fetch(legacyWallet, true, true); 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 }; + 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); - el1.setPassphrase(password); + if (password) { + el1.setPassphrase(password); + } if (el1.validateMnemonic()) { yield { wallet: el1 }; // not fetching txs or balances, fuck it, yolo, life is too short } @@ -341,7 +430,9 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal yield { progress: 'electrum p2pkh' }; const el2 = new HDLegacyElectrumSeedP2PKHWallet(); el2.setSecret(text); - el2.setPassphrase(password); + if (password) { + el2.setPassphrase(password); + } if (el2.validateMnemonic()) { yield { wallet: el2 }; // not fetching txs or balances, fuck it, yolo, life is too short } @@ -350,39 +441,44 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal yield { progress: 'aezeed' }; const aezeed2 = new HDAezeedWallet(); aezeed2.setSecret(text); - aezeed2.setPassphrase(password); + if (password) { + 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 + // Let's try SLIP39 yield { progress: 'SLIP39' }; - if (text.includes('\n')) { - const s1 = new SLIP39SegwitP2SHWallet(); - s1.setSecret(text); + const s1 = new SLIP39SegwitP2SHWallet(); + s1.setSecret(text); - if (s1.validateMnemonic()) { - yield { progress: 'SLIP39 p2wpkh-p2sh' }; + if (s1.validateMnemonic()) { + yield { progress: 'SLIP39 p2wpkh-p2sh' }; + if (password) { s1.setPassphrase(password); - if (await s1.wasEverUsed()) { - yield { wallet: s1 }; - } + } + if (await wasUsed(s1)) { + yield { wallet: s1 }; + } - yield { progress: 'SLIP39 p2pkh' }; - const s2 = new SLIP39LegacyP2PKHWallet(); + yield { progress: 'SLIP39 p2pkh' }; + const s2 = new SLIP39LegacyP2PKHWallet(); + if (password) { s2.setPassphrase(password); - s2.setSecret(text); - if (await s2.wasEverUsed()) { - yield { wallet: s2 }; - } + } + s2.setSecret(text); + if (await wasUsed(s2)) { + yield { wallet: s2 }; + } - yield { progress: 'SLIP39 p2wpkh' }; - const s3 = new SLIP39SegwitBech32Wallet(); - s3.setSecret(text); + yield { progress: 'SLIP39 p2wpkh' }; + const s3 = new SLIP39SegwitBech32Wallet(); + s3.setSecret(text); + if (password) { s3.setPassphrase(password); - yield { wallet: s3 }; } + yield { wallet: s3 }; } // is it BC-UR payload with multiple accounts? @@ -400,6 +496,23 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal } } } 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 @@ -411,6 +524,7 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal 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 => { diff --git a/class/wallets/abstract-hd-electrum-wallet.ts b/class/wallets/abstract-hd-electrum-wallet.ts index 463401dcc04..08f4b0d4e80 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,7 +152,7 @@ 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; @@ -116,13 +160,14 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { 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 +293,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 +315,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 +330,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 +342,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 +351,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 +366,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 +400,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 +445,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 +466,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 +496,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 +517,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 +545,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 +561,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 +581,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 +592,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 +611,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 +627,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 +721,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 +750,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 +766,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 +792,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 +820,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 +852,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 +902,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 +921,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 +937,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 +957,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 +977,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 +992,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 +1026,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 +1042,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 +1056,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 +1085,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 +1098,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 +1129,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 +1141,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 +1153,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 +1163,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 +1174,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 +1237,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 +1249,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 +1264,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 +1294,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 +1331,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 +1341,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 +1358,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { ], witnessUtxo: { script: p2wpkh.output, - value: input.value, + value: BigInt(input.value), }, }); @@ -1292,15 +1393,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 +1423,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 +1457,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 +1484,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 +1561,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 +1575,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 +1613,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 +1765,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 +1788,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 +1797,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 +1865,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; + } + + /** + * 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; } - _getNextFreePaymentCodeAddress(paymentCode: string) { - return this._next_free_payment_code_address_index[paymentCode] || 0; + _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..4d769b9c85a 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[] { @@ -163,11 +156,11 @@ export class AbstractWallet { return true; } - allowRBF(): boolean { + allowSilentPaymentSend(): boolean { return false; } - allowHodlHodlTrading(): boolean { + allowRBF(): boolean { return false; } @@ -219,10 +212,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 +278,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 +286,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 +308,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 +321,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 +331,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 +351,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 +370,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 +381,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 +448,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 +462,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 +515,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.ts similarity index 73% rename from class/wallets/hd-aezeed-wallet.js rename to class/wallets/hd-aezeed-wallet.ts index 593e86c0ab9..aea2e92aee4 100644 --- a/class/wallets/hd-aezeed-wallet.js +++ b/class/wallets/hd-aezeed-wallet.ts @@ -1,10 +1,12 @@ -import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; -import b58 from 'bs58check'; +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 bitcoin = require('bitcoinjs-lib'); -const { CipherSeed } = require('aezeed'); const bip32 = BIP32Factory(ecc); /** @@ -18,21 +20,27 @@ const bip32 = BIP32Factory(ecc); * @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) { + 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() { + _getEntropyCached(): Uint8Array { if (this._entropyHex) { // cache hit - return Buffer.from(this._entropyHex, 'hex'); + return hexToUint8Array(this._entropyHex); } else { throw new Error('Entropy cache is not filled'); } @@ -49,13 +57,13 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet { // 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; } - validateMnemonic() { + validateMnemonic(): boolean { throw new Error('Use validateMnemonicAsync()'); } @@ -75,7 +83,7 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet { try { const cipherSeed1 = await CipherSeed.fromMnemonic(this.secret, passphrase); this._entropyHex = cipherSeed1.entropy.toString('hex'); // save cache - } catch (error) { + } catch (error: any) { return error.message === 'Invalid Password'; } return false; @@ -97,7 +105,7 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet { return node.derive(1); } - _getInternalAddressByIndex(index) { + _getInternalAddressByIndex(index: number): string { index = index * 1; // cast to int if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit @@ -106,11 +114,14 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet { 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) { + _getExternalAddressByIndex(index: number): string { index = index * 1; // cast to int if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit @@ -119,11 +130,14 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet { 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, index) { + _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}`; @@ -132,7 +146,7 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet { return child.toWIF(); } - _getNodePubkeyByIndex(node, index) { + _getNodePubkeyByIndex(node: number, index: number) { index = index * 1; // cast to int if (node === 0 && !this._node0) { @@ -143,20 +157,22 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet { this._node1 = this._getNode1(); } - if (node === 0) { + if (node === 0 && this._node0) { return this._node0.derive(index).publicKey; } - if (node === 1) { + 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 node.publicKey.toString('hex'); + return uint8ArrayToHex(node.publicKey); } // since its basically a bip84 wallet, we allow all other standard BIP84 features: @@ -165,10 +181,6 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet { return true; } - allowHodlHodlTrading() { - return true; - } - allowRBF() { return true; } diff --git a/class/wallets/hd-legacy-breadwallet-wallet.js b/class/wallets/hd-legacy-breadwallet-wallet.ts similarity index 71% rename from class/wallets/hd-legacy-breadwallet-wallet.js rename to class/wallets/hd-legacy-breadwallet-wallet.ts index 5a6d5e92914..adc1e027b79 100644 --- a/class/wallets/hd-legacy-breadwallet-wallet.js +++ b/class/wallets/hd-legacy-breadwallet-wallet.ts @@ -1,10 +1,14 @@ +import BIP32Factory, { BIP32Interface } from 'bip32'; 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 { 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 BlueElectrum = require('../../blue_modules/BlueElectrum'); const bip32 = BIP32Factory(ecc); /** @@ -12,32 +16,46 @@ const bip32 = BIP32Factory(ecc); * In particular, Breadwallet-compatible (Legacy addresses) */ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { - static type = 'HDLegacyBreadwallet'; - static typeReadable = 'HD Legacy Breadwallet (P2PKH)'; - static derivationPath = "m/0'"; + 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 = null; - _internal_segwit_index = null; + _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, index, p2wpkh = false) { - let _node; + _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, index) { + _getNodeAddressByIndex(node: number, index: number): string { index = index * 1; // cast to int if (node === 0) { if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit @@ -64,6 +82,8 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { if (node === 1) { return (this.internal_addresses_cache[index] = address); } + + throw new Error('Internal error: unknown node'); } async fetchBalance() { @@ -96,8 +116,8 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { } } - async _binarySearchIteration(startIndex, endIndex, node = 0, p2wpkh = false) { - const gerenateChunkAddresses = chunkNum => { + 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)); @@ -105,11 +125,11 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { return ret; }; - let lastChunkWithUsedAddressesNum = null; - let lastHistoriesWithUsedAddresses = null; + 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 (this.constructor._getTransactionsFromHistories(histories).length > 0) { + if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) { // in this particular chunk we have used addresses lastChunkWithUsedAddressesNum = c; lastHistoriesWithUsedAddresses = histories; @@ -121,11 +141,11 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { let lastUsedIndex = startIndex; - if (lastHistoriesWithUsedAddresses) { + 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; + let c = lastChunkWithUsedAddressesNum! * this.gap_limit; + c < lastChunkWithUsedAddressesNum! * this.gap_limit + this.gap_limit; c++ ) { const address = this._calcNodeAddressByIndex(node, c, p2wpkh); @@ -138,11 +158,11 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { return lastUsedIndex; } - _addPsbtInput(psbt, input, sequence, masterFingerprintBuffer) { + _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 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.ts similarity index 68% rename from class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.js rename to class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.ts index ea46112434c..6ce6aac804d 100644 --- a/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.js +++ b/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.ts @@ -1,13 +1,18 @@ -import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; 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 bitcoin = require('bitcoinjs-lib'); -const mn = require('electrum-mnemonic'); 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. @@ -15,9 +20,13 @@ const PREFIX = mn.PREFIXES.standard; * @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'; + 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); @@ -35,14 +44,14 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet { if (this._xpub) { return this._xpub; // cache hit } - const args = { prefix: PREFIX }; + 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) { + _getInternalAddressByIndex(index: number) { index = index * 1; // cast to int if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit @@ -50,11 +59,14 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet { 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) { + _getExternalAddressByIndex(index: number) { index = index * 1; // cast to int if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit @@ -62,13 +74,16 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet { 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, index) { + _getWIFByIndex(internal: boolean, index: number): string | false { if (!this.secret) return false; - const args = { prefix: PREFIX }; + 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}`; @@ -77,7 +92,7 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet { return child.toWIF(); } - _getNodePubkeyByIndex(node, index) { + _getNodePubkeyByIndex(node: number, index: number) { index = index * 1; // cast to int if (node === 0 && !this._node0) { @@ -92,12 +107,14 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet { this._node1 = hdNode.derive(node); } - if (node === 0) { + if (node === 0 && this._node0) { return this._node0.derive(index).publicKey; } - if (node === 1) { + 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.ts similarity index 59% rename from class/wallets/hd-legacy-p2pkh-wallet.js rename to class/wallets/hd-legacy-p2pkh-wallet.ts index 088db99b5b1..8b81f9e8689 100644 --- a/class/wallets/hd-legacy-p2pkh-wallet.js +++ b/class/wallets/hd-legacy-p2pkh-wallet.ts @@ -1,8 +1,13 @@ -import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; -import BIP32Factory from 'bip32'; +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); -const BlueElectrum = require('../../blue_modules/BlueElectrum'); /** * HD Wallet (BIP39). @@ -10,9 +15,13 @@ const BlueElectrum = require('../../blue_modules/BlueElectrum'); * @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'"; + 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; @@ -46,37 +55,41 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet { 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) { + _hdNodeToAddress(hdNode: BIP32Interface): string { return this._nodeToLegacyAddress(hdNode); } - async fetchUtxo() { + 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), - 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) { + _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, 44); + 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'); @@ -92,9 +105,13 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet { }, ], // non-segwit inputs now require passing the whole previous tx as Buffer - nonWitnessUtxo: Buffer.from(input.txhex, 'hex'), + 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.ts similarity index 62% rename from class/wallets/hd-segwit-bech32-wallet.js rename to class/wallets/hd-segwit-bech32-wallet.ts index e86d158ed74..bb51a22c203 100644 --- a/class/wallets/hd-segwit-bech32-wallet.js +++ b/class/wallets/hd-segwit-bech32-wallet.ts @@ -6,19 +6,19 @@ import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; * @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'"; + 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; } - allowHodlHodlTrading() { - return true; - } - allowRBF() { return true; } @@ -50,4 +50,8 @@ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet { 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.ts similarity index 67% rename from class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.js rename to class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.ts index 469830a10ea..0ec4071844d 100644 --- a/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.js +++ b/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.ts @@ -1,14 +1,20 @@ -import b58 from 'bs58check'; -import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; 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 bitcoin = require('bitcoinjs-lib'); -const mn = require('electrum-mnemonic'); 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. @@ -16,9 +22,13 @@ const PREFIX = mn.PREFIXES.segwit; * @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'"; + 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); @@ -36,7 +46,7 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { if (this._xpub) { return this._xpub; // cache hit } - const args = { prefix: PREFIX }; + 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(); @@ -44,13 +54,13 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { // 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; } - _getInternalAddressByIndex(index) { + _getInternalAddressByIndex(index: number) { index = index * 1; // cast to int if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit @@ -59,11 +69,14 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { 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) { + _getExternalAddressByIndex(index: number) { index = index * 1; // cast to int if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit @@ -72,13 +85,16 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { 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, index) { + _getWIFByIndex(internal: boolean, index: number): string | false { if (!this.secret) return false; - const args = { prefix: PREFIX }; + 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}`; @@ -87,7 +103,7 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { return child.toWIF(); } - _getNodePubkeyByIndex(node, index) { + _getNodePubkeyByIndex(node: number, index: number) { index = index * 1; // cast to int if (node === 0 && !this._node0) { @@ -102,13 +118,15 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { this._node1 = hdNode.derive(node); } - if (node === 0) { + if (node === 0 && this._node0) { return this._node0.derive(index).publicKey; } - if (node === 1) { + if (node === 1 && this._node1) { return this._node1.derive(index).publicKey; } + + throw new Error('Internal error: this._node0 or this._node1 is undefined'); } isSegwit() { diff --git a/class/wallets/hd-segwit-p2sh-wallet.js b/class/wallets/hd-segwit-p2sh-wallet.ts similarity index 57% rename from class/wallets/hd-segwit-p2sh-wallet.js rename to class/wallets/hd-segwit-p2sh-wallet.ts index 5ed8171502a..0e3d88f6ed0 100644 --- a/class/wallets/hd-segwit-p2sh-wallet.js +++ b/class/wallets/hd-segwit-p2sh-wallet.ts @@ -1,9 +1,14 @@ +import BIP32Factory, { BIP32Interface } from 'bip32'; +import * as bitcoin from 'bitcoinjs-lib'; +import { Psbt } from 'bitcoinjs-lib'; import b58 from 'bs58check'; -import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; -import BIP32Factory from 'bip32'; +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); -const bitcoin = require('bitcoinjs-lib'); /** * HD Wallet (BIP39). @@ -11,10 +16,14 @@ const bitcoin = require('bitcoinjs-lib'); * @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'"; + 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; @@ -28,10 +37,6 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet { return true; } - allowHodlHodlTrading() { - return true; - } - allowMasterFingerprint() { return true; } @@ -40,7 +45,7 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet { return true; } - _hdNodeToAddress(hdNode) { + _hdNodeToAddress(hdNode: BIP32Interface): string { return this._nodeToP2shSegwitAddress(hdNode); } @@ -59,23 +64,35 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet { 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); - data = Buffer.concat([Buffer.from('049d7cb2', 'hex'), data]); - this._xpub = b58.encode(data); + const concatenated = concatUint8Arrays([hexToUint8Array('049d7cb2'), data]); + this._xpub = b58.encode(concatenated); return this._xpub; } - _addPsbtInput(psbt, input, sequence, masterFingerprintBuffer) { + _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, @@ -90,7 +107,7 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet { ], witnessUtxo: { script: p2sh.output, - value: input.amount || input.value, + value: BigInt(input.value), }, redeemScript: p2wpkh.output, }); @@ -101,4 +118,8 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet { 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..2684dd75c64 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 { HDSegwitBech32Wallet } from '..'; +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,8 +20,12 @@ bitcoin.initEccLib(ecc); * (legacy P2PKH compressed) */ export class LegacyWallet extends AbstractWallet { - static type = 'legacy'; - static typeReadable = 'Legacy (P2PKH)'; + static readonly type = 'legacy'; + static readonly typeReadable = 'Legacy (P2PKH)'; + // @ts-ignore: override + public readonly type = LegacyWallet.type; + // @ts-ignore: override + public readonly typeReadable = LegacyWallet.typeReadable; _txs_by_external_index: Transaction[] = []; _txs_by_internal_index: Transaction[] = []; @@ -58,25 +63,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 +124,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 +153,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 +163,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 +200,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 +215,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 +268,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 +276,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 +316,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 +330,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); @@ -374,24 +365,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 +427,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 +446,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 +469,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 +482,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 +508,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 +532,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 +549,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 +618,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..f8d3adafc72 --- /dev/null +++ b/class/wallets/lightning-ark-wallet.ts @@ -0,0 +1,491 @@ +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 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(); + 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...'); + await new Ramps(this._wallet).onboard(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; + }); + } +} diff --git a/class/wallets/lightning-custodian-wallet.js b/class/wallets/lightning-custodian-wallet.ts similarity index 62% rename from class/wallets/lightning-custodian-wallet.js rename to class/wallets/lightning-custodian-wallet.ts index 9def152da52..00731ac17c2 100644 --- a/class/wallets/lightning-custodian-wallet.js +++ b/class/wallets/lightning-custodian-wallet.ts @@ -1,36 +1,40 @@ -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'); +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 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; - } + 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) { - this.baseURI = URI; + setBaseURI(URI: string | undefined) { + this.baseURI = URI?.endsWith('/') ? URI.slice(0, -1) : URI; } getBaseURI() { @@ -41,11 +45,11 @@ export class LightningCustodianWallet extends LegacyWallet { return true; } - getAddress() { + getAddress(): string | false { if (this.refill_addressess.length > 0) { return this.refill_addressess[0]; } else { - return undefined; + return false; } } @@ -61,27 +65,10 @@ export class LightningCustodianWallet extends LegacyWallet { 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() { @@ -92,34 +79,37 @@ export class LightningCustodianWallet extends LegacyWallet { return (+new Date() - this._refresh_token_created_ts) / 1000 >= 3600 * 24 * 7; // 7d } - generate() { + generate(): Promise { // nop + return Promise.resolve(); } - async createAccount(isTest) { - const response = await this._api.post('/create', { - body: { partnerid: 'bluewallet', accounttype: (isTest && 'test') || 'common' }, + 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 = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); } - if (json && json.error) { + 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(response.body)); + throw new Error('API unexpected response: ' + JSON.stringify(json)); } this.secret = 'lndhub://' + json.login + ':' + json.password; } - async payInvoice(invoice, freeAmount = 0) { - const response = await this._api.post('/payinvoice', { - body: { invoice, amount: freeAmount }, + 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', @@ -127,22 +117,12 @@ export class LightningCustodianWallet extends LegacyWallet { }, }); - 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 = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); } - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.originalResponse)); - } - - if (json && json.error) { + if (json.error) { throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); } @@ -154,22 +134,23 @@ export class LightningCustodianWallet extends LegacyWallet { * * @return {Promise.} */ - async getUserInvoices(limit = false) { + async getUserInvoices(limit: number | false = false) { let limitString = ''; - if (limit) limitString = '?limit=' + parseInt(limit, 10); - const response = await this._api.get('/getuserinvoices' + 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 = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.originalResponse)); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); } - if (json && json.error) { + if (json.error) { throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); } @@ -192,7 +173,7 @@ export class LightningCustodianWallet extends LegacyWallet { } } - this.user_invoices_raw = json.sort(function (a, b) { + this.user_invoices_raw = json.sort(function (a: { timestamp: number }, b: { timestamp: number }) { return a.timestamp - b.timestamp; }); @@ -209,34 +190,35 @@ export class LightningCustodianWallet extends LegacyWallet { await this.getUserInvoices(); } - isInvoiceGeneratedByWallet(paymentRequest) { + isInvoiceGeneratedByWallet(paymentRequest: string) { return this.user_invoices_raw.some(invoice => invoice.payment_request === paymentRequest); } - weOwnAddress(address) { + weOwnAddress(address: string) { return this.refill_addressess.some(refillAddress => address === refillAddress); } - async addInvoice(amt, memo) { - const response = await this._api.post('/addinvoice', { - body: { amt: amt + '', memo }, + 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 = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.originalResponse)); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); } - if (json && json.error) { + 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(response.body)); + throw new Error('API unexpected response: ' + JSON.stringify(json)); } return json.pay_req; @@ -257,22 +239,23 @@ export class LightningCustodianWallet extends LegacyWallet { 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 }, + 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 = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); } - if (json && json.error) { + 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(response.body)); + throw new Error('API unexpected response: ' + JSON.stringify(json)); } this.refresh_token = json.refresh_token; @@ -304,22 +287,23 @@ export class LightningCustodianWallet extends LegacyWallet { } async refreshAcessToken() { - const response = await this._api.post('/auth?type=refresh_token', { - body: { refresh_token: this.refresh_token }, + 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 = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); } - if (json && json.error) { + 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(response.body)); + throw new Error('API unexpected response: ' + JSON.stringify(json)); } this.refresh_token = json.refresh_token; @@ -329,7 +313,8 @@ export class LightningCustodianWallet extends LegacyWallet { } async fetchBtcAddress() { - const response = await this._api.get('/getbtc', { + const response = await fetch(this.baseURI + '/getbtc', { + method: 'GET', headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json', @@ -337,12 +322,12 @@ export class LightningCustodianWallet extends LegacyWallet { }, }); - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); } - if (json && json.error) { + if (json.error) { throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); } @@ -367,26 +352,22 @@ export class LightningCustodianWallet extends LegacyWallet { } } - 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 || []; + 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 - // 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); + tx.value = (tx.amt * 1 + tx.fee * 1) * -1; } if (tx.type === 'paid_invoice') { @@ -402,18 +383,20 @@ export class LightningCustodianWallet extends LegacyWallet { if (tx.type === 'user_invoice') { // incoming ln tx tx.value = parseInt(tx.amt, 10); + tx.fee = 0; tx.memo = tx.description || 'Lightning invoice'; } - tx.received = new Date(tx.timestamp * 1000).toString(); + tx.timestamp = tx.timestamp || tx.time; } - return txs.sort(function (a, b) { + return txs.sort(function (a: { timestamp: number }, b: { timestamp: number }) { return b.timestamp - a.timestamp; }); } async fetchPendingTransactions() { - const response = await this._api.get('/getpending', { + const response = await fetch(this.baseURI + '/getpending', { + method: 'GET', headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json', @@ -421,12 +404,12 @@ export class LightningCustodianWallet extends LegacyWallet { }, }); - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response)); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); } - if (json && json.error) { + if (json.error) { throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); } @@ -441,7 +424,8 @@ export class LightningCustodianWallet extends LegacyWallet { queryRes += '?limit=' + limit; queryRes += '&offset=' + offset; - const response = await this._api.get('/gettxs' + queryRes, { + const response = await fetch(this.baseURI + '/gettxs' + queryRes, { + method: 'GET', headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json', @@ -449,17 +433,17 @@ export class LightningCustodianWallet extends LegacyWallet { }, }); - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); } - if (json && json.error) { + 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(response.body)); + throw new Error('API unexpected response: ' + JSON.stringify(json)); } this._lastTxFetch = +new Date(); @@ -470,10 +454,11 @@ export class LightningCustodianWallet extends LegacyWallet { return this.balance; } - async fetchBalance(noRetry) { + async fetchBalance(noRetry?: boolean): Promise { await this.checkLogin(); - const response = await this._api.get('/balance', { + const response = await fetch(this.baseURI + '/balance', { + method: 'GET', headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json', @@ -481,12 +466,12 @@ export class LightningCustodianWallet extends LegacyWallet { }, }); - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); } - if (json && json.error) { + if (json.error) { if (json.code * 1 === 1 && !noRetry) { await this.authorize(); return this.fetchBalance(true); @@ -495,10 +480,9 @@ export class LightningCustodianWallet extends LegacyWallet { } if (!json.BTC || typeof json.BTC.AvailableBalance === 'undefined') { - throw new Error('API unexpected response: ' + JSON.stringify(response.body)); + throw new Error('API unexpected response: ' + JSON.stringify(json)); } - this.balance_raw = json; this.balance = json.BTC.AvailableBalance; this._lastBalanceFetch = +new Date(); } @@ -517,99 +501,79 @@ export class LightningCustodianWallet extends LegacyWallet { * route_hints: [] } * * @param invoice BOLT invoice string - * @return {payment_hash: string} + * @return {DecodedInvoice} */ - decodeInvoice(invoice) { + decodeInvoice(invoice: string): DecodedInvoice { + if (_staticDecodedInvoiceCache[invoice]) return _staticDecodedInvoiceCache[invoice]; // cache hit + 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(), + 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 = data; + decoded.payment_hash = String(data); break; case 'purpose_commit_hash': - decoded.description_hash = data; + decoded.description_hash = String(data); break; case 'min_final_cltv_expiry': decoded.cltv_expiry = data.toString(); break; case 'expire_time': - decoded.expiry = data.toString(); + decoded.expiry = +data; break; case 'description': - decoded.description = data; + decoded.description = String(data); break; } } - if (!decoded.expiry) decoded.expiry = '3600'; // default + 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(); + if (decoded.num_satoshis === 0 && decoded.num_millisatoshis > 0) { + decoded.num_satoshis = Math.floor(decoded.num_millisatoshis / 1000); } - return (this.decoded_invoice_raw = decoded); + _staticDecodedInvoiceCache[invoice] = decoded; + + return decoded; } - async fetchInfo() { - const response = await this._api.get('/getinfo', { + 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', - Authorization: 'Bearer' + ' ' + this.access_token, }, }); - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); } - if (json && json.error) { + if (json.code && json.code !== 1) { 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; } @@ -637,10 +601,11 @@ export class LightningCustodianWallet extends LegacyWallet { * @param invoice BOLT invoice string * @return {Promise.} */ - async decodeInvoiceRemote(invoice) { + async decodeInvoiceRemote(invoice: string) { await this.checkLogin(); - const response = await this._api.get('/decodeinvoice?invoice=' + invoice, { + const response = await fetch(this.baseURI + '/decodeinvoice?invoice=' + invoice, { + method: 'GET', headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json', @@ -648,23 +613,23 @@ export class LightningCustodianWallet extends LegacyWallet { }, }); - const json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); + const json = await response.json(); + if (!json) { + throw new Error('API failure: ' + response.statusText); } - if (json && json.error) { + 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(response.body)); + throw new Error('API unexpected response: ' + JSON.stringify(json)); } - return (this.decoded_invoice_raw = json); + return json; } - weOwnTransaction(txid) { + weOwnTransaction(txid: string) { for (const tx of this.getTransactions()) { if (tx && tx.payment_hash && tx.payment_hash === txid) return true; } @@ -672,9 +637,23 @@ export class LightningCustodianWallet extends LegacyWallet { return false; } - authenticate(lnurl) { + 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; + } } /* 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.ts similarity index 74% rename from class/wallets/multisig-hd-wallet.js rename to class/wallets/multisig-hd-wallet.ts index 88506d73d3d..1ffe2cd0c8d 100644 --- a/class/wallets/multisig-hd-wallet.js +++ b/class/wallets/multisig-hd-wallet.ts @@ -1,24 +1,61 @@ -import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; +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 { decodeUR } from '../../blue_modules/ur'; +import { CoinSelectOutput, CoinSelectReturnInput, CoinSelectTarget } from 'coinselect'; +import { sha256 } from '@noble/hashes/sha256'; import { ECPairFactory } from 'ecpair'; -import BIP32Factory from 'bip32'; +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 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 => ({ +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 => ({ +const electrumStandart = (passphrase?: string): SeedOpts => ({ prefix: mn.PREFIXES.standard, ...(passphrase ? { passphrase } : {}), }); @@ -26,8 +63,12 @@ const electrumStandart = passphrase => ({ const ELECTRUM_SEED_PREFIX = 'electrumseed:'; export class MultisigHDWallet extends AbstractHDElectrumWallet { - static type = 'HDmultisig'; - static typeReadable = 'Multisig Vault'; + 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'; @@ -38,19 +79,17 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { 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; - } + 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; @@ -76,25 +115,25 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { this._isLegacy = true; } - setM(m) { + setM(m: number) { this._m = m; } /** * @returns {number} How many minumim signatures required to authorize a spend */ - getM() { + getM(): number { return this._m; } /** * @returns {number} Total count of cosigners */ - getN() { + getN(): number { return this._cosigners.length; } - setDerivationPath(path) { + setDerivationPath(path: string) { this._derivationPath = path; switch (this._derivationPath) { case "m/48'/0'/0'/2'": @@ -112,33 +151,33 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { } } - getCustomDerivationPathForCosigner(index) { + 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(); + return this._cosignersCustomPaths[index - 1] || this.getDerivationPath()!; } - getCosigner(index) { + getCosigner(index: number) { if (index === 0) throw new Error('cosigners indexation starts from 1'); return this._cosigners[index - 1]; } - getFingerprint(index) { + getFingerprint(index: number) { if (index === 0) throw new Error('cosigners fingerprints indexation starts from 1'); return this._cosignersFingerprints[index - 1]; } - getCosignerForFingerprint(fp) { + getCosignerForFingerprint(fp: string) { const index = this._cosignersFingerprints.indexOf(fp); return this._cosigners[index]; } - getPassphrase(index) { + getCosignerPassphrase(index: number) { if (index === 0) throw new Error('cosigners indexation starts from 1'); return this._cosignersPassphrases[index - 1]; } - static isXpubValid(key) { + static isXpubValid(key: string): boolean { let xpub; try { @@ -151,7 +190,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { return false; } - static isXprvValid(xprv) { + static isXprvValid(xprv: string): boolean { try { xprv = MultisigHDWallet.convertMultisigXprvToRegularXprv(xprv); bip32.fromBase58(xprv); @@ -168,12 +207,12 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { * @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) { + 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 && !this.constructor.isPathValid(path)) { + if (path && !MultisigHDWallet.isPathValid(path)) { throw new Error('path is not valid'); } @@ -214,13 +253,13 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { if (passphrase) this._cosignersPassphrases[index] = passphrase; } - static convertMultisigXprvToRegularXprv(Zprv) { + static convertMultisigXprvToRegularXprv(Zprv: string) { let data = b58.decode(Zprv); data = data.slice(4); - return b58.encode(Buffer.concat([Buffer.from('0488ade4', 'hex'), data])); + return b58.encode(concatUint8Arrays([hexToUint8Array('0488ade4'), data])); } - static convertXprvToXpub(xprv) { + static convertXprvToXpub(xprv: string) { const restored = bip32.fromBase58(MultisigHDWallet.convertMultisigXprvToRegularXprv(xprv)); return restored.neutered().toBase58(); } @@ -228,15 +267,18 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { /** * 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 + * @param index {number} * @returns {string} xpub * @private */ - _getXpubFromCosigner(cosigner) { + 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)) { - const index = this._cosigners.indexOf(cosigner); xpub = MultisigHDWallet.seedToXpub( cosigner, this._cosignersCustomPaths[index] || this._derivationPath, @@ -246,7 +288,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { return this._zpubToXpub(xpub); } - _getExternalAddressByIndex(index) { + _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 @@ -256,15 +298,15 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { return address; } - _getAddressFromNode(nodeIndex, index) { + _getAddressFromNode(nodeIndex: number, index: number) { + this._nodes = this._nodes || {}; const pubkeys = []; - for (const [cosignerIndex, cosigner] of this._cosigners.entries()) { - this._nodes = this._nodes || []; - this._nodes[nodeIndex] = this._nodes[nodeIndex] || []; + for (const [cosignerIndex] of this._cosigners.entries()) { + this._nodes[nodeIndex] = this._nodes[nodeIndex] || {}; let _node; if (!this._nodes[nodeIndex][cosignerIndex]) { - const xpub = this._getXpubFromCosigner(cosigner); + const xpub = this._getXpubFromCosignerIndex(cosignerIndex); const hdNode = bip32.fromBase58(xpub); _node = hdNode.derive(nodeIndex); this._nodes[nodeIndex][cosignerIndex] = _node; @@ -281,18 +323,27 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { 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 { @@ -300,7 +351,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { } } - _getInternalAddressByIndex(index) { + _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 @@ -310,7 +361,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { return address; } - static seedToXpub(mnemonic, path, passphrase) { + static seedToXpub(mnemonic: string, path: string, passphrase?: string): string { let seed; if (mnemonic.startsWith(ELECTRUM_SEED_PREFIX)) { seed = MultisigHDWallet.convertElectrumMnemonicToSeed(mnemonic, passphrase); @@ -331,35 +382,35 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { * @param xpub {string} Any kind of xpub, including zpub etc since we are only swapping the prefix bytes * @returns {string} */ - convertXpubToMultisignatureXpub(xpub) { + convertXpubToMultisignatureXpub(xpub: string): string { let data = b58.decode(xpub); data = data.slice(4); if (this.isNativeSegwit()) { - return b58.encode(Buffer.concat([Buffer.from('02aa7ed3', 'hex'), data])); + return b58.encode(concatUint8Arrays([hexToUint8Array('02aa7ed3'), data])); } else if (this.isWrappedSegwit()) { - return b58.encode(Buffer.concat([Buffer.from('0295b43f', 'hex'), data])); + return b58.encode(concatUint8Arrays([hexToUint8Array('0295b43f'), data])); } return xpub; } - convertXprvToMultisignatureXprv(xpub) { + convertXprvToMultisignatureXprv(xpub: string): string { let data = b58.decode(xpub); data = data.slice(4); if (this.isNativeSegwit()) { - return b58.encode(Buffer.concat([Buffer.from('02aa7a99', 'hex'), data])); + return b58.encode(concatUint8Arrays([hexToUint8Array('02aa7a99'), data])); } else if (this.isWrappedSegwit()) { - return b58.encode(Buffer.concat([Buffer.from('0295b005', 'hex'), data])); + return b58.encode(concatUint8Arrays([hexToUint8Array('0295b005'), data])); } return xpub; } - static isXpubString(xpub) { + static isXpubString(xpub: string): boolean { return ['xpub', 'ypub', 'zpub', 'Ypub', 'Zpub'].includes(xpub.substring(0, 4)); } - static isXprvString(xpub) { + static isXprvString(xpub: string): boolean { return ['xprv', 'yprv', 'zprv', 'Yprv', 'Zprv'].includes(xpub.substring(0, 4)); } @@ -369,7 +420,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { * @param xfp {number} For example 64392470 * @returns {string} For example 168DD603 */ - static ckccXfp2fingerprint(xfp) { + 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 @@ -400,15 +451,15 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { ret += 'Policy: ' + this.getM() + ' of ' + this.getN() + '\n'; let hasCustomPaths = 0; - const customPaths = {}; + 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; - - if (this.getDerivationPath()) customPaths[this.getDerivationPath()] = 1; + 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)) { @@ -442,7 +493,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { // 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])) { + if (MultisigHDWallet.isXpubString(this._cosigners[index])) { ret += this._cosignersFingerprints[index] + ': ' + this._cosigners[index] + '\n'; } else { if (coordinationSetup) { @@ -468,11 +519,11 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { return ret; } - setSecret(secret) { + setSecret(secret: string) { if (secret.toUpperCase().startsWith('UR:BYTES')) { - const decoded = decodeUR([secret]); - const b = Buffer.from(decoded, 'hex'); - secret = b.toString(); + const decoded = decodeUR([secret]) as string; + const b = hexToUint8Array(decoded); + secret = uint8ArrayToString(b); } // is it Coldcard json file? @@ -482,7 +533,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { } 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; + return this; } // is it electrum json? @@ -513,7 +564,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { } // coldcard & cobo txt format: - let customPathForCurrentCosigner = false; + let customPathForCurrentCosigner: string | undefined; for (const line of secret.split('\n')) { const [key, value] = line.split(':'); @@ -552,7 +603,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { customPathForCurrentCosigner = value.trim(); } else if (key === 'seed') { const [seed, passphrase] = value.split(' - '); - this.addCosigner(seed.trim(), false, customPathForCurrentCosigner, passphrase); + this.addCosigner(seed.trim(), undefined, customPathForCurrentCosigner, passphrase); } break; } @@ -584,12 +635,13 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { 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 hexFingerprint = m[1].split('/')[0]; - const path = 'm/' + m[1].split('/').slice(1).join('/').replace(/[h]/g, "'"); + 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('/')); @@ -648,15 +700,17 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { } for (const pk of json.extendedPublicKeys) { - const path = this.constructor.isPathValid(json.bip32Path) ? json.bip32Path : "m/1'"; + 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, customPathPrefix) { + _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; @@ -668,30 +722,38 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { return false; } - _getWifForAddress(address) { + _getWifForAddress(address: string): string { + // @ts-ignore not applicable in multisig return false; } - _getPubkeyByAddress(address) { + _getPubkeyByAddress(address: string): false | Buffer { throw new Error('Not applicable in multisig'); } - _getDerivationPathByAddress(address) { + _getDerivationPathByAddress(address: string): string { throw new Error('Not applicable in multisig'); } - _addPsbtInput(psbt, input, sequence, masterFingerprintBuffer) { + _addPsbtInput(psbt: Psbt, input: CoinSelectReturnInput, sequence: number, masterFingerprintBuffer?: Uint8Array) { const bip32Derivation = []; // array per each pubkey thats gona be used const pubkeys = []; - for (const [cosignerIndex, cosigner] of this._cosigners.entries()) { + 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 = Buffer.from(this._cosignersFingerprints[cosignerIndex], 'hex'); + const masterFingerprint = hexToUint8Array(this._cosignersFingerprints[cosignerIndex]); - const xpub = this._getXpubFromCosigner(cosigner); + 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]; @@ -707,27 +769,32 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { }); } + 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; - if (!input.txhex) throw new Error('Electrum server didnt provide txhex to properly create PSBT transaction'); - psbt.addInput({ - hash: input.txId, + hash: input.txid, index: input.vout, sequence, bip32Derivation, witnessUtxo: { script: p2wsh.output, - value: input.value, + 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: Buffer.from(input.txhex, 'hex'), + nonWitnessUtxo: hexToUint8Array(input.txhex), }); } else if (this.isWrappedSegwit()) { const p2shP2wsh = bitcoin.payments.p2sh({ @@ -735,36 +802,43 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { 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, + hash: input.txid, index: input.vout, sequence, bip32Derivation, witnessUtxo: { script: p2shP2wsh.output, - value: input.value, + 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: Buffer.from(input.txhex, 'hex'), + 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, + hash: input.txid, index: input.vout, sequence, bip32Derivation, redeemScript, - nonWitnessUtxo: Buffer.from(input.txhex, 'hex'), + nonWitnessUtxo: hexToUint8Array(input.txhex), }); } else { throw new Error('Dont know how to add input'); @@ -773,18 +847,22 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { return psbt; } - _getOutputDataForChange(outputData) { - const bip32Derivation = []; // array per each pubkey thats gona be used + _getOutputDataForChange(address: string): TOutputData { + const bip32Derivation: TBip32Derivation = []; // array per each pubkey thats gona be used const pubkeys = []; - for (const [cosignerIndex, cosigner] of this._cosigners.entries()) { + for (const [cosignerIndex] of this._cosigners.entries()) { const path = this._getDerivationPathByAddressWithCustomPath( - outputData.address, + 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 masterFingerprint = hexToUint8Array(this._cosignersFingerprints[cosignerIndex]); + + if (!path) { + throw new Error('Could not find derivation path for address ' + address); + } - const xpub = this._getXpubFromCosigner(cosigner); + const xpub = this._getXpubFromCosignerIndex(cosignerIndex); const hdNode0 = bip32.fromBase58(xpub); const splt = path.split('/'); const internal = +splt[splt.length - 2]; @@ -800,30 +878,51 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { }); } - 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()) { + 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) }), }), }); - outputData.witnessScript = p2shP2wsh.redeem.redeem.output; - outputData.redeemScript = p2shP2wsh.redeem.output; - } else if (this.isNativeSegwit()) { + 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) }), }); - outputData.witnessScript = p2wsh.redeem.output; - } else { - throw new Error('dont know how to add change output'); + const witnessScript = p2wsh?.redeem?.output; + if (!witnessScript) { + throw new Error('Could not create witnessScript'); + } + return { + bip32Derivation, + witnessScript, + }; } - return outputData; + throw new Error('dont know how to add change output'); } howManySignaturesCanWeMake() { @@ -835,26 +934,53 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { 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; + 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) { - u.script = u.script || {}; + for (const u of _utxos) { + if (u.script?.length) { + continue; + } + if (this.isNativeSegwit()) { - u.script.length = u.script.length || Math.ceil((8 + this.getM() * 74 + this.getN() * 34) / 4); + 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); + 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; + u.script = { + length: 2 + this.getM() * 74 + this.getN() * 34, + }; } } - const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress); + 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(); @@ -868,18 +994,23 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { outputs.forEach(output => { // if output has no address - this is change output let change = false; - if (!output.address) { + let address: string | undefined = output.address; + if (!address) { change = true; output.address = changeAddress; + address = changeAddress; } - let outputData = { - address: output.address, - value: output.value, + let outputData: Parameters[0] = { + address, + value: BigInt(output.value), }; if (change) { - outputData = this._getOutputDataForChange(outputData); + outputData = { + ...outputData, + ...this._getOutputDataForChange(address), + }; } psbt.addOutput(outputData); @@ -915,7 +1046,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { return { tx, inputs, outputs, fee, psbt }; } - static convertElectrumMnemonicToSeed(cosigner, passphrase) { + static convertElectrumMnemonicToSeed(cosigner: string, passphrase?: string) { let seed; try { seed = mn.mnemonicToSeedSync(cosigner.replace(ELECTRUM_SEED_PREFIX, ''), electrumSegwit(passphrase)); @@ -931,21 +1062,19 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { /** * @see https://github.com/bitcoin/bips/blob/master/bip-0067.mediawiki - * - * @param bufArr {Array.} - * @returns {Array.} */ - static sortBuffers(bufArr) { - return bufArr.sort(Buffer.compare); + 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) { - const root = bip32.fromSeed(Buffer.alloc(32)); + static isPathValid(path: string): boolean { + const root = bip32.fromSeed(new Uint8Array(32)); try { root.derivePath(path); return true; @@ -966,7 +1095,6 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { // 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, ); @@ -981,23 +1109,23 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { getID() { const string2hash = [...this._cosigners].sort().join(',') + ';' + [...this._cosignersFingerprints].sort().join(','); - return createHash('sha256').update(string2hash).digest().toString('hex'); + return uint8ArrayToHex(sha256(string2hash)); } - calculateFeeFromPsbt(psbt) { + calculateFeeFromPsbt(psbt: Psbt) { let goesIn = 0; - const cacheUtxoAmounts = {}; + const cacheUtxoAmounts: { [key: string]: number } = {}; for (const inp of psbt.data.inputs) { if (inp.witnessUtxo && inp.witnessUtxo.value) { // segwit input - goesIn += inp.witnessUtxo.value; + 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.fromHex(inp.nonWitnessUtxo); + const inputTx = bitcoin.Transaction.fromBuffer(inp.nonWitnessUtxo); let index = 0; for (const out of inputTx.outs) { - cacheUtxoAmounts[inputTx.getId() + ':' + index] = out.value; + cacheUtxoAmounts[inputTx.getId() + ':' + index] = Number(out.value); index++; } } @@ -1007,20 +1135,20 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { // 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; + 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 += output.value; + goesOut += Number(output.value); } return goesIn - goesOut; } - calculateHowManySignaturesWeHaveFromPsbt(psbt) { + calculateHowManySignaturesWeHaveFromPsbt(psbt: Psbt) { let sigsHave = 0; for (const inp of psbt.data.inputs) { sigsHave = Math.max(sigsHave, inp.partialSig?.length || 0); @@ -1034,11 +1162,8 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { /** * 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) { + 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; @@ -1080,7 +1205,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { // ^^^ 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)) { + if (child.privateKey && psbt.inputHasPubkey(cc, child.publicKey)) { const keyPair = ECPair.fromPrivateKey(child.privateKey); try { psbt.signInput(cc, keyPair); @@ -1091,22 +1216,18 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { } } - let tx = false; if (this.calculateHowManySignaturesWeHaveFromPsbt(psbt) >= this.getM()) { - tx = psbt.finalizeAllInputs().extractTransaction(); + const tx = psbt.finalizeAllInputs().extractTransaction(); + return { tx }; } - return { tx }; + return { tx: false }; } /** * 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) { + 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'); @@ -1120,10 +1241,8 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { /** * Looks up cosigner with seed by index, and repalces it with xpub - * - * @param externalIndex {number} */ - replaceCosignerSeedWithXpub(externalIndex) { + 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'); @@ -1134,7 +1253,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { this._cosignersPassphrases[index] = undefined; } - deleteCosigner(fp) { + deleteCosigner(fp: string) { const foundIndex = this._cosignersFingerprints.indexOf(fp); if (foundIndex === -1) throw new Error('Cant find cosigner by fingerprint'); @@ -1163,9 +1282,9 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { } 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; + 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'); } @@ -1174,7 +1293,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { * @param fp {string} Exactly 8 chars of hex * @return {boolean} */ - static isFpValid(fp) { + static isFpValid(fp: string) { if (fp.length !== 8) return false; return /^[0-9A-F]{8}$/i.test(fp); } @@ -1187,7 +1306,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { * @param xpub * @return {boolean} */ - static isXpubForMultisig(xpub) { + static isXpubForMultisig(xpub: string): boolean { return ['xpub', 'Ypub', 'Zpub'].includes(xpub.substring(0, 4)); } diff --git a/class/wallets/segwit-bech32-wallet.js b/class/wallets/segwit-bech32-wallet.ts similarity index 57% rename from class/wallets/segwit-bech32-wallet.js rename to class/wallets/segwit-bech32-wallet.ts index e184dcdc60b..3e3f49db40b 100644 --- a/class/wallets/segwit-bech32-wallet.js +++ b/class/wallets/segwit-bech32-wallet.ts @@ -1,15 +1,24 @@ -import { LegacyWallet } from './legacy-wallet'; +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); -const bitcoin = require('bitcoinjs-lib'); export class SegwitBech32Wallet extends LegacyWallet { - static type = 'segwitBech32'; - static typeReadable = 'P2 WPKH'; - static segwitType = 'p2wpkh'; - - getAddress() { + 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 { @@ -24,18 +33,20 @@ export class SegwitBech32Wallet extends LegacyWallet { } catch (err) { return false; } - this._address = address; + this._address = address ?? false; return this._address; } - static witnessToAddress(witness) { + static witnessToAddress(witness: string): string | false { try { - const pubKey = Buffer.from(witness, 'hex'); - return bitcoin.payments.p2wpkh({ - pubkey: pubKey, - network: bitcoin.networks.bitcoin, - }).address; + const pubkey = hexToUint8Array(witness); + return ( + bitcoin.payments.p2wpkh({ + pubkey, + network: bitcoin.networks.bitcoin, + }).address ?? false + ); } catch (_) { return false; } @@ -47,41 +58,46 @@ export class SegwitBech32Wallet extends LegacyWallet { * @param scriptPubKey * @returns {boolean|string} Either bech32 address or false */ - static scriptPubKeyToAddress(scriptPubKey) { + static scriptPubKeyToAddress(scriptPubKey: string): string | false { try { - const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex'); - return bitcoin.payments.p2wpkh({ - output: scriptPubKey2, - network: bitcoin.networks.bitcoin, - }).address; + const scriptPubKey2 = hexToUint8Array(scriptPubKey); + return ( + bitcoin.payments.p2wpkh({ + output: scriptPubKey2, + network: bitcoin.networks.bitcoin, + }).address ?? false + ); } catch (_) { return false; } } - createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false, masterFingerprint) { + 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'); - // 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); + 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 = {}; - let keyPair; + const values: Record = {}; + const keyPair = ECPair.fromWIF(this.secret); 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 }); + if (!p2wpkh.output) { + throw new Error('Internal error: no p2wpkh.output during createTransaction()'); + } psbt.addInput({ hash: input.txid, @@ -89,7 +105,7 @@ export class SegwitBech32Wallet extends LegacyWallet { sequence, witnessUtxo: { script: p2wpkh.output, - value: input.value, + value: BigInt(input.value), }, }); }); @@ -102,7 +118,7 @@ export class SegwitBech32Wallet extends LegacyWallet { const outputData = { address: output.address, - value: output.value, + value: BigInt(output.value), }; psbt.addOutput(outputData); diff --git a/class/wallets/segwit-p2sh-wallet.js b/class/wallets/segwit-p2sh-wallet.ts similarity index 65% rename from class/wallets/segwit-p2sh-wallet.js rename to class/wallets/segwit-p2sh-wallet.ts index d0e28a558f6..4c382fa3e9b 100644 --- a/class/wallets/segwit-p2sh-wallet.js +++ b/class/wallets/segwit-p2sh-wallet.ts @@ -1,8 +1,13 @@ -import { LegacyWallet } from './legacy-wallet'; +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); -const bitcoin = require('bitcoinjs-lib'); /** * Creates Segwit P2SH Bitcoin address @@ -10,23 +15,25 @@ const bitcoin = require('bitcoinjs-lib'); * @param network * @returns {String} */ -function pubkeyToP2shSegwitAddress(pubkey, network) { - network = network || bitcoin.networks.bitcoin; +function pubkeyToP2shSegwitAddress(pubkey: Uint8Array): string | false { const { address } = bitcoin.payments.p2sh({ - redeem: bitcoin.payments.p2wpkh({ pubkey, network }), - network, + redeem: bitcoin.payments.p2wpkh({ pubkey }), }); - return address; + return address ?? false; } export class SegwitP2SHWallet extends LegacyWallet { - static type = 'segwitP2SH'; - static typeReadable = 'SegWit (P2SH)'; - static segwitType = 'p2sh(p2wpkh)'; - - static witnessToAddress(witness) { + 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 = Buffer.from(witness, 'hex'); + const pubKey = hexToUint8Array(witness); return pubkeyToP2shSegwitAddress(pubKey); } catch (_) { return false; @@ -39,19 +46,21 @@ export class SegwitP2SHWallet extends LegacyWallet { * @param scriptPubKey * @returns {boolean|string} Either p2sh address or false */ - static scriptPubKeyToAddress(scriptPubKey) { + static scriptPubKeyToAddress(scriptPubKey: string): string | false { try { - const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex'); - return bitcoin.payments.p2sh({ - output: scriptPubKey2, - network: bitcoin.networks.bitcoin, - }).address; + const scriptPubKey2 = hexToUint8Array(scriptPubKey); + return ( + bitcoin.payments.p2sh({ + output: scriptPubKey2, + network: bitcoin.networks.bitcoin, + }).address ?? false + ); } catch (_) { return false; } } - getAddress() { + getAddress(): string | false { if (this._address) return this._address; let address; try { @@ -72,7 +81,7 @@ export class SegwitP2SHWallet extends LegacyWallet { /** * - * @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 @@ -81,30 +90,33 @@ export class SegwitP2SHWallet extends LegacyWallet { * @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) { + 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'); - // 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); + 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 = {}; - let keyPair; + const values: Record = {}; + const keyPair = ECPair.fromWIF(this.secret); 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 }); + if (!p2sh.output) { + throw new Error('Internal error: no p2sh.output during createTransaction()'); + } psbt.addInput({ hash: input.txid, @@ -112,7 +124,7 @@ export class SegwitP2SHWallet extends LegacyWallet { sequence, witnessUtxo: { script: p2sh.output, - value: input.value, + value: BigInt(input.value), }, redeemScript: p2wpkh.output, }); @@ -126,7 +138,7 @@ export class SegwitP2SHWallet extends LegacyWallet { const outputData = { address: output.address, - value: output.value, + value: BigInt(output.value), }; psbt.addOutput(outputData); diff --git a/class/wallets/slip39-wallets.js b/class/wallets/slip39-wallets.ts similarity index 50% rename from class/wallets/slip39-wallets.js rename to class/wallets/slip39-wallets.ts index a8672ec1a0a..857f948d779 100644 --- a/class/wallets/slip39-wallets.js +++ b/class/wallets/slip39-wallets.ts @@ -1,30 +1,38 @@ +import { sha256 } from '@noble/hashes/sha256'; import slip39 from 'slip39'; -import { WORD_LIST } from 'slip39/dist/slip39_helper'; -import createHash from 'create-hash'; +import { WORD_LIST } from 'slip39/src/slip39_helper'; import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; -import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-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() { - const master = slip39.recoverSecret(this.secret, this.passphrase); - return Buffer.from(master); + _getSeed(): Uint8Array { + const self = this as unknown as TWalletThis; + const master = slip39.recoverSecret(self.secret, self.passphrase); + return Uint8Array.from(master); }, validateMnemonic() { - if (!this.secret.every(m => slip39.validateMnemonic(m))) return false; + const self = this as unknown as TWalletThis; + if (!self.secret.every(m => slip39.validateMnemonic(m))) return false; try { - slip39.recoverSecret(this.secret); + slip39.recoverSecret(self.secret); } catch (e) { return false; } return true; }, - setSecret(newSecret) { + 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); @@ -36,7 +44,7 @@ const SLIP39Mixin = { return map; }, new Map()); - this.secret = newSecret + self.secret = newSecret .trim() .split('\n') .filter(s => s) @@ -54,18 +62,23 @@ const SLIP39Mixin = { return secret; }); - return this; + return self; }, getID() { - const string2hash = this.secret.sort().join(',') + (this.getPassphrase() || ''); - return createHash('sha256').update(string2hash).digest().toString('hex'); + 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 type = 'SLIP39legacyP2PKH'; - static typeReadable = 'SLIP39 Legacy (P2PKH)'; + 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; @@ -73,23 +86,33 @@ export class SLIP39LegacyP2PKHWallet extends HDLegacyP2PKHWallet { _getSeed = SLIP39Mixin._getSeed; validateMnemonic = SLIP39Mixin.validateMnemonic; + // @ts-ignore: this type mismatch setSecret = SLIP39Mixin.setSecret; getID = SLIP39Mixin.getID; } export class SLIP39SegwitP2SHWallet extends HDSegwitP2SHWallet { - static type = 'SLIP39segwitP2SH'; - static typeReadable = 'SLIP39 SegWit (P2SH)'; + 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 type = 'SLIP39segwitBech32'; - static typeReadable = 'SLIP39 SegWit (Bech32)'; + 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; @@ -97,6 +120,7 @@ export class SLIP39SegwitBech32Wallet extends HDSegwitBech32Wallet { _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.ts similarity index 69% rename from class/wallets/watch-only-wallet.js rename to class/wallets/watch-only-wallet.ts index 60f8955ec9d..e1cfbf86f49 100644 --- a/class/wallets/watch-only-wallet.js +++ b/class/wallets/watch-only-wallet.ts @@ -1,22 +1,29 @@ -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 * 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 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; - } + 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 @@ -37,7 +44,7 @@ export class WatchOnlyWallet extends LegacyWallet { } allowSend() { - return this.useWithHardwareWalletEnabled() && this.isHd() && this._hdWalletInstance.allowSend(); + return this.useWithHardwareWalletEnabled() && this.isHd() && this._hdWalletInstance!.allowSend(); } allowSignVerifyMessage() { @@ -65,13 +72,33 @@ export class WatchOnlyWallet extends LegacyWallet { * 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(); + 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; @@ -84,6 +111,7 @@ export class WatchOnlyWallet extends LegacyWallet { 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]; } @@ -117,6 +145,7 @@ export class WatchOnlyWallet extends LegacyWallet { 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); @@ -127,6 +156,7 @@ export class WatchOnlyWallet extends LegacyWallet { 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); @@ -134,18 +164,18 @@ export class WatchOnlyWallet extends LegacyWallet { } } - async getAddressAsync() { + 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) { + _getExternalAddressByIndex(index: number) { if (this._hdWalletInstance) return this._hdWalletInstance._getExternalAddressByIndex(index); throw new Error('Not initialized'); } - _getInternalAddressByIndex(index) { + _getInternalAddressByIndex(index: number) { if (this._hdWalletInstance) return this._hdWalletInstance._getInternalAddressByIndex(index); throw new Error('Not initialized'); } @@ -170,29 +200,30 @@ export class WatchOnlyWallet extends LegacyWallet { throw new Error('Not initialized'); } - getUtxo(...args) { + getUtxo(...args: Parameters) { 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); + combinePsbt(...args: Parameters) { + if (this._hdWalletInstance) return this._hdWalletInstance.combinePsbt(...args); throw new Error('Not initialized'); } - broadcastTx(hex) { - if (this._hdWalletInstance) return this._hdWalletInstance.broadcastTx(hex); + 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) - * @see HDSegwitBech32Wallet.createTransaction */ - createTransaction(utxos, targets, feeRate, changeAddress, sequence) { + createTransaction(...args: Parameters) { + const [utxos, targets, feeRate, changeAddress, sequence] = args; if (this._hdWalletInstance && this.isHd()) { - return this._hdWalletInstance.createTransaction(utxos, targets, feeRate, changeAddress, sequence, true, this.getMasterFingerprint()); + 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)'); } @@ -224,7 +255,7 @@ export class WatchOnlyWallet extends LegacyWallet { return this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub'); } - weOwnAddress(address) { + weOwnAddress(address: string) { if (this.isHd()) { if (this._hdWalletInstance) return this._hdWalletInstance.weOwnAddress(address); throw new Error('Not initialized'); @@ -235,19 +266,15 @@ export class WatchOnlyWallet extends LegacyWallet { return this.getAddress() === address; } - allowHodlHodlTrading() { - return this.isHd(); - } - allowMasterFingerprint() { - return this.getSecret().startsWith('zpub'); + return this.getSecret().startsWith('zpub') || this.getSecret().startsWith('ypub') || this.getSecret().startsWith('xpub'); } useWithHardwareWalletEnabled() { return !!this.use_with_hardware_wallet; } - setUseWithHardwareWalletEnabled(enabled) { + setUseWithHardwareWalletEnabled(enabled: boolean) { this.use_with_hardware_wallet = !!enabled; } @@ -266,7 +293,7 @@ export class WatchOnlyWallet extends LegacyWallet { if (this.secret.startsWith('zpub')) { xpub = this._zpubToXpub(this.secret); } else if (this.secret.startsWith('ypub')) { - xpub = this.constructor._ypubToXpub(this.secret); + xpub = AbstractWallet._ypubToXpub(this.secret); } else { xpub = this.secret; } @@ -279,33 +306,38 @@ export class WatchOnlyWallet extends LegacyWallet { return false; } - addressIsChange(...args) { + addressIsChange(...args: Parameters) { if (this._hdWalletInstance) return this._hdWalletInstance.addressIsChange(...args); return super.addressIsChange(...args); } - getUTXOMetadata(...args) { + getUTXOMetadata(...args: Parameters) { if (this._hdWalletInstance) return this._hdWalletInstance.getUTXOMetadata(...args); return super.getUTXOMetadata(...args); } - setUTXOMetadata(...args) { + setUTXOMetadata(...args: Parameters) { if (this._hdWalletInstance) return this._hdWalletInstance.setUTXOMetadata(...args); return super.setUTXOMetadata(...args); } - getDerivationPath(...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) { + 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() { + 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/components/AddWalletButton.tsx b/components/AddWalletButton.tsx new file mode 100644 index 00000000000..d98f15bf45e --- /dev/null +++ b/components/AddWalletButton.tsx @@ -0,0 +1,60 @@ +import React, { useCallback, useMemo } from 'react'; +import { StyleSheet, GestureResponderEvent, Pressable } from 'react-native'; +import { Icon } from '@rneui/themed'; +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 styles = StyleSheet.create({ + ball: { + width: 30, + height: 30, + borderRadius: 15, + justifyContent: 'center', + alignContent: 'center', + }, + pressed: { + opacity: 0.6, + }, +}); + +const AddWalletButton: React.FC = ({ onPress }) => { + const { colors } = useTheme(); + const navigation = useExtendedNavigation(); + const stylesHook = StyleSheet.create({ + ball: { + backgroundColor: colors.buttonBackgroundColor, + }, + }); + + 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 ( + + [pressed ? styles.pressed : null, styles.ball, stylesHook.ball]} onPress={onPress}> + + + + ); +}; + +export default AddWalletButton; 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..c549fbdb6fb --- /dev/null +++ b/components/AddressInput.tsx @@ -0,0 +1,100 @@ +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, + }, +}); + +export default AddressInput; diff --git a/components/AddressInputScanButton.tsx b/components/AddressInputScanButton.tsx new file mode 100644 index 00000000000..835991111fe --- /dev/null +++ b/components/AddressInputScanButton.tsx @@ -0,0 +1,178 @@ +import React, { useCallback, useMemo } from 'react'; +import { Image, Keyboard, Platform, StyleSheet, Text } 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 RNQRGenerator from 'rn-qr-generator'; +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 values = await RNQRGenerator.detect({ + base64: base64Data, + }); + + if (values && values.values.length > 0) { + onChangeText(values.values[0]); + } 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 buttonStyle = useMemo(() => [styles.scan, stylesHook.scan], [stylesHook.scan]); + + 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: 'space-between', + borderRadius: 4, + paddingVertical: 4, + paddingHorizontal: 8, + marginHorizontal: 4, + }, + scanText: { + marginLeft: 4, + }, + linkText: { + textAlign: 'center', + fontSize: 16, + }, +}); 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..d8e5668a9c8 --- /dev/null +++ b/components/AmountInput.tsx @@ -0,0 +1,396 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Badge, Icon, Text } from '@rneui/themed'; +import BigNumber from 'bignumber.js'; +import dayjs from 'dayjs'; +import { + Image, + LayoutAnimation, + NativeSyntheticEvent, + Pressable, + StyleSheet, + TextInput, + TextInputProps, + TextInputSelectionChangeEventData, + TouchableOpacity, + View, +} from 'react-native'; + +import { + CurrencyRate, + fiatToBTC, + getCurrencySymbol, + isRateOutdated, + mostRecentFetchedRate, + satoshiToBTC, + updateExchangeRate, +} from '../blue_modules/currency'; +import { BlueText } from '../BlueComponents'; +import confirm from '../helpers/confirm'; +import loc, { formatBalancePlain, formatBalanceWithoutSuffix, removeTrailingZeros } from '../loc'; +import { BitcoinUnit } from '../models/bitcoinUnits'; +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; +}; + +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; +}; + +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, ...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 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()) { + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + 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 + */ + const log = `${amount}(${previousUnit}) ->`; + 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); + 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 + 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 handleSelectionChange = useCallback( + (event: NativeSyntheticEvent) => { + 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 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 ( + + + {!disabled && } + + + {unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && ( + {getCurrencySymbol() + ' '} + )} + {amount !== BitcoinUnit.MAX ? ( + = 0 ? String(amount) : undefined} + placeholderTextColor={disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2} + style={[styles.input, stylesHook.input]} + {...otherProps} + /> + ) : ( + + {BitcoinUnit.MAX} + + )} + {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', + }, + center: { + alignSelf: 'center', + }, + flex: { + flex: 1, + }, + spacing8: { + width: 8, + }, + disabledButton: { + opacity: 0.5, + }, + enabledButon: { + opacity: 1, + }, + outdatedRateContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + margin: 16, + }, + 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, + }, +}); diff --git a/components/ArrowPicker.tsx b/components/ArrowPicker.tsx index 273b9cc2b1a..87f7cdd3cf4 100644 --- a/components/ArrowPicker.tsx +++ b/components/ArrowPicker.tsx @@ -1,9 +1,10 @@ /* 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 { Keyboard, Pressable, StyleSheet, View } from 'react-native'; +import { Icon, Text } from '@rneui/themed'; + import loc from '../loc'; -import { useTheme } from '@react-navigation/native'; +import { useTheme } from './themes'; interface IHash { [key: string]: string; @@ -23,7 +24,6 @@ export const ArrowPicker = (props: ArrowPickerProps) => { const stylesHook = { text: { - // @ts-ignore: Ignore theme typescript error color: colors.foregroundColor, }, }; @@ -50,9 +50,7 @@ export const ArrowPicker = (props: ArrowPickerProps) => { styles.wrapperCustom, ]} > - {/* -// @ts-ignore: Ignore */} - + {props.isItemUnknown ? loc.send.fee_custom : keys[keyIndex]} @@ -78,9 +76,7 @@ export const ArrowPicker = (props: ArrowPickerProps) => { styles.wrapperCustom, ]} > - {/* -// @ts-ignore: Ignore */} - + ); diff --git a/components/BlueBigCheckmark.tsx b/components/BlueBigCheckmark.tsx new file mode 100644 index 00000000000..28cf8f90edd --- /dev/null +++ b/components/BlueBigCheckmark.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { StyleSheet, View, ViewProps } from 'react-native'; +import { Icon } from '@rneui/themed'; +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/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/BlurredBalanceView.tsx b/components/BlurredBalanceView.tsx new file mode 100644 index 00000000000..82cd75b45a6 --- /dev/null +++ b/components/BlurredBalanceView.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { Icon } from '@rneui/themed'; + +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/BottomModal.tsx b/components/BottomModal.tsx new file mode 100644 index 00000000000..a5a8961a592 --- /dev/null +++ b/components/BottomModal.tsx @@ -0,0 +1,272 @@ +import React, { forwardRef, useImperativeHandle, useRef, ReactElement, ComponentType } from 'react'; +import { SheetSize, SizeChangeEvent, TrueSheet, TrueSheetProps } from '@lodev09/react-native-true-sheet'; +import { Keyboard, Image, StyleSheet, View, Pressable, Platform, GestureResponderEvent, Text } from 'react-native'; +import SaveFileButton from './SaveFileButton'; +import { useTheme } from './themes'; +import { Icon } from '@rneui/base'; + +interface BottomModalProps extends TrueSheetProps { + children?: React.ReactNode; + onClose?: () => void; + onCloseModalPressed?: () => Promise; + isGrabberVisible?: boolean; + sizes?: SheetSize[] | undefined; + footer?: ReactElement | ComponentType | null; + footerDefaultMargins?: boolean | number; + onPresent?: () => void; + onSizeChange?: (event: SizeChangeEvent) => void; + showCloseButton?: boolean; + shareContent?: BottomModalShareContent; + shareButtonOnPress?: (event: GestureResponderEvent) => void; + header?: ReactElement | ComponentType | null; + headerTitle?: string; + headerSubtitle?: string; +} + +type BottomModalShareContent = { + fileName: string; + fileContent: string; +}; + +export interface BottomModalHandle { + present: () => Promise; + dismiss: () => Promise; +} + +const BottomModal = forwardRef( + ( + { + onClose, + onCloseModalPressed, + onPresent, + onSizeChange, + showCloseButton = true, + isGrabberVisible = true, + shareContent, + shareButtonOnPress, + sizes = ['auto'], + footer, + footerDefaultMargins, + header, + headerTitle, + headerSubtitle, + children, + ...props + }, + ref, + ) => { + const trueSheetRef = useRef(null); + const { colors, closeImage } = useTheme(); + const stylesHook = StyleSheet.create({ + barButton: { + backgroundColor: colors.lightButton, + }, + headerTitle: { + color: colors.foregroundColor, + }, + }); + + useImperativeHandle(ref, () => ({ + present: async () => { + Keyboard.dismiss(); + if (trueSheetRef.current?.present) { + await trueSheetRef.current.present(); + } else { + return Promise.resolve(); + } + }, + dismiss: async () => { + Keyboard.dismiss(); + if (trueSheetRef.current?.dismiss) { + await trueSheetRef.current.dismiss(); + } else { + return Promise.resolve(); + } + }, + })); + + const dismiss = async () => { + try { + await onCloseModalPressed?.(); + await trueSheetRef.current?.dismiss(); + } catch (error) { + console.error('Error during dismiss:', error); + } + }; + + const renderTopRightButton = () => { + const buttons = []; + if (shareContent || shareButtonOnPress) { + if (shareContent) { + buttons.push( + + + , + ); + } else if (shareButtonOnPress) { + buttons.push( + [pressed && styles.pressed, styles.topRightButton, stylesHook.barButton]} + onPress={shareButtonOnPress} + > + + , + ); + } + } + if (showCloseButton) { + buttons.push( + [pressed && styles.pressed, styles.topRightButton, stylesHook.barButton]} + onPress={dismiss} + key="ModalDoneButton" + testID="ModalDoneButton" + > + + , + ); + } + return {buttons}; + }; + + const renderHeader = () => { + if (headerTitle || headerSubtitle) { + return ( + + + {headerTitle && {headerTitle}} + {headerSubtitle && {headerSubtitle}} + + {renderTopRightButton()} + + ); + } + + if (showCloseButton || shareContent) + return ( + + {typeof header === 'function' ?
: header} + {renderTopRightButton()} + + ); + + if (React.isValidElement(header)) { + return ( + + {header} + {renderTopRightButton()} + + ); + } + return null; + }; + + const renderFooter = (): ReactElement | undefined => { + if (React.isValidElement(footer)) { + return footerDefaultMargins ? {footer} : footer; + } else if (typeof footer === 'function') { + const ModalFooterComponent = footer as ComponentType; + return ; + } + + return undefined; + }; + + const FooterComponent = renderFooter(); + + return ( + + {children} + {renderHeader()} + + ); + }, +); + +export default BottomModal; + +const styles = StyleSheet.create({ + footerContainer: { + alignItems: 'center', + justifyContent: 'center', + }, + headerContainer: { + position: 'absolute', + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 8, + minHeight: 22, + right: 16, + top: 16, + }, + headerContainerWithCloseButton: { + position: 'absolute', + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 8, + minHeight: 22, + width: '100%', + top: 16, + left: 0, + justifyContent: 'space-between', + }, + headerContent: { + flex: 1, + justifyContent: 'center', + minHeight: 22, + }, + headerTitle: { + fontSize: 18, + fontWeight: 'bold', + }, + headerSubtitle: { + fontSize: 14, + }, + topRightButton: { + justifyContent: 'center', + alignItems: 'center', + width: 30, + height: 30, + borderRadius: 15, + marginLeft: 22, + }, + topRightButtonContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + childrenContainer: { + paddingTop: 66, + paddingHorizontal: 16, + width: '100%', + }, + pressed: { + opacity: 0.6, + }, +}); diff --git a/components/Button.tsx b/components/Button.tsx new file mode 100644 index 00000000000..8c649515c99 --- /dev/null +++ b/components/Button.tsx @@ -0,0 +1,104 @@ +import React, { forwardRef } from 'react'; +import { ActivityIndicator, StyleProp, StyleSheet, Text, Pressable, PressableProps, View, ViewStyle, Platform } from 'react-native'; +import { Icon } from '@rneui/themed'; + +import { useTheme } from './themes'; + +interface ButtonProps extends PressableProps { + backgroundColor?: string; + buttonTextColor?: string; + disabled?: boolean; + testID?: string; + icon?: { + name: string; + type: string; + 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..116d6f90545 --- /dev/null +++ b/components/CameraScreen.tsx @@ -0,0 +1,267 @@ +import { Icon } from '@rneui/base'; +import React, { useRef, useState } from 'react'; +import { Animated, Platform, 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'; + +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 && ( + + + + {Platform.OS === 'ios' ? ( + + ) : ( + + )} + + + + {showImagePickerButton && ( + + + + + + )} + {showFilePickerButton && ( + + + + + + )} + + + )} + + + + + + {loc._.cancel} + + {isDesktop ? ( + + {showImagePickerButton && ( + + + + + + )} + {showFilePickerButton && ( + + + + + + )} + + ) : ( + + + {Platform.OS === 'ios' ? ( + + ) : ( + + )} + + + )} + + + ); +}; + +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.tsx similarity index 75% rename from components/CoinsSelected.js rename to components/CoinsSelected.tsx index 2c7c12550a1..e8b8c17132a 100644 --- a/components/CoinsSelected.js +++ b/components/CoinsSelected.tsx @@ -1,7 +1,6 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; -import { Avatar } from 'react-native-elements'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { Avatar } from '@rneui/themed'; import loc from '../loc'; @@ -34,7 +33,13 @@ const styles = StyleSheet.create({ }, }); -const CoinsSelected = ({ number, onContainerPress, onClose }) => ( +interface CoinsSelectedProps { + number: number; + onContainerPress: () => void; + onClose: () => void; +} + +const CoinsSelected: React.FC = ({ number, onContainerPress, onClose }) => ( {loc.formatString(loc.cc.coins_selected, { number })} @@ -45,10 +50,4 @@ const CoinsSelected = ({ number, onContainerPress, onClose }) => ( ); -CoinsSelected.propTypes = { - number: PropTypes.number.isRequired, - onContainerPress: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, -}; - export default CoinsSelected; diff --git a/components/Context/SettingsProvider.tsx b/components/Context/SettingsProvider.tsx new file mode 100644 index 00000000000..ac1c6123e01 --- /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'; +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..e61dca3bbc6 --- /dev/null +++ b/components/Context/StorageProvider.tsx @@ -0,0 +1,559 @@ +import React, { createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { InteractionManager, LayoutAnimation } from 'react-native'; +import { BlueApp as BlueAppClass, LegacyWallet, TCounterpartyMetadata, TTXMetadata, WatchOnlyWallet } from '../../class'; +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 { getLastScanWasBBQR } 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; + } + await InteractionManager.runAfterInteractions(async () => { + 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; + + await new Promise(resolve => InteractionManager.runAfterInteractions(() => resolve())); + + const TIMEOUT_DURATION = 30000; + let refreshTimeout; + const timeoutPromise = new Promise( + (_resolve, reject) => + (refreshTimeout = setTimeout(() => { + console.debug('[refreshAllWalletTransactions] Timeout reached'); + reject(new Error('Timeout reached')); + }, TIMEOUT_DURATION)), + ); + + 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'); + + // Restore fetch payment codes timing measurement + if (typeof BlueApp.fetchSenderPaymentCodes === 'function') { + const codesStart = Date.now(); + console.debug('[refreshAllWalletTransactions] Fetching sender payment codes'); + await BlueApp.fetchSenderPaymentCodes(lastSnappedTo); + const codesEnd = Date.now(); + console.debug('[refreshAllWalletTransactions] fetch payment codes took', (codesEnd - codesStart) / 1000, 'sec'); + } else { + console.warn('[refreshAllWalletTransactions] fetchSenderPaymentCodes is not available'); + } + + console.debug('[refreshAllWalletTransactions] Fetching wallet balances and transactions'); + await Promise.race([ + (async () => { + const balanceStart = Date.now(); + await BlueApp.fetchWalletBalances(lastSnappedTo); + const balanceEnd = Date.now(); + console.debug('[refreshAllWalletTransactions] fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec'); + + const txStart = Date.now(); + await BlueApp.fetchWalletTransactions(lastSnappedTo); + const txEnd = Date.now(); + console.debug('[refreshAllWalletTransactions] fetch tx took', (txEnd - txStart) / 1000, 'sec'); + + clearTimeout(refreshTimeout); + + console.debug('[refreshAllWalletTransactions] Saving data to disk'); + await saveToDisk(); + })(), + timeoutPromise, + ]); + console.debug('[refreshAllWalletTransactions] Refresh completed successfully'); + } catch (error) { + console.error('[refreshAllWalletTransactions] Error:', error); + } finally { + console.debug('[refreshAllWalletTransactions] Resetting wallet transaction status and refresh lock'); + setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE); + refreshingRef.current = false; + } + }, + [saveToDisk], + ); + + const fetchAndSaveWalletTransactions = useCallback( + async (walletID: string) => { + await InteractionManager.runAfterInteractions(async () => { + 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 (getLastScanWasBBQR()) { + // 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); + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + 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..db49653d6ec --- /dev/null +++ b/components/CopyTextToClipboard.tsx @@ -0,0 +1,68 @@ +import Clipboard from '@react-native-clipboard/clipboard'; +import React, { forwardRef, useEffect, useState } from 'react'; +import { Animated, StyleSheet, TouchableOpacity, View } from 'react-native'; + +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; +import loc from '../loc'; + +type CopyTextToClipboardProps = { + text: string; + truncated?: boolean; +}; + +const styleCopyTextToClipboard = StyleSheet.create({ + address: { + marginVertical: 32, + fontSize: 15, + color: '#9aa0aa', + textAlign: 'center', + }, +}); + +const CopyTextToClipboard = forwardRef, CopyTextToClipboardProps>(({ text, truncated }, ref) => { + const [hasTappedText, setHasTappedText] = useState(false); + const [address, setAddress] = useState(text); + + useEffect(() => { + if (!hasTappedText) { + setAddress(text); + } + }, [text, hasTappedText]); + + const copyToClipboard = () => { + setHasTappedText(true); + Clipboard.setString(text); + triggerHapticFeedback(HapticFeedbackTypes.Selection); + setAddress(loc.wallets.xpub_copiedToClipboard); // Adjust according to your localization logic + setTimeout(() => { + setHasTappedText(false); + setAddress(text); + }, 1000); + }; + + return ( + + + + {address} + + + + ); +}); + +export default CopyTextToClipboard; + +const styles = StyleSheet.create({ + container: { justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }, +}); 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..76f6bef2738 --- /dev/null +++ b/components/DevMenu.tsx @@ -0,0 +1,167 @@ +import React, { useEffect } from 'react'; +import { DevSettings, Alert, Platform, AlertButton } from 'react-native'; +import { useStorage } from '../hooks/context/useStorage'; +import { HDSegwitBech32Wallet, WatchOnlyWallet } from '../class'; +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..4e18037b744 --- /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 '../BlueComponents'; +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/DoneAndDismissKeyboardInputAccessory.tsx b/components/DoneAndDismissKeyboardInputAccessory.tsx new file mode 100644 index 00000000000..1bbad02e380 --- /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 '../BlueComponents'; +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.tsx similarity index 80% rename from components/DynamicQRCode.js rename to components/DynamicQRCode.tsx index 67c77905746..9a6d8e8cc15 100644 --- a/components/DynamicQRCode.js +++ b/components/DynamicQRCode.tsx @@ -1,18 +1,34 @@ -/* 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 { Text } from '@rneui/themed'; + import { encodeUR } from '../blue_modules/ur'; -import QRCodeComponent from './QRCodeComponent'; import { BlueCurrentTheme } from '../components/themes'; -import { BlueSpacing20 } from '../BlueComponents'; import loc from '../loc'; +import QRCodeComponent from './QRCodeComponent'; +import { BlueSpacing20 } from './BlueSpacing'; const { height, width } = Dimensions.get('window'); -export class DynamicQRCode extends Component { - constructor() { - super(); +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 = { @@ -24,12 +40,12 @@ export class DynamicQRCode extends Component { }; } - fragments = []; + fragments: string[] = []; componentDidMount() { - const { value, capacity = 200, hideControls = true } = this.props; + const { value, capacity = 175, hideControls = true, walletID } = this.props; try { - this.fragments = encodeUR(value, capacity); + this.fragments = encodeUR(value, capacity, walletID); this.setState( { total: this.fragments.length, @@ -67,7 +83,7 @@ export class DynamicQRCode extends Component { }; stopAutoMove = () => { - clearInterval(this.state.intervalHandler); + clearInterval(this.state.intervalHandler as number); this.setState(() => ({ intervalHandler: null, })); @@ -138,21 +154,21 @@ export class DynamicQRCode extends Component { {loc.send.dynamic_prev} {this.state.intervalHandler ? loc.send.dynamic_stop : loc.send.dynamic_start} {loc.send.dynamic_next} @@ -189,6 +205,17 @@ const animatedQRCodeStyle = StyleSheet.create({ height: 45, justifyContent: 'center', }, + buttonPrev: { + width: '25%', + alignItems: 'flex-start', + }, + buttonStopStart: { + width: '50%', + }, + buttonNext: { + width: '25%', + alignItems: 'flex-end', + }, text: { fontSize: 14, color: BlueCurrentTheme.colors.foregroundColor, 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..710d9377c35 --- /dev/null +++ b/components/FloatButtons.tsx @@ -0,0 +1,737 @@ +import React, { forwardRef, ReactNode, useEffect, useRef, useState, useCallback, useMemo } from 'react'; +import { + Animated, + LayoutAnimation, + Platform, + PixelRatio, + StyleSheet, + Text, + TouchableOpacity, + UIManager, + useWindowDimensions, + View, + StyleProp, + TextStyle, +} from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useTheme } from './themes'; +import { useSizeClass, SizeClass } from '../blue_modules/sizeClass'; +import { isDesktop } from '../blue_modules/environment'; +import debounce from '../blue_modules/debounce'; + +if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) { + UIManager.setLayoutAnimationEnabledExperimental(true); +} + +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, + SINGLE_BUTTON_HEIGHT: 58, + CONTAINER_SIDE_MARGIN: 20, + DEFAULT_BORDER_RADIUS: 8, + SINGLE_BUTTON_RADIUS: 29, + SINGLE_BUTTON_WIDTH_FACTOR: 0.625, + MAX_BUTTON_FONT_SIZE: 24, + SAFETY_MARGIN: 20, + ANIMATION_DURATION: 300, + SPRING_CONFIG: { + speed: 12, + bounciness: 4, + useNativeDriver: true, + }, + TIMING_CONFIG: { + duration: 300, + useNativeDriver: true, + }, +}; + +const useFloatButtonAnimation = (height: number) => { + const slideAnimation = useRef(new Animated.Value(height)).current; + const animatedButtonRadius = useRef(new Animated.Value(LAYOUT.DEFAULT_BORDER_RADIUS)).current; + const animatedSingleButtonRadius = useRef(new Animated.Value(LAYOUT.SINGLE_BUTTON_RADIUS)).current; + const [isAnimating, setIsAnimating] = useState(false); + const animationInterrupted = useRef(false); + + useEffect(() => { + slideAnimation.setValue(height); + + if (isDesktop) { + slideAnimation.setValue(0); + return; + } + + Animated.spring(slideAnimation, { + toValue: 0, + friction: 7, + tension: 40, + useNativeDriver: true, + }).start(); + }, [height, slideAnimation]); + + const configureLayoutAnimation = useCallback(() => { + if (isDesktop) return; + + LayoutAnimation.configureNext({ + duration: 250, + create: { + type: LayoutAnimation.Types.easeInEaseOut, + property: LayoutAnimation.Properties.opacity, + }, + update: { + type: LayoutAnimation.Types.spring, + springDamping: 0.85, + }, + delete: { + type: LayoutAnimation.Types.easeInEaseOut, + property: LayoutAnimation.Properties.opacity, + }, + }); + }, []); + + const animateBorderRadius = useCallback( + (buttonRadius: number, singleRadius: number, onComplete?: () => void) => { + if (isDesktop) { + animatedButtonRadius.setValue(buttonRadius); + animatedSingleButtonRadius.setValue(singleRadius); + if (onComplete) onComplete(); + return; + } + + if (isAnimating) { + animationInterrupted.current = true; + return; + } + + setIsAnimating(true); + animationInterrupted.current = false; + + Animated.parallel([ + Animated.timing(animatedButtonRadius, { + toValue: buttonRadius, + duration: 250, + useNativeDriver: true, + }), + Animated.timing(animatedSingleButtonRadius, { + toValue: singleRadius, + duration: 250, + useNativeDriver: true, + }), + ]).start(({ finished }) => { + setIsAnimating(false); + if (finished && !animationInterrupted.current && onComplete) { + onComplete(); + } + }); + }, + [animatedButtonRadius, animatedSingleButtonRadius, isAnimating], + ); + + return { + slideAnimation, + animatedButtonRadius, + animatedSingleButtonRadius, + isAnimating: isDesktop ? false : isAnimating, + setIsAnimating, + configureLayoutAnimation, + animateBorderRadius, + }; +}; + +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); + + let buttonRadius; + if (totalChildren === 1) { + buttonRadius = LAYOUT.SINGLE_BUTTON_RADIUS; + } else { + buttonRadius = Math.min(LAYOUT.DEFAULT_BORDER_RADIUS * 1.5, calculatedWidth / 12); + } + + const multiButtonRadius = Math.max(LAYOUT.DEFAULT_BORDER_RADIUS, Math.floor(buttonRadius)); + const singleButtonRadius = LAYOUT.SINGLE_BUTTON_RADIUS; + + return { buttonRadius: multiButtonRadius, singleButtonRadius, 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', + }, + icon: { + alignItems: 'center', + justifyContent: 'center', + }, + iconContainer: { + alignItems: 'center', + justifyContent: 'center', + minWidth: 24, + minHeight: 24, + overflow: 'visible', + alignSelf: 'center', + }, + touchContainer: { + width: '100%', + height: '100%', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + centeredText: { + textAlign: 'center', + textAlignVertical: 'center', + }, +}); + +const buttonContentStaticStyles = StyleSheet.create({ + root: { + height: LAYOUT.BUTTON_HEIGHT, + overflow: 'hidden', + justifyContent: 'center', + }, + rootSingle: { + height: LAYOUT.SINGLE_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; + first?: boolean; + last?: boolean; + singleChild?: boolean; + isVertical?: boolean; + borderRadius?: number | Animated.Value; + fontSize?: number; + isAnimating?: boolean; + disabled?: boolean; + testID?: string; + onPress: () => void; + onLongPress?: () => void; +} + +interface ButtonContentProps { + icon: ReactNode; + text: string; + textStyle: StyleProp; + iconStyle: StyleProp; +} + +const getScaledIconSize = (fontSize: number): number => { + return Math.max(Math.round(fontSize * 1.2), 16); +}; + +const ButtonContent = ({ icon, text, textStyle, iconStyle }: 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, { + ...iconElement.props, + size: iconSize, + width: iconSize, + height: iconSize, + }); + } else { + scaledIcon = icon; + } + + return ( + + {scaledIcon} + + {text} + + + ); +}; + +export const FButton = ({ + text, + icon, + width, + first, + last, + singleChild, + isVertical, + borderRadius = LAYOUT.DEFAULT_BORDER_RADIUS, + fontSize = LAYOUT.MAX_BUTTON_FONT_SIZE, + isAnimating = false, + testID, + ...props +}: FButtonProps) => { + const { colors } = useTheme(); + + const customButtonStyles = useMemo(() => { + const baseStyles = singleChild ? { ...buttonContentStaticStyles.rootSingle } : { ...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, singleChild]); + + 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]; + + if (isAnimating && borderRadius instanceof Animated.Value) { + return ( + + + + + + ); + } + + return ( + + + + ); +}; + +export const FContainer = forwardRef((props, ref) => { + const insets = useSafeAreaInsets(); + const { height, width } = useWindowDimensions(); + const { sizeClass } = useSizeClass(); + + const [newWidth, setNewWidth] = useState(undefined); + const [isVertical, setIsVertical] = useState(false); + const [layoutReady, setLayoutReady] = useState(false); + const [buttonBorderRadius, setButtonBorderRadius] = useState(LAYOUT.DEFAULT_BORDER_RADIUS); + const [singleButtonBorderRadius, setSingleButtonBorderRadius] = useState(LAYOUT.SINGLE_BUTTON_RADIUS); + + const layoutWidth = useRef(0); + const layoutCalculated = useRef(false); + const orientationChangeTimestamp = useRef(0); + const animationInProgress = useRef(false); + const pendingAnimationParams = useRef(null); + + const bottomInsets = useMemo( + () => ({ + bottom: insets.bottom ? insets.bottom + 10 : 30, + }), + [insets.bottom], + ); + + const { slideAnimation, animatedButtonRadius, animatedSingleButtonRadius, isAnimating, configureLayoutAnimation, animateBorderRadius } = + useFloatButtonAnimation(height); + + const { calculateButtonWidth, calculateVisualParameters, calculateContainerHeight, buttonFontSize } = useFloatButtonLayout( + width, + sizeClass, + ); + + const handleBorderRadiusAnimation = useCallback( + (buttonRadius: number, singleRadius: number, shouldBeVertical: boolean, calculatedWidth: number) => { + const now = Date.now(); + const isOrientationChange = Math.abs(width / height - height / width) > 0.8; + + if (isOrientationChange) { + orientationChangeTimestamp.current = now; + setNewWidth(calculatedWidth); + setIsVertical(shouldBeVertical); + setButtonBorderRadius(buttonRadius); + setSingleButtonBorderRadius(singleRadius); + return; + } + + if (animationInProgress.current) { + pendingAnimationParams.current = { buttonRadius, singleRadius, shouldBeVertical, calculatedWidth }; + return; + } + + if (isDesktop || now - orientationChangeTimestamp.current < 1000) { + setNewWidth(calculatedWidth); + setIsVertical(shouldBeVertical); + setButtonBorderRadius(buttonRadius); + setSingleButtonBorderRadius(singleRadius); + return; + } + + animationInProgress.current = true; + + if (shouldBeVertical !== isVertical) { + configureLayoutAnimation(); + } + + setNewWidth(calculatedWidth); + setIsVertical(shouldBeVertical); + + animateBorderRadius(buttonRadius, singleRadius, () => { + setButtonBorderRadius(buttonRadius); + setSingleButtonBorderRadius(singleRadius); + animationInProgress.current = false; + + if (pendingAnimationParams.current) { + const { + buttonRadius: nextRadius, + singleRadius: nextSingle, + shouldBeVertical: nextVertical, + calculatedWidth: nextWidth, + } = pendingAnimationParams.current; + pendingAnimationParams.current = null; + + setTimeout(() => { + handleBorderRadiusAnimation(nextRadius, nextSingle, nextVertical, nextWidth); + }, 50); + } + }); + }, + [animateBorderRadius, configureLayoutAnimation, height, width, isVertical], + ); + + const calculateLayout = useCallback(() => { + if (!layoutReady || layoutWidth.current <= 0) return; + + scheduleInNextFrame(() => { + const totalChildren = React.Children.toArray(props.children).filter(Boolean).length; + const calculatedWidth = calculateButtonWidth(layoutWidth.current, totalChildren); + const { buttonRadius, singleButtonRadius, shouldBeVertical } = calculateVisualParameters(calculatedWidth, totalChildren); + + if (shouldBeVertical !== isVertical || newWidth !== calculatedWidth) { + handleBorderRadiusAnimation(buttonRadius, singleButtonRadius, shouldBeVertical, calculatedWidth); + } else { + setNewWidth(calculatedWidth); + setIsVertical(shouldBeVertical); + setButtonBorderRadius(buttonRadius); + setSingleButtonBorderRadius(singleButtonRadius); + } + + layoutCalculated.current = true; + }); + }, [ + layoutReady, + calculateButtonWidth, + calculateVisualParameters, + handleBorderRadiusAnimation, + isVertical, + newWidth, + props.children, + setNewWidth, + setIsVertical, + setButtonBorderRadius, + setSingleButtonBorderRadius, + ]); + + const debouncedCalculateLayout = useMemo(() => debounce(calculateLayout, 16), [calculateLayout]); + + useEffect(() => { + debouncedCalculateLayout(); + }, [debouncedCalculateLayout, width, height, props.children, 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; + layoutCalculated.current = false; + } + + if (!layoutReady) { + setLayoutReady(true); + } + } + }; + + const renderChild = (child: ReactNode, index: number, array: ReactNode[]): ReactNode => { + if (typeof child === 'string') { + return ( + + + {child} + + + ); + } + + const isSingleChild = array.length === 1; + const borderRadiusToUse = isSingleChild + ? isAnimating + ? animatedSingleButtonRadius + : singleButtonBorderRadius + : isAnimating + ? animatedButtonRadius + : buttonBorderRadius; + + return React.cloneElement(child as React.ReactElement, { + width: newWidth, + key: index, + first: index === 0, + last: index === array.length - 1, + singleChild: isSingleChild, + isVertical, + borderRadius: borderRadiusToUse, + fontSize: buttonFontSize, + isAnimating, + }); + }; + + const totalChildren = React.Children.toArray(props.children).filter(Boolean).length; + const containerHeight = useMemo( + () => calculateContainerHeight(totalChildren, isVertical), + [calculateContainerHeight, totalChildren, isVertical], + ); + + const dynamicRoundStyle = useMemo(() => { + if (totalChildren === 1) { + return { + borderRadius: isAnimating ? animatedSingleButtonRadius : singleButtonBorderRadius, + overflow: 'hidden', + }; + } + return null; + }, [totalChildren, singleButtonBorderRadius, isAnimating, animatedSingleButtonRadius]); + + const combinedStyles = useMemo( + () => [ + containerStyles.root, + props.inline ? containerStyles.rootInline : containerStyles.rootAbsolute, + bottomInsets, + newWidth ? (isVertical ? containerStyles.rootPostVertical : containerStyles.rootPost) : containerStyles.rootPre, + dynamicRoundStyle, + isVertical ? containerHeight : null, + { transform: [{ translateY: slideAnimation }] }, + ], + [props.inline, bottomInsets, newWidth, isVertical, dynamicRoundStyle, containerHeight, slideAnimation], + ); + + return ( + + {newWidth && layoutReady ? React.Children.toArray(props.children).filter(Boolean).map(renderChild) : props.children} + + ); +}); diff --git a/components/HandOffComponent.ios.tsx b/components/HandOffComponent.ios.tsx new file mode 100644 index 00000000000..8e726a59ee0 --- /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'; +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..6e3d8d1a40e --- /dev/null +++ b/components/HeaderMenuButton.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { Pressable, Platform } from 'react-native'; +import ToolTipMenu from './TooltipMenu'; +import { useTheme } from './themes'; +import { Icon } from '@rneui/themed'; +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 ( + [{ opacity: pressed ? 0.5 : 1 }]} + > + + + ); + } + + const menuActions = Array.isArray(actions[0]) ? (actions as Action[][]) : (actions as Action[]); + + return ( + + + + ); +}; + +export default HeaderMenuButton; diff --git a/components/HeaderRightButton.tsx b/components/HeaderRightButton.tsx new file mode 100644 index 00000000000..f02f5fcdb19 --- /dev/null +++ b/components/HeaderRightButton.tsx @@ -0,0 +1,43 @@ +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 = true, onPress, title, testID }) => { + const { colors } = useTheme(); + const opacity = disabled ? 0.5 : 1; + return ( + + {title} + + ); +}; + +const styles = StyleSheet.create({ + save: { + alignItems: 'center', + justifyContent: 'center', + width: 80, + 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/InputAccessoryAllFunds.js b/components/InputAccessoryAllFunds.tsx similarity index 80% rename from components/InputAccessoryAllFunds.js rename to components/InputAccessoryAllFunds.tsx index 4f7ec698fc1..6a50a055100 100644 --- a/components/InputAccessoryAllFunds.js +++ b/components/InputAccessoryAllFunds.tsx @@ -1,14 +1,18 @@ 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 { InputAccessoryView, Keyboard, Platform, StyleSheet, View } from 'react-native'; +import { Text } from '@rneui/themed'; +import { BlueButtonLink } from '../BlueComponents'; import loc from '../loc'; import { BitcoinUnit } from '../models/bitcoinUnits'; -import { BlueButtonLink } from '../BlueComponents'; +import { useTheme } from './themes'; -const InputAccessoryAllFunds = ({ balance, canUseAll, onUseAllPressed }) => { +interface InputAccessoryAllFundsProps { + balance: string; + canUseAll: boolean; + onUseAllPressed: () => void; +} + +const InputAccessoryAllFunds: React.FC = ({ balance, canUseAll, onUseAllPressed }) => { const { colors } = useTheme(); const stylesHook = StyleSheet.create({ @@ -42,7 +46,7 @@ const InputAccessoryAllFunds = ({ balance, canUseAll, onUseAllPressed }) => { ); if (Platform.OS === 'ios') { - return {inputView}; + return {inputView}; } // androidPlaceholder View is needed to force shrink screen (KeyboardAvoidingView) where this component is used @@ -54,13 +58,7 @@ const InputAccessoryAllFunds = ({ balance, canUseAll, onUseAllPressed }) => { ); }; -InputAccessoryAllFunds.InputAccessoryViewID = 'useMaxInputAccessoryViewID'; - -InputAccessoryAllFunds.propTypes = { - balance: PropTypes.string.isRequired, - canUseAll: PropTypes.bool.isRequired, - onUseAllPressed: PropTypes.func.isRequired, -}; +export const InputAccessoryAllFundsAccessoryViewID = 'useMaxInputAccessoryViewID'; const styles = StyleSheet.create({ root: { 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..4ffd85b0589 --- /dev/null +++ b/components/ListItem.tsx @@ -0,0 +1,156 @@ +import React, { useMemo } from 'react'; +import { Pressable, PressableProps, StyleSheet, Switch, TouchableOpacity, View } from 'react-native'; +import { ListItem as RNElementsListItem } from '@rneui/themed'; +import { useLocale } from '@react-navigation/native'; + +import { useTheme } from './themes'; + +interface ListItemProps { + leftAvatar?: React.JSX.Element; + containerStyle?: object; + Component?: typeof React.Component | typeof PressableWrapper; + bottomDivider?: boolean; + testID?: string; + onPress?: () => void; + disabled?: boolean; + switch?: object; + title: string; + subtitle?: string | React.ReactNode; + subtitleNumberOfLines?: number; + rightTitle?: string; + rightTitleStyle?: object; + chevron?: boolean; + checkmark?: boolean; +} + +export class PressableWrapper extends React.Component { + render() { + return ; + } +} + +export class TouchableOpacityWrapper extends React.Component { + render() { + return ; + } +} + +const ListItem: React.FC = React.memo( + ({ + Component = TouchableOpacityWrapper, + leftAvatar, + containerStyle, + bottomDivider = true, + testID, + onPress, + disabled, + switch: switchProps, + title, + subtitle, + subtitleNumberOfLines, + rightTitle, + rightTitleStyle, + chevron, + checkmark, + }: ListItemProps) => { + const { colors } = useTheme(); + const { direction } = useLocale(); + const stylesHook = StyleSheet.create({ + title: { + color: disabled ? colors.buttonDisabledTextColor : colors.foregroundColor, + fontSize: 16, + fontWeight: '500', + writingDirection: direction, + }, + subtitle: { + flexWrap: 'wrap', + writingDirection: direction, + color: colors.alternativeTextColor, + fontWeight: '400', + paddingVertical: switchProps ? 8 : 0, + lineHeight: 20, + fontSize: 14, + }, + + containerStyle: { + backgroundColor: colors.background, + }, + }); + + const memoizedSwitchProps = useMemo(() => { + return switchProps ? { ...switchProps } : undefined; + }, [switchProps]); + + const renderContent = () => ( + <> + {leftAvatar && ( + <> + {leftAvatar} + + + )} + + + {title} + + {subtitle && ( + + {subtitle} + + )} + + + {rightTitle && ( + + + {rightTitle} + + + )} + {chevron && } + {switchProps && ( + + )} + {checkmark && ( + + )} + + ); + + return ( + + {renderContent()} + + ); + }, +); + +export default ListItem; + +const styles = StyleSheet.create({ + margin8: { + margin: 8, + }, + margin16: { + marginLeft: 16, + }, + width16: { width: 16 }, +}); diff --git a/components/ManageWalletsListItem.tsx b/components/ManageWalletsListItem.tsx new file mode 100644 index 00000000000..f87dec9b4b3 --- /dev/null +++ b/components/ManageWalletsListItem.tsx @@ -0,0 +1,497 @@ +import React, { useCallback, useState, useEffect, useRef } from 'react'; +import { StyleSheet, ViewStyle, TouchableOpacity, ActivityIndicator, Platform, Animated, View, Text, TextStyle } from 'react-native'; +import { Icon, ListItem } from '@rneui/base'; +import { ExtendedTransaction, LightningTransaction, Transaction, TWallet } from '../class/wallets/types'; +import { WalletCarouselItem } from './WalletsCarousel'; +import { TransactionListItem } from './TransactionListItem'; +import { useTheme } from './themes'; +import { BitcoinUnit } from '../models/bitcoinUnits'; +import loc from '../loc'; +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; +import { AddressItem } from './addresses/AddressItem'; +import { ItemType, AddressItemData } from '../models/itemTypes'; +import WalletGradient from '../class/wallet-gradient'; + +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; + drag?: () => void; + isPlaceHolder?: boolean; + onPressIn?: () => void; + onPressOut?: () => void; + state: { wallets: TWallet[]; searchQuery: string; isSearchFocused?: boolean }; + navigateToWallet: (wallet: TWallet) => void; + navigateToAddress?: (address: string, walletID: string) => void; + renderHighlightedText: (text: string, query: string) => JSX.Element; + handleToggleHideBalance: (wallet: TWallet) => void; + isActive?: boolean; + style?: ViewStyle; + globalDragActive?: boolean; +} + +interface SwipeContentProps { + onPress: () => void; + hideBalance?: boolean; + colors: any; +} + +const LeftSwipeContent: React.FC = ({ onPress, hideBalance, colors }) => ( + + + +); + +const ManageWalletsListItem: React.FC = ({ + item, + isDraggingDisabled, + drag, + state, + isPlaceHolder = false, + navigateToWallet, + navigateToAddress, + renderHighlightedText, + handleToggleHideBalance, + onPressIn, + onPressOut, + isActive, + globalDragActive, + style, +}) => { + const { colors } = useTheme(); + const [isLoading, setIsLoading] = useState(false); + const [isSwipeActive, setIsSwipeActive] = useState(false); + const resetFunctionRef = useRef<(() => void) | null>(null); + + const CARD_SORT_ACTIVE = 1.06; + const INACTIVE_SCALE_WHEN_ACTIVE = 0.9; + const SCALE_DURATION = 200; + const scaleValue = useRef(new Animated.Value(1)).current; + const prevIsActive = useRef(isActive); + + const DEFAULT_VERTICAL_MARGIN = -10; + const REDUCED_VERTICAL_MARGIN = -50; + + const animateItemIn = useCallback(() => { + if (Platform.OS === 'ios') { + Animated.spring(scaleValue, { + toValue: isActive ? CARD_SORT_ACTIVE : globalDragActive ? INACTIVE_SCALE_WHEN_ACTIVE : 1, + friction: 8, + tension: 40, + useNativeDriver: true, + }).start(); + } else { + Animated.timing(scaleValue, { + toValue: isActive ? CARD_SORT_ACTIVE : globalDragActive ? INACTIVE_SCALE_WHEN_ACTIVE : 1, + duration: SCALE_DURATION, + useNativeDriver: true, + }).start(); + } + }, [isActive, globalDragActive, scaleValue, CARD_SORT_ACTIVE, INACTIVE_SCALE_WHEN_ACTIVE, SCALE_DURATION]); + + useEffect(() => { + if (isActive !== prevIsActive.current) { + triggerHapticFeedback(HapticFeedbackTypes.ImpactMedium); + } + prevIsActive.current = isActive; + + animateItemIn(); + }, [isActive, globalDragActive, animateItemIn]); + + const onPress = useCallback(() => { + if (item.type === ItemType.WalletSection) { + setIsLoading(true); + navigateToWallet(item.data); + setIsLoading(false); + } else if (item.type === ItemType.AddressSection && navigateToAddress) { + navigateToAddress(item.data.address, item.data.walletID); + } + }, [item, navigateToWallet, navigateToAddress]); + + const handleLeftPress = (reset: () => void) => { + handleToggleHideBalance(item.data as TWallet); + reset(); + }; + + const leftContent = (reset: () => void) => { + resetFunctionRef.current = reset; + return handleLeftPress(reset)} hideBalance={(item.data as TWallet).hideBalance} colors={colors} />; + }; + + const startDrag = useCallback(() => { + if (isSwipeActive) { + return; + } + + if (resetFunctionRef.current) { + resetFunctionRef.current(); + } + + scaleValue.setValue(CARD_SORT_ACTIVE); + triggerHapticFeedback(HapticFeedbackTypes.ImpactMedium); + if (drag) { + drag(); + } + }, [CARD_SORT_ACTIVE, drag, scaleValue, isSwipeActive]); + + if (isLoading) { + return ; + } + + if (item.type === ItemType.WalletSection) { + const animatedStyle = { + transform: [{ scale: scaleValue }], + marginVertical: globalDragActive && !isActive ? REDUCED_VERTICAL_MARGIN : DEFAULT_VERTICAL_MARGIN, + }; + + const backgroundColor = isActive || globalDragActive ? colors.brandingColor : colors.background; + + // Disable swiping only when search bar is focused or during active dragging + const swipeDisabled = isActive || globalDragActive || state.isSearchFocused === true; + + return ( + + { + if (!swipeDisabled) { + console.debug(`Swipe began: ${direction}`); + setIsSwipeActive(true); + } + }} + onSwipeEnd={() => { + if (!swipeDisabled) { + console.debug('Swipe ended'); + setIsSwipeActive(false); + } + }} + > + + + + + + ); + } 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 ? wallet.allowSignVerifyMessage() : false, + onPress: navigateToAddress ? () => navigateToAddress(item.data.address, item.data.walletID) : undefined, + 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) => JSX.Element; + isSearching: boolean; +} + +const WalletGroupComponent: React.FC = ({ + wallet, + transactions, + addresses, + state, + navigateToWallet, + navigateToAddress, + renderHighlightedText, + isSearching, +}) => { + const { colors } = useTheme(); + const [expanded] = useState(true); // Always show child items when searching + const fadeAnim = useRef(new Animated.Value(0)).current; + + useEffect(() => { + Animated.timing(fadeAnim, { + toValue: 1, + duration: 300, + useNativeDriver: true, + }).start(); + }, [fadeAnim]); + + const walletGradientColors = WalletGradient.gradientsFor(wallet.type); + const primaryColor = walletGradientColors[0]; + + const containerStyle: ViewStyle = { + marginHorizontal: 10, + marginVertical: 16, + borderRadius: 10, + overflow: 'hidden' as const, + backgroundColor: colors.elevated, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 3, + borderWidth: 1, + borderColor: primaryColor + '30', + }; + + const headerStyle: ViewStyle = { + padding: 12, + backgroundColor: primaryColor + '15', // Using translucent primary color as background + borderTopLeftRadius: 10, + borderTopRightRadius: 10, + borderTopWidth: Platform.OS === 'ios' ? 4 : 2, + borderTopColor: primaryColor, + paddingVertical: 12, + }; + + const childItemsContainerStyle = { + borderBottomLeftRadius: 10, + borderBottomRightRadius: 10, + backgroundColor: colors.elevated, + borderLeftWidth: 1, + borderRightWidth: 1, + borderBottomWidth: 1, + borderColor: primaryColor + '20', + }; + + const childItemStyle = (): ViewStyle => ({ + borderLeftWidth: 3, + borderLeftColor: primaryColor, + backgroundColor: colors.inputBackgroundColor, + }); + + const sectionHeaderStyle: ViewStyle = { + paddingHorizontal: 16, + paddingVertical: 8, + backgroundColor: primaryColor + '10', + borderTopWidth: 1, + borderBottomWidth: 1, + borderColor: primaryColor + '30', + }; + + const sectionHeaderTextStyle: TextStyle = { + color: colors.foregroundColor, + fontWeight: '600' as const, + fontSize: 14, + }; + + const dividerStyle = [styles.itemDivider, { backgroundColor: primaryColor + '20' }]; + + const onWalletPress = useCallback(() => { + navigateToWallet(wallet); + }, [navigateToWallet, wallet]); + + return ( + + + {/* Wallet Header */} + + + + + {/* Search results container */} + {expanded && ( + + {/* Transactions section */} + {transactions.length > 0 && ( + <> + + + {loc.addresses.transactions} ({transactions.length}) + + + {transactions.map((transaction, index) => ( + + + + + {index < transactions.length - 1 && } + + ))} + + )} + + {/* Addresses section */} + {addresses.length > 0 && ( + <> + + + {loc.addresses.addresses_title} ({addresses.length}) + + + {addresses.map((address, index) => { + const addressItemProps = { + item: { + key: address.data.address, + index: address.data.index, + address: address.data.address, + isInternal: address.data.isInternal, + balance: 0, + transactions: 0, + }, + balanceUnit: wallet.getPreferredBalanceUnit() || BitcoinUnit.BTC, + walletID: address.data.walletID, + allowSignVerifyMessage: wallet.allowSignVerifyMessage ? wallet.allowSignVerifyMessage() : false, + // Use the onPress function returned by navigateToAddress instead of calling it directly + onPress: navigateToAddress ? () => navigateToAddress(address.data.address, address.data.walletID) : undefined, + searchQuery: state.searchQuery, + renderHighlightedText, + }; + + return ( + + + + + {index < addresses.length - 1 && } + + ); + })} + + )} + + )} + + + ); +}; + +const styles = StyleSheet.create({ + leftButtonContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + carouselItem: { + width: '100%', + }, + transparentBackground: { + backgroundColor: 'transparent', + }, + itemDivider: { + height: 1, + width: '100%', + }, +}); + +export { LeftSwipeContent, WalletGroupComponent }; +export default ManageWalletsListItem; diff --git a/components/MultipleStepsListItem.js b/components/MultipleStepsListItem.tsx similarity index 55% rename from components/MultipleStepsListItem.js rename to components/MultipleStepsListItem.tsx index 9fde9226eae..d747950f2f6 100644 --- a/components/MultipleStepsListItem.js +++ b/components/MultipleStepsListItem.tsx @@ -1,19 +1,68 @@ -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 }); +import React, { useRef } from 'react'; +import { + ActivityIndicator, + findNodeHandle, + GestureResponderEvent, + Platform, + StyleProp, + StyleSheet, + Text, + Pressable, + View, + ViewStyle, +} from 'react-native'; +import { Icon } from '@rneui/themed'; +import ActionSheet from '../screen/ActionSheet'; +import { useTheme } from './themes'; +import { ActionSheetOptions } from '../screen/ActionSheet.common'; -const MultipleStepsListItem = props => { +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, + dashes = MultipleStepsListItemDashType.None, circledText = '', leftText = '', checked = false, + isActionSheet = false, + actionSheetOptions = null, // Default to null or appropriate default } = props; const stylesHook = StyleSheet.create({ provideKeyButton: { @@ -35,10 +84,33 @@ const MultipleStepsListItem = props => { 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); - const renderDashes = () => { + 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: + case MultipleStepsListItemDashType.TopAndBottom: return { width: 1, borderStyle: 'dashed', @@ -49,7 +121,7 @@ const MultipleStepsListItem = props => { marginLeft: 20, position: 'absolute', }; - case MultipleStepsListItemDashType.bottom: + case MultipleStepsListItemDashType.Bottom: return { width: 1, borderStyle: 'dashed', @@ -60,7 +132,7 @@ const MultipleStepsListItem = props => { marginLeft: 20, position: 'absolute', }; - case MultipleStepsListItemDashType.top: + case MultipleStepsListItemDashType.Top: return { width: 1, borderStyle: 'dashed', @@ -77,6 +149,7 @@ const MultipleStepsListItem = props => { }; 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 ( @@ -103,45 +176,68 @@ const MultipleStepsListItem = props => { {!showActivityIndicator && props.button && ( <> {props.button.buttonType === undefined || - (props.button.buttonType === MultipleStepsListItemButtohType.full && ( - [ + Platform.OS === 'ios' && pressed ? styles.pressed : null, + styles.provideKeyButton, + stylesHook.provideKeyButton, + buttonOpacity, + ]} + onPress={onPress} > {props.button.text} - + ))} - {props.button.buttonType === MultipleStepsListItemButtohType.partial && ( + {props.button.buttonType === MultipleStepsListItemButtonType.Partial && ( {props.button.leftText} - [ + Platform.OS === 'ios' && pressed ? styles.pressed : null, + styles.rowPartialRightButton, + stylesHook.provideKeyButton, + rightButtonOpacity, + ]} + onPress={onPress} > - - {props.button.text} - - + {props.button.showActivityIndicator ? ( + + ) : ( + + {props.button.text} + + )} + )} )} {!showActivityIndicator && props.rightButton && checked && ( - [pressed && styles.pressed, styles.rightButton]} onPress={props.rightButton.onPress} > - {props.rightButton.text} - + {props.rightButton.showActivityIndicator ? ( + + ) : ( + {props.rightButton.text} + )} + )} @@ -149,26 +245,6 @@ const MultipleStepsListItem = props => { ); }; -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', @@ -238,6 +314,9 @@ const styles = StyleSheet.create({ 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..cfa79ddfb2c --- /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: 20, + duration: 80, + easing: Easing.linear, + useNativeDriver: true, + }), + Animated.timing(shakeAnimation, { + toValue: -20, + duration: 80, + easing: Easing.linear, + useNativeDriver: true, + }), + Animated.timing(shakeAnimation, { + toValue: 20, + duration: 80, + easing: Easing.linear, + useNativeDriver: true, + }), + Animated.timing(shakeAnimation, { + toValue: 0, + duration: 80, + easing: Easing.linear, + 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/PromptPasswordConfirmationModal.tsx b/components/PromptPasswordConfirmationModal.tsx new file mode 100644 index 00000000000..514d610d984 --- /dev/null +++ b/components/PromptPasswordConfirmationModal.tsx @@ -0,0 +1,451 @@ +import React, { useState, useRef, forwardRef, useImperativeHandle, useEffect } from 'react'; +import { View, Text, TextInput, StyleSheet, Animated, Easing, ViewStyle, Keyboard, Platform, UIManager } from 'react-native'; +import BottomModal, { BottomModalHandle } from './BottomModal'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { SecondButton } from './SecondButton'; +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; +import { useKeyboard } from '../hooks/useKeyboard'; + +if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) { + UIManager.setLayoutAnimationEnabledExperimental(true); +} + +export const MODAL_TYPES = { + ENTER_PASSWORD: 'ENTER_PASSWORD', + CREATE_PASSWORD: 'CREATE_PASSWORD', + CREATE_FAKE_STORAGE: 'CREATE_FAKE_STORAGE', + SUCCESS: 'SUCCESS', +} as const; + +export type ModalType = (typeof MODAL_TYPES)[keyof typeof MODAL_TYPES]; + +interface PromptPasswordConfirmationModalProps { + modalType: ModalType; + onConfirmationSuccess: (password: string) => Promise; + onConfirmationFailure: () => void; +} + +export interface PromptPasswordConfirmationModalHandle { + present: () => Promise; + dismiss: () => Promise; +} + +const PromptPasswordConfirmationModal = forwardRef( + ({ modalType, onConfirmationSuccess, onConfirmationFailure }, ref) => { + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + const [showExplanation, setShowExplanation] = useState(false); // State to toggle between explanation and password input for CREATE_PASSWORD and CREATE_FAKE_STORAGE + const modalRef = useRef(null); + const fadeOutAnimation = useRef(new Animated.Value(1)).current; + const fadeInAnimation = useRef(new Animated.Value(0)).current; + const scaleAnimation = useRef(new Animated.Value(1)).current; + const shakeAnimation = useRef(new Animated.Value(0)).current; + const explanationOpacity = useRef(new Animated.Value(1)).current; + const { colors } = useTheme(); + const passwordInputRef = useRef(null); + const confirmPasswordInputRef = useRef(null); + const { isVisible } = useKeyboard(); + + const stylesHook = StyleSheet.create({ + modalContent: { + backgroundColor: colors.elevated, + width: '100%', + }, + input: { + backgroundColor: colors.inputBackgroundColor, + borderColor: colors.formBorder, + color: colors.foregroundColor, + width: '100%', + }, + feeModalCustomText: { + color: colors.buttonAlternativeTextColor, + }, + feeModalLabel: { + color: colors.successColor, + }, + }); + + useImperativeHandle(ref, () => ({ + present: async () => { + resetState(); + modalRef.current?.present(); + if (modalType === MODAL_TYPES.CREATE_PASSWORD || (modalType === MODAL_TYPES.CREATE_FAKE_STORAGE && !showExplanation)) { + passwordInputRef.current?.focus(); + } else if (modalType === MODAL_TYPES.ENTER_PASSWORD) { + passwordInputRef.current?.focus(); + } + }, + dismiss: async () => { + await modalRef.current?.dismiss(); + resetState(); + }, + })); + + const resetState = () => { + setPassword(''); + setConfirmPassword(''); + setIsSuccess(false); + setIsLoading(false); + fadeOutAnimation.setValue(1); + fadeInAnimation.setValue(0); + scaleAnimation.setValue(1); + shakeAnimation.setValue(0); + explanationOpacity.setValue(1); + setShowExplanation(modalType === MODAL_TYPES.CREATE_PASSWORD); + }; + + useEffect(() => { + resetState(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [modalType]); + + const performShake = (shakeAnimRef: Animated.Value) => { + Animated.sequence([ + Animated.timing(shakeAnimRef, { + toValue: 10, + duration: 100, + easing: Easing.inOut(Easing.ease), + useNativeDriver: true, + }), + Animated.timing(shakeAnimRef, { + toValue: -10, + duration: 100, + easing: Easing.inOut(Easing.ease), + useNativeDriver: true, + }), + Animated.timing(shakeAnimRef, { + toValue: 5, + duration: 100, + easing: Easing.inOut(Easing.ease), + useNativeDriver: true, + }), + Animated.timing(shakeAnimRef, { + toValue: -5, + duration: 100, + easing: Easing.inOut(Easing.ease), + useNativeDriver: true, + }), + Animated.timing(shakeAnimRef, { + toValue: 0, + duration: 100, + easing: Easing.inOut(Easing.ease), + useNativeDriver: true, + }), + ]).start(); + }; + + const handleShakeAnimation = () => { + performShake(shakeAnimation); + }; + + const handleSuccessAnimation = () => { + // Step 1: Cross-fade current content out and success content in + Animated.timing(fadeOutAnimation, { + toValue: 0, // Fade out current content + duration: 300, + easing: Easing.out(Easing.ease), + useNativeDriver: true, + }).start(() => { + setIsSuccess(true); + + Animated.timing(fadeInAnimation, { + toValue: 1, // Fade in success content + duration: 300, + easing: Easing.out(Easing.ease), + useNativeDriver: true, + }).start(() => { + // Step 2: Perform any additional animations like scaling if necessary + Animated.timing(scaleAnimation, { + toValue: 1.1, + duration: 300, + easing: Easing.out(Easing.ease), + useNativeDriver: true, + }).start(() => { + Animated.timing(scaleAnimation, { + toValue: 1, // Return scale to normal size + duration: 300, + easing: Easing.out(Easing.ease), + useNativeDriver: true, + }).start(() => { + // Optional delay before dismissing the modal + setTimeout(async () => { + await modalRef.current?.dismiss(); + }, 1000); + }); + }); + }); + }); + }; + + const handleConfirmationFailure = () => { + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); + if (!isSuccess) handleShakeAnimation(); + onConfirmationFailure(); + }; + + const handleConfirmSuccess = () => { + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); + handleSuccessAnimation(); + }; + + const handleSubmit = async () => { + Keyboard.dismiss(); + setIsLoading(true); + let success = false; + + try { + if (modalType === MODAL_TYPES.CREATE_PASSWORD || modalType === MODAL_TYPES.CREATE_FAKE_STORAGE) { + if (password === confirmPassword && password) { + success = await onConfirmationSuccess(password); + success ? handleConfirmSuccess() : handleConfirmationFailure(); + } else { + handleConfirmationFailure(); + } + } else if (modalType === MODAL_TYPES.ENTER_PASSWORD) { + success = await onConfirmationSuccess(password); + success ? handleConfirmSuccess() : handleConfirmationFailure(); + } + } finally { + setIsLoading(false); // Ensure loading state is reset + if (success) { + // Ensure shake animation is reset before starting the success animation + shakeAnimation.setValue(0); + } + } + }; + + const handleTransitionToCreatePassword = () => { + Animated.timing(explanationOpacity, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start(() => { + setShowExplanation(false); + explanationOpacity.setValue(1); // Reset opacity for when transitioning back + passwordInputRef.current?.focus(); + }); + }; + + const handleCancel = async () => { + onConfirmationFailure(); + await modalRef.current?.dismiss(); + }; + + const animatedViewStyle: Animated.WithAnimatedObject = { + opacity: fadeOutAnimation, + transform: [{ scale: scaleAnimation }], + width: '100%', + }; + + const onModalDismiss = () => { + resetState(); + onConfirmationFailure(); + }; + + const opacity = isVisible ? 0 : 1; + return ( + + + + ) : ( + + + + ) + ) : null + } + > + {!isSuccess && ( + + {modalType === MODAL_TYPES.CREATE_PASSWORD && showExplanation && ( + + {loc.settings.encrypt_storage_explanation_headline} + + + {loc.settings.encrypt_storage_explanation_description_line1} + + + {loc.settings.encrypt_storage_explanation_description_line2} + + + + + )} + {(modalType === MODAL_TYPES.ENTER_PASSWORD || + ((modalType === MODAL_TYPES.CREATE_PASSWORD || modalType === MODAL_TYPES.CREATE_FAKE_STORAGE) && !showExplanation)) && ( + <> + + {modalType === MODAL_TYPES.CREATE_PASSWORD + ? loc.settings.password_explain + : modalType === MODAL_TYPES.CREATE_FAKE_STORAGE + ? `${loc.settings.password_explain} ${loc.plausibledeniability.create_password_explanation}` + : loc._.enter_password} + + + + + + {(modalType === MODAL_TYPES.CREATE_PASSWORD || modalType === MODAL_TYPES.CREATE_FAKE_STORAGE) && ( + + + + )} + + + )} + + )} + {isSuccess && ( + + + + + ✔️ + + + + + )} + + ); + }, +); + +export default PromptPasswordConfirmationModal; + +const styles = StyleSheet.create({ + modalContent: { + padding: 22, + width: '100%', + justifyContent: 'center', + alignItems: 'center', + }, + minHeight: { + minHeight: 420, + }, + feeModalFooter: { + padding: 16, + }, + feeModalFooterSpacing: { + padding: 16, + marginVertical: 24, + }, + inputContainer: { + marginBottom: 10, + width: '100%', + }, + input: { + borderRadius: 4, + padding: 8, + marginVertical: 8, + fontSize: 16, + width: '100%', + }, + textLabel: { + fontSize: 20, + fontWeight: '600', + marginBottom: 16, + textAlign: 'center', + }, + description: { + fontSize: 16, + marginBottom: 12, + textAlign: 'center', + }, + successContainer: { + justifyContent: 'center', + alignItems: 'center', + margin: 24, + marginBottom: 48, + }, + circle: { + width: 60, + height: 60, + borderRadius: 30, + backgroundColor: 'green', + justifyContent: 'center', + alignItems: 'center', + }, + checkmark: { + color: 'white', + fontSize: 30, + }, +}); diff --git a/components/QRCodeComponent.tsx b/components/QRCodeComponent.tsx index 4af4d21fbc2..f0bac9a2349 100644 --- a/components/QRCodeComponent.tsx +++ b/components/QRCodeComponent.tsx @@ -1,11 +1,14 @@ -import React, { useRef } from 'react'; -import { View, StyleSheet, Platform } from 'react-native'; +import Clipboard from '@react-native-clipboard/clipboard'; +import React, { useCallback, useRef } from 'react'; +import { Platform, StyleSheet, View } 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'; +import { ActionIcons } from '../typings/ActionIcons'; +import { useTheme } from './themes'; +import ToolTipMenu from './TooltipMenu'; +import { Action } from './types'; interface QRCodeComponentProps { value: string; @@ -17,43 +20,40 @@ interface QRCodeComponentProps { 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 BORDER_WIDTH = 6; const actionIcons: { [key: string]: ActionIcons } = { Share: { - iconType: 'SYSTEM', iconValue: 'square.and.arrow.up', }, Copy: { - iconType: 'SYSTEM', iconValue: 'doc.on.doc', }, }; +const actionKeys = { + Share: 'share', + Copy: 'copy', +}; + +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.Copy, + text: loc.transactions.details_copy, + icon: actionIcons.Copy, + }, + ]; + const QRCodeComponent: React.FC = ({ value = '', isLogoRendered = true, @@ -64,7 +64,7 @@ const QRCodeComponent: React.FC = ({ onError = () => {}, }) => { const qrCode = useRef(); - const { colors } = useTheme(); + const { colors, dark } = useTheme(); const handleShareQRCode = () => { qrCode.current.toDataURL((data: string) => { @@ -72,43 +72,31 @@ const QRCodeComponent: React.FC = ({ const shareImageBase64 = { url: `data:image/png;base64,${data}`, }; - Share.open(shareImageBase64).catch((error: any) => console.log(error)); + Share.open(shareImageBase64).catch((error: Error) => console.log(error)); }); }; - const onPressMenuItem = (id: string) => { + const onPressMenuItem = useCallback((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; - }; + // Adjust the size of the QR code to account for the border width + const newSize = dark ? size - BORDER_WIDTH * 2 : size; + const stylesHook = StyleSheet.create({ + container: { borderWidth: dark ? BORDER_WIDTH : 0 }, + }); const renderQRCode = ( = ({ ); return ( - + {isMenuAvailable ? ( - + {renderQRCode} ) : ( @@ -133,5 +128,5 @@ const QRCodeComponent: React.FC = ({ export default QRCodeComponent; const styles = StyleSheet.create({ - qrCodeContainer: { borderWidth: 6, borderRadius: 8, borderColor: '#FFFFFF' }, + container: { borderColor: '#FFFFFF' }, }); diff --git a/components/ReplaceFeeSuggestions.tsx b/components/ReplaceFeeSuggestions.tsx new file mode 100644 index 00000000000..bbec0ad5d64 --- /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 '../BlueComponents'; +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..2adff5b40a3 --- /dev/null +++ b/components/SafeArea.tsx @@ -0,0 +1,47 @@ +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'; +} + +const SafeArea = (props: SafeAreaProps) => { + const { style, floatingButtonHeight, ...otherProps } = props; + const { colors } = useTheme(); + const insets = useSafeAreaInsets(); + + const padding = useMemo( + () => + props.orientation === 'portrait' + ? { + paddingTop: insets.top, + paddingBottom: insets.bottom, + } + : { + paddingTop: insets.top, + paddingBottom: insets.bottom + (floatingButtonHeight ?? 0), + paddingLeft: insets.left, + paddingRight: insets.right, + }, + [insets, props.orientation, floatingButtonHeight], + ); + + 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..adce3a1be35 --- /dev/null +++ b/components/SaveFileButton.tsx @@ -0,0 +1,80 @@ +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); + onMenuWillHide?: () => void; + onMenuWillShow?: () => void; +} + +const SaveFileButton: React.FC = ({ + fileName, + fileContent, + children, + style, + beforeOnPress, + afterOnPress, + onMenuWillHide, + onMenuWillShow, +}) => { + 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..eb3fb414ff3 --- /dev/null +++ b/components/SecondButton.tsx @@ -0,0 +1,83 @@ +import React, { forwardRef } from 'react'; +import { StyleSheet, Text, TouchableOpacity, View, ActivityIndicator } from 'react-native'; +import { Icon } from '@rneui/themed'; + +import { useTheme } from './themes'; + +type IconProps = { + name: string; + type: string; + color: string; +}; + +type SecondButtonProps = { + backgroundColor?: string; + disabled?: boolean; + icon?: IconProps; + title?: 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 = 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/SegmentControl.tsx b/components/SegmentControl.tsx new file mode 100644 index 00000000000..ebcee31deec --- /dev/null +++ b/components/SegmentControl.tsx @@ -0,0 +1,56 @@ +import React, { useMemo } from 'react'; +import { requireNativeComponent, View, StyleSheet, NativeSyntheticEvent } from 'react-native'; + +interface SegmentedControlProps { + values: string[]; + selectedIndex: number; + onChange: (index: number) => void; +} + +interface SegmentedControlEvent { + selectedIndex: number; +} + +interface NativeSegmentedControlProps { + values: string[]; + selectedIndex: number; + onChangeEvent: (event: NativeSyntheticEvent) => void; + style?: object; +} + +const NativeSegmentedControl = requireNativeComponent('CustomSegmentedControl'); + +const SegmentedControl: React.FC = ({ values, selectedIndex, onChange }) => { + const handleChange = useMemo( + () => (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: 18, + 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..e17d02c5388 --- /dev/null +++ b/components/SettingsBlockExplorerCustomUrlListItem.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { StyleSheet, TextInput, View, Switch } from 'react-native'; +import { ListItem } from '@rneui/themed'; +import { useTheme } from './themes'; +import loc from '../loc'; + +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(); + + return ( + + + + {loc.settings.block_explorer_preferred} + + + + + {isCustomEnabled && ( + + + + )} + + ); +}; + +export default SettingsBlockExplorerCustomUrlItem; + +const styles = StyleSheet.create({ + container: { + minHeight: 60, + paddingVertical: 10, + }, + title: { + fontSize: 16, + fontWeight: '500', + }, + uriContainer: { + flexDirection: 'row', + borderWidth: 1, + borderRadius: 4, + marginHorizontal: 15, + marginVertical: 10, + paddingHorizontal: 10, + alignItems: 'center', + }, + 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.tsx similarity index 68% rename from components/SquareEnumeratedWords.js rename to components/SquareEnumeratedWords.tsx index 680ab840e67..b2667fbbc04 100644 --- a/components/SquareEnumeratedWords.js +++ b/components/SquareEnumeratedWords.tsx @@ -1,15 +1,22 @@ import React from 'react'; -import { useTheme } from '@react-navigation/native'; -import { TouchableOpacity, Text, StyleSheet, View } from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -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; +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: { @@ -18,6 +25,9 @@ const SquareEnumeratedWords = props => { entryText: { color: colors.labelText, }, + container: { + justifyContent: contentAlign, + }, }); const renderSecret = () => { @@ -50,7 +60,7 @@ const SquareEnumeratedWords = props => { return component; }; - return {renderSecret()}; + return {renderSecret()}; }; const styles = StyleSheet.create({ @@ -72,10 +82,5 @@ const styles = StyleSheet.create({ flexWrap: 'wrap', }, }); -SquareEnumeratedWords.propTypes = { - entries: PropTypes.arrayOf(PropTypes.string), - contentAlign: PropTypes.string, - appendNumber: PropTypes.bool, -}; export default SquareEnumeratedWords; diff --git a/components/Button.js b/components/StyledButton.tsx similarity index 60% rename from components/Button.js rename to components/StyledButton.tsx index ac2c1a82ae5..74dad06d3b6 100644 --- a/components/Button.js +++ b/components/StyledButton.tsx @@ -1,11 +1,18 @@ -import React from 'react'; -import { TouchableOpacity, View, Text, StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; -import { useTheme } from '@react-navigation/native'; +import React, { FC } from 'react'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -export const ButtonStyle = { default: 'default', destroy: 'destroy', grey: 'grey' }; -const Button = props => { - const { onPress, text = '', disabled = false, buttonStyle = ButtonStyle.default } = props; +import { useTheme } from './themes'; + +export const StyledButtonType: Record = { default: 'default', destroy: 'destroy', grey: 'grey' }; + +interface StyledButtonProps { + onPress: () => void; + text: string; + disabled?: boolean; + buttonStyle?: keyof typeof StyledButtonType; +} + +const StyledButton: FC = ({ onPress, text, disabled = false, buttonStyle = StyledButtonType.default }) => { const { colors } = useTheme(); const stylesHook = StyleSheet.create({ buttonGrey: { @@ -14,11 +21,14 @@ const Button = props => { textGray: { color: colors.buttonTextColor, }, + container: { + opacity: disabled ? 0.5 : 1.0, + }, }); const textStyles = () => { - if (buttonStyle === ButtonStyle.grey) { + if (buttonStyle === StyledButtonType.grey) { return stylesHook.textGray; - } else if (buttonStyle === ButtonStyle.destroy) { + } else if (buttonStyle === StyledButtonType.destroy) { return styles.textDestroy; } else { return styles.textDefault; @@ -26,19 +36,17 @@ const Button = props => { }; const buttonStyles = () => { - if (buttonStyle === ButtonStyle.grey) { + if (buttonStyle === StyledButtonType.grey) { return stylesHook.buttonGrey; - } else if (buttonStyle === ButtonStyle.destroy) { + } else if (buttonStyle === StyledButtonType.destroy) { return styles.buttonDestroy; } else { return styles.buttonDefault; } }; - const opacity = { opacity: disabled ? 0.5 : 1.0 }; - return ( - + {text} @@ -76,10 +84,4 @@ const styles = StyleSheet.create({ }, }); -export default Button; -Button.propTypes = { - onPress: PropTypes.func.isRequired, - text: PropTypes.string.isRequired, - disabled: PropTypes.bool, - buttonStyle: PropTypes.string, -}; +export default StyledButton; 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..f4940829c7a --- /dev/null +++ b/components/TipBox.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { View, StyleSheet, ViewStyle } from 'react-native'; +import { useTheme } from './themes'; +import { BlueText } from '../BlueComponents'; + +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.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..4d0a6519d39 --- /dev/null +++ b/components/TooltipMenu.tsx @@ -0,0 +1,136 @@ +import React, { useCallback, useMemo } from 'react'; +import { Platform, TouchableOpacity } from 'react-native'; +import { MenuView, MenuAction, NativeActionEvent } from '@react-native-menu/menu'; +import { ToolTipMenuProps, Action } from './types'; +import { useSettings } from '../hooks/context/useSettings'; + +const ToolTipMenu = (props: ToolTipMenuProps) => { + const { + title = '', + isMenuPrimaryAction = false, + disabled = false, + onPress, + buttonStyle, + onPressMenuItem, + children, + isButton = false, + ...restProps + } = props; + + const { language } = useSettings(); + + // Map Menu Items for RN Menu (supports subactions and displayInline) + const mapMenuItemForMenuView = useCallback((action: Action): MenuAction | null => { + if (!action.id) return null; + + // Check for subactions + const subactions = + action.subactions?.map(subaction => { + const subMenuItem: MenuAction = { + id: subaction.id.toString(), + title: subaction.text, + subtitle: subaction.subtitle, + image: subaction.icon?.iconValue ? subaction.icon.iconValue : undefined, + attributes: { disabled: subaction.disabled, destructive: subaction.destructive, hidden: subaction.hidden }, + }; + if ('menuState' in subaction) { + subMenuItem.state = subaction.menuState ? 'on' : 'off'; + } + if (subaction.subactions && subaction.subactions.length > 0) { + const deepSubactions = subaction.subactions.map(deepSub => { + const deepMenuItem: MenuAction = { + id: deepSub.id.toString(), + title: deepSub.text, + subtitle: deepSub.subtitle, + image: deepSub.icon?.iconValue ? deepSub.icon.iconValue : undefined, + attributes: { disabled: deepSub.disabled, destructive: deepSub.destructive, hidden: deepSub.hidden }, + }; + if ('menuState' in deepSub) { + deepMenuItem.state = deepSub.menuState ? 'on' : 'off'; + } + return deepMenuItem; + }); + subMenuItem.subactions = deepSubactions; + } + return subMenuItem; + }) || []; + + const menuItem: MenuAction = { + id: action.id.toString(), + title: action.text, + subtitle: action.subtitle, + image: action.icon?.iconValue ? action.icon.iconValue : undefined, + attributes: { disabled: action.disabled, destructive: action.destructive, hidden: action.hidden }, + displayInline: action.displayInline || false, + }; + if ('menuState' in action) { + menuItem.state = action.menuState ? 'on' : 'off'; + } + if (subactions.length > 0) { + menuItem.subactions = subactions; + } + return menuItem; + }, []); + + const menuViewItemsIOS = useMemo(() => { + return props.actions + .map(actionGroup => { + if (Array.isArray(actionGroup) && actionGroup.length > 0) { + return { + id: actionGroup[0].id.toString(), + title: '', + subactions: actionGroup + .filter(action => action.id) + .map(mapMenuItemForMenuView) + .filter(item => item !== null) as MenuAction[], + displayInline: true, + }; + } else if (!Array.isArray(actionGroup) && actionGroup.id) { + return mapMenuItemForMenuView(actionGroup); + } + return null; + }) + .filter(item => item !== null) as MenuAction[]; + }, [props.actions, mapMenuItemForMenuView]); + + const menuViewItemsAndroid = useMemo(() => { + const mergedActions = props.actions.flat().filter(action => action.id); + return mergedActions.map(mapMenuItemForMenuView).filter(item => item !== null) as MenuAction[]; + }, [props.actions, mapMenuItemForMenuView]); + + const handlePressMenuItemForMenuView = useCallback( + ({ nativeEvent }: NativeActionEvent) => { + onPressMenuItem(nativeEvent.event); + }, + [onPressMenuItem], + ); + + const renderMenuView = () => { + return ( + + {isMenuPrimaryAction || isButton ? ( + + {children} + + ) : ( + children + )} + + ); + }; + + return props.actions.length > 0 ? renderMenuView() : null; +}; + +export default ToolTipMenu; diff --git a/components/TotalWalletsBalance.tsx b/components/TotalWalletsBalance.tsx new file mode 100644 index 00000000000..230ec9461b0 --- /dev/null +++ b/components/TotalWalletsBalance.tsx @@ -0,0 +1,133 @@ +import React, { useMemo, useCallback } from 'react'; +import { TouchableOpacity, Text, StyleSheet, LayoutAnimation, View } from 'react-native'; +import { useStorage } from '../hooks/context/useStorage'; +import loc, { formatBalanceWithoutSuffix } from '../loc'; +import { BitcoinUnit } from '../models/bitcoinUnits'; +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) => { + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + 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 () => { + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + const nextUnit = + totalBalancePreferredUnit === BitcoinUnit.BTC + ? BitcoinUnit.SATS + : totalBalancePreferredUnit === BitcoinUnit.SATS + ? BitcoinUnit.LOCAL_CURRENCY + : BitcoinUnit.BTC; + await setTotalBalancePreferredUnitStorage(nextUnit); + }, [totalBalancePreferredUnit, setTotalBalancePreferredUnitStorage]); + + if (wallets.length <= 1 || !isTotalBalanceEnabled) return null; + + return ( + + + {loc.wallets.total_balance} + + + {totalBalanceFormatted}{' '} + {totalBalancePreferredUnit !== BitcoinUnit.LOCAL_CURRENCY && ( + {totalBalancePreferredUnit} + )} + + + + + ); +}); + +const styles = StyleSheet.create({ + container: { + flexDirection: 'column', + alignItems: 'flex-start', + padding: 16, + }, + label: { + fontSize: 14, + marginBottom: 4, + 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..b90c4514630 --- /dev/null +++ b/components/TransactionListItem.tsx @@ -0,0 +1,439 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState, memo } from 'react'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import Clipboard from '@react-native-clipboard/clipboard'; +import { uint8ArrayToHex } from '../blue_modules/uint8array-extras'; +import { Linking, View, ViewStyle, StyleSheet } 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, transactionTimeToReadable } from '../loc'; +import { BitcoinUnit } from '../models/bitcoinUnits'; +import { useSettings } from '../hooks/context/useSettings'; +import ListItem from './ListItem'; +import { useTheme } from './themes'; +import { Action, ToolTipMenuProps } 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 HighlightedText from './HighlightedText'; + +const styles = StyleSheet.create({ + subtitle: { + color: 'colors.foregroundColor', + fontSize: 13, + }, + highlight: { + backgroundColor: '#FFF5C0', + color: '#000000', + fontSize: 13, + fontWeight: '600', + }, +}); + +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) => JSX.Element; + onPress?: () => void; +} + +type NavigationProps = NativeStackNavigationProp; + +export const TransactionListItem: React.FC = memo( + ({ + item, + itemPriceUnit = BitcoinUnit.BTC, + walletID, + searchQuery, + style, + renderHighlightedText, + onPress: customOnPress, + }: TransactionListItemProps) => { + const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1); + const { colors } = useTheme(); + const { navigate } = useExtendedNavigation(); + const menuRef = useRef(); + const { txMetadata, counterpartyMetadata, wallets } = useStorage(); + const { language, selectedBlockExplorer } = useSettings(); + const insets = useSafeAreaInsets(); + const containerStyle = useMemo( + () => ({ + backgroundColor: colors.background, + borderBottomColor: colors.lightBorder, + paddingLeft: 16, + + paddingRight: 16, + }), + [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); + }; + + const title = useMemo(() => { + if (item.confirmations === 0) { + return loc.transactions.pending; + } else { + return transactionTimeToReadable(item.timestamp); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [item.confirmations, item.timestamp, language]); + + let counterparty; + if (item.counterparty) { + counterparty = counterpartyMetadata?.[item.counterparty]?.label ?? item.counterparty; + } + const txMemo = (counterparty ? `[${shortenContactName(counterparty)}] ` : '') + (txMetadata[item.hash]?.memo ?? ''); + const subtitle = useMemo(() => { + let sub = Number(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 || undefined; + }, [txMemo, item.confirmations, item.memo]); + + const formattedAmount = useMemo(() => { + return formatBalanceWithoutSuffix(item.value && 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', + textAlign: 'right', + paddingRight: insets.right, + paddingLeft: insets.left, + }; + }, [ + 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]); + + useEffect(() => { + setSubtitleNumberOfLines(1); + }, [subtitle]); + + const onPress = useCallback(async () => { + menuRef?.current?.dismissMenu?.(); + // If a custom onPress handler was provided, use it and return + if (customOnPress) { + customOnPress(); + return; + } + + if (item.hash) { + if (renderHighlightedText) { + pop(); + } + 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 = uint8ArrayToHex(new Uint8Array((paymentHash as any).data)); + } + const loaded = await LN.loadSuccessfulPayment(paymentHash); + if (loaded) { + navigate('ScanLNDInvoiceRoot', { + screen: 'LnurlPaySuccess', + params: { + paymentHash, + 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]); + + const handleOnExpandNote = useCallback(() => { + setSubtitleNumberOfLines(0); + }, []); + + const handleOnDetailsPress = useCallback(() => { + if (walletID && item && item.hash) { + navigate('TransactionDetails', { tx: item, hash: item.hash, walletID }); + } 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(subtitle ?? ''), [subtitle]); + 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.ExpandNote.id) { + handleOnExpandNote(); + } 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, + handleOnExpandNote, + handleOnViewOnBlockExplorer, + ], + ); + const toolTipActions = useMemo((): Action[] => { + const actions: (Action | Action[])[] = [ + { + ...CommonToolTipActions.CopyAmount, + hidden: rowTitle === loc.lnd.expired, + }, + { + ...CommonToolTipActions.CopyNote, + hidden: !subtitle, + }, + { + ...CommonToolTipActions.CopyTXID, + hidden: !item.hash, + }, + { + ...CommonToolTipActions.CopyBlockExplorerLink, + hidden: !item.hash, + }, + [{ ...CommonToolTipActions.OpenInBlockExplorer, hidden: !item.hash }, CommonToolTipActions.Details], + [ + { + ...CommonToolTipActions.ExpandNote, + hidden: subtitleNumberOfLines !== 1, + }, + ], + ]; + + return actions as Action[]; + }, [rowTitle, subtitle, item.hash, subtitleNumberOfLines]); + + const accessibilityState = useMemo(() => { + return { + expanded: subtitleNumberOfLines === 0, + }; + }, [subtitleNumberOfLines]); + + return ( + + + ) + ) : undefined + } + Component={View} + chevron={false} + rightTitle={rowTitle} + rightTitleStyle={rowTitleStyle} + containerStyle={combinedStyle} + testID="TransactionListItem" + /> + + ); + }, + (prevProps, nextProps) => { + return ( + prevProps.item.hash === nextProps.item.hash && + prevProps.item.timestamp === nextProps.item.timestamp && + prevProps.itemPriceUnit === nextProps.itemPriceUnit && + prevProps.walletID === nextProps.walletID && + prevProps.searchQuery === nextProps.searchQuery + ); + }, +); 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..d30dff2ad12 --- /dev/null +++ b/components/TransactionPendingIconBig.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { Icon } from '@rneui/themed'; + +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..ef65ab5f287 100644 --- a/components/TransactionsNavigationHeader.tsx +++ b/components/TransactionsNavigationHeader.tsx @@ -1,55 +1,46 @@ -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, useState } from 'react'; import Clipboard from '@react-native-clipboard/clipboard'; +import { ImageBackground, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; -import { AbstractWallet, HDSegwitBech32Wallet, LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class'; -import { BitcoinUnit } from '../models/bitcoinUnits'; +import { LightningArkWallet, LightningCustodianWallet, MultisigHDWallet } from '../class'; 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; } 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, }) => { - const [wallet, setWallet] = useState(initialWallet); + const { hideBalance } = wallet; const [allowOnchainAddress, setAllowOnchainAddress] = useState(false); - - const context = useContext(BlueStorageContext); - const menuRef = useRef(null); + const { preferredFiatCurrency } = useSettings(); + const { direction } = useLocale(); const verifyIfWalletAllowsOnchainAddress = useCallback(() => { - if (wallet.type === LightningCustodianWallet.type) { + if (wallet.type === LightningCustodianWallet.type || wallet.type === LightningArkWallet.type) { 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); }); } @@ -59,39 +50,18 @@ const TransactionsNavigationHeader: React.FC 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(); if (newWalletPreferredUnit === BitcoinUnit.BTC) { @@ -102,157 +72,154 @@ const TransactionsNavigationHeader: React.FC newWalletPreferredUnit = BitcoinUnit.BTC; } - const updatedWallet = updateWalletWithNewUnit(wallet, newWalletPreferredUnit); - setWallet(updatedWallet); - onWalletUnitChange?.(updatedWallet); + 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 onPressMenuItem = (id: string) => { - if (id === 'walletBalanceVisibility') { - handleBalanceVisibility(); - } else if (id === 'copyToClipboard') { - 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 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 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]); + + const imageSource = useMemo(() => { + switch (wallet.type) { + 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'); } - }; + }, [direction, wallet.type]); - 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()]); + useAnimateOnChange(balance); + useAnimateOnChange(hideBalance); + useAnimateOnChange(unit); + useAnimateOnChange(wallet.getID?.()); 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 ? ( - - ) : ( - - {balance} - - )} - - - {wallet.type === LightningCustodianWallet.type && allowOnchainAddress && ( + + + + {hideBalance ? ( + + ) : ( + + + {balance} + + + )} + + + + + {unit === BitcoinUnit.LOCAL_CURRENCY ? (preferredFiatCurrency?.endPointKey ?? FiatUnit.USD) : unit} + + + + {(wallet.type === LightningCustodianWallet.type || wallet.type === LightningArkWallet.type) && allowOnchainAddress && ( {loc.lnd.title} )} - - {wallet.allowBIP47() && wallet.isBIP47Enabled() && ( - - - {loc.bip47.payment_code} - - - )} - {wallet.type === LightningLdkWallet.type && ( - - - {loc.lnd.title} - - - )} {wallet.type === MultisigHDWallet.type && ( - - - {loc.multisig.manage_keys} - + handleManageFundsPressed()}> + {loc.multisig.manage_keys} )} @@ -276,14 +243,11 @@ const styles = StyleSheet.create({ 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, @@ -301,34 +265,52 @@ const styles = StyleSheet.create({ 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/WalletToImport.js b/components/WalletToImport.tsx similarity index 68% rename from components/WalletToImport.js rename to components/WalletToImport.tsx index bfd14dfc538..6e6b3ca81d8 100644 --- a/components/WalletToImport.js +++ b/components/WalletToImport.tsx @@ -1,11 +1,20 @@ 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'; +import { StyleSheet, TouchableOpacity, View } from 'react-native'; +import { Text } from '@rneui/themed'; +import { useLocale } from '@react-navigation/native'; -const WalletToImport = ({ title, subtitle, active, onPress }) => { +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: { @@ -14,11 +23,11 @@ const WalletToImport = ({ title, subtitle, active, onPress }) => { }, title: { color: colors.newBlue, - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + writingDirection: direction, }, subtitle: { color: colors.alternativeTextColor, - writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + writingDirection: direction, }, }); @@ -55,11 +64,4 @@ const styles = StyleSheet.create({ }, }); -WalletToImport.propTypes = { - title: PropTypes.string, - subtitle: PropTypes.string, - active: PropTypes.bool, - onPress: PropTypes.func, -}; - 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..e0d7b23df72 --- /dev/null +++ b/components/WalletsCarousel.tsx @@ -0,0 +1,764 @@ +import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useEffect, createRef } from 'react'; +import { + Animated, + FlatList, + ImageBackground, + Platform, + Pressable, + StyleSheet, + Text, + useWindowDimensions, + View, + FlatListProps, + ListRenderItemInfo, + ViewStyle, + LayoutAnimation, + UIManager, +} from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import { LightningArkWallet, LightningCustodianWallet, MultisigHDWallet } from '../class'; +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 { useStorage } from '../hooks/context/useStorage'; +import { WalletTransactionsStatus } from './Context/StorageProvider'; +import { Transaction, TWallet } from '../class/wallets/types'; +import HighlightedText from './HighlightedText'; +import { BlueSpacing10 } from './BlueSpacing'; +import { useLocale } from '@react-navigation/native'; + +if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) { + UIManager.setLayoutAnimationEnabledExperimental(true); +} + +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 = width * 0.82 > 375 ? 375 : width * 0.82; + const { isLarge } = useSizeClass(); + const nStylesHooks = StyleSheet.create({ + container: isLarge + ? { + paddingHorizontal: 24, + marginVertical: 16, + } + : { paddingVertical: 16, paddingHorizontal: 24 }, + }); + + const scale = useRef(new Animated.Value(1)).current; + + const handlePressIn = useCallback(() => { + Animated.spring(scale, { + toValue: 0.97, + useNativeDriver: true, + friction: 4, + }).start(); + }, [scale]); + + const handlePressOut = useCallback(() => { + Animated.spring(scale, { + toValue: 1, + useNativeDriver: true, + friction: 4, + }).start(); + }, [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; + onPress: (item: TWallet) => void; + handleLongPress?: () => void; + isSelectedWallet?: boolean; + customStyle?: ViewStyle; + horizontal?: boolean; + isPlaceHolder?: boolean; + searchQuery?: string; + renderHighlightedText?: (text: string, query: string) => JSX.Element; + animationsEnabled?: boolean; + onPressIn?: () => void; + onPressOut?: () => void; + isNewWallet?: boolean; + isExiting?: boolean; +} + +const iStyles = StyleSheet.create({ + root: { paddingRight: 20 }, + rootLargeDevice: { marginVertical: 20 }, + grad: { + padding: 15, + borderRadius: 12, + minHeight: 164, + }, + balanceContainer: { + height: 40, + }, + image: { + width: 99, + height: 94, + position: 'absolute', + bottom: 0, + right: 0, + }, + br: { + backgroundColor: 'transparent', + }, + label: { + backgroundColor: 'transparent', + fontSize: 19, + }, + balance: { + backgroundColor: 'transparent', + fontWeight: 'bold', + fontSize: 36, + }, + latestTx: { + backgroundColor: 'transparent', + fontSize: 13, + }, + latestTxTime: { + backgroundColor: 'transparent', + fontWeight: 'bold', + fontSize: 16, + }, + shadowContainer: { + ...Platform.select({ + ios: { + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 25 / 100, + shadowRadius: 8, + borderRadius: 12, + }, + android: { + elevation: 8, + borderRadius: 12, + }, + }), + }, +}); + +export const WalletCarouselItem: React.FC = React.memo( + ({ + item, + onPress, + handleLongPress, + isSelectedWallet, + customStyle, + horizontal, + searchQuery, + renderHighlightedText, + animationsEnabled = true, + isPlaceHolder = false, + onPressIn, + onPressOut, + isNewWallet = false, + isExiting = false, + }: WalletCarouselItemProps) => { + const scaleValue = useRef(new Animated.Value(1.0)).current; + const opacityValue = useRef(new Animated.Value(isSelectedWallet === false ? 0.5 : 1.0)).current; + const translateYValue = useRef(new Animated.Value(isNewWallet ? 20 : 0)).current; + const { colors } = useTheme(); + const { walletTransactionUpdateStatus } = useStorage(); + const { width } = useWindowDimensions(); + const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; + const { sizeClass } = useSizeClass(); + const { direction } = useLocale(); + + const springConfig = useMemo(() => ({ useNativeDriver: true, tension: 100 }), []); + const animateScale = useCallback( + (toValue: number, callback?: () => void) => { + Animated.spring(scaleValue, { toValue, ...springConfig }).start(callback); + }, + [scaleValue, springConfig], + ); + + useEffect(() => { + if (!animationsEnabled) return; + + const targetOpacity = isSelectedWallet === false ? 0.5 : 1.0; + Animated.spring(opacityValue, { + toValue: targetOpacity, + useNativeDriver: true, + tension: 30, + friction: 7, + velocity: 0.1, + }).start(); + }, [isSelectedWallet, opacityValue, animationsEnabled]); + + const onPressedIn = useCallback(() => { + if (animationsEnabled) { + animateScale(0.95); + } + if (onPressIn) onPressIn(); + }, [animateScale, animationsEnabled, onPressIn]); + + const onPressedOut = useCallback(() => { + if (animationsEnabled) { + animateScale(1.0); + } + if (onPressOut) onPressOut(); + }, [animateScale, animationsEnabled, onPressOut]); + + const handlePress = useCallback(() => { + onPress(item); + }, [item, onPress]); + + useEffect(() => { + if (isNewWallet && animationsEnabled) { + Animated.parallel([ + Animated.timing(translateYValue, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }), + Animated.spring(opacityValue, { + toValue: isSelectedWallet === false ? 0.5 : 1.0, + useNativeDriver: true, + friction: 7, + }), + ]).start(); + } + }, [isNewWallet, animationsEnabled, translateYValue, opacityValue, isSelectedWallet]); + + useEffect(() => { + if (isExiting && animationsEnabled) { + Animated.parallel([ + Animated.timing(translateYValue, { + toValue: -20, + duration: 200, + useNativeDriver: true, + }), + Animated.timing(opacityValue, { + toValue: 0, + duration: 200, + useNativeDriver: true, + }), + ]).start(); + } + }, [isExiting, animationsEnabled, translateYValue, opacityValue]); + + 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 (walletTransactionUpdateStatus === WalletTransactionsStatus.ALL || walletTransactionUpdateStatus === item.getID()) { + latestTransactionText = loc.transactions.updating; + } else 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()); + } + + const balance = !item.hideBalance && formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true); + + return ( + + { + if (handleLongPress) handleLongPress(); + }} + onPress={handlePress} + delayHoverIn={0} + delayHoverOut={0} + > + + + + + {!isPlaceHolder && ( + <> + + {renderHighlightedText && searchQuery ? ( + + ) : ( + item.getLabel() + )} + + + {item.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) => JSX.Element; + 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: 16, + 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(() => (width * 0.82 > 375 ? 375 : width * 0.82), [width]); + + const prevDataLength = useRef(data.length); + 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, y, widthVal, heightVal, pageX, pageY) => { + 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()] = createRef(); + } + }); + }, [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; + prevDataLength.current = data.length; + 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; + }); + + // Always animate layout changes + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + + // 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); + } + + // Handle wallet removals + if (prevDataLength.current > data.length) { + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + } + + // Update refs for next comparison + prevWalletIds.current = currentWalletIds; + prevDataLength.current = data.length; + + // 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, index }: ListRenderItemInfo) => + item ? ( + + ) : null, + [horizontal, selectedWallet, handleLongPress, onPress, searchQuery, renderHighlightedText, animateChanges], + ); + + 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, index) => + item ? ( + { + if (walletRefs.current[item.getID()]?.current && newWalletsMap.current[item.getID()]) { + walletRefs.current[item.getID()].current?.measure((x, y, widthVal, heightVal, pageX, pageY) => { + console.debug(`[WalletsCarousel] New wallet ${item.getID()} positioned at y=${y}, pageY=${pageY}`); + }); + } + }} + > + + + ) : null, + ); + }, [data, horizontal, selectedWallet, handleLongPress, onPress, props.searchQuery, props.renderHighlightedText, animateChanges]); + + 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..aa61040abd9 --- /dev/null +++ b/components/WatchOnlyWarning.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; +import { Icon } from '@rneui/themed'; +import loc from '../loc'; + +interface Props { + handleDismiss: () => void; +} + +const WatchOnlyWarning: React.FC = ({ handleDismiss }) => { + return ( + + + + + + + {loc.transactions.watchOnlyWarningTitle} + {loc.transactions.watchOnlyWarningDescription} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: '#fc990e', + padding: 16, + margin: 16, + borderRadius: 8, + position: 'relative', + }, + dismissButton: { + position: 'absolute', + top: 8, + right: 8, + backgroundColor: 'black', + borderRadius: 15, + width: 30, + height: 30, + justifyContent: 'center', + alignItems: 'center', + zIndex: 1, + }, + content: { + alignItems: 'center', + }, + title: { + color: 'white', + fontWeight: 'bold', + marginBottom: 8, + marginTop: 8, + }, + description: { + color: 'white', + textAlign: 'center', + }, +}); + +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..e831bb92c72 --- /dev/null +++ b/components/addresses/AddressItem.tsx @@ -0,0 +1,244 @@ +import React, { useMemo, useCallback } from 'react'; +import Clipboard from '@react-native-clipboard/clipboard'; +import { StyleSheet, Text, View } from 'react-native'; +import { ListItem } from '@rneui/themed'; +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 QRCodeComponent from '../QRCodeComponent'; +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'; + +interface AddressItemProps { + item: any; + balanceUnit: BitcoinUnit; + walletID: string; + allowSignVerifyMessage: boolean; + onPress?: () => void; // example: ManageWallets uses this + searchQuery?: string; + renderHighlightedText?: (text: string, query: string) => JSX.Element; +} + +type NavigationProps = NativeStackNavigationProp; + +const AddressItem = ({ + item, + balanceUnit, + walletID, + allowSignVerifyMessage, + onPress, + searchQuery = '', + renderHighlightedText, +}: AddressItemProps) => { + const { wallets } = useStorage(); + const { colors } = useTheme(); + const { isBiometricUseCapableAndEnabled } = useBiometrics(); + + const hasTransactions = item.transactions > 0; + + const stylesHook = StyleSheet.create({ + container: { + borderBottomColor: colors.lightBorder, + backgroundColor: colors.elevated, + }, + + index: { + color: colors.alternativeTextColor, + }, + balance: { + color: colors.alternativeTextColor, + }, + address: { + color: hasTransactions ? colors.darkGray : colors.buttonTextColor, + }, + }); + + 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); + + 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], + ); + + const renderPreview = useCallback(() => , [item.address]); + + // 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({ + address: { + fontWeight: 'bold', + marginHorizontal: 4, + }, + index: { + fontSize: 15, + }, + balance: { + marginTop: 4, + }, + row: { + flexDirection: 'row', + alignItems: 'center', + }, + leftSection: { + marginRight: 8, + }, + middleSection: { + flex: 1, + }, + rightContainer: { + justifyContent: 'center', + alignItems: 'flex-end', + }, +}); + +export { AddressItem }; diff --git a/components/addresses/AddressTypeBadge.js b/components/addresses/AddressTypeBadge.tsx similarity index 74% rename from components/addresses/AddressTypeBadge.js rename to components/addresses/AddressTypeBadge.tsx index 8b60d1ad2a4..05b8f647d1c 100644 --- a/components/addresses/AddressTypeBadge.js +++ b/components/addresses/AddressTypeBadge.tsx @@ -1,23 +1,12 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { useTheme } from '@react-navigation/native'; -import { StyleSheet, View, Text } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; + import loc, { formatStringAddTwoWhiteSpaces } from '../../loc'; +import { useTheme } from '../themes'; -const styles = StyleSheet.create({ - container: { - paddingVertical: 4, - paddingHorizontal: 10, - borderRadius: 20, - alignSelf: 'flex-end', - }, - badgeText: { - fontSize: 12, - textAlign: 'center', - }, -}); +type Props = { isInternal: boolean; hasTransactions: boolean }; -const AddressTypeBadge = ({ isInternal, hasTransactions }) => { +export const AddressTypeBadge: React.FC = ({ isInternal, hasTransactions }) => { const { colors } = useTheme(); const stylesHook = StyleSheet.create({ @@ -32,8 +21,8 @@ const AddressTypeBadge = ({ isInternal, hasTransactions }) => { const badgeLabel = hasTransactions ? loc.addresses.type_used : isInternal - ? formatStringAddTwoWhiteSpaces(loc.addresses.type_change) - : formatStringAddTwoWhiteSpaces(loc.addresses.type_receive); + ? formatStringAddTwoWhiteSpaces(loc.addresses.type_change) + : formatStringAddTwoWhiteSpaces(loc.addresses.type_receive); // eslint-disable-next-line prettier/prettier const badgeStyle = hasTransactions @@ -56,9 +45,15 @@ const AddressTypeBadge = ({ isInternal, hasTransactions }) => { ); }; -AddressTypeBadge.propTypes = { - isInternal: PropTypes.bool, - hasTransactions: PropTypes.bool, -}; - -export { AddressTypeBadge }; +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/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..63ad9ab059e --- /dev/null +++ b/components/icons/SettingsButton.tsx @@ -0,0 +1,56 @@ +import React, { useCallback, useMemo } from 'react'; +import { StyleSheet, TouchableOpacity } from 'react-native'; +import { Icon } from '@rneui/themed'; +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: 30, + height: 30, + borderRadius: 15, + justifyContent: 'center', + alignContent: 'center', + }, +}); diff --git a/components/icons/TransactionExpiredIcon.js b/components/icons/TransactionExpiredIcon.tsx similarity index 63% rename from components/icons/TransactionExpiredIcon.js rename to components/icons/TransactionExpiredIcon.tsx index 37cc9c7f73e..e9fa28bd4af 100644 --- a/components/icons/TransactionExpiredIcon.js +++ b/components/icons/TransactionExpiredIcon.tsx @@ -1,26 +1,25 @@ import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import { Icon } from '@rneui/themed'; + +import { useTheme } from '../themes'; const styles = StyleSheet.create({ boxIncoming: { position: 'relative', - }, + } as ViewStyle, ballOutgoingExpired: { width: 30, height: 30, borderRadius: 15, justifyContent: 'center', - }, - icon: { - left: 0, - top: 0, - }, + alignItems: 'center', + } as ViewStyle, }); -const TransactionExpiredIcon = props => { +const TransactionExpiredIcon: React.FC = () => { const { colors } = useTheme(); + const stylesHooks = StyleSheet.create({ ballOutgoingExpired: { backgroundColor: colors.ballOutgoingExpired, @@ -30,7 +29,7 @@ const TransactionExpiredIcon = props => { return ( - + ); diff --git a/components/icons/TransactionIncomingIcon.js b/components/icons/TransactionIncomingIcon.tsx similarity index 73% rename from components/icons/TransactionIncomingIcon.js rename to components/icons/TransactionIncomingIcon.tsx index 65aa45eea02..74a9ea4bb3a 100644 --- a/components/icons/TransactionIncomingIcon.js +++ b/components/icons/TransactionIncomingIcon.tsx @@ -1,23 +1,26 @@ import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import { Icon } from '@rneui/themed'; + +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 = props => { +const TransactionIncomingIcon: React.FC = () => { const { colors } = useTheme(); + const stylesHooks = StyleSheet.create({ ballIncoming: { backgroundColor: colors.ballReceive, diff --git a/components/icons/TransactionOffchainIcon.js b/components/icons/TransactionOffchainIcon.tsx similarity index 67% rename from components/icons/TransactionOffchainIcon.js rename to components/icons/TransactionOffchainIcon.tsx index a1f78e9c8b4..318d113dba5 100644 --- a/components/icons/TransactionOffchainIcon.js +++ b/components/icons/TransactionOffchainIcon.tsx @@ -1,25 +1,25 @@ import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import { Icon } from '@rneui/themed'; + +import { useTheme } from '../themes'; const styles = StyleSheet.create({ boxIncoming: { position: 'relative', - }, + } as ViewStyle, ballOutgoingWithoutRotate: { width: 30, height: 30, borderRadius: 15, - }, - icon: { - left: 0, - marginTop: 6, - }, + justifyContent: 'center', + alignItems: 'center', + } as ViewStyle, }); -const TransactionOffchainIcon = props => { +const TransactionOffchainIcon: React.FC = () => { const { colors } = useTheme(); + const stylesHooks = StyleSheet.create({ ballOutgoingWithoutRotate: { backgroundColor: colors.ballOutgoing, @@ -29,7 +29,7 @@ const TransactionOffchainIcon = props => { return ( - + ); diff --git a/components/icons/TransactionOffchainIncomingIcon.js b/components/icons/TransactionOffchainIncomingIcon.tsx similarity index 66% rename from components/icons/TransactionOffchainIncomingIcon.js rename to components/icons/TransactionOffchainIncomingIcon.tsx index d78359fcf48..13f04578712 100644 --- a/components/icons/TransactionOffchainIncomingIcon.js +++ b/components/icons/TransactionOffchainIncomingIcon.tsx @@ -1,25 +1,25 @@ import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import { Icon } from '@rneui/themed'; + +import { useTheme } from '../themes'; const styles = StyleSheet.create({ boxIncoming: { position: 'relative', - }, + } as ViewStyle, ballIncomingWithoutRotate: { width: 30, height: 30, borderRadius: 15, - }, - icon: { - left: 0, - marginTop: 6, - }, + justifyContent: 'center', + alignItems: 'center', + } as ViewStyle, }); -const TransactionOffchainIncomingIcon = props => { +const TransactionOffchainIncomingIcon: React.FC = () => { const { colors } = useTheme(); + const stylesHooks = StyleSheet.create({ ballIncomingWithoutRotate: { backgroundColor: colors.ballReceive, @@ -29,7 +29,7 @@ const TransactionOffchainIncomingIcon = props => { return ( - + ); diff --git a/components/icons/TransactionOnchainIcon.js b/components/icons/TransactionOnchainIcon.tsx similarity index 66% rename from components/icons/TransactionOnchainIcon.js rename to components/icons/TransactionOnchainIcon.tsx index 785e425e1c0..019fb5836ba 100644 --- a/components/icons/TransactionOnchainIcon.js +++ b/components/icons/TransactionOnchainIcon.tsx @@ -1,28 +1,26 @@ import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import { Icon } from '@rneui/themed'; + +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', - }, - icon: { - left: 0, - top: 0, - transform: [{ rotate: '-45deg' }], - }, + alignItems: 'center', + } as ViewStyle, }); -const TransactionOnchainIcon = props => { +const TransactionOnchainIcon: React.FC = () => { const { colors } = useTheme(); + const stylesBlueIconHooks = StyleSheet.create({ ballIncoming: { backgroundColor: colors.ballReceive, @@ -32,7 +30,7 @@ const TransactionOnchainIcon = props => { return ( - + ); diff --git a/components/icons/TransactionOutgoingIcon.js b/components/icons/TransactionOutgoingIcon.tsx similarity index 74% rename from components/icons/TransactionOutgoingIcon.js rename to components/icons/TransactionOutgoingIcon.tsx index 2ae3c92bd8f..1fc1c3eaf37 100644 --- a/components/icons/TransactionOutgoingIcon.js +++ b/components/icons/TransactionOutgoingIcon.tsx @@ -1,23 +1,26 @@ import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import { Icon } from '@rneui/themed'; + +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 = props => { +const TransactionOutgoingIcon: React.FC = () => { const { colors } = useTheme(); + const stylesBlueIconHooks = StyleSheet.create({ ballOutgoing: { backgroundColor: colors.ballOutgoing, diff --git a/components/icons/TransactionPendingIcon.js b/components/icons/TransactionPendingIcon.tsx similarity index 64% rename from components/icons/TransactionPendingIcon.js rename to components/icons/TransactionPendingIcon.tsx index ddd5c9c6398..fc841549c2e 100644 --- a/components/icons/TransactionPendingIcon.js +++ b/components/icons/TransactionPendingIcon.tsx @@ -1,25 +1,25 @@ import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Icon } from 'react-native-elements'; -import { useTheme } from '@react-navigation/native'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import { Icon } from '@rneui/themed'; + +import { useTheme } from '../themes'; const styles = StyleSheet.create({ boxIncoming: { position: 'relative', - }, + } as ViewStyle, ball: { width: 30, height: 30, borderRadius: 15, - }, - icon: { - left: 0, - top: 7, - }, + justifyContent: 'center', + alignItems: 'center', + } as ViewStyle, }); -const TransactionPendingIcon = props => { +const TransactionPendingIcon: React.FC = () => { const { colors } = useTheme(); + const stylesHook = StyleSheet.create({ ball: { backgroundColor: colors.buttonBackgroundColor, @@ -29,7 +29,7 @@ const TransactionPendingIcon = props => { return ( - + ); diff --git a/components/navigationStyle.tsx b/components/navigationStyle.tsx index f312242e1ca..96ce32e1dad 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,98 @@ 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, + onCloseButtonPressed, ...opts - }: NavigationOptions & { - closeButton?: boolean; - - closeButtonFunc?: (deps: { navigation: any; route: any }) => React.ReactElement; + }: NativeStackNavigationOptions & { + closeButtonPosition?: 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 = 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,63 +111,24 @@ 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; - -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, }; @@ -135,3 +139,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..1573320b880 --- /dev/null +++ b/components/platform/index.tsx @@ -0,0 +1,706 @@ +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/MaterialIcons'; +import Ionicons from 'react-native-vector-icons/Ionicons'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import FontAwesome5 from 'react-native-vector-icons/FontAwesome5'; + +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: isIOS ? 44 : 56, + 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 getSettingsHeaderOptions = ( + title: string, + colors: { text?: string; foregroundColor?: string; background?: string } = platformColors, +) => { + const headerTextColor = 'text' in colors ? colors.text : colors.foregroundColor; + const headerBackgroundColor = 'background' in colors ? colors.background : platformColors.background; + + 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, ...rest } = props; + const insets = useSafeAreaInsets(); + const resolvedHeaderHeight = useMemo(() => headerHeight ?? getSettingsHeaderHeight(insets.top), [headerHeight, insets.top]); + + 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, ...rest } = props; + const insets = useSafeAreaInsets(); + const resolvedHeaderHeight = useMemo(() => headerHeight ?? getSettingsHeaderHeight(insets.top), [headerHeight, insets.top]); + + 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 renderVectorIcon = (icon: IconProps) => { + const size = platformSizing.iconInnerSize; + const color = icon.color ?? 'black'; + switch (icon.type) { + case 'ionicon': + return ; + case 'material': + return ; + case 'material-community': + return ; + default: + 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; +} + +const isIconProps = (icon: IconProps | React.ReactElement): icon is IconProps => 'name' in icon; + +const usePlatformStyles = () => { + const { colors } = useTheme(); + return useMemo(() => { + const card = colors.lightButton ?? colors.modal ?? colors.elevated ?? colors.background; + 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 : card, + 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 : card }, + 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]); +}; + +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, +}) => { + const theme = useTheme(); + const { colors: themeColors, dark } = theme; + const { fontScale } = useWindowDimensions(); + + const themeStyles = usePlatformStyles(); + 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: platformColors.text, + fontSize: platformSizing.titleFontSize, + fontWeight: platformSizing.titleFontWeight, + writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + }, + subtitle: { + flexWrap: 'wrap', + writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', + color: platformColors.secondaryText, + fontWeight: platformSizing.subtitleFontWeight, + paddingVertical: platformSizing.subtitlePaddingVertical, + lineHeight: platformSizing.subtitleLineHeight * fontScale, + fontSize: platformSizing.subtitleFontSize, + }, + container: { + backgroundColor: platformColors.card, + paddingVertical: isAndroid ? 8 : platformSizing.containerPaddingVertical, + paddingHorizontal: platformSizing.horizontalPadding, + minHeight, + borderBottomWidth: platformLayout.showBorderBottom && !isLast ? StyleSheet.hairlineWidth : 0, + borderBottomColor: platformLayout.showBorderBottom && !isLast ? platformColors.separator : 'transparent', + borderRadius: platformLayout.showBorderRadius ? platformSizing.containerBorderRadius : 0, + elevation: platformLayout.showElevation ? platformSizing.containerElevation : 0, + marginVertical: isAndroid ? 0 : platformSizing.containerMarginVertical, + }, + chevron: { + color: platformColors.chevron, + opacity: 0.7, + }, + }) as const, + [fontScale, minHeight, isLast], + ); + + 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: platformColors.card, + }; + + const outerContainerStyle = [ + containerStyle, + dynamicContainerStyle, + { + backgroundColor: dynamicStyles.container.backgroundColor, + borderBottomWidth: dynamicStyles.container.borderBottomWidth, + borderBottomColor: dynamicStyles.container.borderBottomColor, + borderRadius: dynamicStyles.container.borderRadius, + 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..7dcea549d9d 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', @@ -65,6 +69,8 @@ export const BlueDefaultTheme = { changeText: '#F38C47', receiveBackground: '#D1F9D6', receiveText: '#37C0A1', + navigationBarColor: '#FFFFFF', + androidRippleColor: '#CCCCCC', }, }; @@ -81,6 +87,7 @@ export const BlueDarkTheme: Theme = { customHeader: '#000000', brandingColor: '#000000', borderTopColor: '#9aa0aa', + background: '#000000', foregroundColor: '#ffffff', buttonDisabledBackgroundColor: '#3A3A3C', buttonBackgroundColor: '#3A3A3C', @@ -120,12 +127,23 @@ export const BlueDarkTheme: Theme = { changeText: '#F38C47', receiveBackground: 'rgba(210,248,214,.2)', receiveText: '#37C0A1', + navigationBarColor: '#3A3A3C', + androidRippleColor: '#444444', }, }; // 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 +154,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..cca1824eccd --- /dev/null +++ b/components/types.ts @@ -0,0 +1,56 @@ +import { AccessibilityRole, ViewStyle, ColorValue } 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; + dismissMenu?: () => void; + onPressMenuItem: (id: string) => void; + title?: string; + isMenuPrimaryAction?: boolean; + isButton?: boolean; + renderPreview?: () => React.ReactNode; + onPress?: () => void; + previewValue?: string; + accessibilityRole?: AccessibilityRole; + disabled?: boolean; + testID?: string; + style?: ViewStyle | ViewStyle[]; + accessibilityLabel?: string; + accessibilityHint?: string; + accessibilityState?: object; + buttonStyle?: ViewStyle | ViewStyle[]; + onMenuWillShow?: () => void; + onMenuWillHide?: () => void; +} + +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..dbaf2e46b85 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,1275 @@ +# 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 + +default_platform(:android) +project_root = File.expand_path("..", __dir__) + +# 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| + skip_auth_lanes = ['register_devices_from_txt'] + + # Check if we need App Store Connect for this lane + unless skip_auth_lanes.include?(lane) + 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 + keystore_file_hex = ENV['KEYSTORE_FILE_HEX'] + UI.user_error!("KEYSTORE_FILE_HEX environment variable is missing") if keystore_file_hex.nil? + + UI.message("Creating keystore from HEX...") + File.write("bluewallet-release-key.keystore.hex", keystore_file_hex) + + sh("xxd -plain -revert bluewallet-release-key.keystore.hex > bluewallet-release-key.keystore") do |status| + UI.user_error!("Error reverting hex to keystore") unless status.success? + end + UI.message("Keystore created successfully.") + + File.delete("bluewallet-release-key.keystore.hex") + end + end + + desc "Update version, build number, and sign APK" + lane :update_version_build_and_sign_apk do + Dir.chdir(project_root) do + build_number = ENV['BUILD_NUMBER'] + UI.user_error!("BUILD_NUMBER environment variable is missing") if build_number.nil? + + # Extract versionName from build.gradle + version_name = sh("grep versionName android/app/build.gradle | awk '{print $2}' | tr -d '\"'").strip + UI.user_error!("Failed to extract versionName from build.gradle") if version_name.nil? || version_name.empty? + + # Update versionCode in build.gradle + UI.message("Updating versionCode in build.gradle to #{build_number}...") + build_gradle_path = "android/app/build.gradle" + build_gradle_contents = File.read(build_gradle_path) + new_build_gradle_contents = build_gradle_contents.gsub(/versionCode\s+\d+/, "versionCode #{build_number}") + File.write(build_gradle_path, new_build_gradle_contents) + + # Determine branch name and sanitize it + branch_name = ENV['GITHUB_HEAD_REF'] || `git rev-parse --abbrev-ref HEAD`.strip + branch_name = branch_name.gsub(/[^a-zA-Z0-9_-]/, '_') # Replace non-alphanumeric characters with underscore + branch_name = 'master' if branch_name.nil? || branch_name.empty? + + # Define APK name based on branch + signed_apk_name = branch_name != 'master' ? + "BlueWallet-#{version_name}-#{build_number}-#{branch_name}.apk" : + "BlueWallet-#{version_name}-#{build_number}.apk" + + # Define paths + unsigned_apk_path = "android/app/build/outputs/apk/release/app-release-unsigned.apk" + signed_apk_path = "android/app/build/outputs/apk/release/#{signed_apk_name}" + + # Build APK + UI.message("Building APK...") + sh("cd android && ./gradlew assembleRelease --no-daemon") + UI.message("APK build completed.") + + # Rename APK + if File.exist?(unsigned_apk_path) + UI.message("Renaming APK to #{signed_apk_name}...") + FileUtils.mv(unsigned_apk_path, signed_apk_path) + ENV['APK_OUTPUT_PATH'] = File.expand_path(signed_apk_path) + else + UI.error("Unsigned APK not found at path: #{unsigned_apk_path}") + next + end + + # Sign APK + UI.message("Signing APK with apksigner...") + apksigner_path = Dir.glob("#{ENV['ANDROID_HOME']}/build-tools/*/apksigner").sort.last + UI.user_error!("apksigner not found in Android build-tools") if apksigner_path.nil? || apksigner_path.empty? + sh("#{apksigner_path} sign --ks #{project_root}/bluewallet-release-key.keystore --ks-pass=pass:#{ENV['KEYSTORE_PASSWORD']} #{signed_apk_path}") + UI.message("APK signed successfully: #{signed_apk_path}") + end + 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 + # Determine APK path + 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 + + # Upload to BrowserStack + 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'] + ) + + # Extract BrowserStack URL + app_url = ENV['BROWSERSTACK_LIVE_APP_ID'] + UI.user_error!("BrowserStack upload failed, no app URL returned") if app_url.nil? || app_url.empty? + + # Prepare PR comment + apk_filename = File.basename(apk_path) + apk_download_url = ENV['APK_OUTPUT_PATH'] # Ensure this path is accessible + 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 + + # Delete Previous BrowserStack Comments + if pr_number + begin + repo = ENV['GITHUB_REPOSITORY'] # Format: "owner/repo" + 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 + + # Post New Comment to PR + 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 + + +# =========================== +# 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 + + desc "Fetch development certificates and provisioning profiles for Mac Catalyst" + lane :fetch_dev_profiles_catalyst do + match( + type: "development", + platform: "catalyst", + app_identifier: 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: app_identifiers, + readonly: true, + clone_branch_directly: true + ) + end + + desc "Setup provisioning profiles for Mac Catalyst" + lane :setup_catalyst_provisioning_profiles do + 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 "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/ios/fastlane/metadata/ja/keywords.txt b/fastlane/metadata/ios/ja/keywords.txt similarity index 57% rename from ios/fastlane/metadata/ja/keywords.txt rename to fastlane/metadata/ios/ja/keywords.txt index 45c637c91a2..dab2d7b0fe7 100644 --- a/ios/fastlane/metadata/ja/keywords.txt +++ b/fastlane/metadata/ios/ja/keywords.txt @@ -1 +1 @@ -ビットコイン,ウォレット,ビットコインウォレット,ブロックチェーン,btc,仮想通貨,暗号通貨,electrum,イーサリアム +ビットコイン,ウォレット,ビットコインウォレット,ブロックチェーン,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..abfe9f94c4e --- /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'; +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..92fadd080a0 100644 --- a/helpers/prompt.ts +++ b/helpers/prompt.ts @@ -2,7 +2,7 @@ import { Platform } from 'react-native'; import prompt from 'react-native-prompt-android'; import loc from '../loc'; -module.exports = ( +export default ( title: string, text: string, isCancelable = true, diff --git a/helpers/scan-qr.ts b/helpers/scan-qr.ts index 433bbed633c..e7337d8a18f 100644 --- a/helpers/scan-qr.ts +++ b/helpers/scan-qr.ts @@ -1,39 +1,36 @@ -/** - * 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 lastScanWasBBQR = 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) => { + lastScanWasBBQR = useBBQR; + resolve(data); + }, + }); + } }); }; -export {}; +const getLastScanWasBBQR = () => lastScanWasBBQR; +const resetLastScanWasBBQR = () => { + lastScanWasBBQR = false; +}; + +export { isCameraAuthorizationStatusGranted, requestCameraAuthorization, scanQrHelper, getLastScanWasBBQR, resetLastScanWasBBQR }; 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..434e000d127 --- /dev/null +++ b/hooks/useAnimateOnChange.ts @@ -0,0 +1,13 @@ +import { useEffect, useRef } from 'react'; +import { LayoutAnimation } from 'react-native'; + +const useAnimateOnChange = (value: T) => { + const prevValue = useRef(undefined); + useEffect(() => { + if (prevValue.current !== undefined && prevValue.current !== value) { + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + } + prevValue.current = value; + }, [value]); +}; +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..9738bda3392 --- /dev/null +++ b/hooks/useCompanionListeners.ts @@ -0,0 +1,347 @@ +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'; +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 RNQRGenerator from 'rn-qr-generator'; +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(); + 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 qrResult; + try { + qrResult = await RNQRGenerator.detect({ uri: decodedUrl }); + } catch (e) { + console.error('QR detection first attempt failed:', e); + } + if (!qrResult || !qrResult.values || qrResult.values.length === 0) { + const altUrl = decodedUrl.replace(/^file:\/\//, ''); + try { + qrResult = await RNQRGenerator.detect({ uri: altUrl }); + } catch (e) { + console.error('QR detection second attempt failed:', e); + } + } + if (qrResult?.values?.length) { + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); + DeeplinkSchemaMatch.navigationRouteFor( + { url: qrResult.values[0] }, + (value: [string, any]) => navigationRef.navigate(...value), + { + wallets, + addWallet, + saveToDisk, + setSharedCosigner, + }, + ); + } else { + throw new Error(loc.send.qr_error_no_qrcode); + } + } 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..14e8a315a9b --- /dev/null +++ b/hooks/useDeviceQuickActions.ts @@ -0,0 +1,163 @@ +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); + 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 => { + 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) { + if (DeeplinkSchemaMatch.hasSchema(url)) { + handleOpenURL({ url }); + } + } + } + }; + + 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..ee34101003b --- /dev/null +++ b/hooks/useHandoffListener.ios.ts @@ -0,0 +1,67 @@ +import { useEffect, useCallback } from 'react'; +import { NativeEventEmitter, NativeModules } from 'react-native'; +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 = NativeModules.EventEmitter; +const eventEmitter = EventEmitter ? new NativeEventEmitter(EventEmitter) : 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 (EventEmitter && EventEmitter.getMostRecentUserActivity) { + EventEmitter.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..cf06b90d3fe --- /dev/null +++ b/hooks/useMenuElements.ios.ts @@ -0,0 +1,93 @@ +import { useEffect, useCallback, useRef } from 'react'; +import { NativeEventEmitter, NativeModules, Platform } from 'react-native'; +import { navigationRef } from '../NavigationService'; + +type MenuActionHandler = () => void; + +const { MenuElementsEmitter } = NativeModules; +let eventEmitter: NativeEventEmitter | null = null; +const handlerRegistry = new Map(); + +try { + if (Platform.OS === 'ios' && MenuElementsEmitter) { + eventEmitter = new NativeEventEmitter(MenuElementsEmitter); + if (typeof MenuElementsEmitter.sharedInstance === 'function') { + MenuElementsEmitter.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..533ff4f8b1f --- /dev/null +++ b/hooks/useScreenProtect.ts @@ -0,0 +1,29 @@ +import { CaptureProtection, useCaptureProtection } from 'react-native-capture-protection'; +import { isDesktop } from '../blue_modules/environment'; + +export const useScreenProtect = () => { + const { protectionStatus, status } = useCaptureProtection(); + + const enableScreenProtect = () => { + if (isDesktop) return; + CaptureProtection.prevent(); + }; + + const disableScreenProtect = async () => { + if (isDesktop) return; + await CaptureProtection.allow(); + }; + + const isScreenBeingRecorded = async () => { + if (isDesktop) return false; + return await CaptureProtection.isScreenRecording(); + }; + + return { + enableScreenProtect, + disableScreenProtect, + isScreenBeingRecorded, + protectionStatus, + status, + }; +}; \ No newline at end of file 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..3e8a48e7266 --- /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(); + 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..f83c7e931a9 --- /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'; +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/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/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/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..9e4aa2db028 100644 --- a/index.js +++ b/index.js @@ -1,25 +1,31 @@ -import React, { useEffect } from 'react'; +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(); }, []); - return ( - - - - ); + return ; }; AppRegistry.registerComponent('BlueWallet', () => BlueAppComponent); diff --git a/ios/BlueWallet-Bridging-Header.h b/ios/BlueWallet-Bridging-Header.h index 1b2cb5d6d09..eba1403e24f 100644 --- a/ios/BlueWallet-Bridging-Header.h +++ b/ios/BlueWallet-Bridging-Header.h @@ -1,4 +1,10 @@ // -// 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 +#import "RNQuickActionManager.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..d33f4e4550d 100644 --- a/ios/BlueWallet.xcodeproj/project.pbxproj +++ b/ios/BlueWallet.xcodeproj/project.pbxproj @@ -3,35 +3,31 @@ 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 */; }; + 17CDA0718F42DB2CE856C872 /* libPods-BlueWallet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 040819EDF8BD9C50A9C83E24 /* libPods-BlueWallet.a */; }; 32B5A32A2334450100F8D608 /* Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5A3292334450100F8D608 /* Bridge.swift */; }; 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 +35,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 +44,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 +54,116 @@ 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 /* CustomSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B3EC202D69FF6C00327F3D /* CustomSegmentedControl.swift */; }; + B4B3EC252D69FF8700327F3D /* EventEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B3EC232D69FF8700327F3D /* EventEmitter.swift */; }; + 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 */; }; + B4D899942DCAE67700B959AA /* CustomSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = B4D899932DCAE67700B959AA /* CustomSegmentedControl.m */; }; 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 */; }; + C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -96,13 +188,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 +198,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,49 +221,28 @@ 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 = ""; }; @@ -209,8 +273,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 +281,52 @@ 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,14 +335,55 @@ 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 = ""; }; + 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 /* CustomSegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CustomSegmentedControl.swift; path = SegmentedControl/CustomSegmentedControl.swift; sourceTree = ""; }; + B4B3EC232D69FF8700327F3D /* EventEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventEmitter.swift; sourceTree = ""; }; + 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 = ""; }; + B4D899932DCAE67700B959AA /* CustomSegmentedControl.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomSegmentedControl.m; sourceTree = ""; }; + B4EFF73A2C3F6C5E0095D655 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; 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 = ""; }; @@ -302,7 +395,6 @@ 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 +402,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 +413,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 +438,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 +462,16 @@ 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 +480,6 @@ isa = PBXGroup; children = ( B43B69BA225C46D800925B1E /* libRCTLinking.a */, - B43B69B8225C462E00925B1E /* libPods-RCTLinking.a */, B43D046E22584C1B00FBAA95 /* libRNWatch.a */, ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED2971642150620600B7C4FE /* JavaScriptCore.framework */, @@ -401,12 +488,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 +499,14 @@ 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 */, + CF4A4D7AAD974D67A2D62B3E /* MaterialIcons.ttf */, ); name = Resources; sourceTree = ""; @@ -445,7 +523,8 @@ 6D2AA8062568B8E50090B089 /* Fiat */ = { isa = PBXGroup; children = ( - 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */, + B440340E2BCC40A400162242 /* fiatUnits.json */, + B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */, 6D2AA8072568B8F40090B089 /* FiatUnit.swift */, ); path = Fiat; @@ -454,6 +533,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 +562,7 @@ 6DD4109F266CADF10087DE03 /* Widgets */ = { isa = PBXGroup; children = ( + B48630EB2CCEEEA700A8425C /* WalletAppShortcuts.swift */, 6DD410C3266CCB780087DE03 /* WidgetsExtension.entitlements */, 6DD410A0266CADF10087DE03 /* Widgets.swift */, 6DD410A4266CADF40087DE03 /* Info.plist */, @@ -503,16 +588,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 +609,21 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + 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 +635,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 +655,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 +700,8 @@ B43D03242258474500FBAA95 /* Objects */ = { isa = PBXGroup; children = ( + B4793DC02CEDACE700C92C2E /* WalletType.swift */, + B4793DBE2CEDACDA00C92C2E /* TransactionType.swift */, B43D0374225847C500FBAA95 /* Transaction.swift */, B43D0375225847C500FBAA95 /* TransactionTableRow.swift */, B43D0376225847C500FBAA95 /* Wallet.swift */, @@ -647,10 +709,87 @@ 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 = ( + B4D899932DCAE67700B959AA /* CustomSegmentedControl.m */, + B4AA75232DAA339E00CF5CBE /* MenuElementsEmitter.m */, + B4B3EC232D69FF8700327F3D /* EventEmitter.swift */, + B409AB052D71E07500BA06F8 /* MenuElementsEmitter.swift */, + B4B3EC202D69FF6C00327F3D /* CustomSegmentedControl.swift */, + B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */, + ); + 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 +797,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 +861,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 +879,9 @@ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1250; - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1620; ORGANIZATIONNAME = BlueWallet; TargetAttributes = { 13B07F861A680F5B00A75B9A = { @@ -781,27 +900,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 +933,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 +948,6 @@ targets = ( 13B07F861A680F5B00A75B9A /* BlueWallet */, B40D4E2F225841EC00428FCC /* BlueWalletWatch */, - B40D4E3B225841ED00428FCC /* BlueWalletWatch Extension */, 6D2A6460258BA92C0092292B /* Stickers */, 6DD4109B266CADF10087DE03 /* WidgetsExtension */, ); @@ -850,7 +960,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 +973,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + B4742E9A2CCDBE8300380EEE /* Localizable.xcstrings in Resources */, 6D2A6464258BA92D0092292B /* Stickers.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -867,8 +982,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 +993,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 +1011,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 +1032,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 +1054,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 +1072,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 +1120,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 +1147,44 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */, - 13B07FC11A68108700A75B9A /* main.m in Sources */, - B461B852299599F800E431AA /* AppDelegate.mm 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 */, + 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 */, + B4D899942DCAE67700B959AA /* CustomSegmentedControl.m in Sources */, + B44033E22BCC36CB00162242 /* Placeholders.swift in Sources */, + B4793DBB2CEDACBD00C92C2E /* Chain.swift in Sources */, + B4B3EC222D69FF6C00327F3D /* CustomSegmentedControl.swift in Sources */, + B4B1A4622BFA73110072E3BB /* WidgetHelper.swift in Sources */, + B48630E12CCEE7C800A8425C /* PriceWidgetEntryView.swift in Sources */, + B44033DA2BCC369A00162242 /* Colors.swift in Sources */, + B44033D32BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */, 32B5A32A2334450100F8D608 /* Bridge.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 +1193,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 +1244,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 +1291,6 @@ /* Begin PBXTargetDependency section */ 6D2A6467258BA92D0092292B /* PBXTargetDependency */ = { isa = PBXTargetDependency; - platformFilter = ios; target = 6D2A6460258BA92C0092292B /* Stickers */; targetProxy = 6D2A6466258BA92D0092292B /* PBXContainerItemProxy */; }; @@ -1142,11 +1303,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 +1338,8 @@ 6D294A9924D512690039E22B /* uk */, 6D294A9B24D512770039E22B /* tr */, 6D294A9D24D5127F0039E22B /* xh */, + B4B31A352C77BBA000663334 /* nb */, + B4742E9C2CCDC31300380EEE /* en_US */, ); name = Interface.storyboard; sourceTree = ""; @@ -1191,16 +1349,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 = 1703239999; + 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 +1372,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 = 7.2.4; 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 = 1703239999; + 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 = 7.2.4; 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 +1474,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 = 1703239999; + 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 = 7.2.4; 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 +1516,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 = 1703239999; + 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 = 7.2.4; 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 +1559,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 = 1703239999; + 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 = 7.2.4; 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 +1619,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 = 1703239999; + 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 = 7.2.4; 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 +1672,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 +1699,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 +1710,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 +1719,22 @@ 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)"; + OTHER_CPLUSPLUSFLAGS = "$(inherited)"; + 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_VERSION = 5.0; + USE_HERMES = true; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Debug; }; @@ -1492,8 +1742,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,140 +1769,95 @@ 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; + OTHER_CFLAGS = "$(inherited)"; + OTHER_CPLUSPLUSFLAGS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_VERSION = 4.2; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + USE_HERMES = true; VALIDATE_PRODUCT = YES; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Release; }; - B40D4E4F225841ED00428FCC /* Debug */ = { + B40D4E53225841ED00428FCC /* Debug */ = { 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"; + CLANG_ENABLE_MODULES = YES; 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_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 = 1703239999; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=watchos*]" = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "BlueWalletWatch Extension/Info.plist"; + INFOPLIST_FILE = BlueWalletWatch/Info.plist; + 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 = ( - "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - 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 = ( + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", ); - 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; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 5.0; - }; - name = Release; - }; - B40D4E53225841ED00428FCC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - 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; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = ""; - GCC_C_LANGUAGE_STANDARD = gnu11; - IBSC_MODULE = BlueWalletWatch_Extension; - INFOPLIST_FILE = BlueWalletWatch/Info.plist; - MARKETING_VERSION = 6.4.9; + MACOSX_DEPLOYMENT_TARGET = 12.4; + MARKETING_VERSION = 7.2.4; 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 +1865,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 +1875,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 = 1703239999; + 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 = 7.2.4; 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 +1957,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 = ( @@ -1755,9 +1972,17 @@ 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/EFPrefix/EFQRCode.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 6.2.2; + }; + }; + B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/bugsnag/bugsnag-cocoa"; requirement = { kind = exactVersion; - version = 6.2.2; + version = 6.28.1; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -1768,6 +1993,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.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.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"> + LastUpgradeVersion = "1620" + wasCreatedForAppExtension = "YES" + version = "2.0"> + BlueprintIdentifier = "6DD4109B266CADF10087DE03" + BuildableName = "WidgetsExtension.appex" + BlueprintName = "WidgetsExtension" + ReferencedContainer = "container:BlueWallet.xcodeproj"> - - - - @@ -54,67 +41,87 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> + skipped = "NO" + parallelizable = "YES"> - - - - - - - + allowLocationSimulation = "YES" + launchAutomaticallySubstyle = "2"> + - - - + + + + + + + + + + + + + + debugDocumentVersioning = "YES" + askForAppToLaunch = "Yes" + launchAutomaticallySubstyle = "2"> diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWalletWatch (Notification).xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/PriceWidget.xcscheme similarity index 56% rename from ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWalletWatch (Notification).xcscheme rename to ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/PriceWidget.xcscheme index ce67c26c8a4..35e5cca60be 100644 --- a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWalletWatch (Notification).xcscheme +++ b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/PriceWidget.xcscheme @@ -1,6 +1,7 @@ @@ -40,41 +41,71 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> + + + + - + launchAutomaticallySubstyle = "2"> + - + + + + + + + + + + + + + + askForAppToLaunch = "Yes" + launchAutomaticallySubstyle = "2"> 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..abbd11c779d --- /dev/null +++ b/ios/BlueWallet/AppDelegate.swift @@ -0,0 +1,477 @@ +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) + + let center = UNUserNotificationCenter.current() + center.delegate = self + + setupUserDefaultsListener() + registerNotificationCategories() + + // Access the singleton via the class method + _ = MenuElementsEmitter.sharedInstance() + NSLog("[MenuElements] AppDelegate: Initialized emitter singleton") + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + 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 applicationWillTerminate(_ application: UIApplication) { + userDefaultsGroup?.removeObject(forKey: "onUserActivityOpen") + + 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) + } + } + } + + RNCPushNotificationIOS.didReceive(response) + 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..ec541528a9a 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..a72c80addc6 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..5727bf33172 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..84ad2342285 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 @@ -102,13 +130,26 @@ https http + LSMinimumSystemVersion + 11 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace + NSAppIntents + + + INIntentClassName + PriceView + IntentDescription + Quickly view the current Bitcoin market rate. + IntentName + Bitcoin Price + + NSAppTransportSecurity - NSAllowsArbitraryLoads + NSAllowsLocalNetworking NSExceptionDomains @@ -140,47 +181,29 @@ - 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 + UIAppFonts - AntDesign.ttf Entypo.ttf - EvilIcons.ttf - Feather.ttf FontAwesome.ttf FontAwesome5_Brands.ttf FontAwesome5_Regular.ttf FontAwesome5_Solid.ttf - Foundation.ttf Ionicons.ttf - MaterialCommunityIcons.ttf MaterialIcons.ttf Octicons.ttf - SimpleLineIcons.ttf - Zocial.ttf UIBackgroundModes @@ -188,26 +211,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 +252,6 @@ UTTypeDescription Partially Signed Bitcoin Transaction - UTTypeIconFiles - UTTypeIdentifier io.bluewallet.psbt UTTypeTagSpecification @@ -230,43 +263,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 +298,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/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/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..5d476e681ff 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,38 @@ $(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 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 Extension/Objects/TransactionTableRow.swift b/ios/BlueWalletWatch/Objects/TransactionTableRow.swift similarity index 60% rename from ios/BlueWalletWatch Extension/Objects/TransactionTableRow.swift rename to ios/BlueWalletWatch/Objects/TransactionTableRow.swift index ca798901688..f738c10ab41 100644 --- a/ios/BlueWalletWatch Extension/Objects/TransactionTableRow.swift +++ b/ios/BlueWalletWatch/Objects/TransactionTableRow.swift @@ -3,7 +3,7 @@ // BlueWalletWatch Extension // // Created by Marcos Rodriguez on 3/10/19. -// Copyright © 2019 Facebook. All rights reserved. + // import WatchKit @@ -31,17 +31,21 @@ class TransactionTableRow: NSObject { var time: String = "" { willSet { - transactionTimeLabel.setText(newValue) + if type == .pending { + transactionTimeLabel.setText("Pending...") + } else { + transactionTimeLabel.setText(newValue) + } } } - var type: String = "" { + var type: TransactionType = .pending { willSet { - if (newValue == "pendingConfirmation") { + if newValue == .pending { transactionTypeImage.setImage(UIImage(named: "pendingConfirmation")) - } else if (newValue == "received") { + } else if newValue == .received { transactionTypeImage.setImage(UIImage(named: "receivedArrow")) - } else if (newValue == "sent") { + } else if newValue == .sent { transactionTypeImage.setImage(UIImage(named: "sentArrow")) } else { transactionTypeImage.setImage(nil) @@ -50,3 +54,19 @@ class TransactionTableRow: NSObject { } } + +// 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 Extension/Objects/WalletInformation.swift b/ios/BlueWalletWatch/Objects/WalletInformation.swift similarity index 68% rename from ios/BlueWalletWatch Extension/Objects/WalletInformation.swift rename to ios/BlueWalletWatch/Objects/WalletInformation.swift index 47c36953399..5b709d0c213 100644 --- a/ios/BlueWalletWatch Extension/Objects/WalletInformation.swift +++ b/ios/BlueWalletWatch/Objects/WalletInformation.swift @@ -3,7 +3,7 @@ // BlueWalletWatch Extension // // Created by Marcos Rodriguez on 3/10/19. -// Copyright © 2019 Facebook. All rights reserved. + // import WatchKit @@ -14,6 +14,7 @@ class WalletInformation: NSObject { @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 { @@ -27,10 +28,15 @@ class WalletInformation: NSObject { } } - var type: WalletGradient = .SegwitHD { - willSet { - walletGroup.setBackgroundImageNamed(newValue.imageString) - } - } + +} + +// 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 index 87c7573dc59..ae3944ae53f 100644 --- a/ios/Bridge.swift +++ b/ios/Bridge.swift @@ -3,7 +3,6 @@ // BlueWallet // // Created by Marcos Rodriguez on 9/19/19. -// Copyright © 2019 Facebook. All rights reserved. // import Foundation diff --git a/ios/Components/CustomSegmentedControl.m b/ios/Components/CustomSegmentedControl.m new file mode 100644 index 00000000000..87bf27bb379 --- /dev/null +++ b/ios/Components/CustomSegmentedControl.m @@ -0,0 +1,21 @@ +// +// RCT.h +// BlueWallet +// +// Created by Marcos Rodriguez on 4/22/25. +// Copyright © 2025 BlueWallet. All rights reserved. +// + + +#import +#import +#import +#import + +@interface RCT_EXTERN_MODULE(CustomSegmentedControlManager, RCTViewManager) + +RCT_EXPORT_VIEW_PROPERTY(values, NSArray) +RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSNumber) +RCT_EXPORT_VIEW_PROPERTY(onChangeEvent, RCTDirectEventBlock) + +@end diff --git a/ios/Components/EventEmitter.swift b/ios/Components/EventEmitter.swift new file mode 100644 index 00000000000..f7b43bb002d --- /dev/null +++ b/ios/Components/EventEmitter.swift @@ -0,0 +1,32 @@ +import Foundation +import React + +@objc(EventEmitter) +class EventEmitter: RCTEventEmitter { + static let sharedInstance = EventEmitter() + + override class func requiresMainQueueSetup() -> Bool { + return true + } + + @objc static func shared() -> EventEmitter { + return sharedInstance + } + + override func supportedEvents() -> [String]! { + return ["onUserActivityOpen"] + } + + @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..e4cd1e7d86c --- /dev/null +++ b/ios/Components/MenuElementsEmitter.m @@ -0,0 +1,20 @@ +#import +#import + +// This macro exposes the Swift class to Objective-C +@interface RCT_EXTERN_MODULE(MenuElementsEmitter, RCTEventEmitter) + +// Expose the Swift method to JS +RCT_EXTERN_METHOD(shared) +RCT_EXTERN_METHOD(openSettings) +RCT_EXTERN_METHOD(addWalletMenuAction) +RCT_EXTERN_METHOD(importWalletMenuAction) +RCT_EXTERN_METHOD(reloadTransactionsMenuAction) +RCT_EXTERN_METHOD(checkListenerStatus) + +// Make sure we share the same instance between native UI and JS ++ (BOOL)requiresMainQueueSetup { + return YES; +} + +@end diff --git a/ios/Components/SegmentedControl/CustomSegmentedControl.swift b/ios/Components/SegmentedControl/CustomSegmentedControl.swift new file mode 100644 index 00000000000..dbdca395822 --- /dev/null +++ b/ios/Components/SegmentedControl/CustomSegmentedControl.swift @@ -0,0 +1,47 @@ +import UIKit +import React + +@objc(CustomSegmentedControl) +class CustomSegmentedControl: UISegmentedControl { + @objc var onChangeEvent: RCTDirectEventBlock? + + @objc var values: [String] = [] { + didSet { + removeAllSegments() + for (index, title) in values.enumerated() { + insertSegment(withTitle: title, at: index, animated: false) + } + } + } + + @objc var selectedIndex: NSNumber = 0 { + didSet { + self.selectedSegmentIndex = selectedIndex.intValue + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + addTarget(self, action: #selector(onChange(_:)), for: .valueChanged) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + addTarget(self, action: #selector(onChange(_:)), for: .valueChanged) + } + + @objc func onChange(_ sender: UISegmentedControl) { + onChangeEvent?(["selectedIndex": sender.selectedSegmentIndex]) + } +} + +@objc(CustomSegmentedControlManager) +class CustomSegmentedControlManager: RCTViewManager { + override func view() -> UIView! { + return CustomSegmentedControl(frame: .zero) + } + + override class func requiresMainQueueSetup() -> Bool { + return true + } +} diff --git a/ios/Components/WidgetHelper.swift b/ios/Components/WidgetHelper.swift new file mode 100644 index 00000000000..37065bbdafc --- /dev/null +++ b/ios/Components/WidgetHelper.swift @@ -0,0 +1,11 @@ +import WidgetKit + +@objc class WidgetHelper: NSObject { + @objc static func reloadAllWidgets() { + if #available(iOS 14.0, *) { + WidgetCenter.shared.reloadAllTimelines() + } else { + // Fallback on earlier versions + } + } +} 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..0ca2b12ca0e --- /dev/null +++ b/ios/Localizable.xcstrings @@ -0,0 +1,384 @@ +{ + "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" + } + } + } + }, + "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.0" +} \ No newline at end of file diff --git a/ios/MenuElementsEmitter/MenuElementsEmitter.m b/ios/MenuElementsEmitter/MenuElementsEmitter.m new file mode 100644 index 00000000000..f740da29cd5 --- /dev/null +++ b/ios/MenuElementsEmitter/MenuElementsEmitter.m @@ -0,0 +1,12 @@ +#import +#import + +@interface RCT_EXTERN_MODULE(MenuElementsEmitter, RCTEventEmitter) + +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/MenuElementsEmitter/MenuElementsEmitter.swift b/ios/MenuElementsEmitter/MenuElementsEmitter.swift new file mode 100644 index 00000000000..6153469dd65 --- /dev/null +++ b/ios/MenuElementsEmitter/MenuElementsEmitter.swift @@ -0,0 +1,66 @@ +import Foundation +import React + +@objc(MenuElementsEmitter) +class MenuElementsEmitter: RCTEventEmitter { + + 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! + } + + override func supportedEvents() -> [String]! { + return ["openSettings", "addWalletMenuAction", "importWalletMenuAction", "reloadTransactionsMenuAction"] + } + + override class func requiresMainQueueSetup() -> Bool { + return true + } + + 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/Podfile b/ios/Podfile index 35373ac7083..e6c1158ce78 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,72 +1,66 @@ -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 +ENV['RCT_NEW_ARCH_ENABLED'] = '0' +min_ios_version_supported = '15.1' +node_require('react-native/scripts/react_native_pods.rb') +node_require('react-native-permissions/scripts/setup.rb') + +# Resolve react_native_pods.rb with node to allow for hoisting +require Pod::Executable.execute_command('node', ['-p', + 'require.resolve( + "react-native/scripts/react_native_pods.rb", + {paths: [process.argv[1]]}, + )', __dir__]).strip 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) +target 'BlueWallet' do + configure_target() +end + +post_install do |installer| + react_native_post_install( + installer, + :mac_catalyst_enabled => true, + # :ccache_enabled => true + ) + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1' + + 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 end - - end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 57323374e13..b17dce05233 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,528 +1,2029 @@ PODS: - - boost (1.76.0) - - BugsnagReactNative (7.20.2): + - boost (1.84.0) + - BugsnagReactNative (8.4.0): - React-Core - - BVLinearGradient (2.8.0): + - BVLinearGradient (2.8.3): - 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) + - fast_float (6.1.4) + - FBLazyVector (0.78.2) + - fmt (11.0.2) - 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): + - hermes-engine (0.78.2): + - hermes-engine/Pre-built (= 0.78.2) + - hermes-engine/Pre-built (0.78.2) + - lottie-ios (4.5.0) + - lottie-react-native (7.3.4): + - DoubleConversion + - glog + - hermes-engine + - lottie-ios (= 4.5.0) + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - RCT-Folly (2024.11.18.00): + - boost + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - RCT-Folly/Default (= 2024.11.18.00) + - RCT-Folly/Default (2024.11.18.00): + - boost + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - RCT-Folly/Fabric (2024.11.18.00): - boost - DoubleConversion - - fmt (~> 6.2.1) + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - RCTDeprecation (0.78.2) + - RCTRequired (0.78.2) + - RCTTypeSafety (0.78.2): + - FBLazyVector (= 0.78.2) + - RCTRequired (= 0.78.2) + - React-Core (= 0.78.2) + - React (0.78.2): + - React-Core (= 0.78.2) + - React-Core/DevSupport (= 0.78.2) + - React-Core/RCTWebSocket (= 0.78.2) + - React-RCTActionSheet (= 0.78.2) + - React-RCTAnimation (= 0.78.2) + - React-RCTBlob (= 0.78.2) + - React-RCTImage (= 0.78.2) + - React-RCTLinking (= 0.78.2) + - React-RCTNetwork (= 0.78.2) + - React-RCTSettings (= 0.78.2) + - React-RCTText (= 0.78.2) + - React-RCTVibration (= 0.78.2) + - React-callinvoker (0.78.2) + - React-Core (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default (= 0.78.2) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/CoreModulesHeaders (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/Default (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/DevSupport (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default (= 0.78.2) + - React-Core/RCTWebSocket (= 0.78.2) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/RCTActionSheetHeaders (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/RCTAnimationHeaders (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/RCTBlobHeaders (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/RCTImageHeaders (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/RCTLinkingHeaders (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/RCTNetworkHeaders (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/RCTSettingsHeaders (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/RCTTextHeaders (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/RCTVibrationHeaders (0.78.2): + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-Core/RCTWebSocket (0.78.2): - glog - - RCT-Folly/Default (= 2021.07.22.00) - - RCT-Folly/Default (2021.07.22.00): + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTDeprecation + - React-Core/Default (= 0.78.2) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.1) + - Yoga + - React-CoreModules (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - RCT-Folly (= 2024.11.18.00) + - RCTTypeSafety (= 0.78.2) + - React-Core/CoreModulesHeaders (= 0.78.2) + - React-jsi (= 0.78.2) + - React-jsinspector + - React-NativeModulesApple + - React-RCTBlob + - React-RCTFBReactNativeSpec + - React-RCTImage (= 0.78.2) + - ReactCommon + - SocketRocket (= 0.7.1) + - React-cxxreact (0.78.2): - boost - DoubleConversion - - fmt (~> 6.2.1) + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - React-callinvoker (= 0.78.2) + - React-debug (= 0.78.2) + - React-jsi (= 0.78.2) + - React-jsinspector + - React-logger (= 0.78.2) + - React-perflogger (= 0.78.2) + - React-runtimeexecutor (= 0.78.2) + - React-timing (= 0.78.2) + - React-debug (0.78.2) + - React-defaultsnativemodule (0.78.2): + - hermes-engine + - RCT-Folly + - React-domnativemodule + - React-featureflagsnativemodule + - React-idlecallbacksnativemodule + - React-jsi + - React-jsiexecutor + - React-microtasksnativemodule + - React-RCTFBReactNativeSpec + - React-domnativemodule (0.78.2): + - hermes-engine + - RCT-Folly + - React-Fabric + - React-FabricComponents + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - Yoga + - React-Fabric (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/animations (= 0.78.2) + - React-Fabric/attributedstring (= 0.78.2) + - React-Fabric/componentregistry (= 0.78.2) + - React-Fabric/componentregistrynative (= 0.78.2) + - React-Fabric/components (= 0.78.2) + - React-Fabric/consistency (= 0.78.2) + - React-Fabric/core (= 0.78.2) + - React-Fabric/dom (= 0.78.2) + - React-Fabric/imagemanager (= 0.78.2) + - React-Fabric/leakchecker (= 0.78.2) + - React-Fabric/mounting (= 0.78.2) + - React-Fabric/observers (= 0.78.2) + - React-Fabric/scheduler (= 0.78.2) + - React-Fabric/telemetry (= 0.78.2) + - React-Fabric/templateprocessor (= 0.78.2) + - React-Fabric/uimanager (= 0.78.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/animations (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/attributedstring (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/componentregistry (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/componentregistrynative (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/components/legacyviewmanagerinterop (= 0.78.2) + - React-Fabric/components/root (= 0.78.2) + - React-Fabric/components/view (= 0.78.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/legacyviewmanagerinterop (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/root (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/view (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-Fabric/consistency (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/core (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/dom (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/imagemanager (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/leakchecker (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/mounting (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/observers (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/observers/events (= 0.78.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/observers/events (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/scheduler (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/observers/events + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-performancetimeline + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/telemetry (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/templateprocessor (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/uimanager (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/uimanager/consistency (= 0.78.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/uimanager/consistency (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-FabricComponents (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components (= 0.78.2) + - React-FabricComponents/textlayoutmanager (= 0.78.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components/inputaccessory (= 0.78.2) + - React-FabricComponents/components/iostextinput (= 0.78.2) + - React-FabricComponents/components/modal (= 0.78.2) + - React-FabricComponents/components/rncore (= 0.78.2) + - React-FabricComponents/components/safeareaview (= 0.78.2) + - React-FabricComponents/components/scrollview (= 0.78.2) + - React-FabricComponents/components/text (= 0.78.2) + - React-FabricComponents/components/textinput (= 0.78.2) + - React-FabricComponents/components/unimplementedview (= 0.78.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/inputaccessory (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/iostextinput (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/modal (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/rncore (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/safeareaview (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/scrollview (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/text (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/textinput (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/components/unimplementedview (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-FabricComponents/textlayoutmanager (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-FabricImage (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired (= 0.78.2) + - RCTTypeSafety (= 0.78.2) + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsiexecutor (= 0.78.2) + - React-logger + - React-rendererdebug + - React-utils + - ReactCommon + - Yoga + - React-featureflags (0.78.2): + - RCT-Folly (= 2024.11.18.00) + - React-featureflagsnativemodule (0.78.2): + - hermes-engine + - RCT-Folly + - React-featureflags + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - React-graphics (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - React-jsi + - React-jsiexecutor + - React-utils + - React-hermes (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - React-cxxreact (= 0.78.2) + - React-jsi + - React-jsiexecutor (= 0.78.2) + - React-jsinspector + - React-perflogger (= 0.78.2) + - React-runtimeexecutor + - React-idlecallbacksnativemodule (0.78.2): + - glog + - hermes-engine + - RCT-Folly + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimescheduler + - ReactCommon/turbomodule/core + - React-ImageManager (0.78.2): + - glog + - RCT-Folly/Fabric + - React-Core/Default + - React-debug + - React-Fabric + - React-graphics + - React-rendererdebug + - React-utils + - React-jserrorhandler (0.78.2): + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.11.18.00) + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - ReactCommon/turbomodule/bridging + - React-jsi (0.78.2): + - boost + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - React-jsiexecutor (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - React-cxxreact (= 0.78.2) + - React-jsi (= 0.78.2) + - React-jsinspector + - React-perflogger (= 0.78.2) + - React-jsinspector (0.78.2): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly + - React-featureflags + - React-jsi + - React-jsinspectortracing + - React-perflogger (= 0.78.2) + - React-runtimeexecutor (= 0.78.2) + - React-jsinspectortracing (0.78.2): + - RCT-Folly + - React-jsitracing (0.78.2): + - React-jsi + - React-logger (0.78.2): + - glog + - React-Mapbuffer (0.78.2): + - glog + - React-debug + - React-microtasksnativemodule (0.78.2): + - hermes-engine + - RCT-Folly + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - 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.0.7): + - React-Core + - react-native-document-picker (10.1.7): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - react-native-get-random-values (1.11.0): + - React-Core + - react-native-image-picker (8.2.1): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - react-native-menu (1.2.4): + - React + - react-native-safe-area-context (5.5.2): + - React-Core + - react-native-secure-key-store (2.0.10): + - React-Core + - react-native-tcp-socket (6.3.0): + - CocoaAsyncSocket + - React-Core + - react-native-true-sheet (2.0.0): + - DoubleConversion - 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 + - RCT-Folly (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core - - React-jsi - - React-jsiexecutor + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - React-Core (0.71.11): - - glog - - 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-Core/CoreModulesHeaders (0.71.11): - - glog - - 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/Default (0.71.11): + - React-NativeModulesApple (0.78.2): - glog - 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 - - 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-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 - - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default - - React-cxxreact (= 0.71.11) + - React-callinvoker + - React-Core + - React-cxxreact + - React-jsi + - React-jsinspector + - React-runtimeexecutor + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - React-perflogger (0.78.2): + - DoubleConversion + - RCT-Folly (= 2024.11.18.00) + - React-performancetimeline (0.78.2): + - RCT-Folly (= 2024.11.18.00) + - React-cxxreact + - React-featureflags + - React-jsinspectortracing + - React-timing + - React-RCTActionSheet (0.78.2): + - React-Core/RCTActionSheetHeaders (= 0.78.2) + - React-RCTAnimation (0.78.2): + - RCT-Folly (= 2024.11.18.00) + - RCTTypeSafety + - React-Core/RCTAnimationHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - React-RCTAppDelegate (0.78.2): + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-CoreModules + - React-debug + - React-defaultsnativemodule + - React-Fabric + - React-featureflags + - React-graphics - 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-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RCTImage + - React-RCTNetwork + - React-rendererdebug + - React-RuntimeApple + - React-RuntimeCore + - React-RuntimeHermes + - React-runtimescheduler + - React-utils + - ReactCommon + - React-RCTBlob (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) - 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): + - RCT-Folly (= 2024.11.18.00) + - React-Core/RCTBlobHeaders + - React-Core/RCTWebSocket + - React-jsi + - React-jsinspector + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - React-RCTFabric (0.78.2): - glog - 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) + - RCT-Folly/Fabric (= 2024.11.18.00) + - React-Core + - React-debug + - React-Fabric + - React-FabricComponents + - React-FabricImage + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-jsinspectortracing + - React-performancetimeline + - React-RCTImage + - React-RCTText + - React-rendererconsistency + - React-rendererdebug + - React-runtimescheduler + - React-utils - Yoga - - React-Core/RCTImageHeaders (0.71.11): - - glog + - React-RCTFBReactNativeSpec (0.78.2): - 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) + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React-Core + - React-jsi + - React-jsiexecutor + - React-NativeModulesApple + - ReactCommon + - React-RCTImage (0.78.2): + - RCT-Folly (= 2024.11.18.00) + - RCTTypeSafety + - React-Core/RCTImageHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - React-RCTLinking (0.78.2): + - React-Core/RCTLinkingHeaders (= 0.78.2) + - React-jsi (= 0.78.2) + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactCommon/turbomodule/core (= 0.78.2) + - React-RCTNetwork (0.78.2): + - RCT-Folly (= 2024.11.18.00) + - RCTTypeSafety + - React-Core/RCTNetworkHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - React-RCTSettings (0.78.2): + - RCT-Folly (= 2024.11.18.00) + - RCTTypeSafety + - React-Core/RCTSettingsHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - React-RCTText (0.78.2): + - React-Core/RCTTextHeaders (= 0.78.2) - Yoga - - React-Core/RCTLinkingHeaders (0.71.11): - - glog + - React-RCTVibration (0.78.2): + - RCT-Folly (= 2024.11.18.00) + - React-Core/RCTVibrationHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - React-rendererconsistency (0.78.2) + - React-rendererdebug (0.78.2): + - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) + - RCT-Folly (= 2024.11.18.00) + - React-debug + - React-rncore (0.78.2) + - React-RuntimeApple (0.78.2): - hermes-engine - - RCT-Folly (= 2021.07.22.00) + - RCT-Folly/Fabric (= 2024.11.18.00) + - React-callinvoker - 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): + - React-CoreModules + - React-cxxreact + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-Mapbuffer + - React-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-runtimescheduler + - React-utils + - React-RuntimeCore (0.78.2): - glog - 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 + - RCT-Folly/Fabric (= 2024.11.18.00) + - React-cxxreact + - React-Fabric + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-performancetimeline + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - React-runtimeexecutor (0.78.2): + - React-jsi (= 0.78.2) + - React-RuntimeHermes (0.78.2): - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-Core/Default - - React-cxxreact (= 0.71.11) + - RCT-Folly/Fabric (= 2024.11.18.00) + - React-featureflags - React-hermes - - React-jsi (= 0.71.11) - - React-jsiexecutor (= 0.71.11) - - React-perflogger (= 0.71.11) - - Yoga - - React-Core/RCTTextHeaders (0.71.11): + - React-jsi + - React-jsinspector + - React-jsitracing + - React-RuntimeCore + - React-utils + - React-runtimescheduler (0.78.2): - glog - 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/RCTVibrationHeaders (0.71.11): + - RCT-Folly (= 2024.11.18.00) + - React-callinvoker + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - React-performancetimeline + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-timing + - React-utils + - React-timing (0.78.2) + - React-utils (0.78.2): - glog - 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/RCTWebSocket (0.71.11): + - RCT-Folly (= 2024.11.18.00) + - React-debug + - React-jsi (= 0.78.2) + - ReactAppDependencyProvider (0.78.2): + - ReactCodegen + - ReactCodegen (0.78.2): + - DoubleConversion - glog - 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) + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React-Core + - 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 + - ReactCommon (0.78.2): + - ReactCommon/turbomodule (= 0.78.2) + - ReactCommon/turbomodule (0.78.2): - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) - 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): + - RCT-Folly (= 2024.11.18.00) + - React-callinvoker (= 0.78.2) + - React-cxxreact (= 0.78.2) + - React-jsi (= 0.78.2) + - React-logger (= 0.78.2) + - React-perflogger (= 0.78.2) + - ReactCommon/turbomodule/bridging (= 0.78.2) + - ReactCommon/turbomodule/core (= 0.78.2) + - ReactCommon/turbomodule/bridging (0.78.2): - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) - 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) + - RCT-Folly (= 2024.11.18.00) + - React-callinvoker (= 0.78.2) + - React-cxxreact (= 0.78.2) + - React-jsi (= 0.78.2) + - React-logger (= 0.78.2) + - React-perflogger (= 0.78.2) + - ReactCommon/turbomodule/core (0.78.2): - DoubleConversion + - fast_float (= 6.1.4) + - fmt (= 11.0.2) - glog - hermes-engine - - RCT-Folly (= 2021.07.22.00) - - React-jsiexecutor (0.71.11): + - RCT-Folly (= 2024.11.18.00) + - React-callinvoker (= 0.78.2) + - React-cxxreact (= 0.78.2) + - React-debug (= 0.78.2) + - React-featureflags (= 0.78.2) + - React-jsi (= 0.78.2) + - React-logger (= 0.78.2) + - React-perflogger (= 0.78.2) + - React-utils (= 0.78.2) + - ReactNativeCameraKit (16.2.0): - 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 + - RCT-Folly (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging - 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): + - Yoga + - RealmJS (20.1.0): - React - - rn-ldk (0.8.4): + - RNCAsyncStorage (2.2.0): - React-Core - - RNCAsyncStorage (1.19.1): - - React-Core - - RNCClipboard (1.11.2): + - RNCClipboard (1.16.3): - React-Core - RNCPushNotificationIOS (1.11.0): - React-Core - - RNDefaultPreference (1.4.4): + - 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.25.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - RNHandoff (0.0.3): - React - - RNKeychain (8.1.1): + - RNKeychain (9.1.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - RNLocalize (3.5.4): - React-Core - - RNLocalize (3.0.2): + - RNPermissions (5.4.4): - React-Core - - RNPrivacySnapshot (1.0.0): + - RNQrGenerator (1.4.3): - React + - ZXingObjC - RNQuickAction (0.3.13): - React - RNRate (1.2.12): - React-Core - - RNReactNativeHapticFeedback (2.0.3): + - RNReactNativeHapticFeedback (2.3.3): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - RNReanimated (3.18.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety - React-Core - - RNReanimated (2.17.0): + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNReanimated/reanimated (= 3.18.0) + - RNReanimated/worklets (= 3.18.0) + - Yoga + - RNReanimated/reanimated (3.18.0): - DoubleConversion - - FBLazyVector - - FBReactNativeSpec - glog - - RCT-Folly + - hermes-engine + - RCT-Folly (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - - React-callinvoker - React-Core - - React-Core/DevSupport - - React-Core/RCTWebSocket - - React-CoreModules - - React-cxxreact + - 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-RCTImage - - React-RCTLinking - - React-RCTNetwork - - React-RCTSettings - - React-RCTText + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNReanimated/reanimated/apple (= 3.18.0) + - Yoga + - RNReanimated/reanimated/apple (3.18.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - RNReanimated/worklets (3.18.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNReanimated/worklets/apple (= 3.18.0) + - Yoga + - RNReanimated/worklets/apple (3.18.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNScreens (3.20.0): + - RNScreens (4.16.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric - React-RCTImage - - RNShare (8.2.2): + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - RNShare (12.1.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety - React-Core - - RNSVG (13.9.0): + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - RNSVG (15.12.1): - React-Core - - RNVectorIcons (9.2.0): + - RNVectorIcons (10.2.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - RNWatch (1.1.0): - React - - Yoga (1.14.0) + - SocketRocket (0.7.1) + - Yoga (0.0.0) + - ZXingObjC (3.6.9): + - ZXingObjC/All (= 3.6.9) + - ZXingObjC/All (3.6.9) 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`) + - fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) + - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - 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`) + - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../node_modules/react-native/Libraries/Required`) - 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/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-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-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) + - 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-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-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-menu (from `../node_modules/@react-native-menu/menu`)" - 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-true-sheet (from `../node_modules/@lodev09/react-native-true-sheet`)" + - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) + - 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-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-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-rncore (from `../node_modules/react-native/ReactCommon`) + - 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`) + - ReactAppDependencyProvider (from `build/generated/ios`) + - ReactCodegen (from `build/generated/ios`) - 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`) - 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`)" @@ -533,7 +2034,8 @@ 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`) + - RNQrGenerator (from `../node_modules/rn-qr-generator`) - RNQuickAction (from `../node_modules/react-native-quick-actions`) - RNRate (from `../node_modules/react-native-rate`) - RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`) @@ -548,8 +2050,9 @@ DEPENDENCIES: SPEC REPOS: trunk: - CocoaAsyncSocket - - fmt - - libevent + - lottie-ios + - SocketRocket + - ZXingObjC EXTERNAL SOURCES: boost: @@ -560,78 +2063,109 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-linear-gradient" DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + fast_float: + :podspec: "../node_modules/react-native/third-party-podspecs/fast_float.podspec" FBLazyVector: :path: "../node_modules/react-native/Libraries/FBLazyVector" - FBReactNativeSpec: - :path: "../node_modules/react-native/React/FBReactNativeSpec" + fmt: + :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" 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-2025-01-13-RNv0.78.0-a942ef374897d85da38e9c8904574f8376555388 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" 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-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-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-jsinspectortracing: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" + 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-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-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-menu: + :path: "../node_modules/@react-native-menu/menu" 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-true-sheet: + :path: "../node_modules/@lodev09/react-native-true-sheet" + React-NativeModulesApple: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" React-perflogger: :path: "../node_modules/react-native/ReactCommon/reactperflogger" + React-performancetimeline: + :path: "../node_modules/react-native/ReactCommon/react/performance/timeline" React-RCTActionSheet: :path: "../node_modules/react-native/Libraries/ActionSheetIOS" React-RCTAnimation: @@ -640,6 +2174,10 @@ 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: @@ -652,16 +2190,36 @@ EXTERNAL SOURCES: :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-rendererdebug: + :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" + React-rncore: + :path: "../node_modules/react-native/ReactCommon" + 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" + ReactAppDependencyProvider: + :path: build/generated/ios + ReactCodegen: + :path: build/generated/ios ReactCommon: :path: "../node_modules/react-native/ReactCommon" ReactNativeCameraKit: - :path: "../node_modules/react-native-camera-kit" + :path: "../node_modules/react-native-camera-kit-no-google" RealmJS: :path: "../node_modules/realm" - rn-ldk: - :path: "../node_modules/rn-ldk" RNCAsyncStorage: :path: "../node_modules/@react-native-async-storage/async-storage" RNCClipboard: @@ -682,8 +2240,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" + RNQrGenerator: + :path: "../node_modules/rn-qr-generator" RNQuickAction: :path: "../node_modules/react-native-quick-actions" RNRate: @@ -706,86 +2266,115 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: 57d2868c099736d80fcd648bf211b4431e51a558 - BugsnagReactNative: bf6f4ebababa8536726b3014c7d3e4af8c53d488 - BVLinearGradient: 612a04ff38e8480291f3379ee5b5a2c571f03fe0 + boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 + BugsnagReactNative: e5067a5ffdfe6276eabff91be20c989b4bb20a0b + BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 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 + DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb + fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 + FBLazyVector: e32d34492c519a2194ec9d7f5e7a79d11b73f91c + fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd + glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8 + hermes-engine: 2771b98fb813fdc6f92edd7c9c0035ecabf9fee7 + lottie-ios: a881093fab623c467d3bce374367755c272bdd59 + lottie-react-native: 04061d06c966a4179c9c1352aac63b699642c77e + RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 + RCTDeprecation: be794de7dc6ed8f9f7fbf525f86e7651b8b68746 + RCTRequired: a83787b092ec554c2eb6019ff3f5b8d125472b3b + RCTTypeSafety: 48ad3c858926b1c46f46a81a58822b476e178e2c + React: 3b5754191f1b65f1dbc52fbea7959c3d2d9e39c9 + React-callinvoker: 6beeaf4c7db11b6cc953fac45f2c76e3fb125013 + React-Core: 88e817c42de035378cc71e009193b9a044d3f595 + React-CoreModules: dcf764d71efb4f75d38fcae8d4513b6729f49360 + React-cxxreact: 8cdcc937c5fbc406fe843a381102fd69440ca78a + React-debug: 440175830c448e7e53e61ebb8d8468c3256b645e + React-defaultsnativemodule: 4824bcd7b96ee2d75c28b1ca21f58976867f5535 + React-domnativemodule: a421118b475618961cf282e8ea85347cc9bb453c + React-Fabric: 6ac7de06009eb96b609a770b17abba6e460b5f45 + React-FabricComponents: e3bc2680a5a9a4917ff0c8d7f390688c30ef753c + React-FabricImage: 8bad558dec7478077974caa96acc79692d6b71f5 + React-featureflags: b9cf9b35baca1c7f20c06a104ffc325a02752faa + React-featureflagsnativemodule: dc93d81da9f41f7132e24455ec8b4b60802fd5b0 + React-graphics: aaa5a38bea15d7b895b210d95d554af45a07002a + React-hermes: 08ad9fb832d1b9faef391be17309aa6a69fad23b + React-idlecallbacksnativemodule: aacea33ef6c511a9781f9286cc7cdf93f39bba14 + React-ImageManager: c596c3b658c9c14607f9183ed0f635c8dd77987c + React-jserrorhandler: 987609b2f16b7d79d63fcd621bf0110dd7400b35 + React-jsi: afa286d7e0c102c2478dc420d4f8935e13c973fc + React-jsiexecutor: 08f5b512b4db9e2f147416d60a0a797576b9cfef + React-jsinspector: 5a94bcae66e3637711c4d96a00038ab9ec935bf5 + React-jsinspectortracing: a12589a0adbb2703cbc4380dabe9a58800810923 + React-jsitracing: 0b1a403d7757cec66b7dd8b308d04db85eef75f3 + React-logger: 304814ae37503c8eb54359851cc55bd4f936b39c + React-Mapbuffer: b588d1ca18d2ce626f868f04ab12d8b1f004f12c + React-microtasksnativemodule: 11831d070aa47755bb5739069eb04ec621fec548 + react-native-biometrics: 352e5a794bfffc46a0c86725ea7dc62deb085bdc 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-bw-file-access: b232fd1d902521ca046f3fc5990ab1465e1878d7 + react-native-capture-protection: 96e391c860f9263ab372d46ddaef20f6cd91197f + react-native-document-picker: 1a1cf8084215ab2e4ace82891cc58cb659bb70cf + react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06 + react-native-image-picker: 186ad584e64736397c7d1f988d9d07bb0d85b942 + react-native-menu: aed489e483401e8e42cad0556b8989ea4409cf10 + react-native-safe-area-context: 63c9a6def15e6f6377e59cea51566148a2774d62 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 + react-native-tcp-socket: feb47ef87c6f598ec2a85b7534a279282ce6f925 + react-native-true-sheet: 36f837fe648251a68be6df22d9311d21c4092668 + React-NativeModulesApple: 79a4404ac301b40bec3b367879c5e9a9ce81683c + React-perflogger: 0ea25c109dba33d47dec36b2634bf7ea67c1a555 + React-performancetimeline: f74480de6efbcd8541c34317c0baedb433f27296 + React-RCTActionSheet: 2ef95837e89b9b154f13cd8401f9054fc3076aff + React-RCTAnimation: 33d960d7f58a81779eea6dea47ad0364c67e1517 + React-RCTAppDelegate: d9a3d89b3dd9bcfed7d7c55b0b7e48a97e5cb31e + React-RCTBlob: 74c986a02d951931d2f6ed0e07ed5a7eb385bfc0 + React-RCTFabric: bf6790a26671e95d9fd680000db641488fc0d0d4 + React-RCTFBReactNativeSpec: 5fe1cdf377ffb52bb3833e9f8f5cc03765b6a22e + React-RCTImage: 2c58b5ddeb3c65e52f942bbe13ff9c59bd649b09 + React-RCTLinking: b6b14f8a3e62c02fc627ac4f3fb0c7bd941f907c + React-RCTNetwork: 1d050f2466c1541b339587d46f78d5eee218d626 + React-RCTSettings: 8148f6be0ccc0cfe6e313417ebf8a479caaa2146 + React-RCTText: 64114531ad1359e4e02a4a8af60df606dbbabc25 + React-RCTVibration: f4859417a7dd859b6bf18b1aba897e52beb72ef6 + React-rendererconsistency: 5ac4164ec18cfdd76ed5f864dbfdc56a5a948bc9 + React-rendererdebug: 3dc1d97bbee0c0c13191e501a96ed9325bbd920e + React-rncore: 0bace3b991d8843bb5b57c5f2301ec6e9c94718b + React-RuntimeApple: 1e1e0a0c6086bc8c3b07e8f1a2f6ca99b50419a0 + React-RuntimeCore: d39322c59bef2a4b343fda663d20649f29f57fcc + React-runtimeexecutor: 876dfc1d8daa819dfd039c40f78f277c5a3e66a6 + React-RuntimeHermes: 44f5f2baf039f249b31ea4f3e224484fd1731e0e + React-runtimescheduler: 3b3c5b50743bb8743ca49b9e5a70c2c385f156e1 + React-timing: 1ee3572c398f5579c9df5bf76aacddf5683ff74e + React-utils: 0cfb7c7fb37d4e5f31cc18ffc7426be0ae6bf907 + ReactAppDependencyProvider: b48473fe434569ff8f6cb6ed4421217ebcbda878 + ReactCodegen: 78b64f0ad96ef733616f54d0c20923f6c67287fd + ReactCommon: 547db015202a80a5b3e7e041586ea54c4a087180 + ReactNativeCameraKit: a357c57696bfd1f124aebf3a27bce3bc3adf5bdc + RealmJS: 389361669e771fe4d13a86be48928d560d214294 + RNCAsyncStorage: 23e56519cc41d3bade3c8d4479f7760cb1c11996 + RNCClipboard: dfeb43751adff21e588657b5b6c888c72f3aa68e RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8 - RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31 - RNDeviceInfo: aad3c663b25752a52bf8fce93f2354001dd185aa + RNDefaultPreference: a617d8598db8a9168a5a6d9017dd2b42f834cb6e + RNDeviceInfo: 8b6fa8379062949dd79a009cf3d6b02a9c03ca59 RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 - RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39 + RNGestureHandler: 83e18e53d3388ac5cb165a1404bc3be1e4cbdad0 RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa - RNKeychain: ff836453cba46938e0e9e4c22e43d43fa2c90333 - RNLocalize: dbea38dcb344bf80ff18a1757b1becf11f70cae4 - RNPrivacySnapshot: 71919dde3c6a29dd332115409c2aec564afee8f4 + RNKeychain: 1cb3f8e1fa34d6ca74ebbf525469860656cbd328 + RNLocalize: 828754cd4edbe5db83e35c83f0df39d57dbc155d + RNPermissions: 2e8c61bbdb069de0eb8158762589e57208378b90 + RNQrGenerator: ac6a6c766e80dd3625038929ed2b13e2f3edcafb RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93 RNRate: ef3bcff84f39bb1d1e41c5593d3eea4aab2bd73a - RNReactNativeHapticFeedback: afa5bf2794aecbb2dba2525329253da0d66656df - RNReanimated: f186e85d9f28c9383d05ca39e11dd194f59093ec - RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f - RNShare: d82e10f6b7677f4b0048c23709bd04098d5aee6c - RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315 - RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8 + RNReactNativeHapticFeedback: 288ef2881046914e9108441e783f64899807282a + RNReanimated: 66107a435cfd8fa8f0fb909f270a438e130d4386 + RNScreens: 8f13e30fb86d3e2130643a1878e1198bd59ca453 + RNShare: 695fa820be00ea917f5d78e7bb67377d49f8542c + RNSVG: 9b6027ac1cb7fd45255987898622466d424f9175 + RNVectorIcons: 85ed086927e7548461d593e68b190c38a6606a05 RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236 - Yoga: f7decafdc5e8c125e6fa0da38a687e35238420fa + SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 + Yoga: e14bad835e12b6c7e2260fc320bd00e0f4b45add + ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 -PODFILE CHECKSUM: 8e9e7955e2b1beadc75774b0b7f8eb52f7299757 +PODFILE CHECKSUM: eb430c3fd96af23d4962c4c041798d65efc6843e -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/Widgets/Shared/Fiat/FiatUnit.swift b/ios/Shared/Bundle+decode.swift similarity index 79% rename from ios/Widgets/Shared/Fiat/FiatUnit.swift rename to ios/Shared/Bundle+decode.swift index eaf2c73e84e..0eec91eccff 100644 --- a/ios/Widgets/Shared/Fiat/FiatUnit.swift +++ b/ios/Shared/Bundle+decode.swift @@ -1,23 +1,12 @@ // -// FiatUnit.swift +// Bundle+decode.swift // BlueWallet // -// Created by Marcos Rodriguez on 11/20/20. -// Copyright © 2020 BlueWallet. All rights reserved. +// Created by Marcos Rodriguez on 4/14/24. +// Copyright © 2024 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 -} +import Foundation extension Bundle { func decode(_ type: T.Type, from file: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T { 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/Widgets/Shared/UserDefaultsExtension.swift b/ios/Shared/UserDefaultsExtension.swift similarity index 70% rename from ios/Widgets/Shared/UserDefaultsExtension.swift rename to ios/Shared/UserDefaultsExtension.swift index d08423adc59..b9200c9a154 100644 --- a/ios/Widgets/Shared/UserDefaultsExtension.swift +++ b/ios/Shared/UserDefaultsExtension.swift @@ -11,7 +11,7 @@ import Foundation extension UserDefaults { func codable(forKey key: String) -> Element? { - guard let data = UserDefaults.standard.data(forKey: key) else { return nil } + 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/Widgets/Shared/WidgetDataStore.swift b/ios/Shared/WidgetDataStore.swift similarity index 50% rename from ios/Widgets/Shared/WidgetDataStore.swift rename to ios/Shared/WidgetDataStore.swift index d5228ab1090..300972c4ab0 100644 --- a/ios/Widgets/Shared/WidgetDataStore.swift +++ b/ios/Shared/WidgetDataStore.swift @@ -1,33 +1,20 @@ // -// Created by Marcos Rodriguez on 11/3/19. +// WidgetDataStore.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: "") - } -} - 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.locale = Locale(identifier: Currency.getUserPreferredCurrencyLocale()) numberFormatter.numberStyle = .currency numberFormatter.maximumFractionDigits = 0 numberFormatter.minimumFractionDigits = 0 @@ -42,7 +29,7 @@ struct WidgetDataStore: Codable { var formattedRateForComplication: String? { let numberFormatter = NumberFormatter() - numberFormatter.locale = Locale(identifier: WidgetAPI.getUserPreferredCurrencyLocale()) + numberFormatter.locale = Locale(identifier: Currency.getUserPreferredCurrencyLocale()) numberFormatter.numberStyle = .currency numberFormatter.currencySymbol = "" if let rateString = numberFormatter.string(from: NSNumber(value: rateDouble)) { @@ -57,7 +44,7 @@ struct WidgetDataStore: Codable { dateFormatter.locale = Locale.current dateFormatter.timeStyle = .short - return isoDateFormatter.date(from: lastUpdate) + return isoDateFormatter.date(from: lastUpdate) } var formattedDate: String? { let isoDateFormatter = ISO8601DateFormatter() @@ -72,14 +59,4 @@ struct WidgetDataStore: Codable { } } -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/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..f50ceb7912e 100644 --- a/ios/Widgets/Info.plist +++ b/ios/Widgets/Info.plist @@ -30,5 +30,39 @@ apiKey 17ba9059f676f1cc4f45d98182388b01 + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + onion + + NSExceptionAllowsInsecureHTTPLoads + + NSIncludesSubdomains + + + tailscale.net + + NSExceptionAllowsInsecureHTTPLoads + + NSIncludesSubdomains + + + ts.net + + NSExceptionAllowsInsecureHTTPLoads + + NSIncludesSubdomains + + + + 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/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/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/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/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..b438a3252cc --- /dev/null +++ b/jest.config.js @@ -0,0 +1,15 @@ +module.exports = { + testEnvironment: '/tests/custom-environment.js', + reporters: ['default', ['/tests/custom-reporter.js', {}]], + preset: 'react-native', + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + }, + moduleFileExtensions: ['js', 'json', 'ts', 'tsx'], + transformIgnorePatterns: ['node_modules/(?!((jest-)?react-native(-.*)?|@react-native(-community)?)|@rneui|silent-payments|@arkade-os)/'], + moduleNameMapper: { + '^expo/fetch$': '/util/expo-fetch-nodejs.js', + }, + setupFiles: ['./tests/setup.js'], + watchPathIgnorePatterns: ['/node_modules'], +}; diff --git a/loc/ar.json b/loc/ar.json index 78781af1f3f..8903eaea070 100644 --- a/loc/ar.json +++ b/loc/ar.json @@ -4,33 +4,24 @@ "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": "تغيير عملة الادخال", "refresh": "تحديث", - "more": "المزيد", - "pick_image": "اختر صورة من الصور", "pick_file": "اختر ملف", "enter_amount": "أدخل القيمة", "qr_custom_input_button": "أنقر ١٠ مرات لإدخال قيمة مخصصة" }, - "alert": { - "default": "تنبيه" - }, "azteco": { "codeIs": "رمز القسيمة الخاص بك هو", "errorBeforeRefeem": "يجب عليك إضافة محفظة بتكوين أولًا قبل الاسترداد.", @@ -51,78 +42,50 @@ "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": "لايمكنك دفع البرقية من نفس المحفظة", - "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": "بالساتوشي", "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": "حسنًا، لقد حفظتها.", "text": "يُرجى أخذ لحظة من وقتك لكتابة هذه العبارة التذكيرية على ورقة. إنها وسيلتك الاحتياطية لاستعادة المحفظة على جهاز آخر.", "text_lnd": "يُرجى حفظ هذه النسخة الاحتياطية للمحفظة. لكي تتتمكن من استعادة المحفظة في حالة فقدها.", - "title": "تم إنشاء محفظتك" + "title": "تم إنشاء محفظتك..." }, "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}" }, @@ -161,7 +124,6 @@ "details_create": "إنشاء برقية", "details_error_decode": "يتعذَّر تجزئة وتحليل عنوان البتكوين", "details_fee_field_is_not_valid": "حقل الرسوم غير صالح", - "details_frozen": "{amount} بتكوين تم تجميدها", "details_next": "التالي", "details_no_signed_tx": "لا يحتوي الملف المحدَّد على معاملة موقَّعة يمكن استيرادها.", "details_note_placeholder": "ملاحظة شخصية", @@ -193,8 +155,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,11 +164,9 @@ "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" }, "settings": { @@ -225,17 +183,12 @@ "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": "ستتم إزالة جميع محافظك وفك تشفير التخزين الخاص بك. هل انت متأكد انك تريد المتابعة؟", "currency": "العملة", - "currency_source": "تم الاستعلام عن السعر عن طريق", "currency_fetch_error": "حدث خطأ أثناء الحصول على سعر العملة المحددة.", "default_desc": "عند تعطيل هذا الإعداد، ستفتح BlueWallet المحفظة المحدَّدة فور التشغيل.", "default_info": "المعلومات الافتراضية", @@ -243,7 +196,6 @@ "default_wallets": "عرض جميع المحافظ", "electrum_connected": "متصل", "electrum_connected_not": "غير متصل", - "electrum_error_connect": "يتعذَّر الاتصال بخادم Electrum المقدَّم", "lndhub_uri": "على سبيل المثال، {example}", "electrum_host": "على سبيل المثال، {example}", "electrum_offline_mode": "وضع عدم الاتصال بالشبكة", @@ -252,32 +204,16 @@ "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. اترك الحقل فارغًا لاستخدام الإعدادات الافتراضية", @@ -285,11 +221,8 @@ "language": "اللغة", "last_updated": "آخر تحديث", "language_isRTL": "يجب اعادة تشغيل BlueWallet حتى تظهر تعديلات تغيير اللغة.", - "lightning_error_lndhub_uri": "معرِّف URI لبرنامج تضمين LndHub غير صالح", "lightning_saved": "تم حفظ تغييراتك بنجاح", "lightning_settings": "إعدادات البرق", - "tor_settings": "اعدادات tor", - "lightning_settings_explain": "للاتصال بنود LND الخاص بك، يُرجى تثبيت LNDHub ثم وضع رابطه هنا في الإعدادات. تذكر: فقط المحافظ التي يتم إنشاؤها بعد حفظ التغييرات ستتصل بنود LNDHub التي قمت بإضافتها.", "network": "الشبكة", "network_broadcast": "بث العملية", "network_electrum": "خادم Electrum", @@ -297,33 +230,25 @@ "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": "المس مع الاستمرار ايقونة تطبيق 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_explanation": "اعرض الرصيد الإجمالي لجميع محافظك على ويدجت الشاشة الرئيسية الخاصة بك.", "widgets": "ويدجت", "tools": "ادوات" }, "notifications": { - "would_you_like_to_receive_notifications": "هل ترغب في تلقي إشعارات عند استلام مدفوعات؟", - "no_and_dont_ask": "لا، ولا تسألني مرة أخرى", - "ask_me_later": "اسالني لاحقا" + "would_you_like_to_receive_notifications": "هل ترغب في تلقي إشعارات عند استلام مدفوعات؟" }, "transactions": { "cancel_explain": "سنستبدل هذه المعاملة بمعاملة ذات رسوم أعلى وتُدفع لك انت؛ سيؤدي ذلك الى الغاء المعاملة واعادة المبلغ لمحفظتك. وهذا ما يُسمَّى RBF؛ أي الاستبدال بالرسوم.", @@ -338,9 +263,7 @@ "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": "نسخ معرّف المعاملة", @@ -349,8 +272,6 @@ "details_outputs": "المخرجات", "date": "التاريخ", "details_received": "التاريخ", - "transaction_note_saved": "تم حفظ مذكرة المعاملة بنجاح.", - "details_show_in_block_explorer": "العرض في مستكشف الكتل", "details_title": "العملية", "details_to": "إلى", "enable_offline_signing": "هذه المحفظة لا يتم استعمالها مع التوقيع دون اتصال. هل ترغب في تمكينه الآن؟", @@ -363,6 +284,7 @@ "eta_1d": "الوقت المقدر للتأكيد: في حوالي يوم واحد", "view_wallet": "عرض {walletLabel}", "list_title": "العمليات", + "transaction": "العملية", "open_url_error": "تعذر فتح الرابط باستخدام المتصفح الافتراضي. يرجى تغيير المتصفح الافتراضي الخاص بك وحاول مرة أخرى.", "rbf_explain": "سنستبدل هذه المعاملة بمعاملة جديدة تدفع رسوم اعلى؛ حتى يجري تعدينها بشكلٍ أسرع. وهذا ما يُسمَّى RBF؛ أي الاستبدال بالرسوم.", "rbf_title": "تسريع العملية (RBF)", @@ -370,50 +292,46 @@ "status_cancel": "إلغاء العملية", "transactions_count": "عدد العمليات", "txid": "معرّف العملية", - "updating": "جارٍ التحديث ..." + "updating": "جارٍ التحديث ...", + "watchOnlyWarningDescription": "تنبيه احتيال: انتبه إلى أن المحتالين عادةً ما يستخدمون هذا النوع من المحفظة \"للمشاهدة فقط\" لمحاولة السرقة من المستخدمين. هذه المحفظة التي لا يمكنك التحكم بها أو الإرسال منها، إلا بتصريح جهاز آخر، المحفظة تسمح فقط بمراقبة الرصيد." }, "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_delete": "الحذف", "details_delete_wallet": "حذف المحفظة", "details_derivation_path": "مسار الاشتقاق (derivation path)", - "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": "تصدير المحفظة", @@ -433,44 +351,37 @@ "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": "اضغط باستمرار على اي محفظة لتحريكها عبر القائمة", "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 للمحفظة" }, + "total_balance_view": { + "title": "الرصيد الاجمالي" + }, "multisig": { - "multisig_vault": "خزنة", + "multisig_vault": "خزنة متعددة التواقيع", "default_label": "خزنة متعددة التواقيع", "multisig_vault_explain": "افضل حماية للمبالغ الكبيرة", "provide_signature": "قدم توقيعًا", @@ -480,7 +391,6 @@ "fee_btc": "{number} BTC", "confirm": "تأكيد", "header": "إرسال", - "share": "المشاركة", "view": "عرض", "manage_keys": "إدارة المفاتيح", "how_many_signatures_can_bluewallet_make": "كم عدد التوقيعات التي يمكن لـ BlueWallet ان تنشأها", @@ -507,20 +417,14 @@ "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": "تم انشاء خزنتك. يُرجى أخذ لحظة من وقتك لتدوين عبارة الاسترداد في مكان آمن.", "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)", @@ -545,21 +449,20 @@ "owns": "{label} يملك {address}", "enter_address": "أدخل العنوان", "check_address": "تحقق من العنوان", - "no_wallet_owns_address": "لا تمتلك أي من المحافظ المتاحة هذا العنوان.", - "view_qrcode": "عرض رمز الاستجابة السريع (QR)" + "no_wallet_owns_address": "لا تمتلك أي من المحافظ المتاحة هذا العنوان." }, "cc": { "change": "تغيير", "coins_selected": "العملات المحددة ({number})", "selected_summ": "{value} مختاره", - "empty": "لا تحتوي هذه المحفظة على أي عملات في الوقت الحالي.", "freeze": "تجميد", "freezeLabel": "تجميد", "freezeLabel_un": "فك التجميد", "header": "التحكم في العملات", "use_coin": "استخدم العملة", "use_coins": "استخدام العملات", - "tip": "تتيح لك هذه الميزة رؤية العملات أو تسميتها أو تجميدها أو تحديدها لتحسين إدارة المحفظة. يمكنك تحديد عدة عملات من خلال النقر على الدوائر الملونة." + "tip": "تتيح لك هذه الميزة رؤية العملات أو تسميتها أو تجميدها أو تحديدها لتحسين إدارة المحفظة. يمكنك تحديد عدة عملات من خلال النقر على الدوائر الملونة.", + "sort_status": "الحالة" }, "units": { "BTC": "BTC", @@ -601,8 +504,6 @@ }, "bip47": { "payment_code": "كود الدفع", - "payment_codes_list": "قائمة أكواد الدفع", - "who_can_pay_me": "من يستطيع الدفع لي:", "purpose": "أكواد المشاركة التي يمكن أعادة استخدامها (BIP47)", "not_found": "لم يتم العثور على كود الدفع" } diff --git a/loc/be@tarask.json b/loc/be@tarask.json index 069dcfd0d72..af516178a3d 100644 --- a/loc/be@tarask.json +++ b/loc/be@tarask.json @@ -4,24 +4,17 @@ "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": "Папка Запамповак" - }, - "alert": { - "default": "Папярэджанне" + "wallet_key": "Ключ ад кашалька" }, "azteco": { "codeIs": "Ваш код ваўчара", @@ -42,17 +35,7 @@ "network": "Памылка Сеткі" }, "lnd": { - "inactive": "Неактыўны", - "channels": "Каналы", - "no_channels": "Няма каналаў", - "close_channel": "Зачыніць канал", - "new_channel": "Новы канал", - "errorInvoiceExpired": "Тэрмін дзеяння рахункі скончыўся", - "expired": "Скончыўся", - "open_channel": "Адкрыць Канал" - }, - "plausibledeniability": { - "success": "Посьпех" + "expired": "Скончыўся" }, "send": { "broadcastError": "Памылка", @@ -60,10 +43,9 @@ "create_to": "Да" }, "settings": { - "electrum_clear_alert_cancel": "Адмяніць", "save": "Захаваць" }, "wallets": { - "details_save": "Захаваць" + "add_entropy": "Энтрапія" } } diff --git a/loc/bg_bg.json b/loc/bg_bg.json index 424891a7323..5f07f1c7a7a 100644 --- a/loc/bg_bg.json +++ b/loc/bg_bg.json @@ -3,6 +3,7 @@ "bad_password": "Грешна парола, опитайте отново.", "cancel": "Отказ", "continue": "Продължи", + "discard_changes": "Отказваш промените?", "enter_password": "Въведете парола", "never": "Никога", "of": "{number} от {total}", @@ -10,13 +11,9 @@ "storage_is_encrypted": "Вашият портфейл е криптиран. Необходима е парола за декриптиране", "yes": "Да", "no": "Не", - "save": "Запази", "seed": "Сиид", "success": "Успех", - "wallet_key": "Парола на портфейла", - "invalid_animated_qr_code_fragment": "Невалиден анимиран QRCode фрагмент. Моля, опитай отново.", - "file_saved": "Файлът {filePath} беше запазен в {destination}.", - "downloads_folder": "Папка с изтегляния" + "wallet_key": "Парола на портфейла" }, "azteco": { "codeIs": "Цода на вашият ваучър е", @@ -38,50 +35,41 @@ "network": "Грешка с мрежата" }, "lnd": { - "errorInvoiceExpired": "Изтекла фактура", "expired": "Изтекла", "payButton": "Плати", - "placeholder": "Фактура", "potentialFee": "Възможна такса: {fee}", "refill": "Зареди", "refill_create": "За да продължите, моля създайте Биткойн портфейл", "refill_external": "Зареди с Външен Портфейл", "refill_lnd_balance": "Зареди Лайтнинг Баланс", - "sameWalletAsInvoiceError": "Не можете да платите фактура със същия портфейл в който е създадена", "title": "Управление на средства" }, "lndViewInvoice": { "additional_info": "Допълнителна информация", "for": "За:", "lightning_invoice": "Лайтнинг фактура", - "open_direct_channel": "Директно свързване с нода:", "please_pay": "Моля, плати", "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": "Plausible Deniability" }, "pleasebackup": { "ask": "Запазихте ли паролата - 12/24 думи за портфейла? В случай, че изгубите достъп до устройството, паролата е необходима за да въстановите средствата. В случай, че загубите паролата - 12/24 думи, перманентно ще изгубите достъп до средствата.", - "ask_no": "Не, не съм", - "ask_yes": "Да", - "text_lnd": "Моля, запазете паролата/ думите. Те ви позволяват да възтановите портфейла и средствата си на друго устройство." + "ok_lnd": "Да, запазих паролата.", + "text_lnd": "Моля, запазете паролата/ думите. Те ви позволяват да възтановите портфейла и средствата си на друго устройство.", + "title": "Портфейла е създаден." }, "receive": { "details_create": "Създай", "details_label": "Описание", "details_setAmount": "Получаване на определена сума", - "details_share": "Сподели", "header": "Получаване" }, "send": { @@ -116,7 +104,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": "Сумата надвишава наличният баланс.", @@ -140,11 +127,8 @@ "input_paste": "Постави", "input_total": "Тотално:", "permission_camera_message": "Необходимо е вашето разрешение за достъп до камерата.", - "permission_camera_title": "Разрешение за достъп до камерата", "psbt_sign": "Подпиши транзакция", "open_settings": "Отвори настройки", - "permission_storage_later": "Питай ме по-късно", - "permission_storage_message": "Блу Уолет се нуждае от достъп до папките с файлове за да запази този файл.", "permission_storage_title": "Достъп до папките с файлове", "psbt_clipboard": "Копирай", "psbt_this_is_psbt": "Това е Частично Подписана Биткойн Транзакция (ЧПБТ - en. PSBT). Моля, подпишете транзакцията на хардуер портфейла си.", @@ -153,7 +137,6 @@ "psbt_tx_open": "Отвори подписана транзакция", "psbt_tx_scan": "Подпиши транзакция", "success_done": "Готово", - "txSaved": "Файл с трансакцията ({filePath}) беше запазен в папката Свалени.", "problem_with_psbt": "Проблем с ЧПБТ / PSBT" }, "settings": { @@ -167,14 +150,10 @@ "about_selftest": "Направете селф-тест", "about_selftest_ok": "Теста прмина успешно. Портфейла работи отлично.", "about_sm_github": "GitHub", - "about_sm_discord": "Discord", "about_sm_telegram": "Telegram", - "about_sm_twitter": "Последвайте ни в Twitter", - "advanced_options": "Допълнителни Опции", "biometrics": "Биометрични данни", "biom_10times": "Опитахте се да въведете вашата парола 10 пъти. Желаете ли да направите ресет на апликацията? Това ще изтрие всички портфейли и ще декриптира хранилището.", "biom_conf_identity": "Моля, потвърдете вашата самоличност.", - "biom_no_passcode": "Вашето устройство няма създадена парола. За да продължите, конфигурирайте парола в 'Настройки'на устройството.", "biom_remove_decrypt": "Всички портфейли ще бъдат изтрити и хранилището ще бъде декриптирано. Сигурни ли сте, че искате да продължите?", "currency": "Валута", "default_info": "Информация", @@ -182,53 +161,40 @@ "default_wallets": "Виж всички портфейли", "electrum_connected": "Свързан", "electrum_connected_not": "Няма връзка", - "electrum_error_connect": "Невъзможно свързване със посочения Електрум сървър", "electrum_saved": "Промените бяха запазени успешно. Моля, рестартирайте Блу Уолет за да видите промените.", "set_electrum_server_as_default": "Задайте {server} като Електрум сървър по подразбиране? ", - "set_lndhub_as_default": "Задайте {url} като LNDHub сървър по подразбиране?", "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": "Изчисти", + "electrum_reset": "Начални настройки", "encrypt_decrypt": "Декриптирай хранилището", "encrypt_decrypt_q": "Сигурни ли сте, че искате да декриптирате хранилището? Това ще направи портфейлите ви достъпни без парола.", - "encrypt_enc_and_pass": "Криптиран и защитен с парола", "encrypt_title": "Сигурност", "encrypt_tstorage": "Хранилище", "encrypt_use": "Използвай {type}", "general": "Основно", - "general_adv_mode": "Развирени Настройки", "plausible_deniability": "Plausible Deniability", - "retype_password": "Повторете паролата", "save": "Запази" }, - "notifications": { - "ask_me_later": "Питай ме по-късно" - }, "transactions": { "cpfp_create": "Създай", "pending": "Не потвърдена транзакция" }, "wallets": { "add_create": "Създай", + "add_entropy": "Ентропия", "details_address": "Адрес", - "details_save": "Запази" + "paste_from_clipboard": "Постави" }, "multisig": { "confirm": "Потвърди", "header": "Изпрати", - "share": "Сподели", "create": "Създай", "co_sign_transaction": "Подпиши транзакция", "ms_help_title5": "Развирени Настройки" }, + "cc": { + "sort_status": "Статус" + }, "addresses": { "sign_placeholder_address": "Адрес", "type_receive": "Получаване" diff --git a/loc/bqi.json b/loc/bqi.json new file mode 100644 index 00000000000..1e865950137 --- /dev/null +++ b/loc/bqi.json @@ -0,0 +1,373 @@ +{ + "_": { + "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": "پؽشنهاڌی" + }, + "azteco": { + "codeIs": "کوڌ تخفیف ایسا", + "errorBeforeRefeem": "پؽش ز فعال کردن ٱول وا ی کیف پیل بیت کوین ازاف کۊنین.", + "errorSomething": "موشکلؽ پؽش ٱووڌ. ای کوڌ تخفیف هنی زبال هڌ؟", + "redeem": "ازاف کردن و کیف پیل", + "redeemButton": "فعال کردن", + "success": "سر ٱنجوم گرهڌ", + "title": "فعال کردن کوڌ تخفیف Azte.co" + }, + "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": "انکار مووجه" + }, + "pleasebackup": { + "ask_no": "ن، مو نڌاروم.", + "ask_yes": "هری، مو داروم.", + "ok": "خا، هو ن هؽل کردوم.", + "ok_lnd": "خا، هو ن زفت کردوم.", + "title": "کیف پیل ایسا وورکل وابی." + }, + "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 کود سی ای نشۊوی" + }, + "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": "ویرداشت و خوت", + "counterparty_label_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" + }, + "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_info": "دووسمندیا پؽش فرز", + "default_title": "موقه ره وندن", + "default_wallets": "نیشتن پوی کیفا پیل", + "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": "اوزارا" + }, + "transactions": { + "cancel_title": "ای تراکونشن لقو کوݩ (RBF)", + "confirmations_lowercase": "{confirmations} تاییڌ", + "copy_link": "لف گیری لینگ", + "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": "تراکونش", + "offchain": "آفچین", + "onchain": "آنچین", + "pending": "مندیر سی زفت", + "transaction": "تراکونش", + "open_url_error": "نا مووفق منه گۊشیڌن لینگ وا گشت گر پؽش فرز. گشت گر پؽش فرز خوته آلشت کوݩ ۉ ز نۊ تفره کو." + }, + "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": "و هر هال پاک بۊ" + }, + "total_balance_view": { + "display_in_bitcoin": "نشووݩ داڌن من بیت کوین", + "hide": "بؽڌار", + "display_in_sats": "نشووݩ داڌن و ری ساتۊشی", + "display_in_fiat": "نشووݩ داڌن و ری (ٱرز)", + "title": "پوی مۉجۊدی" + }, + "multisig": { + "confirm": "تاییڌ", + "header": "فشناڌن", + "share": "یک رسۊوی...", + "create": "وورکل", + "co_sign_transaction": "امزا کردن تراکونش", + "ms_help_title5": "هالت پؽش رئڌه" + }, + "cc": { + "sort_status": "وزیت" + }, + "addresses": { + "sign_placeholder_address": "آدرس", + "type_receive": "گرؽڌن" + } +} diff --git a/loc/ca.json b/loc/ca.json index 110d260bb15..389cf9ae5b7 100644 --- a/loc/ca.json +++ b/loc/ca.json @@ -4,23 +4,23 @@ "cancel": "Cancel·lar", "continue": "Continuar", "clipboard": "Porta-retalls", + "discard_changes": "Descartar els canvis?", "enter_password": "Introduïu la contrasenya", "never": "mai", - "disabled": "Desactivat", "of": "{number} de {total}", "ok": "OK", + "enter_url": "Entrar URL", "storage_is_encrypted": "L'informació està xifrada. Es requereix la contrasenya per a desxifrar-la.", "yes": "Si", "no": "No", - "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", + "enter_amount": "Introdueix la quantitat", + "qr_custom_input_button": "Toqueu 10 vegades per introduir una entrada personalitzada" }, "azteco": { "codeIs": "El codi del teu val és", @@ -39,67 +39,47 @@ "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?", "expired": "Caducat", - "node_alias": "Àlies del node", "expiresIn": "Caduca en {time} minuts", "payButton": "Pagar", - "placeholder": "Factura", - "open_channel": "Obra el canal", - "potentialFee": "Comissió potencial: {fee}", + "payment": "Pagament", + "placeholder": "Factura o adreça", "refill": "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", "sats": "sats", "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.", "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", "ok_lnd": "D'acord, l'he guardat", "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" }, "send": { @@ -134,7 +114,6 @@ "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_next": "Següent", "details_note_placeholder": "comentari (útil per tu)", "details_scan": "Escanejar", @@ -162,8 +141,6 @@ "permission_camera_message": "Necessitem el vostre permís per usar la vostra càmera.", "psbt_sign": "Signar una transacció", "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_title": "Permís d'accés a emmagatzematge", "psbt_clipboard": "Copiar al portapapers", "psbt_tx_export": "Exportar a arxiu", @@ -177,10 +154,7 @@ "about_review": "Deixa'ns una ressenya", "about_selftest": "Executa l'autotest", "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", "biometrics": "Biometria", "biom_conf_identity": "Si us plau, confirmeu la vostra identitat.", "currency": "Moneda", @@ -190,48 +164,30 @@ "electrum_connected_not": "No conectat", "use_ssl": "Utilitza SSL", "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_reset": "Restableix la configuració predeterminada", - "electrum_clear": "Neteja", - "tor_supported": "Tor suportat", - "tor_unsupported": "Les conexions Tor no estan suportades", "encrypt_title": "Seguretat", "encrypt_use": "Utilitza {type}", "general": "General", - "general_adv_mode": "Habilitar el mode avançat", "header": "Configuració", "language": "Idioma", "last_updated": "Última actualització", - "lightning_error_lndhub_uri": "LNDHub URI no vàlid", "lightning_settings": "Configuració Lightning", - "tor_settings": "Configuració de Tor", "network": "Xarxa", "network_electrum": "Servidor Electrum", "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", "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", "save": "guardar", "saved": "Desat", "total_balance": "Saldo total", "tools": "Eines" }, - "notifications": { - "no_and_dont_ask": "No, i no m'ho tornis a preguntar", - "ask_me_later": "Pregunta'm després" - }, "transactions": { "cancel_title": "Cancel·lar aquesta transacció (RBF)", "confirmations_lowercase": "{confirmations} confirmacions", @@ -239,22 +195,19 @@ "cpfp_create": "Crear", "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_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_title": "Transacció", "details_to": "A", "list_conf": "Conf: {number}", "pending": "Pendent", "view_wallet": "Veure {walletLabel}", "list_title": "transaccions", + "transaction": "Transacció", "status_cancel": "Cancel·lar la Transacció", "txid": "ID de la transacció", "updating": "Actualitzant..." @@ -263,6 +216,8 @@ "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Moneder de Bitcoin senzill i potent", "add_create": "Crear", + "total_balance": "Saldo total", + "add_entropy": "Entropia", "add_import_wallet": "Importar moneder", "add_lightning": "Lightning", "add_title": "Afegir moneder", @@ -276,13 +231,11 @@ "details_delete_wallet": "Eliminar Moneder", "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_title": "Detalls del moneder", + "wallets": "moneders", "details_type": "Tipus", "details_use_with_hardware_wallet": "Usar amb un moneder hardware", - "details_wallet_updated": "Moneder actualitzat", "details_yes_delete": "Si, eliminar", "export_title": "Exportació de moneder", "import_do_import": "Importar", @@ -291,27 +244,27 @@ "import_scan_qr": "o escanejar codi QR?", "import_success": "Èxit", "import_title": "importar", - "import_derivation_found": "trobat", - "import_derivation_found_not": "No trobat", "list_create_a_button": "Afegir ara", "list_create_a_wallet": "Afegeix un moneder", "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_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", "select_wallet": "Seleccioni moneder", "xpub_copiedToClipboard": "Copiat al porta-retalls." }, + "total_balance_view": { + "title": "Saldo total" + }, "multisig": { - "multisig_vault": "Bóveda", "fee_btc": "{number} BTC", "confirm": "Confirmar", "header": "Enviar", - "share": "Compartir", "view": "Vista", "create": "Crear", "co_sign_transaction": "Signar una transacció", @@ -322,6 +275,10 @@ "ms_help": "Ajuda", "ms_help_title5": "Habilitar el mode avançat" }, + "cc": { + "sort_label": "Etiqueta", + "sort_status": "estat" + }, "units": { "sats": "sats" }, diff --git a/loc/cs_cz.json b/loc/cs_cz.json index e6d5b542140..8b567be26fe 100644 --- a/loc/cs_cz.json +++ b/loc/cs_cz.json @@ -1,320 +1,320 @@ { "_": { - "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", + "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", + "port": "Port", + "ssl_port": "SSL port", + "suggested": "Doporučené" }, "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.", + "date_time": "Datum a čas", "wasnt_paid_and_expired": "Tato faktura nebyla zaplacena a její platnost vypršela." }, "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", + "counterparty_label_placeholder": "Upravit jméno kontaktu", "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 teno 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ěnily a již neodpovídají zvoleným nastavením zabezpečení v aplikaci. Aktivujte prosím znovu biometrii nebo přístupový kód, a poté aplikaci restartujte, aby se tyto změny projevily.", + "biom_10times": "Pokusili jste se zadat heslo 10×. Chcete obnovit úložiště? Tím odstraníte všechny peněženky a dešifrujete úložiště.", + "biom_conf_identity": "Potvrďte prosím svoji identitu.", + "biom_no_passcode": "Vaše zařízení nemá povolené odemykání pomocí přístupového kódu nebo biometriky. Abyste mohli pokračovat, nastavte prosím přístupový kód nebo biometriku v aplikaci Nastavení.", "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_desc": "Pokud je možnost zakázána, BlueWallet při spuštění ihned otevře vybranou peněženku.", + "default_info": "Výchozí peněženka", "default_title": "Při spuštění", "default_wallets": "Zobrazit všechny peněženky", "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_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ě.", + "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.", "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 +322,364 @@ }, "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", + "copy_link": "Zkopírovat odkaz", "expand_note": "Rozbalit poznámku", "cpfp_create": "Vytvořit", - "cpfp_exp": "Vytvoříme další transakci, která utratí vaši nepotvrzenou transakci. Celkový poplatek bude vyšší než původní transakční poplatek, takže by měl být těžen rychleji. Tomu se říká CPFP - 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_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_from": "Vstup", "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", + "eta_10m": "Doručení: Za ~10 minut", + "eta_3h": "Doručení: Za ~3 hodiny", + "eta_1d": "Doručení: Za ~1 den", "view_wallet": "Zobrazit {walletLabel}", "list_title": "Transakce", - "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)", + "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", + "from": "Od: {counterparty}", + "to": "Pro: {counterparty}", + "updating": "Aktualizování…", + "watchOnlyWarningTitle": "Bezpečnostní upozornění", + "watchOnlyWarningDescription": "Dávejte si pozor na podvodníky, kteří často používají peněženky „pouze pro sledování“ ke klamání uživatelů. Tyto peněženky vám neumožňují kontrolovat nebo odesílat prostředky; umožňují vám pouze zobrazit zůstatek.", + "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." }, "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", "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ý spolupodepsaný: toto není spolupodepsaný pro {format} formát.", + "create_new_key": "Vytvořit nový", "scan_or_open_file": "Naskenujte nebo otevřete soubor", - "i_have_mnemonics": "Mám seed pro tento klíč...", - "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? Vaš mnemotechnický seed bude ztracen, pokud nemáte zálohu.", + "forget_this_seed": "Zapomenout tento seed a použít namísto něj XPUB.", "view_edit_cosigners": "Zobrazit/upravit spolupodepisující", - "this_cosigner_is_already_imported": "Tento spolupodepisující je již importován", - "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": { + "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": "Change", + "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..1f1415c0656 100644 --- a/loc/cy.json +++ b/loc/cy.json @@ -3,6 +3,7 @@ "bad_password": "Cyfrinair anghywir. Tria eto. ", "cancel": "Canslo", "continue": "Parhau", + "discard_changes": "Gwaredu newidiadau?", "enter_password": "Cyfrinair", "never": "Byth", "of": "{number} o {total}", @@ -10,7 +11,6 @@ "storage_is_encrypted": "Mae'r storfa wedi encryptio. Mae angen Cyfrinair i'w ddad-gryptio. ", "yes": "Ie", "no": "Na", - "save": "Safio", "seed": "Hadyn", "success": "Llwyddiant", "wallet_key": "Allwedd waled" @@ -28,11 +28,8 @@ "network": "Camgymeriad rhwydwaith" }, "lnd": { - "errorInvoiceExpired": "Anfoneb wedi gorffen", "expired": "Gorffen", "payButton": "Talu", - "placeholder": "Anfoneb", - "potentialFee": "Ffi Tebygol: {fee}", "refill": "Ail-lenwi", "refill_create": "Er mwyn parhau, ffurfia waled Bitcoin i'w ail-lenwi. ", "refill_external": "Ail-lenwi efo Waled Allanol", @@ -44,24 +41,21 @@ "additional_info": "Gwybodaeth Ychwanegol", "for": "Ar Gyfer:", "lightning_invoice": "Anfoneb Mellten", - "open_direct_channel": "Agor sianel uniongyrchol efo'r nodyn hwn:", "please_pay": "Talu", "wasnt_paid_and_expired": "Ni chafodd yr anfoneb ei thalu, ac mae wedi gorffen." }, "plausibledeniability": { "create_fake_storage": "Creu storfa wedi encryptio", - "create_password": "Creu cyfrinair", "create_password_explanation": "Ni ddyliai'r cyfrinair ar gyfer y storfa ffug fod yr un cyfrinair ag ar gyfer y brif storfa", - "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" + "password_should_not_match": "Mae'r cyfrinair yn cael ei ddefnyddio'n barod. Tria gyfrinair gwahanol." + }, + "pleasebackup": { + "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" }, "send": { @@ -104,7 +98,6 @@ "input_paste": "Pastio", "input_total": "Cyfanswm:", "permission_camera_message": "Angen dy ganiatad i ddefnyddio dy gamera.", - "permission_camera_title": "Caniatad i ddefnyddio'r camera", "psbt_sign": "Arwyddo trafodyn", "open_settings": "Agor Gosodiadau", "success_done": "Wedi Cwblhau" @@ -114,10 +107,6 @@ "default_wallets": "Gweld Pob Waled", "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", "encrypt_decrypt": "Dadgryptio'r Storfa", "encrypt_title": "Diogelwch", "encrypt_tstorage": "Storfa", @@ -132,10 +121,7 @@ "network_broadcast": "Darlledu Trafodyn", "notifications": "Hysbysebion", "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", "save": "Safio", "saved": "Wedi Safio", "total_balance": "Balans Llawn" @@ -152,11 +138,13 @@ "details_title": "Trafodyn", "details_to": "Allbwn", "pending": "Disgwyl", - "list_title": "Trafodion" + "list_title": "Trafodion", + "transaction": "Trafodyn" }, "wallets": { "add_bitcoin_explain": "Waled Bitcoin syml a cryf", "add_create": "Creu", + "total_balance": "Balans Llawn", "add_import_wallet": "Mewnforio waled", "add_lightning": "Mellten", "add_lightning_explain": "Er mwyn gwario yn syth", @@ -171,9 +159,8 @@ "details_connected_to": "Wedi cysylltu efo", "details_delete": "Gwaredu", "details_delete_wallet": "Gwaredu Waled", - "details_no_cancel": "Na, canslo", - "details_save": "Safio", "details_show_addresses": "Dangos cyfeiriadau", + "wallets": "Waledi", "details_type": "Math", "details_yes_delete": "Ia, dileu", "enter_bip38_password": "Angen cyfrinair i ddad-gryptio", @@ -189,18 +176,20 @@ "list_empty_txs2": "Dechrau efo dy waled", "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_title": "Waledi", "list_tryagain": "Trio eto", "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." + "pull_to_refresh": "Tynnu i Adnewyddu" + }, + "total_balance_view": { + "title": "Balans Llawn" }, "multisig": { "confirm": "Cadarnhau", "header": "Anfon", - "share": "Rhannu", "view": "Golwg", "create": "Creu", "co_sign_transaction": "Arwyddo trafodyn", diff --git a/loc/da_dk.json b/loc/da_dk.json index 997badb7227..7f81c15fa42 100644 --- a/loc/da_dk.json +++ b/loc/da_dk.json @@ -6,7 +6,6 @@ "never": "aldrig", "ok": "OK", "storage_is_encrypted": "Lageret er krypteret. Indtast adgangskode for at dekryptere", - "save": "save", "success": "Succes" }, "azteco": { @@ -22,21 +21,16 @@ }, "plausibledeniability": { "create_fake_storage": "Opret falsk kryopteret lager", - "create_password": "Opret adgangskode", "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.", "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" }, "receive": { "details_create": "Opret", "details_label": "Beskrivelse", "details_setAmount": "Modtag med beløb", - "details_share": "del", "header": "Modtag" }, "send": { @@ -65,26 +59,21 @@ "settings": { "about": "Andet", "currency": "Valuta", - "electrum_clear_alert_cancel": "Annuller", - "general_adv_mode": "Enable advanced mode", "header": "indstillinger", "language": "Sprog", "lightning_settings": "Lightning settings", "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" }, "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" }, "wallets": { "add_bitcoin_explain": "Simple and powerful Bitcoin wallet", @@ -96,8 +85,6 @@ "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_yes_delete": "Ja, slet", "export_title": "wallet eksport", @@ -111,16 +98,17 @@ "list_create_a_wallet": "Tilføj en tegnebog", "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." }, "multisig": { "confirm": "Bekræft", - "share": "del", "create": "Opret", "ms_help_title5": "Enable advanced mode" }, + "cc": { + "sort_label": "Etiket" + }, "addresses": { "sign_placeholder_address": "adresse", "type_receive": "Modtag", diff --git a/loc/de_de.json b/loc/de_de.json index 04be97109c9..6eab315ad9a 100644 --- a/loc/de_de.json +++ b/loc/de_de.json @@ -4,32 +4,31 @@ "cancel": "Abbrechen", "continue": "Fortsetzen", "clipboard": "Zwischenablage", + "discard_changes": "Änderungen verwerfen?", + "discard_changes_explain": "Die ungespeicherten Änderungen verwerfen und den Bildschrim 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", "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", @@ -38,12 +37,14 @@ "redeem": "Einlösen in eine Wallet", "redeemButton": "Einlösen", "success": "Erfolg", + "successMessage": "Gutschein erfolgreich eingelöst! Ihre Gelder sollten in Kürze in Ihrer Bitcoin-Wallet ankommen.", "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,67 +52,46 @@ "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" + "sameWalletAsInvoiceError": "Die Rechnung lässt sich nicht mit gleichen 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", "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.", + "help": "BlueWallet erlaubt die Erstellung eines zweiten verschlüsselten Speichers mit eigenem Passwort. Sodass unter Zwang das Passwort preiszugeben, dann dies, anstelle des richtigen Passwortes genannt werden kann. BlueWallet öffnet dann die Wallet, welche im zweiten Speicher zur Täuschung angelegt ist und der Hauptspeicher bleibt geheim und sicher.", "help2": "Der zweite Speicher ist funktional identisch. Zahle auf die darin angelegten Wallet ein Minimalbetrag ein, um die Täuschung glaubhafter zu machen.", "password_should_not_match": "Das Passwort für den täuschenden Speicher darf nicht mit dem deines Hauptspeichers übereinstimmen", - "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 des Wallets gesichert? Ohne Sie lässt sich nie mehr auf die bitcoin zugreifen und sie wären für immer verloren, sollte das Gerät verloren oder kaputt gehen.", "ask_no": "Nein, habe ich nicht.", "ask_yes": "Ja, habe ich.", - "ok": "Ok, 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.", + "ok": "Ok, sie sind notiert.", + "ok_lnd": "Die Sicherung ist erstellt.", + "text": "Unbedingt die mnemonischen Wörter auf Papier schreiben.\nDiese Wörter sind das Backup zur Wallet-Wiederherstellung.", "text_lnd": "Zur Wiederherstellung des Wallet im Verlustfall bitte dieses Wallet-Backup sichern. ", "title": "Dein Wallet ist erstellt." }, @@ -120,11 +100,15 @@ "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.", @@ -146,14 +130,21 @@ "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 +152,14 @@ "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", + "counterparty_label_placeholder": "Kontaktnamen bearbeiten", "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", @@ -180,6 +173,7 @@ "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.", @@ -192,23 +186,26 @@ "input_total": "Gesamt:", "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 Stille Zahlung Adressen senden", + "cant_send_to_bip47": "Dieses Wallet kann nicht an BIP47 Zahlungscodes senden", + "cant_find_bip47_notification": "Diesen Zahlungscode zuerst zu den Kontakten hinzufügen ", "problem_with_psbt": "PSBT-Problem" }, "settings": { @@ -222,20 +219,21 @@ "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 Sie BlueWallet schließen und erneut öffnen.", "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 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 Wallet 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", @@ -244,40 +242,42 @@ "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?", + "set_lndhub_as_default": "{url} als Standard LNDhub-Server festlegen?", "electrum_settings_server": "Electrum Server", - "electrum_settings_explain": "Leer lassen, um den Standard zu verwenden.", "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_preferred_server": "Präferierter Server", + "electrum_preferred_server_description": "Den Server eingeben, der die Wallet für alle Bitcoin-Aktivitäten verwenden soll. Sobald festgelegt, wird die Wallet um Salden abzufragen, Transaktionen zu senden und Netzwerkdaten abzurufen ausschliesslich diesen Server verwenden. Prüfen sie vorher, dass der Server vertrauenswürdig ist.", + "electrum_unable_to_connect": "Verbindung zu {server} kann nicht hergestellt werden.", + "electrum_history": "Historie", + "electrum_reset_to_default": "Dies lässt BlueWallet zufällig einen Server aus der Liste der Server auswählen.", "electrum_reset": "Zurücksetzten", - "electrum_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_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 Wallet ohne Passwortschutz direkt benutzbar. ", + "encrypt_storage_explanation_headline": "Speicherverschlüsselung aktivieren", + "encrypt_storage_explanation_description_line1": "Die Aktivierung der Speicherverschlüsselung fügt Ihrer App ein zusätzlicher Schutz hinzu. Die Art und Weise, wie die Daten auf Ihrem Gerät gespeichert werden, macht es anderen damit schwieriger, ohne Erlaubnis darauf zuzugreifen.", + "encrypt_storage_explanation_description_line2": "Diese Verschlüsselung betrifft den Zugriff auf die im Gerät gespeicherten Schlüssel, schützt also die Wallets. Die Wallet selbst werden dabei nicht mit einem Passwort oder einem anderen zusätzlichen Schutz versehen.", + "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.", + "biometrics_fail": "Wenn {type} nicht aktiviert ist oder entsperrt werden kann, alternativ Ihren 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,32 +285,32 @@ "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 Ihrem eigenen LND-Knoten zu verbinden, LNDhub installieren und dessen URL hier in den Einstellungen eintragen. Achtung. Nur Geldbörsen, die nach dem Speichern der Änderungen erstellt werden, verbinden sich mit dem angegebenen LNDhub.", "network": "Netzwerk", "network_broadcast": "Transaktion publizieren", "network_electrum": "Electrum Server", + "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 Benachrichtigungen an das Gerät zu adressieren, die Wallet-Informationen ermöglichen, eingehende Transaktionen und Bestätigungen zu notifizieren.\n\nNach der Aktivierung werden nur künftige, nicht aber vergangene Transaktions-IDs übertragen.\n\nMit der Deaktivierung, werden alle diese Informationen wieder vom Server entfernt. Das Gleiche passiert beim löschen der Wallet-App.", "selfTest": "Selbsttest", "save": "Speichern", "saved": "Gespeichert", @@ -321,26 +321,27 @@ "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_note": "Beschreibung kopieren", "details_copy_txid": "Transaktions-ID kopieren", @@ -349,9 +350,14 @@ "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}", @@ -363,6 +369,7 @@ "eta_1d": "In ca. 1 Tag verbucht.", "view_wallet": "{walletLabel} anzeigen", "list_title": "Transaktionen", + "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,50 +377,62 @@ "status_cancel": "Transaktion abbrechen", "transactions_count": "Anzahl Transaktionen", "txid": "Transaktions-ID", - "updating": "Aktualisiere...." + "from": "Von: {counterparty}", + "to": "Zu: {counterparty}", + "updating": "Aktualisiere....", + "watchOnlyWarningTitle": "Sicherheitswarnung", + "watchOnlyWarningDescription": "Achtung. Betrüger verwenden „Watch-only“-Wallets um Nutzern echte Wallet vorzutäsuchen. Sie können mit diesem Wallet keine Gelder kontrollieren oder senden, sondern nur den Kontostand einsehen.", + "custom_fee_warning_title": "Warnung", + "custom_fee_warning_description": "Gebühren unter 1 sat/vB sind gültig, aber werden ggf. nicht weitergeleitet." }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Einfache und leistungsstarke Bitcoin Wallet", "add_create": "Erstellen", + "total_balance": "Gesamtes Guthaben", + "add_entropy_reset_title": "Entropie zurücksetzten", + "add_entropy_reset_message": "Ein Wechsel des Wallet Typs wird die Entropie zurücksetzten. Wirklich weiterfahren? ", + "add_entropy": "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": "Ihren LNDhub verbinden", + "add_lndhub_error": "Die Adresse verweist auf einen ungültigen LNDhub-Knoten.", "add_lndhub_placeholder": "Bitcoin 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_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_show_xpub": "Wallet xPub zeigen", "details_show_addresses": "Adressen anzeigen", "details_title": "Wallet", + "wallets": "Wallets", "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", "export_title": "Wallet exportieren", @@ -429,51 +448,76 @@ "import_success_watchonly": "Deine Wallet wurde erfolgreich importiert. WARNUNG: Dies ist eine reine Wallet nur zum Anschauen, du kannst NICHT mit ihr ausgeben.", "import_search_accounts": "Konten suchen", "import_title": "Importieren", + "learn_more": "Mehr erfahren", "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_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.", "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.", + "manage_title": "Wallets verwalten", + "no_results_found": "Keine Ergebnisse gefunden.", "please_continue_scanning": "Bitte Scanvorgang fortsetzten", "select_no_bitcoin": "Es sind momentan keine Bitcoin Wallets verfügbar.", "select_no_bitcoin_exp": "Eine Bitcoin Wallet ist Voraussetzung dafür, um eine Lightning Wallet zu befüllen. Bitte erstelle oder importiere eines.", "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 um nötig die Wallet später wiederherzustellen.", + "copy_ln_public": "Diese Information kopieren und sicher speichern. Sie ist nötig um die Wallet später wiederherzustellen.", "add_ln_wallet_first": "Bitte zuerst ein Lightning-Wallet hinzufügen.", "identity_pubkey": "Pubkey-Identität", - "xpub_title": "Wallet xPub" + "xpub_title": "Wallet xPub", + "manage_wallets_search_placeholder": "Wallets, Adressen, Transaktionen und Memos suchen", + "more_info": "Mehr Infos", + "details_delete_wallet_error_message": "Problem beim Entfernen der Wallet aus Benachrichtigungen – evtl. Netzwerkproblem oder schlechte Verbindung. Bei Fortfahren könnten Transaktions-Benachrichtigungen für diese Wallet weiterhin ankommen, selbst nach dessen Löschung.", + "details_delete_anyway": "Trotzdem löschen" + }, + "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 Ihrem Gerät und der Wallet, in der der Schlüssel ist, die Transaktion signieren.", + "provide_signature_details_bluewallet": "Zum Menü des Senden-Bildschirms gehen, dort auswählen", + "provide_signature_next_steps": "Signierte Transaktion scannen oder importieren", + "provide_signature_next_steps_details": "Sobald Ihre Wallet die Transaktion signiert hat, den bereitgestellten QR-Code scannen oder die Datei importieren. Alle Transaktionsdetails vor dem Übertragen prüfen.", "vault_key": "Tresor-Schlüssel: {number}", "required_keys_out_of_total": "Erforderliche Schlüssel aus dem Total", "fee": "Gebhür: {number}", @@ -482,6 +526,8 @@ "header": "Senden", "share": "Teilen", "view": "Anzeigen", + "shared_key_detected": "Geteilte Mitsignierer", + "shared_key_detected_question": "Ein Mitsignierer wurde mit Ihnen geteilt. Diesen jetzt importierten?", "manage_keys": "Schlüssel verwalten", "how_many_signatures_can_bluewallet_make": "Anzahl Signaturen durch BlueWallet", "signatures_required_to_spend": "Benötigte Signaturen {number}", @@ -507,20 +553,21 @@ "quorum_header": "Signaturfähigkeit", "of": "von", "wallet_type": "Typ des Wallets", - "invalid_mnemonics": "Ungültige mnemonische Phrase", + "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_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.", + "this_is_cosigners_xpub_airdrop": "Zur AirDrop-Freigabe müssen alle Empfänger auf dem Koordinierungsbildschirm sein.", "wallet_key_created": "Dein Tresorschlüssel wurde erstellt. Nimm dir Zeit ein sicheres Backup des mnemonischen Seeds herzustellen. ", - "are_you_sure_seed_will_be_lost": "Bist du sicher? Dein mnemonischer Seed ist ohne Backup verloren!", + "are_you_sure_seed_will_be_lost": "Wirklich ok? Der mnemonischer Seed ist ohne Backup verloren!", "forget_this_seed": "Seed vergessen und xPub verwenden.", - "view_edit_cosigners": "Mitsignierer anzeigen/bearbeiten", - "this_cosigner_is_already_imported": "Dieser Mitsignierer ist schon vorhanden", + "view_edit_cosigners": "Mitsignierer Anzeigen/Bearbeiten", + "this_cosigner_is_already_imported": "Dieser Mitsignierer ist schon vorhanden.", "export_signed_psbt": "Signierte PSBT exportieren", "input_fp": "Fingerabdruckkennung eingeben", "input_fp_explain": "Überspringen, um den Standard zu verwenden (00000000)", @@ -528,15 +575,15 @@ "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": "Ein Wallet mit mehreren Schlüsseln zur gemeinsamen Verwahrung oder zur Erhöhung der Sicherheit.", "ms_help_title1": "Dazu sind mehrere Geräte empfohlen.", - "ms_help_1": "Der Tresor funktioniert mit weiteren BlueWallet Apps und PSBT kompatiblen wallets wie Electrum, Specter, Coldcard, 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_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_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 ein Tresor zu importieren, die Multisignatur Backupdatei mittels Import-Funktion laden. Seeds der erweiterten Schlüssel während der Tresor-Erstellung hinzufügen.", "ms_help_title5": "Erweiterte Optionen", "ms_help_5": "Der geplante Tresor erfordert 2 von 3 Signaturen. Zum Ändern der Anzahl oder des Adresstyps unter Einstellungen > Allgemein den erweiterten Modus aktivieren." }, @@ -548,18 +595,30 @@ "no_wallet_owns_address": "Keines der verfügbaren Wallet besitzt die eingegebene Adresse.", "view_qrcode": "QR-Code anzeigen" }, + "autofill_word": { + "enter": "Die unvollendete mnemonische Phrase eingeben", + "generate_word": "Erzeuge das letzte Word", + "error": "Die Eingabe ist keine unvollendete 11- oder 23 Wort Phrase. Bitte erneut versuchen." + }, "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": "Dieses Wallet hat aktuell keine Münzen.", "freeze": "einfrieren", "freezeLabel": "Einfrieren", "freezeLabel_un": "Entblocken", "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." + "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,6 +627,8 @@ "sats": "sats" }, "addresses": { + "copy_private_key": "Privaten Schlüssel kopieren", + "sensitive_private_key": "Warnung: Private Schlüssel sind gefahrvoll. Weiterfahren?", "sign_title": "Meldung signieren/verifizieren", "sign_help": "Auf einer Bitcoin-Adresse basierende, kryptografische Signatur erstellen oder verifizieren.", "sign_sign": "Signieren", @@ -584,7 +645,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 +661,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": "Neuer Kontaktnahme eingeben", + "add_contact": "Kontakt hinzufügen", + "provide_payment_code": "Zahlungscode eingeben", + "invalid_pc": "Ungültiger Zahlungscode", + "notification_tx_unconfirmed": "Benachrichtigungstransaktion noch unbestätigt. Bitte warten", + "failed_create_notif_tx": "On-Chain Transaktion konnte nicht in erstellt werden", + "onchain_tx_needed": "On-Chain Transaktion benötigt.", + "notif_tx_sent": "Benachrichtigungstransaktion ist gesendet. Auf Bestätigung warten.", + "notif_tx": "Benachrichtigungstransaktion", + "not_found": "Zahlungscode nicht gefunden" } } diff --git a/loc/el.json b/loc/el.json index 0aa93ee5840..0dadc7afd5e 100644 --- a/loc/el.json +++ b/loc/el.json @@ -6,19 +6,14 @@ "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": "Εισαγωγή ενός ποσού" }, "azteco": { @@ -37,26 +32,16 @@ "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", - "title": "Διαχείριση χρημάτων", - "can_send": "Μπορεί να αποσταλεί", - "can_receive": "Μπορεί να ληφθεί" + "sameWalletAsInvoiceError": "Δεν μπορείς να εξοφλήσεις ένα τιμολόγιο από το ίδιο πορτοφόλι με το οποίο δημιουργήθηκε.", + "title": "Διαχείριση χρημάτων" }, "lndViewInvoice": { "additional_info": "Επιπρόσθετη πληροφορία", @@ -68,30 +53,25 @@ }, "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": "Το πορτοφόλι σας δημιουργήθηκε." }, "receive": { "details_create": "Δημιουργία", "details_label": "Περιγραφή", "details_setAmount": "Λήψη με ποσό", - "details_share": "Δώσε", "header": "Λήψη", "maxSats": "Το μέγιστο ποσό είναι {max} sats", "maxSatsFull": "Το μέγιστο ποσό είναι {max} sats ή {currency}", @@ -152,9 +132,8 @@ "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": "Άδεια πρόσβαση στον αποθηκευτικό χώρο", "psbt_clipboard": "Αντιγραφή στο πρόχειρο", @@ -167,98 +146,90 @@ "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": "Παρακαλώ επιβεβαιώστε την ταυτότητα σας.", "currency": "Νόμισμα", - "currency_source": "Η τιμή προέρχεται από", + "default_title": "Στην έναρξη", "default_wallets": "Προβολή όλων των πορτοφολιών", "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", + "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": "Εργαλεία" }, - "notifications": { - "no_and_dont_ask": "Όχι, να μην ερωτηθώ ξανά", - "ask_me_later": "Ρώτησε με αργότερα" - }, "transactions": { + "cancel_no": "Αυτή η συναλλαγή δεν μπορεί να αντικατασταθεί.", + "confirmations_lowercase": "{confirmations} επιβεβαιώσεις", "copy_link": "Αντιγραφή συνδέσμου", "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": "Ακύρωση συναλλαγής" + "transaction": "Συναλλαγή", + "status_cancel": "Ακύρωση συναλλαγής", + "txid": "ID συναλλαγής" }, "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 +237,86 @@ "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 του πορτοφολιού" }, + "total_balance_view": { + "title": "Συνολικό υπόλοιπο" + }, "multisig": { - "multisig_vault": "Θησαυροφυλάκιο", "provide_signature": "Παροχή υπογραφής", + "fee_btc": "{number} BTC", "confirm": "Επιβεβαίωση", "header": "Αποστολή", - "share": "Δώσε", "view": "Προβολή", "manage_keys": "Διαχείριση κλειδιών", + "scan_or_import_file": "Σάρωση ή εισαγωγή αρχείου", "lets_start": "Ας ξεκινήσουμε", "create": "Δημιούργησε", + "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" }, "is_it_my_address": { - "enter_address": "Εισαγωγή διεύθυνσης" + "title": "Είναι δική μου διεύθυνση;", + "enter_address": "Εισαγωγή διεύθυνσης", + "check_address": "Έλεγχος διεύθυνσης", + "view_qrcode": "Προβολή κωδικού QR" }, "cc": { - "change": "Αλλαγή" + "change": "Αλλαγή", + "sort_label": "Ετικέτα", + "sort_status": "Κατάσταση" }, "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": "Υπογραφή", @@ -333,5 +325,9 @@ "type_receive": "Λήψη", "type_used": "Έχει γίνει χρήση", "transactions": "Συναλλαγές" + }, + "bip47": { + "payment_code": "Κωδικός πληρωμής", + "not_found": "Ο κωδικός πληρωμής δεν βρέθηκε" } } diff --git a/loc/en.json b/loc/en.json index 5b1ddf74e62..b8064e0cc77 100644 --- a/loc/en.json +++ b/loc/en.json @@ -4,32 +4,31 @@ "cancel": "Cancel", "continue": "Continue", "clipboard": "Clipboard", + "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 +37,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 +52,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 +130,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 +152,14 @@ "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", + "counterparty_label_placeholder": "Edit contact name", "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,36 @@ "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_enc_and_pass_description": "Encrypted storage with password. Biometrics will not be used to unlock the encrypted storage.", + "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 +287,39 @@ "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", + "plausible_deniability_description": "Advanced protection, proceed with caution.", + "multiple_storages": "Multiple storages", "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,13 +327,16 @@ }, "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", @@ -338,9 +346,7 @@ "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", @@ -349,9 +355,14 @@ "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}", @@ -363,6 +374,7 @@ "eta_1d": "ETA: In ~1 day", "view_wallet": "View {walletLabel}", "list_title": "Transactions", + "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)", @@ -370,50 +382,62 @@ "status_cancel": "Cancel Transaction", "transactions_count": "Transactions Count", "txid": "Transaction ID", - "updating": "Updating..." + "from": "From: {counterparty}", + "to": "To: {counterparty}", + "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." }, "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_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_show_xpub": "Show Wallet XPUB", "details_show_addresses": "Show addresses", "details_title": "Wallet", + "wallets": "Wallets", "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 +453,86 @@ "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 +558,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 +598,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 +633,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 +668,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..6adb86e5573 100644 --- a/loc/es.json +++ b/loc/es.json @@ -4,24 +4,17 @@ "cancel": "Cancelar", "continue": "Continua", "clipboard": "Portapapeles", + "discard_changes": "¿Descartar cambios? ", "enter_password": "Introduce la contraseña", "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", @@ -43,66 +36,38 @@ "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?", "expired": "Expirado", - "node_alias": "Alias del nodo", "expiresIn": "Expira en {time} minutos", "payButton": "Pagar", - "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:", "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." }, "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.", "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", + "ok": "OK, ya la he anotado.", + "ok_lnd": "OK, lo he guardado.", "text": "Por favor, apunta esta frase mnemotécnica en un papel. Será tu copia de seguridad y te permitirá restaurar la cartera en otro dispositivo.", "text_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,7 +76,6 @@ "details_create": "Crear", "details_label": "Descripción", "details_setAmount": "Recibir con monto", - "details_share": "Compartir", "header": "Recibir", "maxSats": "La cantidad máxima es {max} sats", "maxSatsFull": "La cantidad máxima es {max} sats o {currency}", @@ -153,7 +117,6 @@ "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_next": "Siguiente", "details_no_signed_tx": "El archivo seleccionado no contiene una transacción que se pueda importar.", "details_note_placeholder": "Nota personal", @@ -185,8 +148,6 @@ "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", @@ -196,11 +157,9 @@ "outdated_rate": "Fecha de la última actualización de la tarifa de cambio: {date}", "psbt_tx_open": "Abrir transacción firmada", "psbt_tx_scan": "Escanear transacción firmada", - "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.", "problem_with_psbt": "Problema con PSBT" }, "settings": { @@ -215,17 +174,12 @@ "about_selftest_electrum_disabled": "La autocomprobación no está disponible en el modo sin conexión de Electrum. Desactiva el modo sin conexión y vuelve a intentarlo. ", "about_selftest_ok": "Todas las pruebas internas han concluido satisfactoriamente. La billetera funciona bien. ", "about_sm_github": "GitHub", - "about_sm_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", @@ -233,7 +187,6 @@ "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", @@ -242,32 +195,16 @@ "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_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.", + "electrum_reset": "Restablecer valores predeterminados", "encrypt_decrypt": "Desencriptar almacenamiento", "encrypt_decrypt_q": "¿Seguro que quieres desencriptar tu almacenamiento? Al hacerlo, se podrá acceder a tus carteras sin contraseña.", - "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.", "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.", @@ -275,45 +212,34 @@ "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_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", "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" }, "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": "¿Quires recibir notificaciones cuando detectemos transferencias entrantes?" }, "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.", @@ -328,9 +254,7 @@ "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", @@ -339,8 +263,6 @@ "details_outputs": "Outputs", "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_to": "Destino", "enable_offline_signing": "Esta billetera no se está usando en conjunción con una firma offline. ¿Quieres activarlo ahora? ", @@ -353,6 +275,7 @@ "eta_1d": "Tiempo estimado: En ~1 día", "view_wallet": "Ver {walletLabel}", "list_title": "Transacciones", + "transaction": "Transaccion", "open_url_error": "No se puede abrir el enlace con el navegador predeterminado. Cambia tu navegador predeterminado y vuelve a intentarlo.", "rbf_explain": "Reemplazaremos esta transacción con una tarifa más alta para que se ejecute más rápido. Esto se llama RBF—Reemplazo por comisión.", "rbf_title": "Incrementar comisión (RBF)", @@ -366,44 +289,39 @@ "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Una cartera de Bitcoin útil y facil de usar", "add_create": "Crear", + "total_balance": "Balance total", + "add_entropy": "Entropía ", "add_entropy_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_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", "export_title": "Exportación de cartera", @@ -422,44 +340,37 @@ "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.", "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_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" }, + "total_balance_view": { + "title": "Balance total" + }, "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,7 +380,6 @@ "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", @@ -496,20 +406,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)", @@ -534,21 +438,21 @@ "owns": "{address} pertenece a {label}", "enter_address": "Introduce la dirección", "check_address": "Comprobar dirección", - "no_wallet_owns_address": "Ninguna de las carteras disponibles posee la dirección proporcionada.", - "view_qrcode": "Ver código QR" + "no_wallet_owns_address": "Ninguna de las carteras disponibles posee la dirección proporcionada." }, "cc": { "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" }, "units": { "BTC": "BTC", diff --git a/loc/es_419.json b/loc/es_419.json index faf3d4fb0ff..45e5f4479ca 100644 --- a/loc/es_419.json +++ b/loc/es_419.json @@ -4,11 +4,13 @@ "cancel": "Cancelar", "continue": "Continúa", "clipboard": "Portapapeles", + "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", "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", @@ -16,20 +18,17 @@ "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 +37,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" + "undo": "Deshacer", + "amountOfEntropy": "{bits} de {limit} bits" }, "errors": { "broadcast": "Error de transmisión", @@ -51,80 +52,63 @@ "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", "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", - "preimage": "Preimagen", + "preimage": "Imagen previa", "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.", "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.", @@ -146,8 +130,15 @@ "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", @@ -161,12 +152,14 @@ "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_frozen": "{amount} BTC está congelado.", "details_next": "Siguiente", "details_no_signed_tx": "El archivo seleccionado no contiene una transacción que se pueda importar.", "details_note_placeholder": "Nota personal", + "counterparty_label_placeholder": "Editar nombre de contacto", "details_scan": "Escanear", "details_scan_hint": "Toca dos veces para escanear o importar un destino", + "details_scan_error": "Error de escaneo", "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,6 +173,7 @@ "fee_1d": "1d", "fee_3h": "3h", "fee_custom": "Personalizado", + "insert_custom_fee": "Insertar tasa", "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.", @@ -192,9 +186,8 @@ "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", @@ -204,11 +197,15 @@ "outdated_rate": "La tarifa se actualizó por última vez: {date}", "psbt_tx_open": "Abrir transacción firmada", "psbt_tx_scan": "Escanear transacción firmada", - "qr_error_no_qrcode": "No pudimos encontrar un código QR 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,20 +219,21 @@ "performance_score": "Puntuación de rendimiento: {num}", "run_performance_test": "Prueba de rendimiento", "about_selftest": "Ejecutar auto-prueba", + "block_explorer_invalid_custom_url": "La URL proporcionada no es válida. Ingresa una URL válida que comience con http:// o https://.", "about_selftest_electrum_disabled": "La autocomprobación no está disponible con el modo sin conexión de Electrum. Desactiva el modo sin conexión y vuelve a intentarlo. ", "about_selftest_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", + "privacy_temporary_screenshots": "Permitir captura de pantalla", + "privacy_temporary_screenshots_instructions": "La protección contra capturas de pantalla se desactivará temporalmente, lo que permitirá realizar capturas y grabaciones de pantalla. La protección se reactivará automáticamente cuando cierre y vuelva a abrir BlueWallet.", "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 tarifa 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", @@ -244,6 +242,7 @@ "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", @@ -252,32 +251,36 @@ "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_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_enc_and_pass_description": "Almacenamiento cifrado con contraseña. No se utilizarán datos biométricos para desbloquear el almacenamiento cifrado.", + "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.", + "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": "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.", "general_continuity": "Continuidad", "general_continuity_e": "Cuando esté habilitado, podrás ver carteras seleccionadas y transacciones utilizando tus otros dispositivos conectados a Apple iCloud.", "groundcontrol_explanation": "GroundControl es un servidor de notificaciones push de código abierto gratuito para billeteras Bitcoin. Puedes instalar tu propio servidor GroundControl y poner tu URL aquí para no depender de la infraestructura de BlueWallet. Déjalo en blanco para usar el predeterminado.", @@ -285,36 +288,38 @@ "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.", "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", + "plausible_deniability_description": "Protección avanzada, proceder con precaución.", + "multiple_storages": "Almacenamientos múltiples", "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 cartera.", "privacy_clipboard_explanation": "Proporciona atajos si encuentras una dirección o factura en tu portapapeles.", "privacy_do_not_track": "Desactivar análisis", "privacy_do_not_track_explanation": "La información de rendimiento y confiabilidad no se enviará para su análisis.", - "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", @@ -322,25 +327,26 @@ }, "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)", + "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", @@ -349,9 +355,14 @@ "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}", @@ -363,57 +374,70 @@ "eta_1d": "TEA: en ~ 1 día", "view_wallet": "Ver {walletLabel}", "list_title": "Transacciones", + "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..." + "from": "De: {counterparty}", + "to": "A: {counterparty}", + "updating": "Actualizando...", + "watchOnlyWarningTitle": "Advertencia de seguridad", + "watchOnlyWarningDescription": "Ten cuidado con los estafadores que suelen utilizar billeteras de \"sólo ver\" para engañar a los usuarios. Estas billeteras no permiten controlar ni enviar fondos; solo permiten ver el saldo.", + "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." }, "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", + "add_wallet_seed_length": "Longitud de la semilla", + "add_wallet_seed_length_12": "12 palabras", + "add_wallet_seed_length_24": "24 palabras", "clipboard_bitcoin": "Tienes una dirección de Bitcoin en tu portapapeles. ¿te gustaría usarlo para una transacción?", "clipboard_lightning": "Tienes una factura Lightning en tu portapapeles. ¿Te gustaría usarlo para una transacción?", + "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", "details_type": "Tipo", "details_use_with_hardware_wallet": "Usar con Billetera de Hardware", - "details_wallet_updated": "Billetera actualizada", "details_yes_delete": "Si, eliminar", "enter_bip38_password": "Ingresa la contraseña para descifrar", "export_title": "Exportación de Billetera", @@ -426,62 +450,89 @@ "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 él.", "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 cartera.", "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}", @@ -507,34 +558,35 @@ "quorum_header": "Quorum", "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.", "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", @@ -546,7 +598,13 @@ "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" + "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 +617,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 cartera. 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 +633,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 +668,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..5537b4445ec 100644 --- a/loc/et_EE.json +++ b/loc/et_EE.json @@ -3,6 +3,7 @@ "bad_password": "Vale parool. Palun proovi uuesti.", "cancel": "Katkesta", "continue": "Jätka", + "discard_changes": "Loobu muudatustest?", "enter_password": "Sisesta parool", "never": "Mitte kunagi", "of": "{number} {total}-st", @@ -10,11 +11,9 @@ "storage_is_encrypted": "Sinu hoidla on krüpteeritud. Dekrüpteerimiseks on vajalik sisestada parool.", "yes": "Jah", "no": "Ei", - "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." + "wallet_key": "Rahakoti võti" }, "azteco": { "codeIs": "Sinu vautšeri kood on", @@ -35,19 +34,15 @@ "error": "Viga", "network": "Võrgu viga" }, - "plausibledeniability": { - "success": "Toiming õnnestus" - }, "send": { "broadcastError": "Viga", "broadcastSuccess": "Toiming õnnestus", "create_to": "Sihtkoht" }, "settings": { - "electrum_clear_alert_cancel": "Katkesta", "save": "Salvesta" }, "wallets": { - "details_save": "Salvesta" + "add_entropy": "Entroopia" } } diff --git a/loc/fa.json b/loc/fa.json index 677683c139c..32ed5a3fcbf 100644 --- a/loc/fa.json +++ b/loc/fa.json @@ -4,28 +4,22 @@ "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": "ویرایش ارز ورودی", "refresh": "تازه‌سازی", - "more": "بیشتر", - "enter_amount": "مقدار را وارد کنید" - }, - "alert": { - "default": "هشدار" + "enter_amount": "مقدار را وارد کنید", + "qr_custom_input_button": "برای وارد کردن ورودی دلبخواه، 10 بار ضربه بزنید" }, "azteco": { "codeIs": "کد تخفیف شما عبارت است از", @@ -47,62 +41,38 @@ "network": "خطای شبکه" }, "lnd": { - "active": "فعال", - "inactive": "غیرفعال", - "channels": "کانال‌ها", - "no_channels": "بدون کانال", - "claim_balance": "تسویهٔ موجودی {balance}", - "close_channel": "بستن کانال", - "new_channel": "کانال جدید", - "errorInvoiceExpired": "صورت‌حساب منقضی شد", - "force_close_channel": "بستن اجباری کانال؟", "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": "برای: ", "lightning_invoice": "صورت‌حساب لایتنینگ", - "open_direct_channel": "کانال مستقیمی با این گره باز کن:", "please_pay_between_and": "لطفاً بین {min} و {max} بپردازید", "please_pay": "لطفاً", - "preimage": "پیش‌نگاره", "sats": "ساتوشی بپردازید.", "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": "خب، آن را ذخیره کردم.", "text": "لطفاً درنگ کرده و این عبارت یادیار (mnemonic phrase) را روی یک تکه کاغذ یادداشت کنید. این کلمه‌ها نسخهٔ پشتیبان شما هستند، و می‌توانید از آن‌ها برای بازیابی کیف پول در دستگاه دیگری استفاده کنید.", @@ -113,7 +83,6 @@ "details_create": "ایجاد", "details_label": "شرح", "details_setAmount": "دریافت با مقدار", - "details_share": "اشتراک‌گذاری", "header": "دریافت", "maxSats": "بیشترین مقدار {max} ساتوشی است.", "maxSatsFull": "بیشترین مقدار {max} ساتوشی یا {currency} است.", @@ -155,7 +124,6 @@ "details_create": "ایجاد صورت‌حساب", "details_error_decode": "ناموفق در رمزگشایی آدرس بیت‌کوین", "details_fee_field_is_not_valid": "کارمزد معتبر نیست.", - "details_frozen": "{amount} بیت‌کوین مسدود شده است.", "details_next": "بعدی", "details_no_signed_tx": "فایل انتخاب‌شده حاوی تراکنشی نیست که بتوان آن را وارد کرد.", "details_note_placeholder": "یادداشت به خود", @@ -187,22 +155,18 @@ "permission_camera_message": "برای استفاده از دوربین به اجازهٔ شما نیاز داریم.", "psbt_sign": "امضاکردن تراکنش", "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 بوده و محتوای اضافی‌ای همچون متن یا دکمه درون آن وجود ندارد.", "reset_amount": "بازنشانی مقدار", "reset_amount_confirm": "آیا می‌خواهید مقدار را بازنشانی کنید؟", "success_done": "انجام شد", - "txSaved": "فایل تراکنش ({filePath}) در پوشهٔ دانلودهای شما ذخیره شده است.", "problem_with_psbt": "مشکل با تراکنش ناقص‌امضاشده (PSBT)" }, "settings": { @@ -219,17 +183,12 @@ "about_selftest_electrum_disabled": "خودآزمایی در حالت آفلاین در دسترس نیست. لطفاً حالت آفلاین را غیرفعال کرده و مجدد تلاش کنید.", "about_selftest_ok": "تمام بررسی‌های داخلی با موفقیت انجام شدند. کیف پول به‌درستی کار می‌کند.", "about_sm_github": "گیت‌هاب", - "about_sm_discord": "سرور دیسکورد", "about_sm_telegram": "کانال تلگرام", - "about_sm_twitter": "ما را در توئیتر دنبال کنید", - "advanced_options": "گزینه‌های پیشرفته", "biometrics": "بیومتریک", "biom_10times": "شما برای واردکردن گذرواژهٔ خود ۱۰ بار تلاش کرده‌اید. آیا می‌خواهید فضای ذخیره‌سازی خود را بازنشانی کنید؟ این کار تمام کیف پول‌ها را حذف و فضای ذخیره‌سازی شما را رمزگشایی خواهد کرد.", "biom_conf_identity": "لطفاً هویت خود را تأیید کنید.", - "biom_no_passcode": "دستگاه شما دارای گذرواژه نیست. برای ادامه، لطفاً گذرواژه‌ای را در تنظیمات دستگاه تعیین کنید.", "biom_remove_decrypt": "تمام کیف پول‌ها حذف و فضای ذخیره‌سازی شما رمزگشایی خواهد شد. آیا مطمئن هستید که می‌خواهید ادامه دهید؟", "currency": "واحد پول", - "currency_source": "قیمت برگرفته از", "currency_fetch_error": "حین دریافت نرخ برای واحد پول انتخاب‌شده خطایی رخ داد.", "default_desc": "درصورت غیرفعال‌کردن، BlueWallet بلافاصله کیف پول انتخابی را هنگام راه‌اندازی باز می‌کند.", "default_info": "اطلاعات پیش‌فرض", @@ -237,7 +196,6 @@ "default_wallets": "مشاهدهٔ همهٔ کیف پول‌ها", "electrum_connected": "متصل", "electrum_connected_not": "عدم اتصال", - "electrum_error_connect": "نمی‌توان به سرور الکترام ارائه‌شده متصل شد", "lndhub_uri": "به‌عنوان مثال، {example}", "electrum_host": "به‌عنوان مثال، {example}", "electrum_offline_mode": "حالت آفلاین", @@ -246,32 +204,16 @@ "use_ssl": "از SSL استفاده کن", "electrum_saved": "تغییرات شما با موفقیت ذخیره شدند. ممکن است برای اعمال تغییرات به راه‌اندازی مجدد برنامه نیاز داشته باشید.", "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_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": "درصورت فعال‌بودن، گزینه‌های پیشرفته‌ای مانند انواع مختلف کیف پول، امکان تعیین سرور LNDHub جهت اتصال، و آنتروپی سفارشی را در هنگام ایجاد کیف پول مشاهده خواهید کرد.", "general_continuity": "پیوستگی", "general_continuity_e": "درصورت فعال‌بودن، می‌توانید کیف پول‌های انتخاب‌شده و تراکنش‌ها را با استفاده از سایر دستگاه‌های متصل به Apple iCloud خود مشاهده کنید.", "groundcontrol_explanation": "سرویس GroundControl یک سرور اعلانات متن‌باز و رایگان برای کیف پول‌های بیت‌کوین است. شما می‌توانید سرور GroundControl خود را نصب کرده و آدرس آن را اینجا قرار دهید تا به زیرساخت‌های BlueWallet متکی نباشید. برای استفاده از تنظیمات پیش‌فرض خالی بگذارید.", @@ -279,10 +221,9 @@ "language": "زبان", "last_updated": "آخرین به‌روزرسانی", "language_isRTL": "راه‌اندازی مجدد BlueWallet جهت اعمال تغییرات چینش زبان ضروری است.", - "lightning_error_lndhub_uri": "یوآرآی LNDHub غیرمعتبر", + "license": "پروانه", "lightning_saved": "تغییرات شما با موفقیت ذخیره شدند.", "lightning_settings": "تنظیمات لایتنینگ", - "tor_settings": "تنظیمات تور", "network": "شبکه", "network_broadcast": "انتشار تراکنش", "network_electrum": "سرور الکترام", @@ -290,8 +231,6 @@ "notifications": "اعلانات", "open_link_in_explorer": "بازکردن پیوند در مرورگر", "password": "گذرواژه", - "password_explain": "گذرواژه‌ای را که برای رمزگشایی فضای ذخیره‌سازی استفاده خواهید کرد ایجاد کنید.", - "passwords_do_not_match": "گذرواژه‌ها مطابقت ندارند.", "plausible_deniability": "انکار موجه", "privacy": "حریم خصوصی", "privacy_read_clipboard": "خواندن کلیپ‌بورد", @@ -301,22 +240,17 @@ "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": "ابزارها" }, "notifications": { - "would_you_like_to_receive_notifications": "آیا می‌خواهید هنگام دریافت وجه اعلان دریافت کنید؟", - "no_and_dont_ask": "نه، و دیگر از من نپرس", - "ask_me_later": "بعداً از من بپرس" + "would_you_like_to_receive_notifications": "آیا می‌خواهید هنگام دریافت وجه اعلان دریافت کنید؟" }, "transactions": { "cancel_explain": "ما این تراکنش را با تراکنشی که گیرندهٔ آن شما هستید و کارمزد بیشتری دارد جایگزین خواهیم کرد. این درعمل تراکنش کنونی را لغو می‌کند. این کار Replace by Fee (به‌اختصار RBF) نام دارد—جایگزینی با کارمزد.", @@ -331,9 +265,7 @@ "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": "کپی شناسهٔ تراکنش", @@ -342,8 +274,6 @@ "details_outputs": "خروجی‌ها", "date": "تاریخ", "details_received": "دریافت‌شده", - "transaction_note_saved": "یادداشت تراکنش با موفقیت ذخیره شد.", - "details_show_in_block_explorer": "مشاهده در مرورگر بلاک", "details_title": "تراکنش", "details_to": "خروجی", "enable_offline_signing": "این کیف پول در کنار امضای آفلاین استفاده نمی‌شود. آیا مایل به فعال‌کردن این امکان هستید؟", @@ -356,6 +286,7 @@ "eta_1d": "زمان تقریبی رسیدن: حدود یک روز", "view_wallet": "مشاهدهٔ {walletLabel}", "list_title": "تراکنش‌ها", + "transaction": "تراکنش", "open_url_error": "ناموفق در بازکردن لینک با مرورگر پیش‌فرض. لطفاً مرورگر پیش‌فرض خود را تغییر داده و مجدد تلاش کنید.", "rbf_explain": "ما این تراکنش را با تراکنشی با کارمزد بالاتر جایگزین خواهیم کرد تا سریع‌تر تأیید شود. این کار Replace by Fee (به‌اختصار RBF) نام دارد—جایگزینی با کارمزد.", "rbf_title": "افزایش کارمزد (RBF)", @@ -369,43 +300,39 @@ "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": "آدرس گره ارائه‌شده گره LNDHub معتبری نیست.", "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_delete": "حذف", "details_delete_wallet": "حذف کیف پول", "details_derivation_path": "مسیر اشتقاق", - "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": "صادرکردن کیف پول", @@ -428,41 +355,38 @@ "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": "برای تراکنش‌های روزمره بهتر است از کیف پول لایتنینگ استفاده شود. کارمزدها به‌طرز غیرمنصفانه‌ای ارزان و سرعت فوق‌العاده بالاست.", "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": "روی یک کیف پول بزنید و نگه دارید تا آن را در لیست جابه‌جا کنید.", "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": "هویت/کلید عمومی", "xpub_title": "کلید XPUB کیف پول" }, + "total_balance_view": { + "title": "موجودی کل" + }, "multisig": { - "multisig_vault": "گاوصندوق", + "multisig_vault": "گاوصندوق چندامضایی", "default_label": "گاوصندوق چندامضایی", "multisig_vault_explain": "بالاترین امنیت برای مقادیر زیاد", "provide_signature": "ارائهٔ امضا", @@ -472,7 +396,6 @@ "fee_btc": "{number} بیت‌کوین", "confirm": "تأیید", "header": "ارسال", - "share": "اشتراک‌گذاری", "view": "مشاهده", "manage_keys": "مدیریت کلیدها", "how_many_signatures_can_bluewallet_make": "امضاهایی که BlueWallet می‌تواند ایجاد کند", @@ -499,20 +422,14 @@ "quorum_header": "حد نصاب", "of": "از", "wallet_type": "نوع کیف پول", - "invalid_mnemonics": "به‌نظر نمی‌رسد این عبارت یادیار (mnemonic phrase) معتبر باشد.", - "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": "کلید گاوصندوق شما ایجاد شد. لحظه‌ای درنگ کرده تا با خیال راحت از سید خود نسخهٔ پشتیبان تهیه کنید.", "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": "جهت استفاده از تنظیمات پیش‌فرض (۰۰۰۰۰۰۰۰) رد کنید", @@ -537,21 +454,20 @@ "owns": "آدرس {address} متعلق به «{label}» است.", "enter_address": "آدرس را وارد کنید", "check_address": "بررسی آدرس", - "no_wallet_owns_address": "آدرس ارائه‌شده متعلق به هیچ‌کدام از کیف پول‌های موجود نیست.", - "view_qrcode": "مشاهدهٔ کد QR" + "no_wallet_owns_address": "آدرس ارائه‌شده متعلق به هیچ‌کدام از کیف پول‌های موجود نیست." }, "cc": { "change": "باقی‌مانده (change)", "coins_selected": "کوین‌های انتخاب‌شده ({number})", "selected_summ": "انتخاب‌شده: {value}", - "empty": "این کیف پول درحال‌حاضر هیچ کوینی ندارد.", "freeze": "مسدود", "freezeLabel": "مسدودکردن", "freezeLabel_un": "عدم مسدودسازی", "header": "مدیریت کوین", "use_coin": "استفاده از کوین", "use_coins": "استفاده از کوین‌ها", - "tip": "به شما اجازه می‌دهد برای مدیریت بهتر کیف پول، کوین‌ها را مشاهده، برچسب‌گذاری، مسدود، یا انتخاب کنید. شما می‌توانید با زدن روی دایره‌های رنگی بیش از یک کوین را انتخاب کنید." + "tip": "به شما اجازه می‌دهد برای مدیریت بهتر کیف پول، کوین‌ها را مشاهده، برچسب‌گذاری، مسدود، یا انتخاب کنید. شما می‌توانید با زدن روی دایره‌های رنگی بیش از یک کوین را انتخاب کنید.", + "sort_status": "وضعیت" }, "units": { "BTC": "بیت‌کوین", @@ -593,7 +509,12 @@ }, "bip47": { "payment_code": "کد پرداخت", - "payment_codes_list": "فهرست کدهای پرداخت", + "contacts": "مخاطبان", + "purpose": "کد بازکاربردپذیر و قابل همرسانی (بیپ47)", + "pay_this_contact": "پرداخت به این مخاطب", + "copy_payment_code": "رونویسی کد پرداخت", + "rename": "ویرایش نام", + "add_contact": "افزودن مخاطب", "not_found": "کد پرداخت یافت نشد" } } diff --git a/loc/fi_fi.json b/loc/fi_fi.json index 2cfdda4ce61..f7dcbb8aae5 100644 --- a/loc/fi_fi.json +++ b/loc/fi_fi.json @@ -4,24 +4,24 @@ "cancel": "Peruuta", "continue": "Jatka", "clipboard": "Leikepöytä", + "discard_changes": "Hylkää muutokset?", + "discard_changes_explain": "Sinulla on tallentamattomia muutoksia. Haluatko varmasti hylätä ne ja poistu 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", "yes": "Kyllä", "no": "Ei", - "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ä", + "enter_amount": "Syötä määrä", + "qr_custom_input_button": "Napauta 10 kertaa syöttääksesi halutun arvon", + "unlock": "Avaa lukitus" }, "azteco": { "codeIs": "Kuponkikoodisi on", @@ -43,41 +43,24 @@ "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", @@ -86,22 +69,18 @@ }, "plausibledeniability": { "create_fake_storage": "Luo Salattu tallennustila", - "create_password": "Luo salasana", "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,12 +89,13 @@ "details_create": "Luo", "details_label": "Selite", "details_setAmount": "Vastaanotettava summa", - "details_share": "jaa", + "details_share": "Jaa...", "header": "Vastaanota", "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" }, "send": { "provided_address_is_invoice": "Tämä osoite vaikuttaa olevan Salamalasku. Maksun suorittamiseksi, siirry Salamalompakkoosi.", @@ -152,10 +132,11 @@ "details_create": "Luo Lasku", "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", + "counterparty_label_placeholder": "Muokkaa yhteystiedon nimeä", "details_scan": "Skannaa", "details_scan_hint": "Scannaa tai tuo tupla-napauttamalla", "details_total_exceeds_balance": "Lähetettävä summa ylittää katteen", @@ -184,8 +165,6 @@ "permission_camera_message": "Tarvitsemme lupasi kameran käyttöön", "psbt_sign": "Allekirjoita siirtotapahtuma", "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_title": "Tallennustilan käyttöoikeus", "psbt_clipboard": "Kopioi Leikepöydälle", @@ -195,11 +174,9 @@ "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" }, "settings": { @@ -216,17 +193,12 @@ "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_sm_github": "GitHub", - "about_sm_discord": "Discord-serveri", "about_sm_telegram": "Telegram-kanava", - "about_sm_twitter": "Seuraa meitä Twitterissä", - "advanced_options": "Lisäasetukset", "biometrics": "Biometriset tiedot", "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_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", @@ -243,32 +215,16 @@ "use_ssl": "Käytä SSL", "electrum_saved": "Muutoksesi on tallennettu onnistuneesti. Uudelleenkäynnistys voi olla tarpeen, jotta muutokset tulevat voimaan.", "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_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": "Palauta oletusasetuksiin", "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_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.", "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 +232,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 +242,6 @@ "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ää", "plausible_deniability": "Uskottava kiistettävyys", "privacy": "Yksityisyys", "privacy_read_clipboard": "Lue Leikepöytä", @@ -299,13 +251,11 @@ "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", "rate": "Vaihtokurssi", - "retype_password": "Salasana uudelleen", "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,8 +263,7 @@ }, "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" + "no_and_dont_ask": "En, ja Älä Kysy Minulta Uudelleen." }, "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.", @@ -329,9 +278,7 @@ "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", @@ -340,20 +287,19 @@ "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_title": "Siirtotapahtuma", "details_to": "Ulostulo", "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_1d": "Saapuu noin vuorokaudessa", "view_wallet": "Näytä {walletLabel}", "list_title": "Siirtotapahtumat", + "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 +307,49 @@ "status_cancel": "Peruuta Siirtotapahtuma", "transactions_count": "Siirtotapahtumien määrä", "txid": "Siirtotapahtuman tunnus", + "from": "Lähettäjä: {counterparty}", + "to": "Vastaanottaja: {counterparty}", "updating": "Päivitetään..." }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Yksinkertainen ja tehokas Bitcoin-lompakko", "add_create": "Luo", + "total_balance": "Kokonaissaldo", + "add_entropy": "Entropia", "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_placeholder": "solmusi osoite", "add_placeholder": "minun lompakkoni", "add_title": "lisää lompakko", "add_wallet_name": "nimi", "add_wallet_type": "tyyppi", - "balance": "Saldo", "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?", "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_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_delete": "Poista", "details_delete_wallet": "Poista lompakko", "details_derivation_path": "johdantopolku", - "details_display": "näkyy lompakkojen listassa", + "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_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", @@ -425,43 +370,43 @@ "import_discovery_derivation": "Vaihtoehtoinen derivation path", "import_discovery_no_wallets": "Lompakkoja ei löytynyt", "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_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.", "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", "add_ln_wallet_first": "Sinun on ensin lisättävä Salamalompakko.", "identity_pubkey": "Tunnus Pubkey", "xpub_title": "lompakon XPUB" }, + "total_balance_view": { + "title": "Kokonaissaldo" + }, "multisig": { - "multisig_vault": "Vault", + "multisig_vault": "Multisig Vault", "default_label": "Multisig Vault", "multisig_vault_explain": "Paras turvallisuus suurille summille", "provide_signature": "Toimita allekirjoitus", @@ -471,7 +416,7 @@ "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ä", @@ -498,10 +443,10 @@ "quorum_header": "Quorum", "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", + "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...", @@ -510,7 +455,7 @@ "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", @@ -539,18 +484,23 @@ "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" + }, "cc": { "change": "vaihto", "coins_selected": "Kolikot 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ä. " + "tip": "Antaa sinun nähdä, merkitä, jäädyttää tai valita kolikoita parempaan lompakon hallintaan. Voit valita useita kolikoita napauttamalla värillisiä ympyröitä. ", + "sort_label": "Etiketti", + "sort_status": "Tila" }, "units": { "BTC": "BTC", @@ -559,6 +509,7 @@ "sats": "sattia" }, "addresses": { + "copy_private_key": "Kopio yksityinen avain", "sign_title": "Allekirjoita/Varmenna viesti", "sign_help": "Täällä voit luoda tai varmentaa Bitcoin-osoitteeseen perustuvan kryptografisen allekirjoituksen.", "sign_sign": "Allekirjoita", @@ -592,8 +543,10 @@ }, "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", + "purpose": "Uudelleenkäytettävä ja jaettavissa oleva koodi (BIP47)", + "rename_contact": "Nimeä uudelleen yhteystieto", + "rename": "Nimeä uudelleen", + "add_contact": "Lisää yhteystieto" } } diff --git a/loc/fo.json b/loc/fo.json new file mode 100644 index 00000000000..f48cc5b2386 --- /dev/null +++ b/loc/fo.json @@ -0,0 +1,526 @@ +{ + "_": { + "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 gjørt 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}", + "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", + "close": "Aftur", + "change_input_currency": "Broyt 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": "Varpan 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 áframm.", + "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": "Eyka upplýsingar", + "for": "Frágreiðing:", + "lightning_invoice": "Lightning gjaldsumbøn", + "please_pay_between_and": "Vinaliga rinda millum {min} og {max}", + "please_pay": "Vinaliga rinda", + "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 gjalda gjaldsumbønina.", + "broadcastButton": "Varpa", + "broadcastError": "Villa", + "broadcastNone": "Inntøppa flytingardátur, sum sekstandatøl/hexadecimal", + "broadcastSuccess": "Eydnaðist", + "create_amount": "Upphædd", + "create_broadcast": "Varpa", + "create_details": "Smálutir", + "create_fee": "Avgjald", + "create_memo": "Viðmerking", + "create_satoshi_per_vbyte": "Satoshi fyri tBýt", + "create_this_is_hex": "Hettar er flytinging, sum sekstandatøl/hexadecimal — undirritað og klár at varpa til netið.", + "create_to": "Til", + "create_tx_size": "Flytingarstødd", + "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 ógildug.", + "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 frystar 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 ógildug.", + "details_amount_field_is_less_than_minimum_amount_sat": "Ásetta upphæddin er ov lítil. Vinaliga áset eina upphæddi 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 ógildugt.", + "details_frozen": "{amount} BTC er læst.", + "details_next": "Halt áfram", + "details_no_signed_tx": "Valda fílan inniheldur ikki nakra flyting ið kann innlesast.", + "details_note_placeholder": "Tekstur á egið avrit", + "details_scan": "Skanna", + "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 frystar 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 fllyting.", + "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_slow": "Sein", + "input_clear": "Tómstilla", + "input_paste": "Innset", + "permission_camera_message": "Appini tørvar loyvi, frá tær, at brúka títt myndatól.", + "psbt_sign": "Undirrita eina flyting", + "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": "Kursurin 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": "Tað miseydnaðist at finna eina gilduga 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?", + "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", + "problem_with_psbt": "PSBT villa" + }, + "settings": { + "about": "Um", + "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", + "block_explorer_invalid_custom_url": "Inntøppaða URL'ið er ógildugt. Vinaliga inntøppa eitt gildugt URL ið byrjar við http:// ella https://.", + "about_sm_github": "GitHub", + "privacy_temporary_screenshots": "Loyv skíggjaavmyndan", + "biometrics": "Lívmátilig", + "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_remove_decrypt": "Innlisnu mappurnar verða strikaðar og goymslan verður avbrongla. Ynskir tú at halda áfram?", + "currency": "Gjaldoyra", + "currency_source": "Kursurin er frá", + "currency_fetch_error": "Vegnað villu bar ikki til at fáa kursin fyri valda gjaldoyrað.", + "default_desc": "Annars verður mappan, ásett niðjanfyri, víst undir byrjan.", + "default_info": "Forsett mappa", + "default_title": "Byrjan", + "default_wallets": "Vís allar mappur", + "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 brúka til alt Bitcoin virksemi. Mappurnar brúka bert hendan ambætaran; til at kanna saldur, varpa 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_description": "Goymslan er bronglað við loyniorðið. Lvímátiligir háttir verða ikki nýttir til goymsluavbronglan.", + "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", + "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.", + "general": "Yvirskipaðar", + "header": "Stillingar", + "language": "Mál", + "last_updated": "Dagført", + "lightning_error_lndhub_uri": "Ógildugt LNDhub URI", + "lightning_error_lndhub_uri_tor": "Ógildugt 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.", + "network": "Net", + "network_broadcast": "Varpa 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": "Ógildugt 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", + "plausible_deniability_description": "Víðkað trygd. Hald áfram við varsemi.", + "multiple_storages": "Fleiri goymslur", + "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": "Kursur", + "save": "Goym", + "saved": "Goymt", + "success_transaction_broadcasted": "Tað eydnaðist at varpa tína flyting!", + "total_balance": "Samlaða salda" + }, + "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." + }, + "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.", + "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óneyku-leinkju", + "details_copy_note": "Avrita tekst", + "details_from": "Inntak", + "details_inputs": "Inntøk", + "details_outputs": "Úttøk", + "date": "Dagfesting og tíð", + "details_received": "Móttikin", + "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", + "list_conf": "Váttanir: {number}", + "pending": "Óváttað", + "pending_with_amount": "Óváttað {amt1} ({amt2})", + "received_with_amount": "+{amt1} ({amt2})", + "view_wallet": "Vís {walletLabel}", + "list_title": "Flytingar", + "transaction": "Flyting", + "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", + "updating": "Innlesur…", + "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 gildug, men verða ikki neyðturviliga framsend vegnað knútapolitikk." + }, + "wallets": { + "add_bitcoin": "Bitcoin", + "add_create": "Ger mappu", + "total_balance": "Samlaða salda", + "add_entropy_reset_title": "Tómstilla entropi", + "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_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 gildugan LNDhub knút.", + "add_lndhub_placeholder": "Adressan til tín knút", + "add_placeholder": "mín fyrsta mappa", + "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?", + "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_q": "Mappan hevur eina saldu. Áðrenn tú heldur áframm, skalt tú hava í huga at tú ikki kanst fáa mappuna, og tilhoyrandi pening, aftur uttan tilhoyrandi rótorð. Vinaliga inntøppa mappu-salduna 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_export_backup": "Tak út/Trygdaravrita", + "details_export_history": "Tak flytingaryvirlit út sum CSV", + "details_multisig_type": "fjølundirritan", + "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", + "export_title": "Mappu úttøka", + "import_do_import": "Innles", + "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_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_derivation_loading": "Innlesur…", + "import_derivation_title": "Avleiðsluleið", + "import_derivation_unknown": "Ókend", + "import_wrong_path": "Skeiv avleiðsluleið", + "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ðinar kolandi skjótar.", + "list_empty_txs2_lightning": "\nTrýst á 'Umsit pening' fyri at fylla á og kunna brúka hana.", + "list_latest_transaction": "Seinasta flyting", + "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.", + "manage_title": "Umsit mappur", + "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", + "xpub_copiedToClipboard": "Avritað til setiborð.", + "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.", + "wallet_type_this": "Mappan er av slagnum: {type}.", + "manage_wallets_search_placeholder": "Leita eftir mappum, adressum, flytingum og tekstboðum" + }, + "total_balance_view": { + "hide": "Fjal", + "title": "Samlaða salda", + "explanation": "Vís samlaðu salduna av øllum tínum mappum á yvirlit skíggjamyndini." + }, + "multisig": { + "multisig_vault": "Fjølundirritaraboks", + "default_label": "Fjølundirritaraboks", + "multisig_vault_explain": "Tryggast fyri stórar upphæddir", + "fee": "Avgjald: {number}", + "fee_btc": "{number} BTC", + "share": "Deil…", + "view": "Vís", + "manage_keys": "Umsit lyklar", + "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 fílu", + "cosign_this_transaction": "Samundirrita flytingina?", + "lets_start": "Halt áfram", + "create": "Ger", + "wrapped_segwit_title": "Besta sínamillumvirkni", + "co_sign_transaction": "Undirrita eina flyting", + "what_is_vault_numberOfWallets": " {m}-av-{n} fjølundirritan", + "wallet_type": "Mappu slag", + "invalid_mnemonics": "Áminningarramsan tykist vera ógildug.", + "invalid_cosigner": "Ógildugar samundirritaradátur", + "not_a_multisignature_xpub": "XPUB hoyrur ikki til eina fjølundirritanarmappu!", + "i_have_mnemonics": "Eg havi rótorðini tilhoyrandi lykilin.", + "are_you_sure_seed_will_be_lost": "Er tú vís/ur? Hevur tú einki trygdaravrit verða tíni rótorð/áminningarramsa burtur.", + "forget_this_seed": "Gloym hesi rótorð 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_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ølundirritaraboksir: Frágreiðingar og ráð", + "ms_help_text": "Ein mappa við fleiri (høvuðs)lyklum, fyri økta trygd og/ella felags varðveitslu", + "ms_help_title1": "Viðmælt er at brúka fleiri eindir.", + "ms_help_1": "Boksin 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 boks" + }, + "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": { + "generate_word": "Útrokna síðsta orðið" + }, + "cc": { + "change": "Vekslipeningur", + "coins_selected": "{number} myntir valdar", + "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 og læsa læsa, umframt at 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", + "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_sign": "Undirrita", + "sign_verify": "Vátta", + "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 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_2": "við tíni Lightning mappu?" + }, + "bip47": { + "payment_code": "Gjaldkota", + "bip47_explain_subtitle": "BIP47", + "copy_payment_code": "Avrita gjaldkotu", + "rename": "Nýnevn", + "invalid_pc": "Ógildug gjaldkota", + "notification_tx_unconfirmed": "Fráboðanarflytingin er ikki váttað enn, vinaliga bíða", + "failed_create_notif_tx": "Miseydnaðist at gera áketu-flyting", + "onchain_tx_needed": "Tørvur er á áketu-flyting", + "notif_tx_sent": "Fráboðanarflytingin er send. Vinaliga bíða til hon verður váttað.", + "notif_tx": "BIP47 fráboðanarflyting" + } +} diff --git a/loc/fr_fr.json b/loc/fr_fr.json index cbcf7cda75a..cb4d1279d80 100644 --- a/loc/fr_fr.json +++ b/loc/fr_fr.json @@ -1,29 +1,34 @@ { "_": { - "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", + "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": "Deverouiller", + "port": "Port", + "ssl_port": "Port SSL", + "suggested": "Suggéré" }, "azteco": { "codeIs": "Votre code promo est", @@ -37,7 +42,8 @@ "entropy": { "save": "Enregistrer", "title": "Entropie", - "undo": "Défaire" + "undo": "Défaire", + "amountOfEntropy": "{bits} de {limit} bits" }, "errors": { "broadcast": "La diffusion a échouée.", @@ -45,69 +51,48 @@ "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" + "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 :", "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_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_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", @@ -115,16 +100,19 @@ "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}" + "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." }, "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": "Inserez la transaction en format hexadécimal", "broadcastPending": "en cours", "broadcastSuccess": "succès", "confirm_header": "Confirmer", @@ -140,8 +128,15 @@ "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": "Efface tous les destinataires", + "details_recipients_title": "Destinataires", + "details_recipient_title": "Destinataire n° {nombre} 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", @@ -155,10 +150,11 @@ "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)", + "counterparty_label_placeholder": "Éditer le nom du contact", "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.", @@ -174,6 +170,7 @@ "fee_1d": "1j", "fee_3h": "3h", "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.", @@ -186,9 +183,8 @@ "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.", + "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 autorisation de stockage.", "permission_storage_title": "Permission d'accès au stockage pour BlueWallet", "psbt_clipboard": "Copier dans le presse-papier", @@ -198,11 +194,15 @@ "outdated_rate": "Les taux ont été mis a 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 ?", "success_done": "Terminé", - "txSaved": "Le fichier de transaction ({filePath}) a été enregistré dans votre dossier Téléchargements.", + "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 a des adresses SilentPayment", + "cant_send_to_bip47": "Ce portefeuille ne peut pas envoyer a des codes de paiement BIP47", + "cant_find_bip47_notification": "Ajoutez ce Code paiement au contact d'abord", "problem_with_psbt": "Problème avec PSBT" }, "settings": { @@ -213,21 +213,22 @@ "about_license": "License MIT", "about_release_notes": "Notes de version", "about_review": "Laissez-nous votre avis", + "performance_score": "Score performance: {num}", + "run_performance_test": "Test 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_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", + "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 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.", "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_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é. Etes vous sure de vouloir continuer ?", "currency": "Devise", - "currency_source": "Le prix est obtenu de", + "currency_source": "Taux 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", @@ -236,6 +237,7 @@ "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", @@ -244,65 +246,66 @@ "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?", + "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 au 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_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 bloc", + "block_explorer_preferred": "Utiliser l'explorateur de bloc préféré", + "block_explorer_error_saving_custom": "Erreur lors de la sauvegarde de l'explorateur de bloc 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": "License", + "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_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_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. Seules les informations provenant d'une fois que vous avez activé les notifications sont transmises ; rien d'avant n'est collecté.La désactivation des notifications supprimera tout de ces informations depuis le 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é", @@ -314,13 +317,16 @@ }, "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" + "notifications_subtitle": "Paiements entrants et confirmations de transactions", + "no_and_dont_ask": "Non, et ne pas 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)", + "transaction_loading_error": "Un problème est survenu lors du chargement de la transaction. Veuillez réessayer plus tard.", + "transaction_not_available": "Transaction indisponible", "confirmations_lowercase": "{confirmations} confirmations", "copy_link": "Copier le lien", "expand_note": "Développer la note", @@ -330,19 +336,23 @@ "cpfp_title": "Frais de propulsion (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", + "date": "Date", "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_view_in_browser": "Voir dans le navigateur", "details_title": "Transaction", + "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}", @@ -354,6 +364,7 @@ "eta_1d": "ETA: Dans ~1 jour", "view_wallet": "Voir {walletLabel}", "list_title": "transactions", + "transaction": "Transaction", "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)", @@ -361,52 +372,63 @@ "status_cancel": "Annuler la transaction", "transactions_count": "Nombre de transactions", "txid": "ID de transaction", - "updating": "Chargement..." + "from": "De: {counterparty}", + "to": "A: {counterparty}", + "updating": "Chargement...", + "watchOnlyWarningTitle": "Avertissement de sécurité", + "watchOnlyWarningDescription": "Méfiez-vous des fraudeurs qui utilisent souvent des portefeuilles « lecture seul» 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." }, "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": "{octets} 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", + "add_wallet_seed_length": "Longueur de graîne", + "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 Ligthning 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_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_export_history": "Exporter l'historique au format CSV", "details_master_fingerprint": "Empreinte maitresse", "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", + "wallets": "Portefeuilles", "details_type": "Type", "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", + "export_title": "Export du portefeuille", "import_do_import": "Importer", "import_passphrase": "Phrase secrète", "import_passphrase_title": "Phrase secrète", @@ -416,19 +438,22 @@ "import_imported": "Importé", "import_scan_qr": "ou scaner 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 lectures seules, 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_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.", @@ -437,40 +462,65 @@ "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_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.", + "manage_title": "Gérer les portefeuilles", + "no_results_found": "Pas de résultats trouvés.", "please_continue_scanning": "Merci de continuer à scaner", "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" + }, + "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", "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_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}", @@ -496,17 +546,18 @@ "quorum_header": "Quorum", "of": "de", "wallet_type": "Type portefeuille", - "invalid_mnemonics": "Cette phrase mnémonique ne semble pas valide ", + "invalid_mnemonics": "Cette phrase mnémonique ne semble pas valide.", "invalid_cosigner": "Données de cosigner 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 incorrecte: Ceci nest 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.", + "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éé. Prenez un moment pour sauvegarder votre graine sous forme de mnémonique ", - "are_you_sure_seed_will_be_lost": "Etes Vous sûr? ", + "are_you_sure_seed_will_be_lost": "Etes-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é.", @@ -517,9 +568,9 @@ "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'autre BlueWallet et les portefeuille compatible 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_title3": "Sauvegardes coffre", @@ -527,7 +578,7 @@ "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_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." }, "is_it_my_address": { "title": "Est ce mon adresse?", @@ -537,18 +588,30 @@ "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." + }, "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", + "empty": "Ce portefeuille ne possède aucune pièces en ce moment.", "freeze": "Geler", "freezeLabel": "Gelé", "freezeLabel_un": "Dégeler", "header": "Controle 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": "Libelé", + "sort_status": "Statut", + "sort_by": "Trier par" }, "units": { "BTC": "BTC", @@ -557,6 +620,8 @@ "sats": "sats" }, "addresses": { + "copy_private_key": "Copier clé privée", + "sensitive_private_key": "Attention : les clés privées sont extrêmement sensibles. Continuer?", "sign_title": "Signer/Verifier message", "sign_help": "Ici vous pouvez créer ou vérifier une signature électronique basée sur une adresse bitcoin.", "sign_sign": "Signe", @@ -587,5 +652,27 @@ "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", + "contacts": "Contacts", + "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" } } diff --git a/loc/he.json b/loc/he.json index fd3b30be6a9..5d553741347 100644 --- a/loc/he.json +++ b/loc/he.json @@ -4,32 +4,31 @@ "cancel": "ביטול", "continue": "המשך", "clipboard": "לוח גזירים", + "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 +37,14 @@ "redeem": "מימוש לארנק", "redeemButton": "מימוש", "success": "הצלחה", + "successMessage": "שובר מומש בהצלחה! הכספים שלך אמורים להגיע לארנק הביטקוין שלך בקרוב.", "title": "מימוש שובר Azte.co" }, "entropy": { "save": "שמירה", "title": "אנטרופיה", - "undo": "ביטול" + "undo": "ביטול", + "amountOfEntropy": "{bits} מתוך {limit} סיביות" }, "errors": { "broadcast": "שידור כשל.", @@ -51,79 +52,60 @@ "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", + "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": "אוקיי, רשמתי את זה", "ok_lnd": "אוקיי, שמרתי את זה.", "text": "אנא קחו רגע כדי לכתוב על דף נייר את מילות הגיבוי האלו. זה הגיבוי שלכם ואיתו תוכלו לשחזר את הארנק.", "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 לכתובת" }, "send": { "provided_address_is_invoice": "נראה שכתובת זו היא חשבונית ברק. אנא עברו לארנק הברק שלכם כדי לבצע תשלום עבור חשבונית זו.", @@ -145,8 +127,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 +149,14 @@ "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": "הערה לעצמך", + "counterparty_label_placeholder": "עריכת שם איש קשר", "details_scan": "סריקה", "details_scan_hint": "לחיצה כפולה לסריקה או יבוא יעד", + "details_scan_error": "שגיאת סריקה", "details_total_exceeds_balance": "הסכום לשליחה חורג מהיתרה הזמינה.", "details_total_exceeds_balance_frozen": "סכום השליחה חרג מהיתרה הזמינה. אנא שימו לב שמטבעות מוקפאים מוחרגים.", "details_unrecognized_file_format": "פורמט קובץ לא מזוהה", @@ -179,9 +170,11 @@ "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": "ניקוי", @@ -190,9 +183,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": "העתקה ללוח", @@ -202,11 +194,14 @@ "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": "ארנק זה אינו יכול לשלוח לכתובות תשלום שקט", + "cant_send_to_bip47": "ארנק אינו יכול לשלוח לקודי תשלום BIP47", + "cant_find_bip47_notification": "הוספת קוד תשלום זה לרשימת אנשי קשר", "problem_with_psbt": "בעיה עם PBST" }, "settings": { @@ -220,20 +215,20 @@ "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": "אפשר צילומי מסך", + "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": "פתיחת ברירת מחדל", @@ -252,30 +247,28 @@ "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_storage_explanation_headline": "הפעלת הצפנת אחסון", + "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": "לא ניתן להשתמש בתכונה זאת עם הצפנת אחסון מופעלת.", + "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. השאירו ריק כדי להשתמש בברירת המחדל", @@ -283,19 +276,21 @@ "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 המוגדר.", "network": "רשת", "network_broadcast": "שידור פעולה", "network_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": "קריאה מקליפבורד", @@ -305,13 +300,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,13 +312,16 @@ }, "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": "הרחבת הערה", @@ -335,9 +331,7 @@ "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": "העתקת מזהה פעולה", @@ -346,9 +340,12 @@ "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}", @@ -359,6 +356,7 @@ "eta_1d": "ETA: תוך ~1 יום", "view_wallet": "הצגת {walletLabel}", "list_title": "תנועות", + "transaction": "פעולה", "open_url_error": "לא ניתן לפתוח קישור עם דפדפן ברירת המחדל. אנא שנו את דפדפן ברירת המחדל שלכם ונסו שוב", "rbf_explain": "אנו נחליף את פעולה זו בפעולה עם עמלה גבוהה יותר, כך שמהירות קבלת האישור אמורה לעלות. פעולה זאת נקראת RBF—Replace by Fee.", "rbf_title": "העלאת עמלה (RBF)", @@ -366,28 +364,42 @@ "status_cancel": "ביטול פעולה", "transactions_count": "מספר תנועות", "txid": "מזהה פעולה", - "updating": "מעדכן..." + "from": "מאת: {counterparty}", + "to": "עבור: {counterparty}", + "updating": "מעדכן...", + "watchOnlyWarningTitle": "אזהרת אבטחה", + "watchOnlyWarningDescription": "היזהרו מנוכלים אשר לעיתים קרובות משתמשים בארנקי ”צפייה-בלבד“ כדי להונות משתמשים. ארנקים אלה אינם מאפשרים לכם לשלוט בכספים או לשלוח כספים; הם מאפשרים לכם אך ורק לצפות במאזן.", + "custom_fee_warning_title": "אזהרה", + "custom_fee_warning_description": "עמלות מתחת ל- 1 sat/vB תקינות, אבל יתכן ולא ישודרו בשל מדיניות צומת." }, "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": "התחברו ל- 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 +409,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 +435,7 @@ "import_success_watchonly": "ארנקך יובא בהצלחה. אזהרה: זהו ארנק צפייה-בלבד, אין באפשרותך לבזבז ממנו.", "import_search_accounts": "חיפוש חשבונות", "import_title": "יבוא", + "learn_more": "למדו עוד", "import_discovery_title": "גילוי", "import_discovery_subtitle": "בחירת ארנק שהתגלה", "import_discovery_derivation": "שימוש בנתיב גזירה מותאם", @@ -432,7 +443,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 +455,62 @@ "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_title": "מפתח צפייה של הארנק", + "manage_wallets_search_placeholder": "חיפוש ארנקים, כתובות, פעולות ותזכירים", + "more_info": "מידע נוסף", + "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": "סריקת או יבוא פעולה חתומה", "vault_key": "מפתח כספת {number}", "required_keys_out_of_total": " מפתחות נדרשים מתוך הסך הכולל", "fee": "עמלה: {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} ", @@ -504,12 +538,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": "שכח את גרעין זה והשתמש במפתח צפייה במקום.", @@ -542,24 +577,37 @@ "no_wallet_owns_address": "הכתובת שסופקה אינה שייכת לאחד מהארנקים הזמינים.", "view_qrcode": "הצגת קוד QR" }, + "autofill_word": { + "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": "מקס'" }, "addresses": { + "copy_private_key": "העתקת מפתח פרטי", + "sensitive_private_key": "אזהרה: מפתחות פרטיים רגישים בצורה קיצונית. המשך?", "sign_title": "חתימת/אימות הודעה", "sign_help": "פה תוכלו ליצור או לאמת חתימה קריפטוגרפית מבוססת על כתובת ביטקוין.", "sign_sign": "חתימה", @@ -593,9 +641,23 @@ }, "bip47": { "payment_code": "קוד תשלום", - "payment_codes_list": "רשימת קודי תשלום", - "who_can_pay_me": "מי יכול לשלם לי:", + "contacts": "אנשי קשר", + "bip47_explain": "קוד רב פעמי ובר שיתוף", "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..5661ba83ea9 100644 --- a/loc/hr_hr.json +++ b/loc/hr_hr.json @@ -9,7 +9,6 @@ "storage_is_encrypted": "Vaš spremnik je kriptiran. Za dekripcoju je potrebna lozinka.", "yes": "Da", "no": "Ne", - "save": "Spremi", "seed": "Izvor", "success": "Uspjeh" }, @@ -23,25 +22,21 @@ "expired": "Isteklo", "refill": "Dopuni", "refill_lnd_balance": "Dopuni Lightning volet saldo", + "sameWalletAsInvoiceError": "Buraz! Ne možeš platiti račun s istim voletom s kojim si račun stvorio, ono.", "title": "Uredi novčeke" }, "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" }, "receive": { "details_create": "Stvori", "details_label": "Opis", "details_setAmount": "Odredi iznos za primiti", - "details_share": "podijeli", "header": "Primi" }, "send": { @@ -72,26 +67,21 @@ "settings": { "about": "Informacije", "currency": "Valuta", - "electrum_clear_alert_cancel": "Otkaži", - "general_adv_mode": "Enable advanced mode", "header": "Postavke", "language": "Jezik", "lightning_settings": "Lightning postavke", "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" }, "transactions": { "cpfp_create": "Stvori", "details_copy": "Kopiraj", "details_from": "Od", - "details_show_in_block_explorer": "Prikaži u blok eksploreru", "details_title": "Transakcija", "details_to": "Za", - "list_title": "transakcije" + "list_title": "transakcije", + "transaction": "Transakcija" }, "wallets": { "add_create": "Stvori", @@ -103,10 +93,9 @@ "details_are_you_sure": "Jesi li ziher?", "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", + "wallets": "Voleti", "details_type": "Tip", "details_yes_delete": "Da, briši", "export_title": "izvoz voleta", @@ -121,7 +110,6 @@ "list_empty_txs1": "Vaše transakcije će se pojaviti ovdje", "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" @@ -129,10 +117,12 @@ "multisig": { "confirm": "Potvrdi", "header": "Šalji", - "share": "podijeli", "create": "Stvori", "ms_help_title5": "Enable advanced mode" }, + "cc": { + "sort_label": "Oznaka" + }, "addresses": { "sign_placeholder_address": "adresa", "type_receive": "Primi", diff --git a/loc/hu_hu.json b/loc/hu_hu.json index d5ec9362f29..750dba37c39 100644 --- a/loc/hu_hu.json +++ b/loc/hu_hu.json @@ -4,24 +4,17 @@ "cancel": "Mégse", "continue": "Folytatás", "clipboard": "Vágólap", + "discard_changes": "Módosítások elvetése?", "enter_password": "Írd be a jelszót", "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", "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 ", @@ -43,42 +36,23 @@ "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" + "title": "kezelés" }, "lndViewInvoice": { "additional_info": "További információk", "for": "Cím:", "lightning_invoice": "Villám Számla", - "open_direct_channel": "Közvetlen csatorna nyitása erre a csomópontra:", "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,31 +61,26 @@ }, "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.", "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_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" }, "send": { @@ -129,6 +98,7 @@ "create_details": "Részletek", "create_fee": "Díj", "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", @@ -148,12 +118,14 @@ "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_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_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_total_exceeds_balance_frozen": "A küldeni kívánt össze meghaladja az elérhető egyenlegét. Lefagyasztott érmék nem voltak használva.", "details_unrecognized_file_format": "Nemismert fálj formá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", @@ -167,6 +139,7 @@ "fee_custom": "beállított", "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", @@ -175,11 +148,8 @@ "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", "psbt_clipboard": "Másolás vágólapra", @@ -189,11 +159,9 @@ "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": { @@ -208,14 +176,10 @@ "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_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.", @@ -234,32 +198,16 @@ "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_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_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_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.", @@ -267,11 +215,8 @@ "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_electrum": "Electrum szerver", @@ -279,8 +224,6 @@ "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", @@ -290,9 +233,7 @@ "privacy_clipboard_explanation": "Jelentsen 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", "rate": "Ráta", - "retype_password": "Jelszó megerősítése", "selfTest": "Önteszt", "save": "Ment", "saved": "Elmentve", @@ -304,10 +245,10 @@ }, "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" + "no_and_dont_ask": "Nem és ne kérdezd újra." }, "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", @@ -319,9 +260,7 @@ "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", @@ -329,8 +268,6 @@ "details_inputs": "Bejövő utalások", "details_outputs": "Kimenő utalások", "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", "enable_offline_signing": "Ezt a pénztárcát nem használják offline aláírással. Szeretné most engedélyezni?", @@ -342,6 +279,9 @@ "eta_3h": "ETA: ~3 órán belül", "eta_1d": "ETA: ~1 napon belül", "list_title": "tranzakciók", + "transaction": "Tranzakció", + "open_url_error": "Nem lehetett megnyitni a 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", @@ -353,20 +293,19 @@ "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_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_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? ", "details_address": "Cím", @@ -378,18 +317,15 @@ "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_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", @@ -408,44 +344,44 @@ "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", + "import_derivation_unknown": "Ismeretlen", + "import_wrong_path": "Hibás derivációs útvonal", "list_create_a_button": "add hozzá", "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_long_choose": "Válassz fényképet", - "list_long_clipboard": "Másolás vágólapról", + "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.", "identity_pubkey": "Nyilvános kulcs", "xpub_title": "a tárca XPUB kulcsa" }, + "total_balance_view": { + "title": "Teljes egyenleg" + }, "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 ", @@ -455,7 +391,6 @@ "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", @@ -494,7 +429,7 @@ "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árs aláírok 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", @@ -534,7 +469,9 @@ "header": "Érme Kontroll", "use_coin": "Cryptovaluta 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": "Cimke", + "sort_status": "Állapot" }, "units": { "BTC": "BTC", @@ -543,8 +480,12 @@ "sats": "sats" }, "addresses": { + "sign_title": "Aláíró/Hitelesíŧő ü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", diff --git a/loc/id_id.json b/loc/id_id.json index 7b18c30d265..55209a8d85f 100644 --- a/loc/id_id.json +++ b/loc/id_id.json @@ -4,33 +4,23 @@ "cancel": "Batalkan", "continue": "Lanjutkan", "clipboard": "Clipboard", + "discard_changes": "Batalkan perubahan?", "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:", "yes": "Ya", "no": "Tidak", - "save": "Simpan", "seed": "Benih", "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", "enter_amount": "Masukkan nominal", "qr_custom_input_button": "Ketuk 10 kali untuk memasuki input khusus" }, - "alert": { - "default": "Peringatan" - }, "azteco": { "codeIs": "Kode voucher anda adalah", "errorBeforeRefeem": "Sebelum menebus anda harus menambahkan dompet Bitcoin", @@ -51,78 +41,50 @@ "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?", "expired": "Kadaluarsa", - "node_alias": "Alias node", - "expiresIn": "Kadaluwarsa dalam {waktu} menit", + "expiresIn": "Kadaluwarsa 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?", "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}", + "please_pay_between_and": "Silakan bayar antara {min} dan {max}", "please_pay": "Silakan bayar", - "preimage": "Pra gambar", "sats": "sat.", "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_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", + "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", "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}" + "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}" }, "send": { "provided_address_is_invoice": "Alamat ini tampaknya untuk sebuah fraktur Lightning. Silakan ke dompet Lightning Anda untuk membayar fraktur ini.", @@ -159,9 +121,7 @@ "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_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", @@ -180,7 +140,7 @@ "fee_custom": "Custom", "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 nilai tarif (satoshi per vByte) yang Anda ingin bayar harus lebih tinggi dari {min} say/vByte.", "fee_satvbyte": "dalam sat/vByte", "fee_slow": "Lambat", "header": "Kirim", @@ -191,22 +151,18 @@ "permission_camera_message": "Kami membutuhkan izin anda untuk menggunakan kamera.", "psbt_sign": "Tanda tangani transaksi", "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.", "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.", "problem_with_psbt": "Ada masalah dengan PSBT" }, "settings": { @@ -217,60 +173,41 @@ "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", "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", "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. ", "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_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", "default_title": "Diluncurkan", "default_wallets": "Lihat Semua Dompet", "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}", + "lndhub_uri": "Contoh {example}", + "electrum_host": "Contoh {example}", "electrum_offline_mode": "Mode luring", "use_ssl": "Gunakan SSL", "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_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": "Atur ulang ke bawaan", "encrypt_title": "Keamanan", "encrypt_tstorage": "Penyimpanan", - "encrypt_use": "Gunakan {ketik}", + "encrypt_use": "Gunakan {type}", "general": "Umum", - "general_adv_mode": "Enable advanced mode", "general_continuity": "Keberlanjutan", "header": "setting", "language": "Bahasa", "last_updated": "Terakhir Diperbaharui", - "lightning_error_lndhub_uri": "LNDHub URI tidak valid", "lightning_saved": "Perubahan Anda telah berhasil disimpan.", "lightning_settings": "Pengaturan Lightning", - "tor_settings": "Pengaturan tor", "network": "Jaringan", "network_broadcast": "Siaran Transaksi", "network_electrum": "Server Electrum", @@ -278,52 +215,42 @@ "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", "plausible_deniability": "Plausible deniability...", "privacy": "Privasi", "privacy_read_clipboard": "Baca Clipboard", "privacy_system_settings": "Pengaturan sistem", "privacy_quickactions": "Jalan Pintas Dompet", "privacy_do_not_track": "Nonaktifkan Analitik", - "retype_password": "Ulangi kata sandi", "save": "simpan", "saved": "Tersimpan", - "success_transaction_broadcasted": "Berhasil! Transaksi Anda telah disiarkan!", "total_balance": "Total saldo", "tools": "Alat" }, - "notifications": { - "no_and_dont_ask": "Tidak, dan jangan tanya saya lagi", - "ask_me_later": "Tanyakan saya nanti" - }, "transactions": { "cancel_no": "Transaksi ini tidak bisa digantikan.", "cancel_title": "Batalkan transaksi ini (RBF)", - "confirmations_lowercase": "{konfirmasi} konfirmasi", + "confirmations_lowercase": "{confirmations} konfirmasi", "copy_link": "Salin tautan", "expand_note": "Perluas Catatan", "cpfp_create": "Buat", "details_balance_hide": "Sembunyikan Saldo", "details_balance_show": "Tunjukkan Saldo", "details_copy": "Salin", - "details_copy_amount": "Salin Nominal", "details_copy_note": "Salin Catatan", "details_copy_txid": "Salin ID Transaksi", "details_from": "Input", "details_inputs": "Input", "date": "Tanggal", "details_received": "Diterima", - "transaction_note_saved": "Catatan transaksi telah berhasil tersimpan.", - "details_show_in_block_explorer": "Tampilkan di block explorer", "details_title": "Transaksi", "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", + "transaction": "Transaksi", "status_cancel": "Batalkan Transaksi", "txid": "ID Transaksi", "updating": "Memperbaharui..." @@ -331,28 +258,26 @@ "wallets": { "add_bitcoin": "Bitcoin", "add_create": "Buat", + "total_balance": "Total saldo", + "add_entropy": "Entropi", "add_import_wallet": "Impor dompet", - "add_lndhub": "Hubungan ke LNDHub Anda", "add_placeholder": "dompet pertama saya", "add_title": "tambah dompet", "add_wallet_name": "nama dompet", "add_wallet_type": "tipe", - "balance": "Saldo", "details_address": "Alamat", "details_advanced": "Lanjutan", "details_are_you_sure": "Yakin?", "details_connected_to": "Terhubung ke", "details_delete": "Hapus", "details_delete_wallet": "Hapus Dompet", - "details_display": "Tampilkan dalam Daftar Dompet", "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_show_xpub": "Tampilkan XPUB dompet", "details_show_addresses": "Tunjukkan alamat", "details_title": "Dompet", + "wallets": "Dompet", "details_type": "Tipe", "details_yes_delete": "Ya, hapus", "export_title": "ekspor dompet", @@ -368,41 +293,37 @@ "import_discovery_title": "Penemuan", "import_discovery_subtitle": "Pilih dompet yang ditemukan", "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", "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_empty_txs1": "Transaksimu akan muncul di sini,", "list_empty_txs2": "Mulai dengan dompet Anda.", "list_latest_transaction": "transaksi terbaru", "list_long_choose": "Pilih Foto", + "paste_from_clipboard": "Tempel", "list_long_scan": "Pindai QR Code", "list_title": "Dompet", "list_tryagain": "Coba lagi", - "reorder_title": "Susun Dompet", "please_continue_scanning": "Harap lanjutkan memindai.", "select_wallet": "Pilih dompet", "xpub_copiedToClipboard": "Disalin ke clipboard.", "pull_to_refresh": "Tarik untuk Segarkan", "xpub_title": "XPUB dompet" }, + "total_balance_view": { + "title": "Total saldo" + }, "multisig": { - "multisig_vault": "Brankas", "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", + "vault_key": "Kunci Brankas {number}", + "fee": "Tarif: {number}", + "fee_btc": "{number} BTC", "confirm": "Konfirmasi", "header": "Kirim", - "share": "bagikan", "view": "Lihat", "manage_keys": "Kelola kunci", "how_many_signatures_can_bluewallet_make": "BlueWallet bisa membuat berapa tanda tangan?", - "signatures_we_can_make": "bisa membuat {nomor}", + "signatures_we_can_make": "bisa membuat {number}", "scan_or_import_file": "Pindai atau impor berkas", "lets_start": "Ayo mulai", "create": "Buat", @@ -424,10 +345,9 @@ }, "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" + "check_address": "Cek alamat" }, "cc": { "change": "Ubah", @@ -435,7 +355,8 @@ "freezeLabel": "Bekukan", "freezeLabel_un": "Jangan bekukan", "use_coin": "Gunakan Koin", - "use_coins": "Gunakan koin" + "use_coins": "Gunakan koin", + "sort_status": "Status" }, "units": { "BTC": "BTC", diff --git a/loc/index.ts b/loc/index.ts index 1fc0a85ac41..a15e2775a3d 100644 --- a/loc/index.ts +++ b/loc/index.ts @@ -1,21 +1,29 @@ -import Localization from 'react-localization'; 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 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; +} + const setDateTimeLocale = async () => { let lang = (await AsyncStorage.getItem(STORAGE_KEY)) ?? ''; let localeForDayJSAvailable = true; @@ -30,12 +38,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 +71,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 +102,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 +145,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 +152,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 +177,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'); @@ -198,14 +244,15 @@ const init = async () => { }; init(); -const loc = new Localization({ - en: require('./en.json'), +const loc: ILocalization = new Localization({ + en: enJson, ar: require('./ar.json'), be: require('./be@tarask.json'), bg_bg: require('./bg_bg.json'), + bqi: require('./bqi.json'), ca: require('./ca.json'), - cy: require('./cy.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'), @@ -214,6 +261,7 @@ const loc = new Localization({ 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'), @@ -221,20 +269,24 @@ const loc = new Localization({ 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'), - kn: require('./kn.json'), - ne: require('./ne.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'), - 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'), + sq_AL: require('./sq_AL.json'), sr_rs: require('./sr_RS.json'), sv_se: require('./sv_se.json'), th_th: require('./th_th.json'), @@ -259,10 +311,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 +328,12 @@ 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) => { +export const removeTrailingZeros = (value: number | string): string => { let ret = value.toString(); if (ret.indexOf('.') === -1) { @@ -295,17 +352,17 @@ 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 { + return satoshiToLocalCurrency(balance); } } @@ -316,21 +373,18 @@ 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 { + return satoshiToLocalCurrency(balance); } - return balance.toString(); } /** @@ -344,7 +398,7 @@ export function formatBalanceWithoutSuffix(balance = 0, toUnit: string, withForm export function formatBalancePlain(balance = 0, toUnit: string, withFormatting = false) { 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..6e44f788ad4 100644 --- a/loc/it.json +++ b/loc/it.json @@ -4,11 +4,13 @@ "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", "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", @@ -16,20 +18,16 @@ "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" }, "azteco": { "codeIs": "Il codice del tuo voucher è", @@ -51,69 +49,47 @@ "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_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", - "preimage": "Preimage", "sats": "sats.", + "date_time": "Data e Ora", "wasnt_paid_and_expired": "Questa fattura non è stata pagata ed è scaduta." }, "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", + "ask_yes": "Si", + "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", @@ -161,9 +137,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", @@ -193,22 +167,18 @@ "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" }, "settings": { @@ -225,17 +195,12 @@ "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", @@ -243,7 +208,6 @@ "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", @@ -252,32 +216,16 @@ "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,11 +233,8 @@ "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", @@ -297,33 +242,25 @@ "notifications": "Notifiche", "open_link_in_explorer": "Apri il collegamento nel navigatore", "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", "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" }, "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" + "would_you_like_to_receive_notifications": "Desideri ricevere delle notifiche quando ricevi dei pagamenti?" }, "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.", @@ -338,9 +275,7 @@ "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", @@ -349,8 +284,6 @@ "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?", @@ -363,6 +296,7 @@ "eta_1d": "ETA: In ~1 giorno", "view_wallet": "Visualizza {walletLabel}", "list_title": "Transazioni", + "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)", @@ -376,44 +310,39 @@ "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_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_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", "enter_bip38_password": "Inserisci la password per decriptare", "export_title": "Esporta portafoglio", @@ -433,44 +362,36 @@ "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_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" }, + "total_balance_view": { + "title": "Saldo totale" + }, "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 +428,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)", @@ -545,21 +460,21 @@ "owns": "{label} è proprietario dell'indirizzo {address}", "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" + "no_wallet_owns_address": "Nessuno dei portafogli disponibili è proprietario dell'indirizzo fornito." }, "cc": { "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" }, "units": { "BTC": "BTC", @@ -601,8 +516,6 @@ }, "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" } diff --git a/loc/jp_jp.json b/loc/jp_jp.json index 6410c53ff98..3e86db0161d 100644 --- a/loc/jp_jp.json +++ b/loc/jp_jp.json @@ -4,32 +4,31 @@ "cancel": "中止", "continue": "続行", "clipboard": "クリップボード", + "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 +37,14 @@ "redeem": "ウォレットに交換する", "redeemButton": "交換する", "success": "成功", + "successMessage": "バウチャーの交換に成功しました! まもなくビットコインウォレットに入金されます。", "title": "Azte.co バウチャーを交換" }, "entropy": { "save": "保存", "title": "エントロピー", - "undo": "元に戻す" + "undo": "元に戻す", + "amountOfEntropy": "{limit}ビット中{bits}ビット" }, "errors": { "broadcast": "ブロードキャスト失敗", @@ -51,80 +52,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 のダミーの暗号化ウォレットを復号することとなり、メインのウォレットは隠匿されコインは安全に保護されます。", "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,8 +130,15 @@ "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": "費用のバンプ(増加)を許可", @@ -165,8 +156,10 @@ "details_next": "次", "details_no_signed_tx": "選択したファイルには、インポート可能なトランザクションが含まれていません。", "details_note_placeholder": "ラベル", + "counterparty_label_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,11 +197,15 @@ "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": { @@ -222,20 +219,21 @@ "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": "デフォルト情報", @@ -244,6 +242,7 @@ "electrum_connected": "接続済", "electrum_connected_not": "未接続", "electrum_error_connect": "指定されたElectrumサーバーに接続できません", + "electrum_error_connect_tor": "入力されたElectrumサーバーに接続できません。Orbitアプリが接続されていることを確認して再度お試しください。", "lndhub_uri": "例:{example}", "electrum_host": "例:{example}", "electrum_offline_mode": "オフラインモード", @@ -254,30 +253,31 @@ "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_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": "ストレージの暗号化が有効の場合この機能は使えません。", + "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 +285,36 @@ "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 へ接続するのは変更保存後に作成されたウォレットのみですので注意してください。", "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,13 +322,16 @@ }, "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": "ノートを展開", @@ -338,9 +341,7 @@ "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をコピー", @@ -349,9 +350,14 @@ "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": "送り先", "enable_offline_signing": "このウォレットではオフライン署名が使われていません。今すぐ有効にしますか?", "list_conf": "コンファメーション: {number}", @@ -363,6 +369,7 @@ "eta_1d": "到着予定:~1日以内", "view_wallet": "{walletLabel}を見る", "list_title": "取引", + "transaction": "取引", "open_url_error": "デフォルトブラウザでリンクを開けません。デフォルトブラウザを変更してやり直してください。", "rbf_explain": "このトランザクションを手数料の高いものに置き換えることで、マイニングが早く行われるようにします。これをRBF - Replace By Feeといいます。", "rbf_title": "手数料をバンプ (RBF)", @@ -370,12 +377,23 @@ "status_cancel": "トランザクションをキャンセル", "transactions_count": "トランザクションカウント", "txid": "トランザクションID", - "updating": "更新中…" + "from": "From: {counterparty}", + "to": "To: {counterparty}", + "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 +407,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 +422,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,10 +448,12 @@ "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": "読み込み中...", @@ -448,40 +469,65 @@ "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": "とにかく削除" + }, + "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": "Bluewalletが作れる署名の数", "signatures_required_to_spend": "必要な署名 {number}", @@ -516,6 +562,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 +595,11 @@ "no_wallet_owns_address": "利用可能なウォレットの中にそのアドレスを持つものはありません。", "view_qrcode": "QRコードを見る" }, + "autofill_word": { + "enter": "部分的なニーモニックフレーズを入力", + "generate_word": "最後のワードを生成", + "error": "入力されたのは11または23語の部分的なニーモニックではありません!" + }, "cc": { "change": "チェンジ", "coins_selected": "選択済コイン ({number})", @@ -559,7 +611,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 +627,8 @@ "sats": "sats" }, "addresses": { + "copy_private_key": "秘密鍵をコピー", + "sensitive_private_key": "警告:秘密鍵はとても重要です。続行しますか?", "sign_title": "メッセージの署名/検証", "sign_help": "ここではビットコインアドレスに基づく暗号署名の作成・検証ができます。", "sign_sign": "署名", @@ -601,9 +662,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..73d1aa7fd4f --- /dev/null +++ b/loc/kk@Cyrl.json @@ -0,0 +1,84 @@ +{ + "_": { + "bad_password": "Құпиясөз қате. Қайталап көріңіз.", + "cancel": "Бас тарту", + "continue": "Жалғастыру", + "enter_password": "Құпиясөзді еңгізіңіз", + "never": "Ешқашан", + "of": "{number} / {total}", + "ok": "ОК", + "yes": "Иә", + "no": "Жоқ", + "close": "Жабу" + }, + "entropy": { + "save": "Сақтау", + "undo": "Болдырмау" + }, + "errors": { + "error": "Қате" + }, + "lnd": { + "expiresIn": "Мерзімі {time} минутта аяқталады", + "payButton": "Төлем жасау", + "refill": "Қайта толтыру" + }, + "lndViewInvoice": { + "sats": "сат." + }, + "receive": { + "details_create": "Жасау" + }, + "send": { + "broadcastError": "Қате", + "broadcastPending": "Күтілуде", + "confirm_header": "Растау", + "confirm_sendNow": "Қазір жіберу", + "create_fee": "Комиссия", + "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 сағат", + "header": "Жіберу", + "input_done": "Дайын", + "open_settings": "Баптауларды ашу", + "success_done": "Дайын" + }, + "settings": { + "about_license": "MIT License", + "header": "Баптаулар", + "save": "Сақтау" + }, + "transactions": { + "cpfp_create": "Жасау", + "pending": "Күтілуде" + }, + "wallets": { + "add_create": "Жасау", + "details_address": "Адрес", + "import_do_import": "Еңгізу", + "import_search_accounts": "Шот іздеу", + "import_title": "Енгізу", + "list_tryagain": "Қайталау" + }, + "multisig": { + "confirm": "Растау", + "header": "Жіберу", + "create": "Жасау" + }, + "addresses": { + "sign_verify": "Растау", + "sign_placeholder_address": "Адрес" + }, + "bip47": { + "payment_code": "Төлем коды", + "not_found": "Төлем коды табылмады" + } +} diff --git a/loc/kn.json b/loc/kn.json index 66fde1ef389..4f2e212afa0 100644 --- a/loc/kn.json +++ b/loc/kn.json @@ -10,18 +10,12 @@ "storage_is_encrypted": "ನಿಮ್ಮ ಸಂಗ್ರಹಣೆಯನ್ನು ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಅದನ್ನು ಡೀಕ್ರಿಪ್ಟ್ ಮಾಡಲು ಪಾಸ್ವರ್ಡ್ ಅಗತ್ಯವಿದೆ.", "yes": "ಹೌದು", "no": "ಇಲ್ಲ", - "save": "ಉಳಿಸಿ", - "wallet_key": "ವ್ಯಾಲೆಟ್ ಕೀ", - "invalid_animated_qr_code_fragment": "ಅಮಾನ್ಯ ಅನಿಮೇಟೆಡ್ QR ಕೋಡ್. ದಯವಿಟ್ಟು ಪುನಃ ಪ್ರಯತ್ನಿಸಿ." + "wallet_key": "ವ್ಯಾಲೆಟ್ ಕೀ" }, "entropy": { "save": "ಉಳಿಸಿ" }, "settings": { - "electrum_clear_alert_cancel": "ರದ್ದುಮಾಡಿ", "save": "ಉಳಿಸಿ" - }, - "wallets": { - "details_save": "ಉಳಿಸಿ" } } diff --git a/loc/ko_KR.json b/loc/ko_KR.json index 0cb7f599d75..19799641116 100644 --- a/loc/ko_KR.json +++ b/loc/ko_KR.json @@ -4,24 +4,31 @@ "cancel": "취소", "continue": "계속", "clipboard": "붙여넣기판", + "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": "프로모션 코드", @@ -30,12 +37,14 @@ "redeem": "지갑으로 교환", "redeemButton": "교환", "success": "성공", + "successMessage": "바우처가 성공적으로 사용되었습니다! 곧 비트코인 지갑으로 자금이 입금될 것입니다.", "title": "제목" }, "entropy": { "save": "저장", "title": "제목", - "undo": "실행 취소" + "undo": "실행 취소", + "amountOfEntropy": "{limit} 비트 중 {bits} 비트" }, "errors": { "broadcast": "브로드 캐스트 실패", @@ -43,80 +52,63 @@ "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:", "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": "새로운 스토리지가 활성화됩니다. 최소 금액을 넣으면 더 신뢰성있어보입니다. ", "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_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": "이 주소는 라이트닝 청구서로 보입니다. 지불하기 위해 라이트닝 지갑으로 가십시요.", @@ -138,8 +130,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,15 +152,17 @@ "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": "자기보관용 노트", + "counterparty_label_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": "다음", @@ -172,6 +173,7 @@ "fee_1d": "1일", "fee_3h": "3시간", "fee_custom": "맞춤형", + "insert_custom_fee": "수수료 입력", "fee_fast": "빠름", "fee_medium": "중간", "fee_replace_minvb": "지불하기 원하시는 총 수수료율 (가상바이트 당 사토시)은 반드시 {min} 가상바이트당 사토시보다 높아야 합니다", @@ -182,50 +184,56 @@ "input_done": "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 코드를 찾을 수 없습니다.\n이미지에 텍스트나 버튼 등 추가 내용 없이 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": "기본 정보", @@ -234,6 +242,7 @@ "electrum_connected": "연결되었습니다.", "electrum_connected_not": "연결되지 않았습니다.", "electrum_error_connect": "제공된 일렉트럼 서버에 연결할 수 없습니다.", + "electrum_error_connect_tor": "제공된 일렉트럼 서버에 연결할 수 없습니다. Orbot 앱이 연결되어 있는지 확인한 후 다시 시도해 주세요.", "lndhub_uri": "예, {example}", "electrum_host": "예, {example}", "electrum_offline_mode": "오프라인 모드", @@ -242,32 +251,33 @@ "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": "토르 연결이 지원되지 않습니다", + "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}이 사용될 것입니다. 암호화된 저장장치를 해제하는되는 사용되지 않을 것 입니다.", + "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": "사용가능으로 설정시, 애플 아이클라우드에 연결된 다른 기기을 사용하여 선택된 지갑들과 거래내역을 보실수 있습니다.", "groundcontrol_explanation": "그라운드콘트롤은 비트코인 지갑을 위한 무상, 원본공개형 알림장치 서버 입니다.", @@ -275,36 +285,36 @@ "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에 연결됩니다.", "network": "네트워크", "network_broadcast": "거래내역 송출", "network_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": "지갑의 잔고를 신속 확인하기 위해서 바탕화면에 있는 블루월렛 어플 아이콘에 한동안 눌러주기를 하세요", + "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,13 +322,16 @@ }, "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": "노트 확장하기", @@ -328,29 +341,35 @@ "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})", "eta_10m": "예상경과시간: 약 10분", "eta_3h": "예상경과시간: 약 3시간", "eta_1d": "예상경과시간: 약 1일", + "view_wallet": "{walletLabel} 보기", "list_title": "트랜잭션", + "transaction": "트랜잭션", "open_url_error": "기본 브라우저로는 링크를 열수 없습니다. 기존 브라우저를 바꾼다음에 다시 시도해 보시기 바랍니다.", "rbf_explain": "빨이 채굴될 수 있도록 당 거래내역을 더 높은 수수료율을 내는 거래내역으로 교체하겠습니다. 이런 방법을 RBF라고 합니다 -수수료를 통한 교체", "rbf_title": "급행 수수료(CPFP)", @@ -358,49 +377,62 @@ "status_cancel": "트랜잭션 취소", "transactions_count": "거래 건수", "txid": "트랜잭션 아이디", - "updating": "갱신중..." + "from": "보낸 대상: {counterparty}", + "to": "받는 대상: {counterparty}", + "updating": "갱신중...", + "watchOnlyWarningTitle": "보안 경고", + "watchOnlyWarningDescription": "사기꾼들은 종종 ‘조회 전용’ 지갑을 사용해 사용자를 속입니다. 이러한 지갑은 자금을 송금하거나 제어할 수 없고 잔액 조회만 가능합니다.", + "custom_fee_warning_title": "경고", + "custom_fee_warning_description": "1 sat/vB 이하의 수수료도 유효하지만, 노드의 정책에 따라 트랜잭션이 릴레이되지 않을 수 있습니다." }, "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": "지갑", "details_type": "형태", "details_use_with_hardware_wallet": "하드웨어 지갑 사용하기", - "details_wallet_updated": "지갑 갱신완료", "details_yes_delete": "네 삭제합니다.", "enter_bip38_password": "해독하기 위해 패스워드 입력", "export_title": "지갑 내보내기", @@ -413,61 +445,89 @@ "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}", @@ -493,20 +553,21 @@ "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입니다-다른 지갑으로 들여오기할 수 있는 상태. 공유하기를 하셔도 안전합니다.", + "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) 사용 건너뛰기", @@ -532,20 +593,32 @@ "enter_address": "주소 입력", "check_address": "주소 확인", "no_wallet_owns_address": "제공된 주소를 가지고 있는 지갑이 없습니다.", - "view_qrcode": "QR코드 보기" + "view_qrcode": "QR 코드 보기" + }, + "autofill_word": { + "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 +627,8 @@ "sats": "사토시" }, "addresses": { + "copy_private_key": "개인 키 복사", + "sensitive_private_key": "경고: 개인 키는 매우 민감한 정보입니다. 계속하시겠습니까?", "sign_title": "메시지 사인/검증", "sign_help": "여기서 비트코인 주소에 기반한 암호화 서명을 만들거나 검증할 수 있습니다.", "sign_sign": "서명", @@ -584,5 +659,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..4fe1478ca39 100644 --- a/loc/languages.ts +++ b/loc/languages.ts @@ -1,4 +1,4 @@ -export const AvailableLanguages = Object.freeze([ +export const AvailableLanguages: Readonly = Object.freeze([ { label: 'English', value: 'en' }, { label: 'Afrikaans (AFR)', value: 'zar_afr' }, { label: 'العربية (AR)', value: 'ar', isRTL: true }, @@ -17,16 +17,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 +43,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' }, @@ -46,3 +52,9 @@ export const AvailableLanguages = Object.freeze([ { label: 'Türkçe (TR)', value: 'tr_tr' }, { label: 'Xhosa (XHO)', value: 'zar_xho' }, ]); + +export type TLanguage = { + label: string; + value: string; + isRTL?: boolean; +}; diff --git a/loc/lrc.json b/loc/lrc.json new file mode 100644 index 00000000000..b464e5300dd --- /dev/null +++ b/loc/lrc.json @@ -0,0 +1,244 @@ +{ + "_": { + "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" + }, + "azteco": { + "codeIs": "کود تخفیف شما", + "errorSomething": "موشکلؽ پؽش اوما. اؽ کود تخفیف هنی قوۊل هؽ؟", + "redeem": "ازاف کردن و کیف پیلٛ", + "redeemButton": "فعال کردن", + "success": "مۏوفق بی", + "title": "فعال کردن کود تخفیف Azte.co" + }, + "entropy": { + "title": "آنتروپی" + }, + "errors": { + "error": "ختا", + "network": "ختا شبکه" + }, + "lnd": { + "errorInvoiceExpired": "سۊرت هساو مونقزی بیه.", + "expired": "مونقزی بیه", + "payButton": "پرداخت", + "payment": "پرداخت", + "placeholder": "سۊرت هساو یا آدرس", + "potentialFee": "کارمزد ائتمالی: {fee}", + "refill": "پور کردن", + "refill_external": "پور کردن وا کیف پیلٛ خارجی", + "refill_lnd_balance": "پور کردن مۉجۊدی کیف پیلٛ لایتنینگ" + }, + "lndViewInvoice": { + "additional_info": "دونسمنیا بؽشتر", + "for": "سی:", + "lightning_invoice": "سۊرت هساو لایتنینگ", + "sats": "ساتۊشی پرداخت بکو.", + "wasnt_paid_and_expired": "اؽ سۊرت هساو پرداخت نبیه ۉ مونقزی بیه." + }, + "plausibledeniability": { + "password_should_not_match": "رزم ها و کار مؽره. ی رزم هنی ن و کار بیر." + }, + "pleasebackup": { + "ask_no": "ن، م نارم.", + "ask_yes": "ٱ، مه دارم." + }, + "receive": { + "details_setAmount": "گرؽتن وا مقدار", + "details_share": "یک رسونی...", + "header": "گرتن" + }, + "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": "امزا کردن تراکونش" + }, + "settings": { + "about": "دبار", + "about_selftest": "ر ونن خوش آزمایی", + "about_selftest_electrum_disabled": "خوش آزمایی د هالت آفلاین د دسرس نؽ. هالت آفلاینن قیر فعال بکو ۉ دۏورته تفره بکو.", + "about_sm_github": "گیت هاب", + "about_sm_telegram": "تورگه تلگرام", + "biometrics": "بیومتریک", + "biom_conf_identity": "هوویت خوتونه تایید بکؽت.", + "currency": "واهد پیلٛ", + "default_info": "دونسمنیا پؽش فرز", + "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": "اوزاریا" + }, + "transactions": { + "copy_link": "لٛف گری لینگ", + "details_copy": "لٛف گری", + "date": "ویرگار", + "details_received": "گرته بیه", + "details_title": "تراکونش", + "list_title": "تراکونشیا", + "transaction": "تراکونش", + "open_url_error": "نا مووفق د واز کردن لینگ وا گشت گر پؽش فرز. گشت گر پؽش فرز خوته آلشت کوݩ ۉ د نۊ تفره بکوݩ." + }, + "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": "دۏورته تفره بکوݩ" + }, + "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": "هالت پؽش رته" + }, + "cc": { + "change": "باقی منه", + "header": "دؽونداری کردن", + "use_coin": "و کار گرتن کوین", + "use_coins": "و کار گرتن کوینیا", + "sort_value": "مقدار", + "sort_status": "وزیت", + "sort_by": "ترتیب و ری" + }, + "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": "تراکونشیا" + }, + "lnurl_auth": { + "register_question_part_1": "مؽهایت هساوی مؽن", + "login_question_part_1": "مؽهایت مؽن", + "auth_question_part_1": "مؽهایت مؽن", + "authenticate": "ائراز" + }, + "bip47": { + "payment_code": "کود پرداخت", + "contacts": "هومدنگیا", + "add_contact": "اوردن هومدنگ", + "not_found": "کود پرداخت نجۊرست" + } +} diff --git a/loc/ms.json b/loc/ms.json index fcd8b90fba2..b468195a987 100644 --- a/loc/ms.json +++ b/loc/ms.json @@ -4,21 +4,17 @@ "cancel": "Batalkan", "continue": "Teruskan", "clipboard": "Papan Sepit", + "discard_changes": "Buangkan perubahan?", "enter_password": "Masukkan kata laluan", "never": "Tiada", - "disabled": "Dilumpuhkan", "of": "{number} daripada {total}", "ok": "OK", "storage_is_encrypted": "Simpanan anda disulitkan. Kata laluan diperlukan untuk nyahsulit.", "yes": "Ya", "no": "Tidak", - "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" + "wallet_key": "Anak kunci dompet" }, "azteco": { "codeIs": "Kod baucar anda ialah", @@ -40,75 +36,42 @@ "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?", "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", "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" + "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", "sats": "sat.", "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.", "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", + "ok": "OK, sudah disalin.", "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. ", - "text_lnd": "Sila simpan sandaran dompet ini. Sandaran ini membolehkan anda untuk mengembalikan dompet ini dalam kes kehilangan.", - "title": "Dompet anda sudah dicipta" + "text_lnd": "Sila simpan sandaran dompet ini. Sandaran ini membolehkan anda untuk mengembalikan dompet ini dalam kes kehilangan." }, "receive": { "details_create": "Cipta", "details_label": "Penerangan", "details_setAmount": "Terima dengan jumlah", - "details_share": "Kongsi", "header": "Terima" }, "send": { @@ -144,7 +107,6 @@ "details_error_decode": "Tidak dapat menyahkod alamat Bitcoin", "details_fee_field_is_not_valid": "Yuran tidak sah.", "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", @@ -170,11 +132,8 @@ "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", "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", @@ -183,11 +142,9 @@ "no_tx_signing_in_progress": "Tiada penandatanganan urus niaga sedang berjalan.", "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.", "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.", "problem_with_psbt": "Masalah dengan PSBT" }, "settings": { @@ -202,14 +159,10 @@ "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", "biometrics": "Biometrik", "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_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.", @@ -218,7 +171,6 @@ "default_wallets": "Paparkan Semua Dompet", "electrum_connected": "Terhubung", "electrum_connected_not": "Tidak Terhubung", - "electrum_error_connect": "Tidak dapat menghubungi pelayan Electrum yang disediakan", "lndhub_uri": "cth., {example}", "electrum_host": "cth., {example}", "electrum_offline_mode": "Mod Luar Talian", @@ -227,43 +179,24 @@ "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?", "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_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_reset": "Tetapkan semula kepada lalai", "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_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.", "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.", "header": "Tetapan", "language": "Bahasa", "language_isRTL": "Pemulaan semula BlueWallet diperlukan untuk pengalihan bahasa berlaku.", - "lightning_error_lndhub_uri": "URL LNDHub tidak sah", "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.", "network": "Rangkaian", "network_broadcast": "Siarkan Urus Niaga", "network_electrum": "Pelayan Electrum", @@ -271,32 +204,24 @@ "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.", "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_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", "selfTest": "Swaujian", "save": "Simpan", "saved": "Disimpan", - "success_transaction_broadcasted": "Berjaya! Urus niaga anda sudah disiarkan!", "total_balance": "Jumlah Baki", "total_balance_explanation": "Paparkan jumlah baki kesemua dompet anda di widget layar beranda anda.", "widgets": "Widget", "tools": "Alatan" }, "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" + "would_you_like_to_receive_notifications": "Adakah anda mahu menerima pemberitahuan apabila anda mendapat bayaran sedang masuk?" }, "transactions": { "cancel_no": "Urus niaga ini tidak boleh diganti.", @@ -310,14 +235,11 @@ "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_inputs": "Masukan", "details_outputs": "Keluaran", "details_received": "Diterima", - "transaction_note_saved": "Nota urus niaga berjaya disimpan.", - "details_show_in_block_explorer": "Lihat di Penjelajah Bongkah.", "details_title": "Urus niaga", "details_to": "Keluaran", "enable_offline_signing": "Dompet ini tidak digunakan bersama dengan penandatanganan luar talian. Adakah anda mahu membolehkan ciri ini sekarang?", @@ -329,6 +251,7 @@ "eta_3h": "ETA: Dalam ~3 jam", "eta_1d": "ETA: Dalam ~1 hari", "list_title": "Urus niaga", + "transaction": "Urus niaga", "rbf_title": "Tambah Yuran (RBF)", "status_bump": "Tambah Yuran", "status_cancel": "Batalkan Urus Niaga", @@ -340,41 +263,36 @@ "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Dompet Bitcoin yang mudah dan berkuasa", "add_create": "Cipta", + "total_balance": "Jumlah Baki", + "add_entropy": "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_placeholder": "Alamat Nod Anda", "add_title": "Tambah Dompet", "add_wallet_name": "Nama", "add_wallet_type": "Jenis", - "balance": "Baki", "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?", "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_delete": "Padam", "details_delete_wallet": "Padamkan Dompet", "details_derivation_path": "laluan terbitan", - "details_display": "Paparkan di dalam Senarai Dompet", "details_export_backup": "Pindahkan Keluar/Sandarkan", "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", "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", @@ -390,34 +308,34 @@ "import_title": "Pindah Masuk", "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_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", "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.", "add_ln_wallet_first": "Anda perlu menambah dompet Lightning terlebih dahulu.", "identity_pubkey": "Kunci Umum Keperibadian", "xpub_title": "Dompet XPUB" }, + "total_balance_view": { + "title": "Jumlah Baki" + }, "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", @@ -427,7 +345,6 @@ "fee_btc": "{number} BTC", "confirm": "Pasti", "header": "Hantar", - "share": "Kongsi", "view": "Lihat", "manage_keys": "Uruskan Anak Kunci", "how_many_signatures_can_bluewallet_make": "berapa banyak tandatangan BlueWallet boleh buat", @@ -452,20 +369,14 @@ "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", "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}. ", "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.", "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.", "export_signed_psbt": "Pindah Keluar PSBT Bertandatangan Penuh", "input_fp": "Masukkan cap jari", "input_fp_explain": "Langkau untuk gunakan nilai lalai (00000000)", @@ -495,14 +406,14 @@ "cc": { "change": "Ubah", "coins_selected": "Duit yang Dipilih ({number})", - "empty": "Dompet ini tidak mempunyai sebarang duit ketika 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_status": "Keadaan" }, "units": { "BTC": "BTC", diff --git a/loc/nb_no.json b/loc/nb_no.json index ac4b203a3ca..386cd125856 100644 --- a/loc/nb_no.json +++ b/loc/nb_no.json @@ -4,24 +4,17 @@ "cancel": "Avbryt", "continue": "Fortsett", "clipboard": "Utklippstavle", + "discard_changes": "Forkaste endringer?", "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.", "yes": "Ja", "no": "Nei", - "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" + "wallet_key": "Lommebok-nøkkel" }, "azteco": { "codeIs": "Din kupongkode er", @@ -43,75 +36,43 @@ "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?", "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", "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." }, "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.", "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", + "ok": "Greit, jeg skrev det ned.", "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" + "text_lnd": "Vennligst lagre denne sikkerhetskopien. Den lar deg gjenopprette lommeboken i tilfelle tap." }, "receive": { "details_create": "Oprett", "details_label": "Beskrivelse", "details_setAmount": "Motta med beløp", - "details_share": "Del", "header": "Motta", "maxSats": "Maksimalt beløp er {max} sats", "maxSatsFull": "Maksimalt beløp er {max} sats eller {currency}", @@ -153,9 +114,7 @@ "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_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", @@ -185,8 +144,6 @@ "permission_camera_message": "Vi trenger din tillatelse for å bruke kameraet ditt.", "psbt_sign": "Signer en transaksjon", "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,11 +153,9 @@ "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.", "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" }, "settings": { @@ -215,17 +170,12 @@ "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", "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_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_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", @@ -233,41 +183,24 @@ "default_wallets": "Se Alle Lommebøker", "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}", + "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", "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_reset": "Tilbakestill til standard", "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_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.", "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,45 +208,34 @@ "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", "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.", "network": "Nettverk", "network_broadcast": "Kringkast Transaksjon", "network_electrum": "Electrum Server", "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.", "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_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", "selfTest": "Selvtest", "save": "Lagre", "saved": "Lagret", - "success_transaction_broadcasted" : "Suksess! Transaksjonen din har blitt 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" }, "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" + "would_you_like_to_receive_notifications": "Vil du motta varsler når du mottar innbetalinger?" }, "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.", @@ -328,9 +250,7 @@ "cpfp_title": "Betal høyere avgift (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", @@ -338,8 +258,6 @@ "details_inputs": "Inndata", "details_outputs": "Utdata", "details_received": "Mottatt", - "transaction_note_saved": "Transaksjonsnotatet er lagret.", - "details_show_in_block_explorer": "Vis i Block Explorer", "details_title": "Transaksjon", "details_to": "Utdata", "enable_offline_signing": "Denne lommeboken brukes ikke i forbindelse med en offline-signering. Vil du aktivere det nå?", @@ -352,6 +270,7 @@ "eta_1d": "ETA: Om ~1 dag", "view_wallet": "Se {walletLabel}", "list_title": "Transaksjoner", + "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)", @@ -365,43 +284,38 @@ "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Enkel og kraftig Bitcoin-lommebok", "add_create": "Opprett", + "total_balance": "Total balanse", + "add_entropy": "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_placeholder": "Din nodeadresse", "add_placeholder": "min første lommebok", "add_title": "Legg til lommebok", "add_wallet_name": "Navn", "add_wallet_type": "Type", - "balance": "Balanse", "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?", "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_delete": "Slett", "details_delete_wallet": "Slett Lommebok", "details_derivation_path": "derivation path", - "details_display": "Vis i lommebokliste", "details_export_backup": "Eksporter / backup", "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", + "wallets": "Lommebøker", "details_type": "Type", "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", @@ -420,44 +334,37 @@ "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_derivation_title": "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_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_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.", "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.", "add_ln_wallet_first": "Du må først legge til en Lightning-lommebok.", "identity_pubkey": "Identity Pubkey", "xpub_title": "Wallet XPUB" }, + "total_balance_view": { + "title": "Total balanse" + }, "multisig": { - "multisig_vault": "Vault", + "multisig_vault": "Multisig Vault", "default_label": "Multisig Vault", "multisig_vault_explain": "Beste sikkerhet for store beløp", "provide_signature": "Skriv signatur", @@ -467,7 +374,6 @@ "fee_btc": "{number} BTC", "confirm": "Bekreft", "header": "Send", - "share": "Del", "view": "Vis", "manage_keys": "Administrer Nøkler", "how_many_signatures_can_bluewallet_make": "hvor mange signaturer kan BlueWallet lage", @@ -494,20 +400,14 @@ "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", "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.", "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.", "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.", "export_signed_psbt": "Eksporter Signert PSBT", "input_fp": "Skriv inn fingeravtrykk", "input_fp_explain": "Hopp over for å bruke standard (00000000)", @@ -532,21 +432,21 @@ "owns": "{label} eier {address}", "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" + "no_wallet_owns_address": "Ingen av de tilgjengelige lommebøkene eier den oppgitte adressen." }, "cc": { "change": "Endre", "coins_selected": "Mynter valgt ({number})", "selected_summ": "{value} valgt", - "empty": "Denne lommeboken har ingen mynter for øyeblikket.", "freeze": "Frys", "freezeLabel": "Frys", "freezeLabel_un": "Fjern frys", "header": "Myntkontroll", "use_coin": "Bryk 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_label": "Merkelapp", + "sort_status": "Status" }, "units": { "BTC": "BTC", diff --git a/loc/ne.json b/loc/ne.json index 060f9be83ed..9cb34fe7ae6 100644 --- a/loc/ne.json +++ b/loc/ne.json @@ -4,32 +4,23 @@ "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": "अवैध एनिमेटेड QRCode को टुक्रा। फेरि प्रयास गर्नुहोस।", - "file_saved": "तपाईंको फाइल - {filePath}, गन्तव्यमा - {destination} मा सेव गरिएको छ।", - "downloads_folder": "डाउनलोड फोल्डर", "close": "बन्द", "change_input_currency": "इनपुट मुद्रा परिवर्तन गर्नुहोस्", "refresh": "रिफ्रेस गर्नुहोस", - "more": "अरु", - "pick_file": "एउटा फाइल छान्नुहोस्", "enter_amount": "रकम राख्नुहोस्", "qr_custom_input_button": "अनुकूल इनपुट प्रविष्ट गर्न 10 पटक ट्याप गर्नुहोस्" }, - "alert": { - "default": "सचेत" - }, "azteco": { "codeIs": "तपाईंको भाउचर कोड", "errorBeforeRefeem": "रिडिम गर्नु अघि, तपाईंले पहिले बिटकोइन वालेट थप्नु पर्छ।", @@ -50,73 +41,41 @@ "network": "नेटवर्क एर्रोर" }, "lnd": { - "active": "सक्रिय", - "inactive": "निष्क्रिय", - "channels": "च्यानलहरू", - "no_channels": "च्यानलहरू छैनन्", - "claim_balance": "दावी ब्यालेन्स {balance}", - "close_channel": "च्यानलहरू बन्द गर्नुहोस्", - "new_channel": "नयाँ च्यानल", - "errorInvoiceExpired": "इनभ्वाइसको म्याद सकियो", - "force_close_channel": "जबरजस्ती च्यानल बन्द गर्ने हो?", "expired": "म्याद सकियो", - "node_alias": "नोड को उपनाम", "expiresIn": "{time} मिनेटमा म्याद सकिन्छ", "payButton": "तिर्नुहोस्", - "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": "लाइटनिङ वालेट ब्यालेन्स रिफिल गर्नुहोस्", - "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": "सातस।", "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": "ठीक छ, मैले यसलाई सुरक्षित गरेको छु", "text": "कृपया कागजको टुक्रामा यो निमोनिक फ्रेज लेख्न एक क्षण लिनुहोस्।\nयो तपाइँको ब्याकअप हो र तपाइँ वालेट रिकभर गर्न प्रयोग गर्न सक्नुहुन्छ।", - "text_lnd": "कृपया यो वालेट ब्याकअप सेव गर्नुहोस्। यसले वालेट हराएको अवस्थामा पुनर्स्थापना गर्न मद्दत गर्नेछ।", - "title": "तपाईंको वालेट सिर्जना गरिएको छ।" + "text_lnd": "कृपया यो वालेट ब्याकअप सेव गर्नुहोस्। यसले वालेट हराएको अवस्थामा पुनर्स्थापना गर्न मद्दत गर्नेछ।" }, "receive": { "details_create": "बनाउनुहोस", "details_label": "विवरण", "details_setAmount": "रकम सहित प्राप्त गर्नुहोस्", - "details_share": "सेयर गर्नुहोस्", "header": "प्राप्त गर्नुहोस्", "maxSats": "अधिकतम रकम {max} sats हो", "maxSatsFull": "अधिकतम रकम {max} sats वा {currency} हो", @@ -158,9 +117,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": "स्क्यान गर्न वा गन्तव्य आयात गर्न डबल ट्याप गर्नुहोस्", @@ -190,8 +147,6 @@ "permission_camera_message": "हामीलाई तपाईंको क्यामेरा प्रयोग गर्न तपाईंको अनुमति चाहिन्छ।", "psbt_sign": "लेनदेनमा हस्ताक्षर गर्नुहोस्", "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,11 +156,9 @@ "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 मा समस्या" }, "settings": { @@ -218,17 +171,12 @@ "run_performance_test": "परीक्षा प्रदर्शन", "about_selftest": "आत्म परीक्षण गर्नुहोस्", "about_selftest_ok": "सबै आन्तरिक परीक्षाहरू सफलतापूर्वक उत्तीर्ण भएका छन्। वालेट राम्रोसँग काम गर्दछ।", - "about_sm_discord": "विवाद सर्भर", "about_sm_telegram": "टेलिग्राम च्यानल", - "about_sm_twitter": "हामीलाई twitter मा फलोगर्नुहोस्", - "advanced_options": "विकास विकल्प", "biometrics": "बायोमेट्रिक्स", "biom_10times": "तपाईंले आफ्नो पासवर्ड १० पटक प्रविष्ट गर्ने प्रयास गर्नुभएको छ। तपाईं आफ्नो भण्डारण रिसेट गर्न चाहनुहुन्छ? यसले सबै वालेटहरू हटाउनेछ र तपाईंको भण्डारण डिक्रिप्ट गर्नेछ।", "biom_conf_identity": "आफ्नो पहिचान पुष्टि गर्नुहोस्।", - "biom_no_passcode": "तपाईंको यन्त्रमा पासकोड छैन। अगाडि बढ्नको लागि, कृपया सेटिङ एपमा पासकोड कन्फिगर गर्नुहोस्।", "biom_remove_decrypt": "तपाईंको सबै वालेटहरू हटाइनेछ र तपाईंको भण्डारण डिक्रिप्ट गरिनेछ। के तपाई निश्चित रूपमा अगाडि बढ्न चाहनुहुन्छ?", "currency": "मुद्रा", - "currency_source": "मूल्य प्राप्त हुने ठाउँ", "currency_fetch_error": "चयन गरिएको मुद्राको लागि दर प्राप्त गर्दा एर्रोर भयो।", "default_info": "पूर्वनिर्धारित जानकारी", "default_wallets": "सबै वालेटहरू हेर्नुहोस्", @@ -237,22 +185,12 @@ "electrum_offline_mode": "अफलाइन मोड", "electrum_saved": "तपाईंको परिवर्तनहरू सफलतापूर्वक सुरक्षित गरिएको छ। परिवर्तनहरू प्रभाव पार्नको लागि बुलुवालेट पुन: सुरु गर्न आवश्यक हुन सक्छ।", "electrum_status": "स्थिति", - "electrum_clear_alert_title": "स्पष्ट इतिहास?", - "electrum_clear_alert_message": "के तपाई इलेक्ट्रम सेभर्स इतिहास खाली गर्न चाहनुहुन्छ?", - "electrum_clear_alert_cancel": "रद्द गर्नुहोस्", - "electrum_clear_alert_ok": "ठिक छ", - "electrum_select": "छान्नुहोस्", + "electrum_unable_to_connect": "जडान गर्न असमर्थ {server}.", "electrum_reset": "चूकेमा रिसेट गर्नुहोस्", - "electrum_unable_to_connect": "जडान गर्न असमर्थ {सर्भर}.", - "electrum_history": "सर्भर इतिहास", - "electrum_reset_to_default": "के तपाइँ तपाइँको इलेक्ट्रम सेटिङहरू पूर्वनिर्धारितमा रिसेट गर्न निश्चित हुनुहुन्छ?", - "electrum_clear": "सफा", "encrypt_decrypt": "डिक्रिप्ट स्टोरेज", "encrypt_title": "सुरक्षा", "encrypt_tstorage": "स्टोरेज", "encrypt_use": "प्रयोग गर्नुहोस् {type}", - "encrypt_use_expl": "{type} लेनदेन, अनलक, निर्यात, वा वालेट मेटाउनु अघि तपाइँको पहिचान पुष्टि गर्न प्रयोग गरिनेछ। इन्क्रिप्टेड स्टोरेज अनलक गर्न {type} प्रयोग गरिने छैन।", - "general_adv_mode": "अग्रिम मोड", "general_continuity": "निरन्तरता", "header": "सेटिङहरू", "language": "भाषा", @@ -262,21 +200,16 @@ "network": "नेटवर्क", "notifications": "सूचनाहरू", "password": "पासवर्ड", - "passwords_do_not_match": "पासवर्ड मिल्दैन।", "plausible_deniability": "व्यावहारिक अस्वीकार्यता", "privacy": "गोपनीयता", "privacy_quickactions": "वालेट सर्टकटहरू", "rate": "दर", - "retype_password": "पासवर्ड पुन: लेख्नुहोस", "save": "सेव", "saved": "बचत गरियो", - "success_transaction_broadcasted": "सफलता! तपाईंको लेनदेन प्रसारण गरिएको छ!", "total_balance": "पूरा रकम" }, "notifications": { - "would_you_like_to_receive_notifications": "तपाईंले आगमन भुक्तानीहरू प्राप्त गर्दा सूचनाहरू प्राप्त गर्न चाहनुहुन्छ?", - "no_and_dont_ask": "होइन, र मलाई फेरि नसोध्नुहोस्", - "ask_me_later": "पछि सोध्नुहोस्" + "would_you_like_to_receive_notifications": "तपाईंले आगमन भुक्तानीहरू प्राप्त गर्दा सूचनाहरू प्राप्त गर्न चाहनुहुन्छ?" }, "transactions": { "cancel_no": "यो लेनदेन बदल्न सकिने योग्य छैन", @@ -289,26 +222,27 @@ "details_balance_hide": "रकम लुकाउनुहोस्", "details_balance_show": "रकम देखाउनुहोस्", "details_copy": "कापी", - "details_copy_amount": "कापी रकम", "details_copy_note": "नोट कापी", "date": "मिति", "details_received": "प्राप्त भयो", "details_title": "लेनदेन", "pending": "पेनदिंग", "list_title": "लेनदेन", + "transaction": "लेनदेन", "status_cancel": "लेनदेन क्यान्सिल " }, "wallets": { "add_bitcoin": "बिटकोइन", "add_bitcoin_explain": "सजिलो र शक्तिशाली बिटकोइन वालेट", "add_create": "सिर्जना गर्नुहोस्", + "total_balance": "पूरा रकम", + "add_entropy": "एन्ट्रोपी", "add_import_wallet": "वालेट आयात गर्नुहोस्", "add_lightning": "लाइटनिङ", "add_placeholder": "मेरो पहिलो वालेट", "add_title": "वालेट थप्नुहोस्", "add_wallet_name": "नाम", "add_wallet_type": "टाइप गर्नुहोस्", - "balance": "ब्यालेन्स", "clipboard_bitcoin": "तपाईको क्लिपबोर्डमा बिटकोइन ठेगाना छ। के तपाइँ यसलाई लेनदेनको लागि प्रयोग गर्न चाहनुहुन्छ?", "clipboard_lightning": "तपाईंको क्लिपबोर्डमा लाइटनिङ इनभ्वाइस छ। के तपाइँ यसलाई लेनदेनको लागि प्रयोग गर्न चाहनुहुन्छ?", "details_address": "ठेगाना", @@ -316,37 +250,29 @@ "details_connected_to": "जोडिएको", "details_delete": "हटाउनुहोस्", "details_delete_wallet": "वालेट हटाउनुहोस्", - "details_display": "वालेट सूचीमा देखाउनु होस्", "details_master_fingerprint": "मास्टर फिंगरप्रिन्ट", - "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": "हो, मेटाउनुहोस्", "export_title": "वालेट निर्यात", "import_discovery_title": "आविष्कार", "import_discovery_subtitle": "पत्ता लागेको वालेट छान्नुहोस्", "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_create_a_wallet_text": "यो नि: शुल्क छ र तपाईं सिर्जना गर्न सक्नुहुन्छ तपाईलाई मन पर्ने जति।", "list_empty_txs1": "तपाईंको लेनदेन यहाँ देखा पर्नेछ।", "list_empty_txs1_lightning": "तपाईंको दैनिक लेनदेनको लागि लाइटनिङ वालेट प्रयोग गर्नुपर्छ। शुल्कहरू अनुचित रूपमा सस्तो र छिटो छ।", "list_empty_txs2": "आफ्नो वालेटबाट सुरु गर्नुहोस्।", "list_latest_transaction": "पछिल्लो लेनदेन", + "paste_from_clipboard": "पेस्त", "list_title": "वालेटहरू", "list_tryagain": "पुन: प्रयास गर्नुहोस्", "no_ln_wallet_error": "लाइटनिङ इनभ्वाइस भुक्तान गर्नु अघि, तपाईंले पहिले लाइटनिङ वालेट थप्नुपर्छ।", "looks_like_bip38": "यो पासवर्ड सुरक्षित निजी कुञ्जी जस्तो देखिन्छ (BIP38)।", - "reorder_title": "वालेटहरू पुन: अर्डर गर्नुहोस्", "please_continue_scanning": "कृपया स्क्यान जारी राख्नुहोस्।", "select_no_bitcoin": "हाल कुनै बिटकोइन वालेटहरू उपलब्ध छैनन्।", "select_no_bitcoin_exp": "लाइटनिङ वालेटहरू पुन: भर्नको लागि बिटकोइन वालेट आवश्यक छ। कृपया बिटकोइन वालेट सिर्जना वा आयात गर्नुहोस्।", @@ -354,11 +280,13 @@ "pull_to_refresh": "नवीकरण गर्न तान्नुहोस्", "add_ln_wallet_first": "तपाईंले पहिले लाइटनिङ वालेट थप्नुपर्छ।" }, + "total_balance_view": { + "title": "पूरा रकम" + }, "multisig": { - "fee_btc": "{संख्या} BTC", + "fee_btc": "{number} BTC", "confirm": "पक्का ", "header": "पठाउनुहोस्", - "share": "सेयर गर्नुहोस्", "view": "हेर्नुहोस्", "how_many_signatures_can_bluewallet_make": "ब्लू वालेटले कति हस्ताक्षर गर्न सक्छ", "signatures_required_to_spend": "हस्ताक्षर आवश्यक छ {number}", @@ -384,20 +312,19 @@ "title": "यो मेरो ठेगाना हो?", "enter_address": " ठेगाना लेख्नुहोस्", "check_address": "ठेगाना जाँच गर्नुहोस्", - "no_wallet_owns_address": "उपलब्ध वालेटहरू मध्ये कुनै पनि प्रदान गरिएको ठेगानाको स्वामित्व छैन।", - "view_qrcode": "QRCode हेर्नुहोस्" + "no_wallet_owns_address": "उपलब्ध वालेटहरू मध्ये कुनै पनि प्रदान गरिएको ठेगानाको स्वामित्व छैन।" }, "cc": { "change": "परिवर्तन", "coins_selected": "कोइन छानिएको ({number})", "selected_summ": "{value} छानिएको", - "empty": "यो वालेटमा अहिले कुनै पनि कोइनहरू छैन।", "freeze": "फ्रिज", "freezeLabel": "फ्रिज", "freezeLabel_un": "अनफ्रिज गर्नुहोस्", "header": "सिक्का नियन्त्रण", "use_coin": "कोइन प्रयोग गर्नुहोस्", - "use_coins": "कोइनहरू प्रयोग गर्नुहोस्" + "use_coins": "कोइनहरू प्रयोग गर्नुहोस्", + "sort_status": "स्थिति" }, "addresses": { "sign_title": "हस्ताक्षर/प्रमाणित सन्देश", @@ -433,8 +360,6 @@ }, "bip47": { "payment_code": "भुक्तानी कोड", - "payment_codes_list": "भुक्तानी कोड सूची", - "who_can_pay_me": "कसले मलाई तिर्न सक्छ:", "not_found": "भुक्तानी कोड फेला परेन" } } diff --git a/loc/nl_nl.json b/loc/nl_nl.json index a49dd352de8..146b94fc4b9 100644 --- a/loc/nl_nl.json +++ b/loc/nl_nl.json @@ -4,24 +4,22 @@ "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": "Verandere invoer valuta", + "refresh": "Vernieuw", + "enter_amount": "Voer bedrag in", + "qr_custom_input_button": "Tik 10 keer om aangepaste invoer in te geven" }, "azteco": { "codeIs": "De code van uw tegoedbon is", @@ -30,11 +28,11 @@ "redeem": "Inwisselen naar wallet.", "redeemButton": "Inwisselen", "success": "Succes", - "title": "Los uw Atze.co voucher in" + "title": "Verzilver uw Atze.co voucher" }, "entropy": { "save": "Opslaan", - "title": "Entropy", + "title": "Entropie", "undo": "Ongedaan maken" }, "errors": { @@ -43,75 +41,49 @@ "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" }, "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.", "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" }, "receive": { "details_create": "Maken", "details_label": "Omschrijving", "details_setAmount": "Ontvang met bedrag", - "details_share": "delen", "header": "Ontvang" }, "send": { @@ -177,8 +149,6 @@ "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_title": "BlueWallet opslag toegang toestemming", "psbt_clipboard": "Gekopieerd naar Plakbord", @@ -188,11 +158,9 @@ "outdated_rate": "De Rate is voor het laatst geüpdate 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" }, "settings": { @@ -207,14 +175,10 @@ "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", @@ -224,7 +188,6 @@ "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", @@ -233,32 +196,16 @@ "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", @@ -266,11 +213,8 @@ "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", "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,33 +222,25 @@ "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" }, "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" + "would_you_like_to_receive_notifications": "Wil je meldingen ontvangen als je binnenkomende betalingen ontvangt?" }, "transactions": { "cancel_no": "Deze transactie is niet vervangbaar", @@ -318,9 +254,7 @@ "cpfp_title": "Bumb 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", @@ -328,8 +262,6 @@ "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,6 +273,7 @@ "eta_3h": "ETA: In ~3 uur", "eta_1d": "ETA: In ~1 dag", "list_title": "Transacties", + "transaction": "Transacties", "rbf_title": "Bumb fee (RBF)", "status_bump": "Bumb fee", "status_cancel": "Annuleer transactie", @@ -352,43 +285,38 @@ "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Eenvoudige en krachtige Bitcoin-wallet", "add_create": "Aanmaken", + "total_balance": "Totaalbalans", + "add_entropy": "Entropy", "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_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", + "wallets": "Wallets", "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", @@ -407,43 +335,37 @@ "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" }, + "total_balance_view": { + "title": "Totaalbalans" + }, "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", @@ -453,7 +375,6 @@ "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", @@ -480,20 +401,14 @@ "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)", @@ -518,20 +433,19 @@ "owns": "{label} eigen {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" + "no_wallet_owns_address": "Geen van de beschikbare wallets bezit het opgegeven adres." }, "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.", + "sort_status": "Status" }, "units": { "BTC": "BTC", diff --git a/loc/pcm.json b/loc/pcm.json new file mode 100644 index 00000000000..bbc234ea00c --- /dev/null +++ b/loc/pcm.json @@ -0,0 +1,58 @@ +{ + "_": { + "bad_password": "Password wey you enter no correct, run am again", + "cancel": "Cancel", + "never": "lai lai", + "success": "You correct", + "close": "Close am" + }, + "azteco": { + "success": "You correct" + }, + "lnd": { + "refill_create": "To proceed like this so, abeg create Bitcoin wallet wey you go refill am with" + }, + "lndViewInvoice": { + "please_pay_between_and": "Abeg pay between {min} and {max}", + "please_pay": "Abeg pay", + "wasnt_paid_and_expired": "You nor quick pay this invoice and e don kalas" + }, + "plausibledeniability": { + "password_should_not_match": "You dey use this password, run another password wey you never use." + }, + "receive": { + "maxSats": "Maximum amount na {max} sats", + "minSatsFull": "Minimal amount na {min} sats or {currency}" + }, + "send": { + "broadcastPending": "Pend", + "broadcastSuccess": "You correct", + "confirm_header": "Confam", + "confirm_sendNow": "Push am", + "create_copy": "Copy make you broadcast am later", + "details_total_exceeds_balance": "The amount you dey try send pass wetin you get for balance", + "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", + "input_clear": "wipe am", + "psbt_this_is_psbt": "Na Partially Signed Bitcoin Transaction be this. Abeg run am finish with your hardware wallet wey dey offline. ", + "reset_amount_confirm": "You go like reset the bar?" + }, + "settings": { + "about_sm_github": "GitHub", + "about_sm_telegram": "Telegram channel", + "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.", + "currency_fetch_error": "Error dey as we dey find rate for the currency you select. ", + "default_info": "Normal info", + "default_title": "As you launch", + "default_wallets": "See all your wallets", + "electrum_unable_to_connect": "We nor fit connect to [server]" + }, + "transactions": { + "pending": "Pend" + }, + "multisig": { + "confirm": "Confam" + } +} diff --git a/loc/pl.json b/loc/pl.json index a33a0e25b31..74942e9fe64 100644 --- a/loc/pl.json +++ b/loc/pl.json @@ -4,35 +4,34 @@ "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", + "port": "Port", + "ssl_port": "Port SSL", + "suggested": "Sugerowane" }, "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", @@ -43,7 +42,8 @@ "entropy": { "save": "Zapisz", "title": "Entropia", - "undo": "Cofnij" + "undo": "Cofnij", + "amountOfEntropy": "{bits} z {limit} bitów" }, "errors": { "broadcast": "Rozgłoszenie nie powiodło się", @@ -51,80 +51,63 @@ "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ć wezwania z tego samego portfela, który stworzył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", + "reset": "Reset", "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ą." }, "send": { "provided_address_is_invoice": "Ten adres wygląda na fakturę Lightning. Przejdź do swojego portfela Lightning aby ją opłacić.", @@ -137,34 +120,42 @@ "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 szestnastkowej, 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", + "counterparty_label_placeholder": "Edytuj nazwę kontaktu", "details_scan": "Skanuj", "details_scan_hint": "Kliknij dwukrotnie, aby zeskanować lub zaimportować miejsce docelowe", "details_total_exceeds_balance": "Wysłanie tej ilości przekracza dostępne saldo.", @@ -180,10 +171,11 @@ "fee_1d": "1 dzień", "fee_3h": "3 godz.", "fee_custom": "Niestandardowe", + "insert_custom_fee": "Wprowadź opłatę", "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_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": "Wolne", "header": "Wyślij", "input_clear": "Wyczyść", @@ -192,23 +184,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,20 +217,21 @@ "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 pobrazny 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", @@ -243,41 +239,43 @@ "default_wallets": "Wyświetl wszystkie portfele", "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ę.", "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", + "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ć schowek? To pozwoli na dostęp do twoich portfeli bez hasła.", - "encrypt_enc_and_pass": "Szyfrowany i 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.", + "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 +283,36 @@ "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.", "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,13 +320,16 @@ }, "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ę", @@ -338,9 +339,7 @@ "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", @@ -349,9 +348,14 @@ "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}", @@ -363,6 +367,7 @@ "eta_1d": "Szacowany czas: ~1 dzień", "view_wallet": "Pokaż {walletLabel}", "list_title": "Transakcje", + "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óbój 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)", @@ -370,118 +375,155 @@ "status_cancel": "Anuluj transakcję", "transactions_count": "Ilość transakcji", "txid": "ID Transakcji", - "updating": "Aktualizuję..." + "from": "Od: {counterparty}", + "to": "Do: {counterparty}", + "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." }, "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" + }, + "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 +549,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ępniaine 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.", + "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 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.", + "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)", @@ -548,26 +591,40 @@ "no_wallet_owns_address": "Żaden z dostępnych portfeli nie posiada podanego adresu.", "view_qrcode": "Pokaż kod QR" }, + "autofill_word": { + "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: klucz 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 +658,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..e3b2cdf5086 100644 --- a/loc/pt_br.json +++ b/loc/pt_br.json @@ -4,32 +4,31 @@ "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á-los 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" }, "azteco": { "codeIs": "Seu código voucher é", @@ -43,7 +42,8 @@ "entropy": { "save": "Salvar", "title": "Entropia", - "undo": "Desfazer" + "undo": "Desfazer", + "amountOfEntropy": "{bits} de {limit} bits" }, "errors": { "broadcast": "Falha na transmissão.", @@ -51,80 +51,62 @@ "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" + "sameWalletAsInvoiceError": "Você não pode pagar um boleto com a mesma carteira que o criou.", + "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": "Imagem prévia", "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.", "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", + "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á-la 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." }, "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.", @@ -146,8 +128,12 @@ "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,10 +147,11 @@ "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", + "counterparty_label_placeholder": "Editar o nome de contato", "details_scan": "Ler", "details_scan_hint": "Toque duas vezes para escanear ou importar um destino", "details_total_exceeds_balance": "A quantia enviada excede o saldo disponível.", @@ -193,8 +180,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,11 +189,15 @@ "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.", + "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" }, "settings": { @@ -222,20 +211,19 @@ "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_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á descritptografado. 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", @@ -243,7 +231,7 @@ "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 possível conectar com servidor Electrum fornecido", "lndhub_uri": "Por ex. {example}", "electrum_host": "Por ex. {example}", "electrum_offline_mode": "Modo offline", @@ -252,32 +240,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,36 +274,35 @@ "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", @@ -322,13 +310,16 @@ }, "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", @@ -338,9 +329,7 @@ "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", @@ -349,9 +338,14 @@ "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}", @@ -363,6 +357,7 @@ "eta_1d": "Tempo estimado de chegada: Em ~1 dia", "view_wallet": "Ver {walletLabel}", "list_title": "Transações", + "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 +365,21 @@ "status_cancel": "Cancelar Transação", "transactions_count": "Contagem das Transações", "txid": "ID da transação", - "updating": "Atualizando..." + "from": "De:{counterparty}", + "to": "Para:{counterparty}", + "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; eles apenas permitem que você visualize o saldo." }, "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 +393,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_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", @@ -429,59 +434,85 @@ "import_success_watchonly": "A carteira foi importada.\nAVISO: 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 \nquantos 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" + }, + "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 pricinpal." }, "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á a 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 +526,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 +538,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 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á-la .", + "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)", @@ -546,20 +578,32 @@ "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." }, "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 +612,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 +647,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..d64f231b151 100644 --- a/loc/pt_pt.json +++ b/loc/pt_pt.json @@ -1,23 +1,25 @@ { "_": { - "bad_password": "Pasword errada. Por favor, tente novamente.", + "bad_password": "Palavra-passe errada. Por favor, tente novamente.", "cancel": "Cancelar", "continue": "Continuar", + "discard_changes": "Descartar alterações?", "enter_password": "Inserir password", "never": "nunca", "of": "6\n{number} de {total}", "ok": "OK", - "storage_is_encrypted": "O armazenamento está encriptado. Uma password é necessária para desencriptar", + "storage_is_encrypted": "O armazenamento está encriptado. Uma password é necessária para desencriptar.", "yes": "Sim", "no": "Não", - "save": "Salvar", "seed": "Seed", "success": "Sucesso", - "wallet_key": "Chave da carteira" + "wallet_key": "Chave da carteira", + "close": "Fechar", + "enter_amount": "Insira montante" }, "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", @@ -35,51 +37,46 @@ "network": "Erro da rede" }, "lnd": { - "errorInvoiceExpired": "Factura expirada", "expired": "Expirado", + "expiresIn": "Expira em {time} minutos", "payButton": "Paga", - "placeholder": "Factura", + "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" + "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", "sats": "sats", "wasnt_paid_and_expired": "Esta factura não foi paga e expirou" }, "plausibledeniability": { - "create_fake_storage": "Criar armazenamento encriptado FALSO", - "create_password": "Criar password", + "create_fake_storage": "Criar armazenamento encriptado", "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", "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." + "ok_lnd": "Ok, eu guardei.", + "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 wallet foi criada..." }, "receive": { "details_create": "Criar", "details_label": "Descrição", "details_setAmount": "Quantia a receber", - "details_share": "partilhar", - "header": "receber" + "header": "Receber" }, "send": { "broadcastButton": "Transmitir", @@ -134,12 +131,9 @@ "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 usar a sua câmera", "psbt_sign": "Assinar transação", "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_title": "Permissão de acesso ao armazenamento", "psbt_clipboard": "Copiar para área de transferência", @@ -150,7 +144,6 @@ "psbt_tx_open": "Abrir transacção assinada", "psbt_tx_scan": "Scan transacção assinada", "success_done": "Feito", - "txSaved": "O ficheiro de transacção ({filePath}) foi guardado na pasta Downloads.", "problem_with_psbt": "Problema com PSBT" }, "settings": { @@ -163,13 +156,9 @@ "about_review": "Deixa-nos uma review", "about_selftest": "Run self test", "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?", "currency": "Moeda", "default_desc": "Quando desactivado, a BlueWallet abrirá imediatamente a carteira seleccionada no lançamento.", @@ -178,151 +167,165 @@ "default_wallets": "Ver todas as carteiras", "electrum_connected": "Conectado", "electrum_connected_not": "Desconectado", - "electrum_error_connect": "Não é possível conectar ao servidor Electrum fornecido", + "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.", + "set_electrum_server_as_default": "Definir {server} como servidor Electrum predefinido?", "electrum_settings_server": "Electrum server", "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_unable_to_connect": "Não é possível ligar a {server}.", "electrum_reset": "Resetar para o padrão", - "electrum_history": "Histórico do servidor", - "electrum_clear": "Limpar", "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_title": "Segurança", "encrypt_tstorage": "Armazenamento", "encrypt_use": "Usar {type}", "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", "language": "Idioma", + "last_updated": "Última atualização", + "language_isRTL": "É necessário reiniciar a BlueWallet para que a alteração de idioma tenha efeito.", "lightning_saved": "As alterações foram guardadas com sucesso", - "lightning_settings": "Definições do Lightning", + "lightning_settings": "Definições de Lightning", "network": "Rede", - "network_broadcast": "Transmitir transacção", + "network_broadcast": "Transmitir transação", "network_electrum": "Electrum server", + "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...", "privacy": "Privacidade", "privacy_system_settings": "Configurações do Sistema", "privacy_quickactions": "Atalhos da Carteira", - "push_notifications": "Notificações via push", - "retype_password": "Inserir password novamente", "save": "Guardar", "saved": "Guardado", "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": "Você gostaria de receber notificações quando você receber pagamentos?" }, "transactions": { "cancel_no": "Esta transacção não é substituível", "cancel_title": "Cancelar esta transacção (RBF)", "confirmations_lowercase": "{confirmations} confirmações", + "copy_link": "Copiar ligação", "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_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)", "details_balance_hide": "Esconder Saldo", "details_balance_show": "Mostrar Saldo", - "details_block": "Block Height", "details_copy": "Copiar", + "details_copy_note": "Copiar nota", + "details_copy_txid": "Copiar ID da transação", "details_from": "De", "details_inputs": "Inputs", "details_outputs": "Outputs", + "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_to": "Para", + "enable_offline_signing": "Esta carteira não está a ser utilizada em conjunto com uma assinatura offline. Deseja activá-la agora?", "pending": "Pendente", - "list_title": "transacções", - "rbf_title": "Aumento de taxa (RBF)", + "view_wallet": "Ver {walletLabel}", + "list_title": "Transações", + "transaction": "detalhes", + "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 RBF – Replace by Fee (substituição por taxa).", + "rbf_title": "Aumentar taxa (RBF)", "status_bump": "Aumento de taxa", "status_cancel": "Cancelar transacção", - "transactions_count": "contagem de transações", + "transactions_count": "Número de transações", + "txid": "ID da transação", "updating": "A atualizar..." }, "wallets": { "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Carteira Bitcoin simples e poderosa", "add_create": "Adicionar", + "total_balance": "Saldo Total", + "add_entropy": "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_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_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", + "clipboard_bitcoin": "Tem um endereço Bitcoin copiado. Gostaria de o utilizar para uma transação?", + "clipboard_lightning": "Tem uma fatura de Lightning copiada. Gostaria de a utilizar para uma transação?", "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_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_derivation_path": "caminho de derivação", "details_export_backup": "Exportar / backup", + "details_export_history": "Exportar histórico para CSV", "details_master_fingerprint": "Master fingerprint", - "details_no_cancel": "Não, cancelar", - "details_save": "Guardar", + "details_multisig_type": "multisig", "details_show_xpub": "Mostrar XPUB da wallet", - "details_title": "wallet", + "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_yes_delete": "Sim, eliminar", + "enter_bip38_password": "Introduzir a palavra-passe para desencriptar", "export_title": "Exportar Wallet", "import_do_import": "Importar", "import_error": "Falhou. É um dado válido?", + "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_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", "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_txs1": "As suas transaçõ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 baixíssimas e a velocidade é extremamente rápida.", "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_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_tryagain": "Tente novamente", - "reorder_title": "Reordenar Wallets", "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" }, + "total_balance_view": { + "title": "Saldo Total" + }, "multisig": { "confirm": "Confirmar", "header": "Enviar", - "share": "partilhar", "create": "Criar", "co_sign_transaction": "Assinar transação", "ms_help_title5": "Ligar modo avançado" }, + "cc": { + "sort_label": "Nome", + "sort_status": "Estado" + }, "units": { "sats": "sats" }, diff --git a/loc/ro.json b/loc/ro.json index f76a9769bac..0c3d9db0995 100644 --- a/loc/ro.json +++ b/loc/ro.json @@ -4,24 +4,17 @@ "cancel": "Anulează", "continue": "Continuă", "clipboard": "Clipboard", + "discard_changes": "Renunți la modificări?", "enter_password": "Introdu parola", "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", @@ -43,63 +36,36 @@ "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?", "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}", "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" + "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", "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.", "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", + "ok": "OK, o scriu pe foaie.", + "ok_lnd": "Ok, am salvat,", "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. ", "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 +74,6 @@ "details_create": "Creează", "details_label": "Descriere", "details_setAmount": "Primește sumă determinată", - "details_share": "Distribuie", "header": "Primește" }, "send": { @@ -146,7 +111,6 @@ "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ă.", @@ -172,11 +136,8 @@ "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", @@ -188,7 +149,6 @@ "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" }, "settings": { @@ -202,14 +162,10 @@ "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.", @@ -218,7 +174,6 @@ "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,32 +181,16 @@ "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_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.", "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.", @@ -259,10 +198,8 @@ "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,32 +207,24 @@ "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" }, "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?" }, "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. ", @@ -310,17 +239,13 @@ "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", "enable_offline_signing": "Acest portofel nu este folosit în legătură cu o semnare offline. Ai vrea să activezi asta acum?", @@ -331,6 +256,7 @@ "eta_1d": "Estimat: în ~1 zi", "view_wallet": "Vezi {walletLabel}", "list_title": "Tranzacții", + "transaction": "Tranzacție", "rbf_title": "Crește comisionul (RBF)", "status_bump": "Crește comisionul", "status_cancel": "Anulează tranzacția", @@ -342,41 +268,37 @@ "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 +313,37 @@ "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", + "paste_from_clipboard": "Lipește", + "import_file": "Importă fișier", "list_long_scan": "Scaneză 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.", "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.", "identity_pubkey": "Pubkey identitate", "xpub_title": "XPUB-ul portofelului" }, + "total_balance_view": { + "title": "Balanță totală" + }, "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,7 +353,6 @@ "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", @@ -463,19 +379,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)", @@ -498,21 +409,20 @@ "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" + "no_wallet_owns_address": "Niciunul dintre portofelele disponibile nu deține adresa furnizată." }, "cc": { "change": "Schimb", "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. ", + "sort_status": "Status" }, "units": { "BTC": "BTC", diff --git a/loc/ru.json b/loc/ru.json index 71ce11b64b9..12518219cf2 100644 --- a/loc/ru.json +++ b/loc/ru.json @@ -4,173 +4,166 @@ "cancel": "Отмена", "continue": "Продолжить", "clipboard": "Буфер обмена", + "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": "Открыть канал с этой нодой:", "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": "Заметка", + "counterparty_label_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,513 @@ "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 — бесплатный проект с открытым исходным кодом. Создано Биткойн пользователями.", "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": "Расширенные настройки", + "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": "При старте", + "currency_source": "Курс получен от", + "currency_fetch_error": "Ошибка получения курса.", + "default_desc": "Если выключено, при запуске сразу откроется выбранный кошелёк.", + "default_info": "Информация по умолчанию", + "default_title": "При запуске", "default_wallets": "Показать все кошельки", "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_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_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} не будет использоваться для открытия зашифрованного хранилища.", + "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. Новые кошельки будут использовать его.", "network": "Сеть", "network_broadcast": "Отправить транзакцию", "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": "Включая уведомления, вы отправляете токен устройства и новые адреса/txid на сервер — только после включения.", "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_from": "Из", "details_inputs": "Входы", "details_outputs": "Выходы", "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_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 день", + "view_wallet": "Перейти к {walletLabel}", + "list_title": "Транзакции", + "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", + "from": "От: {counterparty}", + "to": "Кому: {counterparty}", + "updating": "Обновление...", + "watchOnlyWarningTitle": "Предупреждение безопасности", + "watchOnlyWarningDescription": "Кошельки «только для чтения» не позволяют тратить средства. Будьте осторожны.", + "custom_fee_warning_title": "Внимание", + "custom_fee_warning_description": "Комиссии ниже 1 сат/вБайт действительны, но могут не ретранслироваться из-за политик узлов." }, "wallets": { "add_bitcoin": "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": "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": "Баланс", + "add_wallet_seed_length": "Длина сид-фразы", + "add_wallet_seed_length_12": "12 слов", + "add_wallet_seed_length_24": "24 слова", "clipboard_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": "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": "Перед оплатой Лайтнинг инвойса добавьте Лайтнинг-кошелёк.", + "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": "Скопировано", + "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": "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": "Показывать в 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": "Введите fingerprint", + "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": { + "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_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": "Хотите произвести аутентификацию на", + "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}.", + "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..4654311aa71 100644 --- a/loc/si_LK.json +++ b/loc/si_LK.json @@ -4,24 +4,17 @@ "cancel": "අවලංගු කරන්න", "continue": "ඉදිරියට යන්න", "clipboard": "පසුරු පුවරුව", + "discard_changes": "වෙනස් කිරීම් ඉවත් කරන්නද?", "enter_password": "මුරපදය ඇතුළත් කරන්න", "never": "නැත", - "disabled": "අක්‍රීය", "of": "{number} of {total}", "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": "ඔබේ වවුචර් කේතය", @@ -43,75 +36,42 @@ "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": "ලයිට්නින් පසුම්බියේ ශේෂය නැවත පුරවන්න", - "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": "සැට්.", "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": "හරි, මම ඒක සුරැකුවා", + "ok": "හරි, මම එය ලිව්වා.", "text": "කරුණාකර මොහොතකට මෙම සිහිවටන වැකිය කඩදාසි කැබැල්ලක සටහන් කර ගන්න. එය ඔබේ උපස්ථය වන අතර පසුම්බිය ආපසු ලබා ගැනීමට ඔබට එය භාවිතා කළ හැකිය.", - "text_lnd": "කරුණාකර මෙම පසුම්බි උපස්ථය සුරකින්න. නැති වූ පසු මුදල් පසුම්බිය නැවත ලබා ගැනීමට එය ඔබට ඉඩ සලසයි.", - "title": "ඔබේ මුදල් පසුම්බිය සාදා ඇත" + "text_lnd": "කරුණාකර මෙම පසුම්බි උපස්ථය සුරකින්න. නැති වූ පසු මුදල් පසුම්බිය නැවත ලබා ගැනීමට එය ඔබට ඉඩ සලසයි." }, "receive": { "details_create": "නිර්මාණය කරන්න", "details_label": "විස්තර", "details_setAmount": "මුදල සමග ලබා ගන්න", - "details_share": "බෙදා ගන්න", "header": "ලබා ගන්න", "maxSats": "උපරිම මුදල සැට් {max} වේ", "maxSatsFull": "උපරිම මුදල {max} sats හෝ {currency} වේ", @@ -152,9 +112,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": "ගමනාන්තයක් පරිලෝකනය කිරීමට හෝ ආනයනය කිරීමට දෙවරක් තට්ටු කරන්න", @@ -181,11 +139,8 @@ "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,11 +150,9 @@ "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 සමඟ ගැටළුව" }, "settings": { @@ -214,14 +167,10 @@ "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": "තෝරාගත් මුදල් සඳහා ගාස්තුව ලබා ගැනීමේදී දෝෂයක් ඇති විය.", @@ -231,7 +180,6 @@ "default_wallets": "සියලුම පසුම්බි බලන්න", "electrum_connected": "සම්බන්ධයි", "electrum_connected_not": "සම්බන්ධ නොවේ", - "electrum_error_connect": "සපයා ඇති ඉලෙක්ට්‍රම් සේවාදායකයට සම්බන්ධ විය නොහැක", "lndhub_uri": "උ.දා., {example}", "electrum_host": "උ.දා., {example}", "electrum_offline_mode": "නොබැඳි මාදිලිය", @@ -240,32 +188,16 @@ "use_ssl": "SSL භාවිතා කරන්න", "electrum_saved": "ඔබේ වෙනස්කම් සාර්ථකව සුරැකී ඇත. වෙනස්කම් බලාත්මක වීමට බ්ලූවොලට් නැවත ආරම්භ කිරීම අවශ්‍ය විය හැකිය.", "set_electrum_server_as_default": "පෙරනිමි ඉලෙක්ට්‍රෝම් සේවාදායකය ලෙස {server} සකසන්නද?", - "set_lndhub_as_default": "පෙරනිමි එල්.එන්.ඩී.හබ් සේවාදායකය ලෙස {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": "හරි", - "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 +205,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,33 +214,25 @@ "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": "මෙවලම්" }, "notifications": { - "would_you_like_to_receive_notifications": "ඔබට ගෙවීම් ලැබෙන විට දැනුම්දීම් ලැබීමට ඔබ කැමතිද?", - "no_and_dont_ask": "නැත, නැවත මගෙන් අහන්න එපා", - "ask_me_later": "පසුව මගෙන් අසන්න" + "would_you_like_to_receive_notifications": "ඔබට ගෙවීම් ලැබෙන විට දැනුම්දීම් ලැබීමට ඔබ කැමතිද?" }, "transactions": { "cancel_explain": "අපි මෙම ගනුදෙනුව ඔබට ගෙවන සහ ඉහළ ගාස්තු සහිත එකක් සමඟ ප්‍රතිස්ථාපනය කරන්නෙමු. මෙය වත්මන් ගනුදෙනුව ඵලදායී ලෙස අවලංගු කරයි. මෙය RBF ලෙස හැඳින්වේ - ගාස්තුවෙන් ප්‍රතිස්ථාපනය කරන්න.", @@ -326,9 +247,7 @@ "cpfp_title": "බම්ප් ගාස්තුව (CPFP)", "details_balance_hide": "Hide Balance", "details_balance_show": "ශේෂය පෙන්වන්න", - "details_block": "උස අවහිර කරන්න", "details_copy": "පිටපත් කරන්න", - "details_copy_amount": "ප්‍රමාණය පිටපත් කරන්න", "details_copy_block_explorer_link": "බ්ලොක් එක්ස්ප්ලෝරර් සම්බන්ධකය පිටපත් කරන්න", "details_copy_note": "සටහන පිටපත් කරන්න", "details_copy_txid": "ගනුදෙනු හැඳුනුම්පත පිටපත් කරන්න", @@ -336,8 +255,6 @@ "details_inputs": "යෙදවුම්", "details_outputs": "ප්‍රතිදාන", "details_received": "ලැබුණි", - "transaction_note_saved": "ගනුදෙනු සටහන සාර්ථකව සුරකින ලදි.", - "details_show_in_block_explorer": "බ්ලොක් එක්ස්ප්ලෝරර් හි බලන්න", "details_title": "ගනුදෙනුව", "details_to": "ප්‍රතිදානය", "enable_offline_signing": "මෙම පසුම්බිය නොබැඳි අත්සන් කිරීම සමඟ එක්ව භාවිතා නොකෙරේ. ඔබ දැන් එය සක්‍රීය කිරීමට කැමතිද?", @@ -350,6 +267,7 @@ "eta_1d": "පැමිණීමට ඇස්තමේන්තු ගත කාලය: දවස් 01 කින්", "view_wallet": "{walletLabel} බලන්න", "list_title": "ගනුදෙනු", + "transaction": "ගනුදෙනුව", "open_url_error": "පෙරනිමි බ්‍රවුසරය සමඟ සබැඳිය විවෘත කළ නොහැක. කරුණාකර ඔබගේ පෙරනිමි බ්‍රවුසරය වෙනස් කර නැවත උත්සාහ කරන්න.", "rbf_title": "බම්ප් ගාස්තුව (RBF)", "status_bump": "බම්ප් ගාස්තුව", @@ -362,43 +280,38 @@ "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_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": "ශේෂය", "clipboard_bitcoin": "ඔබේ පසුරු පුවරුවේ බිට්කොයින් ලිපිනයක් තිබේ. ඔබ එය ගනුදෙනුවක් සඳහා භාවිතා කිරීමට කැමතිද?", "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_addresses": "ලිපිනයන් පෙන්වන්න", "details_title": "පසුම්බිය", + "wallets": "පසුම්බි", "details_type": "ටයිප් කරන්න", "details_use_with_hardware_wallet": "දෘඩාංග පසුම්බිය සමඟ භාවිතා කරන්න", - "details_wallet_updated": "පසුම්බිය යාවත්කාලීන කරන ලදි", "details_yes_delete": "ඔව්, මකන්න", "enter_bip38_password": "විකේතනය කිරීමට මුරපදය ඇතුළත් කරන්න", "export_title": "පසුම්බි අපනයනය", @@ -417,44 +330,37 @@ "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_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": "ලැයිස්තුව හරහා ඇදගෙන යාමට පසුම්බියක් මත තට්ටු කර අල්ලාගෙන සිටින්න.", "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 පසුම්බිය" }, + "total_balance_view": { + "title": "මුළු ශේෂය" + }, "multisig": { - "multisig_vault": "සුරක්ෂිතාගාරය", + "multisig_vault": "බහු සිග් සුරක්ෂිතාගාරය", "default_label": "බහු සිග් සුරක්ෂිතාගාරය", "multisig_vault_explain": "විශාල ප්‍රමාණයක් සඳහා හොඳම ආරක්‍ෂාව", "provide_signature": "අත්සන ලබා දෙන්න", @@ -464,7 +370,6 @@ "fee_btc": "{number} බී.ටී.සී", "confirm": "තහවුරු කරන්න", "header": "යවන්න", - "share": "බෙදාගන්න", "view": "දැක්ම", "manage_keys": "යතුරු කළමනාකරණය කරන්න", "how_many_signatures_can_bluewallet_make": "බ්ලූවොලට්ට කොපමණ අත්සන් කළ හැකිද?", @@ -491,20 +396,14 @@ "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)", @@ -529,21 +428,20 @@ "owns": "{label} සතු {address}", "enter_address": "ලිපිනය ඇතුළත් කරන්න", "check_address": "ලිපිනය පරීක්‍ෂා කරන්න", - "no_wallet_owns_address": "සපයා ඇති ලිපිනයන් සතුව වලංගු මුදල් පසුම්බි කිසිවක් නැත.", - "view_qrcode": "QR කේතය බලන්න" + "no_wallet_owns_address": "සපයා ඇති ලිපිනයන් සතුව වලංගු මුදල් පසුම්බි කිසිවක් නැත." }, "cc": { "change": "වෙනස් කරන්න", "coins_selected": "තෝරා ගත් කාසි ({number})", "selected_summ": "{value} තෝරා ඇත", - "empty": "මෙම පසුම්බියේ මේ මොහොතේ කාසි නොමැත.", "freeze": "කැටි කරන්න", "freezeLabel": "කැටි කරන්න", "freezeLabel_un": "කැරි නොකරන්න", "header": "කාසි පාලනය", "use_coin": "කාසිය භාවිතා කරන්න", "use_coins": "කාසි භාවිතා කරන්න", - "tip": "වැඩි දියුණු කළ පසුම්බිය කළමනාකරණය සඳහා කාසි බැලීමට, ලේබල් කිරීමට, කැටි කිරීමට හෝ තෝරා ගැනීමට මෙම පහසුකම ඔබට ඉඩ සලසයි. වර්ණ කව මත තට්ටු කිරීමෙන් ඔබට කාසි කිහිපයක් තෝරා ගත හැකිය." + "tip": "වැඩි දියුණු කළ පසුම්බිය කළමනාකරණය සඳහා කාසි බැලීමට, ලේබල් කිරීමට, කැටි කිරීමට හෝ තෝරා ගැනීමට මෙම පහසුකම ඔබට ඉඩ සලසයි. වර්ණ කව මත තට්ටු කිරීමෙන් ඔබට කාසි කිහිපයක් තෝරා ගත හැකිය.", + "sort_status": "තත්ත්වය" }, "units": { "BTC": "බී.ටී.සී", diff --git a/loc/sk_sk.json b/loc/sk_sk.json index 1dbf77a4d78..cbc63957e5e 100644 --- a/loc/sk_sk.json +++ b/loc/sk_sk.json @@ -3,24 +3,17 @@ "bad_password": "Nesprávne heslo! Skúste to znovu.", "cancel": "Zrušiť", "continue": "Pokračovať", + "discard_changes": "Zahodiť zmeny?", "enter_password": "Zadajte heslo", "never": "Nikdy", - "disabled": "Vypnuté", "of": "{number} z {total}", "ok": "OK", "storage_is_encrypted": "Vaše úložisko je zašifrované. Zadajte heslo k odomknutiu.", "yes": "Áno", "no": "Nie", - "save": "Uložiť", "seed": "Semienko", "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" + "wallet_key": "Peňaženkový kľúč" }, "azteco": { "codeIs": "Váš voucher kód je", @@ -36,54 +29,39 @@ "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á", "expired": "Expirovaná", "expiresIn": "Vyprší za {time} minút", "payButton": "Zaplatiť", - "placeholder": "Faktúra", - "open_channel": "Otvoriť kanál", "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:", "please_pay": "Prosím zaplaťte", "sats": "sats" }, "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čí.", "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á!" + "ok_lnd": "OK, uložené.", + "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ť" }, "send": { @@ -125,13 +103,10 @@ "header": "Poslať", "input_done": "Hotovo", "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_tx_export": "Exportovať do súboru", "psbt_tx_open": "Otvoriť podpísanú transakciu", "psbt_tx_scan": "Skenovať podpísanú transakciu", - "success_done": "Hotovo", - "txSaved": "Transakcia bola uložená do {filePath}" + "success_done": "Hotovo" }, "settings": { "about": "O BlueWallet", @@ -140,22 +115,17 @@ "about_review": "Napíšte nám recenziu", "about_sm_github": "GitHub", "about_sm_telegram": "Telegram chat", - "about_sm_twitter": "Sledujte nás na Twitteri", - "advanced_options": "Pokročilé nastavenia", "currency": "Mena", "default_wallets": "Zobraziť všetky peňaženky", "electrum_saved": "Vaše zmeny boli úspešne uložené. Aby sa zmeny prejavili, môže byť potrebný reštart.", "electrum_settings_server": "Electrum server", "electrum_status": "Stav", - "electrum_clear_alert_cancel": "Zrušiť", "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_title": "Bezpečnosť", "encrypt_tstorage": "úložisko", "encrypt_use": "Použiť {type}", "general": "Všeobecné", - "general_adv_mode": "Povoliť rozšírené nastavenia", "header": "Nastavenia", "language": "Jazyk", "lightning_saved": "Vaše zmeny boli úspešne uložené.", @@ -164,18 +134,11 @@ "network_electrum": "Electrum server", "notifications": "Notifikácie", "password": "Heslo", - "password_explain": "Vytvorte si heslo k zašifrovanému úložisku.", - "passwords_do_not_match": "Hesla se nezhodujú", "plausible_deniability": "Plausible deniability...", "privacy_system_settings": "Systémové nastavenia", - "push_notifications": "Push notifikácie", - "retype_password": "Heslo znovu", "save": "Uložiť", "saved": "Uložené" }, - "notifications": { - "ask_me_later": "Požiadať neskôr" - }, "transactions": { "cancel_no": "Táto transakcia sa nedá nahradiť", "cancel_title": "Zrušiť transakciu (RBF)", @@ -183,17 +146,16 @@ "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_copy": "Kopírovať", "details_from": "Vstup", "details_inputs": "Vstupy", "details_outputs": "Výstupy", "details_received": "Prijaté", - "details_show_in_block_explorer": "Ukázať v block exploreri", "details_title": "Transakcia", "details_to": "Výstup", "pending": "Čaká...", "list_title": "transakcie", + "transaction": "Transakcia", "rbf_title": "Navýšiť poplatok za transakciu (RBF)", "status_bump": "Navýšiť poplatok", "status_cancel": "Zrušiť transakciu", @@ -202,12 +164,12 @@ "wallets": { "add_bitcoin": "Bitcoin", "add_create": "Vytvoriť", + "add_entropy": "Entropia", "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_lndhub_placeholder": "adresa uzla", "add_title": "pridať peňaženku", "add_wallet_name": "názov peňaženky", @@ -216,16 +178,13 @@ "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_delete_wallet": "Zmazať peňaženku", - "details_display": "zobraziť v zozname peňaženiek", "details_export_backup": "Exportovať / zálohovať", "details_master_fingerprint": "Hlavný odtlačok prsta", - "details_no_cancel": "Nie, zrušiť", - "details_save": "Uložiť", "details_show_xpub": "Ukázať XPUB", "details_title": "Peňaženka", + "wallets": "peňaženky", "details_type": "Typ", "details_use_with_hardware_wallet": "Použiť s hardwarovou peňaženkou", "details_yes_delete": "Ano, zmazať", @@ -243,11 +202,9 @@ "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", "list_long_scan": "Skenovať QR kód", "list_title": "peňaženky", "list_tryagain": "Skúste znovu", - "reorder_title": "Zoradiť peňaženky", "select_no_bitcoin": "Žiadne Bitcoinové peňaženky nie sú k dispozícii.", "select_wallet": "Vyberte peňaženku", "xpub_copiedToClipboard": "Skopírované do schránky.", @@ -256,7 +213,6 @@ "multisig": { "confirm": "Potvrdiť", "header": "Poslať", - "share": "zdieľať", "view": "Zobraziť", "create": "Vytvoriť", "create_new_key": "Vytvoriť novú", @@ -266,6 +222,10 @@ "is_it_my_address": { "title": "Je to moja adresa?" }, + "cc": { + "sort_label": "Popis", + "sort_status": "Stav" + }, "units": { "sats": "sats" }, diff --git a/loc/sl_SI.json b/loc/sl_SI.json index 350a3033115..9a4eda34548 100644 --- a/loc/sl_SI.json +++ b/loc/sl_SI.json @@ -4,24 +4,17 @@ "cancel": "Prekliči", "continue": "Nadaljuj", "clipboard": "Odložišče", + "discard_changes": "Zavrzi spremembe?", "enter_password": "Vnesite geslo", "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", @@ -43,65 +36,38 @@ "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", "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?", "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", "sats": "sats.", "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", + "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 +76,6 @@ "details_create": "Ustvari", "details_label": "Opis", "details_setAmount": "Prejmi znesek", - "details_share": "deli", "header": "Prejmi", "maxSats": "Največji znesek je {max} sats", "maxSatsFull": "Največji znesek je {max} sats ali {currency}", @@ -152,7 +117,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", @@ -184,8 +148,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,11 +157,9 @@ "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" }, "settings": { @@ -214,17 +174,12 @@ "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", @@ -232,7 +187,6 @@ "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 +195,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 +212,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,33 +221,25 @@ "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" }, "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" + "would_you_like_to_receive_notifications": "Želite prikaz obvestil ob prejemu plačila?" }, "transactions": { "cancel_explain": "To transakcijo bomo nadomestili z novo, ki plača višjo omrežnino. Trenutna transakcija bo preklicana (RBF - Replace By Fee).", @@ -327,9 +254,7 @@ "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", @@ -338,8 +263,6 @@ "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 +274,7 @@ "eta_3h": "ETA: Čez ~3 ure", "eta_1d": "ETA: Čez ~1 dan", "list_title": "Transakcije", + "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)", @@ -364,44 +288,39 @@ "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_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 +339,37 @@ "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.", "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" }, + "total_balance_view": { + "title": "Skupno stanje" + }, "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 +379,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 +405,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.", "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)", @@ -532,21 +437,20 @@ "owns": "{label} ima v lasti {address}", "enter_address": "Vnesite naslov", "check_address": "Preveri naslov", - "no_wallet_owns_address": "Nobena od razpoložljivih denarnic ni lastnik navedenega naslova.", - "view_qrcode": "Prikaži QR kodo" + "no_wallet_owns_address": "Nobena od razpoložljivih denarnic ni lastnik navedenega naslova." }, "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" }, "units": { "BTC": "BTC", diff --git a/loc/sq_AL.json b/loc/sq_AL.json new file mode 100644 index 00000000000..b14e401042c --- /dev/null +++ b/loc/sq_AL.json @@ -0,0 +1,339 @@ +{ + "_": { + "bad_password": "Fjalkalimi është i gabuar. Provojeni përsëri.", + "cancel": "Anullo", + "continue": "Vazhdo", + "clipboard": " Memoria e përkohshme", + "discard_changes": "Anullo ndryshimet?", + "discard_changes_explain": "Ju keni ndryshime të pashpëtuara. Jeni i sigurtë që nuk doni ti ruani dhe te dilni?", + "enter_password": "Fusni fjalëkalimin", + "never": "Kurrë", + "of": "{numri} nga {totali}", + "ok": "OK", + "enter_url": "Fusni URL", + "storage_is_encrypted": "Memoria është e kriptuar. Duhet të fusni fjalkalimin për të pasur vizibilitet.", + "yes": "Po", + "no": "Jo", + "seed": "Fara", + "success": "Sukses", + "wallet_key": "Fjalkalimi i portofolit", + "close": "Mbylle", + "change_input_currency": "Ndrysho valuten 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 futur info të pesonalizuar", + "unlock": "Hap", + "suggested": "I sugjeruar" + }, + "azteco": { + "codeIs": "Kodi promocional është", + "errorBeforeRefeem": "Para përdorimit, duhet të shtoni një portofol Bitcoin-i.", + "errorSomething": "Diçka shkoi gabim. Eshtë 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" + }, + "entropy": { + "save": "Ruaj", + "title": "Entropia", + "undo": "Anullo", + "amountOfEntropy": "{bits} nga {limit} bits" + }, + "errors": { + "broadcast": "Shpërndarja nuk funksjonoi.", + "error": "Gabim", + "network": "Problem me Internetin" + }, + "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 në portofol Bitcoin-i për të vazhduar.", + "refill_external": "Rimbush nga një portofol tjetër", + "refill_lnd_balance": "Rimbush ballancë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", + "sats": "sats.", + "wasnt_paid_and_expired": "Fatura nuk është paguar dhe ka skaduar." + }, + "plausibledeniability": { + "create_fake_storage": "Krijo një memorie të kriptuar", + "create_password_explanation": "Fjalekalimi per depozizen fallco nuk duhet te jete i njejti me ate te depozites se vertete.", + "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": "Fjalekalimi eshte ne perdorim. Ju lutem, provin nje fjalekalim tjeter.", + "title": "Mohim i besueshëm" + }, + "pleasebackup": { + "ask": "A i keni shpetuar fjalet per rigjenerimin e portofolit? Keto fjale do ju duhen per te pasur akses ne fondet tuaja ne rastin se humbni kete dispozitiv. Pa keto fjale, fondet tuaja do jene te humbura.", + "ask_no": "Jo, nuk kam.", + "ask_yes": "Po, e kam.", + "ok": "Ok, i shkruajta.", + "ok_lnd": "Ok, i shpëtova.", + "text": "Ju lutem merrni nje moment per te shkruajtur keto fjale ne nje cope letre.\nEshte mundesia juaj e vetme per te rigjeneruar portofolin.", + "text_lnd": "Ju letem shpetoni nje kopje te portofolit. Do ju nevojitet te rigjneroni portofolin ne rast humbje.", + "title": "Portofoli juaj u kriua" + }, + "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 maksimale është {min} sats ose {currency}", + "qrcode_for_the_address": "QR kodi për adresën" + }, + "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_copy": "Kopjoje dhe trasmetoje më vonë", + "create_details": "Detajet", + "create_fee": "Komision", + "create_memo": "Memo", + "create_satoshi_per_vbyte": "Satoshi per vByte", + "create_to": "Te", + "create_tx_size": "Madhësia e Transfertës", + "create_verify": "Verifiko ne 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 sigurte qe doni te fshini te gjithe marresit?", + "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 eshte e vlefshme", + "details_adv_fee_bump": "Lejo rritjen e komisionit", + "details_adv_full": "Perdor te gjithe Ballancen", + "details_adv_full_sure": "Jeni i sigurt që doni të përdorni gjithë bilancin e portofolit tuaj për këtë transferte?", + "details_adv_full_sure_frozen": "Jeni i sigurt që doni të përdorni gjithë bilancin e portofolit tuaj për këtë transaksion? Vini re se monedhat e ngrira nuk mund te perdoren. ", + "details_adv_import": "Importo nje Transferte", + "details_adv_import_qr": "Imprto nje Transferte (QR)", + "details_amount_field_is_not_valid": "Sasia e perdorur nuk eshte e vlefshme", + "details_amount_field_is_less_than_minimum_amount_sat": "Sasia e percaktuar eshte shume e vogel. Ju lutem fusni nje sasi me te madhe se 500 sats.", + "details_create": "Krijo një Faturë", + "details_error_decode": "E pamundur me de-koduar adresen e Bitcoin-it", + "details_fee_field_is_not_valid": "Komisioni nuk eshte i vlefshem", + "details_frozen": "{amount} BTC jane te ngrira.", + "details_next": "Pas", + "details_no_signed_tx": "File-at e zgjedhur nuk permbajne asnje transferte e cila mund te importohet.", + "details_note_placeholder": "Shenim per veten", + "counterparty_label_placeholder": "Ndrysho emrin e kontaktit", + "details_scan": "Skano", + "details_scan_hint": "kliko dy here per skanim ose importo destinacionin.", + "details_total_exceeds_balance": "Sasia qe deshironi te dergoni e tejkalon ballancen tuaj", + "details_total_exceeds_balance_frozen": "Shuma që po dërgoni tejkalon ballancen e disponueshëm. Ju lutemi vini re se monedhat e ngrira nuk mund te dergohen.", + "details_unrecognized_file_format": "Formati i file-it eshte i panjohur.", + "details_wallet_before_tx": "Para se te krijoni nje transferte, duhet te keni nje portofolo Bitcoin-i.", + "dynamic_init": "Duke filluar", + "dynamic_next": "Pas", + "dynamic_prev": "Para", + "dynamic_start": "Fillo", + "dynamic_stop": "Ndalo", + "fee_10m": "10m", + "fee_1d": "1d", + "fee_3h": "3h", + "fee_custom": "Peronal", + "fee_fast": "Shpejt", + "fee_medium": "Mesatar", + "fee_satvbyte": "ne sat/vByte", + "fee_slow": "Avashët", + "header": "Dërgo", + "input_clear": "Fshi", + "input_done": "U kry", + "input_paste": "Ngjit", + "input_total": "Total:", + "psbt_sign": "Firmos një transfertë", + "psbt_tx_export": "Exporto ne file", + "success_done": "U kry" + }, + "settings": { + "about": "Rreth", + "about_license": "Licenca e tipit MIT", + "about_review": "Na leni një review", + "about_sm_github": "GitHub", + "about_sm_telegram": "Knali Telegram", + "biometrics": "Te dhenat Biometrike", + "biom_conf_identity": "Ju lutem konfirmoni identitetin", + "currency": "Valuta", + "electrum_connected": "I lidhur", + "electrum_offline_mode": "Modaliteti Off-Line", + "use_ssl": "Perdor SSL", + "electrum_settings_server": "Serveri Elektrum", + "electrum_status": "Gjëndja", + "electrum_preferred_server": "Serveri i Preferuar", + "encrypt_tstorage": "Depozita", + "general": "Gjenerale", + "language": "Gjuha", + "last_updated": "Perditesimi i Fundit", + "license": "Licenca", + "lightning_saved": "Ndryshimet tuaja jane shpetuar me sukses.", + "lightning_settings": "Serveri Lightning", + "network": "Rrjeti", + "network_broadcast": "Publiko Transferten", + "network_electrum": "Serveri Elektrum", + "password": "Fjalekalim", + "plausible_deniability": "Mohim i besueshëm", + "privacy": "Privatesia", + "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 transfertat 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—asnjanjë të 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": "Shpetuar", + "total_balance": "Ballanca Totale" + }, + "transactions": { + "cpfp_create": "Krijo", + "details_copy": "Kopjo", + "details_title": "Transferte", + "details_to": "Dalje", + "pending": "Në pritje", + "list_title": "Transfertat", + "transaction": "Transferte", + "updating": "Duke u perditesuar..." + }, + "wallets": { + "add_bitcoin": "Bitcoin", + "add_create": "Krijo", + "total_balance": "Ballanca Totale", + "add_entropy": "Entropia", + "add_lightning": "Lightning", + "add_title": "Shto Portofol", + "add_wallet_name": "Emer", + "add_wallet_type": "Tipi", + "add_wallet_seed_length_12": "12 fjalet", + "add_wallet_seed_length_24": "24 fjalet", + "details_address": "Adresa", + "details_show_addresses": "Trego adresen", + "details_title": "Portofol", + "wallets": "Portofola", + "details_type": "Tipi", + "import_discovery_title": "Zbulo", + "import_discovery_no_wallets": "Asnje 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 nje portofol", + "list_create_a_wallet_text": "Eshte fala dhe mund te krijoni \nsa te deshironi.", + "list_long_choose": "Zgjidh Foton", + "paste_from_clipboard": "Ngjit", + "list_long_scan": "Skano QR kodin", + "list_title": "Portofola", + "list_tryagain": "Provojeni perseri", + "manage_title": "Menaxho Portofolat", + "no_results_found": "Asnje rezultat", + "please_continue_scanning": "Vazhdo skanimin.", + "select_no_bitcoin": "Per momentin nuk ka asnje portofol Bitcoin-i.", + "select_wallet": "Zgjidh Portofolin", + "share_number": "Shperndaj {number}", + "more_info": "Me shum Info" + }, + "total_balance_view": { + "hide": "Mçihe", + "title": "Ballanca Totale" + }, + "multisig": { + "confirm": "Konfirmo", + "header": "Dërgo", + "share": "Shpërndaj", + "view": "Shiko", + "manage_keys": "Menaxho Fjalekalimet", + "signatures_required_to_spend": "Firmat janë të nevojshme {number}", + "signatures_we_can_make": "mund të bjmë {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ë transfertë", + "what_is_vault_wallet": "portofol.", + "needs": "I duhet", + "quorum_header": "Kuorumi", + "wallet_type": "Tipi i Portofolit", + "create_new_key": "Krijo te ri", + "scan_or_open_file": "Skano ose hap file-in", + "input_fp": "Fut shenjat e gishtave", + "ms_help": "Ndihme", + "ms_help_title2": "Ndrysho Celësat" + }, + "is_it_my_address": { + "title": "A eshte adresa ime?", + "enter_address": "Fut adresen", + "check_address": "Kontrollo adresen", + "view_qrcode": "Shiko QR kodin" + }, + "autofill_word": { + "generate_word": "Gjenero fjalën përfundimtare." + }, + "cc": { + "change": "Ndrysho", + "empty": "Ky portofol ska asnjë xheton për momentin", + "freeze": "Ngrije", + "freezeLabel": "Ngrije", + "freezeLabel_un": "Shkrije", + "header": "Kontrollo Xhetonin", + "use_coin": "Përdor Xhetonin", + "use_coins": "Përdor Xhetonat", + "sort_height": "Lartësia", + "sort_value": "Vlerë", + "sort_label": "Etiketë", + "sort_status": "Gjëndja", + "sort_by": "Radhiti sipas" + }, + "units": { + "BTC": "BTC", + "MAX": "Max", + "sat_vbyte": "sat/vByte", + "sats": "sats" + }, + "addresses": { + "copy_private_key": "Kopjo celë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": "Transfertat" + }, + "bip47": { + "payment_code": "Kodi i Pagesës", + "contacts": "Kontaktet", + "pay_this_contact": "Paguaj këtë kontakt", + "rename_contact": "Riemëro kontaktin", + "hide_contact": "Mçife kontaktin", + "rename": "Riemëro", + "add_contact": "Shto Kontakt", + "provide_payment_code": "Fut kodin e pagesës" + } +} diff --git a/loc/sr_RS.json b/loc/sr_RS.json index 55ea714d0ca..2bc8788541e 100644 --- a/loc/sr_RS.json +++ b/loc/sr_RS.json @@ -8,8 +8,5 @@ "yes": "Da", "no": "Ne", "wallet_key": "Ključ novčanika" - }, - "settings": { - "electrum_clear_alert_cancel": "Poništi" } } diff --git a/loc/sv_se.json b/loc/sv_se.json index ceb3c97c24e..b2bbba249fd 100644 --- a/loc/sv_se.json +++ b/loc/sv_se.json @@ -6,38 +6,27 @@ "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", "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" - }, "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", "title": "Lös in Azte.co kupong" }, "entropy": { @@ -48,78 +37,50 @@ "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": "sätt in / ta ut" }, "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" }, "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..." }, "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}", @@ -161,9 +122,7 @@ "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", @@ -193,8 +152,6 @@ "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", @@ -204,20 +161,18 @@ "outdated_rate": "Betygsätt senaste uppdateringen: {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" }, "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,71 +180,46 @@ "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", + "electrum_connected_not": "Inte ansluten", "lndhub_uri": "E.g., {example}", "electrum_host": "E.g., {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_electrum": "Electrum server", @@ -297,33 +227,26 @@ "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", "selfTest": "Självtest", "save": "Spara", "saved": "Sparad", - "success_transaction_broadcasted": "Framgång! Din transaktion har sänts ut!", "total_balance": "Total balans", "total_balance_explanation": "Visa det totala saldot för alla dina plånböcker på dina startskärmswidgets.", "widgets": "Widgets", "tools": "Verktyg" }, "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" + "would_you_like_to_receive_notifications": "Vill du få aviseringar när du får inkommande betalningar?" }, "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.", @@ -338,9 +261,7 @@ "cpfp_title": "Höj avgift (CPFP)", "details_balance_hide": "Göm 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_note": "Kopiera anteckning", "details_copy_txid": "Kopiera Transaktions ID", @@ -349,8 +270,6 @@ "details_outputs": "Outputs", "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", "enable_offline_signing": "Den här plånboken används inte i samband med en offlinesignering. Skulle du vilja aktivera det nu?", @@ -363,6 +282,7 @@ "eta_1d": "ETA: In ~1 day", "view_wallet": "Visa {walletLabel}", "list_title": "Transaktioner", + "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)", @@ -376,44 +296,39 @@ "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Enkel och kraftfull Bitcoin-plånbok", "add_create": "Skapa", + "total_balance": "Total balans", + "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_delete": "Radera", "details_delete_wallet": "Radera plånbok", "details_derivation_path": "derivation path", - "details_display": "visa i listan med plånböcker", "details_export_backup": "Exportera / ta backup", "details_export_history": "Exportera historik till CSV", "details_master_fingerprint": "Master Fingerprint", "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_yes_delete": "Ja, ta bort", "enter_bip38_password": "Ange lösenord för att dekryptera", "export_title": "exportera plånbok", @@ -433,44 +348,38 @@ "import_discovery_subtitle": "Välj en upptäckt plånbok", "import_discovery_derivation": "Använd anpassad derivation path", "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", "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" }, + "total_balance_view": { + "title": "Total balans" + }, "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", @@ -480,7 +389,6 @@ "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", @@ -507,20 +415,14 @@ "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.", "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)", @@ -545,21 +447,21 @@ "owns": "{label} äger {address}", "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" + "no_wallet_owns_address": "Ingen av de tillgängliga plånböckerna äger den angivna adressen." }, "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å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.", + "sort_label": "Etikett", + "sort_status": "Status" }, "units": { "BTC": "BTC", @@ -601,8 +503,6 @@ }, "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" } diff --git a/loc/th_th.json b/loc/th_th.json index 5efbc5d8bfb..f1e9ce21010 100644 --- a/loc/th_th.json +++ b/loc/th_th.json @@ -10,7 +10,6 @@ "storage_is_encrypted": "ที่เก็บข้อมูลของคุณถูกเข้ารหัส. ต้องการรหัสผ่านเพื่อถอดรหัส", "yes": "ถูกต้อง", "no": "ไม่", - "save": "บันทึก", "seed": "ซีด", "success": "สำเร็จ", "wallet_key": "กุญแจกระเป๋าเงิน" @@ -35,10 +34,8 @@ "network": "เน็ตเวิร์คผิดพลาด" }, "lnd": { - "errorInvoiceExpired": "ใบวางบิลหมดอายุแล้ว", "expired": "หมดอายุแล้ว", "payButton": "จ่าย", - "placeholder": "ใบวางบิล", "potentialFee": "ค่าธรรมเนียมโดยประมาณ: {fee}", "refill": "เติม", "refill_create": "กรุณาสร้างกระเป๋าสตางค์บิตคอยน์ เพื่อดำเนินการต่อ", @@ -51,34 +48,27 @@ "additional_info": "ข้อมูลเพิ่มเติม", "for": "สำหรับ:", "lightning_invoice": "ใบแจ้งหนี้ไลท์นิง", - "open_direct_channel": "เปิดช่องโดยตรงไปที่โหนดนี้", "please_pay": "กรุณาจ่าย", - "preimage": "Preimage", "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": "ท่านได้บันทึก backup phrase แล้วใช่หรือไม่? ในกรณีที่ท่านสูญเสียอุปกรณ์นี้ ท่านสามารถกู้เงินของท่านได้ด้วย backup phrase อันนี้ หากท่านไม่ได้บันทึก backup phrase อันนี้ไว้ ท่านจะสูญเสียเงินของท่านไปอย่างถาวร", - "ask_no": "ยัง ฉันยังไม่ได้ทำ", - "ask_yes": "ใช่ ฉันได้ทำแล้ว" + "ok_lnd": "ฉันได้บันทึกไว้แล้ว", + "title": "ได้สร้างกระเป๋าสตางค์ของท่านแล้ว" }, "receive": { "details_create": "สร้าง", "details_label": "คำอธิบาย", "details_setAmount": "รับด้วยจำนวน", - "details_share": "แชร์", "header": "รับ" }, "send": { @@ -134,16 +124,13 @@ "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}" + "success_done": "สำเร็จ" }, "settings": { "about": "เกี่ยวกับ", @@ -154,8 +141,6 @@ "about_selftest": "ทำการทดสอบ", "about_sm_github": "กิตฮับ", "about_sm_telegram": "เทเลแกรมแช็ท", - "about_sm_twitter": "ติดตามเราในทวิตเตอร์", - "advanced_options": "ตัวเลือกขั้นสูง", "biometrics": "Biometrics", "biom_conf_identity": "กรุณายืนยันตัวตนของท่าน", "currency": "สกุลเงิน", @@ -164,24 +149,15 @@ "default_wallets": "ดูกระเป๋าสตางค์ทุกอัน", "electrum_connected": "เชื่อมต่อแล้ว", "electrum_connected_not": "ไม่ได้เชื่อมต่อ", - "electrum_error_connect": "ไม่สามารถเชื่อมต่อไปยังเซิร์ฟเวอร์ Electrum ได้", "electrum_saved": "บันทึกสำเร็จ ท่านอาจจำเป็นต้องรีสตาร์ท", "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": "ภาษา", @@ -193,21 +169,14 @@ "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": "ยอดรวม" }, - "notifications": { - "ask_me_later": "ถามฉันภายหลัง" - }, "transactions": { "cancel_no": "ธุรกรรมนี้ไม่สามารทเปลี่ยนแทนได้", "cancel_title": "ยกเลิกธุรกรรม (RBF)", @@ -217,17 +186,16 @@ "cpfp_title": "เพิ่มค่าธรรมเนียม (CPFP)", "details_balance_hide": "ซ่อนยอดคงเหลือ", "details_balance_show": "แสดงยอดคงเหลือ", - "details_block": "บล็อกไฮต์", "details_copy": "ก๊อปปี้", "details_from": "อินพุท", "details_inputs": "อินพุท", "details_outputs": "เอ้าพุท", "details_received": "ได้รับแล้ว", - "details_show_in_block_explorer": "แสดงด้วย block explorer", "details_title": "ธุรกรรม", "details_to": "เอ้าพุท", "pending": "รอดำเนินการ", "list_title": "ธุรกรรม", + "transaction": "ธุรกรรม", "rbf_title": "เพิ่มค่าธรรมเนียม (RBF)", "status_bump": "เพิ่มค่าธรรมเนียม", "status_cancel": "ยกเลิกธุรกรรม", @@ -237,9 +205,10 @@ "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 +217,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": "ส่งออกกระเป๋าสตางค์", @@ -277,11 +242,11 @@ "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": "เลือกกระเป๋าสตางค์", @@ -290,8 +255,11 @@ "add_ln_wallet_first": "คุณต้องเพิ่มกระเป๋าเงินไลท์นิงก่อน", "xpub_title": "XPUB ของกระเป๋าสตางค์" }, + "total_balance_view": { + "title": "ยอดรวม" + }, "multisig": { - "multisig_vault": "ห้องนิรภัย", + "multisig_vault": "ห้องนิรภัยหลายลายเซ็น", "default_label": "ห้องนิรภัยหลายลายเซ็น", "multisig_vault_explain": "ความปลอดภัยที่ดีที่สุดสำหรับเงินจำนวนมาก", "provide_signature": "ใส่ลายเซ็น", @@ -300,7 +268,6 @@ "fee_btc": "{number} บิตคอยน์", "confirm": "ยืนยัน", "header": "ส่ง", - "share": "แชร์", "view": "ดู", "manage_keys": "จัดการกุญแจ", "signatures_required_to_spend": "ต้องมีลายเซ็น {number}", @@ -342,7 +309,9 @@ "freezeLabel_un": "ยกเลิกการระงับ", "header": "ควบคุมเหรียญ", "use_coin": "ใช้เหรียญ", - "use_coins": "ใช้เหรียญ" + "use_coins": "ใช้เหรียญ", + "sort_label": "ป้าย", + "sort_status": "สถานะ" }, "units": { "BTC": "บิตคอยน์", diff --git a/loc/tr_tr.json b/loc/tr_tr.json index adfb73dfbc1..0006bc45e87 100644 --- a/loc/tr_tr.json +++ b/loc/tr_tr.json @@ -1,27 +1,24 @@ { "_": { - "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.", + "ok": "Evet", + "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" }, "azteco": { "codeIs": "Bilet kodunuz ", @@ -43,60 +40,33 @@ "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" + "title": "Bakiyeleri Yönet" }, "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ı." }, "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.", "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": { - "ask_no": "Hayır, kaydetmedim.", - "ask_yes": "Evet, kaydettim.", - "ok": "Evet, deftere yazdım.", - "ok_lnd": "Evet, kaydettim.", + "ok_lnd": "Tamam, 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." }, @@ -104,7 +74,6 @@ "details_create": "Oluştur", "details_label": "Açıklama", "details_setAmount": "Miktar ile al", - "details_share": "Paylaş", "header": "Al" }, "send": { @@ -164,53 +133,40 @@ "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" }, "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", "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" }, - "notifications": { - "no_and_dont_ask": "Hayır, bir daha sorma", - "ask_me_later": "Daha sonra hatırlat" - }, "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" }, "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 +175,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,16 +191,18 @@ "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" }, + "total_balance_view": { + "title": "Bakiye" + }, "multisig": { "confirm": "Onayla", "header": "Gönder", - "share": "Paylaş", "create": "Oluştur", "co_sign_transaction": "Bir işlemi imzalayın", "ms_help": "Yardım", @@ -256,6 +213,9 @@ "enter_address": "Adres gir", "check_address": "Adresi kontrol et" }, + "cc": { + "sort_label": "Etiket" + }, "units": { "BTC": "BTC", "MAX": "Maks", diff --git a/loc/ua.json b/loc/ua.json index a8a6b380ea3..f1eaa12770e 100644 --- a/loc/ua.json +++ b/loc/ua.json @@ -4,35 +4,31 @@ "cancel": "Відміна", "continue": "Продовжити", "clipboard": "Буфер обміну", + "discard_changes": "Скасувати зміни?", "enter_password": "Введіть пароль", "never": "Ніколи", - "disabled": "Вимкнено", "of": "{number} з {total}", "ok": "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": "Змінити валюту введення", "refresh": "Оновити", - "more": "Більше", - "pick_file": "Вибрати файл" - }, - "alert": { - "default": "Сповіщення" + "enter_amount": "Введіть суму", + "qr_custom_input_button": "Торкніться 10 разів, щоб ввести користувацьке введення" }, "azteco": { "codeIs": "Ваш код ваучера", "errorBeforeRefeem": "Перш ніж підтвердити, ви повинні додати гаманець Bitcoin.", "errorSomething": "Щось пішло не так. Цей ваучер ще дійсний?", + "redeem": "Купити на гаманець", "redeemButton": "Купити", - "success": "Успіх" + "success": "Успіх", + "title": "Викупити ваучер Azte.co" }, "entropy": { "save": "Зберегти", @@ -45,58 +41,47 @@ "network": "Помилка Мережі" }, "lnd": { - "active": "Активний", - "inactive": "Неактивний", - "channels": "Канали", - "no_channels": "Немає каналів", - "close_channel": "Закрити канал", - "new_channel": "Новий канал", "expired": "Термін дії закінчився", + "expiresIn": "Закінчується через {time} хвилин", "payButton": "Оплатити", - "open_channel": "Відкрити Канал", - "remote_host": "Віддалений хост", + "placeholder": "Рахунок або адреса", "refill": "Поповнити", + "refill_create": "Щоб продовжити, будь ласка, створіть біткоїн-гаманець для поповнення.", "refill_external": "Поповнити з зовнішнього гаманця", "refill_lnd_balance": "Збільшити баланс Lightning гаманця", - "title": "Мої Кошти", - "can_send": "Можна Надіслати", - "can_receive": "Можна Отримати", - "view_logs": "Переглянути Логи" + "title": "Мої Кошти" }, "lndViewInvoice": { "additional_info": "Додаткова Інформація", "for": "Для:", "lightning_invoice": "Lightning Рахунок", + "please_pay_between_and": "Будь ласка, сплатіть від {min} до {max}", "please_pay": "Будь ласка оплатіть", - "preimage": "Прообраз", "sats": "sats.", "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_no": "Ні, я не маю", - "ask_yes": "Так, я маю", - "ok": "Ок, я записав", - "ok_lnd": "Ок, я зберіг", - "text_lnd": "Будь ласка, збережіть цю резервну копію гаманця. Вона дозволить відновити гаманець у разі втрати.", - "title": "Гаманець було створено" + "ask": "Чи зберегли ви резервну фразу вашого гаманця? Ця резервна фраза необхідна для доступу до ваших коштів, якщо ви втратите цей пристрій. Без резервної фрази ваші кошти будуть безповоротно втрачені.", + "ok": "Ок, я записав.", + "text": "Будь ласка, знайдіть хвилинку і запишіть цю мнемонічну фразу на аркуші паперу.\nЦе ваша резервна копія, і ви зможете використати її для відновлення гаманця.", + "text_lnd": "Будь ласка, збережіть цю резервну копію гаманця. Вона дозволить відновити гаманець у разі втрати." }, "receive": { "details_create": "Створити", "details_label": "Опис", - "details_share": "Поділитися", - "header": "Отримати" + "details_setAmount": "Отримання з сумою", + "header": "Отримати", + "maxSats": "Максимальна кількість - {max} сатоші", + "maxSatsFull": "Максимальна сума - {max} сатоші або {currency}", + "minSats": "Мінімальна кількість - {min} сатоші" }, "send": { "broadcastButton": "Транслювати", @@ -150,8 +135,6 @@ "permission_camera_message": "Нам потрібен дозвіл на використання камери.", "psbt_sign": "Підписати транзакцію", "open_settings": "Відкрити Налаштування", - "permission_storage_later": "Запитати мене пізніше", - "permission_storage_message": "BlueWallet потребує вашого дозволу на доступ до сховища, щоб зберегти цей файл.", "permission_storage_denied_message": "BlueWallet не може зберегти цей файл. Відкрийте налаштування пристрою та дайте дозвіл на зберігання.", "permission_storage_title": "Дозвіл доступу до сховища", "psbt_clipboard": "Копіювати в буфер обміну", @@ -161,7 +144,6 @@ "outdated_rate": "Останнє оновлення ціни: {date}", "psbt_tx_open": "Відкрити Підписану Транзакцію", "psbt_tx_scan": "Сканувати Підписану Транзакцію", - "qr_error_no_qrcode": "Нам не вдалося знайти QR-код у вибраному зображенні. Переконайтеся, що зображення містить лише QR-код без додаткового вмісту, наприклад тексту чи кнопок.", "reset_amount": "Очистити Суму", "reset_amount_confirm": "Бажаєте очистити суму?", "success_done": "Готово", @@ -176,10 +158,7 @@ "about_review": "Залиште нам відгук", "about_selftest": "Запустити самоперевірку", "about_sm_github": "GitHub", - "about_sm_discord": "Discord Сервер", "about_sm_telegram": "Telegram канал", - "about_sm_twitter": "Слідкуйте за нами у Twitter", - "advanced_options": "Розширені Налаштування", "biometrics": "Біометрія", "biom_conf_identity": "Будь ласка підтвердіть свою особистість.", "currency": "Валюта", @@ -190,56 +169,41 @@ "use_ssl": "Використовувати SSL", "electrum_settings_server": "Electrum Сервер", "electrum_status": "Статус", - "electrum_clear_alert_title": "Очистити історію?", - "electrum_clear_alert_cancel": "Відмінити", - "electrum_clear_alert_ok": "Ок", - "electrum_select": "Вибрати", - "electrum_clear": "Очистити", "encrypt_title": "Безпека", "encrypt_tstorage": "Сховище", "general": "Загальні", - "general_adv_mode": "Enable advanced mode", "header": "Налаштування", "language": "Мова", "lightning_saved": "Ваші зміни успішно збережено.", "lightning_settings": "Налаштування Lightning", - "tor_settings": " Налаштування Tor", "network": "Мережа", "network_electrum": "Electrum Сервер", "notifications": "Сповіщення", "password": "Пароль", - "password_explain": "Придумайте пароль для розшифровки сховища.", - "passwords_do_not_match": "Паролі не збігаються.", "plausible_deniability": "Правдоподібне заперечення...", "privacy": "Приватність", "privacy_system_settings": "Системні Налаштування", "privacy_quickactions": "Ярлики Гаманця", "privacy_do_not_track": "Вимкнути Аналітику", - "push_notifications": "Пуш Сповіщення", - "retype_password": "Наберіть пароль ще раз", "save": "Зберегти", "saved": "Збережено", "total_balance": "Загальний Баланс", "widgets": "Віджети", "tools": "Інструменти" }, - "notifications": { - "ask_me_later": "Запитати мене пізніше" - }, "transactions": { "copy_link": "Копіювати Посилання", "cpfp_create": "Створити", "details_balance_hide": "Приховати Баланс", "details_copy": "Копіювати", - "details_copy_amount": "Копіювати Суму", "details_copy_note": "Копіювати Нотатку", "details_from": "Від", - "details_show_in_block_explorer": "Show in block explorer", "details_title": "Деталі транзакції", "details_to": "Кому", "pending": "Очікування", "received_with_amount": "+{amt1} ({amt2})", "list_title": "Транзакції", + "transaction": "Деталі транзакції", "transactions_count": "Кількість Транзакцій", "updating": "Оновлення..." }, @@ -247,15 +211,15 @@ "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Простий і потужний Біткойн гаманець", "add_create": "Створити", + "total_balance": "Загальний Баланс", + "add_entropy": "Ентропія", "add_import_wallet": "Імпортувати гаманець", "add_lightning": "Lightning", - "add_lndhub": "Підключитися до вашого LNDHub", "add_lndhub_placeholder": "Адреса Вашої Ноди", "add_placeholder": "мій перший гаманець", "add_title": "Додати Гаманець", "add_wallet_name": "ім'я гаманця", "add_wallet_type": "Тип Гаманця", - "balance": "Баланс", "details_address": "Адреса", "details_advanced": "Розширені", "details_are_you_sure": "Ви впевнені?", @@ -263,13 +227,11 @@ "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_wallet_updated": "Гаманець оновлено", "details_yes_delete": "Так, видалити", "enter_bip38_password": "Введіть пароль для розшифрування", "export_title": "Експорт Гаманця", @@ -281,16 +243,14 @@ "import_success": "Ваш гаманець було успішно імпортовано.", "import_search_accounts": "Пошук облікових записів", "import_title": "імпорт", - "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_empty_txs2": "Почніть зі свого гаманця.", "list_latest_transaction": "Остання Транзакція", "list_long_choose": "Виберіть Фото", + "paste_from_clipboard": "Вставити", + "import_file": "Імпортувати Файл", "list_long_scan": "Сканувати QR-код", "list_title": "Гаманці", "list_tryagain": "Спробуйте ще раз", @@ -300,13 +260,14 @@ "pull_to_refresh": "Потягніть щоб оновити", "xpub_title": "XPUB Гаманця" }, + "total_balance_view": { + "title": "Загальний Баланс" + }, "multisig": { - "multisig_vault": "Сховище", "fee": "Комісія: {number}", "fee_btc": "{number} BTC", "confirm": "Підтвердити", "header": "Надіслати", - "share": "Поділитися", "view": "Переглянути", "manage_keys": "Керувати Ключами", "how_many_signatures_can_bluewallet_make": "скільки підписів може зробити BlueWallet", @@ -330,19 +291,17 @@ "ms_help_title3": "Резервні Копії Vault", "ms_help_title5": "Enable advanced mode" }, - "is_it_my_address": { - "view_qrcode": "Переглянути QR-код" - }, "cc": { "change": "Змінити", "coins_selected": "Монет Вибрано ({number})", - "empty": "На даний момент у цьому гаманці немає монет.", "freeze": "Заморозити", "freezeLabel": "Заморозити", "freezeLabel_un": "Розморозити", "header": "Керування Монетою", "use_coin": "Використати Монету", - "use_coins": "Використовувати Монети" + "use_coins": "Використовувати Монети", + "sort_label": "Мітка", + "sort_status": "Статус" }, "units": { "BTC": "BTC", diff --git a/loc/vi_vn.json b/loc/vi_vn.json index 65a6483c895..9505a0afdf8 100644 --- a/loc/vi_vn.json +++ b/loc/vi_vn.json @@ -6,31 +6,20 @@ "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", "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" - }, "azteco": { "codeIs": "Mã voucher của bạn là", "errorBeforeRefeem": "Trước khi đổi phiếu, bạn phải thêm một ví Bitcoin.", @@ -51,80 +40,47 @@ "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" + "title": "Quản lý quỹ" }, "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. " }, "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.", "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" + "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." }, "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} " }, "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.", @@ -161,9 +117,7 @@ "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", @@ -193,8 +147,6 @@ "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", @@ -204,11 +156,9 @@ "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" }, "settings": { @@ -225,17 +175,12 @@ "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", @@ -243,7 +188,6 @@ "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", @@ -252,32 +196,16 @@ "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á.", "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,11 +213,8 @@ "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_electrum": "Máy chủ Electrum", @@ -297,33 +222,25 @@ "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", "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" }, "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" + "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?" }, "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í.", @@ -338,9 +255,7 @@ "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", @@ -349,8 +264,6 @@ "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?", @@ -363,6 +276,7 @@ "eta_1d": "Dến trong ~1 ngày", "view_wallet": "Xem {walletLabel}", "list_title": "Các giao dịch", + "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_title": "Tăng phí (RBF)", @@ -376,44 +290,39 @@ "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_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?", "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_delete": "Xoá", "details_delete_wallet": "Xoa 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í", @@ -433,44 +342,36 @@ "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_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í" }, + "total_balance_view": { + "title": "Tổng số dư" + }, "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 +381,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ý", @@ -507,20 +407,14 @@ "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}.", "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ý", "input_fp": "Nhập vân tay", "input_fp_explain": "Bỏ qua để sử dụng mặc định (00000000)", @@ -545,21 +439,20 @@ "owns": "{label} sở hữu {address}", "enter_address": "Nhập địa chỉ", "check_address": "Kiểm tra địa chỉ", - "no_wallet_owns_address": "Không có ví nào có sẵn sở hữu địa chỉ được cung cấp.", - "view_qrcode": "Xem mã QR" + "no_wallet_owns_address": "Không có ví nào có sẵn sở hữu địa chỉ được cung cấp." }, "cc": { "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_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" }, "units": { "BTC": "BTC", @@ -601,8 +494,6 @@ }, "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" } diff --git a/loc/zar_afr.json b/loc/zar_afr.json index 963e7503174..14449cf02cd 100644 --- a/loc/zar_afr.json +++ b/loc/zar_afr.json @@ -4,24 +4,19 @@ "cancel": "Kanselleer", "continue": "Gaan voort", "clipboard": "Knipbord", + "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. ", "yes": "Ja", "no": "Nee", - "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" + "enter_amount": "Sit die bedrag in" }, "azteco": { "codeIs": "Jou koepon kode is", @@ -35,7 +30,8 @@ "entropy": { "save": "Berg", "title": "Entropie", - "undo": "Ontdoen" + "undo": "Ontdoen", + "amountOfEntropy": "{bits} van {limit} bits" }, "errors": { "broadcast": "Sending het misluk.", @@ -43,56 +39,56 @@ "network": "Netwerk Vout" }, "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", "refill": "Herlaai", "refill_create": "Om voort te gaan, skep asb 'n Bitcoin beursie om vondse 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": "U kan nie ‘n faktuur betaal met die selfde beursie waarmee die faktuur geksep is nie.", + "title": "bestuur fondse" }, "lndViewInvoice": { - "open_direct_channel": "Open direkte kanaal met hierdie knoop-punt:" + "additional_info": "Bykomende Inligting", + "lightning_invoice": "Lightning Faktuur", + "please_pay_between_and": "Betaal asseblief tussen {min} en {max}", + "please_pay": "Betaal asseblief", + "sats": "sats.", + "wasnt_paid_and_expired": "Die faktuur was nie betaal nie en het verval." }, "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.", "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": "Vat asseblief ñ oomblik om die woorde op ñ stuk papier neer te skryf.\nDit is jou terugval 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 teug 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...", + "header": "Ontvang", + "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" }, "send": { "broadcastButton": "Saai uit", @@ -107,48 +103,60 @@ "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", + "create_verify": "Verifieer op coinb.in", "details_address": "adres", "details_address_field_is_not_valid": "Adres is ongeldig", "details_amount_field_is_not_valid": "Bedrag is ongeldig", "details_create": "Skep", "details_fee_field_is_not_valid": "Fooi spasie is ongeldig", + "details_next": "Volgende", "details_note_placeholder": "persoonlike notas", "details_scan": "Skandeer", "details_total_exceeds_balance": "Die bedrag is meer as die beskikbare balans.", + "dynamic_next": "Volgende", + "dynamic_prev": "Vorige", + "dynamic_start": "Begin", + "dynamic_stop": "Stop", + "fee_10m": "10m", + "fee_1d": "1d", + "fee_3h": "3h", + "fee_fast": "Vinnig", + "fee_medium": "Medium", + "fee_replace_minvb": "Die totale fooi (satoshi per vByte) wat jy wil betaal, moet meer wees as {min} sat/vByte.", + "fee_satvbyte": "in sat/vByte", + "fee_slow": "Stadig", "header": "Stuur", + "input_clear": "Maak skoon", "input_done": "Klaar", + "input_paste": "Plak", + "permission_camera_message": "Ons het jou toestemming nodig om jou kamera te gebruik. ", + "psbt_sign": "Teken ñ transaksie", "success_done": "Klaar" }, "settings": { "about": "info", "currency": "Geldeenheid", - "electrum_clear_alert_cancel": "Kanselleer", - "general_adv_mode": "Enable advanced mode", "header": "instellings", "language": "Taal", "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.", "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", "plausible_deniability": "Geloofwaardige ontkenbaarheid...", - "retype_password": "Hervoer wagwoord", "save": "stoor" }, "transactions": { "cpfp_create": "Skep", "details_copy": "Kopieer", "details_from": "Inset", - "details_show_in_block_explorer": "Wys in blok verkenner", "details_title": "Transaksie", "details_to": "Resultaat", - "list_title": "transaksies" + "list_title": "transaksies", + "transaction": "Transaksie" }, "wallets": { "add_bitcoin": "Bitcoin", "add_create": "Skep", + "add_entropy": "Entropie", "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_title": "skep beursie", "add_wallet_name": "beursie naam", @@ -157,10 +165,9 @@ "details_are_you_sure": "Is jy seker?", "details_delete": "Vernietig", "details_export_backup": "voer uit / kopieer", - "details_no_cancel": "Nee, kanseleerl", - "details_save": "Berg", "details_show_xpub": "Wys beursie XPUB", "details_title": "Beursiet", + "wallets": "beursies", "details_type": "Tipe", "details_yes_delete": "Ja, vernietig", "export_title": "beursie uitvoer", @@ -174,9 +181,9 @@ "list_empty_txs1": "Jou transaksies sal hier verskyn.", "list_empty_txs2": "Begin met jou beursie.", "list_latest_transaction": "laaste transaksie", + "paste_from_clipboard": "Plak", "list_title": "beursies", "no_ln_wallet_error": "Voordat jy 'n Lightning faktuur betaal moet jy eers 'n Lightning beursie aanwys.", - "reorder_title": "Herorganiseer Beursies", "select_wallet": "Kies Beursie", "xpub_copiedToClipboard": "Gestuur na klipbord.", "add_ln_wallet_first": "Jy moet eers 'n Lightning beursie byvoeg.", @@ -185,10 +192,14 @@ "multisig": { "confirm": "Bevestig", "header": "Stuur", - "share": "deel", + "share": "Deel...", "create": "Skep", + "co_sign_transaction": "Teken ñ transaksie", "ms_help_title5": "Enable advanced mode" }, + "cc": { + "sort_label": "Etiket" + }, "addresses": { "sign_placeholder_address": "adres", "type_receive": "Ontvang", diff --git a/loc/zar_xho.json b/loc/zar_xho.json index 7ee8fa33f7f..091ae50e61a 100644 --- a/loc/zar_xho.json +++ b/loc/zar_xho.json @@ -3,14 +3,27 @@ "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", + "storage_is_encrypted": "Indawo yakho yokugcina inoguqulelo oluntsonkothileyo. Igama lokugqitha liyafuneka ukuyisusa ngokuntsonkotha.", + "yes": "Ewe", + "no": "Hayi", + "seed": "Imbewu", + "success": "Imphumelelo", + "wallet_key": "Isitshixo sesipaji", + "close": "Kufutshane", + "change_input_currency": "Guqula imali yokufaka", + "refresh": "Hlaziya", + "enter_amount": "Faka imali", + "qr_custom_input_button": "Cofa amaxesha ali-10 ukufaka igalelo lesiqhelo" }, "azteco": { + "codeIs": "Ikhowudi yakho yevawutsha ithi", + "errorBeforeRefeem": "Ngaphambi kokuba uhlawule, kufuneka uqale wongeze i-wallet ye-Bitcoin.", "success": "Iphumelele" }, "entropy": { @@ -20,25 +33,21 @@ "expired": "Iphelewe lixesha", "refill": "Gcwalisa", "refill_lnd_balance": "Gcwalisa ingxowa yakho yemali", + "sameWalletAsInvoiceError": ": Awukwazi ukuhlawula i-invoyisi kunye ngengxowa oyisebenzisile ukudala leyo invoyisi.", "title": "lawula imali" }, "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..", "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", "title": "Ukuphika" }, "receive": { "details_create": "Yenza", "details_label": "Inkcazo", "details_setAmount": "Fumana ngexabiso", - "details_share": "yabelana", "header": "Fumana" }, "send": { @@ -68,26 +77,21 @@ "settings": { "about": "Malunga", "currency": "Lwemali", - "electrum_clear_alert_cancel": "Rhoxisa", - "general_adv_mode": "Enable advanced mode", "header": "Izicwangciso", "language": "Ulwimi", "lightning_settings": "Izixhobo zokukhanyisa", "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" }, "transactions": { "cpfp_create": "Yakha", "details_copy": "Ikopi", "details_from": "Negalelo", - "details_show_in_block_explorer": "Bonisa ibhloko umhloi", "details_title": "Ngeniswa", "details_to": "Mveliso", - "list_title": "ngeniswa" + "list_title": "ngeniswa", + "transaction": "Ngeniswa" }, "wallets": { "add_bitcoin_explain": "Simple and powerful Bitcoin wallet", @@ -102,10 +106,9 @@ "details_are_you_sure": "Ingaba uqinisekile?", "details_delete": "Cima", "details_export_backup": "Ukuthumela ngaphandle / yokugcina", - "details_no_cancel": "Hayi, rhoxisa", - "details_save": "Gcina", "details_show_xpub": "Bonise ingxowa XPUB", "details_title": "Ingxowa", + "wallets": "Ingxowa", "details_type": "Uhlobo", "details_yes_delete": "Ewe, yisuse", "export_title": "ukuthunyelwa kweebhanki ", @@ -119,7 +122,6 @@ "list_empty_txs1": "Intengiso yakho iya kubonakala apha,", "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" @@ -127,10 +129,12 @@ "multisig": { "confirm": "Qiniseka", "header": "Thumela", - "share": "yabelana", "create": "Yakha", "ms_help_title5": "Enable advanced mode" }, + "cc": { + "sort_label": "Igama" + }, "addresses": { "sign_placeholder_address": "idilesi", "type_receive": "Fumana", diff --git a/loc/zh_cn.json b/loc/zh_cn.json index dd5b31841d1..0b32c66f895 100644 --- a/loc/zh_cn.json +++ b/loc/zh_cn.json @@ -1,41 +1,50 @@ { "_": { - "bad_password": "密码无效,请重试。", + "bad_password": "密码错误,请重试。", "cancel": "取消", "continue": "继续", "clipboard": "剪贴板", + "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 +52,640 @@ "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": "给自己的备注", + "counterparty_label_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": "所有内部测试均已成功通过,钱包运作良好。", + "block_explorer_invalid_custom_url": "提供的网址 无效。请输入以 http:// 或 https:// 开头的有效网址。", + "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次。 您想重置存储空间吗? 这将删除所有钱包并解密您的存储。", + "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将在启动时立即打开选定的钱包。", + "biom_no_passcode": "您的设备未启用密码或生物识别认证。要继续操作,请在“设置”应用中配置密码或生物识别认证。", + "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_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_enc_and_pass_description": "使用密码来加密存储。生物识别认证将不会用于解锁加密存储。", + "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。", "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": "可合理否认", + "plausible_deniability_description": "高级防护,请谨慎操作。", + "multiple_storages": "多个存储空间", "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}个确认", + "transaction_loading_error": "加载交易时出现问题。请稍后再试。", + "transaction_not_available": "交易不可用", + "confirmations_lowercase": "{confirmations} 次确认", "copy_link": "复制链接", - "expand_note": "打开备注", + "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_copy_block_explorer_link": "复制区块链浏览器链接", + "details_copy_note": "复印备注", + "details_copy_txid": "复制交易 ID", "details_from": "输入", "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 天", + "view_wallet": "查看 {walletLabel}", "list_title": "交易", - "rbf_title": "碰撞费用(RBF)", - "status_bump": "碰撞费用", + "transaction": "交易", + "open_url_error": "无法使用默认浏览器打开链接。请更换默认浏览器并重试。", + "rbf_explain": "我们将使用一笔矿工费更高的交易替换当前交易,以便其能更快被矿工打包确认。这称为 RBF(费用替换)。", + "rbf_title": "追加矿工费(RBF)", + "status_bump": "追加矿工费", "status_cancel": "取消交易", - "transactions_count": "交易计数", - "txid": "交易ID", - "updating": "正在更新..." + "transactions_count": "交易数量", + "txid": "交易 ID", + "from": "从:{counterparty}", + "to": "到:{counterparty}", + "updating": "正在更新……", + "watchOnlyWarningTitle": "安全警告", + "watchOnlyWarningDescription": "请注意骗子,他们经常使用“观察钱包”来欺骗用户。这类钱包无法让您控制或发送资金,只能查看余额。 ", + "custom_fee_warning_title": " 警告", + "custom_fee_warning_description": "低于 1 聪/字节的矿工费是有效的,但可能由于节点政策而无法被转发。 " }, "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": "仍然删除" + }, + "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": "该钱包当前没有任何比特币", "freeze": "冻结", "freezeLabel": "冻结", "freezeLabel_un": "解冻", - "header": "币的控制", - "use_coin": "使用币", - "use_coins": "使用币", - "tip": "此功能使您可以查看、标记、冻结或选择币,以改善钱包管理。 您可以通过点击彩色圆圈选择多个币。" + "header": "选币功能", + "use_coin": "使用比特币", + "use_coins": "使用比特币", + "tip": "此功能可让您查看、标记、冻结或选择比特币,以便更好地管理钱包。您可以通过点击彩色圆圈选择多个比特币。", + "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..85baf254f01 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,12 +12,9 @@ "storage_is_encrypted": "你的儲存資料已經被加密, 請輸入密碼解密。", "yes": "是", "no": "否", - "save": "儲存", "seed": "種子", "success": "成功", - "wallet_key": "錢包密鑰", - "invalid_animated_qr_code_fragment": "無效的動態二維碼,請重試。", - "downloads_folder": "下載資料夾" + "wallet_key": "錢包密鑰" }, "azteco": { "codeIs": "您的優惠碼爲", @@ -38,53 +36,42 @@ "network": "網路錯誤" }, "lnd": { - "errorInvoiceExpired": "賬單已過期", "expired": "已過期", "payButton": "支付", - "placeholder": "賬單", - "potentialFee": "潛在費用:{fee}", "refill": "增值", "refill_create": "爲了繼續進行,請建立一個要增值的比特幣錢包。", "refill_external": "用外部錢包增值", "refill_lnd_balance": "給閃電錢包增值", - "sameWalletAsInvoiceError": "你不能用建立賬單的錢包去支付該賬單。", - "title": "管理資金", - "can_send": "能傳送", - "can_receive": "能接收" + "sameWalletAsInvoiceError": "你不能用建立賬單的錢包去支付該賬單", + "title": "管理資金" }, "lndViewInvoice": { "additional_info": "附加信息", "for": "为了:", "lightning_invoice": "閃電賬單", - "open_direct_channel": "使用此節點來開啟直接頻道:", "please_pay": "請付款", - "preimage": "原像", "sats": "聰", "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": "是的,我完成了", - "text_lnd": "請儲存此錢包備份。這個備份可以在裝置遺失時用來恢複此錢包。" + "ok": "好,我把它写下来了。", + "ok_lnd": "好的,我已經儲存了", + "text_lnd": "請儲存此錢包備份。這個備份可以在裝置遺失時用來恢複此錢包。", + "title": "你的錢包已被建立。" }, "receive": { "details_create": "建立", "details_label": "描述", "details_setAmount": "收款金額", - "details_share": "分享", "header": "收款" }, "send": { @@ -119,7 +106,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,8 +131,6 @@ "permission_camera_message": "我們需要您的授權許可才能使用您的相機。", "psbt_sign": "簽署一單交易", "open_settings": "開啟設定", - "permission_storage_later": "稍後問我", - "permission_storage_message": "BlueWallet需要您的授權許可才能訪問您的儲存空間以儲存此檔案。", "permission_storage_title": "儲存訪問權限", "psbt_clipboard": "複製到剪貼簿", "psbt_this_is_psbt": "這是已部分簽署的比特幣交易(PSBT),請用您的硬體錢包完成簽署。", @@ -155,7 +139,6 @@ "psbt_tx_open": "打開已簽署的交易", "psbt_tx_scan": "掃描已簽署的交易", "success_done": "完成", - "txSaved": "交易檔案({filePath})已儲存在“下載”文件夾中。", "problem_with_psbt": "PSBT的問題" }, "settings": { @@ -169,14 +152,10 @@ "about_selftest": "運行自我檢查", "about_selftest_ok": "所有內部測試均已成功通過,錢包運作良好。", "about_sm_github": "GitHub", - "about_sm_discord": "Discord 伺服器", "about_sm_telegram": "電報(Telegram)頻道", - "about_sm_twitter": "在推特上追蹤我們", - "advanced_options": "進階選項", "biometrics": "生物識別", "biom_10times": "您已嘗試輸入密碼10次。 您想重設儲存空間嗎? 這將刪除所有錢包並解密您的儲存。", "biom_conf_identity": "請確認您的身份。", - "biom_no_passcode": "您的設備沒有密碼。 為了繼續進行,請在“設定”應用程式中配置密碼。", "biom_remove_decrypt": "您的所有錢包將被刪除,您的儲存空間將被解密。您確定要繼續嗎?", "currency": "貨幣", "default_desc": "停用後,BlueWallet將在啟動時立即打開選定的錢包。", @@ -185,31 +164,18 @@ "default_wallets": "查看所有錢包", "electrum_connected": "已連接", "electrum_connected_not": "未連接", - "electrum_error_connect": "無法連接到提供的Electrum伺服器", "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_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的預設伺服器。", @@ -223,17 +189,12 @@ "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": "觸碰並按住主屏幕上的BlueWallet應用圖標,以快速查看您的錢包結餘。", "privacy_clipboard_explanation": "如果在剪貼簿中找到地址或賬單,請提供捷徑。", - "push_notifications": "推送通知", - "retype_password": "再次輸入密碼", "selfTest": "自行測試", "save": "儲存", "saved": "已儲存", @@ -243,9 +204,7 @@ "tools": "工具" }, "notifications": { - "would_you_like_to_receive_notifications": "您想在收到款項時得到通知嗎?", - "no_and_dont_ask": "不,不要再問我", - "ask_me_later": "稍後問我" + "would_you_like_to_receive_notifications": "您想在收到款項時得到通知嗎?" }, "transactions": { "cancel_no": "此交易不可替換。", @@ -259,21 +218,18 @@ "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": "輸出", "enable_offline_signing": "此錢包未與線下簽名結合使用。您想立即啟用它嗎?", "list_conf": "Conf: {number}", "pending": "等待中", "list_title": "交易", + "transaction": "轉賬", "rbf_title": "對碰費用(RBF)", "status_bump": "對碰費用", "status_cancel": "取消交易", @@ -285,13 +241,14 @@ "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": "新增錢包", @@ -303,22 +260,18 @@ "details_advanced": "進階的", "details_are_you_sure": "你確定嗎?", "details_connected_to": "連接到", - "details_del_wb_err": "提供的結餘與此錢包的結餘不匹配,請再試一遍。", "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": "展示錢包公鑰", "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 +283,36 @@ "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_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": "在繳付閃電賬單之前,必須先添加一個閃電錢包。", "looks_like_bip38": "這看起來像是受密碼保護的私鑰(BIP38)。", - "reorder_title": "重新排列錢包", "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": "身份公鑰", "xpub_title": "錢包公鑰" }, + "total_balance_view": { + "title": "結餘" + }, "multisig": { - "multisig_vault": "保管庫", + "multisig_vault": "多重簽名保管庫", "default_label": "多重簽名保管庫", "multisig_vault_explain": "大金額需要的最佳安全性", "provide_signature": "提供簽名", @@ -371,7 +322,6 @@ "fee_btc": "{number} 比特幣", "confirm": "確認", "header": "發送", - "share": "分享", "view": "查看", "manage_keys": "管理密鑰", "how_many_signatures_can_bluewallet_make": "BlueWallet能夠生成多少簽署?", @@ -398,19 +348,14 @@ "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)", @@ -433,20 +378,20 @@ "owns": "{label}擁有{address}", "enter_address": "輸入地址", "check_address": "檢查地址", - "no_wallet_owns_address": "沒有可用的錢包擁有提供的地址。", - "view_qrcode": "檢視二維條碼" + "no_wallet_owns_address": "沒有可用的錢包擁有提供的地址。" }, "cc": { "change": "更變。", "coins_selected": "所選的幣({number})", - "empty": "此錢包目前沒有幣。", "freeze": "凍結", "freezeLabel": "凍結", "freezeLabel_un": "解凍", "header": "币的控制", "use_coin": "使用幣", "use_coins": "使用幣", - "tip": "此功能使您可以查看、標記、凍結或選擇幣,以改善錢包管理。 您可以通過點擊彩色圓圈選擇多個幣。" + "tip": "此功能使您可以查看、標記、凍結或選擇幣,以改善錢包管理。 您可以通過點擊彩色圓圈選擇多個幣。", + "sort_label": "標籤", + "sort_status": "狀態" }, "units": { "BTC": "比特幣", 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..66e906f7683 --- /dev/null +++ b/navigation/AddWalletStack.tsx @@ -0,0 +1,182 @@ +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import React from 'react'; + +import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { + AddComponent, + ImportCustomDerivationPathComponent, + ImportSpeedComponent, + ImportWalletComponent, + ImportWalletDiscoveryComponent, + PleaseBackupComponent, + PleaseBackupLNDHubComponent, + ProvideEntropyComponent, + WalletsAddMultisigComponent, + MultisigAdvancedComponent, + WalletsAddMultisigHelpComponent, + WalletsAddMultisigStep2Component, +} from './LazyLoadAddWalletStack'; +import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack'; +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; + }; + WalletsAddMultisigHelp: undefined; + ScanQRCode: ScanQRCodeParamList; +}; + +const Stack = createNativeStackNavigator(); + +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..f0bcf4a76df --- /dev/null +++ b/navigation/AztecoRedeemStack.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import navigationStyle from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { AztecoRedeemComponent, SelectWalletComponent } from './LazyLoadAztecoRedeemStack'; + +const Stack = createNativeStackNavigator(); + +const AztecoRedeemStackRoot = () => { + const theme = useTheme(); + + return ( + + + + + ); +}; + +export default AztecoRedeemStackRoot; diff --git a/navigation/DetailViewScreensStack.tsx b/navigation/DetailViewScreensStack.tsx new file mode 100644 index 00000000000..a804f817634 --- /dev/null +++ b/navigation/DetailViewScreensStack.tsx @@ -0,0 +1,402 @@ +import React, { useCallback, useMemo } from 'react'; +import { View, Platform, PlatformColor } from 'react-native'; +import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; +import HeaderRightButton from '../components/HeaderRightButton'; +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 TransactionDetails from '../screen/transactions/TransactionDetails'; +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 PaymentCodesListComponent from './LazyLoadPaymentCodeStack'; +import SettingsButton from '../components/icons/SettingsButton'; +import { useSettings } from '../hooks/context/useSettings'; +import { useStorage } from '../hooks/context/useStorage'; +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 { useSizeClass, SizeClass } from '../blue_modules/sizeClass'; +import getWalletTransactionsOptions from './helpers/getWalletTransactionsOptions'; +import { isDesktop } from '../blue_modules/environment'; +import ManageWallets from '../screen/wallets/ManageWallets'; +import ReceiveDetails from '../screen/receive/ReceiveDetails'; + +const DetailViewStackScreensStack = () => { + const theme = useTheme(); + const navigation = useExtendedNavigation(); + const { wallets } = useStorage(); + const { isTotalBalanceEnabled } = useSettings(); + const { sizeClass } = useSizeClass(); + + const DetailButton = useMemo(() => , []); + + const navigateToAddWallet = useCallback(() => { + navigation.navigate('AddWalletRoot'); + }, [navigation]); + + const RightBarButtons = useMemo( + () => + sizeClass === SizeClass.Large ? ( + + ) : ( + <> + + + + + ), + [sizeClass, navigateToAddWallet], + ); + + const useWalletListScreenOptions = useMemo(() => { + const displayTitle = !isTotalBalanceEnabled || wallets.length <= 1; + return { + title: sizeClass === SizeClass.Large ? loc.transactions.list_title : displayTitle ? loc.wallets.wallets : '', + navigationBarColor: theme.colors.navigationBarColor, + headerLargeTitle: displayTitle && sizeClass === SizeClass.Compact, + headerShadowVisible: false, + headerStyle: { + backgroundColor: theme.colors.customHeader, + }, + headerRight: () => (isDesktop ? undefined : RightBarButtons), + }; + }, [RightBarButtons, sizeClass, isTotalBalanceEnabled, theme.colors.customHeader, theme.colors.navigationBarColor, wallets]); + + const walletListScreenOptions = useWalletListScreenOptions; + + // 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: 'minimal' as const, + headerBackTitle: '', + headerBackVisible: true, // Show back button on Android + headerShadowVisible: false, + headerLargeTitle: false, + headerLargeTitleStyle: undefined, + headerTitleStyle: { + color: titleColorString, + }, + headerTransparent: false, + headerBlurEffect: undefined, + headerStyle: { + backgroundColor: theme.colors.customHeader, + }, + }; + }; + + return ( + + + + + + DetailButton, + headerBackButtonDisplayMode: 'default', + })(theme)} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* */} + + + + + + + + + + + + ); +}; + +export default DetailViewStackScreensStack; + +const styles = { + width24: { + width: 24, + }, + walletDetails: { + justifyContent: 'center', + alignItems: 'flex-end', + }, +}; diff --git a/navigation/DetailViewStackParamList.ts b/navigation/DetailViewStackParamList.ts new file mode 100644 index 00000000000..6e7cec37454 --- /dev/null +++ b/navigation/DetailViewStackParamList.ts @@ -0,0 +1,117 @@ +import { AztecoVoucher } from '../class/azteco'; +import { LightningTransaction, Transaction, TWallet } from '../class/wallets/types'; +import { Chain } from '../models/bitcoinUnits'; +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; +}; + +export type DetailViewStackParamList = { + DrawerRoot: undefined; + UnlockWithScreen: undefined; + WalletsList: { onBarScanned?: string }; + WalletTransactions: { isLoading?: boolean; walletID: string; walletType: string; onBarScanned?: string }; + WalletDetails: { walletID: string }; + TransactionDetails: { tx: Transaction; hash: string; walletID: string }; + TransactionStatus: { hash: string; walletID?: string }; + 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; + PlausibleDeniability: undefined; + Licensing: undefined; + NetworkSettings: undefined; + About: undefined; + // DefaultView: undefined; // Commented out - not accessible from UI + ElectrumSettings: { server?: ElectrumServerItem; onBarScanned?: string }; + SettingsBlockExplorer: undefined; + EncryptStorage: undefined; + Language: undefined; + LightningSettings: { + url?: string; + onBarScanned?: string; + }; + NotificationSettings: undefined; + SelfTest: undefined; + ReleaseNotes: undefined; + SettingsTools: undefined; + ViewEditMultisigCosigners: { walletID: string; cosigners: string[]; onBarScanned?: string }; + WalletXpub: { walletID: string; xpub: string }; + SignVerifyRoot: { + screen: 'SignVerify'; + params: { + walletID: string; + address: string; + }; + }; + ReceiveDetails: { + walletID?: string; + address: string; + }; + ScanQRCode: ScanQRCodeParamList; + PaymentCodeList: { + paymentCode: string; + walletID: string; + }; + 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..12e35d268cb --- /dev/null +++ b/navigation/DrawerRoot.tsx @@ -0,0 +1,80 @@ +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, + drawerStatusBarAnimation: 'fade', + + ...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..cc84aaf16a2 --- /dev/null +++ b/navigation/ExportMultisigCoordinationSetupStack.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import navigationStyle from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { ExportMultisigCoordinationSetupComponent } from './LazyLoadExportMultisigCoordinationSetupStack'; + +export type ExportMultisigCoordinationSetupStackRootParamList = { + ExportMultisigCoordinationSetup: { + walletID: string; + }; +}; + +const Stack = createNativeStackNavigator(); + +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..6088e395e80 --- /dev/null +++ b/navigation/LNDCreateInvoiceStack.tsx @@ -0,0 +1,66 @@ +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import React from 'react'; +import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { + LNDCreateInvoiceComponent, + LNDViewAdditionalInvoicePreImageComponent, + LNDViewInvoiceComponent, + SelectWalletComponent, +} from './LazyLoadLNDCreateInvoiceStack'; +import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack'; + +const Stack = createNativeStackNavigator(); + +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/LazyLoadAddWalletStack.tsx b/navigation/LazyLoadAddWalletStack.tsx new file mode 100644 index 00000000000..b2980b1a88e --- /dev/null +++ b/navigation/LazyLoadAddWalletStack.tsx @@ -0,0 +1,89 @@ +import React, { lazy, Suspense } from 'react'; + +import { LazyLoadingIndicator } from './LazyLoadingIndicator'; + +// Define lazy imports with more reliable loading patterns +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')); + +export const AddComponent: React.FC = () => ( + }> + + +); + +export const ImportWalletDiscoveryComponent = () => ( + }> + + +); + +export const ImportCustomDerivationPathComponent = () => ( + }> + + +); + +export const ImportWalletComponent = () => ( + }> + + +); + +export const ImportSpeedComponent = () => ( + }> + + +); + +export const PleaseBackupComponent = () => ( + }> + + +); + +export const PleaseBackupLNDHubComponent = () => ( + }> + + +); + +export const ProvideEntropyComponent = () => ( + }> + + +); + +export const WalletsAddMultisigComponent = () => ( + }> + + +); + +export const MultisigAdvancedComponent = () => ( + }> + + +); + +export const WalletsAddMultisigStep2Component = () => ( + }> + + +); + +export const WalletsAddMultisigHelpComponent = () => ( + }> + + +); diff --git a/navigation/LazyLoadAztecoRedeemStack.tsx b/navigation/LazyLoadAztecoRedeemStack.tsx new file mode 100644 index 00000000000..16dbc39258b --- /dev/null +++ b/navigation/LazyLoadAztecoRedeemStack.tsx @@ -0,0 +1,18 @@ +import React, { lazy, Suspense } from 'react'; + +import { LazyLoadingIndicator } from './LazyLoadingIndicator'; + +const AztecoRedeem = lazy(() => import('../screen/receive/AztecoRedeem')); +const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet')); + +export const AztecoRedeemComponent = () => ( + }> + + +); + +export const SelectWalletComponent = () => ( + }> + + +); diff --git a/navigation/LazyLoadExportMultisigCoordinationSetupStack.tsx b/navigation/LazyLoadExportMultisigCoordinationSetupStack.tsx new file mode 100644 index 00000000000..9e7846fb1c2 --- /dev/null +++ b/navigation/LazyLoadExportMultisigCoordinationSetupStack.tsx @@ -0,0 +1,11 @@ +import React, { lazy, Suspense } from 'react'; + +import { LazyLoadingIndicator } from './LazyLoadingIndicator'; + +const ExportMultisigCoordinationSetup = lazy(() => import('../screen/wallets/ExportMultisigCoordinationSetup')); + +export const ExportMultisigCoordinationSetupComponent = () => ( + }> + + +); diff --git a/navigation/LazyLoadLNDCreateInvoiceStack.tsx b/navigation/LazyLoadLNDCreateInvoiceStack.tsx new file mode 100644 index 00000000000..16842356a3c --- /dev/null +++ b/navigation/LazyLoadLNDCreateInvoiceStack.tsx @@ -0,0 +1,32 @@ +import React, { lazy, Suspense } from 'react'; + +import { LazyLoadingIndicator } from './LazyLoadingIndicator'; + +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')); + +export const LNDCreateInvoiceComponent = () => ( + }> + + +); + +export const SelectWalletComponent = () => ( + }> + + +); + +export const LNDViewInvoiceComponent = () => ( + }> + + +); + +export const LNDViewAdditionalInvoicePreImageComponent = () => ( + }> + + +); diff --git a/navigation/LazyLoadPaymentCodeStack.tsx b/navigation/LazyLoadPaymentCodeStack.tsx new file mode 100644 index 00000000000..3a3c761d635 --- /dev/null +++ b/navigation/LazyLoadPaymentCodeStack.tsx @@ -0,0 +1,13 @@ +import React, { lazy, Suspense } from 'react'; + +import { LazyLoadingIndicator } from './LazyLoadingIndicator'; + +const PaymentCodesList = lazy(() => import('../screen/wallets/PaymentCodesList')); + +const PaymentCodesListComponent = () => ( + }> + + +); + +export default PaymentCodesListComponent; diff --git a/navigation/LazyLoadReceiveDetailsStack.tsx b/navigation/LazyLoadReceiveDetailsStack.tsx new file mode 100644 index 00000000000..fee899e6016 --- /dev/null +++ b/navigation/LazyLoadReceiveDetailsStack.tsx @@ -0,0 +1,11 @@ +import React, { lazy, Suspense } from 'react'; + +import { LazyLoadingIndicator } from './LazyLoadingIndicator'; + +const ReceiveDetails = lazy(() => import('../screen/receive/ReceiveDetails')); + +export const ReceiveDetailsComponent = () => ( + }> + + +); diff --git a/navigation/LazyLoadScanLNDInvoiceStack.tsx b/navigation/LazyLoadScanLNDInvoiceStack.tsx new file mode 100644 index 00000000000..6b9bc66ef48 --- /dev/null +++ b/navigation/LazyLoadScanLNDInvoiceStack.tsx @@ -0,0 +1,40 @@ +import React, { lazy, Suspense } from 'react'; + +import { LazyLoadingIndicator } from './LazyLoadingIndicator'; + +// Lazy loading components for the navigation stack +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')); + +export const ScanLNDInvoiceComponent = () => ( + }> + + +); + +export const SelectWalletComponent = () => ( + }> + + +); + +export const SuccessComponent = () => ( + }> + + +); + +export const LnurlPayComponent = () => ( + }> + + +); + +export const LnurlPaySuccessComponent = () => ( + }> + + +); diff --git a/navigation/LazyLoadScanQRCodeStack.tsx b/navigation/LazyLoadScanQRCodeStack.tsx new file mode 100644 index 00000000000..2db1fa743b8 --- /dev/null +++ b/navigation/LazyLoadScanQRCodeStack.tsx @@ -0,0 +1,11 @@ +import React, { lazy, Suspense } from 'react'; + +import { LazyLoadingIndicator } from './LazyLoadingIndicator'; + +const ScanQRCode = lazy(() => import('../screen/send/ScanQRCode')); + +export const ScanQRCodeComponent = () => ( + }> + + +); diff --git a/navigation/LazyLoadSendDetailsStack.tsx b/navigation/LazyLoadSendDetailsStack.tsx new file mode 100644 index 00000000000..f72975e68aa --- /dev/null +++ b/navigation/LazyLoadSendDetailsStack.tsx @@ -0,0 +1,67 @@ +import React, { lazy, Suspense } from 'react'; + +import { LazyLoadingIndicator } from './LazyLoadingIndicator'; + +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')); + +// Export each component with its lazy loader and optional configurations +export const SendDetailsComponent = () => ( + }> + + +); +export const ConfirmComponent = () => ( + }> + + +); +export const PsbtWithHardwareWalletComponent = () => ( + }> + + +); +export const CreateTransactionComponent = () => ( + }> + + +); +export const PsbtMultisigComponent = () => ( + }> + + +); +export const PsbtMultisigQRCodeComponent = () => ( + }> + + +); +export const SuccessComponent = () => ( + }> + + +); +export const SelectWalletComponent = () => ( + }> + + +); +export const CoinControlComponent = () => ( + }> + + +); + +export const PaymentCodesListComponent = () => ( + }> + + +); diff --git a/navigation/LazyLoadSignVerifyStack.tsx b/navigation/LazyLoadSignVerifyStack.tsx new file mode 100644 index 00000000000..50b74a43a17 --- /dev/null +++ b/navigation/LazyLoadSignVerifyStack.tsx @@ -0,0 +1,11 @@ +import React, { lazy, Suspense } from 'react'; + +import { LazyLoadingIndicator } from './LazyLoadingIndicator'; + +const SignVerify = lazy(() => import('../screen/wallets/signVerify')); + +export const SignVerifyComponent = () => ( + }> + + +); diff --git a/navigation/LazyLoadWalletExportStack.tsx b/navigation/LazyLoadWalletExportStack.tsx new file mode 100644 index 00000000000..a71ca8bfa33 --- /dev/null +++ b/navigation/LazyLoadWalletExportStack.tsx @@ -0,0 +1,12 @@ +import React, { lazy, Suspense } from 'react'; + +import { LazyLoadingIndicator } from './LazyLoadingIndicator'; + +// Define lazy imports +const WalletExport = lazy(() => import('../screen/wallets/WalletExport')); + +export const WalletExportComponent = () => ( + }> + + +); diff --git a/navigation/LazyLoadingIndicator.tsx b/navigation/LazyLoadingIndicator.tsx new file mode 100644 index 00000000000..177df8631ee --- /dev/null +++ b/navigation/LazyLoadingIndicator.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { ActivityIndicator, StyleSheet, View } from 'react-native'; + +export const LazyLoadingIndicator = () => ( + + + +); + +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..d55d55f35f3 --- /dev/null +++ b/navigation/PaymentCodeStack.tsx @@ -0,0 +1,24 @@ +import React 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 PaymentCodesListComponent from './LazyLoadPaymentCodeStack'; + +const Stack = createNativeStackNavigator(); +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/ReceiveDetailsStack.tsx b/navigation/ReceiveDetailsStack.tsx new file mode 100644 index 00000000000..d3be20d9107 --- /dev/null +++ b/navigation/ReceiveDetailsStack.tsx @@ -0,0 +1,30 @@ +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'; + +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..2c14e84b526 --- /dev/null +++ b/navigation/ReceiveDetailsStackParamList.ts @@ -0,0 +1,6 @@ +export type ReceiveDetailsStackParamList = { + ReceiveDetails: { + walletID?: string; + address?: string; + }; +}; diff --git a/navigation/ScanLNDInvoiceStack.tsx b/navigation/ScanLNDInvoiceStack.tsx new file mode 100644 index 00000000000..921cdce7049 --- /dev/null +++ b/navigation/ScanLNDInvoiceStack.tsx @@ -0,0 +1,63 @@ +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import React from 'react'; + +import navigationStyle from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack'; +import { LnurlPayComponent, LnurlPaySuccessComponent, ScanLNDInvoiceComponent, SuccessComponent } from './LazyLoadScanLNDInvoiceStack'; +import { SelectWalletComponent } from './LazyLoadLNDCreateInvoiceStack'; + +const Stack = createNativeStackNavigator(); + +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..ef785c29d02 --- /dev/null +++ b/navigation/SendDetailsStack.tsx @@ -0,0 +1,111 @@ +import React, { useMemo } 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 { + CoinControlComponent, + ConfirmComponent, + CreateTransactionComponent, + PaymentCodesListComponent, + PsbtMultisigComponent, + PsbtMultisigQRCodeComponent, + PsbtWithHardwareWalletComponent, + SelectWalletComponent, + SendDetailsComponent, + SuccessComponent, +} from './LazyLoadSendDetailsStack'; +import { SendDetailsStackParamList } from './SendDetailsStackParamList'; +import HeaderRightButton from '../components/HeaderRightButton'; +import { BitcoinUnit } from '../models/bitcoinUnits'; +import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack'; +import SelectFeeScreen from '../screen/SelectFeeScreen'; +import { Platform } from 'react-native'; + +const Stack = createNativeStackNavigator(); + +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..907474ec78c --- /dev/null +++ b/navigation/SendDetailsStackParamList.ts @@ -0,0 +1,127 @@ +import { Psbt } from 'bitcoinjs-lib'; +import { CreateTransactionTarget, CreateTransactionUtxo, TWallet } 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; + 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; + fromWallet: 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..df2d878f3e2 --- /dev/null +++ b/navigation/SignVerifyStack.tsx @@ -0,0 +1,25 @@ +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import React from 'react'; + +import navigationStyle from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { SignVerifyComponent } from './LazyLoadSignVerifyStack'; + +const Stack = createNativeStackNavigator(); + +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..c6cac720a26 --- /dev/null +++ b/navigation/WalletExportStack.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +import navigationStyle from '../components/navigationStyle'; +import { useTheme } from '../components/themes'; +import loc from '../loc'; +import { WalletExportComponent } from './LazyLoadWalletExportStack'; + +export type WalletExportStackParamList = { + WalletExport: { walletID: string }; +}; + +const Stack = createNativeStackNavigator(); + +const WalletExportStack = () => { + const theme = useTheme(); + + return ( + + + + ); +}; + +export default WalletExportStack; diff --git a/navigation/helpers/getTransactionStatusOptions.tsx b/navigation/helpers/getTransactionStatusOptions.tsx new file mode 100644 index 00000000000..83f6d98f16e --- /dev/null +++ b/navigation/helpers/getTransactionStatusOptions.tsx @@ -0,0 +1,45 @@ +import { RouteProp } from '@react-navigation/native'; +import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; +import HeaderRightButton from '../../components/HeaderRightButton'; +import loc from '../../loc'; +import { DetailViewStackParamList } from '../DetailViewStackParamList'; +import navigationStyle from '../../components/navigationStyle'; +import { Theme } from '../../components/themes'; +import React from 'react'; + +type TransactionStatusRouteProp = RouteProp; + +interface GetTransactionStatusOptionsParams { + route: TransactionStatusRouteProp; + navigation: any; + theme: Theme; +} + +const getTransactionStatusOptions = ({ route, navigation, theme }: GetTransactionStatusOptionsParams): NativeStackNavigationOptions => { + const { hash, walletID } = route.params; + + const navigateToTransactionDetails = () => { + navigation.navigate('TransactionDetails', { hash, walletID }); + }; + + return { + ...navigationStyle({ + title: '', + headerStyle: { + backgroundColor: theme.colors.customHeader, + }, + headerBackTitleStyle: { fontSize: 0 }, + statusBarStyle: 'auto', + })(theme), + headerRight: () => ( + + ), + }; +}; + +export default getTransactionStatusOptions; diff --git a/navigation/helpers/getWalletTransactionsOptions.tsx b/navigation/helpers/getWalletTransactionsOptions.tsx new file mode 100644 index 00000000000..ac373fe3d53 --- /dev/null +++ b/navigation/helpers/getWalletTransactionsOptions.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { TouchableOpacity, StyleSheet } from 'react-native'; +import { Icon } from '@rneui/themed'; +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, + }, + headerShadowVisible: false, + headerTintColor: '#FFFFFF', + statusBarBackgroundColor: backgroundColor, + 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..f1635f2cb9c --- /dev/null +++ b/navigation/index.tsx @@ -0,0 +1,185 @@ +import { createNativeStackNavigator, NativeStackNavigationOptions } from '@react-navigation/native-stack'; +import React, { lazy, Suspense } from 'react'; +import UnlockWith from '../screen/UnlockWith'; +import { LazyLoadingIndicator } 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'; + +// 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', +}; + +export const NavigationFormNoSwipeDefaultOptions: NativeStackNavigationOptions = { + headerShown: false, + presentation: 'modal', + headerShadowVisible: false, + fullScreenGestureEnabled: false, +}; +export const StatusBarLightOptions: NativeStackNavigationOptions = { statusBarStyle: 'light' }; + +const DetailViewStack = createNativeStackNavigator(); + +// Lazy loading wrapper components +const LazyDrawerRoot = () => ( + }> + + +); + +const LazyAddWalletStack = () => ( + }> + + +); + +const LazySendDetailsStack = () => ( + }> + + +); + +const LazyLNDCreateInvoiceRoot = () => ( + }> + + +); + +const LazyScanLNDInvoiceRoot = () => ( + }> + + +); + +const LazyAztecoRedeemStackRoot = () => ( + }> + + +); + +const LazyExportMultisigCoordinationSetupStack = () => ( + }> + + +); + +const LazyViewEditMultisigCosigners = () => ( + }> + + +); + +const LazySignVerifyStackRoot = () => ( + }> + + +); + +const LazyScanQRCodeComponent = () => ( + }> + + +); + +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..85ad26d90b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,218 +1,277 @@ { "name": "bluewallet", - "version": "6.4.9", - "lockfileVersion": 2, + "version": "7.2.4", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bluewallet", - "version": "6.4.9", + "version": "7.2.4", "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.16", + "@arkade-os/sdk": "0.3.10", + "@babel/preset-env": "7.27.2", + "@bugsnag/react-native": "8.4.0", + "@bugsnag/source-maps": "2.3.3", + "@keystonehq/bc-ur-registry": "0.7.1", + "@lodev09/react-native-true-sheet": "github:BlueWallet/react-native-true-sheet#5945184a2fea9fe5ba8f5cfcdb20e3fc5eed6e37", + "@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-async-storage/async-storage": "2.2.0", + "@react-native-clipboard/clipboard": "1.16.3", + "@react-native-community/cli": "15.1.3", + "@react-native-community/cli-platform-android": "15.1.3", + "@react-native-community/cli-platform-ios": "15.1.3", "@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-documents/picker": "10.1.7", + "@react-native-menu/menu": "github:BlueWallet/menu#9933468", + "@react-native/gradle-plugin": "0.78.2", + "@react-native/metro-config": "0.78.2", + "@react-navigation/devtools": "7.0.24", + "@react-navigation/drawer": "7.3.7", + "@react-navigation/native": "7.1.4", + "@react-navigation/native-stack": "7.3.8", + "@rneui/base": "4.0.0-rc.8", + "@rneui/themed": "4.0.0-rc.8", + "@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", + "bip32": "5.0.0", "bip38": "github:BlueWallet/bip38", "bip39": "3.1.0", - "bitcoinjs-lib": "6.1.1", + "bitcoinjs-lib": "7.0.0", "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.19", + "detox": "20.43.0", + "ecpair": "3.0.0", + "electrum-client": "github:BlueWallet/rn-electrum-client#d9f511d", "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.4", + "pako": "file:blue_modules/pako", "payjoin-client": "1.0.1", - "process": "0.11.10", "prop-types": "15.8.1", - "react": "18.2.0", + "react": "19.0.0", "react-localization": "github:BlueWallet/react-localization#ae7969a", - "react-native": "0.71.11", + "react-native": "0.78.2", + "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": "16.2.0", + "react-native-capture-protection": "github:BlueWallet/react-native-capture-protection#d299992", + "react-native-default-preference": "https://github.com/BlueWallet/react-native-default-preference.git#6338a1f1235e4130b8cfc2dd3b53015eeff2870c", + "react-native-device-info": "14.1.1", + "react-native-draglist": "github:BlueWallet/react-native-draglist#0c8049d", "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-gesture-handler": "2.25.0", + "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.3", + "react-native-image-picker": "8.2.1", + "react-native-keychain": "9.1.0", + "react-native-linear-gradient": "2.8.3", + "react-native-localize": "3.5.4", + "react-native-permissions": "5.4.4", + "react-native-prompt-android": "github:BlueWallet/react-native-prompt-android#ed168d66fed556bc2ed07cf498770f058b78a376", "react-native-push-notification": "8.1.1", - "react-native-qrcode-svg": "6.2.0", + "react-native-qrcode-svg": "6.3.21", "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": "3.18.0", + "react-native-safe-area-context": "5.5.2", + "react-native-screens": "4.16.0", + "react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc", + "react-native-share": "12.1.0", + "react-native-svg": "15.12.1", + "react-native-tcp-socket": "6.3.0", + "react-native-vector-icons": "10.2.0", "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-test-renderer": "19.0.0", "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.1.0", + "rn-qr-generator": "https://github.com/BlueWallet/rn-qr-generator.git#731ed8eb445f65f3a659632232e18ff7e1ce56d6", + "silent-payments": "github:BlueWallet/SilentPayments#59a037", + "slip39": "https://github.com/BlueWallet/slip39-js#d316ee6", "stream-browserify": "3.0.0", - "url": "0.11.1", + "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/babel-preset": "0.78.2", + "@react-native/eslint-config": "^0.78.3", + "@react-native/js-polyfills": "^0.78.3", + "@react-native/metro-babel-transformer": "^0.78.3", + "@react-native/typescript-config": "0.78.2", + "@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/crypto-js": "^4.2.2", + "@types/jest": "^29.5.13", "@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/react-native-push-notification": "^8.1.4", + "@types/react-test-renderer": "^19.0.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", + "metro-react-native-babel-preset": "0.76.8", "node-fetch": "^2.6.7", - "prettier": "^3.0.0", - "react-test-renderer": "18.2.0", + "prettier": "^3.2.5", "ts-jest": "^29.1.1", - "typescript": "^5.1.6" + "typescript": "^5.9.3" }, "engines": { - "node": ">=10.16.0", - "npm": ">=6.9.0" + "node": ">=20" } }, "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.16", + "resolved": "https://registry.npmjs.org/@arkade-os/boltz-swap/-/boltz-swap-0.2.16.tgz", + "integrity": "sha512-42vIGlDCvJry1UOifi8jKxfcD3jBjf5Ldfwjs+bz5jfjUZGflCBS2cqZxujtB3vlbANTNUIwP9qI0maE2cFV2A==", + "license": "MIT", + "dependencies": { + "@arkade-os/sdk": "0.3.10", + "@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", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@arkade-os/sdk": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@arkade-os/sdk/-/sdk-0.3.10.tgz", + "integrity": "sha512-3O/ftt5h9equtbMuzBmWEbw2M8AeDrokoLSMQDvoQF4Lz9y8A0azZyVZjLZJlvNhQFL2ZVH+Y8urSI8YoHpJWg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@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", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.0.0.tgz", + "integrity": "sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/helper-validator-identifier": "^7.27.1", + "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.27.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", + "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", + "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 +282,79 @@ } }, "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.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "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.27.1", + "@babel/types": "^7.27.1", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "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.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", + "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", + "license": "MIT", "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", + "@babel/compat-data": "^7.27.2", + "@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.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "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.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -323,11 +364,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.21.0", - "integrity": "sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.3.1" + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -337,120 +381,103 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "version": "0.6.4", + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" + "resolve": "^1.14.2" }, "peerDependencies": { - "@babel/core": "^7.4.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.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" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.24.7", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.21.0", - "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.21.0" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "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.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "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-module-transforms": { - "version": "7.21.2", - "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", + "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.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "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", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "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.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "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", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "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 +487,95 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.20.7", - "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "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.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "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", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "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", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "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.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "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", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "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", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "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.27.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz", + "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/types": "^7.27.1" }, - "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 +583,15 @@ "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.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -585,100 +600,82 @@ "@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==", - "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" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "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" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.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-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "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.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-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "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" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.12.0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "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.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.0.0-0" + "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.18.10", - "integrity": "sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow==", + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", + "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-default-from": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^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-export-namespace-from": { - "version": "7.18.9", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@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" }, "engines": { "node": ">=6.9.0" @@ -687,12 +684,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-json-strings": { + "node_modules/@babel/plugin-proposal-class-properties": { "version": "7.18.6", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -701,12 +699,11 @@ "@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-proposal-export-default-from": { + "version": "7.25.9", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -717,7 +714,8 @@ }, "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { "version": "7.18.6", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -731,7 +729,8 @@ }, "node_modules/@babel/plugin-proposal-numeric-separator": { "version": "7.18.6", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -745,7 +744,8 @@ }, "node_modules/@babel/plugin-proposal-object-rest-spread": { "version": "7.20.7", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.20.5", "@babel/helper-compilation-targets": "^7.20.7", @@ -762,7 +762,8 @@ }, "node_modules/@babel/plugin-proposal-optional-catch-binding": { "version": "7.18.6", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" @@ -776,7 +777,8 @@ }, "node_modules/@babel/plugin-proposal-optional-chaining": { "version": "7.21.0", - "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", @@ -789,13 +791,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, + "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" }, @@ -803,41 +802,11 @@ "@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-async-generators": { + "version": "7.8.4", + "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" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" @@ -845,8 +814,7 @@ }, "node_modules/@babel/plugin-syntax-bigint": { "version": "7.8.3", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -856,7 +824,7 @@ }, "node_modules/@babel/plugin-syntax-class-properties": { "version": "7.12.13", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -866,7 +834,7 @@ }, "node_modules/@babel/plugin-syntax-class-static-block": { "version": "7.14.5", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -879,7 +847,7 @@ }, "node_modules/@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -888,10 +856,10 @@ } }, "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.18.6", - "integrity": "sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew==", + "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" @@ -900,21 +868,27 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.26.0", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.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-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -923,11 +897,13 @@ "@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-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -938,8 +914,7 @@ }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -949,7 +924,7 @@ }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -958,10 +933,10 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "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" @@ -972,7 +947,7 @@ }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -982,7 +957,7 @@ }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -992,7 +967,7 @@ }, "node_modules/@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1002,7 +977,7 @@ }, "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1012,7 +987,7 @@ }, "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1022,7 +997,7 @@ }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1032,7 +1007,7 @@ }, "node_modules/@babel/plugin-syntax-private-property-in-object": { "version": "7.14.5", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1045,7 +1020,7 @@ }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1057,10 +1032,10 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "version": "7.25.9", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1069,26 +1044,28 @@ "@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-syntax-unicode-sets-regex": { + "version": "7.18.6", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@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-transform-async-to-generator": { - "version": "7.20.7", - "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "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-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1097,11 +1074,15 @@ "@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-async-generator-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", + "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1110,11 +1091,15 @@ "@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-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1123,19 +1108,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.21.0", - "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "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-block-scoping": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.1.tgz", + "integrity": "sha512-QEcFlMl9nGTgh1rn2nIeU5bkfb9BAjaQcWbiP4LvKxUot52ABcTkpcyJ7f2Q2U2RuQ84BNLgts3jRme2dTx6Fw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/template": "^7.20.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1158,11 +1139,14 @@ "@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-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1171,25 +1155,35 @@ "@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-class-static-block": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", + "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "node_modules/@babel/plugin-transform-classes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", + "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "globals": "^11.1.0" }, "engines": { "node": ">=6.9.0" @@ -1198,12 +1192,14 @@ "@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-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "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/template": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1212,12 +1208,13 @@ "@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-destructuring": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.1.tgz", + "integrity": "sha512-ttDCqhfvpE9emVkXbPD8vyxxh4TWYACVybGkDj+oReOGwnp066ITEivDlLwe0b1R0+evJ13IXQuLNB5w1fhC5Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-flow": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1226,11 +1223,15 @@ "@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-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1239,13 +1240,14 @@ "@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-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "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,24 +1256,31 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@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-transform-member-expression-literals": { - "version": "7.18.6", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1280,12 +1289,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.20.11", - "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1294,13 +1305,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.21.2", - "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==", + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "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-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1309,14 +1321,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.20.11", - "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.26.5", + "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-plugin-utils": "^7.26.5", + "@babel/plugin-syntax-flow": "^7.26.0" }, "engines": { "node": ">=6.9.0" @@ -1325,12 +1335,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^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" @@ -1339,25 +1351,31 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.20.5", - "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "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 +1384,13 @@ "@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-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1379,12 +1399,13 @@ "@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-logical-assignment-operators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1393,11 +1414,14 @@ "@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-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1406,11 +1430,15 @@ "@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-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", "dependencies": { - "@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" @@ -1419,11 +1447,14 @@ "@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-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "license": "MIT", "dependencies": { - "@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" @@ -1432,15 +1463,17 @@ "@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-modules-systemjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "dev": true, + "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-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1449,11 +1482,15 @@ "@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-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", "dependencies": { - "@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" @@ -1462,25 +1499,30 @@ "@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-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@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-transform-regenerator": { - "version": "7.20.5", - "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "regenerator-transform": "^0.15.1" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1489,11 +1531,13 @@ "@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-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1502,16 +1546,13 @@ "@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-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "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.27.1" }, "engines": { "node": ">=6.9.0" @@ -1520,11 +1561,16 @@ "@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-object-rest-spread": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.2.tgz", + "integrity": "sha512-AIUHD7xJ1mCrj3uPozvtngY3s0xpv7Nu7DoUSnzNY6Xam1Cy4rUznR//pvMHOhQ4AvbCexhbqXCtpxGHOGOO6g==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1533,12 +1579,15 @@ "@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-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1547,11 +1596,13 @@ "@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-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1560,11 +1611,14 @@ "@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-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1573,11 +1627,13 @@ "@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-parameters": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", + "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1586,13 +1642,14 @@ "@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-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "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-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1601,11 +1658,15 @@ "@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-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1614,12 +1675,14 @@ "@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-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "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" @@ -1628,85 +1691,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env": { - "version": "7.20.2", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.25.9", + "license": "MIT", "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" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1715,13 +1704,15 @@ "@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-react-jsx": { + "version": "7.25.9", + "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-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" @@ -1730,27 +1721,24 @@ "@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-react-jsx-self": { + "version": "7.25.9", + "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.25.9" + }, + "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-react-jsx-source": { + "version": "7.25.9", + "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-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1759,15 +1747,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/register": { - "version": "7.21.0", - "integrity": "sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw==", + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.1.tgz", + "integrity": "sha512-B19lbbL7PMrKr52BNPjCqg1IyNUIjTcxKj8uX9zHO+PmWN93s19NDr/f69mIkEp2x9nmDJ08a7lgHaTTzvW7mw==", + "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,1049 +1762,1101 @@ "@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-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/pify": { - "version": "4.0.1", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@babel/register/node_modules/source-map-support": { - "version": "0.5.21", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" - }, - "node_modules/@babel/runtime": { - "version": "7.21.0", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/template": { - "version": "7.20.7", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.26.10", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/traverse": { - "version": "7.21.2", - "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==", + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "license": "MIT", "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" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/types": { - "version": "7.21.2", - "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "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-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@bugsnag/core": { - "version": "7.19.0", - "integrity": "sha512-2KGwdaLD9PhR7Wk7xPi3jGuGsKTatc/28U4TOZIDU3CgC2QhGjubwiXSECel5gwxhZ3jACKcMKSV2ovHhv1NrA==", - "dependencies": { - "@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" - } - }, - "node_modules/@bugsnag/cuid": { - "version": "3.0.2", - "integrity": "sha512-cIwzC93r3PQ/INeuwtZwkZIG2K8WWN0rRLZQhu+mr48Ay+i6sEki4GYfTsflse7hZ1BeDWrNb/Q9vgY3B31xHQ==" - }, - "node_modules/@bugsnag/delivery-react-native": { - "version": "7.19.0", - "integrity": "sha512-Zzl3VOwLDU4KHmf3VweyfNeJcQgL0NzbWG+OCxjCYen093Q4sxNTpWAVBCrYPRjQ2Sq3+D3+YbQg5UUrHL7kig==", - "peerDependencies": { - "@bugsnag/core": "^7.0.0" - } - }, - "node_modules/@bugsnag/plugin-console-breadcrumbs": { - "version": "7.19.0", - "integrity": "sha512-ZHqPAK0WpbvWjj2wwSV8+C8+K9TOyQsfZnRJ7lIadbeUUJORmFRnG0vUHKBvwxMP7bqCj8fOe/S0kKF3dfMMKA==", - "peerDependencies": { - "@bugsnag/core": "^7.0.0" - } - }, - "node_modules/@bugsnag/plugin-network-breadcrumbs": { - "version": "7.19.0", - "integrity": "sha512-Farc0XuUoxv10kJE65zfgZlqujR7TDu8QjwxA4YDxEE41kFM8TAw0CAK15WkQK1UTsNACiiAETZGyU279eB65Q==", - "peerDependencies": { - "@bugsnag/core": "^7.0.0" - } - }, - "node_modules/@bugsnag/plugin-react": { - "version": "7.19.0", - "integrity": "sha512-owC4QXYJWGllMoOPcH5P7sbDIDuFLMCbjGAU6FwH5mBMObSQo+1ViSKImlTJQUFXATM8ySISTBVt7w3C6FFHng==", - "peerDependencies": { - "@bugsnag/core": "^7.0.0" }, - "peerDependenciesMeta": { - "@bugsnag/core": { - "optional": true - } - } - }, - "node_modules/@bugsnag/plugin-react-native-client-sync": { - "version": "7.19.0", - "integrity": "sha512-WyK5pZuIzqVrY0h0HimwuODCo9ty9AyDY3q1pmwjrz2y8JTT21nnwUtHybLsp5Rl2oJR4tG06QkWmazgHDkWdA==", "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bugsnag/plugin-react-native-event-sync": { - "version": "7.19.0", - "integrity": "sha512-OD73WFkDJAq8AheN2Jap+d17M1mPbEBc1Aulz9FCLs//QwlM2IOij8oarB1iF/wgK6FnIgLFEBPTZpGHuZUsyQ==", + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bugsnag/plugin-react-native-global-error-handler": { - "version": "7.19.0", - "integrity": "sha512-zf+KIHqGEAs2ekAzJCTS0rM1nKrmpIfznBhn72xZJwyfYrh0wbvjZjClDEwxDZ24uNVUUHrIymzdqxpHqVb0lg==", + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bugsnag/plugin-react-native-hermes": { - "version": "7.19.0", - "integrity": "sha512-6SGTSR6NMS2t8j02ZQ6FlA+K/nKkZqvGA+8A7WS/0M8HAShzyoMpZH10kGrU2dcCaiEtmD2T6OGBSbpF+385Dg==", + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bugsnag/plugin-react-native-session": { - "version": "7.19.0", - "integrity": "sha512-PVwsUstedp9wTqJU/IKdCaMFKP2YrqHXoeBtqRTQ7FFyr0K8wsiW7nZP2jM31VS388hZWSWBlHQPA/3LZ49tNQ==", + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.27.0", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.27.0", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bugsnag/plugin-react-native-unhandled-rejection": { - "version": "7.19.0", - "integrity": "sha512-+XDk0OoeM6MZhBh7kEalbRwFuhCZST6Y1jOostfz0fhrmT4FdgQYi1FWcPNsUTcjqv7M48pOFZNx8yWI0lGaYg==", + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { - "@bugsnag/core": "^7.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", - "iserror": "^0.0.2" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bugsnag/safe-json-stringify": { - "version": "6.0.0", - "integrity": "sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA==" - }, - "node_modules/@bugsnag/source-maps": { - "version": "2.3.1", - "integrity": "sha512-9xJTcf5+W7+y1fQBftSOste/3ORi+d5EeCCMdvaHSX69MKQP0lrDiSYpLwX/ErcXrTbvu7nimGGKJP2vBdF7zQ==", + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, + "license": "MIT", "dependencies": { - "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" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, - "bin": { - "bugsnag-source-maps": "bin/cli" + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@dominicstop/ts-event-emitter": { - "version": "1.1.0", - "integrity": "sha512-CcxmJIvUb1vsFheuGGVSQf4KdPZC44XolpUT34+vlal+LyQoBUOn31pjFET5M9ctOxEpt8xa0M3/2M7uUiAoJw==" - }, - "node_modules/@egjs/hammerjs": { - "version": "2.0.17", - "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "license": "MIT", "dependencies": { - "@types/hammerjs": "^2.0.36" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=0.8.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6.9.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@babel/core": "^7.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==", + "node_modules/@babel/preset-env": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz", + "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@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-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.27.1", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.27.1", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.1", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@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.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.27.2", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.1", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.27.1", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@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.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.40.0", + "semver": "^6.3.1" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6.9.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", - "dev": true, + "node_modules/@babel/preset-flow": { + "version": "7.25.9", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-flow-strip-types": "^7.25.9" + }, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.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==", + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", "dev": true, + "license": "MIT", "dependencies": { - "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" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, - "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, + "node_modules/@babel/preset-typescript": { + "version": "7.27.0", + "license": "MIT", "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" + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-typescript": "^7.27.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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 - }, - "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==", - "dev": true, + "node_modules/@babel/register": { + "version": "7.25.9", + "license": "MIT", "dependencies": { - "type-fest": "^0.20.2" + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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, + "node_modules/@babel/register/node_modules/make-dir": { + "version": "2.1.0", + "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "pify": "^4.0.1", + "semver": "^5.6.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "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 - }, - "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, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "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==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node_modules/@babel/register/node_modules/semver": { + "version": "5.7.2", + "license": "ISC", + "bin": { + "semver": "bin/semver" } }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "node_modules/@babel/register/node_modules/source-map-support": { + "version": "0.5.21", + "license": "MIT", "dependencies": { - "@hapi/hoek": "^9.0.0" + "buffer-from": "^1.0.0", + "source-map": "^0.6.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==", - "dev": true, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "license": "MIT", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" + "regenerator-runtime": "^0.14.0" }, "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=6.9.0" } }, - "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 - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, + "node_modules/@babel/template": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz", + "integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==", + "license": "MIT", "dependencies": { - "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" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" + "node": ">=6.9.0" } }, - "node_modules/@jest/console": { - "version": "27.5.1", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", - "dev": true, + "node_modules/@babel/traverse": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "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" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", + "debug": "^4.3.1", + "globals": "^11.1.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=6.9.0" } }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/@babel/traverse--for-generate-function-map": { + "name": "@babel/traverse", + "version": "7.27.0", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=6.9.0" } }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=6.9.0" } }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", "dev": true, + "license": "MIT" + }, + "node_modules/@bugsnag/core": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-8.4.0.tgz", + "integrity": "sha512-vmGNO5gQ2qP5CE6/RzIzO2X/5oJCQGrhdUc6OwpEf4CytPidpk9LNCkscvOx9iT9Ym3USKSo7DeZhtnhFP0KKQ==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@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" } }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/@bugsnag/cuid": { + "version": "3.2.1", + "license": "MIT" }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "node_modules/@bugsnag/delivery-react-native": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@bugsnag/delivery-react-native/-/delivery-react-native-8.4.0.tgz", + "integrity": "sha512-yxI6xvyXi5Bd30kD2XSekHpu7o20vbouSdHnPSF5nhwkbIyLv+jWZOqTvJ9p3ec1qra3WRveTFNAJAIsiKoHEg==", + "license": "MIT", + "peerDependencies": { + "@bugsnag/core": "^8.0.0" } }, - "node_modules/@jest/console/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/@bugsnag/plugin-console-breadcrumbs": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@bugsnag/plugin-console-breadcrumbs/-/plugin-console-breadcrumbs-8.4.0.tgz", + "integrity": "sha512-FPkUgnXqiWpGDgeVSO+mAix8pGWrV0R4pwdbC4EKasJwd1iSHV0yd11hLJwy5o+5qnIBYbKXnQpVgwFemA9I0w==", + "license": "MIT", + "peerDependencies": { + "@bugsnag/core": "^8.0.0" } }, - "node_modules/@jest/core": { - "version": "29.4.3", - "integrity": "sha512-56QvBq60fS4SPZCuM7T+7scNrkGIe7Mr6PVIXUpu48ouvRaWOFqRPV91eifvFM0ay2HmfswXiGf97NGUN5KofQ==", - "dev": true, - "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", - "@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" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, + "node_modules/@bugsnag/plugin-network-breadcrumbs": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@bugsnag/plugin-network-breadcrumbs/-/plugin-network-breadcrumbs-8.4.0.tgz", + "integrity": "sha512-LlGtKRZEMdM0QfzJkxTutKU+g/c5Vcy41Dg+ZoRhaMovdcIfU4EXq8gHeHDPNSEBfZHTwK/UFxvAPs9+o8+t3g==", + "license": "MIT", "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "@bugsnag/core": "^8.0.0" + } + }, + "node_modules/@bugsnag/plugin-react": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@bugsnag/plugin-react/-/plugin-react-8.4.0.tgz", + "integrity": "sha512-a+x3SmVE6VQ7a1jsEEU4q68L35rFy9OiDbWHAnZDZRcGXJzhA3ZU2XtB6ep5RCglwfPG0wLmq2dTjQBolSVi+g==", + "license": "MIT", + "peerDependencies": { + "@bugsnag/core": "^8.0.0" }, "peerDependenciesMeta": { - "node-notifier": { + "@bugsnag/core": { "optional": true } } }, - "node_modules/@jest/core/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" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node_modules/@bugsnag/plugin-react-native-client-sync": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-client-sync/-/plugin-react-native-client-sync-8.4.0.tgz", + "integrity": "sha512-vd68hz8QmZqdUZKGj8tXRdwOnMqRmFCdva+4x00LN5+35+S4oR6X8HfRoYBMNVYZMmDkihgy36rGYVjmnBTGkw==", + "license": "MIT", + "peerDependencies": { + "@bugsnag/core": "^8.0.0" } }, - "node_modules/@jest/core/node_modules/@jest/reporters": { - "version": "29.4.3", - "integrity": "sha512-sr2I7BmOjJhyqj9ANC6CTLsL4emMoka7HkQpcoMRlhCbQJjz2zsRzw0BDPiPyEFDXAbxKgGFYuQZiSJ1Y6YoTg==", - "dev": true, - "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", - "@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" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, + "node_modules/@bugsnag/plugin-react-native-event-sync": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-event-sync/-/plugin-react-native-event-sync-8.4.0.tgz", + "integrity": "sha512-JFCEIf+CXAfYCRizzXkkOKSpjdVYbq96IHq8R/W0f4b0A1gN3T99szQaml/vdAEgXFsFcRsmLG5MAuIGhCdVjA==", + "license": "MIT", "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "@bugsnag/core": "^8.0.0" } }, - "node_modules/@jest/core/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", - "dev": true, + "node_modules/@bugsnag/plugin-react-native-global-error-handler": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-global-error-handler/-/plugin-react-native-global-error-handler-8.4.0.tgz", + "integrity": "sha512-tbh52zsRjHgPhzgB8FsLhCTuZc9PGQQUXIjnqOlPS43bzS4IqxWRlwUNl+asjXYJ542xsmpmxTDQc71/AlWrsw==", + "license": "MIT", + "peerDependencies": { + "@bugsnag/core": "^8.0.0" + } + }, + "node_modules/@bugsnag/plugin-react-native-hermes": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-hermes/-/plugin-react-native-hermes-8.4.0.tgz", + "integrity": "sha512-Ii89j4bBFuIMYS/spVzdyXLUzLJTYdEF4nZ2oyJFGff+h5R70fC/gQNA349JfP+ADM4x4xGqt9keADYAy6yeEA==", + "license": "MIT", + "peerDependencies": { + "@bugsnag/core": "^8.0.0" + } + }, + "node_modules/@bugsnag/plugin-react-native-session": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-session/-/plugin-react-native-session-8.4.0.tgz", + "integrity": "sha512-aqwH5IY11f5DdzFDJVZ5egMrPpfpPvaad4ocXTGjMqUKyXjGMzs+CIN6IORw0ndEsq8IFXfHUOrukmRS2tNazQ==", + "license": "MIT", + "peerDependencies": { + "@bugsnag/core": "^8.0.0" + } + }, + "node_modules/@bugsnag/plugin-react-native-unhandled-rejection": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-unhandled-rejection/-/plugin-react-native-unhandled-rejection-8.4.0.tgz", + "integrity": "sha512-vD6O+Dv3qBYlzt9bIxut1YBUh96kq7l2hAIctL/060EUihDFHeRtCp3I34QIAnJNgYbm37jZJAMJTItOjTn/xA==", + "license": "MIT", + "peerDependencies": { + "@bugsnag/core": "^8.0.0" + } + }, + "node_modules/@bugsnag/react-native": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@bugsnag/react-native/-/react-native-8.4.0.tgz", + "integrity": "sha512-kYTBDqRC8nMzg/54govZiDqh4uIMDW7e9iUxnMbVR91oES5NKS4NM161/ecJIh7UaUdzWf+MzDsZTJPEE5x9Xw==", + "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" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@bugsnag/core": "^8.4.0", + "@bugsnag/delivery-react-native": "^8.4.0", + "@bugsnag/plugin-console-breadcrumbs": "^8.4.0", + "@bugsnag/plugin-network-breadcrumbs": "^8.4.0", + "@bugsnag/plugin-react": "^8.4.0", + "@bugsnag/plugin-react-native-client-sync": "^8.4.0", + "@bugsnag/plugin-react-native-event-sync": "^8.4.0", + "@bugsnag/plugin-react-native-global-error-handler": "^8.4.0", + "@bugsnag/plugin-react-native-hermes": "^8.4.0", + "@bugsnag/plugin-react-native-session": "^8.4.0", + "@bugsnag/plugin-react-native-unhandled-rejection": "^8.4.0", + "iserror": "^0.0.2" } }, - "node_modules/@jest/core/node_modules/@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "dev": true, + "node_modules/@bugsnag/safe-json-stringify": { + "version": "6.0.0", + "license": "MIT" + }, + "node_modules/@bugsnag/source-maps": { + "version": "2.3.3", + "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" + "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" }, + "bin": { + "bugsnag-source-maps": "bin/cli" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.1.90" } }, - "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/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "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" + "@types/hammerjs": "^2.0.36" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.8.0" } }, - "node_modules/@jest/core/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.5.1", "dev": true, + "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "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/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jest/core/node_modules/anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, + "license": "MIT", "engines": { - "node": ">= 8" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "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" }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "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==", + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } + "license": "Python-2.0" }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@jest/core/node_modules/jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", "dev": true, + "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" + "type-fest": "^0.20.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/core/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.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" + "argparse": "^2.0.1" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@jest/core/node_modules/jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "license": "MIT" }, - "node_modules/@jest/core/node_modules/jest-resolve": { - "version": "29.4.3", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", "dev": true, + "license": "ISC", "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" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "*" } }, - "node_modules/@jest/core/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", "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" - }, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/core/node_modules/jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", + "node_modules/@eslint/js": { + "version": "8.57.1", "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@jest/core/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/@flatten-js/interval-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@flatten-js/interval-tree/-/interval-tree-1.1.3.tgz", + "integrity": "sha512-xhFWUBoHJFF77cJO1D6REjdgJEMRf2Y2Z+eKEPav8evGKcLSnj1ud5pLXQSbGuxF3VSvT1rWhMfVpXEKJLTL+A==", + "license": "MIT" + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", "dev": true, + "license": "Apache-2.0", "dependencies": { - "has-flag": "^4.0.0" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=10.10.0" } }, - "node_modules/@jest/core/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "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": ">=0.10.0" + "node": "*" } }, - "node_modules/@jest/core/node_modules/resolve.exports": { - "version": "2.0.0", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=10" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@humanwhocodes/object-schema": { + "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", + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "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" }, "engines": { "node": ">=8" } }, - "node_modules/@jest/core/node_modules/v8-to-istanbul": { - "version": "9.1.0", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", - "dev": true, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=10.12.0" + "node": ">=8" } }, - "node_modules/@jest/core/node_modules/write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "p-locate": "^4.1.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=8" } }, - "node_modules/@jest/create-cache-key-function": { - "version": "29.4.3", - "integrity": "sha512-AJVFQTTy6jnZAQiAZrdOaTAPzJUrvAE/4IMe+Foav6WPhypFszqg7a4lOTyuzYQEEiT5RSrGYg9IY+/ivxiyXw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3" + "p-try": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/create-cache-key-function/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "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" + "p-limit": "^2.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/create-cache-key-function/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dependencies": { - "@types/yargs-parser": "*" + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/@jest/create-cache-key-function/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@jest/console": { + "version": "27.5.1", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@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" - }, - "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/create-cache-key-function/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/core": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@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.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.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@jest/create-cache-key-function/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@jest/core/node_modules/@jest/console": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@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": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "node_modules/@jest/core/node_modules/@jest/reporters": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@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", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.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.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", + "v8-to-istanbul": "^9.0.1" + }, "engines": { - "node": ">=8" + "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/create-cache-key-function/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/core/node_modules/@jest/test-result": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.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": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/environment": { - "version": "29.4.3", - "integrity": "sha512-dq5S6408IxIa+lr54zeqce+QgI+CJT4nmmA+1yzFgtcsGK8c/EyiUb9XQOgz3BMKrRDfKseeOaxj2eO8LlD3lA==", + "node_modules/@jest/core/node_modules/@jest/transform": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/node": "*", - "jest-mock": "^29.4.3" + "@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/environment/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/@jest/core/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": "*", @@ -2829,114 +2867,272 @@ "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==", + "node_modules/@jest/core/node_modules/@types/yargs": { + "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" - }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" }, "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==", + "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/environment/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@jest/core/node_modules/jest-haste-map": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.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" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/@jest/environment/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "node_modules/@jest/core/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/environment/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@jest/core/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/@jest/core/node_modules/jest-resolve": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/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/core/node_modules/pretty-format": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/resolve.exports": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/core/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/core/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { "node": ">=8" } }, - "node_modules/@jest/environment/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/core/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/expect": { - "version": "29.4.3", - "integrity": "sha512-iktRU/YsxEtumI9zsPctYUk7ptpC+AVLLk1Ax3AsA4g1C+8OOnKDkIQBDHtD5hA/+VtgMd5AWI5gNlcAlt2vxQ==", + "node_modules/@jest/core/node_modules/v8-to-istanbul": { + "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": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@jest/core/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/create-cache-key-function": { + "version": "29.7.0", + "license": "MIT", "dependencies": { - "expect": "^29.4.3", - "jest-snapshot": "^29.4.3" + "@jest/types": "^29.6.3" }, "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==", - "dev": true, + "node_modules/@jest/create-cache-key-function/node_modules/@jest/types": { + "version": "29.6.3", + "license": "MIT", "dependencies": { - "jest-get-type": "^29.4.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": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers": { - "version": "29.4.3", - "integrity": "sha512-4Hote2MGcCTWSD2gwl0dwbCpBRHhE6olYEuTj8FMowdg3oQWNKr2YuxenPQYZ7+PfqPY1k98wKDU4Z+Hvd4Tiw==", + "node_modules/@jest/create-cache-key-function/node_modules/@types/yargs": { + "version": "17.0.33", + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@sinonjs/fake-timers": "^10.0.2", + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-message-util": "^29.4.3", - "jest-mock": "^29.4.3", - "jest-util": "^29.4.3" + "jest-mock": "^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==", + "node_modules/@jest/environment/node_modules/@jest/types": { + "version": "29.6.3", + "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": "*", @@ -2947,72 +3143,94 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "node_modules/@jest/environment/node_modules/@types/yargs": { + "version": "17.0.33", + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/@jest/fake-timers/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@jest/expect": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "expect": "^29.7.0", + "jest-snapshot": "^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/fake-timers/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "jest-get-type": "^29.6.3" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@jest/fake-timers/node_modules/@jest/types": { + "version": "29.6.3", + "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": "^14.15.0 || ^16.10.0 || >=18.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/@types/yargs": { + "version": "17.0.33", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } }, - "node_modules/@jest/fake-timers/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@jest/fake-timers/node_modules/ansi-styles": { + "version": "5.2.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/@jest/fake-timers/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", + "version": "29.7.0", + "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 +3239,10 @@ } }, "node_modules/@jest/fake-timers/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "version": "29.7.0", + "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 +3253,38 @@ "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==", + "node_modules/@jest/fake-timers/node_modules/pretty-format": { + "version": "29.7.0", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "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 +3296,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==", + "node_modules/@jest/reporters": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^27.5.1", @@ -3190,86 +3346,22 @@ } } }, - "node_modules/@jest/reporters/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/reporters/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/reporters/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/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/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/schemas": { - "version": "29.4.3", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "version": "29.6.3", + "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.25.16" + "@sinclair/typebox": "^0.27.8" }, "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==", + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" }, @@ -3279,8 +3371,8 @@ }, "node_modules/@jest/test-result": { "version": "27.5.1", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "^27.5.1", "@jest/types": "^27.5.1", @@ -3292,13 +3384,13 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.4.3", - "integrity": "sha512-yi/t2nES4GB4G0mjLc0RInCq/cNr9dNwJxcGg8sslajua5Kb4kmozAc+qPLzplhBgfw1vLItbjyHzUN92UXicw==", + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/test-result": "^29.4.3", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", + "jest-haste-map": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -3306,15 +3398,15 @@ } }, "node_modules/@jest/test-sequencer/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": { @@ -3322,12 +3414,12 @@ } }, "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 +3428,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 +3444,38 @@ } }, "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==", + "version": "5.2.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_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" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "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 +3487,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 +3506,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 +3530,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,43 +3543,37 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-sequencer/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/@jest/test-sequencer/node_modules/pretty-format": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, - "funding": { - "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": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-sequencer/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/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,216 +3595,106 @@ "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, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "license": "MIT" }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/types": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@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" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.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/gen-mapping": { + "version": "0.3.8", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": ">=7.0.0" + "node": ">=6.0.0" } }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, - "node_modules/@jest/types": { - "version": "27.5.1", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "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" - } - }, - "node_modules/@jest/types/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/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" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "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==", - "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" - }, - "engines": { - "node": ">=6.0.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "version": "1.5.0", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.25", + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keystonehq/alias-sampling": { + "version": "0.1.2", + "license": "MIT" + }, "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==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@keystonehq/bc-ur-registry/-/bc-ur-registry-0.7.1.tgz", + "integrity": "sha512-6eVIjNt/P+BmuwcYccbPYVS85473SFNplkqWF/Vb3ePCzLX00tn0WZBO1FGpS4X4nfXtceTfvUeNvQKoTGtXrw==", + "license": "Apache-2.0", "dependencies": { "@ngraveio/bc-ur": "^1.1.5", "bs58check": "^2.1.2", "tslib": "^2.3.0" } }, + "node_modules/@lodev09/react-native-true-sheet": { + "version": "2.0.0", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-true-sheet.git#5945184a2fea9fe5ba8f5cfcdb20e3fc5eed6e37", + "integrity": "sha512-Fphn9L0Qffjue7fUgW6A1srV6zdVXn68Uhq7WodwUJH8g9Lxys9JpwyxLrrYoQOiQhVYOye9+B7jPKZwd3slhQ==", + "license": "MIT", + "workspaces": [ + "example", + "docs" + ], + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/@ngraveio/bc-ur": { - "version": "1.1.6", - "integrity": "sha512-G+2XgjXde2IOcEQeCwR250aS43/Swi7gw0FuETgJy2c3HqF8f88SXDMsIGgJlZ8jXd0GeHR4aX0MfjXf523UZg==", + "version": "1.1.13", + "license": "MIT", "dependencies": { - "@apocentre/alias-sampling": "^0.5.3", + "@keystonehq/alias-sampling": "^0.1.1", "assert": "^2.0.0", "bignumber.js": "^9.0.1", "cbor-sync": "^1.0.4", @@ -3796,38 +3705,64 @@ }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", "dev": true, + "license": "MIT", "dependencies": { "eslint-scope": "5.1.1" } }, + "node_modules/@noble/curves": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.0.tgz", + "integrity": "sha512-RiwZZeJnsTnhT+/gg2KvITJZhK5oagQrpZo+yQyd3mv3D5NAG2qEeEHpw7IkXRlpkoD45wl2o4ydHAvY9wyEfw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.0.tgz", + "integrity": "sha512-h8VUBlE8R42+XIDO229cgisD287im3kdY6nbNZJFjc6ZvKIXPYXe6Vc/t+kyjFdMFyt5JpapzTsEg8n63w5/lw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "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/" - } - ] + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "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 +3773,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 +3789,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 +3800,1542 @@ "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", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "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", + "resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.16.3.tgz", + "integrity": "sha512-cMIcvoZKIrShzJHEaHbTAp458R9WOv0fB6UyC7Ek4Qk561Ow/DrzmmJmH/rAZg21Z6ixJ4YSdFDC14crqIBmCQ==", + "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", + "version": "15.1.3", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-clean": "15.1.3", + "@react-native-community/cli-config": "15.1.3", + "@react-native-community/cli-debugger-ui": "15.1.3", + "@react-native-community/cli-doctor": "15.1.3", + "@react-native-community/cli-server-api": "15.1.3", + "@react-native-community/cli-tools": "15.1.3", + "@react-native-community/cli-types": "15.1.3", "chalk": "^4.1.2", "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" + "prompts": "^2.4.2", + "semver": "^7.5.2" }, "bin": { - "react-native": "build/bin.js" + "rnc-cli": "build/bin.js" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/@react-native-community/cli-clean": { - "version": "10.1.1", - "integrity": "sha512-iNsrjzjIRv9yb5y309SWJ8NDHdwYtnCpmxZouQDyOljUdC9MwdZ4ChbtA4rwQyAwgOVfS9F/j56ML3Cslmvrxg==", + "version": "15.1.3", + "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "^10.1.1", + "@react-native-community/cli-tools": "15.1.3", "chalk": "^4.1.2", - "execa": "^1.0.0", - "prompts": "^2.4.0" + "execa": "^5.0.0", + "fast-glob": "^3.3.2" } }, - "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": "15.1.3", + "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": "15.1.3", + "chalk": "^4.1.2", + "cosmiconfig": "^9.0.0", + "deepmerge": "^4.3.0", + "fast-glob": "^3.3.2", + "joi": "^17.2.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": "15.1.3", + "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": "15.1.3", + "chalk": "^4.1.2", + "fast-glob": "^3.3.2", + "fast-xml-parser": "^4.4.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": "15.1.3", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@react-native-community/cli-tools": "15.1.3", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-glob": "^3.3.2" } }, - "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-debugger-ui": { + "version": "15.1.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" + "serve-static": "^1.13.1" } }, - "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-doctor": { + "version": "15.1.3", + "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": "15.1.3", + "@react-native-community/cli-platform-android": "15.1.3", + "@react-native-community/cli-platform-apple": "15.1.3", + "@react-native-community/cli-platform-ios": "15.1.3", + "@react-native-community/cli-tools": "15.1.3", + "chalk": "^4.1.2", + "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", + "semver": "^7.5.2", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/get-stream": { - "version": "4.1.0", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dependencies": { - "pump": "^3.0.0" + "node_modules/@react-native-community/cli-doctor/node_modules/semver": { + "version": "7.7.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=6" - } - }, - "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-clean/node_modules/is-stream": { - "version": "1.1.0", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "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-platform-android": { + "version": "15.1.3", + "license": "MIT", "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" + "@react-native-community/cli-config-android": "15.1.3", + "@react-native-community/cli-tools": "15.1.3", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "logkitty": "^0.7.1" } }, - "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-platform-apple": { + "version": "15.1.3", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-config-apple": "15.1.3", + "@react-native-community/cli-tools": "15.1.3", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.4.1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" + "node_modules/@react-native-community/cli-platform-ios": { + "version": "15.1.3", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-platform-apple": "15.1.3" } }, - "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-community/cli-server-api": { + "version": "15.1.3", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@react-native-community/cli-debugger-ui": "15.1.3", + "@react-native-community/cli-tools": "15.1.3", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^6.2.3" } }, - "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-community/cli-tools": { + "version": "15.1.3", + "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "^10.1.1", + "appdirsjs": "^1.2.4", "chalk": "^4.1.2", - "cosmiconfig": "^5.1.0", - "deepmerge": "^3.2.0", - "glob": "^7.1.3", - "joi": "^17.2.1" + "execa": "^5.0.0", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "open": "^6.2.0", + "ora": "^5.4.1", + "prompts": "^2.4.2", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" } }, - "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==", - "dependencies": { - "color-convert": "^2.0.1" + "node_modules/@react-native-community/cli-tools/node_modules/semver": { + "version": "7.7.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=10" } }, - "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-community/cli-types": { + "version": "15.1.3", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli/node_modules/semver": { + "version": "7.7.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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-community/push-notification-ios": { + "version": "1.11.0", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "invariant": "^2.2.4" }, - "engines": { - "node": ">=7.0.0" + "peerDependencies": { + "react": ">=16.6.3", + "react-native": ">=0.58.4" } }, - "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-documents/picker": { + "version": "10.1.7", + "resolved": "https://registry.npmjs.org/@react-native-documents/picker/-/picker-10.1.7.tgz", + "integrity": "sha512-Tb8SPU+pHxrSJmDHBozSUStIPeyFHTHLrU3MW0N3sUAioLd5z+nmUdypfg5fs+Yzp7KTxVW06APe2HLB1ysLww==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } }, - "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==", - "engines": { - "node": ">=0.10.0" + "node_modules/@react-native-menu/menu": { + "version": "1.2.4", + "resolved": "git+ssh://git@github.com/BlueWallet/menu.git#9933468ad6c85c62e455d29fbd079ae9c4a2ef2c", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "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/assets-registry": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.78.2.tgz", + "integrity": "sha512-VHqQqjj1rnh2KQeS3yx4IfFSxIIIDi1jR4yUeC438Q6srwxDohR4W0UkXuSIz0imhlems5eS7yZTjdgSpWHRUQ==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" } }, - "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/babel-plugin-codegen": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.78.2.tgz", + "integrity": "sha512-0MnQOhIaOdWbQ3Dx3dz0MBbG+1ggBiyUL+Y+xHAeSDSaiRATT8DIsrSloeJU0A+2p5TxF8ITJyJ6KEQkMyB/Zw==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.78.2" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "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/babel-preset": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.78.2.tgz", + "integrity": "sha512-VGOLhztQY/0vktMXrBr01HUN/iBSdkKBRiiZYfrLqx9fB2ql55gZb/6X9lzItjVyYoOc2jyHXSX8yoSfDcWDZg==", + "license": "MIT", "dependencies": { - "serve-static": "^1.13.1" + "@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-arrow-functions": "^7.24.7", + "@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-computed-properties": "^7.24.7", + "@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-function-name": "^7.25.1", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^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-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@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-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/template": "^7.25.0", + "@react-native/babel-plugin-codegen": "0.78.2", + "babel-plugin-syntax-hermes-parser": "0.25.1", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "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/codegen": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.78.2.tgz", + "integrity": "sha512-4r3/W1h22/GAmAMuMRMJWsw/9JGUEDAnSbYNya7zID1XSvizLoA5Yn8Qv+phrRwwsl0eZLxOqONh/nzXJcvpyg==", + "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==", + "@babel/parser": "^7.25.3", + "glob": "^7.1.1", + "hermes-parser": "0.25.1", + "invariant": "^2.2.4", + "jscodeshift": "^17.0.0", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" } }, - "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/community-cli-plugin": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.78.2.tgz", + "integrity": "sha512-xqEnpqxvBlm02mRY58L0NBjF25MTHmbaeA2qBx5VtheH/pXL6MHUbtwB1Q2dJrg9XcK0Np1i9h7N5h9gFwA2Mg==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@react-native/dev-middleware": "0.78.2", + "@react-native/metro-babel-transformer": "0.78.2", + "chalk": "^4.0.0", + "debug": "^2.2.0", + "invariant": "^2.2.4", + "metro": "^0.81.3", + "metro-config": "^0.81.3", + "metro-core": "^0.81.3", + "readline": "^1.3.0", + "semver": "^7.1.3" }, "engines": { - "node": ">=8" + "node": ">=18" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "@react-native-community/cli": "*" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "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/community-cli-plugin/node_modules/@react-native/metro-babel-transformer": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.78.2.tgz", + "integrity": "sha512-H4614LjcbrG+lUtg+ysMX5RnovY8AwrWj4rH8re6ErfhPFwLQXV0LIrl/fgFpq07Vjc5e3ZXzuKuMJF6l7eeTQ==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.78.2", + "hermes-parser": "0.25.1", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">=10" + "node": ">=18" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "@babel/core": "*" } }, - "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/community-cli-plugin/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/@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/community-cli-plugin/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/@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==", - "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/community-cli-plugin/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=4.8" + "node": ">=10" } }, - "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/debugger-frontend": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.78.2.tgz", + "integrity": "sha512-qNJT679OU/cdAKmZxfBFjqTG+ZC5i/4sLyvbcQjFFypunGSOaWl3mMQFQQdCBIQN+DFDPVSUXTPZQK1uI2j/ow==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=18" } }, - "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/dev-middleware": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.78.2.tgz", + "integrity": "sha512-/u0pGiWVgvx09cYNO4/Okj8v1ZNt4K941pQJPhdwg5AHYuggVHNJjROukXJzZiElYFcJhMfOuxwksiIyx/GAkA==", + "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" + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.78.2", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.2.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "selfsigned": "^2.4.1", + "serve-static": "^1.16.2", + "ws": "^6.2.3" }, "engines": { - "node": ">=6" + "node": ">=18" } }, - "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/dev-middleware/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": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" + "ms": "2.0.0" } }, - "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/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": { + "is-docker": "^2.0.0" + }, "engines": { "node": ">=8" } }, - "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==", - "engines": { - "node": ">=0.10.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/dev-middleware/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/@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": { - "path-key": "^2.0.0" + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" }, "engines": { - "node": ">=4" - } - }, - "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": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/eslint-config": { + "version": "0.78.3", + "resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.78.3.tgz", + "integrity": "sha512-YcJsVfOHLgA9OXTfHPV0dSSVhk9Ceu+WzNl8m3mBB8oejEdkjM9VBDrArg64AGCzdRLAMbgiUrWy7vI47yNbmQ==", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^4.1.0" + "@babel/core": "^7.25.2", + "@babel/eslint-parser": "^7.25.1", + "@react-native/eslint-plugin": "0.78.3", + "@typescript-eslint/eslint-plugin": "^7.1.1", + "@typescript-eslint/parser": "^7.1.1", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-ft-flow": "^2.0.1", + "eslint-plugin-jest": "^27.9.0", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-native": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "peerDependencies": { + "eslint": ">=8", + "prettier": ">=2" } }, - "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/eslint-config/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "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/eslint-config/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@react-native-community/cli-hermes/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "color-convert": "^2.0.1" + "@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": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-hermes/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@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": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "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/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": ">=7.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "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_modules/@react-native/eslint-config/node_modules/eslint-config-prettier": { + "version": "8.10.0", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "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/eslint-plugin-jest": { + "version": "27.9.0", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@typescript-eslint/utils": "^5.10.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": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0", + "eslint": "^7.0.0 || ^8.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } } }, - "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==", - "dependencies": { - "color-convert": "^2.0.1" - }, + "node_modules/@react-native/eslint-config/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "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==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "node_modules/@react-native/eslint-config/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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==", - "dependencies": { - "color-name": "~1.1.4" - }, + "node_modules/@react-native/eslint-plugin": { + "version": "0.78.3", + "resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.78.3.tgz", + "integrity": "sha512-PvFAL9J/Jk93K5ibr5Cj5RfiYdMJNqPej6NcDoOSj1kalM6fE22dHxnxF9A1/YApN1F972n+tW16T1+c4F9opw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=7.0.0" + "node": ">=18" } }, - "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/gradle-plugin": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.78.2.tgz", + "integrity": "sha512-LHgmdrbyK9fcBDdxtn2GLOoDAE+aFHtDHgu6vUZ5CSCi9CMd5Krq8IWAmWjeq+BQr+D1rwSXDAHtOrfJ6qOolA==", + "license": "MIT", + "engines": { + "node": ">=18" + } }, - "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/js-polyfills": { + "version": "0.78.3", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.78.3.tgz", + "integrity": "sha512-RvWAV2qU+XgMRVF+WIJQIqKdfrth1ghhdzAoKkXpXRKgWPps/6ZSCFgxkSjYaxAwXREOEx8/HunSmXDCsW+0ag==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=4.8" + "node": ">=18" } }, - "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/metro-babel-transformer": { + "version": "0.78.3", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.78.3.tgz", + "integrity": "sha512-VSzAJ5G7uD1F5nG6NagHZFq6Q6dpsCU6LH+2j7iTsXZ9QUSds54f+WP5RC0UHZcVkQavSfqzu3+wj4pYGv5Pzg==", + "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" + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.78.3", + "hermes-parser": "0.25.1", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "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/metro-babel-transformer/node_modules/@react-native/babel-plugin-codegen": { + "version": "0.78.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.78.3.tgz", + "integrity": "sha512-yKs7KR9CzqGaM8mZi4vdjgaNgqomj094U325h2GWqsdj9+m/lf8e/Crd9sLDFtK0W2UCbcVw2L+M8okqXJ3oHw==", + "dev": true, + "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.78.3" }, "engines": { - "node": ">=6" + "node": ">=18" } }, - "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/metro-babel-transformer/node_modules/@react-native/babel-preset": { + "version": "0.78.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.78.3.tgz", + "integrity": "sha512-L1DRY8CYbrnpFoqVgeRW1FO8ZfgagYd3nx0M+9oaqG/VFX5rrfoMt011ZDeoYpmNayZS7klkqCFQLXVWAMPNBA==", + "dev": true, + "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-arrow-functions": "^7.24.7", + "@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-computed-properties": "^7.24.7", + "@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-function-name": "^7.25.1", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^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-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@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-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/template": "^7.25.0", + "@react-native/babel-plugin-codegen": "0.78.3", + "babel-plugin-syntax-hermes-parser": "0.25.1", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "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/metro-babel-transformer/node_modules/@react-native/codegen": { + "version": "0.78.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.78.3.tgz", + "integrity": "sha512-p6mbFm6vvDskMj3zBzFIhHc85i2G/f47HwkFLJYSdWUITrPaVlXLSjSoCQPhYSNqrMv2g376OZZ+QXjp50XnTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "glob": "^7.1.1", + "hermes-parser": "0.25.1", + "invariant": "^2.2.4", + "jscodeshift": "^17.0.0", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" } }, - "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/metro-config": { + "version": "0.78.2", + "license": "MIT", "dependencies": { - "path-key": "^2.0.0" + "@react-native/js-polyfills": "0.78.2", + "@react-native/metro-babel-transformer": "0.78.2", + "metro-config": "^0.81.3", + "metro-runtime": "^0.81.3" }, "engines": { - "node": ">=4" + "node": ">=18" } }, - "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==", + "node_modules/@react-native/metro-config/node_modules/@react-native/js-polyfills": { + "version": "0.78.2", + "license": "MIT", "engines": { - "node": ">=4" - } - }, - "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==", - "bin": { - "semver": "bin/semver" + "node": ">=18" } }, - "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/metro-config/node_modules/@react-native/metro-babel-transformer": { + "version": "0.78.2", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.78.2", + "hermes-parser": "0.25.1", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "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==", - "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" - } + "node_modules/@react-native/normalize-colors": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.78.2.tgz", + "integrity": "sha512-CA/3ynRO6/g1LDbqU8ewrv0js/1lU4+j04L7qz6btXbLTDk1UkF+AfpGRJGbIVY9UmFBJ7l1AOmzwutrWb3Txw==", + "license": "MIT" }, - "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==", + "node_modules/@react-native/typescript-config": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/typescript-config/-/typescript-config-0.78.2.tgz", + "integrity": "sha512-hgsof1uirO3TuIV2Q1LXp6KYfVZq2aR2HPTV2ciHsLHDUbo/j5wQgdQQf9Dw9QJOyDdfLeY9Jo0cY7F1Rw7KSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.78.2.tgz", + "integrity": "sha512-y/wVRUz1ImR2hKKUXFroTdSBiL0Dd+oudzqcGKp/M8Ybrw9MQ0m2QCXxtyONtDn8qkEGceqllwTCKq5WQwJcew==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">=8" + "node": ">=18" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "@types/react": "^19.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "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-navigation/core": { + "version": "7.8.3", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "@react-navigation/routers": "^7.3.3", + "escape-string-regexp": "^4.0.0", + "nanoid": "3.3.8", + "query-string": "^7.1.3", + "react-is": "^18.2.0", + "use-latest-callback": "^0.2.1", + "use-sync-external-store": "^1.2.2" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "react": ">= 18.2.0" } }, - "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==", + "node_modules/@react-navigation/devtools": { + "version": "7.0.24", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "fast-deep-equal": "^3.1.3", + "nanoid": "3.3.8", + "stacktrace-parser": "^0.1.10" }, - "engines": { - "node": ">=7.0.0" + "peerDependencies": { + "react": ">= 18.2.0" } }, - "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-navigation/drawer": { + "version": "7.3.7", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.3.6", + "color": "^4.2.3", + "react-native-drawer-layout": "^4.1.4", + "use-latest-callback": "^0.2.1" + }, + "peerDependencies": { + "@react-navigation/native": "^7.1.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-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==", + "node_modules/@react-navigation/elements": { + "version": "2.3.6", + "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" + "color": "^4.2.3" }, - "engines": { - "node": ">=4.8" + "peerDependencies": { + "@react-native-masked-view/masked-view": ">= 0.2.0", + "@react-navigation/native": "^7.1.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-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==", + "node_modules/@react-navigation/native": { + "version": "7.1.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" + "@react-navigation/core": "^7.8.3", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "3.3.8", + "use-latest-callback": "^0.2.1" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "react": ">= 18.2.0", + "react-native": "*" } }, - "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-navigation/native-stack": { + "version": "7.3.8", + "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "@react-navigation/elements": "^2.3.6", + "warn-once": "^0.1.1" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "@react-navigation/native": "^7.1.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-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-navigation/routers": { + "version": "7.3.3", + "license": "MIT", + "dependencies": { + "nanoid": "3.3.8" } }, - "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/@realm/fetch": { + "version": "0.1.1", "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "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/@rneui/base": { + "version": "4.0.0-rc.8", + "license": "MIT", "dependencies": { - "path-key": "^2.0.0" + "@types/react-native-vector-icons": "^6.4.10", + "color": "^3.2.1", + "deepmerge": "^4.2.2", + "hoist-non-react-statics": "^3.3.2", + "react-native-ratings": "^8.1.0", + "react-native-size-matters": "^0.4.0" }, - "engines": { - "node": ">=4" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/react-native-elements" + }, + "peerDependencies": { + "react-native-safe-area-context": ">= 3.0.0", + "react-native-vector-icons": ">7.0.0" } }, - "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==", - "engines": { - "node": ">=4" + "node_modules/@rneui/base/node_modules/color": { + "version": "3.2.1", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.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/@rneui/base/node_modules/color-convert": { + "version": "1.9.3", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" } }, - "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==", - "dependencies": { - "has-flag": "^4.0.0" + "node_modules/@rneui/base/node_modules/color-name": { + "version": "1.1.3", + "license": "MIT" + }, + "node_modules/@rneui/themed": { + "version": "4.0.0-rc.8", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/react-native-elements" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@rneui/base": "4.0.0-rc.7" } }, - "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==", - "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" + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@scure/base": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz", + "integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "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/@scure/btc-signer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@scure/btc-signer/-/btc-signer-2.0.1.tgz", + "integrity": "sha512-vk5a/16BbSFZkhh1JIJ0+4H9nceZVo5WzKvJGGWiPp3sQOExeW+L53z3dI6u0adTPoE8ZbL+XEb6hEGzVZSvvQ==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "@noble/curves": "~2.0.0", + "@noble/hashes": "~2.0.0", + "@scure/base": "~2.0.0", + "micro-packed": "~0.8.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://paulmillr.com/funding/" } }, - "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==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/@scure/btc-signer/node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "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/@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/@sideway/address": { + "version": "4.1.5", + "license": "BSD-3-Clause", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@hapi/hoek": "^9.0.0" } }, - "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/@sideway/formula": { + "version": "3.0.1", + "license": "BSD-3-Clause" }, - "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/@sideway/pinpoint": { + "version": "2.0.0", + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "license": "BSD-3-Clause", "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" + "type-detect": "4.0.8" } }, - "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/@sinonjs/fake-timers": { + "version": "10.3.0", + "license": "BSD-3-Clause", "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" + "@sinonjs/commons": "^3.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/@spsina/bip47": { + "version": "1.0.1", + "resolved": "git+ssh://git@github.com/BlueWallet/bip47.git#df823454092a9993edeea11d663f8eb9a522a174", + "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "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": ">=6" - } - }, - "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==", - "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, - "node_modules/@react-native-community/cli-plugin-metro/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/@spsina/bip47/node_modules/bip32": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.1.0.tgz", + "integrity": "sha512-eoeajYEzJ4d6yyVtby8C+XkCeKItiC4Mx56a0M9VaqTMC73SWOm4xVZG7SaR8e/yp4eSyky2XcBpH3DApPdu7Q==", + "license": "MIT", + "dependencies": { + "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": ">=0.10.0" + "node": ">=6.0.0" } }, - "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/@spsina/bip47/node_modules/bitcoinjs-lib": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.7.tgz", + "integrity": "sha512-tlf/r2DGMbF7ky1MgUqXHzypYHakkEnm0SZP23CJKIqNY/5uNAnMbFhMJdhjrL/7anfb/U8+AlpdjPWjPnAalg==", + "license": "MIT", "dependencies": { - "path-key": "^2.0.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": ">=4" + "node": ">=8.0.0" } }, - "node_modules/@react-native-community/cli-plugin-metro/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/@spsina/bip47/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==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" } }, - "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/@spsina/bip47/node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "license": "MIT", + "dependencies": { + "base-x": "^4.0.0" } }, - "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==", + "node_modules/@spsina/bip47/node_modules/ecpair": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz", + "integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "randombytes": "^2.1.0", + "typeforce": "^1.18.0", + "wif": "^2.0.6" }, "engines": { - "node": ">=8" - } - }, - "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==", - "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" + "node": ">=8.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/@testing-library/react-native": { + "version": "13.2.0", + "dev": true, + "license": "MIT", "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" + "chalk": "^4.1.2", + "jest-matcher-utils": "^29.7.0", + "pretty-format": "^29.7.0", + "redent": "^3.0.0" }, "engines": { - "node": ">= 10.14.2" - } - }, - "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==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "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==", - "dependencies": { - "color-convert": "^2.0.1" + "node": ">=18" }, - "engines": { - "node": ">=8" + "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/ansi-styles?sponsor=1" + "peerDependenciesMeta": { + "jest": { + "optional": true + } } }, - "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==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/@testing-library/react-native/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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/@testing-library/react-native/node_modules/pretty-format": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.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/@types/babel__core": { + "version": "7.20.5", + "license": "MIT", + "dependencies": { + "@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-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/@types/babel__generator": { + "version": "7.27.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" + "@babel/types": "^7.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/@types/babel__template": { + "version": "7.4.4", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/@react-native-community/cli-tools": { - "version": "10.1.1", - "integrity": "sha512-+FlwOnZBV+ailEzXjcD8afY2ogFEBeHOw/8+XXzMgPaquU2Zly9B+8W089tnnohO3yfiQiZqkQlElP423MY74g==", + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "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" + "@babel/types": "^7.20.7" } }, - "node_modules/@react-native-community/cli-tools/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@types/bip38": { + "version": "3.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-tools/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@types/bn.js": { + "version": "4.11.6", + "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-tools/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@types/bs58check": { + "version": "2.1.2", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@types/node": "*" } }, - "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/create-hash": { + "version": "1.2.6", + "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" + "@types/node": "*" } }, - "node_modules/@react-native-community/cli-tools/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "dev": true, + "license": "MIT" }, - "node_modules/@react-native-community/cli-tools/node_modules/locate-path": { - "version": "6.0.0", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/node": "*" } }, - "node_modules/@react-native-community/cli-tools/node_modules/p-locate": { - "version": "5.0.0", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "license": "MIT" }, - "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/istanbul-lib-coverage": { + "version": "2.0.6", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/@react-native-community/cli-types": { - "version": "10.0.0", - "integrity": "sha512-31oUM6/rFBZQfSmDQsT1DX/5fjqfxg7sf2u8kTPJK7rXVya5SRpAMaCXsPAG0omsmJxXt+J9HxUi3Ic+5Ux5Iw==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "license": "MIT", "dependencies": { - "joi": "^17.2.1" + "@types/istanbul-lib-report": "*" } }, - "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/jest": { + "version": "29.5.14", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "expect": "^29.0.0", + "pretty-format": "^29.0.0" } }, - "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==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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/jest/node_modules/pretty-format": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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/json-schema": { + "version": "7.0.15", + "dev": true, + "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/json5": { + "version": "0.0.29", + "dev": true, + "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/node": { + "version": "22.14.0", + "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" + "undici-types": "~6.21.0" } }, - "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/node-forge": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.13.tgz", + "integrity": "sha512-zePQJSW5QkwSHKRApqWCVKeKoSOt4xvEnLENZPjyvm9Ezdf/EyDeJM7jqLzOwjVICQQzvLZ63T55MKdJB5H6ww==", + "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/node": "*" } }, - "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==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "license": "MIT" }, - "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/prop-types": { + "version": "15.7.14", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.20", + "license": "MIT", "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" + "@types/prop-types": "*", + "csstype": "^3.0.2" } }, - "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/react-native": { + "version": "0.70.19", + "license": "MIT", + "dependencies": { + "@types/react": "*" } }, - "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-native-push-notification": { + "version": "8.1.4", + "dev": true, + "license": "MIT" }, - "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-native-vector-icons": { + "version": "6.4.18", + "license": "MIT", "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" + "@types/react": "*", + "@types/react-native": "^0.70" } }, - "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/react-test-renderer": { + "version": "19.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" } }, - "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/semver": { + "version": "7.7.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "license": "MIT" + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "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 +5343,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,26 +5370,41 @@ } } }, - "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/scope-manager": { + "version": "7.18.0", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "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", - "tsutils": "^3.21.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": { - "eslint": "*" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5492,963 +5412,927 @@ } } }, - "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/types": { + "version": "7.18.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "prettier-linter-helpers": "^1.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": ">=12.0.0" + "node": "^18.18.0 || >=20.0.0" }, - "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "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/typescript-estree/node_modules/semver": { + "version": "7.7.1", "dev": true, - "dependencies": { - "yallist": "^4.0.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { "node": ">=10" } }, - "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/utils": { + "version": "7.18.0", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" + "@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" }, - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" }, "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/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/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" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "node_modules/@react-native-community/eslint-plugin": { + "node_modules/@ungap/structured-clone": { "version": "1.3.0", - "integrity": "sha512-+zDZ20NUnSWghj7Ku5aFphMzuM9JulqCW+aPXT6IfIXFbb8tzYTTOSeRFOtuekJ99ibW2fUCSsjuKNlwDIbHFg==", - "dev": true + "dev": true, + "license": "ISC" }, - "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/@wix-pilot/core": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@wix-pilot/core/-/core-3.4.2.tgz", + "integrity": "sha512-O8V2NLfPEKI2IviJXG4g/vNbMfsBZNBhzAKFUOOaOR9TTDUJYlZUttqjhjP/fPnaDky0/hrfGES15sO0N7zEkw==", + "license": "MIT", "dependencies": { - "invariant": "^2.2.4" + "chalk": "^4.1.0", + "pngjs": "^7.0.0", + "winston": "^3.17.0" }, "peerDependencies": { - "react": ">=16.6.3", - "react-native": ">=0.58.4" + "expect": "*" + }, + "peerDependenciesMeta": { + "expect": { + "optional": true + } } }, - "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/@wix-pilot/core/node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } }, - "node_modules/@react-native/polyfills": { - "version": "2.0.0", - "integrity": "sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ==" + "node_modules/@wix-pilot/detox": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@wix-pilot/detox/-/detox-1.0.13.tgz", + "integrity": "sha512-/34lM25AfmHNMLOeEIhfKVnx2YZyn5VHC/R4Bs1uXQ2B+Yg0JbxAfvfXA3xqxBsdYqrYImILPO3Ih0c5UzoNxw==", + "peerDependencies": { + "@wix-pilot/core": "^3.4.1", + "detox": ">=20.33.0", + "expect": "29.x.x || 28.x.x || ^27.2.5" + } }, - "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==", - "dev": true, + "node_modules/abort-controller": { + "version": "3.0.0", + "license": "MIT", "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" + "event-target-shim": "^5.0.0" }, - "peerDependencies": { - "react-native": "*", - "react-test-renderer": "18.2.0" + "engines": { + "node": ">=6.5" } }, - "node_modules/@react-navigation/core": { - "version": "5.16.1", - "integrity": "sha512-3AToC7vPNeSNcHFLd1h71L6u34hfXoRAS1CxF9Fc4uC8uOrVqcNvphpeFbE0O9Bw6Zpl0BnMFl7E5gaL3KGzNA==", + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", "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" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, - "peerDependencies": { - "react": "*" + "engines": { + "node": ">= 0.6" } }, - "node_modules/@react-navigation/drawer": { - "version": "5.12.9", - "integrity": "sha512-SYb2BCEAn+BiEwC6WBfCzs1VlWD+ZdQbxmsim6vo1o+ndPW2e+kiq7FXKRs0vUXhQRZVl2oOB3vBn0c3YCllQg==", - "dependencies": { - "color": "^3.1.3", - "react-native-iphone-x-helper": "^1.3.0" + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", "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" + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@react-navigation/native": { - "version": "5.9.8", - "integrity": "sha512-DNbcDHXQPSFDLn51kkVVJjT3V7jJy2GztNYZe/2bEg29mi5QEcHHcpifjMCtyFKntAOWzKlG88UicIQ17UEghg==", + "node_modules/aez": { + "version": "1.0.1", + "license": "MIT", "dependencies": { - "@react-navigation/core": "^5.16.1", - "escape-string-regexp": "^4.0.0", - "nanoid": "^3.1.15" + "blakejs": "^1.1.0", + "safe-buffer": "^5.1.1" }, - "peerDependencies": { - "react": "*", - "react-native": "*" + "engines": { + "node": ">=8.10.0" } }, - "node_modules/@react-navigation/routers": { - "version": "5.7.4", - "integrity": "sha512-0N202XAqsU/FlE53Nmh6GHyMtGm7g6TeC93mrFAFJOqGRKznT0/ail+cYlU6tNcPA9AHzZu1Modw1eoDINSliQ==", + "node_modules/aezeed": { + "version": "0.0.5", + "license": "MIT", "dependencies": { - "nanoid": "^3.1.15" + "aez": "^1.0.1", + "crc-32": "^1.2.1", + "randombytes": "^2.1.0", + "scryptsy": "^2.1.0" } }, - "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/@sideway/address": { - "version": "4.1.4", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "node_modules/ajv": { + "version": "8.17.1", + "license": "MIT", "dependencies": { - "@hapi/hoek": "^9.0.0" + "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/@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/anser": { + "version": "1.4.10", + "license": "MIT" }, - "node_modules/@sinonjs/commons": { - "version": "2.0.0", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "node_modules/ansi-escapes": { + "version": "4.3.2", + "dev": true, + "license": "MIT", "dependencies": { - "type-detect": "4.0.8" + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.0.2", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "node_modules/ansi-fragments": { + "version": "0.2.1", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^2.0.0" + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" } }, - "node_modules/@spsina/bip47": { - "version": "1.0.1", - "resolved": "git+ssh://git@github.com/BlueWallet/bip47.git#0a2f02c90350802f2ec93afa4e6c8843be2d687c", - "integrity": "sha512-lsgEpiEMDgpiYOA2kizOwiSS3vjTeLe2VnkOTIGnJ7Eu7Mkgl9dLES7oSLAjY64aQXr0VolqCRciRDc2nAC++w==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.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" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=6.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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/anymatch": { + "version": "3.1.3", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/@types/async": { - "version": "3.2.18", - "integrity": "sha512-/IsuXp3B9R//uRLi40VlIYoMp7OzhkunPe2fDu7jGfQXI9y3CDCx6FC4juRLSqrpmLst3vgsiK536AAGJFl4Ww==" + "node_modules/appdirsjs": { + "version": "1.2.7", + "license": "MIT" }, - "node_modules/@types/babel__core": { - "version": "7.20.0", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", - "dev": true, + "node_modules/argparse": { + "version": "1.0.10", + "license": "MIT", "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "sprintf-js": "~1.0.2" } }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" + "node_modules/array-back": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "node_modules/array-includes": { + "version": "3.1.8", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.3.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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@types/bn.js": { - "version": "4.11.6", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "dependencies": { - "@types/node": "*" + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/@types/bs58check": { - "version": "2.1.0", - "integrity": "sha512-OxsysnJQh82vy9DRbOcw9m2j/WiyqZLn0YBhKxdQ+aCwoHj+tWzyCgpwAkr79IfDXZKxc6h7k89T9pwS78CqTQ==", + "node_modules/array.prototype.findlast": { + "version": "1.2.5", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*" + "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@types/create-hash": { - "version": "1.2.2", - "integrity": "sha512-Fg8/kfMJObbETFU/Tn+Y0jieYewryLrbKwLCEIwPyklZZVY2qB+64KFjhplGSw+cseZosfFXctXO+PyIYD8iZQ==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*" + "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "node_modules/array.prototype.flat": { + "version": "1.3.3", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "dev": true, + "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "dev": true, + "license": "MIT", "dependencies": { - "@types/istanbul-lib-report": "*" + "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": ">= 0.4" } }, - "node_modules/@types/jest": { - "version": "29.4.0", - "integrity": "sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==", + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", "dev": true, + "license": "MIT", "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" + "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/asap": { + "version": "2.0.6", + "license": "MIT" }, - "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/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "license": "MIT", "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, - "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==", - "dev": true, + "node_modules/assert": { + "version": "2.1.0", + "license": "MIT", "dependencies": { - "@react-native/virtualized-lists": "^0.72.4", - "@types/react": "*" + "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/@types/react-native-vector-icons": { - "version": "6.4.13", - "integrity": "sha512-1PqFoKuXTSzMHwGMAr+REdYJBQAbe9xrww3ecZR0FsHcD1K+vGS/rxuAriL4rsI6+p69sZQjDzpEVAbDQcjSwA==", + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", "dependencies": { - "@types/react": "*", - "@types/react-native": "^0.70" + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" } }, - "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/astral-regex": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/@types/react-test-renderer": { - "version": "18.0.0", - "integrity": "sha512-C7/5FBJ3g3sqUahguGi03O79b8afNeSD6T8/GU50oQrJCU0bVCCGQHaGKUbg2Ce8VQEEqTw8/HiS6lXHHdgkdQ==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "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/async": { + "version": "3.2.6", + "license": "MIT" }, - "node_modules/@types/yargs": { - "version": "16.0.5", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", - "dependencies": { - "@types/yargs-parser": "*" + "node_modules/async-function": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" + "node_modules/async-limiter": { + "version": "1.0.1", + "license": "MIT" }, - "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==", - "dev": true, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "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" + "possible-typed-array-names": "^1.0.0" }, "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==", - "dev": true, + "node_modules/babel-jest": { + "version": "29.7.0", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.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": "^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" + "peerDependencies": { + "@babel/core": "^7.8.0" } }, - "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==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" + "node_modules/babel-jest/node_modules/@jest/transform": { + "version": "29.7.0", + "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" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", - "dev": true, + "node_modules/babel-jest/node_modules/@jest/types": { + "version": "29.6.3", + "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/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": "^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/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==", - "dev": true, + "node_modules/babel-jest/node_modules/@types/yargs": { + "version": "17.0.33", + "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" - }, - "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" + "@types/yargs-parser": "*" } }, - "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==", - "dev": true, + "node_modules/babel-jest/node_modules/jest-haste-map": { + "version": "29.7.0", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "eslint-visitor-keys": "^3.4.1" + "@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/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==", - "dev": true, + "node_modules/babel-jest/node_modules/jest-regex-util": { + "version": "29.6.3", + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", - "dev": true, + "node_modules/babel-jest/node_modules/jest-util": { + "version": "29.7.0", + "license": "MIT", "dependencies": { - "yallist": "^4.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": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", - "dev": true, + "node_modules/babel-jest/node_modules/jest-worker": { + "version": "29.7.0", + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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/@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/babel-jest/node_modules/supports-color": { + "version": "8.1.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" + "has-flag": "^4.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "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/babel-jest/node_modules/write-file-atomic": { + "version": "4.0.2", + "license": "ISC", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "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": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "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==", - "dev": true, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "license": "BSD-3-Clause", "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" + "@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": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=8" } }, - "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/babel-plugin-jest-hoist": { + "version": "29.6.3", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "eslint-visitor-keys": "^3.4.1" + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" }, "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/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_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.13", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.4", + "semver": "^6.3.1" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "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==", - "dev": true, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.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==", - "dev": true, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.4", + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@babel/helper-define-polyfill-provider": "^0.6.4" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.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/babel-plugin-syntax-hermes-parser": { + "version": "0.25.1", + "license": "MIT", + "dependencies": { + "hermes-parser": "0.25.1" + } }, - "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==", - "dev": true, + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@babel/plugin-syntax-flow": "^7.12.1" } }, - "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==", - "dev": true, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "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" + "@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": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@babel/core": "^7.0.0" } }, - "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==", - "dev": true, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "@typescript-eslint/visitor-keys": "6.2.0" + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" }, "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" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "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==", - "dev": true, + "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/bc-bech32": { + "resolved": "blue_modules/bc-bech32", + "link": true + }, + "node_modules/bech32": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/bigi": { + "version": "1.4.2" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "*" } }, - "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==", - "dev": true, + "node_modules/bindings": { + "version": "1.5.0", + "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" - }, + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bip174": { + "version": "2.1.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": ">=8.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==", - "dev": true, + "node_modules/bip21": { + "version": "2.0.3", + "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" - }, - "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" + "qs": "^6.3.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==", - "dev": true, + "node_modules/bip32": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-5.0.0.tgz", + "integrity": "sha512-h043yQ9n3iU4WZ8KLRpEECMl3j1yx2DQ1kcPlzWg8VZC0PtukbDiyLDKbe6Jm79mL6Tfg+WFuZMYxnzVyr/Hyw==", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.2.0", - "eslint-visitor-keys": "^3.4.1" + "@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": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=18.0.0" } }, - "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==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, + "node_modules/bip32/node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/lru-cache": { + "node_modules/bip32/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/bip32/node_modules/bs58": { "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, + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "base-x": "^5.0.0" } }, - "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==", - "dev": true, + "node_modules/bip32/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": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "@noble/hashes": "^1.2.0", + "bs58": "^6.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, + "node_modules/bip32/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": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=14.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, - "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" + "node_modules/bip32/node_modules/valibot": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.37.0.tgz", + "integrity": "sha512-FQz52I8RXgFgOHym3XHYSREbNtkgSjF9prvMFH1nBsRyfL6SfCzoT1GuSDTlbsuPubM7/6Kbw0ZMQb8A+V+VsQ==", + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" }, "peerDependenciesMeta": { "typescript": { @@ -6456,1346 +6340,1344 @@ } } }, - "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/bip32/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": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "bs58check": "^4.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/bip38": { + "version": "3.1.1", + "resolved": "git+ssh://git@github.com/BlueWallet/bip38.git#7ec4b1932b98eaaff16c5a26765a26466958e6b4", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "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" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "react-native-blue-crypto": "*" } }, - "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/bip38/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" }, - "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==", - "dev": true, + "node_modules/bip39": { + "version": "3.1.0", + "license": "ISC", "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" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@noble/hashes": "^1.2.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "node_modules/bip66": { + "version": "1.1.5", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "safe-buffer": "^5.0.1" } }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.3.8", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, + "node_modules/bip68": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/bip68/-/bip68-1.0.4.tgz", + "integrity": "sha512-O1htyufFTYy3EO0JkHg2CLykdXEtV2ssqw47Gq9A0WByp662xpJnMEB9m43LZjsSDjIAOozWRExlFQk2hlV1XQ==", + "license": "ISC", "engines": { - "node": ">=10" + "node": ">=4.5.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { - "version": "4.0.0", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "node_modules/bitcoin-ops": { + "version": "1.4.1", + "license": "MIT" }, - "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, + "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": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "@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" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "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, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18.0.0" } }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" + "node_modules/bitcoinjs-lib/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/abort-controller": { + "node_modules/bitcoinjs-lib/node_modules/bip174": { "version": "3.0.0", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-3.0.0.tgz", + "integrity": "sha512-N3vz3rqikLEu0d6yQL8GTrSkpYb35NQKWMR7Hlza0lOj6ZOlvQ3Xr7N9Y+JPebaCVoEUHdBeBSuLxcHr71r+Lw==", + "license": "MIT", "dependencies": { - "event-target-shim": "^5.0.0" + "uint8array-tools": "^0.0.9", + "varuint-bitcoin": "^2.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/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/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/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": { - "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/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/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/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/aez": { - "version": "1.0.1", - "integrity": "sha512-AtZEVcZcOLBAcNevz2e+Zu1zSLuPjtUA6CFY+ie8tF0IshKfcpJ0LiwnTqePOKaNidQQM/MfvtzUFOfAvHz5wQ==", - "dependencies": { - "blakejs": "^1.1.0", - "safe-buffer": "^5.1.1" - }, + "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": ">=8.10.0" + "node": ">=14.0.0" } }, - "node_modules/ajv": { - "version": "8.12.0", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "node_modules/bitcoinjs-message": { + "version": "2.2.0", + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "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" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=0.10" } }, - "node_modules/anser": { - "version": "1.4.10", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==" + "node_modules/bitcoinjs-message/node_modules/bech32": { + "version": "1.1.4", + "license": "MIT" }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, + "node_modules/bl": { + "version": "4.1.0", + "license": "MIT", "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "node_modules/ansi-fragments": { - "version": "0.2.1", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "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": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "node_modules/ansi-fragments/node_modules/ansi-regex": { - "version": "4.1.1", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "engines": { - "node": ">=6" - } + "node_modules/blakejs": { + "version": "1.2.1", + "license": "MIT" }, - "node_modules/ansi-fragments/node_modules/strip-ansi": { - "version": "5.2.0", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "node_modules/bluebird": { + "version": "3.7.2", + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "4.12.1", + "license": "MIT" + }, + "node_modules/bolt11": { + "version": "1.4.1", + "license": "MIT", "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" + "@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/ansi-regex": { - "version": "5.0.1", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } + "node_modules/bolt11/node_modules/bech32": { + "version": "1.1.4", + "license": "MIT" }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/bolt11/node_modules/bitcoinjs-lib": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.7.tgz", + "integrity": "sha512-tlf/r2DGMbF7ky1MgUqXHzypYHakkEnm0SZP23CJKIqNY/5uNAnMbFhMJdhjrL/7anfb/U8+AlpdjPWjPnAalg==", + "license": "MIT", "dependencies": { - "color-convert": "^1.9.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": ">=4" + "node": ">=8.0.0" } }, - "node_modules/appdirsjs": { - "version": "1.2.7", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==" + "node_modules/bolt11/node_modules/bitcoinjs-lib/node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", + "license": "MIT" }, - "node_modules/argparse": { - "version": "1.0.10", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/bolt11/node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "engines": { - "node": ">=0.10.0" + "base-x": "^4.0.0" } }, - "node_modules/arr-union": { - "version": "3.1.0", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-back": { - "version": "3.1.0", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "engines": { - "node": ">=6" + "node_modules/bolt11/node_modules/bs58check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" } }, - "node_modules/array-includes": { - "version": "3.1.6", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dev": true, + "node_modules/bolt11/node_modules/secp256k1": { + "version": "4.0.4", + "hasInstallScript": true, + "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" + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18.0.0" } }, - "node_modules/array-union": { - "version": "2.1.0", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } + "node_modules/boolbase": { + "version": "1.0.0", + "license": "ISC" }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, + "node_modules/brace-expansion": { + "version": "2.0.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" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "balanced-match": "^1.0.0" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, + "node_modules/braces": { + "version": "3.0.3", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" + "fill-range": "^7.1.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.1", - "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", - "dev": true, - "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" - } + "node_modules/brorand": { + "version": "1.1.0", + "license": "MIT" }, - "node_modules/asap": { - "version": "2.0.6", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + "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==", + "license": "BSD-2-Clause" }, - "node_modules/asn1.js": { - "version": "5.4.1", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "node_modules/browserify-aes": { + "version": "1.2.0", + "license": "MIT", "dependencies": { - "bn.js": "^4.0.0", + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" + "safe-buffer": "^5.0.1" } }, - "node_modules/assert": { - "version": "2.0.0", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "license": "MIT", "dependencies": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" } }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "engines": { - "node": ">=0.10.0" + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, - "node_modules/ast-types": { - "version": "0.14.2", - "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "license": "MIT", "dependencies": { - "tslib": "^2.0.1" + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" }, "engines": { - "node": ">=4" + "node": ">= 0.10" } }, - "node_modules/astral-regex": { - "version": "1.0.0", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "node_modules/browserify-rsa/node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "license": "MIT" + }, + "node_modules/browserify-sign": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "license": "ISC", + "dependencies": { + "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": ">=4" + "node": ">= 0.12" } }, - "node_modules/async": { - "version": "3.2.4", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "node_modules/browserify-sign/node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "license": "MIT" }, - "node_modules/atob": { - "version": "2.1.2", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "bin": { - "atob": "bin/atob.js" + "node_modules/browserify-sign/node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" }, "engines": { - "node": ">= 4.5.0" + "node": ">= 0.10" } }, - "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-sign/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/browserify-sign/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==", + "license": "MIT", + "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/babel-jest": { - "version": "29.4.3", - "integrity": "sha512-o45Wyn32svZE+LnMVWv/Z4x0SwtLbh4FyGcYtR20kIWd+rdrDZ9Fzq8Ml3MYLD+mZvEdzCjZsCnYZ2jpJyQ+Nw==", - "dev": true, + "node_modules/browserify-sign/node_modules/readable-stream/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==", + "license": "MIT" + }, + "node_modules/browserify-sign/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==", + "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" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" + "safe-buffer": "~5.1.0" } }, - "node_modules/babel-jest/node_modules/@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "dev": true, + "node_modules/browserify-sign/node_modules/string_decoder/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==", + "license": "MIT" + }, + "node_modules/browserslist": { + "version": "4.24.4", + "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": { - "@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" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/babel-jest/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/bs-logger": { + "version": "0.2.6", "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" + "fast-json-stable-stringify": "2.x" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 6" } }, - "node_modules/babel-jest/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, + "node_modules/bs58": { + "version": "4.0.1", + "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "base-x": "^3.0.2" } }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/bs58/node_modules/base-x": { + "version": "3.0.11", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "safe-buffer": "^5.0.1" } }, - "node_modules/babel-jest/node_modules/anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, + "node_modules/bs58check": { + "version": "2.1.2", + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" } }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/bser": { + "version": "2.1.1", + "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" + "node-int64": "^0.4.0" } }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/bson": { + "version": "4.7.2", + "license": "Apache-2.0", "dependencies": { - "color-name": "~1.1.4" + "buffer": "^5.6.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=6.9.0" } }, - "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/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/fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" + "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" + } ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/buffer-equals": { + "version": "1.0.4", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/babel-jest/node_modules/jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", + "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": { - "@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" - }, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/babel-jest/node_modules/jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "node_modules/builtins": { + "version": "5.1.0", "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" } }, - "node_modules/babel-jest/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/builtins/node_modules/semver": { + "version": "7.7.1", "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" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/babel-jest/node_modules/jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, + "node_modules/bunyamin": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/bunyamin/-/bunyamin-1.6.3.tgz", + "integrity": "sha512-m1hAijFhu8pFiidsVc0XEDic46uxPK+mKNLqkb5mluNx0nTolNzx/DjwMqHChQWCgfOLMjKYJJ2uPTQLE6t4Ng==", + "license": "MIT", "dependencies": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "@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": "^14.15.0 || ^16.10.0 || >=18.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-jest/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "node_modules/bunyan": { + "version": "1.8.15", + "engines": [ + "node >=0.10.0" + ], + "license": "MIT", + "bin": { + "bunyan": "bin/bunyan" + }, + "optionalDependencies": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" + } + }, + "node_modules/bunyan-debug-stream": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bunyan-debug-stream/-/bunyan-debug-stream-3.1.1.tgz", + "integrity": "sha512-LfMcz4yKM6s9BP5dfT63Prb5B2hAjReLAfQzLbNQF7qBHtn3P1v+/yn0SZ6UAr4PC3VZRX/QzK7HYkkY0ytokQ==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "chalk": "^4.1.2" }, "engines": { - "node": ">=10" + "node": ">=0.12.0" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "peerDependencies": { + "bunyan": "*" + }, + "peerDependenciesMeta": { + "bunyan": { + "optional": true + } } }, - "node_modules/babel-jest/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, + "node_modules/bytes": { + "version": "3.1.2", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/caf": { + "version": "15.0.1", + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.8", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-jest/node_modules/write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 0.4" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, + "node_modules/call-bound": { + "version": "1.0.4", + "license": "MIT", "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" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.4.3", - "integrity": "sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q==", - "dev": true, + "node_modules/caller-callsite": { + "version": "2.0.0", + "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" + "callsites": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=4" } }, - "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" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node_modules/caller-callsite/node_modules/callsites": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "node_modules/caller-path": { + "version": "2.0.0", + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" + "caller-callsite": "^2.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=4" } }, - "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/callsites": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=6" } }, - "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/camelcase": { + "version": "5.3.1", + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "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" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } + "node_modules/caniuse-lite": { + "version": "1.0.30001712", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.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" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } + "node_modules/cbor-sync": { + "version": "1.0.4", + "license": "MIT" }, - "node_modules/babel-preset-jest": { - "version": "29.4.3", - "integrity": "sha512-gWx6COtSuma6n9bw+8/F+2PCXrIgxV/D1TJFnp6OyBK2cxPWg0K9p/sriNYeifKjpUkMViWQ09DSWtzJQRETsw==", - "dev": true, + "node_modules/chalk": { + "version": "4.1.2", + "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^29.4.3", - "babel-preset-current-node-syntax": "^1.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/balanced-match": { + "node_modules/char-regex": { "version": "1.0.2", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } }, - "node_modules/base": { - "version": "0.11.2", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "node_modules/chownr": { + "version": "1.1.4", + "license": "ISC" + }, + "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": { - "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" + "@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": ">=0.10.0" + "node": ">=12.13.0" } }, - "node_modules/base-64": { - "version": "0.1.0", - "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" + "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": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/base-x": { - "version": "3.0.9", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "node_modules/chromium-edge-launcher": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", + "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", + "license": "Apache-2.0", "dependencies": { - "safe-buffer": "^5.0.1" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" } }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "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": { - "is-descriptor": "^1.0.0" + "is-docker": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.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" + "url": "https://github.com/sponsors/sibiraj-s" } - ] - }, - "node_modules/bc-bech32": { - "resolved": "blue_modules/bc-bech32", - "link": true - }, - "node_modules/bech32": { - "version": "2.0.0", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" - }, - "node_modules/big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true, + ], + "license": "MIT", "engines": { - "node": ">=0.6" + "node": ">=8" } }, - "node_modules/bigi": { - "version": "1.4.2", - "integrity": "sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw==" - }, - "node_modules/bignumber.js": { - "version": "9.1.1", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "node_modules/cipher-base": { + "version": "1.0.6", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, "engines": { - "node": "*" + "node": ">= 0.10" } }, - "node_modules/bindings": { - "version": "1.5.0", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "license": "MIT", "dependencies": { - "file-uri-to-path": "1.0.0" + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/bip174": { - "version": "2.1.0", - "integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==", + "node_modules/cli-spinners": { + "version": "2.9.2", + "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bip21": { - "version": "2.0.3", - "integrity": "sha512-L4ODmASLjsHYAU+TG7xffkYNMvHzAe4mkVX7mcvOUyKAr/MDBPrsRgqUhE8EmKdeEKHk5SYpX1Aexzvm/6WdbQ==", + "node_modules/cliui": { + "version": "8.0.1", + "license": "ISC", "dependencies": { - "qs": "^6.3.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "node_modules/bip32": { - "version": "3.0.1", - "integrity": "sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "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" + "ansi-regex": "^5.0.1" }, "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/clone": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "node": ">=0.8" + } }, - "node_modules/bip38": { - "version": "3.1.1", - "resolved": "git+ssh://git@github.com/BlueWallet/bip38.git#60018f7c61e70584647d724b9b9264a7ebc8182e", + "node_modules/clone-deep": { + "version": "4.0.1", + "license": "MIT", "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" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" }, - "peerDependencies": { - "react-native-blue-crypto": "*" + "engines": { + "node": ">=6" } }, - "node_modules/bip38/node_modules/safe-buffer": { - "version": "5.1.2", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/bip39": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", - "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", - "dependencies": { - "@noble/hashes": "^1.2.0" + "node_modules/co": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" } }, - "node_modules/bip66": { - "version": "1.1.5", - "integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==", - "dependencies": { - "safe-buffer": "^5.0.1" - } + "node_modules/coinselect": { + "version": "3.1.13", + "resolved": "git+ssh://git@github.com/BlueWallet/coinselect.git#35f803875f37473e95e4207dd7ac8991c4ac7812", + "integrity": "sha512-fNzdxxhmWuUvLGcYtLUTuUXxDHNsSOw9kT1VZElASG3cJHpEF6fAC955qkmHxxUfpHcIByxIto93Kho2oHcuuQ==", + "license": "MIT" }, - "node_modules/bitcoin-ops": { - "version": "1.4.1", - "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==" + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "dev": true, + "license": "MIT" }, - "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/color": { + "version": "4.2.3", + "license": "MIT", "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" + "color-convert": "^2.0.1", + "color-string": "^1.9.0" }, "engines": { - "node": ">=8.0.0" - } - }, - "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==", - "dependencies": { - "base-x": "^4.0.0" - } - }, - "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==", - "dependencies": { - "@noble/hashes": "^1.2.0", - "bs58": "^5.0.0" + "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", + "license": "MIT" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "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", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "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", + "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/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", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "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", + "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/browserify-aes": { - "version": "1.2.0", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "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/command-line-usage/node_modules/array-back": { + "version": "4.0.2", + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "node_modules/command-line-usage/node_modules/chalk": { + "version": "2.4.2", + "license": "MIT", "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/browserify-des": { - "version": "1.0.2", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "node_modules/command-line-usage/node_modules/color-convert": { + "version": "1.9.3", + "license": "MIT", "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "color-name": "1.1.3" } }, - "node_modules/browserify-rsa": { - "version": "4.1.0", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" + "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/browserify-rsa/node_modules/bn.js": { - "version": "5.2.1", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + "node_modules/command-line-usage/node_modules/has-flag": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "node_modules/browserify-sign": { - "version": "4.2.1", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "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" + "node_modules/command-line-usage/node_modules/supports-color": { + "version": "5.5.0", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/browserify-sign/node_modules/bn.js": { - "version": "5.2.1", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + "node_modules/command-line-usage/node_modules/typical": { + "version": "5.2.0", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "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/commander": { + "version": "9.5.0", + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "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" + "mime-db": ">= 1.43.0 < 2" }, - "bin": { - "browserslist": "cli.js" + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.0", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">= 0.8.0" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", "dependencies": { - "fast-json-stable-stringify": "2.x" + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "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": { + "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", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" }, "engines": { - "node": ">= 6" + "node": ">= 0.10.0" } }, - "node_modules/bs58": { - "version": "4.0.1", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", "dependencies": { - "base-x": "^3.0.2" + "ms": "2.0.0" } }, - "node_modules/bs58check": { - "version": "2.1.2", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/consola": { + "version": "2.15.3", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.41.0", + "license": "MIT", "dependencies": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" + "browserslist": "^4.24.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/bser": { - "version": "2.1.1", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "node_modules/core-util-is": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "license": "MIT", "dependencies": { - "node-int64": "^0.4.0" + "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/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "license": "MIT", "dependencies": { - "buffer": "^5.6.0" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/crc": { + "version": "3.8.0", + "license": "MIT", + "dependencies": { + "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 +7692,1582 @@ "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", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "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" - } - }, - "node_modules/bunyan": { - "version": "1.8.15", - "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", - "engines": [ - "node >=0.10.0" - ], - "bin": { - "bunyan": "bin/bunyan" - }, - "optionalDependencies": { - "dtrace-provider": "~0.8", - "moment": "^2.19.3", - "mv": "~2", - "safe-json-stringify": "~1" + "@types/yargs-parser": "*" } }, - "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/create-jest/node_modules/jest-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.1.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" }, "engines": { - "node": ">=0.12.0" - }, - "peerDependencies": { - "bunyan": "*" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "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": ">=8" + "node": ">= 0.10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "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/node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" }, "engines": { - "node": ">=10" + "node": ">= 0.10" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "license": "MIT" + }, + "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/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/fb55" } }, - "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/css-tree": { + "version": "1.1.3", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "mdn-data": "2.0.14", + "source-map": "^0.6.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=8.0.0" } }, - "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/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==", + "node_modules/css-what": { + "version": "6.1.0", + "license": "BSD-2-Clause", "engines": { - "node": ">=8" + "node": ">= 6" + }, + "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/csstype": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/data-view-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" } }, - "node_modules/cache-base": { + "node_modules/data-view-byte-offset": { "version": "1.0.1", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "license": "MIT", "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" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=0.10.0" - } - }, - "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/call-bind": { - "version": "1.0.2", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "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/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "license": "MIT", "dependencies": { - "callsites": "^2.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=4" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "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/decamelize": { + "version": "4.0.0", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/decode-uri-component": { + "version": "0.2.2", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "license": "MIT", "dependencies": { - "caller-callsite": "^2.0.0" + "mimic-response": "^3.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/callsites": { - "version": "3.1.0", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/dedent": { + "version": "1.5.3", "dev": true, - "engines": { - "node": ">=6" + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/camelcase": { - "version": "5.3.1", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/deep-extend": { + "version": "0.6.0", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=4.0.0" } }, - "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/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" }, - "node_modules/caseless": { - "version": "0.12.0", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + "node_modules/deepmerge": { + "version": "4.3.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/cbor-sync": { + "node_modules/defaults": { "version": "1.0.4", - "integrity": "sha512-GWlXN4wiz0vdWWXBU71Dvc1q3aBo0HytqwAZnXF1wOwjqNnDWA1vZ1gDMFLlqohak31VQzmhiYfiCX5QSSfagA==" + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/chalk": { - "version": "2.4.2", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/define-data-property": { + "version": "1.1.4", + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/define-properties": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, "engines": { - "node": ">=0.8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", "engines": { - "node": ">=10" - } - }, - "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": ">=0.4.0" } }, - "node_modules/ci-info": { - "version": "3.8.0", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], + "node_modules/depd": { + "version": "2.0.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/cipher-base": { - "version": "1.0.4", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "license": "MIT", "dependencies": { "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "minimalistic-assert": "^1.0.0" } }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true + "node_modules/destroy": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, - "node_modules/class-utils": { - "version": "0.3.6", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, + "node_modules/detect-libc": { + "version": "2.0.3", + "license": "Apache-2.0", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dependencies": { - "is-descriptor": "^0.1.0" - }, + "node_modules/detect-newline": { + "version": "3.1.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "node_modules/detox": { + "version": "20.43.0", + "resolved": "https://registry.npmjs.org/detox/-/detox-20.43.0.tgz", + "integrity": "sha512-xEKwFw3t9jeMm85KDq1S51wt40L5wWoxskhIw8Q58slu9PGz18KZA7pGJtwr2px+RPX+oss1Vpxuaif71nTKsQ==", + "hasInstallScript": true, + "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" + "@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": ">=0.10.0" + "node": ">=14" + }, + "peerDependencies": { + "jest": "30.x.x || 29.x.x || 28.x.x || ^27.2.5" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + } } }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/detox/node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=14.14" } }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "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": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "dependencies": { - "kind-of": "^3.0.2" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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/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": { - "is-buffer": "^1.1.5" + "universalify": "^2.0.0" }, - "engines": { - "node": ">=0.10.0" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "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==", + "license": "ISC", "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/class-utils/node_modules/kind-of": { - "version": "5.1.0", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "node_modules/detox/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": ">=0.10.0" + "node": ">=10" } }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "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": { - "restore-cursor": "^3.1.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/cli-spinners": { - "version": "2.7.0", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "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": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "node": ">= 10.0.0" } }, - "node_modules/clone": { - "version": "1.0.4", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "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": ">=0.8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" + "node": ">=8.3.0" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/co": { - "version": "4.6.0", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "node_modules/diff-sequences": { + "version": "29.6.3", "dev": true, + "license": "MIT", "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/coinselect": { - "version": "3.1.13", - "integrity": "sha512-iJOrKH/7N9gX0jRkxgOHuGjvzvoxUMSeylDhH1sHn+CjLjdin5R0Hz2WEBu/jrZV5OrHcm+6DMzxwu9zb5mSZg==" + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true + "node_modules/dijkstrajs": { + "version": "1.0.3", + "license": "MIT" }, - "node_modules/collection-visit": { - "version": "1.0.0", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "path-type": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/color": { - "version": "3.2.1", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/color-convert": { - "version": "1.9.3", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/dom-serializer": { + "version": "2.0.0", + "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "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/domelementtype": { + "version": "2.3.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/domhandler": { + "version": "5.0.3", + "license": "BSD-2-Clause", "dependencies": { - "delayed-stream": "~1.0.0" + "domelementtype": "^2.3.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "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/domutils": { + "version": "3.2.2", + "license": "BSD-2-Clause", "dependencies": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" }, - "engines": { - "node": ">=4.0.0" + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/command-line-usage": { - "version": "6.1.3", - "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", + "node_modules/drbg.js": { + "version": "1.0.1", + "license": "MIT", "dependencies": { - "array-back": "^4.0.2", - "chalk": "^2.4.2", - "table-layout": "^1.0.2", - "typical": "^5.2.0" + "browserify-aes": "^1.0.6", + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/command-line-usage/node_modules/array-back": { - "version": "4.0.2", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/command-line-usage/node_modules/typical": { - "version": "5.2.0", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "engines": { - "node": ">=8" + "node": ">=0.10" } }, - "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==", + "node_modules/dtrace-provider": { + "version": "0.8.8", + "hasInstallScript": true, + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "nan": "^2.14.0" + }, "engines": { - "node": ">=4.0.0" + "node": ">=0.10" } }, - "node_modules/commondir": { + "node_modules/dunder-proto": { "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==", + "license": "MIT", "dependencies": { - "mime-db": ">= 1.43.0 < 2" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" } }, - "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/duplexer2": { + "version": "0.1.4", + "license": "BSD-3-Clause", "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" - }, - "engines": { - "node": ">= 0.8.0" + "readable-stream": "^2.0.2" } }, - "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/duplexer2/node_modules/isarray": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "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/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": { + "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/concat-map": { - "version": "0.0.1", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "license": "MIT" }, - "node_modules/concat-stream": { - "version": "2.0.0", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" + "safe-buffer": "~5.1.0" } }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, + "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==", + "license": "MIT", "engines": { - "node": ">= 0.10.0" + "node": ">=6.0.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==", + "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": { - "ms": "2.0.0" + "uint8array-tools": "^0.0.8", + "valibot": "^0.37.0", + "wif": "^5.0.0" + }, + "engines": { + "node": ">=20.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/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/consola": { - "version": "2.15.3", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + "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": { + "base-x": "^5.0.0" + } }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "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": { + "@noble/hashes": "^1.2.0", + "bs58": "^6.0.0" + } }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "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": ">=0.10.0" + "node": ">=14.0.0" } }, - "node_modules/core-js-compat": { - "version": "3.28.0", - "integrity": "sha512-myzPgE7QodMg4nnd3K1TDoES/nADRStM8Gpz0D6nhkwbmwEnE0ZGJgoWsvQ722FR8D7xS0n0LV556RcEicjTyg==", - "dependencies": { - "browserslist": "^4.21.5" + "node_modules/ecpair/node_modules/valibot": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.37.0.tgz", + "integrity": "sha512-FQz52I8RXgFgOHym3XHYSREbNtkgSjF9prvMFH1nBsRyfL6SfCzoT1GuSDTlbsuPubM7/6Kbw0ZMQb8A+V+VsQ==", + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "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/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": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" + "bs58check": "^4.0.0" } }, - "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/ecurve": { + "version": "1.0.6", + "license": "MIT", "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" + "bigi": "^1.1.0", + "safe-buffer": "^5.0.1" } }, - "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/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "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==", + "node_modules/electron-to-chromium": { + "version": "1.5.132", + "license": "ISC" + }, + "node_modules/electrum-client": { + "version": "3.1.1", + "resolved": "git+ssh://git@github.com/BlueWallet/rn-electrum-client.git#d9f511d97fe64906ce0672f97d689794a245853a", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/crc": { - "version": "3.8.0", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "dependencies": { - "buffer": "^5.1.0" - } - }, - "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" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "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/create-hmac": { - "version": "1.1.7", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "node_modules/electrum-mnemonic": { + "version": "2.0.0", + "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" + "create-hmac": "^1.1.7", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0" } }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "node_modules/elliptic": { + "version": "6.6.1", + "license": "MIT", "dependencies": { - "node-fetch": "2.6.7" + "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/cross-fetch/node_modules/node-fetch": { - "version": "2.6.7", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, + "node_modules/emittery": { + "version": "0.13.1", + "dev": true, + "license": "MIT", "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": ">=12" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/cross-spawn": { - "version": "4.0.2", - "integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==", - "dependencies": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "node_modules/cross-spawn/node_modules/lru-cache": { - "version": "4.1.5", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/cross-spawn/node_modules/yallist": { - "version": "2.1.2", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + "node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" }, - "node_modules/crypto-js": { - "version": "4.1.1", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" }, - "node_modules/css-select": { - "version": "5.1.0", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "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/encodeurl": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "node_modules/css-tree": { - "version": "1.1.3", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "node_modules/end-of-stream": { + "version": "1.4.4", + "license": "MIT", "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" + "once": "^1.4.0" } }, - "node_modules/css-what": { - "version": "6.1.0", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "node_modules/entities": { + "version": "4.5.0", + "license": "BSD-2-Clause", "engines": { - "node": ">= 6" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/fb55" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/csstype": { - "version": "3.1.1", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" - }, - "node_modules/dayjs": { - "version": "1.11.9", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", - "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" + "node_modules/env-paths": { + "version": "2.2.1", + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "node_modules/debug": { - "version": "4.3.4", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" + "node_modules/envinfo": { + "version": "7.14.0", + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=4" } }, - "node_modules/decamelize": { - "version": "4.0.0", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/error-ex": { + "version": "1.3.2", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" } }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "engines": { - "node": ">=0.10" + "node_modules/error-stack-parser": { + "version": "2.1.4", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "node_modules/errorhandler": { + "version": "1.5.1", + "license": "MIT", "dependencies": { - "mimic-response": "^3.1.0" + "accepts": "~1.3.7", + "escape-html": "~1.0.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "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" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "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==", - "dev": true, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "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-shim-unscopables": { + "version": "1.1.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" + "hasown": "^2.0.2" }, "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">= 0.4" } }, - "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==", + "node_modules/es-to-primitive": { + "version": "1.3.0", "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, "engines": { - "node": ">=14.18.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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, + "node_modules/escalade": { + "version": "3.2.0", + "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/default-browser/node_modules/mimic-fn": { + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" + }, + "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, + "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/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": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, - "node_modules/defaults": { - "version": "1.0.4", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "ms": "^2.1.1" } }, - "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-module-utils": { + "version": "2.12.0", "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, "engines": { - "node": ">=12" + "node": ">=4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/define-properties": { - "version": "1.2.0", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "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": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" }, "engines": { - "node": ">= 0.4" + "node": "^14.18.0 || >=16.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "eslint": ">=8" } }, - "node_modules/define-property": { - "version": "2.0.2", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "node_modules/eslint-plugin-eslint-comments": { + "version": "3.2.0", + "dev": true, + "license": "MIT", "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "escape-string-regexp": "^1.0.5", + "ignore": "^5.0.5" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">=0.8.0" } }, - "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/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,1213 +9275,1121 @@ "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==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.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==", + "node_modules/eslint-plugin-prettier": { + "version": "5.2.6", + "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.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_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==", - "dependencies": { - "universalify": "^2.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "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": { + "version": "7.37.5", + "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^5.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" }, "engines": { - "node": ">=10" + "node": ">=4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, - "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==", - "dependencies": { - "yallist": "^4.0.0" - }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "dev": true, + "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_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/cross-spawn": { + "version": "7.0.6", "dev": true, + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=6.0.0" + "node": ">= 8" } }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" + "esrecurse": "^4.3.0", + "estraverse": "^5.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/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://opencollective.com/eslint" + } }, - "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/eslint/node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", "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" + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "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": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", + "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" + }, + "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/espree/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-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/ecpair": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.0.1.tgz", - "integrity": "sha512-iT3wztQMeE/nDTlfnAg8dAFUfBS7Tq2BXzq3ae6L+pWgFU0fQ3l0woTzdTBrJV3OxBjxbzjq8EQhAbEmJNWFSw==", - "dependencies": { - "randombytes": "^2.1.0", - "typeforce": "^1.18.0", - "wif": "^2.0.6" + "node_modules/esprima": { + "version": "4.0.1", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, "engines": { - "node": ">=8.0.0" + "node": ">=4" } }, - "node_modules/ecurve": { - "version": "1.0.6", - "integrity": "sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w==", + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "bigi": "^1.1.0", - "safe-buffer": "^5.0.1" + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" } }, - "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/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } }, - "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", + "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", + "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", + "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", + "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", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "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/execa/node_modules/cross-spawn": { + "version": "7.0.6", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">= 8" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/execa/node_modules/which": { + "version": "2.0.2", + "license": "ISC", "dependencies": { - "is-arrayish": "^0.2.1" + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "dependencies": { - "stackframe": "^1.3.4" + "node_modules/exeunt": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/exeunt/-/exeunt-1.1.0.tgz", + "integrity": "sha512-dd++Yn/0Fp+gtJ04YHov7MeAii+LFivJc6KqnJNfplzLVUkUDrfKoQDTLlCgzcW15vY5hKlHasWeIsQJ8agHsw==", + "license": "MPL-2.0", + "engines": { + "node": ">=0.10" } }, - "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/exit": { + "version": "0.1.2", + "dev": true, "engines": { - "node": ">= 0.8" + "node": ">= 0.8.0" } }, - "node_modules/es-abstract": { - "version": "1.21.1", - "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", + "node_modules/expand-template": { + "version": "2.0.3", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "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/ansi-styles": { + "version": "5.2.0", "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es6-object-assign": { - "version": "1.1.0", - "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==" - }, - "node_modules/escalade": { - "version": "3.1.1", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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/escape-string-regexp": { - "version": "4.0.0", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" + "node_modules/expect/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" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/eslint": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz", - "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==", + "node_modules/expect/node_modules/jest-util": { + "version": "29.7.0", "dev": true, + "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", + "@jest/types": "^29.6.3", + "@types/node": "*", "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" + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^14.15.0 || ^16.10.0 || >=18.0.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==", + "node_modules/expect/node_modules/pretty-format": { + "version": "29.7.0", "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, - "peerDependencies": { - "eslint": ">=7.0.0" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/eslint-config-standard": { - "version": "17.0.0", - "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "node_modules/exponential-backoff": { + "version": "3.1.2", + "license": "Apache-2.0" + }, + "node_modules/fast-base64-decode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz", + "integrity": "sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.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" - } - ], - "peerDependencies": { - "eslint": "^8.0.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0", - "eslint-plugin-promise": "^6.0.0" + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "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.8" + }, + "engines": { + "node": ">=8.6.0" } }, - "node_modules/eslint-config-standard-jsx": { - "version": "11.0.0", - "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==", + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "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" + "url": "https://github.com/sponsors/fastify" }, { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" + "type": "opencollective", + "url": "https://opencollective.com/fastify" } ], - "peerDependencies": { - "eslint": "^8.8.0", - "eslint-plugin-react": "^7.28.0" - } + "license": "BSD-3-Clause" }, - "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": "4.5.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" + "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "peerDependencies": { - "eslint": "^8.8.0", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.6.0" + "license": "MIT", + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, + "node_modules/fastq": { + "version": "1.19.1", + "license": "ISC", "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" + "reusify": "^1.0.4" } }, - "node_modules/eslint-import-resolver-node/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-module-utils": { - "version": "2.7.4", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^3.2.7" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/filelist": { + "version": "1.0.4", "dev": true, + "license": "Apache-2.0", "dependencies": { - "ms": "^2.1.1" + "minimatch": "^5.0.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==", + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", "dev": true, + "license": "ISC", "dependencies": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.6.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "eslint": ">=8" + "node": ">=10" } }, - "node_modules/eslint-plugin-eslint-comments": { - "version": "3.2.0", - "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", - "dev": true, + "node_modules/fill-range": { + "version": "7.1.1", + "license": "MIT", "dependencies": { - "escape-string-regexp": "^1.0.5", - "ignore": "^5.0.5" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=6.5.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" + "node": ">=8" } }, - "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": { - "version": "1.0.5", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, + "node_modules/filter-obj": { + "version": "1.1.0", + "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-ft-flow": { - "version": "2.0.3", - "integrity": "sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg==", - "dev": true, + "node_modules/finalhandler": { + "version": "1.1.2", + "license": "MIT", "dependencies": { - "lodash": "^4.17.21", - "string-natural-compare": "^3.0.1" - }, - "engines": { - "node": ">=12.22.0" - }, - "peerDependencies": { - "@babel/eslint-parser": "^7.12.0", - "eslint": "^8.1.0" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, - "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" + "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": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "node": ">= 0.8" } }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/find-cache-dir": { "version": "2.1.0", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/eslint-plugin-jest": { - "version": "26.9.0", - "integrity": "sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==", - "dev": true, + "node_modules/find-cache-dir/node_modules/find-up": { + "version": "3.0.0", + "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^5.10.0" + "locate-path": "^3.0.0" }, "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": ">=6" } }, - "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-cache-dir/node_modules/locate-path": { + "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" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" }, "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=7.0.0" + "node": ">=6" } }, - "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-cache-dir/node_modules/make-dir": { + "version": "2.1.0", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "pify": "^4.0.1", + "semver": "^5.6.0" }, "engines": { - "node": ">=10" + "node": ">=6" } }, - "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==", - "dev": true, + "node_modules/find-cache-dir/node_modules/p-limit": { + "version": "2.3.0", + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "p-try": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/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, + "node_modules/find-cache-dir/node_modules/p-locate": { + "version": "3.0.0", + "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" + "p-limit": "^2.0.0" }, "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": ">=6" } }, - "node_modules/eslint-plugin-promise": { - "version": "6.1.1", - "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", - "dev": true, + "node_modules/find-cache-dir/node_modules/path-exists": { + "version": "3.0.0", + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "node": ">=4" } }, - "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/find-cache-dir/node_modules/pkg-dir": { + "version": "3.0.0", + "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" + "find-up": "^3.0.0" }, "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, - "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_modules/find-cache-dir/node_modules/semver": { + "version": "5.7.2", + "license": "ISC", + "bin": { + "semver": "bin/semver" } }, - "node_modules/eslint-plugin-react-native": { - "version": "4.0.0", - "integrity": "sha512-kMmdxrSY7A1WgdqaGC+rY/28rh7kBGNBRsk48ovqkQmdg5j4K+DaFmegENDzMrdLkoufKGRNkKX6bgSwQTCAxQ==", - "dev": true, + "node_modules/find-replace": { + "version": "3.0.0", + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.7.4", - "eslint-plugin-react-native-globals": "^0.1.1" + "array-back": "^3.0.1" }, - "peerDependencies": { - "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8" + "engines": { + "node": ">=4.0.0" } }, - "node_modules/eslint-plugin-react-native-globals": { - "version": "0.1.2", - "integrity": "sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==", - "dev": true - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, + "node_modules/find-up": { + "version": "5.0.0", + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, + "node_modules/flat": { + "version": "5.0.2", + "license": "BSD-3-Clause", "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "flat": "cli.js" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/flat-cache": { + "version": "3.2.0", "dev": true, + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=8.0.0" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "4.3.0", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/flatted": { + "version": "3.3.3", "dev": true, + "license": "ISC" + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "license": "MIT" + }, + "node_modules/flow-parser": { + "version": "0.266.1", + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=0.4.0" } }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/for-each": { + "version": "0.3.5", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "node_modules/form-data": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", + "license": "MIT", "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" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">= 6" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/fresh": { + "version": "0.5.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=6 <7 || >=8" } }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "node_modules/fs.realpath": { + "version": "1.0.0", + "license": "ISC" }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "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": { - "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", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/functions-have-names": { + "version": "1.2.3", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/funpermaproxy": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/funpermaproxy/-/funpermaproxy-1.1.0.tgz", + "integrity": "sha512-2Sp1hWuO8m5fqeFDusyhKqYPT+7rGLw34N3qonDcdRP8+n7M7Gl/yKp/q7oCxnnJ6pWCectOmLFJpsMU/++KrQ==", + "license": "Apache-2.0", "engines": { - "node": ">=7.0.0" + "node": ">=8.3.0" } }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "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-caller-file": { + "version": "2.0.5", + "license": "ISC", "engines": { - "node": ">= 8" + "node": "6.* || 8.* || >= 10.*" } }, - "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==", - "dev": true, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "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": "^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==", - "dev": true, + "node_modules/get-package-type": { + "version": "0.1.0", + "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/eslint/node_modules/find-up": { - "version": "5.0.0", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "license": "MIT", "engines": { "node": ">=10" }, @@ -10495,117 +10397,118 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.20.0", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "node_modules/get-symbol-description": { + "version": "1.1.0", "dev": true, + "license": "MIT", "dependencies": { - "type-fest": "^0.20.2" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "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" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/get-tsconfig": { + "version": "4.10.0", "dev": true, + "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "resolve-pkg-maps": "^1.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "node_modules/github-from-package": { + "version": "0.0.0", + "license": "MIT" }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "node_modules/glob": { + "version": "7.2.3", + "license": "ISC", "dependencies": { - "p-locate": "^5.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/p-locate": { - "version": "5.0.0", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/glob-parent": { + "version": "6.0.2", "dev": true, + "license": "ISC", "dependencies": { - "p-limit": "^3.0.2" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10.13.0" } }, - "node_modules/eslint/node_modules/shebang-command": { - "version": "2.0.0", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/eslint/node_modules/shebang-regex": { - "version": "3.0.0", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "node_modules/globals": { + "version": "11.12.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/globalthis": { + "version": "1.0.4", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/globby": { + "version": "11.1.0", "dev": true, + "license": "MIT", + "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" + }, "engines": { "node": ">=10" }, @@ -10613,776 +10516,707 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/which": { - "version": "2.0.2", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", "dev": true, - "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" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } + "license": "MIT" }, - "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-bigints": { + "version": "1.1.0", "dev": true, + "license": "MIT", "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-flag": { + "version": "4.0.0", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/esquery": { - "version": "1.4.2", - "integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==", - "dev": true, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "estraverse": "^5.1.0" + "es-define-property": "^1.0.0" }, - "engines": { - "node": ">=0.10" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/has-proto": { + "version": "1.2.0", "dev": true, + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" + "dunder-proto": "^1.0.0" }, "engines": { - "node": ">=4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esutils": { - "version": "2.0.3", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/hash-base": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=4" } }, - "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/hash.js": { + "version": "1.1.7", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "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/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==", + "license": "MIT" }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "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==", + "license": "MIT", "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" + "hermes-estree": "0.25.1" } }, - "node_modules/execa": { - "version": "5.1.1", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/hmac-drbg": { + "version": "1.0.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": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "7.0.3", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "license": "BSD-3-Clause", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "react-is": "^16.7.0" } }, - "node_modules/execa/node_modules/shebang-command": { - "version": "2.0.0", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "license": "MIT" }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "3.0.0", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } + "node_modules/hosted-git-info": { + "version": "2.8.9", + "license": "ISC" }, - "node_modules/execa/node_modules/which": { + "node_modules/html-escaper": { "version": "2.0.2", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">= 8" + "node": ">= 0.8" } }, - "node_modules/exit": { - "version": "0.1.2", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">= 0.8" } }, - "node_modules/exit-on-epipe": { - "version": "1.0.1", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "node_modules/human-signals": { + "version": "2.1.0", + "license": "Apache-2.0", "engines": { - "node": ">=0.8" + "node": ">=10.17.0" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "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": ">=6" + "node": ">= 4" } }, - "node_modules/expect": { - "version": "29.4.3", - "integrity": "sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg==", - "dev": true, + "node_modules/image-size": { + "version": "1.2.1", + "license": "MIT", "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" + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=16.x" } }, - "node_modules/expect/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, + "node_modules/import-fresh": { + "version": "3.3.1", + "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" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/expect/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=4" } }, - "node_modules/expect/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/import-local": { + "version": "3.2.0", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expect/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/imurmurhash": { + "version": "0.1.4", + "license": "MIT", "engines": { - "node": ">=7.0.0" + "node": ">=0.8.19" } }, - "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": { + "node_modules/indent-string": { "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/expect/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, + "node_modules/inflight": { + "version": "1.0.6", + "license": "ISC", "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" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/expect/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "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": { - "@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" + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" } }, - "node_modules/expect/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/invariant": { + "version": "2.2.4", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "loose-envify": "^1.0.0" } }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "node_modules/is-arguments": { + "version": "1.2.0", + "license": "MIT", "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "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-array-buffer": { + "version": "3.0.5", + "dev": true, + "license": "MIT", "dependencies": { - "is-plain-object": "^2.0.4" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "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/is-arrayish": { + "version": "0.2.1", + "license": "MIT" }, - "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-async-function": { + "version": "2.1.1", "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" + "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.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-bigint": { + "version": "1.1.0", "dev": true, + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "has-bigints": "^1.0.2" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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" - } - ], + "node_modules/is-boolean-object": { + "version": "1.2.2", + "dev": true, + "license": "MIT", "dependencies": { - "strnum": "^1.0.5" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fastq": { - "version": "1.15.0", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "node_modules/is-builtin-module": { + "version": "3.2.1", "dev": true, + "license": "MIT", "dependencies": { - "reusify": "^1.0.4" + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dependencies": { - "bser": "2.1.1" + "node_modules/is-callable": { + "version": "1.2.7", + "license": "MIT", + "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==", - "dev": true, + "node_modules/is-core-module": { + "version": "2.16.1", + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "hasown": "^2.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==", + "node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/filter-obj": { + "node_modules/is-date-object": { "version": "1.1.0", - "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", - "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==", + "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.2", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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_modules/is-directory": { + "version": "0.3.1", + "license": "MIT", + "engines": { + "node": ">=0.10.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==" - }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "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": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-cache-dir/node_modules/find-up": { - "version": "3.0.0", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, + "node_modules/is-extglob": { + "version": "2.1.1", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/find-cache-dir/node_modules/locate-path": { - "version": "3.0.0", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "call-bound": "^1.0.3" }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-cache-dir/node_modules/make-dir": { + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-generator-fn": { "version": "2.1.0", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, + "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/find-cache-dir/node_modules/p-limit": { - "version": "2.3.0", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/is-generator-function": { + "version": "1.1.0", + "license": "MIT", "dependencies": { - "p-try": "^2.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/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-glob": { + "version": "4.0.3", + "license": "MIT", "dependencies": { - "p-limit": "^2.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/find-cache-dir/node_modules/path-exists": { - "version": "3.0.0", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "node_modules/is-interactive": { + "version": "1.0.0", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/find-cache-dir/node_modules/pify": { - "version": "4.0.1", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "node_modules/is-map": { + "version": "2.0.3", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "3.0.0", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "node_modules/is-nan": { + "version": "1.3.2", + "license": "MIT", "dependencies": { - "find-up": "^3.0.0" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" }, "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-replace": { - "version": "3.0.0", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "dependencies": { - "array-back": "^3.0.1" - }, + "node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", "engines": { - "node": ">=4.0.0" + "node": ">=0.12.0" } }, - "node_modules/find-up": { - "version": "4.1.0", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/is-number-object": { + "version": "1.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flat-cache": { - "version": "3.0.4", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "node_modules/is-path-inside": { + "version": "3.0.3", "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, + "license": "MIT", "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=8" } }, - "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==", + "node_modules/is-plain-obj": { + "version": "2.1.0", + "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">=8" } }, - "node_modules/for-each": { - "version": "0.3.3", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "node_modules/is-plain-object": { + "version": "2.0.4", + "license": "MIT", "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "isobject": "^3.0.1" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/form-data": { - "version": "3.0.1", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "node_modules/is-regex": { + "version": "1.2.1", + "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "node_modules/is-set": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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 +11225,66 @@ "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", + "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,1070 +11293,1132 @@ "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", + "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" - }, + "node_modules/iserror": { + "version": "0.0.2", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "license": "MIT", "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.10.0" } }, - "node_modules/globals": { - "version": "11.12.0", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "license": "BSD-3-Clause", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/globalthis": { - "version": "1.0.3", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "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_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==", - "dependencies": { - "hermes-estree": "0.8.0" + "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/test-result": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "source-map": "^0.7.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": ">=8" - } - }, - "node_modules/hermes-profile-transformer/node_modules/source-map": { - "version": "0.7.4", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } - }, - "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": "^14.15.0 || ^16.10.0 || >=18.0.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/types": { + "version": "29.6.3", + "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" + "@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" - } - }, - "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": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "engines": { - "node": ">=10.17.0" + "node_modules/jest-circus/node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" } }, - "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/ansi-styles": { + "version": "5.2.0", "dev": true, + "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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" + "node_modules/jest-circus/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": ">=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-circus/node_modules/jest-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.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": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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-circus/node_modules/pretty-format": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.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": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.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": { - "import-local-fixture": "fixtures/cli.js" + "jest": "bin/jest.js" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "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/console": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" + "@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": ">= 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/test-result": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^6.0.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": ">=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-cli/node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "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-array-buffer": { - "version": "3.0.1", - "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "node_modules/jest-cli/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@types/yargs-parser": "*" } }, - "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-cli/node_modules/ansi-styles": { + "version": "5.2.0", "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" + "license": "MIT", + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "node_modules/jest-cli/node_modules/jest-message-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 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" }, - "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" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "node_modules/jest-cli/node_modules/jest-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "has": "^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" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "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-cli/node_modules/pretty-format": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^6.0.0" + "@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/is-date-object": { - "version": "1.0.5", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "node_modules/jest-config": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "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-descriptor": { - "version": "1.0.2", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "node_modules/jest-config/node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, + "license": "MIT", "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "@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/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==", - "engines": { - "node": ">=0.10.0" + "node_modules/jest-config/node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" } }, - "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/ansi-styles": { + "version": "5.2.0", "dev": true, - "bin": { - "is-docker": "cli.js" - }, + "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "engines": { - "node": ">=0.10.0" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/jest-config/node_modules/jest-haste-map": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "@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": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "node_modules/jest-config/node_modules/jest-regex-util": { + "version": "29.6.3", + "dev": true, + "license": "MIT", "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-config/node_modules/jest-resolve": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "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": ">=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-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-glob": { - "version": "4.0.3", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/jest-config/node_modules/jest-worker": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "@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-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==", + "node_modules/jest-config/node_modules/pretty-format": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "node_modules/jest-config/node_modules/resolve.exports": { + "version": "2.0.3", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/is-nan": { - "version": "1.3.2", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "node_modules/jest-config/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "node_modules/jest-diff": { + "version": "29.7.0", "dev": true, - "engines": { - "node": ">= 0.4" + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "engines": { - "node": ">=0.12.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, "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-docblock": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/jest-each": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "isobject": "^3.0.1" + "@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": ">=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-each/node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "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-shared-array-buffer": { - "version": "1.0.2", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "node_modules/jest-each/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@types/yargs-parser": "*" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/jest-each/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-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-symbol": { - "version": "1.0.4", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "node_modules/jest-each/node_modules/pretty-format": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.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-typed-array": { - "version": "1.1.10", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "node_modules/jest-environment-emit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-emit/-/jest-environment-emit-1.2.0.tgz", + "integrity": "sha512-dSFBrRuIiWbHK2LSUA6CutXpMcNGjjuhvxFLF+TVz5tYFAAH0eesrZgrQ3UtOptajDYNt/fIGRqtlHqGq/bLbA==", + "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" + "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.4" + "node": ">=16.14.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "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-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" + "node_modules/jest-environment-emit/node_modules/bunyan": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-2.0.5.tgz", + "integrity": "sha512-Jvl74TdxCN6rSP9W1I6+UOUtwslTDqsSFkDqZlFb/ilaSvQ+bZAnXT/GT97IZ5L+Vph0joPZPhxUyn6FLNmFAA==", + "engines": [ + "node >=0.10.0" + ], + "license": "MIT", + "dependencies": { + "exeunt": "1.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "bunyan": "bin/bunyan" + }, + "optionalDependencies": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" } }, - "node_modules/is-weakref": { - "version": "1.0.2", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, + "node_modules/jest-environment-emit/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": { - "call-bind": "^1.0.2" + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/is-windows": { - "version": "1.0.2", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "license": "MIT", + "dependencies": { + "@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.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-wsl": { - "version": "1.1.0", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "node_modules/jest-environment-node/node_modules/@jest/types": { + "version": "29.6.3", + "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": ">=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/jest-environment-node/node_modules/@types/yargs": { + "version": "17.0.33", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } }, - "node_modules/isobject": { - "version": "3.0.1", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "node_modules/jest-environment-node/node_modules/jest-util": { + "version": "29.7.0", + "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": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "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/istanbul-lib-instrument": { - "version": "5.2.1", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "node_modules/jest-haste-map": { + "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", + "@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/istanbul-lib-report": { - "version": "3.0.0", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "node_modules/jest-leak-detector": { + "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-get-type": "^29.6.3", + "pretty-format": "^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-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest": { - "version": "29.4.3", - "integrity": "sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA==", + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", "dev": true, - "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" - }, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-changed-files": { - "version": "29.4.3", - "integrity": "sha512-Vn5cLuWuwmi2GNNbokPOEcvrXGSGrqVnPEZV7rC6P7ck07Dyw9RFnvWglnupSh+hGys0ajGtw/bc2ZgweljQoQ==", + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus": { - "version": "29.4.3", - "integrity": "sha512-Vw/bVvcexmdJ7MLmgdT3ZjkJ3LKu8IlpefYokxiqoZy6OCQ2VAm6Vk3t/qHiAGUXbdbJKJWnc8gH3ypTbB/OBw==", + "node_modules/jest-message-util": { + "version": "27.5.1", "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", - "@types/node": "*", + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", "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", + "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": "^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/node_modules/@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "27.5.1", "dev": true, + "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" + "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/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", + "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", + "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" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { "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==", - "dev": true, + "node_modules/jest-mock/node_modules/@jest/types": { + "version": "29.6.3", + "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,387 +12429,248 @@ "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==", - "dev": true, + "node_modules/jest-mock/node_modules/@types/yargs": { + "version": "17.0.33", + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/jest-mock/node_modules/jest-util": { + "version": "29.7.0", + "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-circus/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=6" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" + "peerDependencies": { + "jest-resolve": "*" }, - "engines": { - "node": ">=7.0.0" + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "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-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/jest-circus/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", + "node_modules/jest-resolve": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^27.5.1", "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": "^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": "^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/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/jest-resolve-dependencies": { + "version": "29.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" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { "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-resolve-dependencies/node_modules/jest-regex-util": { + "version": "29.6.3", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-cli": { - "version": "29.4.3", - "integrity": "sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg==", + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "5.2.0", "dev": true, - "dependencies": { - "@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" - }, - "bin": { - "jest": "bin/jest.js" - }, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-cli/node_modules/@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", + "node_modules/jest-resolve/node_modules/camelcase": { + "version": "6.3.0", "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" - }, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-cli/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", + "node_modules/jest-resolve/node_modules/jest-get-type": { + "version": "27.5.1", "dev": true, - "dependencies": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, + "license": "MIT", "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-cli/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/jest-resolve/node_modules/jest-validate": { + "version": "27.5.1", "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" + "@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" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-cli/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-cli/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-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" - }, - "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" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-resolve/node_modules/pretty-format": { + "version": "27.5.1", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" }, "engines": { - "node": ">=7.0.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.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-resolve/node_modules/react-is": { + "version": "17.0.2", "dev": true, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/jest-cli/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", + "node_modules/jest-runner": { + "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", + "@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", + "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "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", + "source-map-support": "0.5.13" }, "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-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", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.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/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-runner/node_modules/@jest/test-result": { + "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" + "@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": ">=12" - } - }, - "node_modules/jest-cli/node_modules/yargs-parser": { - "version": "21.1.1", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config": { - "version": "29.4.3", - "integrity": "sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==", + "node_modules/jest-runner/node_modules/@jest/transform": { + "version": "29.7.0", "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", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", "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", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.4.3", + "pirates": "^4.0.4", "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" + "write-file-atomic": "^4.0.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } } }, - "node_modules/jest-config/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "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": "*", @@ -12929,106 +12681,39 @@ "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==", + "node_modules/jest-runner/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/jest-config/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-config/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-config/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "5.2.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_modules/jest-config/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-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_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-config/node_modules/jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", + "node_modules/jest-runner/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,25 +12724,44 @@ "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-runner/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-runner/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/jest-config/node_modules/jest-resolve": { - "version": "29.4.3", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", + "node_modules/jest-runner/node_modules/jest-resolve": { + "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" @@ -13066,12 +12770,12 @@ "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-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", @@ -13082,13 +12786,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-runner/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 +12800,146 @@ "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-runner/node_modules/pretty-format": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, - "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": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/resolve.exports": { - "version": "2.0.0", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", + "node_modules/jest-runner/node_modules/resolve.exports": { + "version": "2.0.3", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "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_modules/jest-diff": { - "version": "29.4.3", - "integrity": "sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^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" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "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-diff/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-runner/node_modules/write-file-atomic": { + "version": "4.0.2", "dev": true, + "license": "ISC", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-runtime": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@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", + "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.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": ">=7.0.0" - } - }, - "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, - "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-runtime/node_modules/@jest/console": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.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": ">=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-runtime/node_modules/@jest/test-result": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "detect-newline": "^3.0.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/jest-each": { - "version": "29.4.3", - "integrity": "sha512-1ElHNAnKcbJb/b+L+7j0/w7bDvljw4gTv1wL9fYOczeJrbTbkMGQ5iQPFJ3eFQH19VPTx1IyfePdqSpePKss7Q==", + "node_modules/jest-runtime/node_modules/@jest/transform": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@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", - "jest-get-type": "^29.4.3", - "jest-util": "^29.4.3", - "pretty-format": "^29.4.3" + "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-each/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "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": "*", @@ -13257,489 +12950,441 @@ "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-runtime/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==", + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "5.2.0", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" }, "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==", + "node_modules/jest-runtime/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-each/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-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==", + "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-each/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/jest-runtime/node_modules/jest-resolve": { + "version": "29.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" + "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-each/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-runtime/node_modules/jest-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.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": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-node": { - "version": "29.4.3", - "integrity": "sha512-gAiEnSKF104fsGDXNkwk49jD/0N0Bqu2K9+aMQXA6avzsA9H3Fiv1PW2D+gzbOSR705bWd2wJZRFEFpV0tXISg==", + "node_modules/jest-runtime/node_modules/jest-worker": { + "version": "29.7.0", + "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" + "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-environment-node/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/jest-runtime/node_modules/pretty-format": { + "version": "29.7.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" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { "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==", - "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" - }, + "node_modules/jest-runtime/node_modules/resolve.exports": { + "version": "2.0.3", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=10" } }, - "node_modules/jest-environment-node/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "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-environment-node/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-runtime/node_modules/write-file-atomic": { + "version": "4.0.2", + "dev": true, + "license": "ISC", "dependencies": { - "color-name": "~1.1.4" + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" }, "engines": { - "node": ">=7.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": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/jest-environment-node/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/jest-serializer": { + "version": "27.5.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" + "graceful-fs": "^4.2.9" }, "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-environment-node/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-snapshot": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@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_modules/jest-get-type": { - "version": "29.4.3", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", - "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-haste-map": { - "version": "27.5.1", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "node_modules/jest-snapshot/node_modules/@jest/transform": { + "version": "29.7.0", "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", + "@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-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-haste-map/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-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/jest-haste-map/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.4.3", - "integrity": "sha512-9yw4VC1v2NspMMeV3daQ1yXPNxMgCzwq9BocCwYrRgXe4uaEJPAN0ZK37nFBhcy3cUwEVstFecFLaTHpF7NiGA==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" + "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-matcher-utils": { - "version": "29.4.3", - "integrity": "sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg==", + "node_modules/jest-snapshot/node_modules/@jest/types": { + "version": "29.6.3", "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" + "@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-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-snapshot/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "@types/yargs-parser": "*" } }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.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" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-snapshot/node_modules/jest-haste-map": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.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" }, "engines": { - "node": ">=7.0.0" - } - }, - "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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/jest-message-util": { - "version": "27.5.1", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "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": "^27.5.1", + "@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": "^27.5.1", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-snapshot/node_modules/jest-regex-util": { + "version": "29.6.3", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", "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-message-util/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-snapshot/node_modules/jest-util": { + "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/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "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-message-util/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-snapshot/node_modules/jest-worker": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.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/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "27.5.1", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.1", "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" } }, - "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "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/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "17.0.2", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "node_modules/jest-message-util/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-mock": { - "version": "29.4.3", - "integrity": "sha512-LjFgMg+xed9BdkPMyIJh+r3KeHt1klXPJYBULXVVAkbTaaKjPX1o1uVCAZADMEp/kOxGTwy/Ot8XbvgItOrHEg==", + "node_modules/jest-util": { + "version": "27.5.1", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^27.5.1", "@types/node": "*", - "jest-util": "^29.4.3" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "license": "MIT", + "dependencies": { + "@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": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/jest-validate/node_modules/@jest/types": { + "version": "29.6.3", + "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": "*", @@ -13750,254 +13395,121 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "node_modules/jest-validate/node_modules/@types/yargs": { + "version": "17.0.33", + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/jest-mock/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-mock/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/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "license": "MIT", "engines": { "node": ">=10" }, "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/jest-validate/node_modules/pretty-format": { + "version": "29.7.0", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "node_modules/jest-watcher": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", + "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock/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-pnp-resolver": { - "version": "1.2.3", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "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/jest-resolve": { - "version": "27.5.1", - "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", + "node_modules/jest-watcher/node_modules/@jest/console": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.3", + "@types/node": "*", "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", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.4.3", - "integrity": "sha512-uvKMZAQ3nmXLH7O8WAOhS5l0iWyT3WmnJBdmIHiV5tBbdaDZ1wqtNX04FONGoaFvSOSHBJxnwAVnSn1WHdGVaw==", - "dev": true, - "dependencies": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.4.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies/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/jest-resolve/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-resolve/node_modules/camelcase": { - "version": "6.3.0", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-resolve/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-resolve/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-watcher/node_modules/@jest/test-result": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@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": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "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": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve/node_modules/jest-validate": { - "version": "27.5.1", - "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", + "node_modules/jest-watcher/node_modules/@jest/types": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "@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" + "@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.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve/node_modules/pretty-format": { - "version": "27.5.1", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "node_modules/jest-watcher/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "@types/yargs-parser": "*" } }, - "node_modules/jest-resolve/node_modules/pretty-format/node_modules/ansi-styles": { + "node_modules/jest-watcher/node_modules/ansi-styles": { "version": "5.2.0", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -14005,114 +13517,87 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-resolve/node_modules/react-is": { - "version": "17.0.2", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-watcher/node_modules/jest-message-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@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": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner": { - "version": "29.4.3", - "integrity": "sha512-GWPTEiGmtHZv1KKeWlTX9SIFuK19uLXlRQU43ceOQ2hIfA5yPEJC7AMkvFKpdCHx6pNEdOD+2+8zbniEi3v3gA==", + "node_modules/jest-watcher/node_modules/jest-util": { + "version": "29.7.0", "dev": true, + "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", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "emittery": "^0.13.1", + "ci-info": "^3.2.0", "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" + "picomatch": "^2.2.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", + "node_modules/jest-watcher/node_modules/pretty-format": { + "version": "29.7.0", "dev": true, + "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/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", + "node_modules/jest-worker": { + "version": "27.5.1", "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" + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 10.13.0" } }, - "node_modules/jest-runner/node_modules/@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", "dev": true, + "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" + "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-runner/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": "*", @@ -14123,840 +13608,848 @@ "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==", + "node_modules/jest/node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, + "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/joi": { + "version": "17.13.3", + "license": "BSD-3-Clause", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "@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-runner/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/js-message": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", + "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==", + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=0.6.0" } }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "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==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "easy-stack": "^1.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=1.0.0" } }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "engines": { - "node": ">=7.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/jsbi": { + "version": "3.2.5", + "license": "Apache-2.0" }, - "node_modules/jest-runner/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "license": "0BSD" }, - "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/jscodeshift": { + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-17.3.0.tgz", + "integrity": "sha512-LjFrGOIORqXBU+jwfC9nbkjmQfFldtMIoS6d9z2LG/lkmyNXsJAySPT+2SWXJEoE68/bCWcxKpXH37npftgmow==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/preset-flow": "^7.24.7", + "@babel/preset-typescript": "^7.24.7", + "@babel/register": "^7.24.6", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.7", + "neo-async": "^2.5.0", + "picocolors": "^1.0.1", + "recast": "^0.23.11", + "tmp": "^0.2.3", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.js" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=16" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + }, + "peerDependenciesMeta": { + "@babel/preset-env": { + "optional": true + } } }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/jscodeshift/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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/jscodeshift/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "license": "ISC", "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" + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": "^14.17.0 || ^16.13.0 || >=18.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/jsesc": { + "version": "3.1.0", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" } }, - "node_modules/jest-runner/node_modules/jest-regex-util": { - "version": "29.4.3", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "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-runner/node_modules/jest-resolve": { - "version": "29.4.3", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "license": "MIT" + }, + "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-without-jsonify": { + "version": "1.0.1", "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" + }, + "node_modules/json5": { + "version": "2.2.3", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" } }, - "node_modules/jest-runner/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/jsonfile": { + "version": "4.0.0", + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/jest-runner/node_modules/jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", + "node_modules/jsx-ast-utils": { + "version": "3.3.5", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "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-runner/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/keyv": { + "version": "4.5.4", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "json-buffer": "3.0.1" } }, - "node_modules/jest-runner/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, + "node_modules/kind-of": { + "version": "6.0.3", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/jest-runner/node_modules/resolve.exports": { - "version": "2.0.0", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", - "dev": true, + "node_modules/kleur": { + "version": "3.0.3", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=6" } }, - "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/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "node_modules/leven": { + "version": "3.1.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/jest-runner/node_modules/write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "node_modules/levn": { + "version": "0.4.1", "dev": true, + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 0.8.0" } }, - "node_modules/jest-runtime": { - "version": "29.4.3", - "integrity": "sha512-F5bHvxSH+LvLV24vVB3L8K467dt3y3dio6V3W89dUz9nzvTpqd/HcT9zfYKL2aZPvD63vQFgLvaUX/UpUhrP6Q==", - "dev": true, + "node_modules/light-bolt11-decoder": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/light-bolt11-decoder/-/light-bolt11-decoder-3.2.0.tgz", + "integrity": "sha512-3QEofgiBOP4Ehs9BI+RkZdXZNtSys0nsJ6fyGeSiAGCBsMwHGUDS/JQlY/sTnWs91A2Nh0S9XXfA8Sy9g6QpuQ==", + "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", - "@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" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@scure/base": "1.1.1" } }, - "node_modules/jest-runtime/node_modules/@jest/console": { - "version": "29.4.3", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", - "dev": true, + "node_modules/light-bolt11-decoder/node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "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": { - "@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" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "debug": "^2.6.9", + "marky": "^1.2.2" } }, - "node_modules/jest-runtime/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", - "dev": true, - "dependencies": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.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-runtime/node_modules/@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "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": { - "@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" + "ms": "2.0.0" } }, - "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" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "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-runtime/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } + "node_modules/lines-and-columns": { + "version": "1.2.4", + "license": "MIT" }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "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": { - "color-convert": "^2.0.1" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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" - }, - "engines": { - "node": ">= 8" - } + "node_modules/lodash": { + "version": "4.17.21", + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "license": "MIT" }, - "node_modules/jest-runtime/node_modules/chalk": { + "node_modules/lodash.memoize": { "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "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", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@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": ">=7.0.0" + "node": ">= 12.0.0" } }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/logkitty": { + "version": "0.7.1", + "license": "MIT", + "dependencies": { + "ansi-fragments": "^0.2.1", + "dayjs": "^1.8.15", + "yargs": "^15.1.0" + }, + "bin": { + "logkitty": "bin/logkitty.js" + } }, - "node_modules/jest-runtime/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "node_modules/logkitty/node_modules/cliui": { + "version": "6.0.0", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } }, - "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" - ], + "node_modules/logkitty/node_modules/decamelize": { + "version": "1.2.0", + "license": "MIT", "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/logkitty/node_modules/find-up": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "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/logkitty/node_modules/locate-path": { + "version": "5.0.0", + "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" + "p-locate": "^4.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": ">=8" } }, - "node_modules/jest-runtime/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, + "node_modules/logkitty/node_modules/p-limit": { + "version": "2.3.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" + "p-try": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/logkitty/node_modules/p-locate": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-runtime/node_modules/jest-resolve": { - "version": "29.4.3", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", - "dev": true, + "node_modules/logkitty/node_modules/strip-ansi": { + "version": "6.0.1", + "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" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-runtime/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, + "node_modules/logkitty/node_modules/wrap-ansi": { + "version": "6.2.0", + "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-runtime/node_modules/jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, + "node_modules/logkitty/node_modules/y18n": { + "version": "4.0.3", + "license": "ISC" + }, + "node_modules/logkitty/node_modules/yargs": { + "version": "15.4.1", + "license": "MIT", "dependencies": { - "@types/node": "*", - "jest-util": "^29.4.3", - "merge-stream": "^2.0.0", - "supports-color": "^8.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": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-runtime/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "node_modules/logkitty/node_modules/yargs-parser": { + "version": "18.1.3", + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" }, "engines": { - "node": ">=10" + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "bin": { + "loose-envify": "cli.js" } }, - "node_modules/jest-runtime/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node_modules/lottie-react-native": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-7.3.4.tgz", + "integrity": "sha512-XUh7eGFb7ID8JRdU6U4N4cYQeYmjtdQRvd8ZXJ6xrdSsn5gZD0c79ITOREPcwJg4YupBFHgyV1GXdAHQP+KYUQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@lottiefiles/dotlottie-react": "^0.13.5", + "react": "*", + "react-native": ">=0.46", + "react-native-windows": ">=0.63.x" + }, + "peerDependenciesMeta": { + "@lottiefiles/dotlottie-react": { + "optional": true + }, + "react-native-windows": { + "optional": true + } } }, - "node_modules/jest-runtime/node_modules/resolve.exports": { - "version": "2.0.0", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", + "node_modules/lru-cache": { + "version": "5.1.1", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/make-dir/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/jest-runtime/node_modules/write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "node_modules/make-error": { + "version": "1.3.6", "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "license": "BSD-3-Clause", "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, + "tmpl": "1.0.5" + } + }, + "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": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 0.4" } }, - "node_modules/jest-serializer": { - "version": "27.5.1", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", + "node_modules/md5.js": { + "version": "1.3.5", + "license": "MIT", "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.9" + "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/memoize-one": { + "version": "5.2.1", + "license": "MIT" + }, + "node_modules/merge-options": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" } }, - "node_modules/jest-snapshot": { - "version": "29.4.3", - "integrity": "sha512-NGlsqL0jLPDW91dz304QTM/SNO99lpcSYYAjNiX0Ou+sSGgkanKBcSjCfp/pqmiiO1nQaOyLp6XQddAzRcx3Xw==", - "dev": true, + "node_modules/merge-stream": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/merkle-lib": { + "version": "2.0.10", + "license": "MIT" + }, + "node_modules/metro": { + "version": "0.81.4", + "license": "MIT", "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", + "@babel/code-frame": "^7.24.7", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "@babel/types": "^7.25.2", + "accepts": "^1.3.7", "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" + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.25.1", + "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.81.4", + "metro-cache": "0.81.4", + "metro-cache-key": "0.81.4", + "metro-config": "0.81.4", + "metro-core": "0.81.4", + "metro-file-map": "0.81.4", + "metro-resolver": "0.81.4", + "metro-runtime": "0.81.4", + "metro-source-map": "0.81.4", + "metro-symbolicate": "0.81.4", + "metro-transform-plugins": "0.81.4", + "metro-transform-worker": "0.81.4", + "mime-types": "^2.1.27", + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.18" } }, - "node_modules/jest-snapshot/node_modules/@jest/transform": { - "version": "29.4.3", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", - "dev": true, + "node_modules/metro-babel-transformer": { + "version": "0.81.4", + "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" + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.25.1", + "nullthrows": "^1.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.18" } }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, + "node_modules/metro-cache": { + "version": "0.81.4", + "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" + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "metro-core": "0.81.4" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.18" } }, - "node_modules/jest-snapshot/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, + "node_modules/metro-cache-key": { + "version": "0.81.4", + "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18.18" } }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/metro-config": { + "version": "0.81.4", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "connect": "^3.6.5", + "cosmiconfig": "^5.0.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.81.4", + "metro-cache": "0.81.4", + "metro-core": "0.81.4", + "metro-runtime": "0.81.4" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18.18" } }, - "node_modules/jest-snapshot/node_modules/anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, + "node_modules/metro-config/node_modules/cosmiconfig": { + "version": "5.2.1", + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" }, "engines": { - "node": ">= 8" + "node": ">=4" } }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/metro-config/node_modules/import-fresh": { + "version": "2.0.0", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=4" } }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/metro-config/node_modules/parse-json": { + "version": "4.0.0", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=4" } }, - "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": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/fsevents": { - "version": "2.3.2", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "node_modules/metro-config/node_modules/resolve-from": { + "version": "3.0.0", + "license": "MIT", "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=4" } }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/metro-core": { + "version": "0.81.4", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.81.4" + }, "engines": { - "node": ">=8" + "node": ">=18.18" } }, - "node_modules/jest-snapshot/node_modules/jest-haste-map": { - "version": "29.4.3", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", - "dev": true, + "node_modules/metro-file-map": { + "version": "0.81.4", + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", + "debug": "^2.2.0", "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", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", - "walker": "^1.0.8" + "nullthrows": "^1.1.1", + "walker": "^1.0.7" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": ">=18.18" } }, - "node_modules/jest-snapshot/node_modules/jest-message-util": { - "version": "29.4.3", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", - "dev": true, + "node_modules/metro-file-map/node_modules/@jest/types": { + "version": "29.6.3", + "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/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-snapshot/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/metro-file-map/node_modules/@types/yargs": { + "version": "17.0.33", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" } }, - "node_modules/jest-snapshot/node_modules/jest-util": { - "version": "29.4.3", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", - "dev": true, + "node_modules/metro-file-map/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/metro-file-map/node_modules/jest-util": { + "version": "29.7.0", + "license": "MIT", "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -14967,13 +14460,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/jest-worker": { - "version": "29.4.3", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", - "dev": true, + "node_modules/metro-file-map/node_modules/jest-worker": { + "version": "29.7.0", + "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" }, @@ -14981,10 +14473,13 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/jest-worker/node_modules/supports-color": { + "node_modules/metro-file-map/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/metro-file-map/node_modules/supports-color": { "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -14995,162 +14490,191 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "node_modules/metro-minify-terser": { + "version": "0.81.4", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">=18.18" } }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.8", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "node_modules/metro-react-native-babel-preset": { + "version": "0.76.8", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@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" }, "engines": { - "node": ">=10" + "node": ">=16" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/metro-react-native-babel-preset/node_modules/react-refresh": { + "version": "0.4.3", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/jest-snapshot/node_modules/write-file-atomic": { - "version": "4.0.2", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, + "node_modules/metro-resolver": { + "version": "0.81.4", + "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": ">=18.18" } }, - "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==", + "node_modules/metro-runtime": { + "version": "0.81.4", + "license": "MIT", "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" + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=18.18" } }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/metro-source-map": { + "version": "0.81.4", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@babel/traverse": "^7.25.3", + "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", + "@babel/types": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.81.4", + "nullthrows": "^1.1.1", + "ob1": "0.81.4", + "source-map": "^0.5.6", + "vlq": "^1.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18.18" } }, - "node_modules/jest-util/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/metro-source-map/node_modules/source-map": { + "version": "0.5.7", + "license": "BSD-3-Clause", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=0.10.0" } }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/metro-symbolicate": { + "version": "0.81.4", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.81.4", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" }, "engines": { - "node": ">=7.0.0" + "node": ">=18.18" } }, - "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/metro-symbolicate/node_modules/source-map": { + "version": "0.5.7", + "license": "BSD-3-Clause", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/metro-transform-plugins": { + "version": "0.81.4", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">=8" + "node": ">=18.18" } }, - "node_modules/jest-validate": { - "version": "29.4.3", - "integrity": "sha512-J3u5v7aPQoXPzaar6GndAVhdQcZr/3osWSgTeKg5v574I9ybX/dTyH0AJFb5XgXIB7faVhf+rS7t4p3lL9qFaw==", - "dev": true, + "node_modules/metro-transform-worker": { + "version": "0.81.4", + "license": "MIT", "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" + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/types": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "metro": "0.81.4", + "metro-babel-transformer": "0.81.4", + "metro-cache": "0.81.4", + "metro-cache-key": "0.81.4", + "metro-minify-terser": "0.81.4", + "metro-source-map": "0.81.4", + "metro-transform-plugins": "0.81.4", + "nullthrows": "^1.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.18" } }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", - "dev": true, + "node_modules/metro/node_modules/@jest/types": { + "version": "29.6.3", + "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": "*", @@ -15161,999 +14685,755 @@ "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, + "node_modules/metro/node_modules/@types/yargs": { + "version": "17.0.33", + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/metro/node_modules/ci-info": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/metro/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "ms": "2.0.0" + } + }, + "node_modules/metro/node_modules/jest-util": { + "version": "29.7.0", + "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": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, + "node_modules/metro/node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/metro/node_modules/jest-worker": { + "version": "29.7.0", + "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-validate/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/metro/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/metro/node_modules/serialize-error": { + "version": "2.1.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "node_modules/metro/node_modules/source-map": { + "version": "0.5.7", + "license": "BSD-3-Clause", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/jest-watcher": { - "version": "29.4.3", - "integrity": "sha512-zwlXH3DN3iksoIZNk73etl1HzKyi5FuQdYLnkQKm5BW4n8HpoG59xSwpVdFrnh60iRRaRBGw0gcymIxjJENPcA==", - "dev": true, + "node_modules/metro/node_modules/supports-color": { + "version": "8.1.1", + "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" + "has-flag": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-watcher/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" + "node": ">=10" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-watcher/node_modules/@jest/test-result": { - "version": "29.4.3", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", - "dev": true, - "dependencies": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, + "node_modules/metro/node_modules/ws": { + "version": "7.5.10", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-watcher/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": ">=8.3.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/micro-packed": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.8.0.tgz", + "integrity": "sha512-AKb8znIvg9sooythbXzyFeChEY0SkW0C6iXECpy/ls0e5BtwXO45J9wD9SLzBztnS4XmF/5kwZknsq+jyynd/A==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@scure/base": "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-watcher/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/micromatch": { + "version": "4.0.8", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8.6" } }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "bn.js": "^4.0.0", + "brorand": "^1.0.1" }, - "engines": { - "node": ">=7.0.0" - } - }, - "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, - "engines": { - "node": ">=8" + "bin": { + "miller-rabin": "bin/miller-rabin" } }, - "node_modules/jest-watcher/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", + "license": "MIT", + "bin": { + "mime": "cli.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=4.0.0" } }, - "node_modules/jest-watcher/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-db": { + "version": "1.54.0", + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.6" } }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/jest-worker": { - "version": "27.5.1", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", "engines": { - "node": ">= 10.13.0" + "node": ">= 0.6" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/mimic-fn": { + "version": "2.1.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "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/node_modules/@jest/types": { - "version": "29.4.3", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "node_modules/min-indent": { + "version": "1.0.1", "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" - }, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=4" } }, - "node_modules/jest/node_modules/@types/yargs": { - "version": "17.0.22", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "license": "ISC" }, - "node_modules/jest/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "9.0.5", "dev": true, + "license": "ISC", "dependencies": { - "color-convert": "^2.0.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/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" - }, + "node_modules/minimist": { + "version": "1.2.8", + "license": "MIT", "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest/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/mkdirp": { + "version": "0.5.6", + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "^1.2.6" }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "license": "MIT" + }, + "node_modules/moment": { + "version": "2.30.1", + "license": "MIT", + "optional": true, "engines": { - "node": ">=7.0.0" + "node": "*" } }, - "node_modules/jest/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "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==", + "license": "bsd" }, - "node_modules/jest/node_modules/has-flag": { + "node_modules/multipipe": { "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "duplexer2": "^0.1.2", + "object-assign": "^4.1.0" } }, - "node_modules/jest/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/mv": { + "version": "2.1.1", + "license": "MIT", + "optional": true, "dependencies": { - "has-flag": "^4.0.0" + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" }, "engines": { - "node": ">=8" + "node": ">=0.8.0" } }, - "node_modules/joi": { - "version": "17.8.3", - "integrity": "sha512-q5Fn6Tj/jR8PfrLrx4fpGH4v9qM6o+vDUfD4/3vxxyg34OmKcNqYZ1qn2mpLza96S8tL0p0rIw2gOZX+/cTg9w==", + "node_modules/mv/node_modules/brace-expansion": { + "version": "1.1.11", + "license": "MIT", + "optional": true, "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" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "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/mv/node_modules/glob": { + "version": "6.0.4", + "license": "ISC", + "optional": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, "engines": { - "node": ">=0.6.0" + "node": "*" } }, - "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==", + "node_modules/mv/node_modules/minimatch": { + "version": "3.1.2", + "license": "ISC", + "optional": true, "dependencies": { - "easy-stack": "^1.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=1.0.0" + "node": "*" } }, - "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==", + "node_modules/mv/node_modules/rimraf": { + "version": "2.4.5", + "license": "ISC", + "optional": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "glob": "^6.0.1" }, "bin": { - "js-yaml": "bin/js-yaml.js" + "rimraf": "bin.js" } }, - "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/nan": { + "version": "2.22.2", + "license": "MIT" }, - "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" - }, + "node_modules/nanoid": { + "version": "3.3.8", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "bin": { - "jscodeshift": "bin/jscodeshift.js" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" - } - }, - "node_modules/jscodeshift/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/jscodeshift/node_modules/arr-diff": { - "version": "4.0.0", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "engines": { - "node": ">=0.10.0" - } + "node_modules/napi-build-utils": { + "version": "2.0.0", + "license": "MIT" }, - "node_modules/jscodeshift/node_modules/array-unique": { - "version": "0.3.2", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "engines": { - "node": ">=0.10.0" - } + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" }, - "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/ncp": { + "version": "2.0.0", + "license": "MIT", + "optional": true, + "bin": { + "ncp": "bin/ncp" } }, - "node_modules/jscodeshift/node_modules/braces": { - "version": "2.3.2", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "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" - }, + "node_modules/negotiator": { + "version": "0.6.4", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/jscodeshift/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dependencies": { - "is-extendable": "^0.1.0" - }, + "node_modules/neo-async": { + "version": "2.6.2", + "license": "MIT" + }, + "node_modules/nocache": { + "version": "3.0.4", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12.0.0" } }, - "node_modules/jscodeshift/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/node-abi": { + "version": "3.74.0", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "semver": "^7.3.5" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jscodeshift/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "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==", - "dependencies": { - "ms": "2.0.0" - } + "node_modules/node-addon-api": { + "version": "5.1.0", + "license": "MIT" }, - "node_modules/jscodeshift/node_modules/expand-brackets": { - "version": "2.1.4", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "node_modules/node-fetch": { + "version": "2.7.0", + "dev": true, + "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" + "whatwg-url": "^5.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "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": "4.x || >=6.0.0" }, - "engines": { - "node": ">=0.10.0" - } - }, - "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" + "peerDependencies": { + "encoding": "^0.1.0" }, - "engines": { - "node": ">=0.10.0" + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/jscodeshift/node_modules/expand-brackets/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/node-forge": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", + "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { - "node": ">=0.10.0" + "node": ">= 6.13.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_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/jscodeshift/node_modules/extglob": { - "version": "2.0.4", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "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" - }, - "engines": { - "node": ">=0.10.0" - } + "node_modules/node-int64": { + "version": "0.4.0", + "license": "MIT" }, - "node_modules/jscodeshift/node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "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==", + "license": "MIT", "dependencies": { - "is-descriptor": "^1.0.0" + "event-pubsub": "4.3.0", + "js-message": "1.0.7", + "js-queue": "2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.0.0" } }, - "node_modules/jscodeshift/node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dependencies": { - "is-extendable": "^0.1.0" - }, + "node_modules/node-machine-id": { + "version": "1.1.12", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "license": "MIT" + }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" } }, - "node_modules/jscodeshift/node_modules/fill-range": { - "version": "4.0.0", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "node_modules/normalize-package-data": { + "version": "2.5.0", + "license": "BSD-2-Clause", "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, - "node_modules/jscodeshift/node_modules/fill-range/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/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "license": "ISC", + "bin": { + "semver": "bin/semver" } }, - "node_modules/jscodeshift/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/normalize-path": { + "version": "3.0.0", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/jscodeshift/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "node_modules/npm-run-path": { + "version": "4.0.1", + "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" + "path-key": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/jscodeshift/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/nth-check": { + "version": "2.1.1", + "license": "BSD-2-Clause", "dependencies": { - "is-buffer": "^1.1.5" + "boolbase": "^1.0.0" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/jscodeshift/node_modules/is-data-descriptor": { - "version": "0.1.4", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "node_modules/nullthrows": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/ob1": { + "version": "0.81.4", + "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" + "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=0.10.0" + "node": ">=18.18" } }, - "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/object-assign": { + "version": "4.1.1", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/jscodeshift/node_modules/is-number": { - "version": "3.0.0", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dependencies": { - "kind-of": "^3.0.2" - }, + "node_modules/object-inspect": { + "version": "1.13.4", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jscodeshift/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/object-is": { + "version": "1.1.6", + "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/object-keys": { + "version": "1.1.1", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/jscodeshift/node_modules/ms": { - "version": "2.0.0", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/jscodeshift/node_modules/rimraf": { - "version": "2.6.3", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "node_modules/object.assign": { + "version": "4.1.7", + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "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" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jscodeshift/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/object.entries": { + "version": "1.1.9", + "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^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": ">=8" + "node": ">= 0.4" } }, - "node_modules/jscodeshift/node_modules/temp": { - "version": "0.8.4", - "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "node_modules/object.fromentries": { + "version": "2.0.8", + "dev": true, + "license": "MIT", "dependencies": { - "rimraf": "~2.6.2" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jscodeshift/node_modules/to-regex-range": { - "version": "2.1.1", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "node_modules/object.groupby": { + "version": "1.0.3", + "dev": true, + "license": "MIT", "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/jscodeshift/node_modules/write-file-atomic": { - "version": "2.4.3", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "node_modules/object.values": { + "version": "1.2.1", + "dev": true, + "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "bin": { - "jsesc": "bin/jsesc" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/on-finished": { + "version": "2.3.0", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, "engines": { - "node": ">= 4" + "node": ">= 0.8" } }, - "node_modules/json-parse-better-errors": { + "node_modules/on-headers": { "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/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/json-stable-stringify-without-jsonify": { - "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" - }, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.8" } }, - "node_modules/jsonfile": { - "version": "4.0.0", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "node_modules/once": { + "version": "1.4.0", + "license": "ISC", + "dependencies": { + "wrappy": "1" } }, - "node_modules/jsx-ast-utils": { - "version": "3.3.3", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", - "dev": true, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", "dependencies": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" - }, - "engines": { - "node": ">=4.0" + "fn.name": "1.x.x" } }, - "node_modules/junderw-crc32c": { - "version": "1.2.0", - "integrity": "sha512-tP0w5QOrunUS/XgsDBoZfw2jKNFhnUrdM96IXzuJtCyuXd19Hj47Hfd5+WFd81QDQFosiPffpc/jnSrZ35IV+A==", + "node_modules/onetime": { + "version": "5.1.2", + "license": "MIT", "dependencies": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - }, - "bin": { - "crc32": "bin/crc32.njs" + "mimic-fn": "^2.1.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" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/leven": { - "version": "3.1.0", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "node_modules/open": { + "version": "6.4.0", + "license": "MIT", + "dependencies": { + "is-wsl": "^1.1.0" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "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==", + "node_modules/optionator": { + "version": "0.9.4", "dev": true, + "license": "MIT", "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" } }, - "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==", - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/ora": { + "version": "5.4.1", + "license": "MIT", "dependencies": { + "bl": "^4.1.0", "chalk": "^4.1.0", - "is-unicode-supported": "^0.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": ">=10" @@ -16162,507 +15442,433 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "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/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "node_modules/p-try": { + "version": "2.2.0", + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/pako": { + "resolved": "blue_modules/pako", + "link": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/parse-asn1": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "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": ">=8" + "node": ">= 0.10" } }, - "node_modules/logkitty": { - "version": "0.7.1", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", + "node_modules/parse-asn1/node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "license": "MIT", "dependencies": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" }, - "bin": { - "logkitty": "bin/logkitty.js" + "engines": { + "node": ">= 0.10" } }, - "node_modules/logkitty/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/parse-json": { + "version": "5.2.0", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@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": ">=8" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/parseurl": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "node_modules/logkitty/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, + "node_modules/path-browserify": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "license": "MIT", "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "node_modules/logkitty/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/logkitty/node_modules/decamelize": { - "version": "1.2.0", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "node_modules/path-is-absolute": { + "version": "1.0.1", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "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/path-key": { + "version": "3.1.1", + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/logkitty/node_modules/y18n": { - "version": "4.0.3", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + "node_modules/path-parse": { + "version": "1.0.7", + "license": "MIT" }, - "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" - }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/logkitty/node_modules/yargs-parser": { - "version": "18.1.3", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "node_modules/payjoin-client": { + "version": "1.0.1", + "license": "MIT", "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "bitcoinjs-lib": "^5.2.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/payjoin-client/node_modules/bip32": { + "version": "2.0.6", + "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" }, "engines": { - "node": ">=6" + "node": ">=6.0.0" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "node_modules/payjoin-client/node_modules/bitcoinjs-lib": { + "version": "5.2.0", + "license": "MIT", "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.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" }, - "bin": { - "loose-envify": "cli.js" + "engines": { + "node": ">=8.0.0" } }, - "node_modules/lottie-ios": { - "version": "3.4.4", - "integrity": "sha512-ikj8VNuClItRDZ8C9MfRMDxN0U/UIX3HRF2aei3H44F9Hkhwv0EIVRkBwG+SwS/WSoGmBzkcVG8O3BjPk5hW7Q==" - }, - "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/pbkdf2": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz", + "integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==", + "license": "MIT", "dependencies": { - "invariant": "^2.2.2", - "react-native-safe-modules": "^1.0.3" - }, - "peerDependencies": { - "lottie-ios": "^3.4.0", - "react": "*", - "react-native": ">=0.46", - "react-native-windows": ">=0.63.x" + "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" }, - "peerDependenciesMeta": { - "react-native-windows": { - "optional": true - } + "engines": { + "node": ">=0.12" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "node_modules/pbkdf2/node_modules/create-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==", + "license": "MIT", "dependencies": { - "yallist": "^3.0.2" + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "sha.js": "^2.4.0" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, + "node_modules/pbkdf2/node_modules/hash-base": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==", + "license": "MIT", "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "inherits": "^2.0.1" } }, - "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==", + "node_modules/pbkdf2/node_modules/ripemd160": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==", + "license": "MIT", "dependencies": { - "tmpl": "1.0.5" + "hash-base": "^2.0.0", + "inherits": "^2.0.1" } }, - "node_modules/map-cache": { - "version": "0.2.2", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "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/map-visit": { - "version": "1.0.0", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dependencies": { - "object-visit": "^1.0.0" - }, + "node_modules/pify": { + "version": "4.0.1", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "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/pirates": { + "version": "4.0.7", + "license": "MIT", + "engines": { + "node": ">= 6" } }, - "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==", + "node_modules/pkg-dir": { + "version": "4.2.0", + "dev": true, + "license": "MIT", "dependencies": { - "is-plain-obj": "^2.1.0" + "find-up": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "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==", + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", "dev": true, - "engines": { - "node": ">= 8" - } - }, - "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==", + "license": "MIT", "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" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, - "bin": { - "metro": "src/cli.js" - } - }, - "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/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/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/metro-config": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.73.10.tgz", - "integrity": "sha512-wIlybd1Z9I8K2KcStTiJxTB7OK529dxFgogNpKCTU/3DxkgAASqSkgXnZP6kVyqjh5EOWAKFe5U6IPic7kXDdQ==", - "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" + "engines": { + "node": ">=8" } }, - "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==", + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", "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" + "p-locate": "^4.1.0" }, "engines": { - "node": ">= 10.14.2" + "node": ">=8" } }, - "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==", + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "p-limit": "^2.2.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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==", + "node_modules/pngjs": { + "version": "5.0.0", + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10.13.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==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 0.4" } }, - "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/prebuild-install": { + "version": "7.1.3", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "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" + }, + "bin": { + "prebuild-install": "bin.js" }, "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "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/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/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "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/prettier": { + "version": "3.5.3", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, "engines": { - "node": ">= 10.14.2" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "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==", + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "dev": true, + "license": "MIT", "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" + "fast-diff": "^1.1.2" }, "engines": { - "node": ">= 10.14.2" + "node": ">=6.0.0" } }, - "node_modules/metro-config/node_modules/pretty-format": { + "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==", + "license": "MIT", "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -16673,19914 +15879,1589 @@ "node": ">= 10" } }, - "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/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/pretty-format/node_modules/@jest/types": { + "version": "26.6.2", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@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": ">=8" + "node": ">= 10.14.2" } }, - "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/pretty-format/node_modules/@types/yargs": { + "version": "15.0.19", + "license": "MIT", "dependencies": { - "lodash.throttle": "^4.1.1", - "metro-resolver": "0.73.10" + "@types/yargs-parser": "*" } }, - "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/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "license": "MIT" + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "license": "MIT" + }, + "node_modules/promise": { + "version": "8.3.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" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "asap": "~2.0.6" } }, - "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/promisify-child-process": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/promisify-child-process/-/promisify-child-process-4.1.2.tgz", + "integrity": "sha512-APnkIgmaHNJpkAn7k+CrJSi9WMuff5ctYFbD0CO2XIPkM8yO7d/ShouU2clywbpHV/DUsyc4bpJCsNgddNtx4g==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" }, "engines": { - "node": ">= 8" + "node": ">= 6" } }, - "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/prop-types": { + "version": "15.8.1", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" } }, - "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" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "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", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" } }, - "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/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "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/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==", - "engines": { - "node": ">=0.10.0" + "node_modules/pump": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "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/punycode": { + "version": "1.4.1", + "license": "MIT" + }, + "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/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==", + "node_modules/pushdata-bitcoin": { + "version": "1.0.1", + "license": "MIT", "dependencies": { - "connect": "^3.6.5", - "debug": "^2.2.0", - "ws": "^7.5.1", - "yargs": "^17.5.1" - }, - "bin": { - "metro-inspector-proxy": "src/cli.js" + "bitcoin-ops": "^1.3.0" } }, - "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/qrcode": { + "version": "1.5.4", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" }, "engines": { - "node": ">=12" + "node": ">=10.13.0" } }, - "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/qrcode/node_modules/cliui": { + "version": "6.0.0", + "license": "ISC", "dependencies": { - "ms": "2.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, - "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/qrcode/node_modules/decamelize": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "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/qrcode/node_modules/find-up": { + "version": "4.1.0", + "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" + "locate-path": "^5.0.0", + "path-exists": "^4.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/qrcode/node_modules/locate-path": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "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/qrcode/node_modules/p-limit": { + "version": "2.3.0", + "license": "MIT", "dependencies": { - "terser": "^5.15.0" + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/qrcode/node_modules/p-locate": { + "version": "4.1.0", + "license": "MIT", "dependencies": { - "uglify-es": "^3.1.9" + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" } }, - "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==", + "node_modules/qrcode/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", "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" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" + "node": ">=8" } }, - "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==", + "node_modules/qrcode/node_modules/wrap-ansi": { + "version": "6.2.0", + "license": "MIT", "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" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, - "peerDependencies": { - "@babel/core": "*" + "engines": { + "node": ">=8" } }, - "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" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "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/metro-runtime": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.73.10.tgz", - "integrity": "sha512-EpVKm4eN0Fgx2PEWpJ5NiMArV8zVoOin866jIIvzFLpmkZz1UEqgjf2JAfUJnjgv3fjSV3JqeGG2vZCaGQBTow==", - "dependencies": { - "@babel/runtime": "^7.0.0", - "react-refresh": "^0.4.0" - } - }, - "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==", - "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==", - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "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" - }, - "bin": { - "metro-symbolicate": "src/index.js" - }, - "engines": { - "node": ">=8.3" - } - }, - "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==", - "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/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/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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "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==", - "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/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==", + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "license": "ISC" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "license": "MIT", "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", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "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/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==", - "engines": { - "node": ">=8" - } - }, - "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" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "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==", - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "engines": { - "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==", - "dependencies": { - "has-flag": "^4.0.0" + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" }, "engines": { "node": ">=8" } }, - "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==", - "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" - } - }, - "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/micromatch": { - "version": "4.0.5", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "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" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/mime": { - "version": "2.6.0", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "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/minimalistic-crypto-utils": { - "version": "1.0.1", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.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/moment": { - "version": "2.29.4", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "optional": true, - "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_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" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "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" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mv/node_modules/rimraf": { - "version": "2.4.5", - "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", - "optional": true, - "dependencies": { - "glob": "^6.0.1" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "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" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "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" - }, - "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_modules/nanomatch/node_modules/array-unique": { - "version": "0.3.2", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "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": { - "version": "2.0.0", - "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", - "optional": true, - "bin": { - "ncp": "bin/ncp" - } - }, - "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==", - "engines": { - "node": ">= 0.6" - } - }, - "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==", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/node-abi": { - "version": "3.33.0", - "integrity": "sha512-7GGVawqyHF4pfd0YFybhv/eM9JwTtPqx0mAanQ146O3FlSh3pA24zf9IRQTOsfTSqXTNzPSP5iagAJ94jjuVog==", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-abi/node_modules/lru-cache": { - "version": "6.0.0", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.3.8", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "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==", - "dependencies": { - "minimatch": "^3.0.2" - }, - "engines": { - "node": ">= 0.10.5" - } - }, - "node_modules/node-fetch": { - "version": "2.6.9", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "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/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==", - "dependencies": { - "event-pubsub": "4.3.0", - "js-message": "1.0.7", - "js-queue": "2.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "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==", - "engines": { - "node": ">=0.12.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/antelle" - } - }, - "node_modules/node-version": { - "version": "1.2.0", - "integrity": "sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ==", - "engines": { - "node": ">=6.0.0" - } - }, - "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==", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/nullthrows": { - "version": "1.1.1", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" - }, - "node_modules/ob1": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.73.10.tgz", - "integrity": "sha512-aO6EYC+QRRCkZxVJhCWhLKgVjhNuD6Gu1riGjxrIm89CqLsmKgxzYDDEsktmKsoDeRdWGQM5EdMzXDl5xcVfsw==" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/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/object-copy/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/object-copy/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/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" - }, - "engines": { - "node": ">=0.10.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/object-copy/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/object-inspect": { - "version": "1.12.3", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.5", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "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" - } - }, - "node_modules/object.entries": { - "version": "1.1.6", - "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.6", - "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", - "dev": true, - "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" - } - }, - "node_modules/object.hasown": { - "version": "1.1.2", - "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.values": { - "version": "1.1.6", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, - "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" - } - }, - "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==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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/once": { - "version": "1.4.0", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "6.4.0", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/opencollective-postinstall": { - "version": "2.0.3", - "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", - "bin": { - "opencollective-postinstall": "index.js" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "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" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "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" - }, - "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" - }, - "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==", - "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/ora/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/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/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/os-tmpdir": { - "version": "1.0.2", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "^0.1.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==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-asn1": { - "version": "5.1.6", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "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" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "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" - }, - "engines": { - "node": ">=8" - }, - "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" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-type": { - "version": "4.0.0", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "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/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==", - "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" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/payjoin-client/node_modules/bitcoinjs-lib": { - "version": "5.2.0", - "integrity": "sha512-5DcLxGUDejgNBYcieMIUfjORtUeNWl828VWLHJGVKZCb4zIS1oOySTUr0LGmcqJBQgTBz3bGbRQla4FgrdQEIQ==", - "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" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "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" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pngjs": { - "version": "5.0.0", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prebuild-install": { - "version": "7.1.1", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", - "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", - "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" - } - }, - "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, - "engines": { - "node": ">= 0.8.0" - } - }, - "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" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "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" - }, - "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" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "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==", - "bin": { - "printj": "bin/printj.njs" - }, - "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_modules/process-nextick-args": { - "version": "2.0.1", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/promise": { - "version": "8.3.0", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "dependencies": { - "asap": "~2.0.6" - } - }, - "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==", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/proper-lockfile": { - "version": "3.2.0", - "integrity": "sha512-iMghHHXv2bsxl6NchhEaFck8tvX3F9cknEEh1SUpguUOBjN7PAAW9BLzmbc1g/mCD1gY3EE2EABBHPJfFdHFmA==", - "dependencies": { - "graceful-fs": "^4.1.11", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "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/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/punycode": { - "version": "2.3.0", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/pushdata-bitcoin": { - "version": "1.0.1", - "integrity": "sha512-hw7rcYTJRAl4olM8Owe8x0fBuJJ+WGbMhQuLWOXEMN3PxPCKQHRkhfL+XG0+iXUmSHjkMmb3Ba55Mt21cZc9kQ==", - "dependencies": { - "bitcoin-ops": "^1.3.0" - } - }, - "node_modules/qrcode": { - "version": "1.5.1", - "integrity": "sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==", - "dependencies": { - "dijkstrajs": "^1.0.1", - "encode-utf8": "^1.0.3", - "pngjs": "^5.0.0", - "yargs": "^15.3.1" - }, - "bin": { - "qrcode": "bin/qrcode" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/qrcode/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/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/qrcode/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/qrcode/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/qrcode/node_modules/decamelize": { - "version": "1.2.0", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/qrcode/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" - }, - "engines": { - "node": ">=8" - } - }, - "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" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/qrcode/node_modules/yargs-parser": { - "version": "18.1.3", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/query-string": { - "version": "6.14.1", - "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", - "dependencies": { - "decode-uri-component": "^0.2.0", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.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" - }, - "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" - }, - "peerDependencies": { - "react": ">=17.0.0" - } - }, - "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", - "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" - }, - "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_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/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" - } - }, - "node_modules/react-native-crypto": { - "version": "2.2.0", - "integrity": "sha512-eZu9Y8pa8BN9FU2pIex7MLRAi+Cd1Y6bsxfiufKh7sfraAACJvjQTeW7/zcQAT93WMfM+D0OVk+bubvkrbrUkw==", - "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" - }, - "engines": { - "node": "*" - }, - "peerDependencies": { - "react-native-randombytes": ">=2.0.0 <4.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" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/react-native-default-preference": { - "version": "1.4.4", - "integrity": "sha512-h0vtgiSKws3UmMRJykXAVM4ne1SgfoocUcoBD19ewRpQd6wqurE0HJRQGrSxcHK5LdKE7QPSIB1VX3YGIVS8Jg==", - "peerDependencies": { - "react-native": ">=0.47.0" - } - }, - "node_modules/react-native-device-info": { - "version": "8.7.1", - "integrity": "sha512-cVMZztFa2Qn6qpQa601W61CtUwZQ1KXfqCOeltejAWEXmgIWivC692WGSdtGudj4upSi1UgMSaGcvKjfcpdGjg==", - "peerDependencies": { - "react-native": "*" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "invariant": "^2.2.4" - }, - "peerDependencies": { - "react": "*", - "react-native": "*", - "react-native-windows": "*" - }, - "peerDependenciesMeta": { - "react-native-windows": { - "optional": true - } - } - }, - "node_modules/react-native-draggable-flatlist": { - "version": "4.0.1", - "resolved": "git+ssh://git@github.com/BlueWallet/react-native-draggable-flatlist.git#ebfddc4877e8f65d5391a748db61b9cd030430ba", - "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" - } - }, - "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, - "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" - }, - "peerDependencies": { - "react-native-safe-area-context": ">= 3.0.0", - "react-native-vector-icons": ">7.0.0" - } - }, - "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==", - "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": "*" - }, - "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_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" + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "query-string": { - "version": "6.14.1", - "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", - "requires": { - "decode-uri-component": "^0.2.0", + "node_modules/query-string": { + "version": "7.1.3", + "license": "MIT", + "dependencies": { + "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": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "querystringify": { - "version": "2.2.0", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + "node_modules/queue": { + "version": "6.0.2", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.3" + } }, - "queue-microtask": { + "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" + } + ], + "license": "MIT" }, - "randombytes": { + "node_modules/randombytes": { "version": "2.1.0", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { + "license": "MIT", + "dependencies": { "safe-buffer": "^5.1.0" } }, - "randomfill": { + "node_modules/randomfill": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "requires": { + "license": "MIT", + "dependencies": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" } }, - "range-parser": { + "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": ">= 0.6" + } }, - "rc": { + "node_modules/rc": { "version": "1.2.8", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { "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==" - } + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "react": { - "version": "18.2.0", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "requires": { - "loose-envify": "^1.1.0" + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "react-devtools-core": { - "version": "4.27.2", - "integrity": "sha512-8SzmIkpO87alD7Xr6gWIEa1jHkMjawOZ+6egjazlnjB4UUcbnzGDf/vBJ4BzGuWWEM+pzrxuzsPpcMqlQkYK2g==", - "requires": { + "node_modules/react-devtools-core": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.1.tgz", + "integrity": "sha512-TFo1MEnkqE6hzAbaztnyR5uLTMoz6wnEWwWBsCUzNt+sVXJycuRJdDqvL078M4/h65BI/YO5XWTaxZDWVsW0fw==", + "license": "MIT", + "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, - "react-freeze": { - "version": "1.0.3", - "integrity": "sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g==" + "node_modules/react-devtools-core/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 + } + } }, - "react-is": { - "version": "16.13.1", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "node_modules/react-freeze": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=17.0.0" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "license": "MIT" }, - "react-localization": { - "version": "git+ssh://git@github.com/BlueWallet/react-localization.git#ae7969a8998128aebf1169f931fb22587dc5f874", - "from": "react-localization@github:BlueWallet/react-localization#ae7969a", - "requires": { + "node_modules/react-localization": { + "version": "1.0.19", + "resolved": "git+ssh://git@github.com/BlueWallet/react-localization.git#ae7969a8998128aebf1169f931fb22587dc5f874", + "license": "MIT", + "dependencies": { "localized-strings": "github:BlueWallet/localized-strings#178351a7297336618d9ee87277f8e3af9ab7285d" + }, + "peerDependencies": { + "react": "^18.0.0 || ^17.0.0 || ^16.0.0 || ^15.6.0" } }, - "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", + "node_modules/react-native": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.78.2.tgz", + "integrity": "sha512-UilZ8sP9amHCz7TTMWMJ71JeYcMzEdgCJaqTfoB1hC/nYMXq6xqSFxKWCDhf7sR7nz3FKxS4t338t42AMDDkww==", + "license": "MIT", + "dependencies": { + "@jest/create-cache-key-function": "^29.6.3", + "@react-native/assets-registry": "0.78.2", + "@react-native/codegen": "0.78.2", + "@react-native/community-cli-plugin": "0.78.2", + "@react-native/gradle-plugin": "0.78.2", + "@react-native/js-polyfills": "0.78.2", + "@react-native/normalize-colors": "0.78.2", + "@react-native/virtualized-lists": "0.78.2", "abort-controller": "^3.0.0", "anser": "^1.4.9", - "base64-js": "^1.1.2", - "deprecated-react-native-prop-types": "^3.0.1", + "ansi-regex": "^5.0.0", + "babel-jest": "^29.7.0", + "babel-plugin-syntax-hermes-parser": "0.25.1", + "base64-js": "^1.5.1", + "chalk": "^4.0.0", + "commander": "^12.0.0", "event-target-shim": "^5.0.1", + "flow-enums-runtime": "^0.0.6", + "glob": "^7.1.1", "invariant": "^2.2.4", - "jest-environment-node": "^29.2.1", - "jsc-android": "^250231.0.0", + "jest-environment-node": "^29.6.3", "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", + "metro-runtime": "^0.81.3", + "metro-source-map": "^0.81.3", "nullthrows": "^1.1.1", - "pretty-format": "^26.5.2", + "pretty-format": "^29.7.0", "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", + "react-devtools-core": "^6.0.1", + "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", - "scheduler": "^0.23.0", - "stacktrace-parser": "^0.1.3", - "use-sync-external-store": "^1.0.0", + "scheduler": "0.25.0", + "semver": "^7.1.3", + "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", - "ws": "^6.2.2" + "ws": "^6.2.3", + "yargs": "^17.6.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" - } + "bin": { + "react-native": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": "^19.0.0", + "react": "^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true } } }, - "react-native-animatable": { - "version": "1.3.3", - "integrity": "sha512-2ckIxZQAsvWn25Ho+DK3d1mXIgj7tITkrS4pYDvx96WyOttSvzzFeQnM2od0+FUMzILbdHDsDEqZvnz1DYNQ1w==", - "requires": { - "prop-types": "^15.7.2" + "node_modules/react-native-biometrics": { + "version": "3.0.1", + "license": "MIT", + "peerDependencies": { + "react-native": ">=0.60.0" } }, - "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" + "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": "*" + } }, - "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" + "node_modules/react-native-camera-kit-no-google": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/react-native-camera-kit-no-google/-/react-native-camera-kit-no-google-16.2.0.tgz", + "integrity": "sha512-GcjritBUAL8EkUnbmoka9LjkDMb/fTl+RksBMb0WsGK3LV5E/ZwWPTUkFayKM7qKV172lS8jb3Iy0fSxVjc3Ww==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "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" + "node_modules/react-native-capture-protection": { + "version": "2.0.7", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-capture-protection.git#54d9009d8cafc41230e608a1f8b912008e6d8e8e", + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "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/react-native-device-info": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-14.1.1.tgz", + "integrity": "sha512-lXFpe6DJmzbQXNLWxlMHP2xuTU5gwrKAvI8dCAZuERhW9eOXSubOQIesk9lIBnsi9pI19GMrcpJEvs4ARPRYmw==", + "license": "MIT", + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/react-native-draglist": { + "version": "3.10.0", + "resolved": "git+ssh://git@github.com/BlueWallet/react-native-draglist.git#0c8049d5c56f34bad840b1c4a43c58299f87bbc7", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0.1", + "react-native": ">=0.64.0" + } + }, + "node_modules/react-native-drawer-layout": { + "version": "4.1.4", + "license": "MIT", + "dependencies": { + "use-latest-callback": "^0.2.1" }, + "peerDependencies": { + "react": ">= 18.2.0", + "react-native": "*", + "react-native-gesture-handler": ">= 2.0.0", + "react-native-reanimated": ">= 2.0.0" + } + }, + "node_modules/react-native-fs": { + "version": "2.20.0", + "license": "MIT", "dependencies": { - "pbkdf2": { - "version": "3.0.8", - "integrity": "sha512-Bf7yBd61ChnMqPqf+PxHm34Iiq9M9Bkd/+JqzosPOqwG6FiTixtkpCs4PNd38+6/VYRvAxGe/GgPb4Q4GktFzg==", - "requires": { - "create-hmac": "^1.1.2" - } + "base-64": "^0.1.0", + "utf8": "^3.0.0" + }, + "peerDependencies": { + "react-native": "*", + "react-native-windows": "*" + }, + "peerDependenciesMeta": { + "react-native-windows": { + "optional": true } } }, - "react-native-default-preference": { - "version": "1.4.4", - "integrity": "sha512-h0vtgiSKws3UmMRJykXAVM4ne1SgfoocUcoBD19ewRpQd6wqurE0HJRQGrSxcHK5LdKE7QPSIB1VX3YGIVS8Jg==" + "node_modules/react-native-gesture-handler": { + "version": "2.25.0", + "license": "MIT", + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } }, - "react-native-device-info": { - "version": "8.7.1", - "integrity": "sha512-cVMZztFa2Qn6qpQa601W61CtUwZQ1KXfqCOeltejAWEXmgIWivC692WGSdtGudj4upSi1UgMSaGcvKjfcpdGjg==" + "node_modules/react-native-get-random-values": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/react-native-get-random-values/-/react-native-get-random-values-1.11.0.tgz", + "integrity": "sha512-4BTbDbRmS7iPdhYLRcz3PGFIpFJBwNZg9g42iwa2P6FOv9vZj/xJc678RZXnLNZzd0qd7Q3CCF6Yd+CU2eoXKQ==", + "license": "MIT", + "dependencies": { + "fast-base64-decode": "^1.0.0" + }, + "peerDependencies": { + "react-native": ">=0.56" + } }, - "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" + "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": "*" } }, - "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" + "node_modules/react-native-haptic-feedback": { + "version": "2.3.3", + "license": "MIT", + "workspaces": [ + "example" + ], + "peerDependencies": { + "react-native": ">=0.60.0" } }, - "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" + "node_modules/react-native-image-picker": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-8.2.1.tgz", + "integrity": "sha512-FBeGYJGFDjMdGCcyubDJgBAPCQ4L1D3hwLXyUU91jY9ahOZMTbluceVvRmrEKqnDPFJ0gF1NVhJ0nr1nROFLdg==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "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" + "node_modules/react-native-is-edge-to-edge": { + "version": "1.1.7", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "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" + "node_modules/react-native-keychain": { + "version": "9.1.0", + "license": "MIT", + "workspaces": [ + "KeychainExample", + "website" + ], + "engines": { + "node": ">=18" } }, - "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" + "node_modules/react-native-linear-gradient": { + "version": "2.8.3", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "react-native-iphone-x-helper": { - "version": "1.3.1", - "integrity": "sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==" + "node_modules/react-native-localize": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-3.5.4.tgz", + "integrity": "sha512-Sr0XMSh76vFxZtRznD5FE8ZZtA4Srr7+Ix4WePhoQdeiJzuJEFbiQ2Lk3JJhsTMZdsZKn3j1KUwpV3hhnm2BqA==", + "license": "MIT", + "peerDependencies": { + "@expo/config-plugins": "^9.0.0 || ^10.0.0", + "react": "*", + "react-native": "*", + "react-native-macos": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "react-native-macos": { + "optional": true + } + } }, - "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-permissions": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-5.4.4.tgz", + "integrity": "sha512-WB5lRCBGXETfuaUhem2vgOceb9+URCeyfKpLGFSwoOffLuyJCA6+NTR3l1KLkrK4Ykxsig37z16/shUVufmt7A==", + "license": "MIT", + "peerDependencies": { + "react": ">=18.1.0", + "react-native": ">=0.70.0", + "react-native-windows": ">=0.70.0" + }, + "peerDependenciesMeta": { + "react-native-windows": { + "optional": true + } + } }, - "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==" + "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" }, - "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": { + "node_modules/react-native-push-notification": { "version": "8.1.1", - "integrity": "sha512-XpBtG/w+a6WXTxu6l1dNYyTiHnbgnvjoc3KxPTxYkaIABRmvuJZkFxqruyGvfCw7ELAlZEAJO+dthdTabCe1XA==" + "license": "MIT", + "peerDependencies": { + "@react-native-community/push-notification-ios": "^1.10.1", + "react-native": ">=0.33" + } }, - "react-native-qrcode-svg": { - "version": "6.2.0", - "integrity": "sha512-rb2PgUwT8QpQyReVYNvzRY84AHsMh81354Tnkfp6MfqRbcdJURhnBWLBOO11pLMS6eXiwlq4SkcQxy88hRq+Dw==", - "requires": { + "node_modules/react-native-qrcode-svg": { + "version": "6.3.21", + "resolved": "https://registry.npmjs.org/react-native-qrcode-svg/-/react-native-qrcode-svg-6.3.21.tgz", + "integrity": "sha512-6vcj4rcdpWedvphDR+NSJcudJykNuLgNGFwm2p4xYjR8RdyTzlrELKI5LkO4ANS9cQUbqsfkpippPv64Q2tUtA==", + "license": "MIT", + "dependencies": { "prop-types": "^15.8.0", - "qrcode": "^1.5.1" + "qrcode": "^1.5.4", + "text-encoding": "^0.7.0" + }, + "peerDependencies": { + "react": "*", + "react-native": ">=0.63.4", + "react-native-svg": ">=14.0.0" } }, - "react-native-quick-actions": { + "node_modules/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" - } - } - } + "license": "MIT" }, - "react-native-rate": { + "node_modules/react-native-rate": { "version": "1.2.12", - "integrity": "sha512-A/z3s7Zth08aXcJnru6S4p71NG8acx2w5LhIfItwTJUbQruNJugk8WrN51dLBCSDv8W33kbS5YoUT4M9jOP5gA==" + "license": "MIT" }, - "react-native-ratings": { - "version": "8.0.4", - "integrity": "sha512-Xczu5lskIIRD6BEdz9A0jDRpEck/SFxRqiglkXi0u67yAtI1/pcJC76P4MukCbT8K4BPVl+42w83YqXBoBRl7A==", - "requires": { + "node_modules/react-native-ratings": { + "version": "8.1.0", + "license": "MIT", + "dependencies": { "lodash": "^4.17.15" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "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", + "node_modules/react-native-reanimated": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.18.0.tgz", + "integrity": "sha512-eVcNcqeOkMW+BUWAHdtvN3FKgC8J8wiEJkX6bNGGQaLS7m7e4amTfjIcqf/Ta+lerZLurmDaQ0lICI1CKPrb1Q==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-arrow-functions": "^7.0.0-0", + "@babel/plugin-transform-class-properties": "^7.0.0-0", + "@babel/plugin-transform-classes": "^7.0.0-0", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", + "@babel/plugin-transform-optional-chaining": "^7.0.0-0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", + "@babel/plugin-transform-template-literals": "^7.0.0-0", + "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", + "convert-source-map": "^2.0.0", "invariant": "^2.2.4", - "lodash.isequal": "^4.5.0", - "setimmediate": "^1.0.5", - "string-hash-64": "^1.0.3" + "react-native-is-edge-to-edge": "1.1.7" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0", + "react": "*", + "react-native": "*" } }, - "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==" - } + "node_modules/react-native-safe-area-context": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.5.2.tgz", + "integrity": "sha512-t4YVbHa9uAGf+pHMabGrb0uHrD5ogAusSu842oikJ3YKXcYp6iB4PTGl0EZNkUIR3pCnw/CXKn42OCfhsS0JIw==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "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": { + "node_modules/react-native-screens": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz", + "integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==", + "license": "MIT", + "dependencies": { "react-freeze": "^1.0.0", + "react-native-is-edge-to-edge": "^1.2.1", "warn-once": "^0.1.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "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" + "node_modules/react-native-screens/node_modules/react-native-is-edge-to-edge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz", + "integrity": "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } }, - "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-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" }, - "react-native-size-matters": { - "version": "0.3.1", - "integrity": "sha512-mKOfBLIBFBcs9br1rlZDvxD5+mAl8Gfr5CounwJtxI6Z82rGrMO+Kgl9EIg3RMVf3G855a85YVqHJL2f5EDRlw==" + "node_modules/react-native-share": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-12.1.0.tgz", + "integrity": "sha512-9OZrrEx9pZfl8Pw8Uh/k5Tc1o6oBaBfctq3z2wxy6eJBY6O/aRhs7/WPrXAoQmh9dCxc5sIClcKN57ef34iORQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } }, - "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": { + "node_modules/react-native-size-matters": { + "version": "0.4.2", + "license": "MIT", + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/react-native-svg": { + "version": "15.12.1", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.1.tgz", + "integrity": "sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g==", + "license": "MIT", + "dependencies": { "css-select": "^5.1.0", - "css-tree": "^1.1.3" + "css-tree": "^1.1.3", + "warn-once": "0.1.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "react-native-tcp-socket": { - "version": "5.6.2", - "integrity": "sha512-doijFOAJd9p8KmduhfbZaPfqRVd3CZuTLAimJx0yxIqFWy/EDPGHeFVrOEOqRZ3lWBVDcssiCIQJhV0baKu5Pg==", - "requires": { + "node_modules/react-native-tcp-socket": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/react-native-tcp-socket/-/react-native-tcp-socket-6.3.0.tgz", + "integrity": "sha512-NthR04myL5IGL4q+2oCjmAx7OMqEgcjUJk8RExAwNjK/164upCP7Pn2Msno5ZTGd3wL6HuEG8d7T2d8f22N+0A==", + "license": "MIT", + "dependencies": { "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" - } - } + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/Rapsssito" + }, + "peerDependencies": { + "react-native": ">=0.60.0" } }, - "react-native-tor": { - "version": "0.1.8", - "integrity": "sha512-rRArwpqTQoUrQ3WQxc1WLkk1Eg/yjiwU775AxnSt6E7Nfy1V9H6+R9reMD5PJ/39qcuDYv3F7i3MR8Rjy7lOZA==", - "requires": { - "@types/async": "^3.2.6", - "async": "^3.2.0" + "node_modules/react-native-tcp-socket/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", + "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" } }, - "react-native-vector-icons": { - "version": "9.2.0", - "integrity": "sha512-wKYLaFuQST/chH3AJRjmOLoLy3JEs1JR6zMNgTaemFpNoXs0ztRnTxcxFD9xhX7cJe1/zoN5BpQYe7kL0m5yyA==", - "requires": { + "node_modules/react-native-vector-icons": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.2.0.tgz", + "integrity": "sha512-n5HGcxUuVaTf9QJPs/W22xQpC2Z9u0nb0KgLPnVltP8vdUvOp6+R26gF55kilP/fV4eL4vsAHUqUjewppJMBOQ==", + "license": "MIT", + "dependencies": { "prop-types": "^15.7.2", "yargs": "^16.1.1" + }, + "bin": { + "fa-upgrade.sh": "bin/fa-upgrade.sh", + "fa5-upgrade": "bin/fa5-upgrade.sh", + "fa6-upgrade": "bin/fa6-upgrade.sh", + "generate-icon": "bin/generate-icon.js" + } + }, + "node_modules/react-native-vector-icons/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/react-native-vector-icons/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/react-native-vector-icons/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "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/react-native-vector-icons/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" } }, - "react-native-watch-connectivity": { + "node_modules/react-native-watch-connectivity": { "version": "1.1.0", - "integrity": "sha512-s+zlKOVENRXgkVQvJt5f73CyqpC6ZhKlRsXLybtaFIsR1KR3ARzExm0yTm3DAb5K9AqtCpYX+1SOd4d0Af2ZNQ==", - "requires": { + "license": "MIT", + "dependencies": { "lodash.sortby": "^4.7.0" + }, + "peerDependencies": { + "react": ">=15.1", + "react-native": ">=0.40" + } + }, + "node_modules/react-native/node_modules/@react-native/js-polyfills": { + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.78.2.tgz", + "integrity": "sha512-b7eCPAs3uogdDeTvOTrU6i8DTTsHyjyp48R5pVakJIREhEx+SkUnlVk11PYjbCKGYjYgN939Tb5b1QWNtdrPIQ==", + "license": "MIT", + "engines": { + "node": ">=18" } }, - "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" + "node_modules/react-native/node_modules/ansi-styles": { + "version": "5.2.0", + "license": "MIT", + "engines": { + "node": ">=10" }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-native/node_modules/commander": { + "version": "12.1.0", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native/node_modules/pretty-format": { + "version": "29.7.0", + "license": "MIT", "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==" - } + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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" + "node_modules/react-native/node_modules/regenerator-runtime": { + "version": "0.13.11", + "license": "MIT" }, - "react-refresh": { - "version": "0.4.3", - "integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==" + "node_modules/react-native/node_modules/semver": { + "version": "7.7.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "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" + "node_modules/react-refresh": { + "version": "0.14.2", + "license": "MIT", + "engines": { + "node": ">=0.10.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" - }, + "node_modules/react-test-renderer": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.0.0.tgz", + "integrity": "sha512-oX5u9rOQlHzqrE/64CNr0HB0uWxkCQmZNSfozlYvwE71TLVgeZxVf0IjouGEr1v7r1kcDifdAJBeOhdhxsG/DA==", + "license": "MIT", "dependencies": { - "react-is": { - "version": "18.2.0", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - } + "react-is": "^19.0.0", + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" } }, - "read-pkg": { + "node_modules/react-test-renderer/node_modules/react-is": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", + "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==", + "license": "MIT" + }, + "node_modules/read-pkg": { "version": "5.2.0", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "requires": { + "license": "MIT", + "dependencies": { "@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==" - } + "engines": { + "node": ">=8" } }, - "read-pkg-up": { + "node_modules/read-pkg-up": { "version": "7.0.1", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "requires": { + "license": "MIT", + "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/find-up": { + "version": "4.1.0", + "license": "MIT", "dependencies": { - "type-fest": { - "version": "0.8.1", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" - } + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" } }, - "readable-stream": { + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "license": "(MIT OR CC0-1.0)", + "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==", - "requires": { + "license": "MIT", + "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "readline": { + "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==" + "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", + "license": "BSD" }, - "realm": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/realm/-/realm-12.0.0.tgz", - "integrity": "sha512-QaFnn92eCwpWCvbnxE26N0mAHhuXRwtM23JF0tA5fKtTA+djj1+LhuT/iGJryCrGVbN8m++EOBsvWgGG8hpsuw==", - "requires": { + "node_modules/realm": { + "version": "20.1.0", + "hasInstallScript": true, + "license": "apache-2.0", + "dependencies": { + "@realm/fetch": "^0.1.1", "bson": "^4.7.2", "debug": "^4.3.4", - "node-fetch": "^2.6.9", "node-machine-id": "^1.1.12", - "prebuild-install": "^7.1.1" + "path-browserify": "^1.0.1", + "prebuild-install": "^7.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react-native": ">=0.71.0" + }, + "peerDependenciesMeta": { + "react-native": { + "optional": true + } } }, - "recast": { - "version": "0.20.5", - "integrity": "sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==", - "requires": { - "ast-types": "0.14.2", + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" } }, - "reduce-flatten": { - "version": "2.0.0", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==" + "node_modules/redent": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, - "regenerate": { - "version": "1.4.2", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + "node_modules/reduce-flatten": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "regenerate-unicode-properties": { - "version": "10.1.0", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "requires": { - "regenerate": "^1.4.2" + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "regenerator-runtime": { - "version": "0.13.11", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "node_modules/regenerate": { + "version": "1.4.2", + "license": "MIT" }, - "regenerator-transform": { - "version": "0.15.1", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", - "requires": { - "@babel/runtime": "^7.8.4" + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" } }, - "regex-not": { - "version": "1.0.2", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "license": "MIT" }, - "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" + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "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" } }, - "regexpu-core": { - "version": "5.3.1", - "integrity": "sha512-nCOzW2V/X15XpLsK2rlgdwrysrBq+AauCn+omItIz4R1pIcmeot5zvjdmOBRLzEH/CkC6IxMJVmxDe3QcMuNVQ==", - "requires": { - "@babel/regjsgen": "^0.8.0", + "node_modules/regexpu-core": { + "version": "6.2.0", + "license": "MIT", + "dependencies": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" } }, - "regjsparser": { - "version": "0.9.1", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "requires": { - "jsesc": "~0.5.0" - }, + "node_modules/regjsgen": { + "version": "0.8.0", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "license": "BSD-2-Clause", "dependencies": { - "jsesc": { - "version": "0.5.0", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" - } + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" } }, - "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==" + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } }, - "require-directory": { + "node_modules/require-directory": { "version": "2.1.1", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "require-from-string": { + "node_modules/require-from-string": { "version": "2.0.2", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "require-main-filename": { + "node_modules/require-main-filename": { "version": "2.0.0", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "license": "ISC" }, - "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", + "node_modules/resolve": { + "version": "1.22.10", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "resolve-cwd": { + "node_modules/resolve-cwd": { "version": "3.0.0", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" } }, - "resolve-from": { + "node_modules/resolve-from": { "version": "5.0.0", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "resolve-url": { - "version": "0.2.1", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==" + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } }, - "resolve.exports": { + "node_modules/resolve.exports": { "version": "1.1.1", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } }, - "restore-cursor": { + "node_modules/restore-cursor": { "version": "3.1.0", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "requires": { + "license": "MIT", + "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" } }, - "ret": { - "version": "0.1.15", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" - }, - "retry": { + "node_modules/retry": { "version": "0.12.0", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==" + "license": "MIT", + "engines": { + "node": ">= 4" + } }, - "reusify": { - "version": "1.0.4", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "node_modules/reusify": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } }, - "rimraf": { + "node_modules/rimraf": { "version": "3.0.2", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { + "license": "ISC", + "dependencies": { "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "ripemd160": { + "node_modules/ripemd160": { "version": "2.0.2", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "requires": { + "license": "MIT", + "dependencies": { "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" + "node_modules/rn-qr-generator": { + "version": "1.4.3", + "resolved": "git+ssh://git@github.com/BlueWallet/rn-qr-generator.git#731ed8eb445f65f3a659632232e18ff7e1ce56d6", + "integrity": "sha512-+hZFzqkWjxg7Ey9Tw8wjGuyY6I7R8ypQJa23wL0GUyeEBISxGCnz0W/2ULMxvd/w2FethMeTAkXe8+lDy8/vvw==", + "license": "MIT", + "peerDependencies": { + "react-native": ">=0.55" + } }, - "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" - } + "node_modules/run-parallel": { + "version": "1.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "rimraf": { - "version": "2.7.1", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "semver": { - "version": "5.7.1", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + { + "type": "consulting", + "url": "https://feross.org/support" } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" } }, - "run-applescript": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", - "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "node_modules/safe-array-concat": { + "version": "1.1.3", "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" + "license": "MIT", + "dependencies": { + "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": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "safe-buffer": { + "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" + } + ], + "license": "MIT" }, - "safe-json-stringify": { + "node_modules/safe-json-stringify": { "version": "1.2.0", - "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "license": "MIT", "optional": true }, - "safe-regex": { - "version": "1.1.0", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "requires": { - "ret": "~0.1.10" - } - }, - "safe-regex-test": { + "node_modules/safe-push-apply": { "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" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "safer-buffer": { - "version": "2.1.2", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, - "sanitize-filename": { + "node_modules/sanitize-filename": { "version": "1.6.3", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", - "requires": { + "license": "WTFPL OR ISC", + "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, - "scheduler": { - "version": "0.23.0", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "requires": { - "loose-envify": "^1.1.0" - } + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" }, - "scryptsy": { + "node_modules/scryptsy": { "version": "2.1.0", - "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==" + "license": "MIT" }, - "secp256k1": { - "version": "3.8.0", - "integrity": "sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw==", - "requires": { + "node_modules/secp256k1": { + "version": "3.8.1", + "hasInstallScript": true, + "license": "MIT", + "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", + "elliptic": "^6.5.7", "nan": "^2.14.0", "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4.0.0" } }, - "semver": { + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/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": { + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.0", + "license": "MIT", + "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -36595,1325 +17476,1515 @@ "range-parser": "~1.2.1", "statuses": "2.0.1" }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", "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==" - } + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-error": { + "version": "8.1.0", + "license": "MIT", + "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", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "license": "ISC" + }, + "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.4" + } + }, + "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": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" } }, - "serialize-error": { - "version": "8.1.0", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", - "requires": { - "type-fest": "^0.20.2" - }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", "dependencies": { - "type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" - } + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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" + "node_modules/shallow-clone": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" } }, - "set-blocking": { + "node_modules/shebang-command": { "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" - } - } + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "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" + "node_modules/shebang-regex": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" } }, - "shallow-clone": { - "version": "3.0.1", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "requires": { - "kind-of": "^6.0.2" + "node_modules/shell-quote": { + "version": "1.8.2", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "shebang-command": { - "version": "1.2.0", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "requires": { - "shebang-regex": "^1.0.0" + "node_modules/side-channel": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "shebang-regex": { + "node_modules/side-channel-list": { "version": "1.0.0", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "shell-quote": { - "version": "1.8.0", - "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==" + "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "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" + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "signal-exit": { + "node_modules/signal-exit": { "version": "3.0.7", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "license": "ISC" + }, + "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" + } }, - "simple-concat": { + "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" + } + ], + "license": "MIT" }, - "simple-get": { + "node_modules/simple-get": { "version": "4.0.1", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "requires": { + "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": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, - "simple-swizzle": { + "node_modules/simple-swizzle": { "version": "0.2.2", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "requires": { - "is-arrayish": "^0.3.1" - }, + "license": "MIT", "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - } + "is-arrayish": "^0.3.1" } }, - "sisteransi": { - "version": "1.0.5", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "license": "MIT" }, - "sjcl": { - "version": "1.0.8", - "integrity": "sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ==" + "node_modules/sisteransi": { + "version": "1.0.5", + "license": "MIT" }, - "slash": { + "node_modules/slash": { "version": "3.0.0", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "slice-ansi": { + "node_modules/slice-ansi": { "version": "2.1.0", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "requires": { + "license": "MIT", + "dependencies": { "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==" - } + "engines": { + "node": ">=6" } }, - "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" - } - } + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "snapdragon-util": { - "version": "3.0.1", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "requires": { - "kind-of": "^3.2.0" - }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "1.9.3", + "license": "MIT", "dependencies": { - "kind-of": { - "version": "3.2.2", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } + "color-name": "1.1.3" } }, - "source-map": { - "version": "0.6.1", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.3", + "license": "MIT" }, - "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" + "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" } }, - "source-map-url": { - "version": "0.4.1", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" + "node_modules/source-map-support": { + "version": "0.5.13", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } }, - "spdx-correct": { - "version": "3.1.1", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "requires": { + "node_modules/spdx-correct": { + "version": "3.2.0", + "license": "Apache-2.0", + "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, - "spdx-exceptions": { - "version": "2.3.0", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "license": "CC-BY-3.0" }, - "spdx-expression-parse": { + "node_modules/spdx-expression-parse": { "version": "3.0.1", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "requires": { + "license": "MIT", + "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, - "spdx-license-ids": { - "version": "3.0.12", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==" + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "license": "CC0-1.0" }, - "split-on-first": { + "node_modules/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" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "sprintf-js": { + "node_modules/sprintf-js": { "version": "1.0.3", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + "license": "BSD-3-Clause" }, - "stack-generator": { + "node_modules/stack-generator": { "version": "2.0.10", - "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", - "requires": { + "license": "MIT", + "dependencies": { "stackframe": "^1.3.4" } }, - "stack-utils": { + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/stack-utils": { "version": "2.0.6", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "requires": { + "license": "MIT", + "dependencies": { "escape-string-regexp": "^2.0.0" }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - } + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=8" } }, - "stackframe": { + "node_modules/stackframe": { "version": "1.3.4", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" + "license": "MIT" }, - "stacktrace-parser": { - "version": "0.1.10", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "requires": { + "node_modules/stacktrace-parser": { + "version": "0.1.11", + "license": "MIT", + "dependencies": { "type-fest": "^0.7.1" }, - "dependencies": { - "type-fest": { - "version": "0.7.1", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==" - } + "engines": { + "node": ">=6" } }, - "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==" - } + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" } }, - "statuses": { + "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": ">= 0.6" + } }, - "stream-browserify": { + "node_modules/stream-browserify": { "version": "3.0.0", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "requires": { + "license": "MIT", + "dependencies": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" } }, - "stream-chain": { + "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==" + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause" }, - "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": { + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "license": "BSD-3-Clause", + "dependencies": { "stream-chain": "^2.2.5" } }, - "strict-uri-encode": { + "node_modules/strict-uri-encode": { "version": "2.0.0", - "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==" + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "string_decoder": { + "node_modules/string_decoder": { "version": "1.3.0", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { + "license": "MIT", + "dependencies": { "safe-buffer": "~5.2.0" } }, - "string-hash-64": { - "version": "1.0.3", - "integrity": "sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==" - }, - "string-length": { + "node_modules/string-length": { "version": "4.0.2", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "string-natural-compare": { + "node_modules/string-natural-compare": { "version": "3.0.1", - "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", - "dev": true + "dev": true, + "license": "MIT" }, - "string-width": { + "node_modules/string-width": { "version": "4.2.3", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { + "license": "MIT", + "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", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", "dependencies": { - "is-fullwidth-code-point": { - "version": "3.0.0", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - } + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "string.prototype.matchall": { - "version": "4.0.8", - "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "node_modules/string.prototype.matchall": { + "version": "4.0.12", "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" + "license": "MIT", + "dependencies": { + "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "string.prototype.trimend": { - "version": "1.0.6", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "node_modules/string.prototype.repeat": { + "version": "1.0.0", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" } }, - "string.prototype.trimstart": { - "version": "1.0.6", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "node_modules/string.prototype.trim": { + "version": "1.2.10", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "license": "MIT", + "dependencies": { + "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "strip-ansi": { - "version": "6.0.1", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" + "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.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "strip-bom": { - "version": "4.0.0", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true + "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "strip-eof": { - "version": "1.0.0", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==" + "node_modules/strip-ansi": { + "version": "5.2.0", + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "strip-final-newline": { + "node_modules/strip-final-newline": { "version": "2.0.0", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } }, - "strip-json-comments": { + "node_modules/strip-json-comments": { "version": "3.1.1", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + "node_modules/strnum": { + "version": "1.1.2", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" }, - "sudo-prompt": { + "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==" + "license": "MIT" }, - "supports-color": { - "version": "5.5.0", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" + "node_modules/supports-color": { + "version": "7.2.0", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "supports-hyperlinks": { + "node_modules/supports-hyperlinks": { "version": "2.3.0", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "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" - } - } + "engines": { + "node": ">=8" } }, - "supports-preserve-symlinks-flag": { + "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "synckit": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", - "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "node_modules/synckit": { + "version": "0.11.2", "dev": true, - "requires": { - "@pkgr/utils": "^2.3.1", - "tslib": "^2.5.0" + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/table-layout": { + "version": "1.0.2", + "license": "MIT", + "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", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/table-layout/node_modules/typical": { + "version": "5.2.0", + "license": "MIT", + "engines": { + "node": ">=8" } }, - "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" - }, + "node_modules/tar-fs": { + "version": "2.1.2", + "license": "MIT", "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": { + "node_modules/tar-stream": { "version": "2.2.0", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "requires": { + "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": ">=6" } }, - "telnet-client": { + "node_modules/telnet-client": { "version": "1.2.8", - "integrity": "sha512-W+w4k3QAmULVNhBVT2Fei369kGZCh/TH25M7caJAXW+hLxwoQRuw0di3cX4l0S9fgH3Mvq7u+IFMoBDpEw/eIg==", - "requires": { + "license": "MIT", + "dependencies": { "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" + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" } }, - "terminal-link": { + "node_modules/terminal-link": { "version": "2.1.1", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "terser": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.0.tgz", - "integrity": "sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==", - "requires": { + "node_modules/terser": { + "version": "5.39.0", + "license": "BSD-2-Clause", + "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/commander": { + "version": "2.20.3", + "license": "MIT" + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "license": "MIT", "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" - } - } + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "test-exclude": { + "node_modules/test-exclude": { "version": "6.0.0", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "text-table": { + "node_modules/text-encoding": { + "version": "0.7.0", + "license": "(Unlicense OR Apache-2.0)" + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/text-table": { "version": "0.2.0", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "license": "MIT" }, - "throat": { + "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" }, - "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" - } - } - } + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" }, - "tiny-secp256k1": { - "version": "1.1.6", - "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", - "requires": { + "node_modules/tiny-secp256k1": { + "version": "1.1.7", + "hasInstallScript": true, + "license": "MIT", + "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" } }, - "titleize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", - "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", - "dev": true + "node_modules/tmp": { + "version": "0.2.3", + "license": "MIT", + "engines": { + "node": ">=14.14" + } }, - "tmpl": { + "node_modules/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==" + "license": "BSD-3-Clause" }, - "to-object-path": { - "version": "0.3.0", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "requires": { - "kind-of": "^3.0.2" - }, + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "license": "MIT", "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" + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" } }, - "to-regex-range": { + "node_modules/to-regex-range": { "version": "5.0.1", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { + "license": "MIT", + "dependencies": { "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "toidentifier": { + "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": ">=0.6" + } }, - "tr46": { + "node_modules/tr46": { "version": "0.0.3", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "dev": true, + "license": "MIT" }, - "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" + "node_modules/trace-event-lib": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/trace-event-lib/-/trace-event-lib-1.4.1.tgz", + "integrity": "sha512-TOgFolKG8JFY+9d5EohGWMvwvteRafcyfPWWNIqcuD1W/FUvxWcy2MSCZ/beYHM63oYPHYHCd3tkbgCctHVP7w==", + "license": "MIT", + "dependencies": { + "browser-process-hrtime": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" } }, - "truncate-utf8-bytes": { + "node_modules/truncate-utf8-bytes": { "version": "1.0.2", - "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", - "requires": { + "license": "WTFPL", + "dependencies": { "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", + "node_modules/ts-api-utils": { + "version": "1.4.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-jest": { + "version": "29.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "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.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" + "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": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@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/ts-jest/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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/ts-jest/node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/ts-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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.39.1", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" }, - "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 - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "tsconfig-paths": { - "version": "3.14.1", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "node_modules/tsconfig-paths": { + "version": "3.15.0", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" - }, + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "dev": true, + "license": "MIT", "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 - } + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" + "node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" }, - "tsutils": { + "node_modules/tsutils": { "version": "3.21.0", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "tslib": "^1.8.1" }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } + "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" } }, - "tunnel-agent": { + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel-agent": { "version": "0.6.0", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "requires": { + "license": "Apache-2.0", + "dependencies": { "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" } }, - "type-check": { + "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, - "requires": { + "license": "MIT", + "dependencies": { "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "type-detect": { + "node_modules/type-detect": { "version": "4.0.8", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "type-fest": { + "node_modules/type-fest": { "version": "0.21.3", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "dev": true, + "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" + } }, - "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", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "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", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.9.tgz", + "integrity": "sha512-9vqDWmoSXOoi+K14zNaf6LBV51Q8MayF0/IiQs3GlygIKUYtog603e6virExkjjFosfJUBI4LhbQK1iq8IG11A==", + "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.0", + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "unicode-property-aliases-ecmascript": { + "node_modules/unicode-property-aliases-ecmascript": { "version": "2.1.0", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" - }, - "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" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "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==" + "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.1.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.3", + "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.4.0", + "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 +18992,407 @@ "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==" - }, - "uuid": { - "version": "3.4.0", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } }, - "v8-to-istanbul": { + "node_modules/v8-to-istanbul": { "version": "8.1.1", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^1.6.0", "source-map": "^0.7.3" }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul/node_modules/source-map": { + "version": "0.7.4", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "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 } } }, - "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==" + "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==" + "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", - "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", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "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", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "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", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "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" + } + }, + "node_modules/wordwrapjs/node_modules/typical": { + "version": "5.2.0", + "license": "MIT", + "engines": { + "node": ">=8" } }, - "wrap-ansi": { + "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==" - }, - "xtend": { - "version": "4.0.2", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "node_modules/ws": { + "version": "6.2.3", + "license": "MIT", + "dependencies": { + "async-limiter": "~1.0.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" }, - "yargs": { - "version": "16.2.0", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "requires": { - "cliui": "^7.0.2", + "node_modules/yaml": { + "version": "2.7.1", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "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" + } } } } diff --git a/package.json b/package.json index b5a4aed3207..75b12b45331 100644 --- a/package.json +++ b/package.json @@ -1,219 +1,185 @@ { "name": "bluewallet", - "version": "6.4.9", + "version": "7.2.4", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/BlueWallet/BlueWallet.git" }, "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/babel-preset": "0.78.2", + "@react-native/eslint-config": "^0.78.3", + "@react-native/js-polyfills": "^0.78.3", + "@react-native/metro-babel-transformer": "^0.78.3", + "@react-native/typescript-config": "0.78.2", + "@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/crypto-js": "^4.2.2", + "@types/jest": "^29.5.13", "@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/react-native-push-notification": "^8.1.4", + "@types/react-test-renderer": "^19.0.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", + "metro-react-native-babel-preset": "0.76.8", "node-fetch": "^2.6.7", - "prettier": "^3.0.0", - "react-test-renderer": "18.2.0", + "prettier": "^3.2.5", "ts-jest": "^29.1.1", - "typescript": "^5.1.6" + "typescript": "^5.9.3" }, "engines": { - "node": ">=10.16.0", - "npm": ">=6.9.0" + "node": ">=20" }, "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: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:relaunch": "adb shell am force-stop io.bluewallet.bluewallet; adb shell monkey -p io.bluewallet.bluewallet -c android.intent.category.LAUNCHER 1", + "adb": "adb reverse tcp:8081 tcp:8081", "android:clean": "cd android; ./gradlew clean ; cd .. ; 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": "", + "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 error --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-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 && 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.16", + "@arkade-os/sdk": "0.3.10", + "@babel/preset-env": "7.27.2", + "@bugsnag/react-native": "8.4.0", + "@bugsnag/source-maps": "2.3.3", + "@keystonehq/bc-ur-registry": "0.7.1", + "@lodev09/react-native-true-sheet": "github:BlueWallet/react-native-true-sheet#5945184a2fea9fe5ba8f5cfcdb20e3fc5eed6e37", + "@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-async-storage/async-storage": "2.2.0", + "@react-native-clipboard/clipboard": "1.16.3", + "@react-native-community/cli": "15.1.3", + "@react-native-community/cli-platform-android": "15.1.3", + "@react-native-community/cli-platform-ios": "15.1.3", "@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-documents/picker": "10.1.7", + "@react-native-menu/menu": "github:BlueWallet/menu#9933468", + "@react-native/gradle-plugin": "0.78.2", + "@react-native/metro-config": "0.78.2", + "@react-navigation/devtools": "7.0.24", + "@react-navigation/drawer": "7.3.7", + "@react-navigation/native": "7.1.4", + "@react-navigation/native-stack": "7.3.8", + "@rneui/base": "4.0.0-rc.8", + "@rneui/themed": "4.0.0-rc.8", + "@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", + "bip32": "5.0.0", "bip38": "github:BlueWallet/bip38", "bip39": "3.1.0", - "bitcoinjs-lib": "6.1.1", + "bitcoinjs-lib": "7.0.0", "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.19", + "detox": "20.43.0", + "ecpair": "3.0.0", + "electrum-client": "github:BlueWallet/rn-electrum-client#d9f511d", "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.4", + "pako": "file:blue_modules/pako", "payjoin-client": "1.0.1", - "process": "0.11.10", "prop-types": "15.8.1", - "react": "18.2.0", + "react": "19.0.0", "react-localization": "github:BlueWallet/react-localization#ae7969a", - "react-native": "0.71.11", + "react-native": "0.78.2", + "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": "16.2.0", + "react-native-capture-protection": "github:BlueWallet/react-native-capture-protection#d299992", + "react-native-default-preference": "https://github.com/BlueWallet/react-native-default-preference.git#6338a1f1235e4130b8cfc2dd3b53015eeff2870c", + "react-native-device-info": "14.1.1", + "react-native-draglist": "github:BlueWallet/react-native-draglist#0c8049d", "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-gesture-handler": "2.25.0", + "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.3", + "react-native-image-picker": "8.2.1", + "react-native-keychain": "9.1.0", + "react-native-linear-gradient": "2.8.3", + "react-native-localize": "3.5.4", + "react-native-permissions": "5.4.4", + "react-native-prompt-android": "github:BlueWallet/react-native-prompt-android#ed168d66fed556bc2ed07cf498770f058b78a376", "react-native-push-notification": "8.1.1", - "react-native-qrcode-svg": "6.2.0", + "react-native-qrcode-svg": "6.3.21", "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": "3.18.0", + "react-native-safe-area-context": "5.5.2", + "react-native-screens": "4.16.0", + "react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc", + "react-native-share": "12.1.0", + "react-native-svg": "15.12.1", + "react-native-tcp-socket": "6.3.0", + "react-native-vector-icons": "10.2.0", "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-test-renderer": "19.0.0", "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.1.0", + "rn-qr-generator": "https://github.com/BlueWallet/rn-qr-generator.git#731ed8eb445f65f3a659632232e18ff7e1ce56d6", + "silent-payments": "github:BlueWallet/SilentPayments#59a037", + "slip39": "https://github.com/BlueWallet/slip39-js#d316ee6", "stream-browserify": "3.0.0", - "url": "0.11.1", + "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/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..d068b3b6b57 --- /dev/null +++ b/screen/ActionSheet.ios.ts @@ -0,0 +1,18 @@ +// ActionSheet.ios.ts +import { ActionSheetIOS, InteractionManager } from 'react-native'; + +import { ActionSheetOptions, CompletionCallback } from './ActionSheet.common'; + +export default class ActionSheet { + static showActionSheetWithOptions(options: ActionSheetOptions, completion: CompletionCallback): void { + InteractionManager.runAfterInteractions(() => { + 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..00761173765 --- /dev/null +++ b/screen/ActionSheet.ts @@ -0,0 +1,27 @@ +// ActionSheet.ts +import { Alert, InteractionManager } from 'react-native'; + +import { ActionSheetOptions, CompletionCallback } from './ActionSheet.common'; + +export default class ActionSheet { + static showActionSheetWithOptions(options: ActionSheetOptions, completion: CompletionCallback): void { + InteractionManager.runAfterInteractions(() => { + 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..bb633f9b3f0 --- /dev/null +++ b/screen/PlausibleDeniability.tsx @@ -0,0 +1,123 @@ +import React, { useReducer, useRef } from 'react'; +import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; +import { BlueCard, BlueText } from '../BlueComponents'; +import presentAlert from '../components/Alert'; +import Button from '../components/Button'; +import loc from '../loc'; +import { useStorage } from '../hooks/context/useStorage'; +import PromptPasswordConfirmationModal, { + PromptPasswordConfirmationModalHandle, + MODAL_TYPES, +} from '../components/PromptPasswordConfirmationModal'; +import { useExtendedNavigation } from '../hooks/useExtendedNavigation'; +import { StackActions } from '@react-navigation/native'; +import SafeAreaScrollView from '../components/SafeAreaScrollView'; +import { BlueSpacing20 } from '../components/BlueSpacing'; +import { BlueLoading } from '../components/BlueLoading'; + +// 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 = () => { + const { cachedPassword, isPasswordInUse, createFakeStorage, resetWallets } = useStorage(); + const [state, dispatch] = useReducer(reducer, initialState); + const navigation = useExtendedNavigation(); + const promptRef = useRef(null); + + const handleOnCreateFakeStorageButtonPressed = async () => { + dispatch({ type: SET_LOADING, payload: true }); + dispatch({ type: SET_MODAL_TYPE, payload: MODAL_TYPES.CREATE_FAKE_STORAGE }); + await promptRef.current?.present(); + }; + + const handleConfirmationSuccess = async (password: string) => { + let success = false; + const isProvidedPasswordInUse = password === cachedPassword || (await isPasswordInUse(password)); + if (isProvidedPasswordInUse) { + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); + presentAlert({ message: loc.plausibledeniability.password_should_not_match }); + return false; + } + + try { + await createFakeStorage(password); + resetWallets(); + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); + + // Set the modal type to SUCCESS to show the success animation instead of the alert + dispatch({ type: SET_MODAL_TYPE, payload: MODAL_TYPES.SUCCESS }); + + success = true; + setTimeout(async () => { + const popToTop = StackActions.popToTop(); + navigation.dispatch(popToTop); + }, 3000); + } catch { + success = false; + dispatch({ type: SET_LOADING, payload: false }); + } + + return success; + }; + + const handleConfirmationFailure = () => { + dispatch({ type: SET_LOADING, payload: false }); + }; + + return ( + + {state.isLoading ? ( + + ) : ( + + {loc.plausibledeniability.help} + + {loc.plausibledeniability.help2} + +