diff --git a/.github/workflows/build-ios-release-pullrequest.yml b/.github/workflows/build-ios-release-pullrequest.yml index 3d407a98a..48e1b1584 100644 --- a/.github/workflows/build-ios-release-pullrequest.yml +++ b/.github/workflows/build-ios-release-pullrequest.yml @@ -30,7 +30,7 @@ jobs: steps: - name: Checkout Project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 # Ensures the full Git history is @@ -490,7 +490,7 @@ jobs: BRANCH_NAME: ${{ needs.build.outputs.branch_name }} steps: - name: Checkout Project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set Up Ruby uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 diff --git a/.github/workflows/build-mac-catalyst.yml b/.github/workflows/build-mac-catalyst.yml index 39b8bbe01..1ecfce403 100644 --- a/.github/workflows/build-mac-catalyst.yml +++ b/.github/workflows/build-mac-catalyst.yml @@ -49,7 +49,7 @@ jobs: - name: Checkout project if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 diff --git a/.github/workflows/build-release-apk.yml b/.github/workflows/build-release-apk.yml index e90c97534..b32090df3 100644 --- a/.github/workflows/build-release-apk.yml +++ b/.github/workflows/build-release-apk.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: "0" @@ -135,7 +135,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up Ruby uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e58b53579..2cb113067 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 @@ -53,6 +53,7 @@ jobs: BIP47_HD_MNEMONIC: ${{ secrets.BIP47_HD_MNEMONIC}} HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }} HD_MNEMONIC_BIP49: ${{ secrets.HD_MNEMONIC_BIP49 }} + HD_MNEMONIC_OLD: ${{ secrets.HD_MNEMONIC_OLD }} HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }} HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }} HD_MNEMONIC_BREAD: ${{ secrets.HD_MNEMONIC_BREAD }} @@ -64,7 +65,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 @@ -83,6 +84,7 @@ jobs: BIP47_HD_MNEMONIC: ${{ secrets.BIP47_HD_MNEMONIC}} HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }} HD_MNEMONIC_BIP49: ${{ secrets.HD_MNEMONIC_BIP49 }} + HD_MNEMONIC_OLD: ${{ secrets.HD_MNEMONIC_OLD }} HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }} HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }} HD_MNEMONIC_BREAD: ${{ secrets.HD_MNEMONIC_BREAD }} diff --git a/.github/workflows/e2e-android.yml b/.github/workflows/e2e-android.yml index 8ce18ad5a..cfdb5bf64 100644 --- a/.github/workflows/e2e-android.yml +++ b/.github/workflows/e2e-android.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Free disk space (Ubuntu) run: | @@ -86,7 +86,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Free disk space (Ubuntu) run: | diff --git a/.github/workflows/e2e-ios.yml b/.github/workflows/e2e-ios.yml index 6bc398ec8..9a07daf2b 100644 --- a/.github/workflows/e2e-ios.yml +++ b/.github/workflows/e2e-ios.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 @@ -168,7 +168,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 diff --git a/android/app/build.gradle b/android/app/build.gradle index ce5ced51d..ec99d4069 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -87,7 +87,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "8.0.0" + versionName "8.0.1" testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' // Keep compatibility across react-native-capture-protection flavor changes. diff --git a/babel.config.js b/babel.config.js index 8ba8eb658..929579229 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,4 +1,7 @@ module.exports = { - presets: ['module:@react-native/babel-preset'], + // Pin the @babel/runtime version so Metro resolves a single copy instead of + // bundling duplicate helpers, which bloats the bundle. + // See https://github.com/babel/babel/issues/18050 + presets: [['module:@react-native/babel-preset', { enableBabelRuntime: '^7.26.0' }]], plugins: ['react-native-worklets/plugin'], }; diff --git a/blue_modules/constants.ts b/blue_modules/constants.ts index 28d136f3c..4d74f710a 100644 --- a/blue_modules/constants.ts +++ b/blue_modules/constants.ts @@ -2,4 +2,7 @@ * Let's keep config vars, constants and definitions here */ -export const groundControlUri: string = 'https://groundcontrol.bluewallet.io/'; +export const groundControlUri: string = 'https://groundcontrol.bluewallet.io'; + +/** bitcoin-payment-push-service base URL, no trailing slash. Empty = disabled. */ +export const arkadePaymentPushUri: string = 'https://electrum2.bluewallet.io:444'; diff --git a/blue_modules/noble_ecc.ts b/blue_modules/noble_ecc.ts index c83b88fb0..db3208da5 100644 --- a/blue_modules/noble_ecc.ts +++ b/blue_modules/noble_ecc.ts @@ -26,44 +26,93 @@ export interface TinySecp256k1InterfaceExtended { signDER(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array; } -necc.utils.sha256Sync = (...messages: Uint8Array[]): Uint8Array => { - const combinedMessages = messages.reduce((acc, msg) => { - const newArray = new Uint8Array(acc.length + msg.length); - newArray.set(acc); - newArray.set(msg, acc.length); - return newArray; - }, new Uint8Array(0)); - return sha256(combinedMessages); -}; +// @noble/hashes types differ slightly from @noble/secp256k1 v3 hash slot typings. +necc.hashes.sha256 = sha256 as NonNullable; +necc.hashes.hmacSha256 = ((key: Uint8Array, message: Uint8Array) => hmac(sha256, key, message)) as NonNullable< + typeof necc.hashes.hmacSha256 +>; -necc.utils.hmacSha256Sync = (key: Uint8Array, ...messages: Uint8Array[]): Uint8Array => { - const combinedMessages = messages.reduce((acc, msg) => { - const newArray = new Uint8Array(acc.length + msg.length); - newArray.set(acc); - newArray.set(msg, acc.length); - return newArray; - }, new Uint8Array(0)); - return hmac(sha256, key, combinedMessages); -}; - -/* const normal = necc.utils._normalizePrivateKey; +// Removed from @noble/secp256k1 v1.7; vendored from noble test vectors. +// @see https://github.com/paulmillr/noble-secp256k1/blob/1.7.2/test/index.ts type Hex = string | Uint8Array; -type PrivKey = Hex | bigint | number; -necc.utils.privateAdd = (privateKey: PrivKey, tweak: Hex) => { - console.log({ privateKey, tweak }); - const p = normal(privateKey); - const t = normal(tweak); - return necc.utils.privateAdd(necc.utils.mod(p + t, necc.CURVE.n)); -}; */ +const { mod, secretKeyToScalar, numberToBytesBE, bytesToNumberBE, hexToBytes } = necc.etc; +const CURVE_N = necc.Point.CURVE().n; + +function pointFromBytes(p: Uint8Array): necc.Point { + if (p.length === 32) { + const prefixed = new Uint8Array(33); + prefixed[0] = 0x02; + prefixed.set(p, 1); + return necc.Point.fromBytes(prefixed); + } + return necc.Point.fromBytes(p); +} + +const tweakUtils = { + privateAdd: (privateKey: Hex, tweak: Hex): Uint8Array => { + const p = secretKeyToScalar(typeof privateKey === 'string' ? hexToBytes(privateKey) : privateKey); + const t = secretKeyToScalar(typeof tweak === 'string' ? hexToBytes(tweak) : tweak); + return numberToBytesBE(mod(p + t, CURVE_N)); + }, + + privateNegate: (privateKey: Hex): Uint8Array => { + const p = secretKeyToScalar(typeof privateKey === 'string' ? hexToBytes(privateKey) : privateKey); + return numberToBytesBE(CURVE_N - p); + }, + + pointAddScalar: (p: Hex, tweak: Hex, isCompressed?: boolean): Uint8Array => { + const P = typeof p === 'string' ? necc.Point.fromHex(p) : pointFromBytes(p); + const t = secretKeyToScalar(typeof tweak === 'string' ? hexToBytes(tweak) : tweak); + const Q = P.add(necc.Point.BASE.multiply(t)); + if (Q.is0()) throw new Error('Tweaked point at infinity'); + return Q.toBytes(isCompressed); + }, + + pointMultiply: (p: Hex, tweak: Hex, isCompressed?: boolean): Uint8Array => { + const P = typeof p === 'string' ? necc.Point.fromHex(p) : pointFromBytes(p); + const tweakBytes = typeof tweak === 'string' ? hexToBytes(tweak) : tweak; + const t = mod(bytesToNumberBE(tweakBytes), CURVE_N); + if (t === 0n) throw new Error('Point at infinity'); + return P.multiply(t).toBytes(isCompressed); + }, +}; const defaultTrue = (param?: boolean): boolean => param !== false; +function compactToDER(sig: Uint8Array): Uint8Array { + const encodeInt = (bytes: Uint8Array): Uint8Array => { + let i = 0; + while (i < bytes.length - 1 && bytes[i] === 0) i++; + let trimmed = bytes.subarray(i); + if (trimmed[0] >= 0x80) { + const prefixed = new Uint8Array(trimmed.length + 1); + prefixed[0] = 0; + prefixed.set(trimmed, 1); + trimmed = prefixed; + } + const encoded = new Uint8Array(2 + trimmed.length); + encoded[0] = 0x02; + encoded[1] = trimmed.length; + encoded.set(trimmed, 2); + return encoded; + }; + + const rDer = encodeInt(sig.subarray(0, 32)); + const sDer = encodeInt(sig.subarray(32, 64)); + const seqLen = rDer.length + sDer.length; + const der = new Uint8Array(2 + seqLen); + der[0] = 0x30; + der[1] = seqLen; + der.set(rDer, 2); + der.set(sDer, 2 + rDer.length); + return der; +} + function throwToNull(fn: () => Type): Type | null { try { return fn(); } catch (e) { - // console.log(e); return null; } } @@ -71,7 +120,8 @@ function throwToNull(fn: () => Type): Type | null { function isPoint(p: Uint8Array, xOnly: boolean): boolean { if ((p.length === 32) !== xOnly) return false; try { - return !!necc.Point.fromHex(p); + pointFromBytes(p); + return true; } catch (e) { return false; } @@ -79,23 +129,12 @@ function isPoint(p: Uint8Array, xOnly: boolean): boolean { const ecc: TinySecp256k1InterfaceExtended & TinySecp256k1Interface & TinySecp256k1InterfaceBIP32 = { isPoint: (p: Uint8Array): boolean => isPoint(p, false), - isPrivate: (d: Uint8Array): boolean => { - /* if ( - [ - '0000000000000000000000000000000000000000000000000000000000000000', - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142', - ].includes(d.toString('hex')) - ) { - return false; - } */ - return necc.utils.isValidPrivateKey(d); - }, + isPrivate: (d: Uint8Array): boolean => necc.utils.isValidSecretKey(d), isXOnlyPoint: (p: Uint8Array): boolean => isPoint(p, true), xOnlyPointAddTweak: (p: Uint8Array, tweak: Uint8Array): { parity: 0 | 1; xOnlyPubkey: Uint8Array } | null => throwToNull(() => { - const P = necc.utils.pointAddScalar(p, tweak, true); + const P = tweakUtils.pointAddScalar(p, tweak, true); const parity = P[0] % 2 === 1 ? 1 : 0; return { parity, xOnlyPubkey: P.slice(1) }; }), @@ -104,60 +143,56 @@ const ecc: TinySecp256k1InterfaceExtended & TinySecp256k1Interface & TinySecp256 throwToNull(() => necc.getPublicKey(sk, defaultTrue(compressed))), pointCompress: (p: Uint8Array, compressed?: boolean): Uint8Array => { - return necc.Point.fromHex(p).toRawBytes(defaultTrue(compressed)); + return pointFromBytes(p).toBytes(defaultTrue(compressed)); }, pointMultiply: (a: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null => - throwToNull(() => necc.utils.pointMultiply(a, tweak, defaultTrue(compressed))), + throwToNull(() => tweakUtils.pointMultiply(a, tweak, defaultTrue(compressed))), pointAdd: (a: Uint8Array, b: Uint8Array, compressed?: boolean): Uint8Array | null => throwToNull(() => { - const A = necc.Point.fromHex(a); - const B = necc.Point.fromHex(b); - return A.add(B).toRawBytes(defaultTrue(compressed)); + const A = pointFromBytes(a); + const B = pointFromBytes(b); + return A.add(B).toBytes(defaultTrue(compressed)); }), pointAddScalar: (p: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null => - throwToNull(() => necc.utils.pointAddScalar(p, tweak, defaultTrue(compressed))), + throwToNull(() => tweakUtils.pointAddScalar(p, tweak, defaultTrue(compressed))), privateAdd: (d: Uint8Array, tweak: Uint8Array): Uint8Array | null => throwToNull(() => { - // console.log({ d, tweak }); if (d.join('') === '00000000000000000000000000000001' && tweak.join('') === '00000000000000000000000000000000') { return new Uint8Array(d); // make test_ecc happy } - const ret = necc.utils.privateAdd(d, tweak); - // console.log(ret); + const ret = tweakUtils.privateAdd(d, tweak); if (ret.join('') === '00000000000000000000000000000000') { return null; } return ret; }), - privateNegate: (d: Uint8Array): Uint8Array => necc.utils.privateNegate(d), + privateNegate: (d: Uint8Array): Uint8Array => tweakUtils.privateNegate(d), sign: (h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array => { - return necc.signSync(h, d, { der: false, extraEntropy: e }); + return necc.sign(h, d, { prehash: false, extraEntropy: e }); }, signDER: (h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array => { - return necc.signSync(h, d, { der: true, extraEntropy: e }); + return compactToDER(necc.sign(h, d, { prehash: false, extraEntropy: e })); }, signSchnorr: (h: Uint8Array, d: Uint8Array, e: Uint8Array = new Uint8Array(32).fill(0x00)): Uint8Array => { - return necc.schnorr.signSync(h, d, e); + return necc.schnorr.sign(h, d, e); }, verify: (h: Uint8Array, Q: Uint8Array, signature: Uint8Array, strict?: boolean): boolean => { - return necc.verify(signature, h, Q, { strict }); + return necc.verify(signature, h, Q, { prehash: false, lowS: strict !== false }); }, verifySchnorr: (h: Uint8Array, Q: Uint8Array, signature: Uint8Array): boolean => { - return necc.schnorr.verifySync(signature, h, Q); + return necc.schnorr.verify(signature, h, Q); }, }; export default ecc; - -// module.exports.ecc = ecc; diff --git a/blue_modules/notifications.ts b/blue_modules/notifications.ts index 79b36d8ba..59ecaa14e 100644 --- a/blue_modules/notifications.ts +++ b/blue_modules/notifications.ts @@ -8,16 +8,16 @@ import { Notifications, } from 'react-native-notifications'; import { checkNotifications, requestNotifications, RESULTS } from 'react-native-permissions'; +import type { BoltzReverseSwap } from '@arkade-os/boltz-swap'; import loc from '../loc'; -import { groundControlUri } from './constants'; +import { arkadePaymentPushUri, groundControlUri } from './constants'; import { fetch } from '../util/fetch'; const PUSH_TOKEN = 'PUSH_TOKEN'; -const GROUNDCONTROL_BASE_URI = 'GROUNDCONTROL_BASE_URI'; const NOTIFICATIONS_STORAGE = 'NOTIFICATIONS_STORAGE'; const ANDROID_NOTIFICATION_CHANNEL_ID = 'channel_01'; export const NOTIFICATIONS_NO_AND_DONT_ASK_FLAG = 'NOTIFICATIONS_NO_AND_DONT_ASK_FLAG'; -let baseURI = groundControlUri; +const baseURI = groundControlUri; let notificationSubscriptions: EmitterSubscription[] = []; let onProcessNotificationsHandler: undefined | (() => void | Promise); const handledNotificationKeys = new Set(); @@ -252,6 +252,29 @@ export const tryToObtainPermissions = async (): Promise => { return false; } }; + +export const enqueueTestPushNotification = async (): Promise => { + const pushToken = await getPushToken(); + if (!pushToken?.token || !pushToken?.os) { + throw new Error('No push token available'); + } + + const response = await fetch(`${baseURI}/enqueue`, { + method: 'POST', + headers: _getHeaders(), + body: JSON.stringify({ + type: 5, + token: pushToken.token, + os: pushToken.os, + text: 'Test push notification', + }), + }); + + if (!response.ok) { + throw new Error(`Enqueue request failed with status ${response.status}: ${response.statusText}`); + } +}; + /** * Submits onchain bitcoin addresses and ln invoice preimage hashes to GroundControl server, so later we could * be notified if they were paid @@ -327,6 +350,44 @@ export const majorTomToGroundControl = async (addresses: string[], hashes: strin } }; +/** + * Registers an Ark swap with the bitcoin-payment-push-service so the device is + * pushed when the invoice gets paid. Fire-and-forget: never throws, gated by + * the same opt-out/token rules as majorTomToGroundControl(). The swap's + * preimage is always stripped before leaving the device. + */ +export const registerArkPaymentPush = async (paymentHash: string, label: string, pendingSwap: BoltzReverseSwap): Promise => { + if (!arkadePaymentPushUri) return; + try { + const noAndDontAskFlag = await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG); + if (noAndDontAskFlag === 'true') { + console.warn('User has opted out of notifications.'); + return; + } + + const pushToken = await getPushToken(); + if (!pushToken || !pushToken.token || !pushToken.os) { + return; + } + + const response = await fetch(`${arkadePaymentPushUri}/register`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + topic: paymentHash, + label, + swap: { ...pendingSwap, preimage: '' }, + }), + }); + if (!response.ok) { + throw new Error(`status ${response.status}`); + } + console.log('[ARK] payment push registration ok'); + } catch (e: any) { + console.log('[ARK] payment push registration failed:', e?.message ?? e); + } +}; + /** * Returns a permissions object: * alert: boolean @@ -529,22 +590,6 @@ const configureNotifications = async (onProcessNotifications?: () => void): Prom } }; -/** - * Validates whether the provided GroundControl URI is valid by pinging it. - * - * @param uri {string} - * @returns {Promise} 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 => { @@ -676,38 +721,6 @@ export const removeAllDeliveredNotifications = () => { Notifications.removeAllDeliveredNotifications(); }; -export const getDefaultUri = () => { - return groundControlUri; -}; - -export const saveUri = async (uri: string) => { - try { - baseURI = uri || groundControlUri; - await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, baseURI); - } catch (error) { - console.error('Error saving URI:', error); - throw error; - } -}; - -export const getSavedUri = async () => { - try { - const baseUriStored = await AsyncStorage.getItem(GROUNDCONTROL_BASE_URI); - if (baseUriStored) { - baseURI = baseUriStored; - } - return baseUriStored; - } catch (e) { - console.error(e); - try { - await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, groundControlUri); - } catch (storageError) { - console.error('Failed to reset URI:', storageError); - } - throw e; - } -}; - export const isNotificationsEnabled = async () => { try { const levels = await getLevels(); @@ -757,10 +770,6 @@ export const initializeNotifications = async (onProcessNotifications?: () => voi return; } - const baseUriStored = await AsyncStorage.getItem(GROUNDCONTROL_BASE_URI); - baseURI = baseUriStored || groundControlUri; - console.log('Base URI set to:', baseURI); - setApplicationIconBadgeNumber(0); // Only check permissions, never request @@ -781,7 +790,5 @@ export const initializeNotifications = async (onProcessNotifications?: () => voi } } catch (error) { console.error('Failed to initialize notifications:', error); - baseURI = groundControlUri; - await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, groundControlUri).catch(err => console.error('Failed to reset URI:', err)); } }; diff --git a/class/blue-app.ts b/class/blue-app.ts index 1dbbf959c..5f9bbf658 100644 --- a/class/blue-app.ts +++ b/class/blue-app.ts @@ -147,11 +147,10 @@ export class BlueApp { console.warn('error reading', key, error.message); console.warn('fallback to realm'); const realmKeyValue = await this.openRealmKeyValue(); - const obj = realmKeyValue.objectForPrimaryKey('KeyValue', key); // search for a realm object with a primary key + const obj = realmKeyValue.objectForPrimaryKey<{ key: string; value: string }>('KeyValue', key); value = obj?.value; realmKeyValue.close(); if (value) { - // @ts-ignore value.length console.warn('successfully recovered', value.length, 'bytes from realm for key', key); return value; } @@ -547,10 +546,11 @@ export class BlueApp { (walletToInflate._txs_by_internal_index[tx.index] as Transaction[]).push(transaction); } } else { - if (!Array.isArray(walletToInflate._txs_by_external_index)) walletToInflate._txs_by_external_index = []; - walletToInflate._txs_by_external_index = walletToInflate._txs_by_external_index || []; + // Legacy single-address wallets - store under index 0 + walletToInflate._txs_by_external_index = walletToInflate._txs_by_external_index || {}; + walletToInflate._txs_by_external_index[0] = walletToInflate._txs_by_external_index[0] || []; const transaction = JSON.parse(tx.tx); - (walletToInflate._txs_by_external_index as Transaction[]).push(transaction); + walletToInflate._txs_by_external_index[0].push(transaction); } } } @@ -559,32 +559,6 @@ export class BlueApp { const id = wallet.getID(); const walletToSave = ('_hdWalletInstance' in wallet && wallet._hdWalletInstance) || wallet; - if (Array.isArray(walletToSave._txs_by_external_index)) { - // if this var is an array that means its a single-address wallet class, and this var is a flat array - // with transactions - realm.write(() => { - // cleanup all existing transactions for the wallet first - const walletTransactionsToDelete = realm.objects('WalletTransactions').filtered(`walletid = '${id}'`); - realm.delete(walletTransactionsToDelete); - - // @ts-ignore walletToSave._txs_by_external_index is array - for (const tx of walletToSave._txs_by_external_index) { - realm.create( - 'WalletTransactions', - { - walletid: id, - tx: JSON.stringify(tx), - }, - Realm.UpdateMode.Modified, - ); - } - }); - - return; - } - - /// ######################################################################################################## - if (walletToSave._txs_by_external_index) { realm.write(() => { // cleanup all existing transactions for the wallet first @@ -592,16 +566,14 @@ export class BlueApp { realm.delete(walletTransactionsToDelete); // insert new ones: - for (const index of Object.keys(walletToSave._txs_by_external_index)) { - // @ts-ignore index is number - const txs = walletToSave._txs_by_external_index[index]; + for (const [indexStr, txs] of Object.entries(walletToSave._txs_by_external_index)) { for (const tx of txs) { realm.create( 'WalletTransactions', { walletid: id, internal: false, - index: parseInt(index, 10), + index: parseInt(indexStr, 10), tx: JSON.stringify(tx), }, Realm.UpdateMode.Modified, @@ -609,16 +581,14 @@ export class BlueApp { } } - for (const index of Object.keys(walletToSave._txs_by_internal_index)) { - // @ts-ignore index is number - const txs = walletToSave._txs_by_internal_index[index]; + for (const [indexStr, txs] of Object.entries(walletToSave._txs_by_internal_index)) { for (const tx of txs) { realm.create( 'WalletTransactions', { walletid: id, internal: true, - index: parseInt(index, 10), + index: parseInt(indexStr, 10), tx: JSON.stringify(tx), }, Realm.UpdateMode.Modified, diff --git a/class/hd-segwit-bech32-transaction.ts b/class/hd-segwit-bech32-transaction.ts index adb2b99fd..ba162e477 100644 --- a/class/hd-segwit-bech32-transaction.ts +++ b/class/hd-segwit-bech32-transaction.ts @@ -390,7 +390,7 @@ export class HDSegwitBech32Transaction { } } - // @ts-ignore stfu - return { tx, inputs, outputs, fee }; + // Non-null assertions are safe here because the while loop always runs at least once (add starts at 0) + return { tx: tx!, inputs: inputs!, outputs: outputs!, fee: fee! }; } } diff --git a/class/rng.ts b/class/rng.ts index 02ca007d2..88fed282e 100644 --- a/class/rng.ts +++ b/class/rng.ts @@ -11,7 +11,7 @@ * @return {Promise.} The random bytes */ export async function randomBytes(size: number): Promise { - const g: any = globalThis as any; + const g = globalThis as any; const rnCrypto = g && g.crypto; if (!rnCrypto || typeof rnCrypto.getRandomValues !== 'function') { throw new Error('crypto.getRandomValues is not available'); diff --git a/class/wallets/abstract-hd-electrum-wallet.ts b/class/wallets/abstract-hd-electrum-wallet.ts index 539b63b05..2e921f877 100644 --- a/class/wallets/abstract-hd-electrum-wallet.ts +++ b/class/wallets/abstract-hd-electrum-wallet.ts @@ -45,9 +45,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { _balances_by_external_index: Record; _balances_by_internal_index: Record; - // @ts-ignore _txs_by_external_index: Record; - // @ts-ignore _txs_by_internal_index: Record; _utxo: any[]; @@ -204,70 +202,37 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { return child.toWIF(); } - _getNodeAddressByIndex(node: number, index: number): string { - index = index * 1; // cast to int + _getNodeByIndex(node: 0 | 1, index: number): BIP32Interface { + const cachedNode = node === 0 ? this._node0 : this._node1; + if (cachedNode) { + return cachedNode.derive(index); + } + + const xpub = this._zpubToXpub(this.getXpub()); + const hdNode = bip32.fromBase58(xpub).derive(node); + if (node === 0) { - if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit - } - - if (node === 1) { - if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit - } - - if (node === 0 && !this._node0) { - const xpub = this._zpubToXpub(this.getXpub()); - const hdNode = bip32.fromBase58(xpub); - this._node0 = hdNode.derive(node); - } - - if (node === 1 && !this._node1) { - const xpub = this._zpubToXpub(this.getXpub()); - const hdNode = bip32.fromBase58(xpub); - this._node1 = hdNode.derive(node); - } - - let address: string; - if (node === 0) { - // @ts-ignore - address = this._hdNodeToAddress(this._node0.derive(index)); + this._node0 = hdNode; } else { - // tbh the only possible else is node === 1 - // @ts-ignore - address = this._hdNodeToAddress(this._node1.derive(index)); + this._node1 = hdNode; } - if (node === 0) { - return (this.external_addresses_cache[index] = address); - } else { - // tbh the only possible else option is node === 1 - return (this.internal_addresses_cache[index] = address); - } + return hdNode.derive(index); } - _getNodePubkeyByIndex(node: number, index: number) { - index = index * 1; // cast to int + _getNodeAddressByIndex(node: 0 | 1, index: number): string { + const cache = node === 0 ? this.external_addresses_cache : this.internal_addresses_cache; - if (node === 0 && !this._node0) { - const xpub = this._zpubToXpub(this.getXpub()); - const hdNode = bip32.fromBase58(xpub); - this._node0 = hdNode.derive(node); - } + if (cache[index]) return cache[index]; // cache hit - if (node === 1 && !this._node1) { - const xpub = this._zpubToXpub(this.getXpub()); - const hdNode = bip32.fromBase58(xpub); - this._node1 = hdNode.derive(node); - } + const hdNode = this._getNodeByIndex(node, index); + const address = this._hdNodeToAddress(hdNode); - if (node === 0 && this._node0) { - return this._node0.derive(index).publicKey; - } + return (cache[index] = address); + } - if (node === 1 && this._node1) { - return this._node1.derive(index).publicKey; - } - - throw new Error('Internal error: this._node0 or this._node1 is undefined'); + _getNodePubkeyByIndex(node: 0 | 1, index: number) { + return this._getNodeByIndex(node, index).publicKey; } _getExternalAddressByIndex(index: number): string { @@ -424,137 +389,95 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { // now, we need to put transactions in all relevant `cells` of internal hashmaps: // this._txs_by_internal_index, this._txs_by_external_index & this._txs_by_payment_code_index + // address -> index lookup maps; the single pass over transactions below uses them + // to find which cells a transaction belongs to + const externalIndexByAddress = new Map(); for (let c = 0; c < next_free_address_index + this.gap_limit; c++) { - for (const tx of Object.values(txdatas)) { - for (const vin of tx.vin) { - if (vin.addresses && vin.addresses.indexOf(this._getExternalAddressByIndex(c)) !== -1) { - // this TX is related to our address - this._txs_by_external_index[c] = this._txs_by_external_index[c] || []; - const { vin: txVin, vout: txVout, ...txRest } = tx; - const clonedTx = { - ...txRest, - inputs: txVin.slice(0), - outputs: txVout.slice(0), - timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */, - }; - - // trying to replace tx if it exists already (because it has lower confirmations, for example) - let replaced = false; - for (let cc = 0; cc < this._txs_by_external_index[c].length; cc++) { - if (this._txs_by_external_index[c][cc].txid === clonedTx.txid) { - replaced = true; - this._txs_by_external_index[c][cc] = clonedTx; - } - } - if (!replaced) this._txs_by_external_index[c].push(clonedTx); - } - } - for (const vout of tx.vout) { - if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses.indexOf(this._getExternalAddressByIndex(c)) !== -1) { - // this TX is related to our address - this._txs_by_external_index[c] = this._txs_by_external_index[c] || []; - const { vin: txVin, vout: txVout, ...txRest } = tx; - const clonedTx = { - ...txRest, - inputs: txVin.slice(0), - outputs: txVout.slice(0), - timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */, - }; - - // trying to replace tx if it exists already (because it has lower confirmations, for example) - let replaced = false; - for (let cc = 0; cc < this._txs_by_external_index[c].length; cc++) { - if (this._txs_by_external_index[c][cc].txid === clonedTx.txid) { - replaced = true; - this._txs_by_external_index[c][cc] = clonedTx; - } - } - if (!replaced) this._txs_by_external_index[c].push(clonedTx); - } - } - } + externalIndexByAddress.set(this._getExternalAddressByIndex(c), c); } - + const internalIndexByAddress = new Map(); for (let c = 0; c < next_free_change_address_index + this.gap_limit; c++) { - for (const tx of Object.values(txdatas)) { - for (const vin of tx.vin) { - if (vin.addresses && vin.addresses.indexOf(this._getInternalAddressByIndex(c)) !== -1) { - // this TX is related to our address - this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || []; - const { vin: txVin, vout: txVout, ...txRest } = tx; - const clonedTx = { - ...txRest, - inputs: txVin.slice(0), - outputs: txVout.slice(0), - timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */, - }; - - // trying to replace tx if it exists already (because it has lower confirmations, for example) - let replaced = false; - for (let cc = 0; cc < this._txs_by_internal_index[c].length; cc++) { - if (this._txs_by_internal_index[c][cc].txid === clonedTx.txid) { - replaced = true; - this._txs_by_internal_index[c][cc] = clonedTx; - } - } - if (!replaced) this._txs_by_internal_index[c].push(clonedTx); - } - } - for (const vout of tx.vout) { - if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses.indexOf(this._getInternalAddressByIndex(c)) !== -1) { - // this TX is related to our address - this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || []; - const { vin: txVin, vout: txVout, ...txRest } = tx; - const clonedTx = { - ...txRest, - inputs: txVin.slice(0), - outputs: txVout.slice(0), - timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */, - }; - - // trying to replace tx if it exists already (because it has lower confirmations, for example) - let replaced = false; - for (let cc = 0; cc < this._txs_by_internal_index[c].length; cc++) { - if (this._txs_by_internal_index[c][cc].txid === clonedTx.txid) { - replaced = true; - this._txs_by_internal_index[c][cc] = clonedTx; - } - } - if (!replaced) this._txs_by_internal_index[c].push(clonedTx); - } - } - } + internalIndexByAddress.set(this._getInternalAddressByIndex(c), c); } - + const paymentCodeIndexByAddress = new Map(); for (const pc of this._receive_payment_codes) { for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + this.gap_limit; c++) { - for (const tx of Object.values(txdatas)) { - // since we are iterating PCs who can pay us, we can completely ignore `tx.vin` and only iterate `tx.vout` - for (const vout of tx.vout) { - if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses.indexOf(this._getBIP47AddressReceive(pc, c)) !== -1) { - // this TX is related to our address - this._txs_by_payment_code_index[pc] = this._txs_by_payment_code_index[pc] || {}; - this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c] || []; - const { vin: txVin, vout: txVout, ...txRest } = tx; - const clonedTx = { - ...txRest, - inputs: txVin.slice(0), - outputs: txVout.slice(0), - timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */, - }; + paymentCodeIndexByAddress.set(this._getBIP47AddressReceive(pc, c), { pc, c }); + } + } - // trying to replace tx if it exists already (because it has lower confirmations, for example) - let replaced = false; - for (let cc = 0; cc < this._txs_by_payment_code_index[pc][c].length; cc++) { - if (this._txs_by_payment_code_index[pc][c][cc].txid === clonedTx.txid) { - replaced = true; - this._txs_by_payment_code_index[pc][c][cc] = clonedTx; - } - } - if (!replaced) this._txs_by_payment_code_index[pc][c].push(clonedTx); - } - } + // per-cell txid -> position lookup, used to replace-or-push a transaction into a cell in constant time + const cellPositionsByTxid = new Map>(); + const getCellPositions = (cell: Transaction[]): Map => { + let positions = cellPositionsByTxid.get(cell); + if (!positions) { + positions = new Map(); + for (let cc = 0; cc < cell.length; cc++) positions.set(cell[cc].txid, cc); + cellPositionsByTxid.set(cell, positions); + } + return positions; + }; + + for (const tx of Object.values(txdatas)) { + // collecting which of our address `cells` this transaction touches: + const externalCells = new Set(); + const internalCells = new Set(); + const paymentCodeCells = new Map(); + + const matchAddress = (address: string, isVout: boolean) => { + const externalIndex = externalIndexByAddress.get(address); + if (externalIndex !== undefined) externalCells.add(externalIndex); + const internalIndex = internalIndexByAddress.get(address); + if (internalIndex !== undefined) internalCells.add(internalIndex); + if (isVout) { + // since we are iterating PCs who can pay us, we can completely ignore `tx.vin` and only check `tx.vout` + const paymentCodeIndex = paymentCodeIndexByAddress.get(address); + if (paymentCodeIndex) paymentCodeCells.set(address, paymentCodeIndex); } + }; + + for (const vin of tx.vin) { + for (const address of vin.addresses ?? []) matchAddress(address, false); + } + for (const vout of tx.vout) { + for (const address of vout.scriptPubKey.addresses ?? []) matchAddress(address, true); + } + + if (externalCells.size === 0 && internalCells.size === 0 && paymentCodeCells.size === 0) continue; + + // this TX is related to our address(es) + const upsertClone = (cell: Transaction[]) => { + const { vin: txVin, vout: txVout, ...txRest } = tx; + const clonedTx = { + ...txRest, + inputs: txVin.slice(0), + outputs: txVout.slice(0), + timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */, + }; + + // trying to replace tx if it exists already (because it has lower confirmations, for example) + const positions = getCellPositions(cell); + const existingPosition = positions.get(clonedTx.txid); + if (existingPosition !== undefined) { + cell[existingPosition] = clonedTx; + } else { + positions.set(clonedTx.txid, cell.length); + cell.push(clonedTx); + } + }; + + for (const c of externalCells) { + this._txs_by_external_index[c] = this._txs_by_external_index[c] || []; + upsertClone(this._txs_by_external_index[c]); + } + for (const c of internalCells) { + this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || []; + upsertClone(this._txs_by_internal_index[c]); + } + for (const { pc, c } of paymentCodeCells.values()) { + this._txs_by_payment_code_index[pc] = this._txs_by_payment_code_index[pc] || {}; + this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c] || []; + upsertClone(this._txs_by_payment_code_index[pc][c]); } } @@ -652,8 +575,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { let lastHistoriesWithUsedAddresses = null; for (let c = 0; c < Math.round(index / this.gap_limit); c++) { const histories = await BlueElectrum.multiGetHistoryByAddress(gerenateChunkAddresses(c)); - // @ts-ignore - if (this.constructor._getTransactionsFromHistories(histories).length > 0) { + if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) { // in this particular chunk we have used addresses lastChunkWithUsedAddressesNum = c; lastHistoriesWithUsedAddresses = histories; @@ -695,8 +617,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { let lastHistoriesWithUsedAddresses = null; for (let c = 0; c < Math.round(index / this.gap_limit); c++) { const histories = await BlueElectrum.multiGetHistoryByAddress(gerenateChunkAddresses(c)); - // @ts-ignore - if (this.constructor._getTransactionsFromHistories(histories).length > 0) { + if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) { // in this particular chunk we have used addresses lastChunkWithUsedAddressesNum = c; lastHistoriesWithUsedAddresses = histories; @@ -738,8 +659,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { let lastHistoriesWithUsedAddresses = null; for (let c = 0; c < Math.round(index / this.gap_limit); c++) { const histories = await BlueElectrum.multiGetHistoryByAddress(generateChunkAddresses(c)); - // @ts-ignore - if (this.constructor._getTransactionsFromHistories(histories).length > 0) { + if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) { // in this particular chunk we have used addresses lastChunkWithUsedAddressesNum = c; lastHistoriesWithUsedAddresses = histories; diff --git a/class/wallets/abstract-hd-wallet.ts b/class/wallets/abstract-hd-wallet.ts index a7ccbb04e..ce0cf18b7 100644 --- a/class/wallets/abstract-hd-wallet.ts +++ b/class/wallets/abstract-hd-wallet.ts @@ -315,7 +315,7 @@ export class AbstractHDWallet extends LegacyWallet { throw new Error('Not implemented'); } - _getNodePubkeyByIndex(node: number, index: number): Uint8Array | undefined { + _getNodePubkeyByIndex(node: 0 | 1, index: number): Uint8Array | undefined { throw new Error('Not implemented'); } diff --git a/class/wallets/legacy-wallet.ts b/class/wallets/legacy-wallet.ts index 81a981e1b..8c761deb2 100644 --- a/class/wallets/legacy-wallet.ts +++ b/class/wallets/legacy-wallet.ts @@ -27,8 +27,8 @@ export class LegacyWallet extends AbstractWallet { // @ts-ignore: override public readonly typeReadable: string; - _txs_by_external_index: Transaction[] = []; - _txs_by_internal_index: Transaction[] = []; + _txs_by_external_index: Record = {}; + _txs_by_internal_index: Record = {}; constructor(typeReadable?: string) { super(); @@ -344,14 +344,14 @@ export class LegacyWallet extends AbstractWallet { } } - this._txs_by_external_index = _txsByExternalIndex; + this._txs_by_external_index = { 0: _txsByExternalIndex }; this._lastTxFetch = +new Date(); } getTransactions(): Transaction[] { // a hacky code reuse from electrum HD wallet: - this._txs_by_external_index = this._txs_by_external_index || []; - this._txs_by_internal_index = []; + this._txs_by_external_index = this._txs_by_external_index || {}; + this._txs_by_internal_index = {}; const { HDSegwitBech32Wallet } = require('./hd-segwit-bech32-wallet') as { HDSegwitBech32Wallet: typeof HDSegwitBech32WalletT; diff --git a/class/wallets/lightning-ark-wallet.ts b/class/wallets/lightning-ark-wallet.ts index e4fdab1f3..648356ba7 100644 --- a/class/wallets/lightning-ark-wallet.ts +++ b/class/wallets/lightning-ark-wallet.ts @@ -29,6 +29,7 @@ import assert from 'assert'; import ecc from '../../blue_modules/noble_ecc.ts'; import { Measure } from '../measure.ts'; import { deleteArkadeRealm, getArkadeRealm } from '../../blue_modules/arkade-adapters/realm/realmInstance'; +import { registerArkPaymentPush } from '../../blue_modules/notifications'; const { bech32m } = require('bech32'); const bip32 = BIP32Factory(ecc); @@ -710,6 +711,8 @@ export class LightningArkWallet extends LightningCustodianWallet { console.log('Pending swap', result.pendingSwap); console.log('Preimage', result.preimage); + registerArkPaymentPush(result.paymentHash, memo, result.pendingSwap); // fire-and-forget, never throws + return result.invoice; } diff --git a/components/ListItem.tsx b/components/ListItem.tsx index 73aa55283..2a0928591 100644 --- a/components/ListItem.tsx +++ b/components/ListItem.tsx @@ -21,6 +21,7 @@ interface ListItemProps { subtitleNumberOfLines?: number; rightTitle?: string; rightTitleStyle?: StyleProp; + rightTitleSelectable?: boolean; rightSubtitle?: string | React.ReactNode; rightSubtitleStyle?: StyleProp; chevron?: boolean; @@ -45,6 +46,7 @@ const ListItem: React.FC = React.memo( subtitleNumberOfLines, rightTitle, rightTitleStyle, + rightTitleSelectable, rightSubtitle, rightSubtitleStyle, chevron, @@ -112,7 +114,7 @@ const ListItem: React.FC = React.memo( {rightTitle || rightSubtitle ? ( {rightTitle ? ( - + {rightTitle} ) : null} diff --git a/components/QRCode.tsx b/components/QRCode.tsx index 2014b1ebe..a6db2dc4b 100644 --- a/components/QRCode.tsx +++ b/components/QRCode.tsx @@ -216,24 +216,11 @@ const QRCode: React.FC = ({ const gradFill = `url(#${GRADIENT_ID})`; const finderShapes: React.ReactElement[] = []; - const outerR = 2 * cell; - const holeR = 1.25 * cell; - const dotR = 0.9 * cell; finderOrigins.forEach(([fr, fc], i) => { const x = (fc + 1) * cell; const y = (fr + 1) * cell; finderShapes.push( - , + , = ({ y={y + cell} width={5 * cell} height={5 * cell} - rx={holeR} - ry={holeR} fill={BACKGROUND} />, = ({ y={y + 2 * cell} width={3 * cell} height={3 * cell} - rx={dotR} - ry={dotR} fill={gradFill} />, ); @@ -277,16 +260,7 @@ const QRCode: React.FC = ({ {finderShapes} {isLogoRendered && logoCells > 0 && ( <> - + > { 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; -}; +export type CarouselListRefType = FlatList; const styles = StyleSheet.create({ listHeaderSeparator: { @@ -534,7 +526,7 @@ const styles = StyleSheet.create({ const ListHeaderSeparator = () => ; -const WalletsCarousel = forwardRef((props, ref) => { +const WalletsCarousel = forwardRef((props, ref) => { const { horizontal = true, data, @@ -569,7 +561,7 @@ const WalletsCarousel = forwardRef((props const scrollTimeoutRef = useRef(null); const isInitialMount = useRef(true); - const flatListRef = useRef>(null); + const flatListRef = useRef>(null); const walletRefs = useRef>>({}); const { sizeClass } = useSizeClass(); diff --git a/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index 98ed73665..86ad62e96 100644 --- a/ios/BlueWallet.xcodeproj/project.pbxproj +++ b/ios/BlueWallet.xcodeproj/project.pbxproj @@ -1356,7 +1356,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1703259999; + CURRENT_PROJECT_VERSION = 1703279999; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; @@ -1383,7 +1383,7 @@ "$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(inherited)", ); - MARKETING_VERSION = 8.0.0; + MARKETING_VERSION = 8.0.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1418,7 +1418,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1703259999; + CURRENT_PROJECT_VERSION = 1703279999; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU; @@ -1440,7 +1440,7 @@ "$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(inherited)", ); - MARKETING_VERSION = 8.0.0; + MARKETING_VERSION = 8.0.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1476,7 +1476,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1703259999; + CURRENT_PROJECT_VERSION = 1703279999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; @@ -1489,7 +1489,7 @@ "$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(inherited)", ); - MARKETING_VERSION = 8.0.0; + MARKETING_VERSION = 8.0.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; @@ -1519,7 +1519,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1703259999; + CURRENT_PROJECT_VERSION = 1703279999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; @@ -1532,7 +1532,7 @@ "$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(inherited)", ); - MARKETING_VERSION = 8.0.0; + MARKETING_VERSION = 8.0.1; MTL_FAST_MATH = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers; @@ -1564,7 +1564,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1703259999; + CURRENT_PROJECT_VERSION = 1703279999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; @@ -1584,7 +1584,7 @@ "$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(inherited)", ); - MARKETING_VERSION = 8.0.0; + MARKETING_VERSION = 8.0.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; @@ -1625,7 +1625,7 @@ "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1703259999; + CURRENT_PROJECT_VERSION = 1703279999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; @@ -1645,7 +1645,7 @@ "$(SDKROOT)/System/iOSSupport/usr/lib/swift", "$(inherited)", ); - MARKETING_VERSION = 8.0.0; + MARKETING_VERSION = 8.0.1; MTL_FAST_MATH = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget; @@ -1832,7 +1832,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1703259999; + CURRENT_PROJECT_VERSION = 1703279999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; @@ -1854,7 +1854,7 @@ "$(inherited)", ); MACOSX_DEPLOYMENT_TARGET = 12.4; - MARKETING_VERSION = 8.0.0; + MARKETING_VERSION = 8.0.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; @@ -1890,7 +1890,7 @@ "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1703259999; + CURRENT_PROJECT_VERSION = 1703279999; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; @@ -1912,7 +1912,7 @@ "$(inherited)", ); MACOSX_DEPLOYMENT_TARGET = 12.4; - MARKETING_VERSION = 8.0.0; + MARKETING_VERSION = 8.0.1; MTL_FAST_MATH = YES; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch; diff --git a/loc/en.json b/loc/en.json index 7c27df6c6..eedffa18f 100644 --- a/loc/en.json +++ b/loc/en.json @@ -288,7 +288,6 @@ "general": "General", "general_continuity": "Continuity", "general_continuity_e": "When enabled, you will be able to view selected wallets, and transactions, using your other Apple iCloud connected devices.", - "groundcontrol_explanation": "GroundControl is a free, open-source push notifications server for Bitcoin wallets. You can install your own GroundControl server and put its URL here to not rely on BlueWallet’s infrastructure. Leave blank to use GroundControl’s default server.", "header": "Settings", "language": "Language", "last_updated": "Last Updated", @@ -304,7 +303,6 @@ "network_broadcast": "Broadcast Transaction", "network_electrum": "Electrum Server", "electrum_suggested_description": "When a preferred server is not set, a suggested server will be selected for use at random.", - "not_a_valid_uri": "Invalid URI", "notifications": "Notifications", "open_link_in_explorer": "Open link in explorer", "password": "Password", @@ -322,7 +320,6 @@ "push_notifications_explanation": "By enabling notifications, your device token will be sent to the server, along with wallet addresses and transaction IDs for all wallets and transactions made after enabling notifications. The device token is used to send notifications, and the wallet information allows us to notify you about incoming Bitcoin or transaction confirmations.\n\nOnly information from after you enable notifications is transmitted—nothing from before is collected.\n\nDisabling notifications will remove all of this information from the server. Additionally, deleting a wallet from the app will also remove its associated information from the server.", "selfTest": "Self-Test", "save": "Save", - "saved": "Saved", "success_transaction_broadcasted": "Your transaction has been successfully broadcasted!", "total_balance": "Total Balance", "total_balance_explanation": "Display the total balance of all your wallets on your home screen widgets.", diff --git a/package-lock.json b/package-lock.json index 1afc0f039..8c71485a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,24 @@ { "name": "bluewallet", - "version": "8.0.0", + "version": "8.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bluewallet", - "version": "8.0.0", + "version": "8.0.1", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@arkade-os/boltz-swap": "0.3.37", - "@arkade-os/sdk": "0.4.32", + "@arkade-os/boltz-swap": "0.3.38", + "@arkade-os/sdk": "0.4.33", "@babel/preset-env": "7.29.5", "@bugsnag/react-native": "8.9.0", "@bugsnag/source-maps": "2.3.3", "@keystonehq/bc-ur-registry": "0.7.1", "@ngraveio/bc-ur": "1.1.13", "@noble/hashes": "1.3.3", - "@noble/secp256k1": "1.6.3", + "@noble/secp256k1": "3.1.0", "@react-native-async-storage/async-storage": "2.2.0", "@react-native-clipboard/clipboard": "1.16.3", "@react-native-community/cli": "20.1.3", @@ -26,11 +26,11 @@ "@react-native-community/cli-platform-ios": "20.1.3", "@react-native-documents/picker": "12.0.1", "@react-native-vector-icons/entypo": "13.1.1", - "@react-native-vector-icons/fontawesome": "13.1.1", - "@react-native-vector-icons/fontawesome6": "13.1.1", - "@react-native-vector-icons/ionicons": "13.1.1", - "@react-native-vector-icons/material-design-icons": "13.1.1", - "@react-native-vector-icons/material-icons": "13.1.1", + "@react-native-vector-icons/fontawesome": "13.1.2", + "@react-native-vector-icons/fontawesome6": "13.1.2", + "@react-native-vector-icons/ionicons": "13.1.2", + "@react-native-vector-icons/material-design-icons": "13.1.2", + "@react-native-vector-icons/material-icons": "13.1.2", "@react-native/babel-preset": "0.85.3", "@react-native/codegen": "0.85.3", "@react-native/gradle-plugin": "0.85.3", @@ -58,7 +58,7 @@ "coinselect": "github:BlueWallet/coinselect#35f8038", "crypto-browserify": "3.12.1", "crypto-js": "4.2.0", - "dayjs": "1.11.20", + "dayjs": "1.11.21", "detox": "20.51.3", "ecpair": "3.0.1", "electrum-client": "github:BlueWallet/rn-electrum-client#83420b8", @@ -115,7 +115,6 @@ }, "devDependencies": { "@babel/core": "^7.26.0", - "@babel/preset-env": "^7.26.0", "@babel/runtime": "^7.26.0", "@jest/reporters": "^27.5.1", "@react-native/eslint-config": "^0.85.3", @@ -180,12 +179,12 @@ } }, "node_modules/@arkade-os/boltz-swap": { - "version": "0.3.37", - "resolved": "https://registry.npmjs.org/@arkade-os/boltz-swap/-/boltz-swap-0.3.37.tgz", - "integrity": "sha512-wP4daP/sDpUahmivaIZC8Lfvqz4lhQMWM1R8/Ib5x7NMS6k++FSs4KKQ6wjPKpweF8ULilsJdorhmLpNlEba6A==", + "version": "0.3.38", + "resolved": "https://registry.npmjs.org/@arkade-os/boltz-swap/-/boltz-swap-0.3.38.tgz", + "integrity": "sha512-BVbyw9Fj+1eQn771t0ZO9uW7E1BgViAPLFddb4pnW9p3rM9fCIdWEs2ZrjPnq70leDdhrUxRy++cJuK7zFThuA==", "license": "MIT", "dependencies": { - "@arkade-os/sdk": "0.4.32", + "@arkade-os/sdk": "0.4.33", "@noble/curves": "2.0.1", "@noble/hashes": "2.0.1", "@scure/base": "2.0.0", @@ -208,6 +207,8 @@ }, "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" @@ -217,9 +218,9 @@ } }, "node_modules/@arkade-os/sdk": { - "version": "0.4.32", - "resolved": "https://registry.npmjs.org/@arkade-os/sdk/-/sdk-0.4.32.tgz", - "integrity": "sha512-we7eNPuuW9PWRS/B4Nlw5MHXTgJ7CuQzbdSrisH0u3P2PPQd/0FbSspEW/OQRNjMrJl+29zAEKN5kswy9MTjxA==", + "version": "0.4.33", + "resolved": "https://registry.npmjs.org/@arkade-os/sdk/-/sdk-0.4.33.tgz", + "integrity": "sha512-EvfmDhSyAiZ7DW89o5D1N4woDEFMfZLHXi/zh9C1xKlPHB2PCezEkHpVe51lNF0Vx3rgkf6bx54QXoGOvg1p9A==", "license": "MIT", "dependencies": { "@bitcoinerlab/descriptors-scure": "3.1.7", @@ -584,7 +585,6 @@ }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { "version": "7.28.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -599,7 +599,6 @@ }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -613,7 +612,6 @@ }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -629,7 +627,6 @@ "version": "7.29.3", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.3.tgz", "integrity": "sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", @@ -644,7 +641,6 @@ }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -660,7 +656,6 @@ }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", @@ -688,7 +683,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" @@ -782,7 +776,6 @@ }, "node_modules/@babel/plugin-syntax-import-assertions": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" @@ -796,7 +789,6 @@ }, "node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" @@ -950,7 +942,6 @@ }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", @@ -1008,7 +999,6 @@ }, "node_modules/@babel/plugin-transform-block-scoped-functions": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1049,7 +1039,6 @@ }, "node_modules/@babel/plugin-transform-class-static-block": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", @@ -1082,7 +1071,6 @@ }, "node_modules/@babel/plugin-transform-computed-properties": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", @@ -1111,7 +1099,6 @@ }, "node_modules/@babel/plugin-transform-dotall-regex": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", @@ -1126,7 +1113,6 @@ }, "node_modules/@babel/plugin-transform-duplicate-keys": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1140,7 +1126,6 @@ }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { "version": "7.29.0", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", @@ -1155,7 +1140,6 @@ }, "node_modules/@babel/plugin-transform-dynamic-import": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1169,7 +1153,6 @@ }, "node_modules/@babel/plugin-transform-explicit-resource-management": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", @@ -1184,7 +1167,6 @@ }, "node_modules/@babel/plugin-transform-exponentiation-operator": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" @@ -1198,7 +1180,6 @@ }, "node_modules/@babel/plugin-transform-export-namespace-from": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1240,7 +1221,6 @@ }, "node_modules/@babel/plugin-transform-function-name": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", @@ -1256,7 +1236,6 @@ }, "node_modules/@babel/plugin-transform-json-strings": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" @@ -1270,7 +1249,6 @@ }, "node_modules/@babel/plugin-transform-literals": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1284,7 +1262,6 @@ }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" @@ -1298,7 +1275,6 @@ }, "node_modules/@babel/plugin-transform-member-expression-literals": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1312,7 +1288,6 @@ }, "node_modules/@babel/plugin-transform-modules-amd": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.27.1", @@ -1343,7 +1318,6 @@ "version": "7.29.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.28.6", @@ -1360,7 +1334,6 @@ }, "node_modules/@babel/plugin-transform-modules-umd": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.27.1", @@ -1389,7 +1362,6 @@ }, "node_modules/@babel/plugin-transform-new-target": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1416,7 +1388,6 @@ }, "node_modules/@babel/plugin-transform-numeric-separator": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" @@ -1430,7 +1401,6 @@ }, "node_modules/@babel/plugin-transform-object-rest-spread": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", @@ -1448,7 +1418,6 @@ }, "node_modules/@babel/plugin-transform-object-super": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -1490,7 +1459,6 @@ }, "node_modules/@babel/plugin-transform-parameters": { "version": "7.27.7", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1533,7 +1501,6 @@ }, "node_modules/@babel/plugin-transform-property-literals": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1616,7 +1583,6 @@ }, "node_modules/@babel/plugin-transform-regexp-modifiers": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", @@ -1631,7 +1597,6 @@ }, "node_modules/@babel/plugin-transform-reserved-words": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1676,7 +1641,6 @@ }, "node_modules/@babel/plugin-transform-spread": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", @@ -1691,7 +1655,6 @@ }, "node_modules/@babel/plugin-transform-sticky-regex": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1718,7 +1681,6 @@ }, "node_modules/@babel/plugin-transform-typeof-symbol": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1749,7 +1711,6 @@ }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.27.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1763,7 +1724,6 @@ }, "node_modules/@babel/plugin-transform-unicode-property-regex": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", @@ -1792,7 +1752,6 @@ }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", @@ -1809,7 +1768,6 @@ "version": "7.29.5", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.5.tgz", "integrity": "sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.29.3", @@ -1895,7 +1853,6 @@ "version": "0.14.2", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8", @@ -1907,7 +1864,6 @@ }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", @@ -3579,14 +3535,13 @@ } }, "node_modules/@noble/secp256k1": { - "version": "1.6.3", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.1.0.tgz", + "integrity": "sha512-+F7iS7tUMaNGXcc9X3PjmjvuQnXEuSjCRNzVVA2xAcKXgCaP0dHYz4SFyt4FKNHef7sOP//xihowcySSS7PK9g==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -3890,12 +3845,12 @@ } }, "node_modules/@react-native-vector-icons/common": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@react-native-vector-icons/common/-/common-13.0.0.tgz", - "integrity": "sha512-FJ0Ql5UTGVtK0ak4vLTxmhFHadb8NmTk4yOWoggh7UvC2pVQNyJK7L9nIZeIZ0IaVJtKfmKXtBWA0nKqqzQ/FQ==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/common/-/common-13.0.1.tgz", + "integrity": "sha512-UPC6L3tW5rXCjBn4kgw9RPURUILIg8tFpEY2uaYwU8aCjEHkywNCMcAO8+PvMCDkR6aICPeHYA0OXvMgrjsF4g==", "license": "MIT", "dependencies": { - "find-up": "^7.0.0", + "find-up": "^8.0.0", "picocolors": "^1.1.1", "plist": "^3.1.0" }, @@ -3908,6 +3863,7 @@ "peerDependencies": { "@react-native-vector-icons/get-image": "^13.0.0", "@react-native/assets-registry": "*", + "expo-font": "*", "react": "*", "react-native": "*" }, @@ -3917,36 +3873,38 @@ }, "@react-native/assets-registry": { "optional": true + }, + "expo-font": { + "optional": true } } }, "node_modules/@react-native-vector-icons/common/node_modules/find-up": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", - "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-8.0.0.tgz", + "integrity": "sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==", "license": "MIT", "dependencies": { - "locate-path": "^7.2.0", - "path-exists": "^5.0.0", - "unicorn-magic": "^0.1.0" + "locate-path": "^8.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@react-native-vector-icons/common/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-8.0.0.tgz", + "integrity": "sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==", "license": "MIT", "dependencies": { "p-locate": "^6.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3982,15 +3940,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-vector-icons/common/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/@react-native-vector-icons/common/node_modules/yocto-queue": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", @@ -4026,12 +3975,12 @@ } }, "node_modules/@react-native-vector-icons/fontawesome": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/@react-native-vector-icons/fontawesome/-/fontawesome-13.1.1.tgz", - "integrity": "sha512-GD1eOt1YmkxbUmHZzxpCGMMC3WCif3edo8RKMnv0dlf07KNLktfQDh0mVYJhU4d203oyeTk1E5GWBjNDRw3zWg==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/fontawesome/-/fontawesome-13.1.2.tgz", + "integrity": "sha512-Pae4/aDhvSd5FNVy6QOfcQ8uhj+fpbIc2WFDaO9jLEkqM0p5tMZt39Mcfw1XosOXQ0eSqJdlYoI2x8vqbdyzXg==", "license": "MIT", "dependencies": { - "@react-native-vector-icons/common": "^13.0.0" + "@react-native-vector-icons/common": "^13.0.1" }, "engines": { "node": ">= 18.0.0" @@ -4048,12 +3997,12 @@ } }, "node_modules/@react-native-vector-icons/fontawesome6": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/@react-native-vector-icons/fontawesome6/-/fontawesome6-13.1.1.tgz", - "integrity": "sha512-AwZSCk+2dakqzlBEEKwi/FBc6qg4TtGPPyj2OVt0HcA8sy+gMa0u5iW7hao/Fmq3ad0LQz9HTUYUeslH2jS0jA==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/fontawesome6/-/fontawesome6-13.1.2.tgz", + "integrity": "sha512-oQvQeDE8kSXm3l+oRKQm/Jo4ewR9YdKW2gFDVVl3st1yY5Nml1ZS4m3lTp3a/KehT9w+Uiv2JNn3kG0VOo+AZw==", "license": "MIT", "dependencies": { - "@react-native-vector-icons/common": "^13.0.0" + "@react-native-vector-icons/common": "^13.0.1" }, "engines": { "node": ">= 18.0.0" @@ -4070,12 +4019,12 @@ } }, "node_modules/@react-native-vector-icons/ionicons": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/@react-native-vector-icons/ionicons/-/ionicons-13.1.1.tgz", - "integrity": "sha512-OAIEf7HW5SnDi+YMRR1W/HBwzWmQiQ4msY8aSQRdVisPvbVFvO6vaWJdV33QI2aj1/5lVLh9oKJGcRsSaBzh2Q==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/ionicons/-/ionicons-13.1.2.tgz", + "integrity": "sha512-8TaXKw41MgKADeesrrbUpA3FR81JNy96ogiGRjWgtE1djSEevDsOKMij7Jq/3TfiGaE0prEshU0TcW5qwsf0Ug==", "license": "MIT", "dependencies": { - "@react-native-vector-icons/common": "^13.0.0" + "@react-native-vector-icons/common": "^13.0.1" }, "engines": { "node": ">= 18.0.0" @@ -4092,12 +4041,12 @@ } }, "node_modules/@react-native-vector-icons/material-design-icons": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/@react-native-vector-icons/material-design-icons/-/material-design-icons-13.1.1.tgz", - "integrity": "sha512-bKkai9GSMOrqIwKskHZuegejgO6bLp7xNgp7YdeLprkEK44/HsATjCpXhwvRPYq9RSHdOvrFFKBIKLZbkpijSw==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/material-design-icons/-/material-design-icons-13.1.2.tgz", + "integrity": "sha512-Qc8IQCxbnHOk8CvTAb+dLzYgRMbJOLiZ8Up7TRsNixY6EqwPx9/W3DeK5niKtNQ4dIfbALeYz41yyvDM7w7mag==", "license": "MIT", "dependencies": { - "@react-native-vector-icons/common": "^13.0.0" + "@react-native-vector-icons/common": "^13.0.1" }, "engines": { "node": ">= 18.0.0" @@ -4114,12 +4063,12 @@ } }, "node_modules/@react-native-vector-icons/material-icons": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/@react-native-vector-icons/material-icons/-/material-icons-13.1.1.tgz", - "integrity": "sha512-u13/5ITff+qGBZBnv3QQ+vLNCNgJzxUfXnMnZDK1rHgpUjH6lex3tSORX5XLYbCuaHDW7WFF0cqzoaephYZApg==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/material-icons/-/material-icons-13.1.2.tgz", + "integrity": "sha512-z8fckMFeYvvVzqfWpsM8AkSFf0pFwlwueKq8/HAKetrZEl3GsK29Mr+sv7me0N6kAl9Z+AaNXqD7gNQpCjkZgg==", "license": "MIT", "dependencies": { - "@react-native-vector-icons/common": "^13.0.0" + "@react-native-vector-icons/common": "^13.0.1" }, "engines": { "node": ">= 18.0.0" @@ -8077,9 +8026,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.20", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", - "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", "license": "MIT" }, "node_modules/debug": { @@ -9833,7 +9782,6 @@ }, "node_modules/esutils": { "version": "2.0.3", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -17526,6 +17474,18 @@ "ecpair": "3.0.0" } }, + "node_modules/silent-payments/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/silent-payments/node_modules/@scure/base": { "version": "1.2.6", "license": "MIT", @@ -18880,9 +18840,9 @@ } }, "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "license": "MIT", "engines": { "node": ">=18" diff --git a/package.json b/package.json index 9c2fc5033..dd9df1a6a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bluewallet", - "version": "8.0.0", + "version": "8.0.1", "license": "MIT", "repository": { "type": "git", @@ -16,7 +16,6 @@ }, "devDependencies": { "@babel/core": "^7.26.0", - "@babel/preset-env": "^7.26.0", "@babel/runtime": "^7.26.0", "@jest/reporters": "^27.5.1", "@react-native/eslint-config": "^0.85.3", @@ -91,18 +90,18 @@ "lint": " npm run tslint && node scripts/find-unused-loc.js && node scripts/find-english-leftovers.js && eslint --ext .js,.ts,.tsx '*.@(js|ts|tsx)' screen 'blue_modules/*.@(js|ts|tsx)' class models loc tests components navigation typings", "lint:fix": "npm run lint -- --fix", "lint:quickfix": "git status --porcelain | grep -v '\\.json' | grep -E '\\.js|\\.ts' --color=never | awk '{print $2}' | xargs eslint --fix; exit 0", - "unit": "jest -b -w tests/unit/*" + "unit": "jest -b tests/unit/*" }, "dependencies": { - "@arkade-os/boltz-swap": "0.3.37", - "@arkade-os/sdk": "0.4.32", + "@arkade-os/boltz-swap": "0.3.38", + "@arkade-os/sdk": "0.4.33", "@babel/preset-env": "7.29.5", "@bugsnag/react-native": "8.9.0", "@bugsnag/source-maps": "2.3.3", "@keystonehq/bc-ur-registry": "0.7.1", "@ngraveio/bc-ur": "1.1.13", "@noble/hashes": "1.3.3", - "@noble/secp256k1": "1.6.3", + "@noble/secp256k1": "3.1.0", "@react-native-async-storage/async-storage": "2.2.0", "@react-native-clipboard/clipboard": "1.16.3", "@react-native-community/cli": "20.1.3", @@ -110,11 +109,11 @@ "@react-native-community/cli-platform-ios": "20.1.3", "@react-native-documents/picker": "12.0.1", "@react-native-vector-icons/entypo": "13.1.1", - "@react-native-vector-icons/fontawesome": "13.1.1", - "@react-native-vector-icons/fontawesome6": "13.1.1", - "@react-native-vector-icons/ionicons": "13.1.1", - "@react-native-vector-icons/material-design-icons": "13.1.1", - "@react-native-vector-icons/material-icons": "13.1.1", + "@react-native-vector-icons/fontawesome": "13.1.2", + "@react-native-vector-icons/fontawesome6": "13.1.2", + "@react-native-vector-icons/ionicons": "13.1.2", + "@react-native-vector-icons/material-design-icons": "13.1.2", + "@react-native-vector-icons/material-icons": "13.1.2", "@react-native/babel-preset": "0.85.3", "@react-native/codegen": "0.85.3", "@react-native/gradle-plugin": "0.85.3", @@ -142,7 +141,7 @@ "coinselect": "github:BlueWallet/coinselect#35f8038", "crypto-browserify": "3.12.1", "crypto-js": "4.2.0", - "dayjs": "1.11.20", + "dayjs": "1.11.21", "detox": "20.51.3", "ecpair": "3.0.1", "electrum-client": "github:BlueWallet/rn-electrum-client#83420b8", diff --git a/screen/lnd/lndViewInvoice.tsx b/screen/lnd/lndViewInvoice.tsx index 5eea651be..95257bbe0 100644 --- a/screen/lnd/lndViewInvoice.tsx +++ b/screen/lnd/lndViewInvoice.tsx @@ -46,7 +46,7 @@ const LNDViewInvoice = () => { const [isFetchingInvoices, setIsFetchingInvoices] = useState(true); const [invoiceStatusChanged, setInvoiceStatusChanged] = useState(false); const [qrCodeSize, setQRCodeSize] = useState(90); - const fetchInvoiceInterval = useRef(null); + const fetchInvoiceInterval = useRef | undefined>(undefined); const isModal = useNavigationState(state => state.routeNames[0] === LNDCreateInvoice.routeName); // Per-swap claim/refund lookup, by the `swap-${id}` prefix mapped onto @@ -179,7 +179,6 @@ const LNDViewInvoice = () => { fetchInvoiceInterval.current = setInterval(async () => { if (isFetchingInvoices) { try { - // @ts-ignore - getUserInvoices is not set on TWallet const userInvoices: LightningTransaction[] = await wallet.getUserInvoices(20); // fetching only last 20 invoices // for invoice that was created just now - that should be enough (it is basically the last one, so limit=1 would be sufficient) diff --git a/screen/send/PsbtMultisigQRCode.tsx b/screen/send/PsbtMultisigQRCode.tsx index c73b5974d..388d4ff81 100644 --- a/screen/send/PsbtMultisigQRCode.tsx +++ b/screen/send/PsbtMultisigQRCode.tsx @@ -1,7 +1,7 @@ import { RouteProp, StackActions, useIsFocused, useRoute } from '@react-navigation/native'; import * as bitcoin from 'bitcoinjs-lib'; import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { ActivityIndicator, ScrollView, StyleSheet, View } from 'react-native'; +import { ActivityIndicator, ScrollView, StyleSheet, View, TouchableOpacity } from 'react-native'; import presentAlert from '../../components/Alert'; import { DynamicQRCode } from '../../components/DynamicQRCode'; import SaveFileButton from '../../components/SaveFileButton'; @@ -23,7 +23,7 @@ type RouteParams = RouteProp; const PsbtMultisigQRCode: React.FC = () => { const navigation = useExtendedNavigation(); const { colors } = useTheme(); - const openScannerButton = useRef(null); + const openScannerButton = useRef>(null); const { params } = useRoute(); const { psbtBase64, isShowOpenScanner, walletID } = params; const [isLoading, setIsLoading] = useState(false); diff --git a/screen/send/SendDetails.tsx b/screen/send/SendDetails.tsx index 86b8e4b78..25367b925 100644 --- a/screen/send/SendDetails.tsx +++ b/screen/send/SendDetails.tsx @@ -91,7 +91,7 @@ const SendDetails = () => { const payjoinUrl = route.params?.payjoinUrl; const isTransactionReplaceable = route.params?.isTransactionReplaceable; const routeParams = route.params; - const scrollView = useRef>(null); + const scrollView = useRef>(null); const scrollIndex = useRef(0); /** Used so we only clear coin-selection (utxos) when the user switches wallet, not on first mount (e.g. Send opened from wallet details with pre-selected UTXOs). */ const prevWalletIdForCoinResetRef = useRef(null); @@ -221,9 +221,6 @@ const SendDetails = () => { } return updatedAddresses; }); - - // @ts-ignore: Fix later - setParams(prevParams => ({ ...prevParams, addRecipientParams: undefined })); } else { setAddresses([{ address: '', key: String(Math.random()), unit: amountUnit }]); // key is for the FlatList } diff --git a/screen/settings/NotificationSettings.tsx b/screen/settings/NotificationSettings.tsx index 936ef0f9d..223077017 100644 --- a/screen/settings/NotificationSettings.tsx +++ b/screen/settings/NotificationSettings.tsx @@ -1,22 +1,19 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { Linking, StyleSheet, TextInput, View, Pressable, AppState, Text } from 'react-native'; +import { StyleSheet, View, Pressable, AppState, Text } from 'react-native'; import { - getDefaultUri, getPushToken, - getSavedUri, getStoredNotifications, - saveUri, isNotificationsEnabled, setLevels, tryToObtainPermissions, cleanUserOptOutFlag, - isGroundControlUriValid, checkPermissions, checkNotificationPermissionStatus, + enqueueTestPushNotification, NOTIFICATIONS_NO_AND_DONT_ASK_FLAG, } from '../../blue_modules/notifications'; -import { BlueSpacing20 } from '../../components/BlueSpacing'; import presentAlert from '../../components/Alert'; +import { BlueSpacing20 } from '../../components/BlueSpacing'; import { Button } from '../../components/Button'; import CopyToClipboardButton from '../../components/CopyToClipboardButton'; import { useTheme } from '../../components/themes'; @@ -43,7 +40,6 @@ const NotificationSettings: React.FC = () => { const [isNotificationsEnabledState, setNotificationsEnabledState] = useState(undefined); const [tokenInfo, setTokenInfo] = useState(''); - const [URI, setURI] = useState(); const [tapCount, setTapCount] = useState(0); const { colors } = useTheme(); @@ -139,7 +135,6 @@ const NotificationSettings: React.FC = () => { await updateNotificationStatus(); } - setURI((await getSavedUri()) ?? getDefaultUri()); setTokenInfo( 'token: ' + JSON.stringify(await getPushToken()) + @@ -172,25 +167,17 @@ const NotificationSettings: React.FC = () => { }; }, []); - const save = useCallback(async () => { + const enqueueTestPush = useCallback(async () => { setIsLoading(true); try { - if (URI) { - if (await isGroundControlUriValid(URI)) { - await saveUri(URI); - presentAlert({ message: loc.settings.saved }); - } else { - presentAlert({ message: loc.settings.not_a_valid_uri }); - } - } else { - await saveUri(''); - presentAlert({ message: loc.settings.saved }); - } + await enqueueTestPushNotification(); } catch (error) { - console.error('Error saving URI:', error); + console.error('Error enqueueing test push:', error); + presentAlert({ message: (error as Error).message }); + } finally { + setIsLoading(false); } - setIsLoading(false); - }, [URI]); + }, []); const renderDeveloperSettings = useCallback(() => { if (tapCount < 10) return null; @@ -198,44 +185,9 @@ const NotificationSettings: React.FC = () => { return ( - - - {loc.settings.groundcontrol_explanation} - - - - Linking.openURL('https://github.com/BlueWallet/GroundControl')} - chevron - position="single" - spacingTop - /> - - - - - setTapCount(tapCount + 1)}> ♪ Ground Control to Major Tom ♪ @@ -248,12 +200,12 @@ const NotificationSettings: React.FC = () => { -