REF: more stuff converted to typescript
This commit is contained in:
parent
ef934965b6
commit
a72f874ffc
@ -1,6 +1,6 @@
|
||||
import * as bip39 from 'bip39';
|
||||
|
||||
const WORDLISTS = [
|
||||
const WORDLISTS: string[][] = [
|
||||
bip39.wordlists.english,
|
||||
bip39.wordlists.french,
|
||||
bip39.wordlists.spanish,
|
||||
@ -13,7 +13,7 @@ const WORDLISTS = [
|
||||
bip39.wordlists.portuguese,
|
||||
];
|
||||
|
||||
export function validateMnemonic(mnemonic) {
|
||||
export function validateMnemonic(mnemonic: string) {
|
||||
for (const wordlist of WORDLISTS) {
|
||||
const valid = bip39.validateMnemonic(mnemonic, wordlist);
|
||||
if (valid) return true;
|
||||
@ -2,7 +2,7 @@ import * as bip39 from 'bip39';
|
||||
import createHash from 'create-hash';
|
||||
|
||||
// partial (11 or 23 word) seed phrase
|
||||
export function generateChecksumWords(stringSeedPhrase) {
|
||||
export function generateChecksumWords(stringSeedPhrase: string) {
|
||||
const seedPhrase = stringSeedPhrase.toLowerCase().trim().split(' ');
|
||||
|
||||
if ((seedPhrase.length + 1) % 3 > 0) {
|
||||
@ -1,9 +1,9 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import PushNotificationIOS from '@react-native-community/push-notification-ios';
|
||||
import { AppState, Platform } from 'react-native';
|
||||
import { AppState, AppStateStatus, Platform } from 'react-native';
|
||||
import { getApplicationName, getSystemName, getSystemVersion, getVersion, hasGmsSync, hasHmsSync } from 'react-native-device-info';
|
||||
import { checkNotifications, requestNotifications, RESULTS } from 'react-native-permissions';
|
||||
import PushNotification from 'react-native-push-notification';
|
||||
import PushNotification, { ReceivedNotification } from 'react-native-push-notification';
|
||||
import loc from '../loc';
|
||||
import { groundControlUri } from './constants';
|
||||
import { fetch } from '../util/fetch';
|
||||
@ -15,7 +15,28 @@ export const NOTIFICATIONS_NO_AND_DONT_ASK_FLAG = 'NOTIFICATIONS_NO_AND_DONT_ASK
|
||||
let alreadyConfigured = false;
|
||||
let baseURI = groundControlUri;
|
||||
|
||||
const deepClone = obj => JSON.parse(JSON.stringify(obj));
|
||||
type TPushToken = {
|
||||
token: string;
|
||||
os: string; // its actually ('ios' | 'android'), but types for the lib are a bit more generic...
|
||||
};
|
||||
|
||||
// thats unwrapped `ReceivedNotification`, withall `data` fields inline
|
||||
type TPayload = {
|
||||
// inherited from `ReceivedNotification`:
|
||||
subText?: string;
|
||||
message?: string | object;
|
||||
foreground: boolean;
|
||||
userInteraction: boolean;
|
||||
// hopefully stuffed in `data` and uwrapped when received:
|
||||
address: string;
|
||||
txid: string;
|
||||
type: number;
|
||||
hash: string;
|
||||
};
|
||||
|
||||
function deepClone<T>(obj: T): T {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
const checkAndroidNotificationPermission = async () => {
|
||||
try {
|
||||
@ -40,7 +61,7 @@ export const checkNotificationPermissionStatus = async () => {
|
||||
|
||||
// Listener to monitor notification permission status changes while app is running
|
||||
let currentPermissionStatus = 'unavailable';
|
||||
const handleAppStateChange = async nextAppState => {
|
||||
const handleAppStateChange = async (nextAppState: AppStateStatus) => {
|
||||
if (nextAppState === 'active') {
|
||||
const isDisabledByUser = (await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG)) === 'true';
|
||||
if (!isDisabledByUser) {
|
||||
@ -115,7 +136,7 @@ export const tryToObtainPermissions = async () => {
|
||||
* @param txids {string[]}
|
||||
* @returns {Promise<object>} Response object from API rest call
|
||||
*/
|
||||
export const majorTomToGroundControl = async (addresses, hashes, txids) => {
|
||||
export const majorTomToGroundControl = async (addresses: string[], hashes: string[], txids: string[]) => {
|
||||
console.debug('majorTomToGroundControl: Starting notification registration', {
|
||||
addressCount: addresses?.length,
|
||||
hashCount: hashes?.length,
|
||||
@ -192,7 +213,7 @@ export const majorTomToGroundControl = async (addresses, hashes, txids) => {
|
||||
export const checkPermissions = async () => {
|
||||
try {
|
||||
return new Promise(function (resolve) {
|
||||
PushNotification.checkPermissions(result => {
|
||||
PushNotification.checkPermissions((result: any) => {
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
@ -208,7 +229,7 @@ export const checkPermissions = async () => {
|
||||
* @param levelAll {Boolean}
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export const setLevels = async levelAll => {
|
||||
export const setLevels = async (levelAll: boolean) => {
|
||||
const pushToken = await getPushToken();
|
||||
if (!pushToken || !pushToken.token || !pushToken.os) return;
|
||||
|
||||
@ -231,12 +252,10 @@ export const setLevels = async levelAll => {
|
||||
|
||||
if (!levelAll) {
|
||||
console.debug('Disabling notifications as user opted out...');
|
||||
await Promise.all([
|
||||
new Promise(resolve => PushNotification.removeAllDeliveredNotifications(resolve)),
|
||||
new Promise(resolve => PushNotification.setApplicationIconBadgeNumber(0, resolve)),
|
||||
new Promise(resolve => PushNotification.cancelAllLocalNotifications(resolve)),
|
||||
AsyncStorage.setItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG, 'true'),
|
||||
]);
|
||||
PushNotification.removeAllDeliveredNotifications();
|
||||
PushNotification.setApplicationIconBadgeNumber(0);
|
||||
PushNotification.cancelAllLocalNotifications();
|
||||
await AsyncStorage.setItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG, 'true');
|
||||
console.debug('Notifications disabled successfully');
|
||||
} else {
|
||||
await AsyncStorage.removeItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG); // Clear flag when enabling
|
||||
@ -246,11 +265,11 @@ export const setLevels = async levelAll => {
|
||||
}
|
||||
};
|
||||
|
||||
export const addNotification = async notification => {
|
||||
export const addNotification = async (notification: TPayload) => {
|
||||
let notifications = [];
|
||||
try {
|
||||
const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE);
|
||||
notifications = JSON.parse(stringified);
|
||||
notifications = JSON.parse(String(stringified));
|
||||
if (!Array.isArray(notifications)) notifications = [];
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@ -294,10 +313,9 @@ const postTokenConfig = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const _setPushToken = async token => {
|
||||
const _setPushToken = async (token: TPushToken) => {
|
||||
try {
|
||||
token = JSON.stringify(token);
|
||||
return await AsyncStorage.setItem(PUSH_TOKEN, token);
|
||||
return await AsyncStorage.setItem(PUSH_TOKEN, JSON.stringify(token));
|
||||
} catch (error) {
|
||||
console.error('Error setting push token:', error);
|
||||
throw error;
|
||||
@ -309,14 +327,14 @@ const _setPushToken = async token => {
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export const configureNotifications = async onProcessNotifications => {
|
||||
export const configureNotifications = async (onProcessNotifications?: () => void) => {
|
||||
if (alreadyConfigured) {
|
||||
console.debug('configureNotifications: Already configured, skipping');
|
||||
return true;
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
const handleRegistration = async token => {
|
||||
const handleRegistration = async (token: TPushToken) => {
|
||||
if (__DEV__) {
|
||||
console.debug('configureNotifications: Token received:', token);
|
||||
}
|
||||
@ -325,9 +343,11 @@ export const configureNotifications = async onProcessNotifications => {
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
const handleNotification = async notification => {
|
||||
// const handleNotification = async (notification: TPushNotification & { data: any }) => {
|
||||
const handleNotification = async (notification: Omit<ReceivedNotification, 'userInfo'>) => {
|
||||
// Deep clone to avoid modifying the original object
|
||||
const payload = deepClone({
|
||||
// @ts-ignore some missing properties hopefully will be unwrapped from `.data`
|
||||
const payload: TPayload = deepClone({
|
||||
...notification,
|
||||
...notification.data,
|
||||
});
|
||||
@ -336,9 +356,11 @@ export const configureNotifications = async onProcessNotifications => {
|
||||
const validData = Object.fromEntries(Object.entries(notification.data.data).filter(([_, value]) => value != null));
|
||||
Object.assign(payload, validData);
|
||||
}
|
||||
|
||||
// @ts-ignore stfu ts, its cleanup
|
||||
payload.data = undefined;
|
||||
|
||||
if (!payload.title && !payload.message) {
|
||||
if (!payload.subText && !payload.message) {
|
||||
console.warn('Notification missing required fields:', payload);
|
||||
return;
|
||||
}
|
||||
@ -369,7 +391,7 @@ export const configureNotifications = async onProcessNotifications => {
|
||||
PushNotification.configure({
|
||||
onRegister: handleRegistration,
|
||||
onNotification: handleNotification,
|
||||
onRegistrationError: error => {
|
||||
onRegistrationError: (error: any) => {
|
||||
console.error('Registration error:', error);
|
||||
resolve(false);
|
||||
},
|
||||
@ -392,7 +414,7 @@ export const configureNotifications = async onProcessNotifications => {
|
||||
* @param uri {string}
|
||||
* @returns {Promise<boolean>} TRUE if valid, FALSE otherwise
|
||||
*/
|
||||
export const isGroundControlUriValid = async uri => {
|
||||
export const isGroundControlUriValid = async (uri: string) => {
|
||||
try {
|
||||
const response = await fetch(`${uri}/ping`, { headers: _getHeaders() });
|
||||
const json = await response.json();
|
||||
@ -404,11 +426,10 @@ export const isGroundControlUriValid = async uri => {
|
||||
|
||||
export const isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android';
|
||||
|
||||
export const getPushToken = async () => {
|
||||
export const getPushToken = async (): Promise<TPushToken> => {
|
||||
try {
|
||||
let token = await AsyncStorage.getItem(PUSH_TOKEN);
|
||||
token = JSON.parse(token);
|
||||
return token;
|
||||
const token = await AsyncStorage.getItem(PUSH_TOKEN);
|
||||
return JSON.parse(String(token)) as TPushToken;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
AsyncStorage.removeItem(PUSH_TOKEN);
|
||||
@ -450,7 +471,7 @@ const getLevels = async () => {
|
||||
* @param txids {string[]}
|
||||
* @returns {Promise<object>} Response object from API rest call
|
||||
*/
|
||||
export const unsubscribe = async (addresses, hashes, txids) => {
|
||||
export const unsubscribe = async (addresses: string[], hashes: string[], txids: string[]) => {
|
||||
if (!Array.isArray(addresses) || !Array.isArray(hashes) || !Array.isArray(txids)) {
|
||||
throw new Error('No addresses, hashes, or txids provided');
|
||||
}
|
||||
@ -501,10 +522,10 @@ export const clearStoredNotifications = async () => {
|
||||
} catch (_) {}
|
||||
};
|
||||
|
||||
export const getDeliveredNotifications = () => {
|
||||
export const getDeliveredNotifications: () => Promise<Record<string, any>[]> = () => {
|
||||
try {
|
||||
return new Promise(resolve => {
|
||||
PushNotification.getDeliveredNotifications(notifications => resolve(notifications));
|
||||
PushNotification.getDeliveredNotifications((notifications: Record<string, any>[]) => resolve(notifications));
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting delivered notifications:', error);
|
||||
@ -516,7 +537,7 @@ export const removeDeliveredNotifications = (identifiers = []) => {
|
||||
PushNotification.removeDeliveredNotifications(identifiers);
|
||||
};
|
||||
|
||||
export const setApplicationIconBadgeNumber = badges => {
|
||||
export const setApplicationIconBadgeNumber = (badges: number) => {
|
||||
PushNotification.setApplicationIconBadgeNumber(badges);
|
||||
};
|
||||
|
||||
@ -528,7 +549,7 @@ export const getDefaultUri = () => {
|
||||
return groundControlUri;
|
||||
};
|
||||
|
||||
export const saveUri = async uri => {
|
||||
export const saveUri = async (uri: string) => {
|
||||
try {
|
||||
baseURI = uri || groundControlUri;
|
||||
await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, baseURI);
|
||||
@ -573,11 +594,10 @@ export const isNotificationsEnabled = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getStoredNotifications = async () => {
|
||||
export const getStoredNotifications = async (): Promise<TPayload[]> => {
|
||||
let notifications = [];
|
||||
try {
|
||||
const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE);
|
||||
notifications = JSON.parse(stringified);
|
||||
notifications = JSON.parse(String(await AsyncStorage.getItem(NOTIFICATIONS_STORAGE)));
|
||||
if (!Array.isArray(notifications)) notifications = [];
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
@ -594,7 +614,7 @@ export const getStoredNotifications = async () => {
|
||||
};
|
||||
|
||||
// on app launch (load module):
|
||||
export const initializeNotifications = async onProcessNotifications => {
|
||||
export const initializeNotifications = async (onProcessNotifications?: () => void) => {
|
||||
console.debug('initializeNotifications: Starting initialization');
|
||||
try {
|
||||
const noAndDontAskFlag = await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG);
|
||||
@ -411,7 +411,7 @@ class DeeplinkSchemaMatch {
|
||||
return bip21.decode(replacedUri);
|
||||
}
|
||||
|
||||
static bip21encode(address: string, options: TOptions): string {
|
||||
static bip21encode(address: string, options?: TOptions): string {
|
||||
// uppercase address if bech32 to satisfy BIP_0173
|
||||
const isBech32 = address.startsWith('bc1');
|
||||
if (isBech32) {
|
||||
|
||||
@ -1,21 +1,30 @@
|
||||
import BigNumber from 'bignumber.js';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
import assert from 'assert';
|
||||
|
||||
import * as BlueElectrum from '../blue_modules/BlueElectrum';
|
||||
import { HDSegwitBech32Wallet } from './wallets/hd-segwit-bech32-wallet';
|
||||
import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet';
|
||||
import { CreateTransactionUtxo } from './wallets/types.ts';
|
||||
import { CoinSelectOutput, CoinSelectReturnInput } from 'coinselect';
|
||||
|
||||
/**
|
||||
* Represents transaction of a BIP84 wallet.
|
||||
* Helpers for RBF, CPFP etc.
|
||||
*/
|
||||
export class HDSegwitBech32Transaction {
|
||||
private _txhex: string | null;
|
||||
private _txid: string | null;
|
||||
private _wallet: HDSegwitBech32Wallet | undefined;
|
||||
private _txDecoded: bitcoin.Transaction | undefined;
|
||||
private _remoteTx: any;
|
||||
|
||||
/**
|
||||
* @param txhex {string|null} Object is initialized with txhex
|
||||
* @param txid {string|null} If txhex not present - txid whould be present
|
||||
* @param wallet {HDSegwitBech32Wallet|null} If set - a wallet object to which transacton belongs
|
||||
*/
|
||||
constructor(txhex, txid, wallet) {
|
||||
constructor(txhex: string | null, txid: string | null, wallet: HDSegwitBech32Wallet | null) {
|
||||
if (!txhex && !txid) throw new Error('Bad arguments');
|
||||
this._txhex = txhex;
|
||||
this._txid = txid;
|
||||
@ -40,6 +49,7 @@ export class HDSegwitBech32Transaction {
|
||||
* @private
|
||||
*/
|
||||
async _fetchTxhexAndDecode() {
|
||||
assert(this._txid, 'this._txid must be a string');
|
||||
const hexes = await BlueElectrum.multiGetTransactionByTxid([this._txid], false, 10);
|
||||
this._txhex = hexes[this._txid];
|
||||
if (!this._txhex) throw new Error("Transaction can't be found in mempool");
|
||||
@ -54,6 +64,7 @@ export class HDSegwitBech32Transaction {
|
||||
*/
|
||||
async getMaxUsedSequence() {
|
||||
if (!this._txDecoded) await this._fetchTxhexAndDecode();
|
||||
assert(this._txDecoded, 'Could not fetch tx and decode');
|
||||
|
||||
let max = 0;
|
||||
for (const inp of this._txDecoded.ins) {
|
||||
@ -81,7 +92,7 @@ export class HDSegwitBech32Transaction {
|
||||
* @private
|
||||
*/
|
||||
async _fetchRemoteTx() {
|
||||
const result = await BlueElectrum.multiGetTransactionByTxid([this._txid || this._txDecoded.getId()], true);
|
||||
const result = await BlueElectrum.multiGetTransactionByTxid([this._txid || this._txDecoded!.getId()], true);
|
||||
this._remoteTx = Object.values(result)[0];
|
||||
}
|
||||
|
||||
@ -106,9 +117,9 @@ export class HDSegwitBech32Transaction {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
let found = false;
|
||||
for (const tx of this._wallet.getTransactions()) {
|
||||
if (tx.txid === (this._txid || this._txDecoded.getId())) {
|
||||
if (tx.txid === (this._txid || this._txDecoded!.getId())) {
|
||||
// its our transaction, and its spending transaction, which means we initiated it
|
||||
if (tx.value < 0) found = true;
|
||||
if (tx.value && tx.value < 0) found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
@ -125,8 +136,8 @@ export class HDSegwitBech32Transaction {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
let found = false;
|
||||
for (const tx of this._wallet.getTransactions()) {
|
||||
if (tx.txid === (this._txid || this._txDecoded.getId())) {
|
||||
if (tx.value > 0) found = true;
|
||||
if (tx.txid === (this._txid || this._txDecoded!.getId())) {
|
||||
if (tx.value && tx.value > 0) found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
@ -147,27 +158,26 @@ export class HDSegwitBech32Transaction {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
if (!this._remoteTx) await this._fetchRemoteTx();
|
||||
if (!this._txDecoded) await this._fetchTxhexAndDecode();
|
||||
assert(this._txDecoded, 'could not fetch tx and decode');
|
||||
|
||||
const prevInputs = [];
|
||||
for (const inp of this._txDecoded.ins) {
|
||||
let reversedHash = Buffer.from(inp.hash).reverse();
|
||||
reversedHash = reversedHash.toString('hex');
|
||||
prevInputs.push(reversedHash);
|
||||
prevInputs.push(Buffer.from(inp.hash).reverse().toString('hex'));
|
||||
}
|
||||
|
||||
const prevTransactions = await BlueElectrum.multiGetTransactionByTxid(prevInputs, true);
|
||||
|
||||
// fetched, now lets count how much satoshis went in
|
||||
let wentIn = 0;
|
||||
const utxos = [];
|
||||
const utxos: CreateTransactionUtxo[] = [];
|
||||
for (const inp of this._txDecoded.ins) {
|
||||
let reversedHash = Buffer.from(inp.hash).reverse();
|
||||
reversedHash = reversedHash.toString('hex');
|
||||
const reversedHash = Buffer.from(inp.hash).reverse().toString('hex');
|
||||
if (prevTransactions[reversedHash] && prevTransactions[reversedHash].vout && prevTransactions[reversedHash].vout[inp.index]) {
|
||||
let value = prevTransactions[reversedHash].vout[inp.index].value;
|
||||
value = new BigNumber(value).multipliedBy(100000000).toNumber();
|
||||
wentIn += value;
|
||||
const address = SegwitBech32Wallet.witnessToAddress(inp.witness[inp.witness.length - 1]);
|
||||
const witness = inp.witness[inp.witness.length - 1];
|
||||
const address = String(SegwitBech32Wallet.witnessToAddress(Buffer.isBuffer(witness) ? witness.toString('hex') : witness));
|
||||
utxos.push({ vout: inp.index, value, txid: reversedHash, address });
|
||||
}
|
||||
}
|
||||
@ -185,7 +195,7 @@ export class HDSegwitBech32Transaction {
|
||||
|
||||
// lets take a look at change
|
||||
let changeAmount = 0;
|
||||
const targets = [];
|
||||
const targets: { value?: number; address: string }[] = [];
|
||||
for (const outp of this._remoteTx.vout) {
|
||||
const address = outp.scriptPubKey.addresses[0];
|
||||
const value = new BigNumber(outp.value).multipliedBy(100000000).toNumber();
|
||||
@ -225,6 +235,7 @@ export class HDSegwitBech32Transaction {
|
||||
async thereAreUnknownInputsInTx() {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
if (!this._txDecoded) await this._fetchTxhexAndDecode();
|
||||
assert(this._txDecoded, 'could not fetch tx and decode');
|
||||
|
||||
const spentUtxos = this._wallet.getDerivedUtxoFromOurTransaction(true);
|
||||
for (const inp of this._txDecoded.ins) {
|
||||
@ -250,12 +261,19 @@ export class HDSegwitBech32Transaction {
|
||||
async canCancelTx() {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
if (!this._txDecoded) await this._fetchTxhexAndDecode();
|
||||
assert(this._txDecoded, 'could not fetch tx and decode');
|
||||
|
||||
if (await this.thereAreUnknownInputsInTx()) return false;
|
||||
|
||||
// if theres at least one output we dont own - we can cancel this transaction!
|
||||
for (const outp of this._txDecoded.outs) {
|
||||
if (!this._wallet.weOwnAddress(SegwitBech32Wallet.scriptPubKeyToAddress(outp.script))) return true;
|
||||
const outpScript = outp.script;
|
||||
if (
|
||||
!this._wallet.weOwnAddress(
|
||||
String(SegwitBech32Wallet.scriptPubKeyToAddress(Buffer.isBuffer(outpScript) ? outpScript.toString('hex') : outpScript)),
|
||||
)
|
||||
)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -278,7 +296,7 @@ export class HDSegwitBech32Transaction {
|
||||
* @param newFeerate {number} Sat/byte. Should be greater than previous tx feerate
|
||||
* @returns {Promise<{outputs: Array, tx: Transaction, inputs: Array, fee: Number}>}
|
||||
*/
|
||||
async createRBFcancelTx(newFeerate) {
|
||||
async createRBFcancelTx(newFeerate: any) {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
if (!this._remoteTx) await this._fetchRemoteTx();
|
||||
|
||||
@ -303,7 +321,7 @@ export class HDSegwitBech32Transaction {
|
||||
* @param newFeerate {number} Sat/byte
|
||||
* @returns {Promise<{outputs: Array, tx: Transaction, inputs: Array, fee: Number}>}
|
||||
*/
|
||||
async createRBFbumpFee(newFeerate) {
|
||||
async createRBFbumpFee(newFeerate: number) {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
if (!this._remoteTx) await this._fetchRemoteTx();
|
||||
|
||||
@ -333,7 +351,7 @@ export class HDSegwitBech32Transaction {
|
||||
* @param newFeerate {number} sat/byte
|
||||
* @returns {Promise<{outputs: Array, tx: Transaction, inputs: Array, fee: Number}>}
|
||||
*/
|
||||
async createCPFPbumpFee(newFeerate) {
|
||||
async createCPFPbumpFee(newFeerate: number) {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
if (!this._remoteTx) await this._fetchRemoteTx();
|
||||
|
||||
@ -347,16 +365,21 @@ export class HDSegwitBech32Transaction {
|
||||
const targetFeeRate = 2 * newFeerate - feeRate;
|
||||
|
||||
let add = 0;
|
||||
let tx: bitcoin.Transaction | undefined, inputs: CoinSelectReturnInput[], outputs: CoinSelectOutput[], fee: number;
|
||||
while (add <= 128) {
|
||||
// eslint-disable-next-line no-var
|
||||
var { tx, inputs, outputs, fee } = this._wallet.createTransaction(
|
||||
const createdTx = this._wallet.createTransaction(
|
||||
unconfirmedUtxos,
|
||||
[{ address: myAddress }],
|
||||
targetFeeRate + add,
|
||||
myAddress,
|
||||
HDSegwitBech32Wallet.defaultRBFSequence,
|
||||
);
|
||||
const combinedFeeRate = (oldFee + fee) / (this._txDecoded.virtualSize() + tx.virtualSize()); // avg
|
||||
tx = createdTx.tx;
|
||||
inputs = createdTx.inputs;
|
||||
outputs = createdTx.outputs;
|
||||
fee = createdTx.fee;
|
||||
assert(tx, 'tx is createCPFPbumpFee() is undefined');
|
||||
const combinedFeeRate = (oldFee + fee) / (this._txDecoded!.virtualSize() + tx.virtualSize()); // avg
|
||||
if (Math.round(combinedFeeRate) < newFeerate) {
|
||||
add *= 2;
|
||||
if (!add) add = 2;
|
||||
@ -366,6 +389,7 @@ export class HDSegwitBech32Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore stfu
|
||||
return { tx, inputs, outputs, fee };
|
||||
}
|
||||
}
|
||||
@ -5,14 +5,14 @@
|
||||
|
||||
import crypto from 'crypto';
|
||||
// uses `crypto` module under nodejs/cli and shim under RN
|
||||
// @see blue_modules/crypto.js
|
||||
// check out 'react-native-crypto' in package.json
|
||||
|
||||
/**
|
||||
* Generate cryptographically secure random bytes using native api.
|
||||
* @param {number} size The number of bytes of randomness
|
||||
* @return {Promise.<Buffer>} The random bytes
|
||||
*/
|
||||
export async function randomBytes(size) {
|
||||
export async function randomBytes(size: number): Promise<Buffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
crypto.randomBytes(size, (err, data) => {
|
||||
if (err) reject(err);
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@ -128,6 +128,7 @@
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/react": "^18.2.16",
|
||||
"@types/react-native-push-notification": "^8.1.4",
|
||||
"@types/react-test-renderer": "^19.0.0",
|
||||
"@types/wif": "^2.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
||||
@ -5696,6 +5697,13 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-native-push-notification": {
|
||||
"version": "8.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native-push-notification/-/react-native-push-notification-8.1.4.tgz",
|
||||
"integrity": "sha512-qXu/NcQ7YSk5ZveDMNKFBQkLt9W5FCde3be+h8fYbEnmvd5O+v5m318XGhh8AMPXURAV9pSB5Ads08Wc0KTS7A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react-native-vector-icons": {
|
||||
"version": "6.4.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.18.tgz",
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/react": "^18.2.16",
|
||||
"@types/react-native-push-notification": "^8.1.4",
|
||||
"@types/react-test-renderer": "^19.0.0",
|
||||
"@types/wif": "^2.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
||||
|
||||
@ -4,6 +4,7 @@ import * as bip39 from 'bip39';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
import React, { Component } from 'react';
|
||||
import { Linking, ScrollView, StyleSheet, View } from 'react-native';
|
||||
// @ts-ignore theres no type declaration for this
|
||||
import BlueCrypto from 'react-native-blue-crypto';
|
||||
import wif from 'wif';
|
||||
|
||||
@ -24,9 +25,22 @@ import presentAlert from '../../components/Alert';
|
||||
import Button from '../../components/Button';
|
||||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
import loc from '../../loc';
|
||||
import { CreateTransactionUtxo } from '../../class/wallets/types.ts';
|
||||
|
||||
const bip32 = BIP32Factory(ecc);
|
||||
|
||||
type TState = {
|
||||
isLoading?: boolean;
|
||||
isOk?: boolean;
|
||||
errorMessage?: string;
|
||||
};
|
||||
|
||||
function assertStrictEqual<T>(actual: T, expected: T, message?: string) {
|
||||
if (expected !== actual) {
|
||||
throw new Error(message || 'Assertion failed that ' + JSON.stringify(expected) + ' equals ' + JSON.stringify(actual));
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
center: {
|
||||
alignItems: 'center',
|
||||
@ -34,7 +48,9 @@ const styles = StyleSheet.create({
|
||||
});
|
||||
|
||||
export default class SelfTest extends Component {
|
||||
constructor(props) {
|
||||
state: TState;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
@ -62,7 +78,7 @@ export default class SelfTest extends Component {
|
||||
|
||||
try {
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
const uniqs = {};
|
||||
const uniqs: Record<string, 1> = {};
|
||||
const w = new SegwitP2SHWallet();
|
||||
for (let c = 0; c < 1000; c++) {
|
||||
await w.generate();
|
||||
@ -102,10 +118,10 @@ export default class SelfTest extends Component {
|
||||
// skipping RN-specific test
|
||||
}
|
||||
|
||||
let l = new LegacyWallet();
|
||||
let l: LegacyWallet | SegwitP2SHWallet = new LegacyWallet();
|
||||
l.setSecret('L4ccWrPMmFDZw4kzAKFqJNxgHANjdy6b7YKNXMwB4xac4FLF3Tov');
|
||||
assertStrictEqual(l.getAddress(), '14YZ6iymQtBVQJk6gKnLCk49UScJK7SH4M');
|
||||
let utxos = [
|
||||
let utxos: CreateTransactionUtxo[] = [
|
||||
{
|
||||
txid: 'cc44e933a094296d9fe424ad7306f16916253a3d154d52e4f1a757c18242cec4',
|
||||
vout: 0,
|
||||
@ -115,10 +131,18 @@ export default class SelfTest extends Component {
|
||||
},
|
||||
];
|
||||
|
||||
let txNew = l.createTransaction(utxos, [{ value: 90000, address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, l.getAddress());
|
||||
const txBitcoin = bitcoin.Transaction.fromHex(txNew.tx.toHex());
|
||||
let txNew = l.createTransaction(
|
||||
utxos,
|
||||
[{ value: 90000, address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }],
|
||||
1,
|
||||
String(l.getAddress()),
|
||||
0xffffffff,
|
||||
false,
|
||||
0,
|
||||
);
|
||||
const txBitcoin = bitcoin.Transaction.fromHex(txNew.tx!.toHex());
|
||||
assertStrictEqual(
|
||||
txNew.tx.toHex(),
|
||||
txNew.tx!.toHex(),
|
||||
'0200000001c4ce4282c157a7f1e4524d153d3a251669f10673ad24e49f6d2994a033e944cc000000006b48304502210091e58bd2021f2eeea8d39d7f7b053c9ccc52a747b60f1c3584ba33285e2d150602205b2d35a2536cbe157015e8c54a26f5fc350cc7c72b5ca80b9e548917993f652201210337c09b3cb889801638078fd4e6998218b28c92d338ea2602720a88847aedceb3ffffffff02905f0100000000001976a914aa381cd428a4e91327fd4434aa0a08ff131f1a5a88ac2e260000000000001976a91426e01119d265aa980390c49eece923976c218f1588ac00000000',
|
||||
);
|
||||
assertStrictEqual(txBitcoin.ins.length, 1);
|
||||
@ -148,10 +172,18 @@ export default class SelfTest extends Component {
|
||||
},
|
||||
];
|
||||
|
||||
txNew = wallet.createTransaction(utxos, [{ value: 90000, address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, wallet.getAddress());
|
||||
const tx = bitcoin.Transaction.fromHex(txNew.tx.toHex());
|
||||
txNew = wallet.createTransaction(
|
||||
utxos,
|
||||
[{ value: 90000, address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }],
|
||||
1,
|
||||
String(wallet.getAddress()),
|
||||
0xffffffff,
|
||||
false,
|
||||
0,
|
||||
);
|
||||
const tx = bitcoin.Transaction.fromHex(txNew.tx!.toHex());
|
||||
assertStrictEqual(
|
||||
txNew.tx.toHex(),
|
||||
txNew.tx!.toHex(),
|
||||
'020000000001010c86eb9013616e38b4752e56e5683e864cb34fcd7fe790bdc006b60c08446ba50000000017160014139dc70d73097f9d775f8a3280ba3e3435515641ffffffff02905f0100000000001976a914aa381cd428a4e91327fd4434aa0a08ff131f1a5a88aca73303000000000017a914749118baa93fb4b88c28909c8bf0a8202a0484f4870248304502210080545d30e3d30dff272ab11c91fd6150170b603239b48c3d56a3fa66bf240085022003762404e1b45975adc89f61ec1569fa19d6d4a8d405e060897754c489ebeade012103a5de146762f84055db3202c1316cd9008f16047f4f408c1482fdb108217eda0800000000',
|
||||
);
|
||||
assertStrictEqual(tx.ins.length, 1);
|
||||
@ -192,7 +224,7 @@ export default class SelfTest extends Component {
|
||||
//
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
const hd = new HDSegwitP2SHWallet();
|
||||
const hashmap = {};
|
||||
const hashmap: Record<string, 1> = {};
|
||||
for (let c = 0; c < 1000; c++) {
|
||||
await hd.generate();
|
||||
const secret = hd.getSecret();
|
||||
@ -323,10 +355,3 @@ export default class SelfTest extends Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function assertStrictEqual(actual, expected, message) {
|
||||
if (expected !== actual) {
|
||||
if (message) throw new Error(message);
|
||||
throw new Error('Assertion failed that ' + JSON.stringify(expected) + ' equals ' + JSON.stringify(actual));
|
||||
}
|
||||
}
|
||||
@ -15,10 +15,11 @@ console.warn = (...args) => {
|
||||
};
|
||||
|
||||
const consoleLogOrig = console.log;
|
||||
console.log = (...args) => {
|
||||
console.debug = console.log = (...args) => {
|
||||
if (
|
||||
typeof args[0] === 'string' &&
|
||||
(args[0].startsWith('updating exchange rate') ||
|
||||
args[0].startsWith('Created new currency formatter for') ||
|
||||
args[0].startsWith('begin connection') ||
|
||||
args[0].startsWith('TLS Connected to') ||
|
||||
args[0].startsWith('connected to'))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user