Merge branch 'master' into reproducibility
This commit is contained in:
commit
d2e8e490c6
@ -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
|
||||
|
||||
2
.github/workflows/build-mac-catalyst.yml
vendored
2
.github/workflows/build-mac-catalyst.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Checkout project
|
||||
if: github.event_name == 'workflow_dispatch' || steps.labels.outputs.has_mac_dmg == 'true'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
4
.github/workflows/build-release-apk.yml
vendored
4
.github/workflows/build-release-apk.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: "0"
|
||||
|
||||
@ -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
|
||||
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -34,7 +34,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -53,6 +53,7 @@ jobs:
|
||||
BIP47_HD_MNEMONIC: ${{ secrets.BIP47_HD_MNEMONIC}}
|
||||
HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }}
|
||||
HD_MNEMONIC_BIP49: ${{ secrets.HD_MNEMONIC_BIP49 }}
|
||||
HD_MNEMONIC_OLD: ${{ secrets.HD_MNEMONIC_OLD }}
|
||||
HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }}
|
||||
HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }}
|
||||
HD_MNEMONIC_BREAD: ${{ secrets.HD_MNEMONIC_BREAD }}
|
||||
@ -64,7 +65,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -83,6 +84,7 @@ jobs:
|
||||
BIP47_HD_MNEMONIC: ${{ secrets.BIP47_HD_MNEMONIC}}
|
||||
HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }}
|
||||
HD_MNEMONIC_BIP49: ${{ secrets.HD_MNEMONIC_BIP49 }}
|
||||
HD_MNEMONIC_OLD: ${{ secrets.HD_MNEMONIC_OLD }}
|
||||
HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }}
|
||||
HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }}
|
||||
HD_MNEMONIC_BREAD: ${{ secrets.HD_MNEMONIC_BREAD }}
|
||||
|
||||
4
.github/workflows/e2e-android.yml
vendored
4
.github/workflows/e2e-android.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Free disk space (Ubuntu)
|
||||
run: |
|
||||
@ -86,7 +86,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Free disk space (Ubuntu)
|
||||
run: |
|
||||
|
||||
4
.github/workflows/e2e-ios.yml
vendored
4
.github/workflows/e2e-ios.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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'],
|
||||
};
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -26,44 +26,93 @@ export interface TinySecp256k1InterfaceExtended {
|
||||
signDER(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array;
|
||||
}
|
||||
|
||||
necc.utils.sha256Sync = (...messages: Uint8Array[]): Uint8Array => {
|
||||
const combinedMessages = messages.reduce((acc, msg) => {
|
||||
const newArray = new Uint8Array(acc.length + msg.length);
|
||||
newArray.set(acc);
|
||||
newArray.set(msg, acc.length);
|
||||
return newArray;
|
||||
}, new Uint8Array(0));
|
||||
return sha256(combinedMessages);
|
||||
};
|
||||
// @noble/hashes types differ slightly from @noble/secp256k1 v3 hash slot typings.
|
||||
necc.hashes.sha256 = sha256 as NonNullable<typeof necc.hashes.sha256>;
|
||||
necc.hashes.hmacSha256 = ((key: Uint8Array, message: Uint8Array) => hmac(sha256, key, message)) as NonNullable<
|
||||
typeof necc.hashes.hmacSha256
|
||||
>;
|
||||
|
||||
necc.utils.hmacSha256Sync = (key: Uint8Array, ...messages: Uint8Array[]): Uint8Array => {
|
||||
const combinedMessages = messages.reduce((acc, msg) => {
|
||||
const newArray = new Uint8Array(acc.length + msg.length);
|
||||
newArray.set(acc);
|
||||
newArray.set(msg, acc.length);
|
||||
return newArray;
|
||||
}, new Uint8Array(0));
|
||||
return hmac(sha256, key, combinedMessages);
|
||||
};
|
||||
|
||||
/* const normal = necc.utils._normalizePrivateKey;
|
||||
// Removed from @noble/secp256k1 v1.7; vendored from noble test vectors.
|
||||
// @see https://github.com/paulmillr/noble-secp256k1/blob/1.7.2/test/index.ts
|
||||
type Hex = string | Uint8Array;
|
||||
type PrivKey = Hex | bigint | number;
|
||||
|
||||
necc.utils.privateAdd = (privateKey: PrivKey, tweak: Hex) => {
|
||||
console.log({ privateKey, tweak });
|
||||
const p = normal(privateKey);
|
||||
const t = normal(tweak);
|
||||
return necc.utils.privateAdd(necc.utils.mod(p + t, necc.CURVE.n));
|
||||
}; */
|
||||
const { mod, secretKeyToScalar, numberToBytesBE, bytesToNumberBE, hexToBytes } = necc.etc;
|
||||
const CURVE_N = necc.Point.CURVE().n;
|
||||
|
||||
function pointFromBytes(p: Uint8Array): necc.Point {
|
||||
if (p.length === 32) {
|
||||
const prefixed = new Uint8Array(33);
|
||||
prefixed[0] = 0x02;
|
||||
prefixed.set(p, 1);
|
||||
return necc.Point.fromBytes(prefixed);
|
||||
}
|
||||
return necc.Point.fromBytes(p);
|
||||
}
|
||||
|
||||
const tweakUtils = {
|
||||
privateAdd: (privateKey: Hex, tweak: Hex): Uint8Array => {
|
||||
const p = secretKeyToScalar(typeof privateKey === 'string' ? hexToBytes(privateKey) : privateKey);
|
||||
const t = secretKeyToScalar(typeof tweak === 'string' ? hexToBytes(tweak) : tweak);
|
||||
return numberToBytesBE(mod(p + t, CURVE_N));
|
||||
},
|
||||
|
||||
privateNegate: (privateKey: Hex): Uint8Array => {
|
||||
const p = secretKeyToScalar(typeof privateKey === 'string' ? hexToBytes(privateKey) : privateKey);
|
||||
return numberToBytesBE(CURVE_N - p);
|
||||
},
|
||||
|
||||
pointAddScalar: (p: Hex, tweak: Hex, isCompressed?: boolean): Uint8Array => {
|
||||
const P = typeof p === 'string' ? necc.Point.fromHex(p) : pointFromBytes(p);
|
||||
const t = secretKeyToScalar(typeof tweak === 'string' ? hexToBytes(tweak) : tweak);
|
||||
const Q = P.add(necc.Point.BASE.multiply(t));
|
||||
if (Q.is0()) throw new Error('Tweaked point at infinity');
|
||||
return Q.toBytes(isCompressed);
|
||||
},
|
||||
|
||||
pointMultiply: (p: Hex, tweak: Hex, isCompressed?: boolean): Uint8Array => {
|
||||
const P = typeof p === 'string' ? necc.Point.fromHex(p) : pointFromBytes(p);
|
||||
const tweakBytes = typeof tweak === 'string' ? hexToBytes(tweak) : tweak;
|
||||
const t = mod(bytesToNumberBE(tweakBytes), CURVE_N);
|
||||
if (t === 0n) throw new Error('Point at infinity');
|
||||
return P.multiply(t).toBytes(isCompressed);
|
||||
},
|
||||
};
|
||||
|
||||
const defaultTrue = (param?: boolean): boolean => param !== false;
|
||||
|
||||
function compactToDER(sig: Uint8Array): Uint8Array {
|
||||
const encodeInt = (bytes: Uint8Array): Uint8Array => {
|
||||
let i = 0;
|
||||
while (i < bytes.length - 1 && bytes[i] === 0) i++;
|
||||
let trimmed = bytes.subarray(i);
|
||||
if (trimmed[0] >= 0x80) {
|
||||
const prefixed = new Uint8Array(trimmed.length + 1);
|
||||
prefixed[0] = 0;
|
||||
prefixed.set(trimmed, 1);
|
||||
trimmed = prefixed;
|
||||
}
|
||||
const encoded = new Uint8Array(2 + trimmed.length);
|
||||
encoded[0] = 0x02;
|
||||
encoded[1] = trimmed.length;
|
||||
encoded.set(trimmed, 2);
|
||||
return encoded;
|
||||
};
|
||||
|
||||
const rDer = encodeInt(sig.subarray(0, 32));
|
||||
const sDer = encodeInt(sig.subarray(32, 64));
|
||||
const seqLen = rDer.length + sDer.length;
|
||||
const der = new Uint8Array(2 + seqLen);
|
||||
der[0] = 0x30;
|
||||
der[1] = seqLen;
|
||||
der.set(rDer, 2);
|
||||
der.set(sDer, 2 + rDer.length);
|
||||
return der;
|
||||
}
|
||||
|
||||
function throwToNull<Type>(fn: () => Type): Type | null {
|
||||
try {
|
||||
return fn();
|
||||
} catch (e) {
|
||||
// console.log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -71,7 +120,8 @@ function throwToNull<Type>(fn: () => Type): Type | null {
|
||||
function isPoint(p: Uint8Array, xOnly: boolean): boolean {
|
||||
if ((p.length === 32) !== xOnly) return false;
|
||||
try {
|
||||
return !!necc.Point.fromHex(p);
|
||||
pointFromBytes(p);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
@ -79,23 +129,12 @@ function isPoint(p: Uint8Array, xOnly: boolean): boolean {
|
||||
|
||||
const ecc: TinySecp256k1InterfaceExtended & TinySecp256k1Interface & TinySecp256k1InterfaceBIP32 = {
|
||||
isPoint: (p: Uint8Array): boolean => isPoint(p, false),
|
||||
isPrivate: (d: Uint8Array): boolean => {
|
||||
/* if (
|
||||
[
|
||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141',
|
||||
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142',
|
||||
].includes(d.toString('hex'))
|
||||
) {
|
||||
return false;
|
||||
} */
|
||||
return necc.utils.isValidPrivateKey(d);
|
||||
},
|
||||
isPrivate: (d: Uint8Array): boolean => necc.utils.isValidSecretKey(d),
|
||||
isXOnlyPoint: (p: Uint8Array): boolean => isPoint(p, true),
|
||||
|
||||
xOnlyPointAddTweak: (p: Uint8Array, tweak: Uint8Array): { parity: 0 | 1; xOnlyPubkey: Uint8Array } | null =>
|
||||
throwToNull(() => {
|
||||
const P = necc.utils.pointAddScalar(p, tweak, true);
|
||||
const P = tweakUtils.pointAddScalar(p, tweak, true);
|
||||
const parity = P[0] % 2 === 1 ? 1 : 0;
|
||||
return { parity, xOnlyPubkey: P.slice(1) };
|
||||
}),
|
||||
@ -104,60 +143,56 @@ const ecc: TinySecp256k1InterfaceExtended & TinySecp256k1Interface & TinySecp256
|
||||
throwToNull(() => necc.getPublicKey(sk, defaultTrue(compressed))),
|
||||
|
||||
pointCompress: (p: Uint8Array, compressed?: boolean): Uint8Array => {
|
||||
return necc.Point.fromHex(p).toRawBytes(defaultTrue(compressed));
|
||||
return pointFromBytes(p).toBytes(defaultTrue(compressed));
|
||||
},
|
||||
|
||||
pointMultiply: (a: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null =>
|
||||
throwToNull(() => necc.utils.pointMultiply(a, tweak, defaultTrue(compressed))),
|
||||
throwToNull(() => tweakUtils.pointMultiply(a, tweak, defaultTrue(compressed))),
|
||||
|
||||
pointAdd: (a: Uint8Array, b: Uint8Array, compressed?: boolean): Uint8Array | null =>
|
||||
throwToNull(() => {
|
||||
const A = necc.Point.fromHex(a);
|
||||
const B = necc.Point.fromHex(b);
|
||||
return A.add(B).toRawBytes(defaultTrue(compressed));
|
||||
const A = pointFromBytes(a);
|
||||
const B = pointFromBytes(b);
|
||||
return A.add(B).toBytes(defaultTrue(compressed));
|
||||
}),
|
||||
|
||||
pointAddScalar: (p: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null =>
|
||||
throwToNull(() => necc.utils.pointAddScalar(p, tweak, defaultTrue(compressed))),
|
||||
throwToNull(() => tweakUtils.pointAddScalar(p, tweak, defaultTrue(compressed))),
|
||||
|
||||
privateAdd: (d: Uint8Array, tweak: Uint8Array): Uint8Array | null =>
|
||||
throwToNull(() => {
|
||||
// console.log({ d, tweak });
|
||||
if (d.join('') === '00000000000000000000000000000001' && tweak.join('') === '00000000000000000000000000000000') {
|
||||
return new Uint8Array(d); // make test_ecc happy
|
||||
}
|
||||
|
||||
const ret = necc.utils.privateAdd(d, tweak);
|
||||
// console.log(ret);
|
||||
const ret = tweakUtils.privateAdd(d, tweak);
|
||||
if (ret.join('') === '00000000000000000000000000000000') {
|
||||
return null;
|
||||
}
|
||||
return ret;
|
||||
}),
|
||||
|
||||
privateNegate: (d: Uint8Array): Uint8Array => necc.utils.privateNegate(d),
|
||||
privateNegate: (d: Uint8Array): Uint8Array => tweakUtils.privateNegate(d),
|
||||
|
||||
sign: (h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array => {
|
||||
return necc.signSync(h, d, { der: false, extraEntropy: e });
|
||||
return necc.sign(h, d, { prehash: false, extraEntropy: e });
|
||||
},
|
||||
|
||||
signDER: (h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array => {
|
||||
return necc.signSync(h, d, { der: true, extraEntropy: e });
|
||||
return compactToDER(necc.sign(h, d, { prehash: false, extraEntropy: e }));
|
||||
},
|
||||
|
||||
signSchnorr: (h: Uint8Array, d: Uint8Array, e: Uint8Array = new Uint8Array(32).fill(0x00)): Uint8Array => {
|
||||
return necc.schnorr.signSync(h, d, e);
|
||||
return necc.schnorr.sign(h, d, e);
|
||||
},
|
||||
|
||||
verify: (h: Uint8Array, Q: Uint8Array, signature: Uint8Array, strict?: boolean): boolean => {
|
||||
return necc.verify(signature, h, Q, { strict });
|
||||
return necc.verify(signature, h, Q, { prehash: false, lowS: strict !== false });
|
||||
},
|
||||
|
||||
verifySchnorr: (h: Uint8Array, Q: Uint8Array, signature: Uint8Array): boolean => {
|
||||
return necc.schnorr.verifySync(signature, h, Q);
|
||||
return necc.schnorr.verify(signature, h, Q);
|
||||
},
|
||||
};
|
||||
|
||||
export default ecc;
|
||||
|
||||
// module.exports.ecc = ecc;
|
||||
|
||||
@ -8,16 +8,16 @@ import {
|
||||
Notifications,
|
||||
} from 'react-native-notifications';
|
||||
import { checkNotifications, requestNotifications, RESULTS } from 'react-native-permissions';
|
||||
import type { BoltzReverseSwap } from '@arkade-os/boltz-swap';
|
||||
import loc from '../loc';
|
||||
import { groundControlUri } from './constants';
|
||||
import { arkadePaymentPushUri, groundControlUri } from './constants';
|
||||
import { fetch } from '../util/fetch';
|
||||
|
||||
const PUSH_TOKEN = 'PUSH_TOKEN';
|
||||
const GROUNDCONTROL_BASE_URI = 'GROUNDCONTROL_BASE_URI';
|
||||
const NOTIFICATIONS_STORAGE = 'NOTIFICATIONS_STORAGE';
|
||||
const ANDROID_NOTIFICATION_CHANNEL_ID = 'channel_01';
|
||||
export const NOTIFICATIONS_NO_AND_DONT_ASK_FLAG = 'NOTIFICATIONS_NO_AND_DONT_ASK_FLAG';
|
||||
let baseURI = groundControlUri;
|
||||
const baseURI = groundControlUri;
|
||||
let notificationSubscriptions: EmitterSubscription[] = [];
|
||||
let onProcessNotificationsHandler: undefined | (() => void | Promise<void>);
|
||||
const handledNotificationKeys = new Set<string>();
|
||||
@ -252,6 +252,29 @@ export const tryToObtainPermissions = async (): Promise<boolean> => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const enqueueTestPushNotification = async (): Promise<void> => {
|
||||
const pushToken = await getPushToken();
|
||||
if (!pushToken?.token || !pushToken?.os) {
|
||||
throw new Error('No push token available');
|
||||
}
|
||||
|
||||
const response = await fetch(`${baseURI}/enqueue`, {
|
||||
method: 'POST',
|
||||
headers: _getHeaders(),
|
||||
body: JSON.stringify({
|
||||
type: 5,
|
||||
token: pushToken.token,
|
||||
os: pushToken.os,
|
||||
text: 'Test push notification',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Enqueue request failed with status ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Submits onchain bitcoin addresses and ln invoice preimage hashes to GroundControl server, so later we could
|
||||
* be notified if they were paid
|
||||
@ -327,6 +350,44 @@ export const majorTomToGroundControl = async (addresses: string[], hashes: strin
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers an Ark swap with the bitcoin-payment-push-service so the device is
|
||||
* pushed when the invoice gets paid. Fire-and-forget: never throws, gated by
|
||||
* the same opt-out/token rules as majorTomToGroundControl(). The swap's
|
||||
* preimage is always stripped before leaving the device.
|
||||
*/
|
||||
export const registerArkPaymentPush = async (paymentHash: string, label: string, pendingSwap: BoltzReverseSwap): Promise<void> => {
|
||||
if (!arkadePaymentPushUri) return;
|
||||
try {
|
||||
const noAndDontAskFlag = await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG);
|
||||
if (noAndDontAskFlag === 'true') {
|
||||
console.warn('User has opted out of notifications.');
|
||||
return;
|
||||
}
|
||||
|
||||
const pushToken = await getPushToken();
|
||||
if (!pushToken || !pushToken.token || !pushToken.os) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(`${arkadePaymentPushUri}/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
topic: paymentHash,
|
||||
label,
|
||||
swap: { ...pendingSwap, preimage: '' },
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`status ${response.status}`);
|
||||
}
|
||||
console.log('[ARK] payment push registration ok');
|
||||
} catch (e: any) {
|
||||
console.log('[ARK] payment push registration failed:', e?.message ?? e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a permissions object:
|
||||
* alert: boolean
|
||||
@ -529,22 +590,6 @@ const configureNotifications = async (onProcessNotifications?: () => void): Prom
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the provided GroundControl URI is valid by pinging it.
|
||||
*
|
||||
* @param uri {string}
|
||||
* @returns {Promise<boolean>} TRUE if valid, FALSE otherwise
|
||||
*/
|
||||
export const isGroundControlUriValid = async (uri: string) => {
|
||||
try {
|
||||
const response = await fetch(`${uri}/ping`, { headers: _getHeaders() });
|
||||
const json = await response.json();
|
||||
return !!json.description;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android';
|
||||
|
||||
export const getPushToken = async (): Promise<TPushToken> => {
|
||||
@ -676,38 +721,6 @@ export const removeAllDeliveredNotifications = () => {
|
||||
Notifications.removeAllDeliveredNotifications();
|
||||
};
|
||||
|
||||
export const getDefaultUri = () => {
|
||||
return groundControlUri;
|
||||
};
|
||||
|
||||
export const saveUri = async (uri: string) => {
|
||||
try {
|
||||
baseURI = uri || groundControlUri;
|
||||
await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, baseURI);
|
||||
} catch (error) {
|
||||
console.error('Error saving URI:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getSavedUri = async () => {
|
||||
try {
|
||||
const baseUriStored = await AsyncStorage.getItem(GROUNDCONTROL_BASE_URI);
|
||||
if (baseUriStored) {
|
||||
baseURI = baseUriStored;
|
||||
}
|
||||
return baseUriStored;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
try {
|
||||
await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, groundControlUri);
|
||||
} catch (storageError) {
|
||||
console.error('Failed to reset URI:', storageError);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const isNotificationsEnabled = async () => {
|
||||
try {
|
||||
const levels = await getLevels();
|
||||
@ -757,10 +770,6 @@ export const initializeNotifications = async (onProcessNotifications?: () => voi
|
||||
return;
|
||||
}
|
||||
|
||||
const baseUriStored = await AsyncStorage.getItem(GROUNDCONTROL_BASE_URI);
|
||||
baseURI = baseUriStored || groundControlUri;
|
||||
console.log('Base URI set to:', baseURI);
|
||||
|
||||
setApplicationIconBadgeNumber(0);
|
||||
|
||||
// Only check permissions, never request
|
||||
@ -781,7 +790,5 @@ export const initializeNotifications = async (onProcessNotifications?: () => voi
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize notifications:', error);
|
||||
baseURI = groundControlUri;
|
||||
await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, groundControlUri).catch(err => console.error('Failed to reset URI:', err));
|
||||
}
|
||||
};
|
||||
|
||||
@ -147,11 +147,10 @@ export class BlueApp {
|
||||
console.warn('error reading', key, error.message);
|
||||
console.warn('fallback to realm');
|
||||
const realmKeyValue = await this.openRealmKeyValue();
|
||||
const obj = realmKeyValue.objectForPrimaryKey('KeyValue', key); // search for a realm object with a primary key
|
||||
const obj = realmKeyValue.objectForPrimaryKey<{ key: string; value: string }>('KeyValue', key);
|
||||
value = obj?.value;
|
||||
realmKeyValue.close();
|
||||
if (value) {
|
||||
// @ts-ignore value.length
|
||||
console.warn('successfully recovered', value.length, 'bytes from realm for key', key);
|
||||
return value;
|
||||
}
|
||||
@ -547,10 +546,11 @@ export class BlueApp {
|
||||
(walletToInflate._txs_by_internal_index[tx.index] as Transaction[]).push(transaction);
|
||||
}
|
||||
} else {
|
||||
if (!Array.isArray(walletToInflate._txs_by_external_index)) walletToInflate._txs_by_external_index = [];
|
||||
walletToInflate._txs_by_external_index = walletToInflate._txs_by_external_index || [];
|
||||
// Legacy single-address wallets - store under index 0
|
||||
walletToInflate._txs_by_external_index = walletToInflate._txs_by_external_index || {};
|
||||
walletToInflate._txs_by_external_index[0] = walletToInflate._txs_by_external_index[0] || [];
|
||||
const transaction = JSON.parse(tx.tx);
|
||||
(walletToInflate._txs_by_external_index as Transaction[]).push(transaction);
|
||||
walletToInflate._txs_by_external_index[0].push(transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -559,32 +559,6 @@ export class BlueApp {
|
||||
const id = wallet.getID();
|
||||
const walletToSave = ('_hdWalletInstance' in wallet && wallet._hdWalletInstance) || wallet;
|
||||
|
||||
if (Array.isArray(walletToSave._txs_by_external_index)) {
|
||||
// if this var is an array that means its a single-address wallet class, and this var is a flat array
|
||||
// with transactions
|
||||
realm.write(() => {
|
||||
// cleanup all existing transactions for the wallet first
|
||||
const walletTransactionsToDelete = realm.objects('WalletTransactions').filtered(`walletid = '${id}'`);
|
||||
realm.delete(walletTransactionsToDelete);
|
||||
|
||||
// @ts-ignore walletToSave._txs_by_external_index is array
|
||||
for (const tx of walletToSave._txs_by_external_index) {
|
||||
realm.create(
|
||||
'WalletTransactions',
|
||||
{
|
||||
walletid: id,
|
||||
tx: JSON.stringify(tx),
|
||||
},
|
||||
Realm.UpdateMode.Modified,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// ########################################################################################################
|
||||
|
||||
if (walletToSave._txs_by_external_index) {
|
||||
realm.write(() => {
|
||||
// cleanup all existing transactions for the wallet first
|
||||
@ -592,16 +566,14 @@ export class BlueApp {
|
||||
realm.delete(walletTransactionsToDelete);
|
||||
|
||||
// insert new ones:
|
||||
for (const index of Object.keys(walletToSave._txs_by_external_index)) {
|
||||
// @ts-ignore index is number
|
||||
const txs = walletToSave._txs_by_external_index[index];
|
||||
for (const [indexStr, txs] of Object.entries(walletToSave._txs_by_external_index)) {
|
||||
for (const tx of txs) {
|
||||
realm.create(
|
||||
'WalletTransactions',
|
||||
{
|
||||
walletid: id,
|
||||
internal: false,
|
||||
index: parseInt(index, 10),
|
||||
index: parseInt(indexStr, 10),
|
||||
tx: JSON.stringify(tx),
|
||||
},
|
||||
Realm.UpdateMode.Modified,
|
||||
@ -609,16 +581,14 @@ export class BlueApp {
|
||||
}
|
||||
}
|
||||
|
||||
for (const index of Object.keys(walletToSave._txs_by_internal_index)) {
|
||||
// @ts-ignore index is number
|
||||
const txs = walletToSave._txs_by_internal_index[index];
|
||||
for (const [indexStr, txs] of Object.entries(walletToSave._txs_by_internal_index)) {
|
||||
for (const tx of txs) {
|
||||
realm.create(
|
||||
'WalletTransactions',
|
||||
{
|
||||
walletid: id,
|
||||
internal: true,
|
||||
index: parseInt(index, 10),
|
||||
index: parseInt(indexStr, 10),
|
||||
tx: JSON.stringify(tx),
|
||||
},
|
||||
Realm.UpdateMode.Modified,
|
||||
|
||||
@ -390,7 +390,7 @@ export class HDSegwitBech32Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore stfu
|
||||
return { tx, inputs, outputs, fee };
|
||||
// Non-null assertions are safe here because the while loop always runs at least once (add starts at 0)
|
||||
return { tx: tx!, inputs: inputs!, outputs: outputs!, fee: fee! };
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
* @return {Promise.<Uint8Array>} The random bytes
|
||||
*/
|
||||
export async function randomBytes(size: number): Promise<Uint8Array> {
|
||||
const g: any = globalThis as any;
|
||||
const g = globalThis as any;
|
||||
const rnCrypto = g && g.crypto;
|
||||
if (!rnCrypto || typeof rnCrypto.getRandomValues !== 'function') {
|
||||
throw new Error('crypto.getRandomValues is not available');
|
||||
|
||||
@ -45,9 +45,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||
_balances_by_external_index: Record<number, BalanceByIndex>;
|
||||
_balances_by_internal_index: Record<number, BalanceByIndex>;
|
||||
|
||||
// @ts-ignore
|
||||
_txs_by_external_index: Record<number, Transaction[]>;
|
||||
// @ts-ignore
|
||||
_txs_by_internal_index: Record<number, Transaction[]>;
|
||||
|
||||
_utxo: any[];
|
||||
@ -204,70 +202,37 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||
return child.toWIF();
|
||||
}
|
||||
|
||||
_getNodeAddressByIndex(node: number, index: number): string {
|
||||
index = index * 1; // cast to int
|
||||
_getNodeByIndex(node: 0 | 1, index: number): BIP32Interface {
|
||||
const cachedNode = node === 0 ? this._node0 : this._node1;
|
||||
if (cachedNode) {
|
||||
return cachedNode.derive(index);
|
||||
}
|
||||
|
||||
const xpub = this._zpubToXpub(this.getXpub());
|
||||
const hdNode = bip32.fromBase58(xpub).derive(node);
|
||||
|
||||
if (node === 0) {
|
||||
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
|
||||
}
|
||||
|
||||
if (node === 1) {
|
||||
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
|
||||
}
|
||||
|
||||
if (node === 0 && !this._node0) {
|
||||
const xpub = this._zpubToXpub(this.getXpub());
|
||||
const hdNode = bip32.fromBase58(xpub);
|
||||
this._node0 = hdNode.derive(node);
|
||||
}
|
||||
|
||||
if (node === 1 && !this._node1) {
|
||||
const xpub = this._zpubToXpub(this.getXpub());
|
||||
const hdNode = bip32.fromBase58(xpub);
|
||||
this._node1 = hdNode.derive(node);
|
||||
}
|
||||
|
||||
let address: string;
|
||||
if (node === 0) {
|
||||
// @ts-ignore
|
||||
address = this._hdNodeToAddress(this._node0.derive(index));
|
||||
this._node0 = hdNode;
|
||||
} else {
|
||||
// tbh the only possible else is node === 1
|
||||
// @ts-ignore
|
||||
address = this._hdNodeToAddress(this._node1.derive(index));
|
||||
this._node1 = hdNode;
|
||||
}
|
||||
|
||||
if (node === 0) {
|
||||
return (this.external_addresses_cache[index] = address);
|
||||
} else {
|
||||
// tbh the only possible else option is node === 1
|
||||
return (this.internal_addresses_cache[index] = address);
|
||||
}
|
||||
return hdNode.derive(index);
|
||||
}
|
||||
|
||||
_getNodePubkeyByIndex(node: number, index: number) {
|
||||
index = index * 1; // cast to int
|
||||
_getNodeAddressByIndex(node: 0 | 1, index: number): string {
|
||||
const cache = node === 0 ? this.external_addresses_cache : this.internal_addresses_cache;
|
||||
|
||||
if (node === 0 && !this._node0) {
|
||||
const xpub = this._zpubToXpub(this.getXpub());
|
||||
const hdNode = bip32.fromBase58(xpub);
|
||||
this._node0 = hdNode.derive(node);
|
||||
}
|
||||
if (cache[index]) return cache[index]; // cache hit
|
||||
|
||||
if (node === 1 && !this._node1) {
|
||||
const xpub = this._zpubToXpub(this.getXpub());
|
||||
const hdNode = bip32.fromBase58(xpub);
|
||||
this._node1 = hdNode.derive(node);
|
||||
}
|
||||
const hdNode = this._getNodeByIndex(node, index);
|
||||
const address = this._hdNodeToAddress(hdNode);
|
||||
|
||||
if (node === 0 && this._node0) {
|
||||
return this._node0.derive(index).publicKey;
|
||||
}
|
||||
return (cache[index] = address);
|
||||
}
|
||||
|
||||
if (node === 1 && this._node1) {
|
||||
return this._node1.derive(index).publicKey;
|
||||
}
|
||||
|
||||
throw new Error('Internal error: this._node0 or this._node1 is undefined');
|
||||
_getNodePubkeyByIndex(node: 0 | 1, index: number) {
|
||||
return this._getNodeByIndex(node, index).publicKey;
|
||||
}
|
||||
|
||||
_getExternalAddressByIndex(index: number): string {
|
||||
@ -424,137 +389,95 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||
// now, we need to put transactions in all relevant `cells` of internal hashmaps:
|
||||
// this._txs_by_internal_index, this._txs_by_external_index & this._txs_by_payment_code_index
|
||||
|
||||
// address -> index lookup maps; the single pass over transactions below uses them
|
||||
// to find which cells a transaction belongs to
|
||||
const externalIndexByAddress = new Map<string, number>();
|
||||
for (let c = 0; c < next_free_address_index + this.gap_limit; c++) {
|
||||
for (const tx of Object.values(txdatas)) {
|
||||
for (const vin of tx.vin) {
|
||||
if (vin.addresses && vin.addresses.indexOf(this._getExternalAddressByIndex(c)) !== -1) {
|
||||
// this TX is related to our address
|
||||
this._txs_by_external_index[c] = this._txs_by_external_index[c] || [];
|
||||
const { vin: txVin, vout: txVout, ...txRest } = tx;
|
||||
const clonedTx = {
|
||||
...txRest,
|
||||
inputs: txVin.slice(0),
|
||||
outputs: txVout.slice(0),
|
||||
timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */,
|
||||
};
|
||||
|
||||
// trying to replace tx if it exists already (because it has lower confirmations, for example)
|
||||
let replaced = false;
|
||||
for (let cc = 0; cc < this._txs_by_external_index[c].length; cc++) {
|
||||
if (this._txs_by_external_index[c][cc].txid === clonedTx.txid) {
|
||||
replaced = true;
|
||||
this._txs_by_external_index[c][cc] = clonedTx;
|
||||
}
|
||||
}
|
||||
if (!replaced) this._txs_by_external_index[c].push(clonedTx);
|
||||
}
|
||||
}
|
||||
for (const vout of tx.vout) {
|
||||
if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses.indexOf(this._getExternalAddressByIndex(c)) !== -1) {
|
||||
// this TX is related to our address
|
||||
this._txs_by_external_index[c] = this._txs_by_external_index[c] || [];
|
||||
const { vin: txVin, vout: txVout, ...txRest } = tx;
|
||||
const clonedTx = {
|
||||
...txRest,
|
||||
inputs: txVin.slice(0),
|
||||
outputs: txVout.slice(0),
|
||||
timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */,
|
||||
};
|
||||
|
||||
// trying to replace tx if it exists already (because it has lower confirmations, for example)
|
||||
let replaced = false;
|
||||
for (let cc = 0; cc < this._txs_by_external_index[c].length; cc++) {
|
||||
if (this._txs_by_external_index[c][cc].txid === clonedTx.txid) {
|
||||
replaced = true;
|
||||
this._txs_by_external_index[c][cc] = clonedTx;
|
||||
}
|
||||
}
|
||||
if (!replaced) this._txs_by_external_index[c].push(clonedTx);
|
||||
}
|
||||
}
|
||||
}
|
||||
externalIndexByAddress.set(this._getExternalAddressByIndex(c), c);
|
||||
}
|
||||
|
||||
const internalIndexByAddress = new Map<string, number>();
|
||||
for (let c = 0; c < next_free_change_address_index + this.gap_limit; c++) {
|
||||
for (const tx of Object.values(txdatas)) {
|
||||
for (const vin of tx.vin) {
|
||||
if (vin.addresses && vin.addresses.indexOf(this._getInternalAddressByIndex(c)) !== -1) {
|
||||
// this TX is related to our address
|
||||
this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || [];
|
||||
const { vin: txVin, vout: txVout, ...txRest } = tx;
|
||||
const clonedTx = {
|
||||
...txRest,
|
||||
inputs: txVin.slice(0),
|
||||
outputs: txVout.slice(0),
|
||||
timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */,
|
||||
};
|
||||
|
||||
// trying to replace tx if it exists already (because it has lower confirmations, for example)
|
||||
let replaced = false;
|
||||
for (let cc = 0; cc < this._txs_by_internal_index[c].length; cc++) {
|
||||
if (this._txs_by_internal_index[c][cc].txid === clonedTx.txid) {
|
||||
replaced = true;
|
||||
this._txs_by_internal_index[c][cc] = clonedTx;
|
||||
}
|
||||
}
|
||||
if (!replaced) this._txs_by_internal_index[c].push(clonedTx);
|
||||
}
|
||||
}
|
||||
for (const vout of tx.vout) {
|
||||
if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses.indexOf(this._getInternalAddressByIndex(c)) !== -1) {
|
||||
// this TX is related to our address
|
||||
this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || [];
|
||||
const { vin: txVin, vout: txVout, ...txRest } = tx;
|
||||
const clonedTx = {
|
||||
...txRest,
|
||||
inputs: txVin.slice(0),
|
||||
outputs: txVout.slice(0),
|
||||
timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */,
|
||||
};
|
||||
|
||||
// trying to replace tx if it exists already (because it has lower confirmations, for example)
|
||||
let replaced = false;
|
||||
for (let cc = 0; cc < this._txs_by_internal_index[c].length; cc++) {
|
||||
if (this._txs_by_internal_index[c][cc].txid === clonedTx.txid) {
|
||||
replaced = true;
|
||||
this._txs_by_internal_index[c][cc] = clonedTx;
|
||||
}
|
||||
}
|
||||
if (!replaced) this._txs_by_internal_index[c].push(clonedTx);
|
||||
}
|
||||
}
|
||||
}
|
||||
internalIndexByAddress.set(this._getInternalAddressByIndex(c), c);
|
||||
}
|
||||
|
||||
const paymentCodeIndexByAddress = new Map<string, { pc: string; c: number }>();
|
||||
for (const pc of this._receive_payment_codes) {
|
||||
for (let c = 0; c < this._getNextFreePaymentCodeIndexReceive(pc) + this.gap_limit; c++) {
|
||||
for (const tx of Object.values(txdatas)) {
|
||||
// since we are iterating PCs who can pay us, we can completely ignore `tx.vin` and only iterate `tx.vout`
|
||||
for (const vout of tx.vout) {
|
||||
if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses.indexOf(this._getBIP47AddressReceive(pc, c)) !== -1) {
|
||||
// this TX is related to our address
|
||||
this._txs_by_payment_code_index[pc] = this._txs_by_payment_code_index[pc] || {};
|
||||
this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c] || [];
|
||||
const { vin: txVin, vout: txVout, ...txRest } = tx;
|
||||
const clonedTx = {
|
||||
...txRest,
|
||||
inputs: txVin.slice(0),
|
||||
outputs: txVout.slice(0),
|
||||
timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */,
|
||||
};
|
||||
paymentCodeIndexByAddress.set(this._getBIP47AddressReceive(pc, c), { pc, c });
|
||||
}
|
||||
}
|
||||
|
||||
// trying to replace tx if it exists already (because it has lower confirmations, for example)
|
||||
let replaced = false;
|
||||
for (let cc = 0; cc < this._txs_by_payment_code_index[pc][c].length; cc++) {
|
||||
if (this._txs_by_payment_code_index[pc][c][cc].txid === clonedTx.txid) {
|
||||
replaced = true;
|
||||
this._txs_by_payment_code_index[pc][c][cc] = clonedTx;
|
||||
}
|
||||
}
|
||||
if (!replaced) this._txs_by_payment_code_index[pc][c].push(clonedTx);
|
||||
}
|
||||
}
|
||||
// per-cell txid -> position lookup, used to replace-or-push a transaction into a cell in constant time
|
||||
const cellPositionsByTxid = new Map<Transaction[], Map<string, number>>();
|
||||
const getCellPositions = (cell: Transaction[]): Map<string, number> => {
|
||||
let positions = cellPositionsByTxid.get(cell);
|
||||
if (!positions) {
|
||||
positions = new Map();
|
||||
for (let cc = 0; cc < cell.length; cc++) positions.set(cell[cc].txid, cc);
|
||||
cellPositionsByTxid.set(cell, positions);
|
||||
}
|
||||
return positions;
|
||||
};
|
||||
|
||||
for (const tx of Object.values(txdatas)) {
|
||||
// collecting which of our address `cells` this transaction touches:
|
||||
const externalCells = new Set<number>();
|
||||
const internalCells = new Set<number>();
|
||||
const paymentCodeCells = new Map<string, { pc: string; c: number }>();
|
||||
|
||||
const matchAddress = (address: string, isVout: boolean) => {
|
||||
const externalIndex = externalIndexByAddress.get(address);
|
||||
if (externalIndex !== undefined) externalCells.add(externalIndex);
|
||||
const internalIndex = internalIndexByAddress.get(address);
|
||||
if (internalIndex !== undefined) internalCells.add(internalIndex);
|
||||
if (isVout) {
|
||||
// since we are iterating PCs who can pay us, we can completely ignore `tx.vin` and only check `tx.vout`
|
||||
const paymentCodeIndex = paymentCodeIndexByAddress.get(address);
|
||||
if (paymentCodeIndex) paymentCodeCells.set(address, paymentCodeIndex);
|
||||
}
|
||||
};
|
||||
|
||||
for (const vin of tx.vin) {
|
||||
for (const address of vin.addresses ?? []) matchAddress(address, false);
|
||||
}
|
||||
for (const vout of tx.vout) {
|
||||
for (const address of vout.scriptPubKey.addresses ?? []) matchAddress(address, true);
|
||||
}
|
||||
|
||||
if (externalCells.size === 0 && internalCells.size === 0 && paymentCodeCells.size === 0) continue;
|
||||
|
||||
// this TX is related to our address(es)
|
||||
const upsertClone = (cell: Transaction[]) => {
|
||||
const { vin: txVin, vout: txVout, ...txRest } = tx;
|
||||
const clonedTx = {
|
||||
...txRest,
|
||||
inputs: txVin.slice(0),
|
||||
outputs: txVout.slice(0),
|
||||
timestamp: tx.blocktime || tx.time || Math.floor(+new Date() / 1000) - 30 /* unconfirmed */,
|
||||
};
|
||||
|
||||
// trying to replace tx if it exists already (because it has lower confirmations, for example)
|
||||
const positions = getCellPositions(cell);
|
||||
const existingPosition = positions.get(clonedTx.txid);
|
||||
if (existingPosition !== undefined) {
|
||||
cell[existingPosition] = clonedTx;
|
||||
} else {
|
||||
positions.set(clonedTx.txid, cell.length);
|
||||
cell.push(clonedTx);
|
||||
}
|
||||
};
|
||||
|
||||
for (const c of externalCells) {
|
||||
this._txs_by_external_index[c] = this._txs_by_external_index[c] || [];
|
||||
upsertClone(this._txs_by_external_index[c]);
|
||||
}
|
||||
for (const c of internalCells) {
|
||||
this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || [];
|
||||
upsertClone(this._txs_by_internal_index[c]);
|
||||
}
|
||||
for (const { pc, c } of paymentCodeCells.values()) {
|
||||
this._txs_by_payment_code_index[pc] = this._txs_by_payment_code_index[pc] || {};
|
||||
this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c] || [];
|
||||
upsertClone(this._txs_by_payment_code_index[pc][c]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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');
|
||||
}
|
||||
|
||||
|
||||
@ -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<number, Transaction[]> = {};
|
||||
_txs_by_internal_index: Record<number, Transaction[]> = {};
|
||||
|
||||
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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ interface ListItemProps {
|
||||
subtitleNumberOfLines?: number;
|
||||
rightTitle?: string;
|
||||
rightTitleStyle?: StyleProp<TextStyle>;
|
||||
rightTitleSelectable?: boolean;
|
||||
rightSubtitle?: string | React.ReactNode;
|
||||
rightSubtitleStyle?: StyleProp<TextStyle>;
|
||||
chevron?: boolean;
|
||||
@ -45,6 +46,7 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||
subtitleNumberOfLines,
|
||||
rightTitle,
|
||||
rightTitleStyle,
|
||||
rightTitleSelectable,
|
||||
rightSubtitle,
|
||||
rightSubtitleStyle,
|
||||
chevron,
|
||||
@ -112,7 +114,7 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||
{rightTitle || rightSubtitle ? (
|
||||
<View style={styles.rightColumn}>
|
||||
{rightTitle ? (
|
||||
<Text style={rightTitleStyle} numberOfLines={1} accessibilityRole="text">
|
||||
<Text style={rightTitleStyle} numberOfLines={1} accessibilityRole="text" selectable={rightTitleSelectable}>
|
||||
{rightTitle}
|
||||
</Text>
|
||||
) : null}
|
||||
|
||||
@ -216,24 +216,11 @@ const QRCode: React.FC<QRCodeProps> = ({
|
||||
const gradFill = `url(#${GRADIENT_ID})`;
|
||||
|
||||
const finderShapes: React.ReactElement[] = [];
|
||||
const outerR = 2 * cell;
|
||||
const holeR = 1.25 * cell;
|
||||
const dotR = 0.9 * cell;
|
||||
finderOrigins.forEach(([fr, fc], i) => {
|
||||
const x = (fc + 1) * cell;
|
||||
const y = (fr + 1) * cell;
|
||||
finderShapes.push(
|
||||
<Rect
|
||||
key={`finder-frame-${i}`}
|
||||
testID="qr-finder-frame"
|
||||
x={x}
|
||||
y={y}
|
||||
width={7 * cell}
|
||||
height={7 * cell}
|
||||
rx={outerR}
|
||||
ry={outerR}
|
||||
fill={gradFill}
|
||||
/>,
|
||||
<Rect key={`finder-frame-${i}`} testID="qr-finder-frame" x={x} y={y} width={7 * cell} height={7 * cell} fill={gradFill} />,
|
||||
<Rect
|
||||
key={`finder-hole-${i}`}
|
||||
testID="qr-finder-hole"
|
||||
@ -241,8 +228,6 @@ const QRCode: React.FC<QRCodeProps> = ({
|
||||
y={y + cell}
|
||||
width={5 * cell}
|
||||
height={5 * cell}
|
||||
rx={holeR}
|
||||
ry={holeR}
|
||||
fill={BACKGROUND}
|
||||
/>,
|
||||
<Rect
|
||||
@ -252,8 +237,6 @@ const QRCode: React.FC<QRCodeProps> = ({
|
||||
y={y + 2 * cell}
|
||||
width={3 * cell}
|
||||
height={3 * cell}
|
||||
rx={dotR}
|
||||
ry={dotR}
|
||||
fill={gradFill}
|
||||
/>,
|
||||
);
|
||||
@ -277,16 +260,7 @@ const QRCode: React.FC<QRCodeProps> = ({
|
||||
{finderShapes}
|
||||
{isLogoRendered && logoCells > 0 && (
|
||||
<>
|
||||
<Rect
|
||||
testID="qr-logo-backdrop"
|
||||
x={backdropX}
|
||||
y={backdropY}
|
||||
width={backdropSize}
|
||||
height={backdropSize}
|
||||
rx={cell * 0.5}
|
||||
ry={cell * 0.5}
|
||||
fill={LOGO_BACKGROUND}
|
||||
/>
|
||||
<Rect testID="qr-logo-backdrop" x={backdropX} y={backdropY} width={backdropSize} height={backdropSize} fill={LOGO_BACKGROUND} />
|
||||
<SvgImage
|
||||
testID="qr-logo-image"
|
||||
href={require('../img/qr-code.png')}
|
||||
|
||||
@ -515,15 +515,7 @@ interface WalletsCarouselProps extends Partial<FlatListProps<any>> {
|
||||
animateChanges?: boolean;
|
||||
}
|
||||
|
||||
type FlatListRefType = FlatList<any> & {
|
||||
scrollToEnd(params?: { animated?: boolean | null }): void;
|
||||
scrollToIndex(params: { animated?: boolean | null; index: number; viewOffset?: number; viewPosition?: number }): void;
|
||||
scrollToItem(params: { animated?: boolean | null; item: TWallet; viewPosition?: number }): void;
|
||||
scrollToOffset(params: { animated?: boolean | null; offset: number }): void;
|
||||
recordInteraction(): void;
|
||||
flashScrollIndicators(): void;
|
||||
getNativeScrollRef(): View;
|
||||
};
|
||||
export type CarouselListRefType = FlatList<TWallet>;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
listHeaderSeparator: {
|
||||
@ -534,7 +526,7 @@ const styles = StyleSheet.create({
|
||||
|
||||
const ListHeaderSeparator = () => <View style={styles.listHeaderSeparator} />;
|
||||
|
||||
const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props, ref) => {
|
||||
const WalletsCarousel = forwardRef<CarouselListRefType, WalletsCarouselProps>((props, ref) => {
|
||||
const {
|
||||
horizontal = true,
|
||||
data,
|
||||
@ -569,7 +561,7 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
|
||||
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const isInitialMount = useRef(true);
|
||||
|
||||
const flatListRef = useRef<FlatList<any>>(null);
|
||||
const flatListRef = useRef<FlatList<TWallet>>(null);
|
||||
const walletRefs = useRef<Record<string, React.MutableRefObject<View | null>>>({});
|
||||
|
||||
const { sizeClass } = useSizeClass();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.",
|
||||
|
||||
206
package-lock.json
generated
206
package-lock.json
generated
@ -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"
|
||||
|
||||
23
package.json
23
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",
|
||||
|
||||
@ -46,7 +46,7 @@ const LNDViewInvoice = () => {
|
||||
const [isFetchingInvoices, setIsFetchingInvoices] = useState<boolean>(true);
|
||||
const [invoiceStatusChanged, setInvoiceStatusChanged] = useState<boolean>(false);
|
||||
const [qrCodeSize, setQRCodeSize] = useState<number>(90);
|
||||
const fetchInvoiceInterval = useRef<any>(null);
|
||||
const fetchInvoiceInterval = useRef<ReturnType<typeof setInterval> | 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)
|
||||
|
||||
@ -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<SendDetailsStackParamList, 'PsbtMultisigQRCode'>;
|
||||
const PsbtMultisigQRCode: React.FC = () => {
|
||||
const navigation = useExtendedNavigation();
|
||||
const { colors } = useTheme();
|
||||
const openScannerButton = useRef<any>(null);
|
||||
const openScannerButton = useRef<React.ElementRef<typeof TouchableOpacity>>(null);
|
||||
const { params } = useRoute<RouteParams>();
|
||||
const { psbtBase64, isShowOpenScanner, walletID } = params;
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
@ -91,7 +91,7 @@ const SendDetails = () => {
|
||||
const payjoinUrl = route.params?.payjoinUrl;
|
||||
const isTransactionReplaceable = route.params?.isTransactionReplaceable;
|
||||
const routeParams = route.params;
|
||||
const scrollView = useRef<FlatList<any>>(null);
|
||||
const scrollView = useRef<FlatList<IPaymentDestinations>>(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<string | null>(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
|
||||
}
|
||||
|
||||
@ -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<boolean | undefined>(undefined);
|
||||
|
||||
const [tokenInfo, setTokenInfo] = useState('<empty>');
|
||||
const [URI, setURI] = useState<string | undefined>();
|
||||
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 (
|
||||
<View>
|
||||
<View style={[styles.divider, { backgroundColor: colors.lightBorder ?? colors.borderTopColor }]} />
|
||||
<SettingsCard style={styles.card}>
|
||||
<View style={styles.cardContent}>
|
||||
<Text style={[styles.multilineText, { color: colors.foregroundColor }]}>{loc.settings.groundcontrol_explanation}</Text>
|
||||
</View>
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsListItem
|
||||
title="github.com/BlueWallet/GroundControl"
|
||||
iconName="github"
|
||||
onPress={() => Linking.openURL('https://github.com/BlueWallet/GroundControl')}
|
||||
chevron
|
||||
position="single"
|
||||
spacingTop
|
||||
/>
|
||||
|
||||
<SettingsCard style={styles.card}>
|
||||
<View style={styles.cardContent}>
|
||||
<View
|
||||
style={[
|
||||
styles.uri,
|
||||
{ borderColor: colors.formBorder, borderBottomColor: colors.formBorder, backgroundColor: colors.inputBackgroundColor },
|
||||
]}
|
||||
>
|
||||
<TextInput
|
||||
placeholder={getDefaultUri()}
|
||||
value={URI}
|
||||
onChangeText={setURI}
|
||||
numberOfLines={1}
|
||||
style={[styles.uriText, { color: colors.alternativeTextColor }]}
|
||||
placeholderTextColor="#81868e"
|
||||
editable={!isLoading}
|
||||
textContentType="URL"
|
||||
autoCapitalize="none"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<BlueSpacing20 />
|
||||
<Text style={[styles.centered, { color: colors.foregroundColor }]} onPress={() => setTapCount(tapCount + 1)}>
|
||||
♪ Ground Control to Major Tom ♪
|
||||
</Text>
|
||||
@ -248,12 +200,12 @@ const NotificationSettings: React.FC = () => {
|
||||
</View>
|
||||
|
||||
<BlueSpacing20 />
|
||||
<Button onPress={save} title={loc.settings.save} />
|
||||
<Button onPress={enqueueTestPush} title="Enqueue test push notification" disabled={isLoading} />
|
||||
</View>
|
||||
</SettingsCard>
|
||||
</View>
|
||||
);
|
||||
}, [tapCount, colors, isLoading, URI, tokenInfo, save]);
|
||||
}, [tapCount, colors, isLoading, tokenInfo, enqueueTestPush]);
|
||||
|
||||
const renderPushNotificationsExplanation = useCallback(() => {
|
||||
return (
|
||||
@ -375,28 +327,10 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: horizontalPadding,
|
||||
paddingVertical: isAndroid ? 12 : 10,
|
||||
},
|
||||
multilineText: {
|
||||
lineHeight: 20,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
centered: {
|
||||
textAlign: 'center',
|
||||
marginVertical: 4,
|
||||
},
|
||||
uri: {
|
||||
flexDirection: 'row',
|
||||
borderWidth: 1,
|
||||
borderBottomWidth: 0.5,
|
||||
minHeight: 44,
|
||||
height: 44,
|
||||
alignItems: 'center',
|
||||
},
|
||||
uriText: {
|
||||
flex: 1,
|
||||
marginHorizontal: 8,
|
||||
minHeight: 36,
|
||||
height: 36,
|
||||
},
|
||||
divider: {
|
||||
marginVertical: isAndroid ? 16 : 12,
|
||||
height: 0.5,
|
||||
|
||||
@ -5,7 +5,7 @@ import { StyleSheet, View, ViewStyle, Animated, ScrollView } from 'react-native'
|
||||
import { TWallet } from '../../class/wallets/types';
|
||||
import { Header } from '../../components/Header';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import WalletsCarousel from '../../components/WalletsCarousel';
|
||||
import WalletsCarousel, { CarouselListRefType } from '../../components/WalletsCarousel';
|
||||
import loc from '../../loc';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import TotalWalletsBalance from '../../components/TotalWalletsBalance';
|
||||
@ -94,7 +94,7 @@ const DrawerList: React.FC<DrawerContentComponentProps> = memo((props: DrawerCon
|
||||
const drawerNavigation = props.navigation;
|
||||
|
||||
const [state, dispatch] = useReducer(walletReducer, initialState);
|
||||
const walletsCarousel = useRef<any>(null);
|
||||
const walletsCarousel = useRef<CarouselListRefType>(null);
|
||||
const { wallets, selectedWalletID } = useStorage();
|
||||
const { colors } = useTheme();
|
||||
const isFocused = useIsFocused();
|
||||
|
||||
@ -78,7 +78,7 @@ const ExportMultisigCoordinationSetup: React.FC = () => {
|
||||
const { wallets } = useStorage();
|
||||
const { isPrivacyBlurEnabled } = useSettings();
|
||||
const wallet: TWallet | undefined = wallets.find(w => w.getID() === walletID);
|
||||
const dynamicQRCode = useRef<any>(null);
|
||||
const dynamicQRCode = useRef<DynamicQRCode>(null);
|
||||
const { colors } = useTheme();
|
||||
const { enableScreenProtect, disableScreenProtect } = useScreenProtect();
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import { useTheme } from '../../components/themes';
|
||||
import loc from '../../loc';
|
||||
import { Chain } from '../../models/bitcoinUnits';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import WalletsCarousel from '../../components/WalletsCarousel';
|
||||
import WalletsCarousel, { CarouselListRefType } from '../../components/WalletsCarousel';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import { TWallet } from '../../class/wallets/types';
|
||||
import { pop } from '../../NavigationService';
|
||||
@ -35,7 +35,7 @@ const SelectWallet: React.FC = () => {
|
||||
const { wallets } = useStorage();
|
||||
const { colors } = useTheme();
|
||||
const isModal = useNavigationState(state => state.routes.length > 1);
|
||||
const walletsCarousel = useRef<any>(null);
|
||||
const walletsCarousel = useRef<CarouselListRefType>(null);
|
||||
const previousRouteName = useNavigationState(state => state.routes[state.routes.length - 2]?.name);
|
||||
const [filteredWallets, setFilteredWallets] = useState<TWallet[]>([]);
|
||||
|
||||
|
||||
@ -542,6 +542,7 @@ const WalletDetails: React.FC = () => {
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
testID="WalletNameDisplay"
|
||||
selectable
|
||||
>
|
||||
{walletName}
|
||||
</Text>
|
||||
@ -779,7 +780,6 @@ const WalletDetails: React.FC = () => {
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
onPress={navigateToXPub}
|
||||
title={loc.wallets.details_show_xpub}
|
||||
chevron
|
||||
testID="XpubButton"
|
||||
bottomDivider
|
||||
/>
|
||||
@ -789,7 +789,6 @@ const WalletDetails: React.FC = () => {
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
onPress={navigateToSignVerify}
|
||||
title={loc.addresses.sign_title}
|
||||
chevron
|
||||
testID="SignVerify"
|
||||
bottomDivider={!!(wallet.type === MultisigHDWallet.type)}
|
||||
/>
|
||||
@ -840,6 +839,7 @@ const WalletDetails: React.FC = () => {
|
||||
titleStyle={stylesHook.advancedListItemTitle}
|
||||
rightTitle={wallet.typeReadable}
|
||||
rightTitleStyle={stylesHook.advancedListItemRightTitle}
|
||||
rightTitleSelectable
|
||||
bottomDivider={
|
||||
!!(
|
||||
wallet.type === MultisigHDWallet.type ||
|
||||
@ -880,6 +880,7 @@ const WalletDetails: React.FC = () => {
|
||||
isMasterFingerPrintVisible ? (masterFingerprint ?? loc.wallets.import_derivation_loading) : loc.multisig.view
|
||||
}
|
||||
rightTitleStyle={stylesHook.advancedListItemRightTitle}
|
||||
rightTitleSelectable={isMasterFingerPrintVisible}
|
||||
bottomDivider={!!derivationPath}
|
||||
/>
|
||||
)}
|
||||
@ -890,6 +891,7 @@ const WalletDetails: React.FC = () => {
|
||||
titleStyle={stylesHook.advancedListItemTitle}
|
||||
rightTitle={derivationPath}
|
||||
rightTitleStyle={stylesHook.advancedListItemRightTitle}
|
||||
rightTitleSelectable
|
||||
bottomDivider={false}
|
||||
testID="DerivationPath"
|
||||
/>
|
||||
|
||||
@ -82,7 +82,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }: { rout
|
||||
const [displayUnit, setDisplayUnit] = useState(wallet.preferredBalanceUnit);
|
||||
const [isUnitSwitching, setIsUnitSwitching] = useState(false);
|
||||
const [isWatchOnlyWarningVisible, setIsWatchOnlyWarningVisible] = useState<boolean>(() => {
|
||||
return wallet.type === WatchOnlyWallet.type && (wallet as any).isWatchOnlyWarningVisible;
|
||||
return wallet.type === WatchOnlyWallet.type && (wallet as WatchOnlyWallet).isWatchOnlyWarningVisible;
|
||||
});
|
||||
const MAX_FAILURES = 3;
|
||||
const flatListRef = useRef<FlatList<Transaction>>(null);
|
||||
@ -172,7 +172,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }: { rout
|
||||
}, [wallet, walletID]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsWatchOnlyWarningVisible(wallet.type === WatchOnlyWallet.type && (wallet as any).isWatchOnlyWarningVisible);
|
||||
setIsWatchOnlyWarningVisible(wallet.type === WatchOnlyWallet.type && (wallet as WatchOnlyWallet).isWatchOnlyWarningVisible);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [walletID]);
|
||||
|
||||
@ -547,7 +547,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }: { rout
|
||||
if ('setPreferredBalanceUnit' in wallet) {
|
||||
wallet.setPreferredBalanceUnit(selectedUnit);
|
||||
} else {
|
||||
(wallet as any).preferredBalanceUnit = selectedUnit;
|
||||
(wallet as TWallet).preferredBalanceUnit = selectedUnit;
|
||||
}
|
||||
await saveToDisk();
|
||||
console.debug('[UnitSwitch] persisted preferred unit', { walletID, unit: selectedUnit });
|
||||
|
||||
@ -11,7 +11,7 @@ import presentAlert from '../../components/Alert';
|
||||
import { FButton, FContainer, FloatButtonsBottomFade } from '../../components/FloatButtons';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import { TransactionListItem } from '../../components/TransactionListItem';
|
||||
import WalletsCarousel, { getWalletCarouselItemWidth } from '../../components/WalletsCarousel';
|
||||
import WalletsCarousel, { getWalletCarouselItemWidth, CarouselListRefType } from '../../components/WalletsCarousel';
|
||||
import { useSizeClass, SizeClass } from '../../blue_modules/sizeClass';
|
||||
import loc from '../../loc';
|
||||
import ActionSheet from '../ActionSheet';
|
||||
@ -101,7 +101,7 @@ const WalletsList: React.FC = () => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const { isLoading } = state;
|
||||
const { sizeClass, isLarge } = useSizeClass();
|
||||
const walletsCarousel = useRef<any>(null);
|
||||
const walletsCarousel = useRef<CarouselListRefType>(null);
|
||||
const connectionPoll = useContext(ConnectionPollContext);
|
||||
const currentWalletIndex = useRef<number>(0);
|
||||
const { registerTransactionsHandler, unregisterTransactionsHandler } = useMenuElements();
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
import { closeAllArkadeRealms, __testing__ as realmTesting } from '../../blue_modules/arkade-adapters/realm/realmInstance';
|
||||
import { __testing__ as walletTesting } from '../../class/wallets/lightning-ark-wallet';
|
||||
import { LightningArkWallet, __testing__ as walletTesting } from '../../class/wallets/lightning-ark-wallet';
|
||||
|
||||
const Realm = require('realm');
|
||||
|
||||
@ -81,3 +81,39 @@ export const arkadeMockState = {
|
||||
Keychain.__mockKeychainHelpers.store.set(service, { username: service, password, service });
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Tear down a LightningArkWallet after integration tests. Stops SDK background
|
||||
* loops (ContractWatcher SSE, VtxoManager polling, SwapManager) via dispose()
|
||||
* before clearing module-private caches.
|
||||
*/
|
||||
export async function teardownArkadeWallet(w: LightningArkWallet): Promise<void> {
|
||||
try {
|
||||
await w.onDelete();
|
||||
} catch {
|
||||
// onDelete already logs and swallows per-namespace errors.
|
||||
}
|
||||
}
|
||||
|
||||
/** Best-effort dispose of any Arkade SDK runtime still cached module-wide. */
|
||||
export async function disposeAllArkadeRuntime(): Promise<void> {
|
||||
for (const ns of Object.keys(walletTesting.staticSwapsCache)) {
|
||||
const swaps = walletTesting.staticSwapsCache[ns];
|
||||
try {
|
||||
if (typeof swaps?.dispose === 'function') await swaps.dispose();
|
||||
} catch {}
|
||||
delete walletTesting.staticSwapsCache[ns];
|
||||
}
|
||||
for (const ns of Object.keys(walletTesting.staticWalletCache)) {
|
||||
const sdkWallet = walletTesting.staticWalletCache[ns];
|
||||
try {
|
||||
if (typeof sdkWallet?.dispose === 'function') await sdkWallet.dispose();
|
||||
} catch {}
|
||||
delete walletTesting.staticWalletCache[ns];
|
||||
}
|
||||
walletTesting.initInFlight.clear();
|
||||
walletTesting.restoreInFlight.clear();
|
||||
for (const k of Object.keys(walletTesting.boardingLock)) delete walletTesting.boardingLock[k];
|
||||
closeAllArkadeRealms();
|
||||
realmTesting.openInFlight.clear();
|
||||
}
|
||||
|
||||
@ -110,3 +110,25 @@ export function installSdkProviderSpies(): void {
|
||||
export function restoreSdkProviderSpies(): void {
|
||||
jest.restoreAllMocks();
|
||||
}
|
||||
|
||||
let backgroundLoopSpies: jest.SpiedFunction<any>[] = [];
|
||||
|
||||
export function restoreSdkBackgroundLoopStubs(): void {
|
||||
for (const spy of backgroundLoopSpies) spy.mockRestore();
|
||||
backgroundLoopSpies = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub only the SDK background subscriptions that Jest cannot shut down
|
||||
* cleanly (VtxoManager polling, SwapManager WebSocket, ContractWatcher SSE).
|
||||
* Real HTTP calls (getInfo, getTransactionHistory, restoreSwaps, etc.) still
|
||||
* run — use in env-gated integration tests that hit production services.
|
||||
*/
|
||||
export function installSdkBackgroundLoopStubs(): void {
|
||||
restoreSdkBackgroundLoopStubs();
|
||||
backgroundLoopSpies = [
|
||||
jest.spyOn(VtxoManager.prototype as any, 'initializeSubscription').mockResolvedValue(undefined),
|
||||
jest.spyOn(SwapManager.prototype as any, 'start').mockResolvedValue(undefined),
|
||||
jest.spyOn(ContractManager.prototype as any, 'initialize').mockResolvedValue(undefined),
|
||||
];
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@ import assert from 'assert';
|
||||
|
||||
import { HDSegwitBech32Wallet } from '../../class/wallets/hd-segwit-bech32-wallet';
|
||||
import { LightningArkWallet } from '../../class/wallets/lightning-ark-wallet.ts';
|
||||
import { disposeAllArkadeRuntime, teardownArkadeWallet } from '../helpers/arkadeMocks';
|
||||
import { installSdkBackgroundLoopStubs, restoreSdkBackgroundLoopStubs } from '../helpers/sdkProviderMocks';
|
||||
|
||||
// Ark storage lives in Realm, not AsyncStorage. Realm + Keychain are mocked
|
||||
// globally by tests/setup.js (per-path Realm + service-keyed Keychain), and
|
||||
@ -15,29 +17,41 @@ jest.setTimeout(30_000);
|
||||
const w = new LightningArkWallet();
|
||||
|
||||
beforeAll(async () => {
|
||||
// Install before the env guard: `can generate` runs init() regardless of
|
||||
// HD_MNEMONIC_OLD, and without the stubs its background loops keep Jest alive.
|
||||
installSdkBackgroundLoopStubs();
|
||||
if (!process.env.HD_MNEMONIC_OLD) {
|
||||
console.error('process.env.HD_MNEMONIC_OLD not set, skipped');
|
||||
return;
|
||||
}
|
||||
w.setSecret('arkade://' + process.env.HD_MNEMONIC_OLD);
|
||||
await w.init();
|
||||
await w.restoreSwaps();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 3_000)); // sleep
|
||||
if (process.env.HD_MNEMONIC_OLD) {
|
||||
await teardownArkadeWallet(w);
|
||||
}
|
||||
await disposeAllArkadeRuntime();
|
||||
restoreSdkBackgroundLoopStubs();
|
||||
});
|
||||
|
||||
describe('LightningArkWallet (integration)', () => {
|
||||
it('can generate', async () => {
|
||||
const wGenerated = new LightningArkWallet();
|
||||
await wGenerated.generate();
|
||||
try {
|
||||
await wGenerated.generate();
|
||||
|
||||
assert.ok(wGenerated.getSecret().startsWith('arkade://'));
|
||||
assert.ok(wGenerated.getSecret().startsWith('arkade://'));
|
||||
|
||||
const mnemonics = wGenerated.getSecret().replace('arkade://', '');
|
||||
const hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(mnemonics);
|
||||
assert.ok(hd.validateMnemonic());
|
||||
const mnemonics = wGenerated.getSecret().replace('arkade://', '');
|
||||
const hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(mnemonics);
|
||||
assert.ok(hd.validateMnemonic());
|
||||
} finally {
|
||||
await teardownArkadeWallet(wGenerated);
|
||||
}
|
||||
});
|
||||
|
||||
it('can fetch balance', async () => {
|
||||
@ -70,47 +84,48 @@ describe('LightningArkWallet (integration)', () => {
|
||||
}
|
||||
|
||||
await w.fetchTransactions();
|
||||
await w.fetchUserInvoices();
|
||||
|
||||
const txs = w.getTransactions();
|
||||
assert.ok(txs.length > 0);
|
||||
assert.ok(txs.length > 0, 'Should have transaction history from the Ark indexer');
|
||||
|
||||
// Find the reverse swap (incoming) transaction
|
||||
const receiveTx = txs.find(t => t.value! > 0);
|
||||
assert.ok(receiveTx, 'Should have at least one receive transaction');
|
||||
assert.strictEqual(receiveTx.memo, 'test invoice');
|
||||
assert.strictEqual(receiveTx.value, 9999);
|
||||
assert.strictEqual(receiveTx.timestamp, 1761224952);
|
||||
assert.strictEqual(receiveTx.ispaid, true);
|
||||
assert.ok(receiveTx.payment_hash);
|
||||
assert.ok(receiveTx.payment_request);
|
||||
assert.strictEqual(receiveTx.payment_preimage, '7244f7e956a91171038ea935d56cdb758cc36c345f0aa92764bfed6fe6fc9b17');
|
||||
assert.ok(receiveTx.value! > 0);
|
||||
assert.ok(receiveTx.timestamp! > 0);
|
||||
assert.ok(receiveTx.memo);
|
||||
|
||||
// Find the submarine swap (outgoing) transaction
|
||||
const sendTx = txs.find(t => t.value! < 0);
|
||||
assert.ok(sendTx, 'Should have at least one send transaction');
|
||||
assert.strictEqual(sendTx.value, -8001);
|
||||
assert.strictEqual(sendTx.timestamp, 1761225645);
|
||||
assert.strictEqual(sendTx.ispaid, true);
|
||||
assert.ok(sendTx.payment_hash);
|
||||
assert.ok(sendTx.payment_request);
|
||||
assert.strictEqual(sendTx.payment_preimage, '182fb8f273bda01b22c0e91991e093e18b2970f389fc7f7a2121870324eb2de5');
|
||||
const swapHistory: any[] = (w as any)._swapHistory ?? [];
|
||||
const settledReverse = swapHistory.find(s => s.type === 'reverse' && s.status === 'invoice.settled');
|
||||
if (settledReverse) {
|
||||
// When Boltz reverse-swap history is restored, settled receives are enriched in place.
|
||||
assert.strictEqual(receiveTx.ispaid, true);
|
||||
assert.ok(receiveTx.payment_hash);
|
||||
assert.ok(receiveTx.payment_request);
|
||||
assert.ok(receiveTx.payment_preimage);
|
||||
assert.notStrictEqual(receiveTx.memo, 'Received');
|
||||
|
||||
const ownInvoice = settledReverse.request?.invoice || settledReverse.response?.invoice;
|
||||
if (ownInvoice) {
|
||||
assert.ok(w.isInvoiceGeneratedByWallet(ownInvoice));
|
||||
}
|
||||
}
|
||||
|
||||
const settledSubmarine = swapHistory.find(s => s.type === 'submarine' && s.status === 'transaction.claimed');
|
||||
if (settledSubmarine) {
|
||||
const sendTx = txs.find(t => t.value! < 0);
|
||||
assert.ok(sendTx, 'Should have a send transaction when submarine swap history exists');
|
||||
assert.strictEqual(sendTx.ispaid, true);
|
||||
assert.ok(sendTx.payment_hash);
|
||||
assert.ok(sendTx.payment_request);
|
||||
assert.ok(sendTx.payment_preimage);
|
||||
}
|
||||
|
||||
const invoices = await w.getUserInvoices();
|
||||
assert.ok(invoices.length > 0);
|
||||
assert(invoices[0].value! > 0);
|
||||
assert(invoices[0].ispaid);
|
||||
|
||||
assert.ok(
|
||||
w.isInvoiceGeneratedByWallet(
|
||||
'lnbc100u1p50528cpp5rhy4fgs0ff23asecxtxt9zvc3apn0p8h7fxsj0d5k7j3x92zwhlqdq5w3jhxapqd9h8vmmfvdjscqrp80xqyf8ucsp5vcsrzye432n9wh0zwuv5z8y5n9zvkwpctr685e80utzc2yueccms9qxpqysgqd87swq3hput9k6llp0wxg098hc7ge3e5nrtnvak6zreywzaf4k9s8d3u4hrmt3m22kf0jt7ruqj0caknk5ykzdenjdphz50t7xrstnqqn6aw0m',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
!w.isInvoiceGeneratedByWallet(
|
||||
'lnbc80u1p5052hwpp5z4ln6hyq4wcck809pt7f0q54ag5he6ce797flm7gl9vuccm9lx2sdqqcqzysxqyz5vqsp5nh9fl4g36606tvxswtnfxzy55yze2656cw2fya7dhl8r6u0czyds9qxpqysgq83sw25g9d9ltr05nkfzejnvvunzkrk4qeuxhszuvvsguk5m6vmg3a7n5nd67l9frru3kjzpt8x6jfusjyc7ezh49jeeh900kt3v30qsqzq7fst',
|
||||
),
|
||||
);
|
||||
if (settledReverse) {
|
||||
assert.ok(invoices.length > 0);
|
||||
assert(invoices[0].value! > 0);
|
||||
assert(invoices[0].ispaid);
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
import assert from 'assert';
|
||||
import { isGroundControlUriValid } from '../../blue_modules/notifications';
|
||||
|
||||
// Notifications.default = new Notifications();
|
||||
|
||||
describe('notifications', () => {
|
||||
// yeah, lets rely less on external services...
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('can check groundcontrol server uri validity', async () => {
|
||||
assert.ok(await isGroundControlUriValid('https://groundcontrol.bluewallet.io/'));
|
||||
assert.ok(!(await isGroundControlUriValid('https://www.google.com')));
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
});
|
||||
|
||||
// muted because it causes jest to hang waiting indefinitely
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('can check non-responding url', async () => {
|
||||
assert.ok(!(await isGroundControlUriValid('https://localhost.com')));
|
||||
});
|
||||
});
|
||||
@ -7,7 +7,8 @@ console.warn = (...args) => {
|
||||
if (
|
||||
typeof args[0] === 'string' &&
|
||||
(args[0].startsWith('WARNING: Sending to a future segwit version address can lead to loss of funds') ||
|
||||
args[0].startsWith('only compressed public keys are good'))
|
||||
args[0].startsWith('only compressed public keys are good') ||
|
||||
args[0].startsWith('Using standard fetch instead of expo/fetch'))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -510,6 +511,17 @@ jest.mock('../blue_modules/analytics', () => {
|
||||
return ret;
|
||||
});
|
||||
|
||||
// addInvoice() registers a fire-and-forget payment-push callback; disable the
|
||||
// URI in unit tests so node-fetch does not leave in-flight handles after the
|
||||
// suite exits (which makes Jest fail with "did not exit one second after").
|
||||
jest.mock('../blue_modules/constants', () => {
|
||||
const actual = jest.requireActual('../blue_modules/constants');
|
||||
return {
|
||||
...actual,
|
||||
arkadePaymentPushUri: '',
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('react-native-share', () => {
|
||||
return {
|
||||
open: jest.fn(),
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, render, waitFor } from '@testing-library/react-native';
|
||||
|
||||
import { _setSkipUpdateExchangeRate } from '../../blue_modules/currency';
|
||||
import TransactionStatus from '../../screen/transactions/TransactionStatus';
|
||||
|
||||
// TransactionStatus renders fiat amounts via satoshiToLocalCurrency(), which
|
||||
// kicks off a real exchange-rate fetch when no rate is cached — leaving a TLS
|
||||
// socket open after the run ("Jest did not exit one second after...").
|
||||
_setSkipUpdateExchangeRate();
|
||||
|
||||
type MockStorage = {
|
||||
wallets: any[];
|
||||
txMetadata: Record<string, any>;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user