From 0db90a20acb63aca787c3c8522d2ec0a9a2f349b Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Tue, 2 Jan 2024 10:17:48 -0500 Subject: [PATCH 01/50] refactor(wallet): Beignet Migration Migrates a majority of the on-chain wallet logic out of Bitkit. --- __tests__/fiat.ts | 2 +- __tests__/transactions.ts | 111 - __tests__/utils/wait-for-electrum.ts | 3 +- __tests__/wallet-restore.ts | 61 +- __tests__/wallet-send.ts | 338 --- e2e/settings.e2e.js | 13 +- package.json | 3 +- src/AppOnboarded.tsx | 54 +- src/components/Money.tsx | 5 +- .../onboarding/OnboardingNavigator.tsx | 19 +- src/screens/Activity/ActivityDetail.tsx | 44 +- src/screens/Lightning/CustomSetup.tsx | 7 +- src/screens/Lightning/QuickSetup.tsx | 17 +- src/screens/Scanner/MainScanner.tsx | 18 +- src/screens/Settings/AddressViewer/index.tsx | 25 +- src/screens/Settings/Advanced/index.tsx | 16 +- .../Bitcoin/BitcoinNetworkSelection.tsx | 44 +- src/screens/Settings/ElectrumConfig/index.tsx | 29 +- .../Settings/Lightning/ChannelDetails.tsx | 1 - src/screens/Transfer/Setup.tsx | 6 +- src/screens/Wallets/BoostPrompt.tsx | 10 +- src/screens/Wallets/LNURLPay/Amount.tsx | 4 +- src/screens/Wallets/LNURLWithdraw/Amount.tsx | 4 +- src/screens/Wallets/Receive/ReceiveQR.tsx | 23 +- src/screens/Wallets/Send/Amount.tsx | 4 +- src/screens/Wallets/Send/CoinSelection.tsx | 6 +- src/screens/Wallets/Send/Contacts.tsx | 10 +- src/screens/Wallets/Send/FeeCustom.tsx | 10 +- src/screens/Wallets/Send/FeeRate.tsx | 6 +- src/screens/Wallets/Send/Result.tsx | 17 +- src/screens/Wallets/Send/ReviewAndSend.tsx | 28 +- src/screens/Wallets/Send/Tags.tsx | 10 +- src/store/actions/actions.ts | 1 + src/store/actions/wallet.ts | 1928 +++-------------- src/store/reducers/wallet.ts | 85 +- src/store/reselect/fees.ts | 2 +- src/store/reselect/wallet.ts | 4 +- src/store/shapes/blocktank.ts | 9 + src/store/shapes/lightning.ts | 2 +- src/store/shapes/settings.ts | 16 +- src/store/shapes/wallet.ts | 8 +- src/store/slices/fees.ts | 2 +- src/store/slices/settings.ts | 4 +- src/store/types/fees.ts | 12 +- src/store/types/settings.ts | 12 +- src/store/types/ui.ts | 8 +- src/store/types/wallet.ts | 29 +- src/store/utils/activity.ts | 16 +- src/store/utils/blocktank.ts | 20 +- src/store/utils/fees.ts | 17 +- src/store/utils/settings.ts | 6 +- src/store/utils/ui.ts | 19 + src/utils/activity/index.ts | 7 +- src/utils/blocktank/index.ts | 5 +- src/utils/boost.ts | 2 +- src/utils/checks.ts | 1 - src/utils/electrum/index.ts | 173 +- src/utils/lightning/index.ts | 41 +- src/utils/scanner.ts | 109 +- src/utils/slashtags/index.ts | 1 - src/utils/slashtags2/index.ts | 1 - src/utils/startup/index.ts | 48 +- src/utils/wallet/checks.ts | 16 +- src/utils/wallet/electrum.ts | 706 +----- src/utils/wallet/index.ts | 1347 ++++-------- src/utils/wallet/transactions.ts | 796 +------ yarn.lock | 78 +- 67 files changed, 1263 insertions(+), 5216 deletions(-) delete mode 100644 __tests__/transactions.ts delete mode 100644 __tests__/wallet-send.ts diff --git a/__tests__/fiat.ts b/__tests__/fiat.ts index 69b0338bb..613236fcd 100644 --- a/__tests__/fiat.ts +++ b/__tests__/fiat.ts @@ -66,7 +66,7 @@ describe('Pulls latest fiat exchange rates and checks the wallet store for valid }); it('Blocktank FX rates with default selected currency', async () => { - const res = await updateExchangeRates(); + const res = await updateExchangeRates({}); expect(res.isOk()).toEqual(true); if (res.isErr()) { diff --git a/__tests__/transactions.ts b/__tests__/transactions.ts deleted file mode 100644 index 7d7e402fd..000000000 --- a/__tests__/transactions.ts +++ /dev/null @@ -1,111 +0,0 @@ -// Fix 'getDispatch is not a function' -import '../src/store/utils/ui'; -import { - createWallet, - setupOnChainTransaction, - updateSendTransaction, - updateWallet, -} from '../src/store/actions/wallet'; -import { getSelectedWallet } from '../src/utils/wallet'; -import { EAvailableNetwork } from '../src/utils/networks'; -import { mnemonic, walletState } from './utils/dummy-wallet'; -import { createTransaction } from '../src/utils/wallet/transactions'; -import { isValidBech32mEncodedString } from '../src/utils/scanner'; - -const selectedNetwork = EAvailableNetwork.bitcoinTestnet; - -describe('On chain transactions', () => { - beforeAll(async () => { - require('../nodejs-assets/nodejs-project/main.js'); - - //Seed wallet data including utxo and transaction data - await createWallet({ - mnemonic, - addressAmount: 5, - changeAddressAmount: 5, - selectedNetwork, - }); - - updateWallet({ wallets: { wallet0: walletState } }); - - await setupOnChainTransaction({ selectedNetwork }); - }); - - it('Creates a transaction sending to a taproot address', async () => { - const selectedWallet = getSelectedWallet(); - - const taprootAddress = - 'tb1p0v28xrt49aunergvzw63pve3r3u4ylvg0k65860kdj0l26xc9dpqhqel9g'; - const { isValid, network } = isValidBech32mEncodedString(taprootAddress); - expect(isValid).toEqual(true); - expect(network).toEqual('bitcoinTestnet'); - - updateSendTransaction({ - selectedNetwork, - selectedWallet, - transaction: { - rbf: true, - outputs: [ - { - value: 10001, - index: 0, - address: taprootAddress, - }, - ], - }, - }); - - const res = await createTransaction({ - selectedNetwork, - selectedWallet, - }); - - if (res.isErr()) { - throw res.error; - } - - expect(res.value.hex).toEqual( - '020000000001020c0eab3149ba3ed7abd8f4c98eabe2cbb2b7c3590404b66ca0f01addf61ec67100000000000000000051bd848851cadb71bf34e6e0e46b0c4214c2d06ccc1d5ca0f5baefdcf86269200000000000000000000211270000000000002251207b14730d752f793c8d0c13b510b3311c79527d887db543e9f66c9ff568d82b426ecd010000000000160014669a9323418693b81d44c19da7b1fe7911b2142902473044022034623cf80029f4432bc481762587f3889efaae55b6a24c7448d5dab5223c9187022043bd46754851ac0af772f40b07b3e2b29c3ec7f4fdebc53e4d1766a12db2405201210318cb16a8e659f378002e75abe93f064c4ebcd62576bc15019281b635f96840a80247304402204c4a2f78b5908f75f8d450c0beaaaa9caada0977cdb294591fd4fdc82a2b005d022079fc0dd869ec53552fc4d9e72557c7b1e7cb5ee6f81a2f844c8ef2c6b099cc05012102bb6083f2571ecd26f68edeae341c0700463349a84b2044c271e061e813e0cd0300000000', - ); - - expect(res.value.id).toEqual( - '859fdf43d1c74cf05454dc3376b5e19cc48171a1e049646abc3b0f44343c7a8a', - ); - }); - - it('Creates an on chain transaction from the transaction store', async () => { - const selectedWallet = getSelectedWallet(); - - updateSendTransaction({ - selectedNetwork, - selectedWallet, - transaction: { - rbf: true, - outputs: [ - { - value: 10001, - index: 0, - address: '2N4Pe5o1sZKcXdYC3JVVeJKMXCmEZgVEQFa', - }, - ], - }, - }); - - const res = await createTransaction({ - selectedNetwork, - selectedWallet, - }); - - if (res.isErr()) { - throw res.error; - } - - expect(res.value.hex).toEqual( - '020000000001020c0eab3149ba3ed7abd8f4c98eabe2cbb2b7c3590404b66ca0f01addf61ec67100000000000000000051bd848851cadb71bf34e6e0e46b0c4214c2d06ccc1d5ca0f5baefdcf862692000000000000000000002112700000000000017a9147a40d326e4de19353e2bf8b3f15b395c88b2d241876ecd010000000000160014669a9323418693b81d44c19da7b1fe7911b2142902483045022100e5bf3be5b8626fc72447cee78684416b8e23b905087c8dfadb69732124fd5ba6022021fa2fe097afde801ae0495f95a11b0bfc8273804b88ed63861d72a16548593101210318cb16a8e659f378002e75abe93f064c4ebcd62576bc15019281b635f96840a802483045022100ab9a47bf65d5855e19badaf60b6e74e454b3bcc0af3c7f465b32070a06781b920220336169c94789a4a17b6e22f9a094f3775da96c82f9c4ac57ebcea8e90885bf16012102bb6083f2571ecd26f68edeae341c0700463349a84b2044c271e061e813e0cd0300000000', - ); - - expect(res.value.id).toEqual( - '4155049f78ff36c13dd9ca4c657799600115579a86bb465601ec8ca0af9f6982', - ); - }); -}); diff --git a/__tests__/utils/wait-for-electrum.ts b/__tests__/utils/wait-for-electrum.ts index f2d2b844a..6166dd13c 100644 --- a/__tests__/utils/wait-for-electrum.ts +++ b/__tests__/utils/wait-for-electrum.ts @@ -1,6 +1,7 @@ import net from 'net'; import BitcoinJsonRpc from 'bitcoin-json-rpc'; import ElectrumClient from 'electrum-client'; +import { EProtocol } from 'beignet'; export const sleep = (ms): Promise => { return new Promise((resolve) => { @@ -22,7 +23,7 @@ const initWaitForElectrumToSync = async ( false, elAddr.port, elAddr.host, - 'tcp', + EProtocol.tcp, ); electrum.subscribe.on('blockchain.headers.subscribe', (params) => { diff --git a/__tests__/wallet-restore.ts b/__tests__/wallet-restore.ts index 3716d63dc..f50e4cb98 100644 --- a/__tests__/wallet-restore.ts +++ b/__tests__/wallet-restore.ts @@ -1,28 +1,16 @@ import BitcoinJsonRpc from 'bitcoin-json-rpc'; -import * as electrum from 'rn-electrum-client/helpers'; import '../src/utils/i18n'; import store from '../src/store'; import { restoreSeed, startWalletServices } from '../src/utils/startup'; -import { - updateAddressIndexes, - updateWallet, -} from '../src/store/actions/wallet'; -import { connectToElectrum } from '../src/utils/wallet/electrum'; -import { addElectrumPeer } from '../src/store/slices/settings'; -import initWaitForElectrumToSync from './utils/wait-for-electrum'; -import { dispatch } from '../src/store/helpers'; -import { getSelectedNetwork } from '../src/utils/wallet'; -import { EAvailableNetwork } from '../src/utils/networks'; +import { EAvailableNetworks, EProtocol } from 'beignet'; +import { getOnChainWallet } from '../src/utils/wallet'; jest.setTimeout(60_000); const bitcoinURL = 'http://polaruser:polarpass@127.0.0.1:43782'; -const electrumHost = '127.0.0.1'; -const electrumPort = 60001; describe('Wallet - wallet restore and receive', () => { - let waitForElectrum; const rpc = new BitcoinJsonRpc(bitcoinURL); beforeAll(async () => { @@ -36,15 +24,6 @@ describe('Wallet - wallet restore and receive', () => { await rpc.generateToAddress(10, address); balance = await rpc.getBalance(); } - - waitForElectrum = await initWaitForElectrumToSync( - { host: electrumHost, port: electrumPort }, - bitcoinURL, - ); - }); - - afterAll(async () => { - await electrum.stop({ network: 'bitcoinRegtest' }); }); it("can restore wallet and it's balance", async () => { @@ -54,7 +33,6 @@ describe('Wallet - wallet restore and receive', () => { '1', ); await rpc.generateToAddress(1, await rpc.getNewAddress()); - await waitForElectrum(); // restore wallet let res = await restoreSeed({ @@ -65,34 +43,25 @@ describe('Wallet - wallet restore and receive', () => { throw res.error; } + const wallet = getOnChainWallet(); + expect(res.value).toEqual('Seed restored'); - expect(store.getState().wallet.selectedNetwork).toEqual('bitcoin'); + expect(wallet.network).toEqual(EAvailableNetworks.bitcoin); expect(store.getState().wallet.selectedWallet).toEqual('wallet0'); // switch to regtest - updateWallet({ selectedNetwork: EAvailableNetwork.bitcoinRegtest }); + await wallet.switchNetwork(EAvailableNetworks.bitcoinRegtest); expect(store.getState().wallet.selectedNetwork).toEqual('bitcoinRegtest'); - const selectedNetwork = getSelectedNetwork(); - - dispatch( - addElectrumPeer({ - peer: { - host: '127.0.0.1', - ssl: 60002, - tcp: 60001, - protocol: 'tcp', - }, - network: selectedNetwork, - }), - ); - - res = await connectToElectrum(); - if (res.isErr()) { - throw res.error; - } - - res = await updateAddressIndexes(); + res = await wallet.electrum.connectToElectrum({ + network: EAvailableNetworks.bitcoinRegtest, + servers: { + host: '127.0.0.1', + ssl: 60002, + tcp: 60001, + protocol: EProtocol.tcp, + }, + }); if (res.isErr()) { throw res.error; } diff --git a/__tests__/wallet-send.ts b/__tests__/wallet-send.ts deleted file mode 100644 index c1be5a878..000000000 --- a/__tests__/wallet-send.ts +++ /dev/null @@ -1,338 +0,0 @@ -import BitcoinJsonRpc from 'bitcoin-json-rpc'; -import * as electrum from 'rn-electrum-client/helpers'; - -import '../src/utils/i18n'; -import store from '../src/store'; -import { createNewWallet, startWalletServices } from '../src/utils/startup'; -import { - updateAddressIndexes, - updateWallet, - resetSendTransaction, - updateSendTransaction, - setupOnChainTransaction, -} from '../src/store/actions/wallet'; -import { connectToElectrum } from '../src/utils/wallet/electrum'; -import { - broadcastTransaction, - createTransaction, - sendMax, - updateFee, - validateTransaction, -} from '../src/utils/wallet/transactions'; -import { addElectrumPeer } from '../src/store/slices/settings'; -import { getScriptHash, getSelectedNetwork } from '../src/utils/wallet'; -import initWaitForElectrumToSync from './utils/wait-for-electrum'; -import { runStorageCheck } from '../src/utils/wallet/checks'; -import { dispatch } from '../src/store/helpers'; -import { EAvailableNetwork } from '../src/utils/networks'; - -jest.setTimeout(60_000); - -const bitcoinURL = 'http://polaruser:polarpass@127.0.0.1:43782'; -const electrumHost = '127.0.0.1'; -const electrumPort = 60001; - -describe('Wallet - new wallet, send and receive', () => { - let waitForElectrum; - const rpc = new BitcoinJsonRpc(bitcoinURL); - - beforeAll(async () => { - require('../nodejs-assets/nodejs-project/main.js'); - - // Mine at least 10 Bitcoins before each test - let balance = await rpc.getBalance(); - const address = await rpc.getNewAddress(); - - while (balance < 10) { - await rpc.generateToAddress(10, address); - balance = await rpc.getBalance(); - } - - waitForElectrum = await initWaitForElectrumToSync( - { host: electrumHost, port: electrumPort }, - bitcoinURL, - ); - }); - - afterAll(async () => { - await electrum.stop({ network: 'bitcoinRegtest' }); - }); - - it('Can generate new wallet, receive and send funds', async () => { - // Testplan: - // 0 create new wallet - // 1 create send transaction, rbf disabled - // 2 create send max transaction, rbf enabled - // 3 validate transactions and activity stores - - // create wallet - let res = await createNewWallet(); - if (res.isErr()) { - throw res.error; - } - - expect(res.value).toEqual('Wallet created'); - - // switch to regtest - updateWallet({ selectedNetwork: EAvailableNetwork.bitcoinRegtest }); - expect(store.getState().wallet.selectedNetwork).toEqual('bitcoinRegtest'); - - const selectedNetwork = getSelectedNetwork(); - - dispatch( - addElectrumPeer({ - peer: { - host: '127.0.0.1', - ssl: 60002, - tcp: 60001, - protocol: 'tcp', - }, - network: selectedNetwork, - }), - ); - - res = await connectToElectrum(); - if (res.isErr()) { - throw res.error; - } - - res = await updateAddressIndexes(); - if (res.isErr()) { - throw res.error; - } - - // rescan to generate addresses - res = await startWalletServices({ lightning: false, restore: true }); - if (res.isErr()) { - throw res.error; - } - - // run storage check - res = await runStorageCheck({ - selectedWallet: 'wallet0', - selectedNetwork: EAvailableNetwork.bitcoinRegtest, - }); - if (res.isErr()) { - throw res.error; - } - expect(res.value).toEqual('All Match'); - - const addressIndex1 = - store.getState().wallet.wallets.wallet0.addressIndex.bitcoinRegtest - .p2wpkh; - - expect(addressIndex1.path).toBe("m/84'/0'/0'/0/0"); - expect(addressIndex1.index).toBe(0); - expect(addressIndex1.address).toBeDefined(); - - // send some funds to wallet address - await rpc.sendToAddress(addressIndex1.address, '1'); - await rpc.generateToAddress(1, await rpc.getNewAddress()); - await waitForElectrum(); - - // rescan to fetch new UTXO - res = await startWalletServices({ lightning: false, restore: true }); - if (res.isErr()) { - throw res.error; - } - - const addressIndex2 = - store.getState().wallet.wallets.wallet0.addressIndex.bitcoinRegtest - .p2wpkh; - - // addressIndex should be updated - expect(addressIndex2.path).toBe("m/84'/0'/0'/0/1"); - expect(addressIndex2.index).toBe(1); - expect(addressIndex2.address).toBeDefined(); - - // ** TRANSACION 1: send 0.5 BTC to new address with fee 3 vsat/byte ** - - const receivingAddress1 = await rpc.getNewAddress(); - - // setup transaction - const res2 = await setupOnChainTransaction(); - if (res2.isErr()) { - throw res2.error; - } - - const tx11 = - store.getState().wallet.wallets.wallet0.transaction.bitcoinRegtest; - expect(tx11?.inputs?.length).toBe(1); - expect(tx11?.changeAddress).toBeDefined(); - expect(tx11?.rbf).toBe(false); - expect(tx11?.satsPerByte).toBe(2); - - // set address and amount - res = updateSendTransaction({ - transaction: { - outputs: [{ address: receivingAddress1, value: 50_000_000, index: 0 }], - }, - }); - if (res.isErr()) { - throw res.error; - } - - const tx12 = - store.getState().wallet.wallets.wallet0.transaction.bitcoinRegtest; - expect(tx12?.outputs?.[0].address).toBe(receivingAddress1); - expect(tx12?.satsPerByte).toBe(2); - - // setting fee too high should return an error - let updateFeeRes = updateFee({ - satsPerByte: 100_000_000, - transaction: tx12, - }); - // @ts-ignore - expect(updateFeeRes.error.message).toBe( - 'Unable to increase the fee. It would exceed half of your current balance.', - ); - - // set fee to 3 vsat/byte - updateFeeRes = updateFee({ satsPerByte: 3, transaction: tx12 }); - if (updateFeeRes.isErr()) { - throw updateFeeRes.error; - } - - const tx13 = - store.getState().wallet.wallets.wallet0.transaction.bitcoinRegtest; - expect(tx13.satsPerByte).toBe(3); - expect(tx13.fee).toBe(423); - - res = validateTransaction(tx13); - if (res.isErr()) { - throw res.error; - } - - const res3 = await createTransaction(); - if (res3.isErr()) { - throw res3.error; - } - expect(res3.value.id).toBeDefined(); - expect(res3.value.hex).toBeDefined(); - - // TODO: use rpc.createrawtransaction() to analyze tx hex - - res = await broadcastTransaction({ rawTx: res3.value.hex }); - if (res.isErr()) { - throw res.error; - } - - await rpc.generateToAddress(4, await rpc.getNewAddress()); - await waitForElectrum(); - - // check if tx has been confirmed and receiving address now has 0.5 BTC balance - const scriptHash1 = await getScriptHash(receivingAddress1); - const balance1 = await electrum.getAddressScriptHashBalance({ - scriptHash: scriptHash1, - network: 'bitcoinRegtest', - }); - expect(balance1.data.confirmed).toEqual(50_000_000); - - const txs1 = await electrum.getTransactions({ - txHashes: [res3.value.id], - network: 'bitcoinRegtest', - }); - expect(txs1.data[0].result.txid).toEqual(res3.value.id); - expect(txs1.data[0].result.vin.length).toEqual(1); - expect(txs1.data[0].result.vout.length).toEqual(2); - - // TODO check rbf flag = false - - // rescan to update the wallet state - res = await startWalletServices({ lightning: false, restore: true }); - if (res.isErr()) { - throw res.error; - } - expect( - store.getState().wallet.wallets.wallet0.balance.bitcoinRegtest, - ).toBeLessThan(100_000_000); - - // ** TRANSACION 2: send 0.5 BTC to new address with fee 1 vsat/byte ** - - const receivingAddress2 = await rpc.getNewAddress(); - - // setup new transaction - res = resetSendTransaction(); - if (res.isErr()) { - throw res.error; - } - const res4 = await setupOnChainTransaction(); - if (res4.isErr()) { - throw res4.error; - } - - // set address and amount - res = updateSendTransaction({ - transaction: { - outputs: [{ address: receivingAddress2, value: 50_000_000, index: 0 }], - rbf: true, - }, - }); - - res = sendMax(); - if (res.isErr()) { - throw res.error; - } - - const tx21 = - store.getState().wallet.wallets.wallet0.transaction.bitcoinRegtest; - expect(tx21.rbf).toEqual(true); - - // sending amount + fee should be equal to the balance - expect((tx21.outputs[0]?.value ?? 0) + tx21.fee).toBe( - store.getState().wallet.wallets.wallet0.balance.bitcoinRegtest, - ); - - // TODO check change fee logic while sending MAX amount - // TODO check logic for dustLimit (dust added to fee) - - res = validateTransaction(tx21); - if (res.isErr()) { - throw res.error; - } - - const res5 = await createTransaction(); - if (res5.isErr()) { - throw res5.error; - } - expect(res5.value.id).toBeDefined(); - expect(res5.value.hex).toBeDefined(); - - res = await broadcastTransaction({ rawTx: res5.value.hex }); - if (res.isErr()) { - throw res.error; - } - - await rpc.generateToAddress(4, await rpc.getNewAddress()); - await waitForElectrum(); - - // check if tx has been confirmed and receiving address now has some balance - const scriptHash2 = await getScriptHash(receivingAddress2); - const balance2 = await electrum.getAddressScriptHashBalance({ - scriptHash: scriptHash2, - network: 'bitcoinRegtest', - }); - expect(balance2.data.confirmed).toEqual(tx21?.outputs?.[0].value); - - const txs2 = await electrum.getTransactions({ - txHashes: [res5.value.id], - network: 'bitcoinRegtest', - }); - expect(txs2.data[0].result.txid).toEqual(res5.value.id); - expect(txs2.data[0].result.vin.length).toEqual(1); - expect(txs2.data[0].result.vout.length).toEqual(1); - - // TODO check rbf flag = true - - // rescan to update the wallet state - res = await startWalletServices({ lightning: false, restore: true }); - if (res.isErr()) { - throw res.error; - } - - // we spent everything, balance should be 0 - expect(store.getState().wallet.wallets.wallet0.balance.bitcoinRegtest).toBe( - 0, - ); - }); -}); diff --git a/e2e/settings.e2e.js b/e2e/settings.e2e.js index 0b4b55a50..34de6e521 100644 --- a/e2e/settings.e2e.js +++ b/e2e/settings.e2e.js @@ -10,6 +10,7 @@ import { electrumHost, electrumPort, } from './helpers'; +import { EProtocol } from 'beignet'; const __DEV__ = process.env.DEV === 'true'; @@ -426,13 +427,13 @@ d('Settings', () => { url: `${electrumHost}:${electrumPort}:t`, expectedHost: electrumHost, expectedPort: electrumPort.toString(), - expectedProtocol: 'tcp', + expectedProtocol: EProtocol.tcp, }; const umbrel2 = { url: `${electrumHost}:${electrumPort}:s`, expectedHost: electrumHost, expectedPort: electrumPort.toString(), - expectedProtocol: 'ssl', + expectedProtocol: EProtocol.ssl, }; // should detect protocol for common ports @@ -440,13 +441,13 @@ d('Settings', () => { url: `${electrumHost}:50001`, expectedHost: electrumHost, expectedPort: '50001', - expectedProtocol: 'tcp', + expectedProtocol: EProtocol.tcp, }; const noProto2 = { url: `${electrumHost}:50002`, expectedHost: electrumHost, expectedPort: '50002', - expectedProtocol: 'ssl', + expectedProtocol: EProtocol.ssl, }; // HTTP URL @@ -454,13 +455,13 @@ d('Settings', () => { url: `http://${electrumHost}:${electrumPort}`, expectedHost: electrumHost, expectedPort: electrumPort.toString(), - expectedProtocol: 'tcp', + expectedProtocol: EProtocol.tcp, }; const http2 = { url: `https://${electrumHost}:${electrumPort}`, expectedHost: electrumHost, expectedPort: electrumPort.toString(), - expectedProtocol: 'ssl', + expectedProtocol: EProtocol.ssl, }; const conns = [umbrel1, umbrel2, noProto1, noProto2, http1, http2]; diff --git a/package.json b/package.json index 332e22125..3b82ec2b5 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@sayem314/react-native-keep-awake": "1.2.2", "@shopify/react-native-skia": "0.1.225", "@synonymdev/blocktank-client": "0.0.50", - "@synonymdev/blocktank-lsp-http-client": "0.9.0", + "@synonymdev/blocktank-lsp-http-client": "^0.10.0", "@synonymdev/feeds": "2.1.1", "@synonymdev/react-native-ldk": "0.0.124", "@synonymdev/react-native-lnurl": "0.0.5", @@ -66,6 +66,7 @@ "@synonymdev/web-relay": "1.0.7", "backpack-client": "github:synonymdev/bitkit-backup-client#f08fdb28529d8a3f8bfecc789443c43b966a7581", "bech32": "2.0.0", + "beignet": "^0.0.3", "bip21": "2.0.3", "bip32": "4.0.0", "bip39": "3.0.4", diff --git a/src/AppOnboarded.tsx b/src/AppOnboarded.tsx index a924bbfb3..652dcfba3 100644 --- a/src/AppOnboarded.tsx +++ b/src/AppOnboarded.tsx @@ -6,12 +6,10 @@ import { useTranslation } from 'react-i18next'; import RootNavigator from './navigation/root/RootNavigator'; import InactivityTracker from './components/InactivityTracker'; import { startWalletServices } from './utils/startup'; -import { electrumConnection } from './utils/electrum'; import { unsubscribeFromLightningSubscriptions } from './utils/lightning'; -import i18n from './utils/i18n'; import { useAppSelector } from './hooks/redux'; import { useMigrateSlashtags2 } from './hooks/slashtags2'; -import { dispatch, getStore } from './store/helpers'; +import { dispatch } from './store/helpers'; import { updateUi } from './store/slices/ui'; import { isOnlineSelector } from './store/reselect/ui'; import { @@ -25,29 +23,13 @@ import { selectedWalletSelector, } from './store/reselect/wallet'; import { updateSettings } from './store/slices/settings'; +import { + getCustomElectrumPeers, + getOnChainWalletElectrum, +} from './utils/wallet'; +import { EAvailableNetworks } from 'beignet'; -const onElectrumConnectionChange = (isConnected: boolean): void => { - // get state fresh from store everytime - const { isConnectedToElectrum } = getStore().ui; - - if (!isConnectedToElectrum && isConnected) { - dispatch(updateUi({ isConnectedToElectrum: isConnected })); - showToast({ - type: 'success', - title: i18n.t('other:connection_restored_title'), - description: i18n.t('other:connection_restored_message'), - }); - } - - if (isConnectedToElectrum && !isConnected) { - dispatch(updateUi({ isConnectedToElectrum: isConnected })); - showToast({ - type: 'error', - title: i18n.t('other:connection_reconnect_title'), - description: i18n.t('other:connection_reconnect_msg'), - }); - } -}; +const electrum = getOnChainWalletElectrum(); const AppOnboarded = (): ReactElement => { const { t } = useTranslation('other'); @@ -81,10 +63,6 @@ const AppOnboarded = (): ReactElement => { }, []); useEffect(() => { - let electrumSubscription = electrumConnection.subscribe( - onElectrumConnectionChange, - ); - // on AppState change const appStateSubscription = AppState.addEventListener( 'change', @@ -94,10 +72,16 @@ const AppOnboarded = (): ReactElement => { appState.current.match(/inactive|background/) && nextAppState === 'active' ) { + const customPeers = getCustomElectrumPeers({ selectedNetwork }); // resubscribe to electrum connection changes - electrumSubscription = electrumConnection.subscribe( - onElectrumConnectionChange, - ); + electrum + ?.connectToElectrum({ + network: EAvailableNetworks[selectedNetwork], + servers: customPeers, + }) + .then(() => { + electrum?.startConnectionPolling(); + }); } // on App to background @@ -105,8 +89,7 @@ const AppOnboarded = (): ReactElement => { appState.current.match(/active|inactive/) && nextAppState === 'background' ) { - // resetLdk().then(); - electrumSubscription.remove(); + electrum?.disconnect(); } appState.current = nextAppState; @@ -115,10 +98,9 @@ const AppOnboarded = (): ReactElement => { return () => { appStateSubscription.remove(); - electrumSubscription.remove(); }; // onMount - }, []); + }, [selectedNetwork]); useEffect(() => { // subscribe to connection information diff --git a/src/components/Money.tsx b/src/components/Money.tsx index 2fcc87cbb..ef2909f2f 100644 --- a/src/components/Money.tsx +++ b/src/components/Money.tsx @@ -166,7 +166,10 @@ const Money = (props: IMoney): ReactElement => { {prim} {secd !== '' && ( - + {secd} )} diff --git a/src/navigation/onboarding/OnboardingNavigator.tsx b/src/navigation/onboarding/OnboardingNavigator.tsx index 5f0208dc8..6eb13bf7b 100644 --- a/src/navigation/onboarding/OnboardingNavigator.tsx +++ b/src/navigation/onboarding/OnboardingNavigator.tsx @@ -1,9 +1,5 @@ -import React, { ReactElement, useEffect } from 'react'; -import { - createNativeStackNavigator, - NativeStackNavigationProp, -} from '@react-navigation/native-stack'; -import { useAppSelector } from '../../hooks/redux'; +import React, { ReactElement } from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; import TermsOfUse from '../../screens/Onboarding/TermsOfUse'; import WelcomeScreen from '../../screens/Onboarding/Welcome'; @@ -12,11 +8,6 @@ import RestoreFromSeed from '../../screens/Onboarding/RestoreFromSeed'; import MultipleDevices from '../../screens/Onboarding/MultipleDevices'; import Passphrase from '../../screens/Onboarding/Passphrase'; import { NavigationContainer } from '../../styles/components'; -import { connectToElectrum } from '../../utils/wallet/electrum'; -import { selectedNetworkSelector } from '../../store/reselect/wallet'; - -export type OnboardingNavigationProp = - NativeStackNavigationProp; export type OnboardingStackParamList = { TermsOfUse: undefined; @@ -35,12 +26,6 @@ const navOptionHandler = { }; const OnboardingNavigator = (): ReactElement => { - const selectedNetwork = useAppSelector(selectedNetworkSelector); - - useEffect(() => { - connectToElectrum({ selectedNetwork }); - }, [selectedNetwork]); - return ( diff --git a/src/screens/Activity/ActivityDetail.tsx b/src/screens/Activity/ActivityDetail.tsx index 7f459addf..4a4dffdd8 100644 --- a/src/screens/Activity/ActivityDetail.tsx +++ b/src/screens/Activity/ActivityDetail.tsx @@ -182,29 +182,27 @@ const OnchainActivityDetail = ({ return; } - getTransactions({ txHashes: [{ tx_hash: id }], selectedNetwork }).then( - (txResponse) => { - if (txResponse.isErr()) { - showToast({ - type: 'error', - title: t('activity_error_get'), - description: t('activity_error_get_description'), - }); - return; - } - const txData = txResponse.value.data; - if (txData.length === 0) { - showToast({ - type: 'error', - title: t('activity_error_get'), - description: t('activity_error_tx_not_found'), - }); - return; - } - const data = txData[0].result; - setTxDetails(data); - }, - ); + getTransactions({ txHashes: [{ tx_hash: id }] }).then((txResponse) => { + if (txResponse.isErr()) { + showToast({ + type: 'error', + title: t('activity_error_get'), + description: t('activity_error_get_description'), + }); + return; + } + const txData = txResponse.value.data; + if (txData.length === 0) { + showToast({ + type: 'error', + title: t('activity_error_get'), + description: t('activity_error_tx_not_found'), + }); + return; + } + const data = txData[0].result; + setTxDetails(data); + }); }, [id, activityType, extended, selectedNetwork, txDetails, t]); const showBoost = useMemo(() => { diff --git a/src/screens/Lightning/CustomSetup.tsx b/src/screens/Lightning/CustomSetup.tsx index f438643d0..b4ea8612d 100644 --- a/src/screens/Lightning/CustomSetup.tsx +++ b/src/screens/Lightning/CustomSetup.tsx @@ -133,10 +133,11 @@ const CustomSetup = ({ useFocusEffect( useCallback(() => { - resetSendTransaction({ selectedNetwork, selectedWallet }); - setupOnChainTransaction({ selectedNetwork, selectedWallet }).then(); + resetSendTransaction().then(() => { + setupOnChainTransaction({}).then(); + }); refreshBlocktankInfo().then(); - }, [selectedNetwork, selectedWallet]), + }, []), ); useEffect(() => { diff --git a/src/screens/Lightning/QuickSetup.tsx b/src/screens/Lightning/QuickSetup.tsx index d583a0eab..2f59f2d62 100644 --- a/src/screens/Lightning/QuickSetup.tsx +++ b/src/screens/Lightning/QuickSetup.tsx @@ -59,6 +59,15 @@ const QuickSetup = ({ const [showNumberPad, setShowNumberPad] = useState(false); const [textFieldValue, setTextFieldValue] = useState(''); + useFocusEffect( + useCallback(() => { + resetSendTransaction().then(() => { + setupOnChainTransaction().then(); + }); + refreshBlocktankInfo().then(); + }, []), + ); + const spendingAmount = useMemo((): number => { return convertToSats(textFieldValue, unit); }, [textFieldValue, unit]); @@ -67,14 +76,6 @@ const QuickSetup = ({ lnSetupSelector(state, spendingAmount), ); - useFocusEffect( - useCallback(() => { - resetSendTransaction({ selectedNetwork, selectedWallet }); - setupOnChainTransaction({ selectedNetwork, selectedWallet }).then(); - refreshBlocktankInfo().then(); - }, [selectedNetwork, selectedWallet]), - ); - const btSpendingLimitBalancedUsd = useMemo((): string => { const { fiatWhole } = getFiatDisplayValues({ satoshis: lnSetup.btSpendingLimitBalanced, diff --git a/src/screens/Scanner/MainScanner.tsx b/src/screens/Scanner/MainScanner.tsx index deb1a56d3..5247517bd 100644 --- a/src/screens/Scanner/MainScanner.tsx +++ b/src/screens/Scanner/MainScanner.tsx @@ -48,15 +48,15 @@ const ScannerScreen = ({ return; } - resetSendTransaction({ selectedNetwork, selectedWallet }); - - processInputData({ - data, - source: 'mainScanner', - sdk, - selectedNetwork, - selectedWallet, - }).then(); + resetSendTransaction().then(() => { + processInputData({ + data, + source: 'mainScanner', + sdk, + selectedNetwork, + selectedWallet, + }).then(); + }); }; return ( diff --git a/src/screens/Settings/AddressViewer/index.tsx b/src/screens/Settings/AddressViewer/index.tsx index d2f6cf5a7..eee6c8388 100644 --- a/src/screens/Settings/AddressViewer/index.tsx +++ b/src/screens/Settings/AddressViewer/index.tsx @@ -60,7 +60,6 @@ import { enableDevOptionsSelector } from '../../../store/reselect/settings'; import { resetSendTransaction, setupOnChainTransaction, - updateAddressIndexes, updateSendTransaction, updateWallet, } from '../../../store/actions/wallet'; @@ -167,7 +166,6 @@ const EmptyComponent = memo( const getAllAddresses = async ({ config, - selectedWallet, addressAmount = ADDRESS_AMOUNT, }: { config: TAddressViewerConfig; @@ -188,7 +186,6 @@ const getAllAddresses = async ({ const keyDerivationPath = keyDerivationPathResponse.value; const generateAddressResponse = await generateAddresses({ ...config, - selectedWallet, keyDerivationPath, addressAmount, changeAddressAmount: addressAmount, @@ -682,13 +679,8 @@ const AddressViewer = ({ if (utxosLength <= 0) { return; } - resetSendTransaction({ - selectedWallet, - selectedNetwork, - }); + resetSendTransaction(); const transactionRes = await setupOnChainTransaction({ - selectedWallet, - selectedNetwork, utxos: selectedUtxosLength > 0 ? selectedUtxos : utxos, rbf: true, }); @@ -696,7 +688,6 @@ const AddressViewer = ({ return; } const receiveAddress = await getReceiveAddress({ - selectedWallet, selectedNetwork, }); if (receiveAddress.isErr()) { @@ -707,14 +698,12 @@ const AddressViewer = ({ ...transactionRes.value, outputs: [{ address: receiveAddress.value, value: 0, index: 0 }], }, - selectedWallet, - selectedNetwork, }); dispatch(updateUi({ fromAddressViewer: true })); - sendMax({ selectedWallet, selectedNetwork }); + sendMax({}); showBottomSheet('sendNavigation', { screen: 'ReviewAndSend' }); }, - [selectedNetwork, selectedUtxos, selectedWallet, utxos, dispatch], + [selectedNetwork, selectedUtxos, utxos, dispatch], ); /** @@ -811,12 +800,6 @@ const AddressViewer = ({ ldk.stop(); // Switch to new network. updateWallet({ selectedNetwork: config.selectedNetwork }); - // Generate addresses if none exist for the newly selected wallet and network. - await updateAddressIndexes({ - selectedWallet, - selectedNetwork: config.selectedNetwork, - addressType: config.addressType, - }); // Switching networks requires us to reset LDK. await setupLdk({ selectedWallet, selectedNetwork }); // Start wallet services with the newly selected network. @@ -843,7 +826,6 @@ const AddressViewer = ({ ]; if (_allAddresses.length > 0) { const utxosRes = await getAddressUtxos({ - selectedNetwork, allAddresses: _allAddresses, }); if (utxosRes.isErr()) { @@ -871,7 +853,6 @@ const AddressViewer = ({ setIsCheckingBalances(false); }, [ allAddresses, - config.addressType, config.selectedNetwork, selectedNetwork, selectedWallet, diff --git a/src/screens/Settings/Advanced/index.tsx b/src/screens/Settings/Advanced/index.tsx index c6595e689..8616f18e0 100644 --- a/src/screens/Settings/Advanced/index.tsx +++ b/src/screens/Settings/Advanced/index.tsx @@ -6,12 +6,10 @@ import { EItemType, IListData, ItemData } from '../../../components/List'; import Dialog from '../../../components/Dialog'; import SettingsView from '../SettingsView'; import { enableDevOptionsSelector } from '../../../store/reselect/settings'; -import { updateTransactions } from '../../../store/actions/wallet'; import { resetHiddenTodos } from '../../../store/slices/todos'; import { addressTypes } from '../../../store/shapes/wallet'; import { addressTypeSelector, - selectedWalletSelector, selectedNetworkSelector, } from '../../../store/reselect/wallet'; import { rescanAddresses } from '../../../utils/wallet'; @@ -23,7 +21,6 @@ const AdvancedSettings = ({ }: SettingsScreenProps<'AdvancedSettings'>): ReactElement => { const { t } = useTranslation('settings'); const dispatch = useAppDispatch(); - const selectedWallet = useAppSelector(selectedWalletSelector); const selectedNetwork = useAppSelector(selectedNetworkSelector); const selectedAddressType = useAppSelector(addressTypeSelector); const enableDevOptions = useAppSelector(enableDevOptionsSelector); @@ -105,13 +102,11 @@ const AdvancedSettings = ({ enabled: !rescanning, onPress: async (): Promise => { setRescanning(true); - await rescanAddresses({ selectedWallet, selectedNetwork }); - await updateTransactions({ - scanAllAddresses: true, - replaceStoredTransactions: true, - selectedWallet, - selectedNetwork, - }); + await rescanAddresses({}); + // await wallet.updateTransactions({ + // scanAllAddresses: true, + // replaceStoredTransactions: true, + // }); setRescanning(false); }, }, @@ -142,7 +137,6 @@ const AdvancedSettings = ({ rescanning, enableDevOptions, navigation, - selectedWallet, selectedNetwork, t, ]); diff --git a/src/screens/Settings/Bitcoin/BitcoinNetworkSelection.tsx b/src/screens/Settings/Bitcoin/BitcoinNetworkSelection.tsx index 4549d65c0..b4411be44 100644 --- a/src/screens/Settings/Bitcoin/BitcoinNetworkSelection.tsx +++ b/src/screens/Settings/Bitcoin/BitcoinNetworkSelection.tsx @@ -1,27 +1,15 @@ import React, { memo, ReactElement, useMemo } from 'react'; import { useAppSelector } from '../../../hooks/redux'; import { useTranslation } from 'react-i18next'; -import { ldk } from '@synonymdev/react-native-ldk'; import { EItemType, IListData } from '../../../components/List'; import SettingsView from '../SettingsView'; -import { - updateAddressIndexes, - updateWallet, -} from '../../../store/actions/wallet'; +import { updateWallet } from '../../../store/actions/wallet'; import { dispatch } from '../../../store/helpers'; -import { updateActivityList } from '../../../store/utils/activity'; import { resetActivityState } from '../../../store/slices/activity'; -import { updateOnchainFeeEstimates } from '../../../store/utils/fees'; import { selectedNetworkSelector } from '../../../store/reselect/wallet'; -import { connectToElectrum } from '../../../utils/wallet/electrum'; -import { startWalletServices } from '../../../utils/startup'; -import { setupLdk } from '../../../utils/lightning'; import { networkLabels } from '../../../utils/networks'; -import { - getCurrentWallet, - getSelectedAddressType, -} from '../../../utils/wallet'; +import { switchNetwork } from '../../../utils/wallet'; import { SettingsScreenProps } from '../../../navigation/types'; const BitcoinNetworkSelection = ({ @@ -40,36 +28,12 @@ const BitcoinNetworkSelection = ({ type: EItemType.button, onPress: async (): Promise => { navigation.goBack(); - await ldk.stop(); + //await ldk.stop(); // Wipe existing activity dispatch(resetActivityState()); // Switch to new network. updateWallet({ selectedNetwork: network.id }); - // Grab the selectedWallet. - const { selectedWallet } = getCurrentWallet({ - selectedNetwork: network.id, - }); - const addressType = getSelectedAddressType({ - selectedNetwork: network.id, - selectedWallet, - }); - // Connect to a Electrum Server on the network - await connectToElectrum({ selectedNetwork: network.id }); - // Generate addresses if none exist for the newly selected wallet and network. - await updateAddressIndexes({ - selectedWallet, - selectedNetwork: network.id, - addressType, - }); - // Switching networks requires us to reset LDK. - await setupLdk({ selectedWallet, selectedNetwork }); - // Start wallet services with the newly selected network. - await startWalletServices({ selectedNetwork: network.id }); - await updateOnchainFeeEstimates({ - selectedNetwork: network.id, - forceUpdate: true, - }); - updateActivityList(); + await switchNetwork(network.id); }, }; }), diff --git a/src/screens/Settings/ElectrumConfig/index.tsx b/src/screens/Settings/ElectrumConfig/index.tsx index a677496a5..9fa50aedb 100644 --- a/src/screens/Settings/ElectrumConfig/index.tsx +++ b/src/screens/Settings/ElectrumConfig/index.tsx @@ -11,7 +11,6 @@ import { ScanIcon } from '../../../styles/icons'; import { useAppDispatch, useAppSelector } from '../../../hooks/redux'; import { updateUi } from '../../../store/slices/ui'; import { addElectrumPeer } from '../../../store/slices/settings'; -import { TProtocol } from '../../../store/types/settings'; import { selectedNetworkSelector } from '../../../store/reselect/wallet'; import { customElectrumPeersSelector } from '../../../store/reselect/settings'; import { defaultElectrumPeer } from '../../../store/shapes/settings'; @@ -28,12 +27,13 @@ import { import { showToast } from '../../../utils/notifications'; import { getConnectedPeer, IPeerData } from '../../../utils/wallet/electrum'; import type { SettingsScreenProps } from '../../../navigation/types'; +import { EProtocol } from 'beignet'; -type RadioButtonItem = { label: string; value: TProtocol }; +type RadioButtonItem = { label: string; value: EProtocol }; const radioButtons: RadioButtonItem[] = [ - { label: 'TCP', value: 'tcp' }, - { label: 'TLS', value: 'ssl' }, + { label: 'TCP', value: EProtocol.tcp }, + { label: 'TLS', value: EProtocol.ssl }, ]; const isValidURL = (data: string): boolean => { @@ -93,18 +93,17 @@ const ElectrumConfig = ({ const [connectedPeer, setConnectedPeer] = useState(); const [loading, setLoading] = useState(false); const [host, setHost] = useState(savedPeer.host); - const [protocol, setProtocol] = useState(savedPeer.protocol); + const [protocol, setProtocol] = useState(savedPeer.protocol); const [port, setPort] = useState( savedPeer[savedPeer.protocol].toString(), ); useEffect(() => { getAndUpdateConnectedPeer(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const getAndUpdateConnectedPeer = async (): Promise => { - const peerInfo = await getConnectedPeer(selectedNetwork); + const peerInfo = await getConnectedPeer(); if (peerInfo.isOk()) { setConnectedPeer({ host: peerInfo.value.host, @@ -119,7 +118,7 @@ const ElectrumConfig = ({ const connectAndAddPeer = async (peerData: { host: string; port: string; - protocol: TProtocol; + protocol: EProtocol; }): Promise => { setLoading(true); @@ -134,8 +133,8 @@ const ElectrumConfig = ({ return; } const defaultPorts = { - ssl: getDefaultPort(selectedNetwork, 'ssl'), - tcp: getDefaultPort(selectedNetwork, 'tcp'), + ssl: getDefaultPort(selectedNetwork, EProtocol.ssl), + tcp: getDefaultPort(selectedNetwork, EProtocol.tcp), }; const connectData = { ...defaultPorts, @@ -144,8 +143,8 @@ const ElectrumConfig = ({ [protocol]: Number(peerData.port), }; const connectResponse = await connectToElectrum({ - peer: connectData, selectedNetwork, + customPeers: [connectData], }); dispatch( @@ -192,11 +191,11 @@ const ElectrumConfig = ({ if (!data.startsWith('http://') && !data.startsWith('https://')) { let [_host, _port, shortProtocol] = data.split(':'); - let _protocol: TProtocol = 'tcp'; + let _protocol = EProtocol.tcp; if (shortProtocol) { // Support Umbrel connection URL format - _protocol = shortProtocol === 's' ? 'ssl' : 'tcp'; + _protocol = shortProtocol === 's' ? EProtocol.ssl : EProtocol.tcp; } else { // Prefix protocol for common ports if missing _protocol = getProtocolForPort(_port, selectedNetwork); @@ -213,7 +212,7 @@ const ElectrumConfig = ({ connectData = { host: url.hostname, port: url.port, - protocol: url.protocol === 'https:' ? 'ssl' : 'tcp', + protocol: url.protocol === 'https:' ? EProtocol.ssl : EProtocol.tcp, }; // Add default port back in @@ -316,7 +315,7 @@ const ElectrumConfig = ({ data={radioButtons} value={protocol} onPress={(value): void => { - const radioValue = value as TProtocol; + const radioValue = value as EProtocol; setProtocol(radioValue); // Toggle the port if the protocol changes and the default ports are still set. diff --git a/src/screens/Settings/Lightning/ChannelDetails.tsx b/src/screens/Settings/Lightning/ChannelDetails.tsx index 65b6bf1b6..3cae0d931 100644 --- a/src/screens/Settings/Lightning/ChannelDetails.tsx +++ b/src/screens/Settings/Lightning/ChannelDetails.tsx @@ -261,7 +261,6 @@ const ChannelDetails = ({ } getTransactions({ txHashes: [{ tx_hash: channel.funding_txid }], - selectedNetwork, }).then((txResponse) => { if (txResponse.isErr()) { return; diff --git a/src/screens/Transfer/Setup.tsx b/src/screens/Transfer/Setup.tsx index 45f2713b4..3246ac3e5 100644 --- a/src/screens/Transfer/Setup.tsx +++ b/src/screens/Transfer/Setup.tsx @@ -59,10 +59,10 @@ const Setup = ({ navigation }: TransferScreenProps<'Setup'>): ReactElement => { useFocusEffect( useCallback(() => { - resetSendTransaction({ selectedNetwork, selectedWallet }); - setupOnChainTransaction({ selectedNetwork, selectedWallet }).then(); + resetSendTransaction(); + setupOnChainTransaction({}).then(); refreshBlocktankInfo().then(); - }, [selectedNetwork, selectedWallet]), + }, []), ); const spendingAmount = useMemo((): number => { diff --git a/src/screens/Wallets/BoostPrompt.tsx b/src/screens/Wallets/BoostPrompt.tsx index 11ea66178..146ef8ba9 100644 --- a/src/screens/Wallets/BoostPrompt.tsx +++ b/src/screens/Wallets/BoostPrompt.tsx @@ -17,7 +17,6 @@ import { canBoost, setupBoost, updateFee, - validateTransaction, } from '../../utils/wallet/transactions'; import { showToast } from '../../utils/notifications'; import { btcToSats } from '../../utils/conversion'; @@ -39,6 +38,7 @@ import { selectedWalletSelector, transactionSelector, } from '../../store/reselect/wallet'; +import { validateTransaction } from 'beignet'; const BoostForm = ({ activityItem, @@ -87,7 +87,7 @@ const BoostForm = ({ })(); return (): void => { - resetSendTransaction({ selectedNetwork, selectedWallet }); + resetSendTransaction(); }; }, [activityItem.id, selectedNetwork, selectedWallet, dispatch]); @@ -97,8 +97,6 @@ const BoostForm = ({ const res = updateFee({ satsPerByte: recommendedFee, transaction, - selectedNetwork, - selectedWallet, }); if (res.isErr()) { showToast({ @@ -117,8 +115,6 @@ const BoostForm = ({ const onDecreaseValue = (): void => { const res = adjustFee({ - selectedNetwork, - selectedWallet, adjustBy: -1, transaction, }); @@ -133,8 +129,6 @@ const BoostForm = ({ const onIncreaseValue = (): void => { const res = adjustFee({ - selectedNetwork, - selectedWallet, adjustBy: 1, transaction, }); diff --git a/src/screens/Wallets/LNURLPay/Amount.tsx b/src/screens/Wallets/LNURLPay/Amount.tsx index 6ac719e70..eee8583e3 100644 --- a/src/screens/Wallets/LNURLPay/Amount.tsx +++ b/src/screens/Wallets/LNURLPay/Amount.tsx @@ -73,8 +73,8 @@ const Amount = ({ const onMaxAmount = useCallback((): void => { const result = getNumberPadText(maxSendable, unit); setText(result); - sendMax({ selectedWallet, selectedNetwork }); - }, [maxSendable, unit, selectedWallet, selectedNetwork]); + sendMax({}); + }, [maxSendable, unit]); const onError = (): void => { setError(true); diff --git a/src/screens/Wallets/LNURLWithdraw/Amount.tsx b/src/screens/Wallets/LNURLWithdraw/Amount.tsx index 0cf14101d..6bbd6f639 100644 --- a/src/screens/Wallets/LNURLWithdraw/Amount.tsx +++ b/src/screens/Wallets/LNURLWithdraw/Amount.tsx @@ -73,8 +73,8 @@ const Amount = ({ const onMaxAmount = useCallback((): void => { const result = getNumberPadText(maxWithdrawable, unit); setText(result); - sendMax({ selectedWallet, selectedNetwork }); - }, [maxWithdrawable, unit, selectedWallet, selectedNetwork]); + sendMax({}); + }, [maxWithdrawable, unit]); const onError = (): void => { setError(true); diff --git a/src/screens/Wallets/Receive/ReceiveQR.tsx b/src/screens/Wallets/Receive/ReceiveQR.tsx index 4b19dfafa..aaedf406d 100644 --- a/src/screens/Wallets/Receive/ReceiveQR.tsx +++ b/src/screens/Wallets/Receive/ReceiveQR.tsx @@ -62,6 +62,7 @@ import { receiveSelector } from '../../../store/reselect/receive'; import { ReceiveScreenProps } from '../../../navigation/types'; import { isGeoBlockedSelector } from '../../../store/reselect/user'; import { accountVersionSelector } from '../../../store/reselect/lightning'; +import { getWalletStore } from '../../../store/helpers'; type Slide = () => ReactElement; @@ -83,6 +84,7 @@ const ReceiveQR = ({ const dispatch = useAppDispatch(); const selectedWallet = useAppSelector(selectedWalletSelector); const selectedNetwork = useAppSelector(selectedNetworkSelector); + const selectedAddressType = useAppSelector(addressTypeSelector); const addressType = useAppSelector(addressTypeSelector); const isGeoBlocked = useAppSelector(isGeoBlockedSelector); const accountVersion = useAppSelector(accountVersionSelector); @@ -146,8 +148,6 @@ const ReceiveQR = ({ if (amount > 0) { console.info('getting fresh address'); const response = await generateNewReceiveAddress({ - selectedNetwork, - selectedWallet, addressType, }); if (response.isOk()) { @@ -156,21 +156,31 @@ const ReceiveQR = ({ } } else { const response = await getReceiveAddress({ - selectedNetwork, - selectedWallet, addressType, }); if (response.isOk()) { console.info(`reusing address ${response.value}`); setReceiveAddress(response.value); + } else { + try { + const address = + getWalletStore().wallets[selectedWallet]?.addressIndex[ + selectedNetwork + ][selectedAddressType]?.address; + if (address) { + console.info(`reusing address ${address}`); + setReceiveAddress(address); + } + } catch {} } } }, [ - amount, receiveNavigationIsOpen, + amount, selectedNetwork, selectedWallet, addressType, + selectedAddressType, ]); const setInvoiceDetails = useCallback(async (): Promise => { @@ -181,7 +191,8 @@ const ReceiveQR = ({ setLoading(true); } // Gives the modal animation time to start. - await Promise.all([getLightningInvoice(), getAddress()]); + getLightningInvoice().then(); + await Promise.all([getAddress()]); await sleep(200); setLoading(false); }, [getAddress, getLightningInvoice, loading, receiveNavigationIsOpen]); diff --git a/src/screens/Wallets/Send/Amount.tsx b/src/screens/Wallets/Send/Amount.tsx index a30eb4c46..54505860a 100644 --- a/src/screens/Wallets/Send/Amount.tsx +++ b/src/screens/Wallets/Send/Amount.tsx @@ -120,8 +120,8 @@ const Amount = ({ navigation }: SendScreenProps<'Amount'>): ReactElement => { const onMaxAmount = useCallback((): void => { const result = getNumberPadText(availableAmount, unit); setText(result); - sendMax({ selectedWallet, selectedNetwork }); - }, [availableAmount, unit, selectedWallet, selectedNetwork]); + sendMax({}); + }, [availableAmount, unit]); const onError = (): void => { setError(true); diff --git a/src/screens/Wallets/Send/CoinSelection.tsx b/src/screens/Wallets/Send/CoinSelection.tsx index b5310f2aa..2d7732e2a 100644 --- a/src/screens/Wallets/Send/CoinSelection.tsx +++ b/src/screens/Wallets/Send/CoinSelection.tsx @@ -153,7 +153,7 @@ const CoinSelection = ({ const key = getUtxoKey(utxo); const isEnabled = inputKeys.includes(key); if (!isEnabled) { - addTxInput({ input: utxo, selectedWallet, selectedNetwork }); + addTxInput({ input: utxo }); } }); setAutoSelectionEnabled(true); @@ -182,9 +182,9 @@ const CoinSelection = ({ const isEnabled = inputKeys.includes(key); const onPress = (): void => { if (isEnabled) { - removeTxInput({ input: item, selectedWallet, selectedNetwork }); + removeTxInput({ input: item }); } else { - addTxInput({ input: item, selectedWallet, selectedNetwork }); + addTxInput({ input: item }); } if (autoSelectionEnabled) { setAutoSelectionEnabled(false); diff --git a/src/screens/Wallets/Send/Contacts.tsx b/src/screens/Wallets/Send/Contacts.tsx index 3b58aff11..b59ed2e4e 100644 --- a/src/screens/Wallets/Send/Contacts.tsx +++ b/src/screens/Wallets/Send/Contacts.tsx @@ -31,14 +31,8 @@ const Contacts = ({ const handlePress = async (contact: IContactRecord): Promise => { // make sure we start with a clean transaction state - resetSendTransaction({ - selectedWallet, - selectedNetwork, - }); - await setupOnChainTransaction({ - selectedNetwork, - selectedWallet, - }); + await resetSendTransaction(); + await setupOnChainTransaction({}); setLoading(true); const res = await processInputData({ data: contact.url, diff --git a/src/screens/Wallets/Send/FeeCustom.tsx b/src/screens/Wallets/Send/FeeCustom.tsx index 55484c40c..3efd526e3 100644 --- a/src/screens/Wallets/Send/FeeCustom.tsx +++ b/src/screens/Wallets/Send/FeeCustom.tsx @@ -14,19 +14,13 @@ import { handleNumberPadPress } from '../../../utils/numberpad'; import { showToast } from '../../../utils/notifications'; import { useAppSelector } from '../../../hooks/redux'; import useDisplayValues from '../../../hooks/displayValues'; -import { - selectedNetworkSelector, - selectedWalletSelector, - transactionSelector, -} from '../../../store/reselect/wallet'; +import { transactionSelector } from '../../../store/reselect/wallet'; import type { SendScreenProps } from '../../../navigation/types'; const FeeCustom = ({ navigation, }: SendScreenProps<'FeeCustom'>): ReactElement => { const { t } = useTranslation('wallet'); - const selectedWallet = useAppSelector(selectedWalletSelector); - const selectedNetwork = useAppSelector(selectedNetworkSelector); const transaction = useAppSelector(transactionSelector); const [feeRate, setFeeRate] = useState(transaction.satsPerByte); @@ -56,8 +50,6 @@ const FeeCustom = ({ const onContinue = (): void => { const res = updateFee({ satsPerByte: Number(feeRate), - selectedWallet, - selectedNetwork, transaction, }); if (res.isErr()) { diff --git a/src/screens/Wallets/Send/FeeRate.tsx b/src/screens/Wallets/Send/FeeRate.tsx index 365488324..9d5c2d88b 100644 --- a/src/screens/Wallets/Send/FeeRate.tsx +++ b/src/screens/Wallets/Send/FeeRate.tsx @@ -7,7 +7,6 @@ import BottomSheetNavigationHeader from '../../../components/BottomSheetNavigati import GradientView from '../../../components/GradientView'; import Button from '../../../components/Button'; -import { EFeeId } from '../../../store/types/fees'; import { useBalance } from '../../../hooks/wallet'; import { useAppSelector } from '../../../hooks/redux'; import { showToast } from '../../../utils/notifications'; @@ -24,6 +23,7 @@ import { transactionSelector, } from '../../../store/reselect/wallet'; import SafeAreaInset from '../../../components/SafeAreaInset'; +import { EFeeId } from 'beignet'; const FeeRate = ({ navigation }: SendScreenProps<'FeeRate'>): ReactElement => { const { t } = useTranslation('wallet'); @@ -59,8 +59,6 @@ const FeeRate = ({ navigation }: SendScreenProps<'FeeRate'>): ReactElement => { const _updateFee = useCallback( (feeId: EFeeId, _satsPerByte: number) => { const res = updateFee({ - selectedWallet, - selectedNetwork, transaction, satsPerByte: _satsPerByte, selectedFeeId: feeId, @@ -73,7 +71,7 @@ const FeeRate = ({ navigation }: SendScreenProps<'FeeRate'>): ReactElement => { }); } }, - [selectedNetwork, selectedWallet, transaction, t], + [transaction, t], ); const displayFast = useMemo(() => { diff --git a/src/screens/Wallets/Send/Result.tsx b/src/screens/Wallets/Send/Result.tsx index 0dff8a9a0..600aea25a 100644 --- a/src/screens/Wallets/Send/Result.tsx +++ b/src/screens/Wallets/Send/Result.tsx @@ -134,14 +134,8 @@ const Result = ({ const handleRetry = async (): Promise => { if (transaction.lightningInvoice && transaction.slashTagsUrl) { setLoading(true); - resetSendTransaction({ - selectedWallet, - selectedNetwork, - }); - await setupOnChainTransaction({ - selectedNetwork, - selectedWallet, - }); + await resetSendTransaction(); + await setupOnChainTransaction({}); const res = await processInputData({ data: transaction.slashTagsUrl, source: 'send', @@ -170,11 +164,8 @@ const Result = ({ If unable to properly create a valid transaction for any reason, reset the tx state as done below. */ //If unable to broadcast for any reason, reset the transaction object and try again. - resetSendTransaction({ selectedWallet, selectedNetwork }); - await setupOnChainTransaction({ - selectedWallet, - selectedNetwork, - }); + await resetSendTransaction(); + await setupOnChainTransaction({}); // The transaction was reset due to an unknown broadcast or construction error. // Navigate back to the main send screen to re-enter information. navigation.navigate('Recipient'); diff --git a/src/screens/Wallets/Send/ReviewAndSend.tsx b/src/screens/Wallets/Send/ReviewAndSend.tsx index 9a7b1400c..41a2dee37 100644 --- a/src/screens/Wallets/Send/ReviewAndSend.tsx +++ b/src/screens/Wallets/Send/ReviewAndSend.tsx @@ -33,10 +33,8 @@ import { createTransaction, getTotalFee, getTransactionOutputValue, - validateTransaction, } from '../../../utils/wallet/transactions'; import { - updateWalletBalance, setupFeeForOnChainTransaction, removeTxTag, } from '../../../store/actions/wallet'; @@ -81,6 +79,7 @@ import { updateLastPaidContacts } from '../../../store/slices/slashtags'; import { truncate } from '../../../utils/helpers'; import AmountToggle from '../../../components/AmountToggle'; import LightningSyncing from '../../../components/LightningSyncing'; +import { validateTransaction } from 'beignet'; const Section = memo( ({ @@ -172,7 +171,6 @@ const ReviewAndSend = ({ // TODO: add support for multiple outputs const outputIndex = 0; - const transactionTotal = amount + transaction.fee; const selectedFeeId = transaction.selectedFeeId; const satsPerByte = transaction.satsPerByte; const address = transaction?.outputs[outputIndex]?.address ?? ''; @@ -311,13 +309,6 @@ const ReviewAndSend = ({ return; } - //Temporarily update the balance until the Electrum mempool catches up in a few seconds. - updateWalletBalance({ - balance: onChainBalance - transactionTotal, - selectedWallet, - selectedNetwork, - }); - // save tags to metadata dispatch(updateMetaTxTags({ txId: rawTx.id, tags: transaction.tags })); // save Slashtags contact to metadata @@ -338,18 +329,7 @@ const ReviewAndSend = ({ } navigation.navigate('Result', { success: true, txId: rawTx.id }); - }, [ - onChainBalance, - rawTx, - selectedNetwork, - selectedWallet, - transactionTotal, - _onError, - navigation, - transaction, - dispatch, - t, - ]); + }, [rawTx, selectedNetwork, _onError, navigation, transaction, dispatch, t]); useEffect(() => { if (rawTx) { @@ -446,7 +426,7 @@ const ReviewAndSend = ({ const handleTagRemove = useCallback( (tag: string) => { - const res = removeTxTag({ tag, selectedNetwork, selectedWallet }); + const res = removeTxTag({ tag }); if (res.isErr()) { console.log(res.error.message); showToast({ @@ -456,7 +436,7 @@ const ReviewAndSend = ({ }); } }, - [selectedWallet, selectedNetwork, t], + [t], ); const onSwipeToPay = useCallback(async () => { diff --git a/src/screens/Wallets/Send/Tags.tsx b/src/screens/Wallets/Send/Tags.tsx index 58677dd02..e92fc0297 100644 --- a/src/screens/Wallets/Send/Tags.tsx +++ b/src/screens/Wallets/Send/Tags.tsx @@ -15,25 +15,19 @@ import { useAppDispatch, useAppSelector } from '../../../hooks/redux'; import { addTxTag } from '../../../store/actions/wallet'; import { addLastUsedTag } from '../../../store/slices/metadata'; import { lastUsedTagsSelector } from '../../../store/reselect/metadata'; -import { - selectedNetworkSelector, - selectedWalletSelector, -} from '../../../store/reselect/wallet'; import type { SendScreenProps } from '../../../navigation/types'; const Tags = ({ navigation }: SendScreenProps<'Tags'>): ReactElement => { const { t } = useTranslation('wallet'); const [text, setText] = useState(''); const dispatch = useAppDispatch(); - const selectedWallet = useAppSelector(selectedWalletSelector); - const selectedNetwork = useAppSelector(selectedNetworkSelector); const lastUsedTags = useAppSelector(lastUsedTagsSelector); const handleSubmit = async (): Promise => { if (text.length === 0) { return; } - const res = addTxTag({ tag: text, selectedNetwork, selectedWallet }); + const res = addTxTag({ tag: text }); if (res.isErr()) { console.log(res.error.message); showToast({ @@ -50,7 +44,7 @@ const Tags = ({ navigation }: SendScreenProps<'Tags'>): ReactElement => { }; const handleTagChoose = async (tag: string): Promise => { - const res = addTxTag({ tag, selectedNetwork, selectedWallet }); + const res = addTxTag({ tag }); if (res.isErr()) { console.log(res.error.message); showToast({ diff --git a/src/store/actions/actions.ts b/src/store/actions/actions.ts index d0c32056f..4fb31f72a 100644 --- a/src/store/actions/actions.ts +++ b/src/store/actions/actions.ts @@ -4,6 +4,7 @@ const actions = { // Wallet UPDATE_WALLET: 'UPDATE_WALLET', + UPDATE_WALLET_DATA: 'UPDATE_WALLET_DATA', UPDATE_HEADER: 'UPDATE_HEADER', UPDATE_WALLET_BALANCE: 'UPDATE_WALLET_BALANCE', CREATE_WALLET: 'CREATE_WALLET', diff --git a/src/store/actions/wallet.ts b/src/store/actions/wallet.ts index 59153e3d2..18d8eddd8 100644 --- a/src/store/actions/wallet.ts +++ b/src/store/actions/wallet.ts @@ -2,89 +2,55 @@ import { err, ok, Result } from '@synonymdev/result'; import actions from './actions'; import { - EPaymentType, - IAddresses, + EAddressType, + EBoostType, IAddress, ICreateWallet, - IFormattedTransaction, IFormattedTransactions, IKeyDerivationPath, - ISendTransaction, IUtxo, - EAddressType, - IBoostedTransactions, - EBoostType, - TWalletName, + IWallets, IWalletStore, - TProcessUnconfirmedTransactions, + TWalletName, } from '../types/wallet'; import { blockHeightToConfirmations, - confirmationsToBlockHeight, createDefaultWallet, - formatTransactions, - generateAddresses, - getAddressIndexInfo, getCurrentWallet, - getGapLimit, - getKeyDerivationPathObject, - getNextAvailableAddress, - getSelectedAddressType, + getOnChainWallet, + getOnChainWalletTransaction, getSelectedNetwork, getSelectedWallet, - getUnconfirmedTransactions, - ITransaction, - ITxHash, refreshWallet, - removeDuplicateAddresses, - rescanAddresses, } from '../../utils/wallet'; import { dispatch, - getBlocktankStore, getFeesStore, getSettingsStore, getWalletStore, } from '../helpers'; import { EAvailableNetwork } from '../../utils/networks'; -import { objectKeys } from '../../utils/objectKeys'; -import { - getOnchainTransactionData, - getTotalFee, - updateFee, -} from '../../utils/wallet/transactions'; -import { - IGenerateAddresses, - IGenerateAddressesResponse, -} from '../../utils/types'; -import { getExchangeRates } from '../../utils/exchange-rate'; -import { objectsMatch, removeKeyFromObject } from '../../utils/helpers'; -import { - getAddressHistory, - getTransactions, - getUtxos, - transactionExists, -} from '../../utils/wallet/electrum'; -import { EFeeId } from '../types/fees'; -import { ETransactionSpeed } from '../types/settings'; +import { removeKeyFromObject } from '../../utils/helpers'; import { IHeader } from '../../utils/types/electrum'; -import { - GAP_LIMIT, - GENERATE_ADDRESS_AMOUNT, -} from '../../utils/wallet/constants'; -import { getBoostedTransactionParents } from '../../utils/boost'; -import { updateSlashPayConfig2 } from '../../utils/slashtags2'; -import { - addressTypes, - getDefaultWalletShape, - TAddressIndexInfo, -} from '../shapes/wallet'; +import { addressTypes, getDefaultWalletShape } from '../shapes/wallet'; import { TGetImpactedAddressesRes } from '../types/checks'; -import { showToast } from '../../utils/notifications'; import { getFakeTransaction } from '../../utils/wallet/testing'; -import { updateOnchainActivityItem } from '../slices/activity'; import { updateActivityList } from '../utils/activity'; -import i18n from '../../utils/i18n'; +import { + EAvailableNetworks, + EFeeId, + getDefaultWalletData, + getExchangeRates, + getStorageKeyValues, + IBoostedTransaction, + IExchangeRates, + IOnchainFees, + ISendTransaction, + IWalletData, + TSetupTransactionResponse, +} from 'beignet'; +import { ETransactionSpeed } from '../types/settings'; +import { updateOnchainFeeEstimates } from '../utils/fees'; export const updateWallet = ( payload: Partial, @@ -100,19 +66,15 @@ export const updateWallet = ( * Creates and stores a newly specified wallet. * @param {string} mnemonic * @param {string} [wallet] - * @param {number} [addressAmount] - * @param {number} [changeAddressAmount] * @param {string} [bip39Passphrase] * @param {Partial} [addressTypesToCreate] * @return {Promise>} */ export const createWallet = async ({ - walletName = getDefaultWalletShape().id, + walletName = 'wallet0', mnemonic, bip39Passphrase = '', restore = false, - addressAmount = GENERATE_ADDRESS_AMOUNT, - changeAddressAmount = GENERATE_ADDRESS_AMOUNT, addressTypesToCreate, }: ICreateWallet): Promise> => { if (!addressTypesToCreate) { @@ -124,8 +86,6 @@ export const createWallet = async ({ mnemonic, bip39Passphrase, restore, - addressAmount, - changeAddressAmount, addressTypesToCreate, }); if (response.isErr()) { @@ -141,841 +101,77 @@ export const createWallet = async ({ } }; -export const updateExchangeRates = async (): Promise> => { - const res = await getExchangeRates(); - - if (res.isErr()) { - return err(res.error); - } - - dispatch({ - type: actions.UPDATE_WALLET, - payload: { exchangeRates: res.value }, - }); - - return ok('Successfully updated the exchange rate.'); -}; - -/** - * This method updates the next available (zero-balance) address & changeAddress index. - * @async - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @param {EAddressType} [addressType] - * @return {string} - */ -export const updateAddressIndexes = async ({ - selectedWallet, - selectedNetwork, - addressType, //If this param is left undefined it will update the indexes for all stored address types. +export const createDefaultWalletStructure = async ({ + walletName = 'wallet0', }: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; - addressType?: EAddressType; -} = {}): Promise> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const { currentWallet } = getCurrentWallet({ - selectedWallet, - selectedNetwork, - }); - - let addressTypeKeys = Object.keys(EAddressType) as EAddressType[]; - if (addressType) { - addressTypeKeys = [addressType]; - } - - let updated = false; - - const promises = addressTypeKeys.map(async (addressTypeKey) => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const response = await getNextAvailableAddress({ - selectedWallet, - selectedNetwork, - addressType: addressTypeKey, - }); - if (response.isErr()) { - throw response.error; - } - const result = response.value; - - let addressIndex = - currentWallet.addressIndex[selectedNetwork][addressTypeKey]; - let changeAddressIndex = - currentWallet.changeAddressIndex[selectedNetwork][addressTypeKey]; - let lastUsedAddressIndex = - currentWallet.lastUsedAddressIndex[selectedNetwork][addressTypeKey]; - let lastUsedChangeAddressIndex = - currentWallet.lastUsedChangeAddressIndex[selectedNetwork][addressTypeKey]; - - if ( - addressIndex.index < 0 || - changeAddressIndex.index < 0 || - result.addressIndex.index > addressIndex.index || - result.changeAddressIndex.index > changeAddressIndex.index || - result.lastUsedAddressIndex.index > lastUsedAddressIndex.index || - result.lastUsedChangeAddressIndex.index > - lastUsedChangeAddressIndex?.index - ) { - if (result.addressIndex) { - addressIndex = result.addressIndex; - } - - if (result.changeAddressIndex) { - changeAddressIndex = result.changeAddressIndex; - } - - if (result.lastUsedAddressIndex) { - lastUsedAddressIndex = result.lastUsedAddressIndex; - } - - if (result.lastUsedChangeAddressIndex) { - lastUsedChangeAddressIndex = result.lastUsedChangeAddressIndex; - } - - //Final check to ensure that both addresses and change addresses do not exceed the gap limit/scanning threshold. - //If either does, we generate a new addresses and/or change address at +1 the last used index. - if ( - Math.abs(addressIndex.index - lastUsedAddressIndex.index) > GAP_LIMIT - ) { - const _addressIndex = await generateAddresses({ - selectedWallet, - selectedNetwork, - addressType: addressTypeKey, - addressAmount: 1, - changeAddressAmount: 0, - addressIndex: lastUsedAddressIndex.index + 1, - }); - if (_addressIndex.isErr()) { - return err(_addressIndex.error.message); - } - addressIndex = _addressIndex.value.addresses[0]; - } - - if ( - Math.abs(changeAddressIndex.index - lastUsedChangeAddressIndex.index) > - GAP_LIMIT - ) { - const _changeAddressIndex = await generateAddresses({ - selectedWallet, - selectedNetwork, - addressType: addressTypeKey, - addressAmount: 0, - changeAddressAmount: 1, - changeAddressIndex: lastUsedChangeAddressIndex.index + 1, - }); - if (_changeAddressIndex.isErr()) { - return err(_changeAddressIndex.error.message); - } - changeAddressIndex = _changeAddressIndex.value.changeAddresses[0]; - } - - //Ensure that the address indexes are integers. - if (!Number.isInteger(addressIndex.index)) { - return err('Invalid address index.'); - } - if (!Number.isInteger(changeAddressIndex.index)) { - return err('Invalid change address index.'); - } - if (!Number.isInteger(lastUsedAddressIndex.index)) { - return err('Invalid last used address index.'); - } - if (!Number.isInteger(lastUsedChangeAddressIndex.index)) { - return err('Invalid last used change address index.'); - } - - dispatch({ - type: actions.UPDATE_ADDRESS_INDEX, - payload: { - addressIndex, - changeAddressIndex, - lastUsedAddressIndex, - lastUsedChangeAddressIndex, - addressType: addressTypeKey, - }, - }); - updated = true; - } - }); - + walletName?: TWalletName; +}): Promise> => { try { - await Promise.all(promises); + const payload: IWallets = { + [walletName]: getDefaultWalletShape(), + }; + dispatch({ + type: actions.CREATE_WALLET, + payload, + }); + return ok(''); } catch (e) { return err(e); } - - return ok(updated ? 'Successfully updated indexes.' : 'No update needed.'); }; -/** - * Resets address indexes back to the app's default/original state. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @returns {void} - */ -export const resetAddressIndexes = ({ - selectedWallet, - selectedNetwork, -}: { - selectedWallet: TWalletName; - selectedNetwork: EAvailableNetwork; -}): void => { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); +export const updateExchangeRates = async ( + exchangeRates: IExchangeRates, +): Promise> => { + if (!exchangeRates) { + const res = await getExchangeRates(); + if (res.isErr()) { + return err(res.error); + } + exchangeRates = res.value; } - const addressTypeKeys = objectKeys(EAddressType); - const defaultWalletShape = getDefaultWalletShape(); - - addressTypeKeys.forEach((addressType) => { - dispatch({ - type: actions.UPDATE_ADDRESS_INDEX, - payload: { - selectedWallet, - selectedNetwork, - addressIndex: - defaultWalletShape.addressIndex[selectedNetwork][addressType], - changeAddressIndex: - defaultWalletShape.changeAddressIndex[selectedNetwork][addressType], - lastUsedAddressIndex: - defaultWalletShape.lastUsedAddressIndex[selectedNetwork][addressType], - lastUsedChangeAddressIndex: - defaultWalletShape.lastUsedChangeAddressIndex[selectedNetwork][ - addressType - ], - addressType, - }, - }); + dispatch({ + type: actions.UPDATE_WALLET, + payload: { exchangeRates }, }); + + return ok('Successfully updated the exchange rate.'); }; export const generateNewReceiveAddress = async ({ - selectedWallet, - selectedNetwork, addressType, keyDerivationPath, }: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; addressType?: EAddressType; keyDerivationPath?: IKeyDerivationPath; }): Promise> => { try { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!addressType) { - addressType = getSelectedAddressType({ selectedNetwork, selectedWallet }); - } - const { currentWallet } = getCurrentWallet({ - selectedWallet, - selectedNetwork, - }); - - const getGapLimitResponse = getGapLimit({ - selectedWallet, - selectedNetwork, - addressType, - }); - if (getGapLimitResponse.isErr()) { - return err(getGapLimitResponse.error.message); - } - const { addressDelta } = getGapLimitResponse.value; - - // If the address delta exceeds the default gap limit, only return the current address index. - if (addressDelta >= GAP_LIMIT) { - const addressIndex = currentWallet.addressIndex[selectedNetwork]; - const receiveAddress = addressIndex[addressType]; - return ok(receiveAddress); - } - - const { path } = addressTypes[addressType]; - if (!keyDerivationPath) { - const keyDerivationPathResponse = getKeyDerivationPathObject({ - selectedNetwork, - path, - }); - if (keyDerivationPathResponse.isErr()) { - return err(keyDerivationPathResponse.error.message); - } - keyDerivationPath = keyDerivationPathResponse.value; - } - const addresses = currentWallet.addresses[selectedNetwork][addressType]; - const currentAddressIndex = - currentWallet.addressIndex[selectedNetwork][addressType].index; - const nextAddressIndex = Object.values(addresses).find((address) => { - return address.index === currentAddressIndex + 1; - }); - - // Check if the next address index already exists or if it needs to be generated. - if (nextAddressIndex) { - // Update addressIndex and return the address content. - dispatch({ - type: actions.UPDATE_ADDRESS_INDEX, - payload: { - addressIndex: nextAddressIndex, - addressType, - }, - }); - return ok(nextAddressIndex); - } - - // We need to generate, save and return the new address. - const addAddressesRes = await addAddresses({ - addressAmount: 1, - changeAddressAmount: 0, - addressIndex: currentAddressIndex + 1, - changeAddressIndex: 0, - selectedNetwork, - selectedWallet, - keyDerivationPath, - addressType, - }); - if (addAddressesRes.isErr()) { - return err(addAddressesRes.error.message); - } - const addressKeys = Object.keys(addAddressesRes.value.addresses); - // If for any reason the phone was unable to generate the new address, return error. - if (!addressKeys.length) { - return err('Unable to generate addresses at this time.'); - } - const newAddressIndex = addAddressesRes.value.addresses[addressKeys[0]]; - dispatch({ - type: actions.UPDATE_ADDRESS_INDEX, - payload: { - addressIndex: newAddressIndex, - addressType, - }, - }); - return ok(newAddressIndex); + const wallet = getOnChainWallet(); + return wallet.generateNewReceiveAddress({ addressType, keyDerivationPath }); } catch (e) { console.log(e); return err(e); } }; -/** - * This method will generate addresses as specified and return an object of filtered addresses to ensure no duplicates are returned. - * @async - * @param {TWalletName} [selectedWallet] - * @param {number} [addressAmount] - * @param {number} [changeAddressAmount] - * @param {number} [addressIndex] - * @param {number} [changeAddressIndex] - * @param {EAvailableNetwork} [selectedNetwork] - * @param {IKeyDerivationPath} [keyDerivationPath] - * @param {EAddressType} [addressType] - * @return {Promise>} - */ -export const addAddresses = async ({ - selectedWallet, - addressAmount = 5, - changeAddressAmount = 5, - addressIndex = 0, - changeAddressIndex = 0, - selectedNetwork, - addressType, - keyDerivationPath, -}: IGenerateAddresses): Promise> => { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!addressType) { - addressType = getSelectedAddressType({ selectedWallet, selectedNetwork }); - } - const { path, type } = addressTypes[addressType]; - if (!keyDerivationPath) { - const keyDerivationPathResponse = getKeyDerivationPathObject({ - selectedNetwork, - path, - }); - if (keyDerivationPathResponse.isErr()) { - return err(keyDerivationPathResponse.error.message); - } - keyDerivationPath = keyDerivationPathResponse.value; - } - const generatedAddresses = await generateAddresses({ - addressAmount, - changeAddressAmount, - addressIndex, - changeAddressIndex, - selectedNetwork, - selectedWallet, - keyDerivationPath, - addressType: type, - }); - if (generatedAddresses.isErr()) { - return err(generatedAddresses.error); - } - - const removeDuplicateResponse = await removeDuplicateAddresses({ - addresses: generatedAddresses.value.addresses, - changeAddresses: generatedAddresses.value.changeAddresses, - selectedWallet, - selectedNetwork, - }); - if (removeDuplicateResponse.isErr()) { - return err(removeDuplicateResponse.error.message); - } - - const addresses = removeDuplicateResponse.value.addresses; - const changeAddresses = removeDuplicateResponse.value.changeAddresses; - if (!Object.keys(addresses).length && !Object.keys(changeAddresses).length) { - return err('No addresses to add.'); - } - - const payload = { - addresses, - changeAddresses, - addressType, - }; - dispatch({ - type: actions.ADD_ADDRESSES, - payload, - }); - return ok({ ...generatedAddresses.value, addressType: type }); -}; - -/** - * This method serves two functions. - * 1. Update UTXO data for all addresses and change addresses for a given wallet and network. - * 2. Update the available balance for a given wallet and network. - */ -export const updateUtxos = async ({ - selectedWallet, - selectedNetwork, - scanAllAddresses = false, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; - scanAllAddresses?: boolean; -}): Promise> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - - const utxoResponse = await getUtxos({ - selectedWallet, - selectedNetwork, - scanAllAddresses, - }); - if (utxoResponse.isErr()) { - return err(utxoResponse.error); - } - const { utxos, balance } = utxoResponse.value; - // Ensure we're not adding any duplicates. - const filteredUtxos = utxos.filter( - (utxo, index, _utxos) => - index === - _utxos.findIndex( - (u) => - u.scriptHash === utxo.scriptHash && - u.tx_pos === utxo.tx_pos && - u.tx_hash === utxo.tx_hash, - ), - ); - const payload = { - selectedWallet, - selectedNetwork, - utxos: filteredUtxos, - balance, - }; - dispatch({ - type: actions.UPDATE_UTXOS, - payload, - }); - return ok(payload); -}; - /** * Clears the UTXO array and balance. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] * @returns {Promise} */ -export const clearUtxos = async ({ - selectedWallet, - selectedNetwork, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -} = {}): Promise => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const payload = { - selectedWallet, - selectedNetwork, - utxos: [], - balance: 0, - }; - dispatch({ - type: actions.UPDATE_UTXOS, - payload, - }); - return "Successfully cleared UTXO's."; -}; - -/** - * Clears the transactions object for a given wallet and network. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @returns {string} - */ -export const clearTransactions = ({ - selectedWallet, - selectedNetwork, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): string => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const payload = { - selectedWallet, - selectedNetwork, - }; - dispatch({ - type: actions.RESET_TRANSACTIONS, - payload, - }); - return 'Successfully reset transactions.'; -}; - -/** - * Clears the addresses and changeAddresses object for a given wallet and network. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @returns {string} - */ -export const clearAddresses = ({ - selectedWallet, - selectedNetwork, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): string => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const payload = { - selectedWallet, - selectedNetwork, - }; - dispatch({ - type: actions.RESET_ADDRESSES, - payload, - }); - return 'Successfully reset transactions.'; -}; - -export const updateWalletBalance = ({ - balance, - selectedWallet, - selectedNetwork, -}: { - balance: number; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Result => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const payload = { - balance, - selectedNetwork, - selectedWallet, - }; - dispatch({ - type: actions.UPDATE_WALLET_BALANCE, - payload, - }); - - return ok('Successfully updated balance.'); - } catch (e) { - return err(e); - } -}; - -export interface ITransactionData { - address: string; - height: number; - index: number; - path: string; - scriptHash: string; - tx_hash: string; - tx_pos: number; - value: number; -} - -/** - * This method processes all transactions with less than 6 confirmations and returns the following: - * 1. Transactions that still have less than 6 confirmations and can be considered unconfirmed. (unconfirmedTxs) - * 2. Transactions that have fewer confirmations than before due to a reorg. (outdatedTxs) - * 3. Transactions that have been removed from the mempool. (ghostTxs) - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @returns {Promise> - */ -export const processUnconfirmedTransactions = async ({ - selectedWallet, - selectedNetwork, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Promise> => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - - //Retrieve all unconfirmed transactions (tx less than 6 confirmations in this case) from the store - const oldUnconfirmedTxsRes = await getUnconfirmedTransactions({ - selectedWallet, - selectedNetwork, - }); - if (oldUnconfirmedTxsRes.isErr()) { - return err(oldUnconfirmedTxsRes.error); - } - const oldUnconfirmedTxs = oldUnconfirmedTxsRes.value; - - //Use electrum to check if the transaction was removed/bumped from the mempool or if it still exists. - const tx_hashes: ITxHash[] = Object.values(oldUnconfirmedTxs).map( - (transaction: IFormattedTransaction) => { - return { tx_hash: transaction.txid }; - }, - ); - const txs = await getTransactions({ - selectedNetwork, - txHashes: tx_hashes, - }); - if (txs.isErr()) { - return err(txs.error); - } - - const unconfirmedTxs: IFormattedTransactions = {}; - const outdatedTxs: IUtxo[] = []; //Transactions that have been pushed back into the mempool due to a reorg. We need to update the height. - const ghostTxs: string[] = []; //Transactions that have been removed from the mempool and are no longer in the blockchain. - txs.value.data.forEach((txData: ITransaction) => { - // Check if the transaction has been removed from the mempool/still exists. - if (!transactionExists(txData)) { - //Transaction may have been removed/bumped from the mempool or potentially reorg'd out. - ghostTxs.push(txData.data.tx_hash); - return; - } - - const newHeight = confirmationsToBlockHeight({ - confirmations: txData.result?.confirmations ?? 0, - selectedNetwork, - }); - - if (!txData.result?.confirmations) { - const oldHeight = oldUnconfirmedTxs[txData.data.tx_hash]?.height ?? 0; - if (oldHeight > newHeight) { - //Transaction was reorg'd back to zero confirmations. Add it to the outdatedTxs array. - outdatedTxs.push(txData.data); - } - unconfirmedTxs[txData.data.tx_hash] = { - ...oldUnconfirmedTxs[txData.data.tx_hash], - height: newHeight, - }; - return; - } - - //Check if the transaction has been confirmed. - if (txData.result?.confirmations < 6) { - unconfirmedTxs[txData.data.tx_hash] = { - ...oldUnconfirmedTxs[txData.data.tx_hash], - height: newHeight, - }; - } - }); - return ok({ - unconfirmedTxs, - outdatedTxs, - ghostTxs, - }); - } catch (e) { - return err(e); - } -}; - -/** - * Checks existing unconfirmed transactions that have been received and removes any that have >= 6 confirmations. - * If the tx is reorg'd or bumped from the mempool and no longer exists, the transaction - * will be removed from the store and updated in the activity list. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @returns {Promise>} - */ -export const checkUnconfirmedTransactions = async ({ - selectedWallet, - selectedNetwork, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Promise> => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - - const processRes = await processUnconfirmedTransactions({ - selectedNetwork, - selectedWallet, - }); - if (processRes.isErr()) { - return err(processRes.error.message); - } - - const { unconfirmedTxs, outdatedTxs, ghostTxs } = processRes.value; - if (outdatedTxs.length) { - // Notify user that a reorg has occurred and that the transaction has been pushed back into the mempool. - showToast({ - type: 'info', - title: i18n.t('wallet:reorg_detected'), - description: i18n.t('wallet:reorg_msg_begin', { - count: outdatedTxs.length, - }), - autoHide: false, - }); - //We need to update the height of the transactions that were reorg'd out. - await updateTransactionHeights(outdatedTxs); - } - if (ghostTxs.length) { - // Notify user that a transaction has been removed from the mempool. - showToast({ - type: 'error', - title: i18n.t('wallet:activity_removed_title'), - description: i18n.t('wallet:activity_removed_msg', { - count: ghostTxs.length, - }), - autoHide: false, - }); - //We need to update the ghost transactions in the store & activity-list and rescan the addresses to get the correct balance. - await updateGhostTransactions({ - selectedWallet, - selectedNetwork, - txIds: ghostTxs, - }); - } - const payload = { - selectedNetwork, - selectedWallet, - unconfirmedTransactions: unconfirmedTxs, - }; - - dispatch({ - type: actions.UPDATE_UNCONFIRMED_TRANSACTIONS, - payload, - }); - return ok('Successfully updated unconfirmed transactions.'); - } catch (e) { - return err(e); - } -}; - -/** - * Updates the confirmation state of activity item transactions that were reorg'd out. - * @param {IUtxo[]} txs - */ -export const updateTransactionHeights = async ( - txs: IUtxo[], -): Promise => { - txs.forEach((tx) => { - //Update the activity item to reflect that the transaction has a new height. - dispatch( - updateOnchainActivityItem({ - id: tx.tx_hash, - data: { confirmed: false }, - }), - ); - }); - return 'Successfully updated reorg transactions.'; +export const clearUtxos = async (): Promise => { + const wallet = getOnChainWallet(); + return await wallet.clearUtxos(); }; -/** - * Removes transactions from the store and activity list. - * @param {string[]} txIds - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @returns {Promise>} - */ -export const updateGhostTransactions = async ({ - txIds, - selectedWallet, - selectedNetwork, +export const updateWalletBalance = ({ + balance, }: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; - txIds: string[]; -}): Promise> => { + balance: number; +}): Result => { try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - - txIds.forEach((txId) => { - //Update the activity item to reflect that the transaction no longer exists, but that it did at one point in time. - dispatch( - updateOnchainActivityItem({ - id: txId, - data: { exists: false, confirmed: false }, - }), - ); - }); - - //Rescan the addresses to get the correct balance. - await rescanAddresses({ - selectedWallet, - selectedNetwork, - shouldClearAddresses: false, // No need to clear addresses since we are only updating the balance. - }); - return ok('Successfully deleted transactions.'); + const wallet = getOnChainWallet(); + return wallet.updateWalletBalance({ balance }); } catch (e) { return err(e); } @@ -1009,7 +205,6 @@ export const addUnconfirmedTransactions = ({ Object.keys(transactions).forEach((key) => { const confirmations = blockHeightToConfirmations({ blockHeight: transactions[key].height, - selectedNetwork, }); if (confirmations < 6) { unconfirmedTransactions[key] = transactions[key]; @@ -1093,176 +288,34 @@ export const injectFakeTransaction = ({ * Retrieves, formats & stores the transaction history for the selected wallet/network. * @param {boolean} [scanAllAddresses] * @param {boolean} [replaceStoredTransactions] Setting this to true will set scanAllAddresses to true as well. - * @param {boolean} [showNotification] - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] */ export const updateTransactions = async ({ scanAllAddresses = false, replaceStoredTransactions = false, - selectedWallet, - selectedNetwork, }: { scanAllAddresses?: boolean; replaceStoredTransactions?: boolean; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; }): Promise> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const { currentWallet } = getCurrentWallet({ - selectedWallet, - selectedNetwork, - }); - - //Check existing unconfirmed transactions and remove any that are confirmed. - //If the tx is reorg'd or bumped from the mempool and no longer exists, the transaction will be removed from the store and updated in the activity list. - await checkUnconfirmedTransactions({ selectedWallet, selectedNetwork }); - - const history = await getAddressHistory({ - selectedNetwork, - selectedWallet, - scanAllAddresses: scanAllAddresses || replaceStoredTransactions, - }); - if (history.isErr()) { - return err(history.error.message); - } - if (!history.value.length) { - return ok(undefined); - } - - const getTransactionsResponse = await getTransactions({ - txHashes: history.value, - selectedNetwork, - }); - if (getTransactionsResponse.isErr()) { - return err(getTransactionsResponse.error.message); - } - - const formatTransactionsResponse = await formatTransactions({ - selectedNetwork, - selectedWallet, - transactions: getTransactionsResponse.value.data, - }); - if (formatTransactionsResponse.isErr()) { - return err(formatTransactionsResponse.error.message); - } - const transactions = formatTransactionsResponse.value; - - // Add unconfirmed transactions. - // No need to wait for this to finish. - addUnconfirmedTransactions({ - selectedNetwork, - selectedWallet, - transactions, - }); - - if (replaceStoredTransactions) { - // No need to check the existing txs. Update with the returned formatTransactionsResponse. - dispatch({ - type: actions.UPDATE_TRANSACTIONS, - payload: { - transactions, - selectedNetwork, - selectedWallet, - }, - }); - updateSlashPayConfig2({ selectedWallet, selectedNetwork }); - return ok(undefined); - } - - // Handle new or updated transactions. - const formattedTransactions: IFormattedTransactions = {}; - const storedTransactions = currentWallet.transactions[selectedNetwork]; - const blocktankOrders = getBlocktankStore().orders; - - let notificationTxid: string | undefined; - - Object.keys(transactions).forEach((txid) => { - // check if tx is a payment from Blocktank (i.e. transfer to savings) - const isTransferToSavings = !!blocktankOrders.find((order) => { - return !!transactions[txid].vin.find( - (input) => input.txid === order.channel?.close?.txId, - ); - }); - - //If the tx is new or the tx now has a block height (state changed to confirmed) - if ( - !storedTransactions[txid] || - storedTransactions[txid].height !== transactions[txid].height - ) { - formattedTransactions[txid] = { - ...transactions[txid], - // Keep the previous timestamp if the tx is not new. - timestamp: - storedTransactions[txid]?.timestamp ?? - transactions[txid]?.timestamp ?? - Date.now(), - }; - } - - // if the tx is new, incoming but not from a transfer - show notification - if ( - !storedTransactions[txid] && - transactions[txid].type === EPaymentType.received && - !isTransferToSavings - ) { - notificationTxid = txid; - } - }); - - //No new or updated transactions - if (!Object.keys(formattedTransactions).length) { - return ok(undefined); - } - - dispatch({ - type: actions.UPDATE_TRANSACTIONS, - payload: { - transactions: formattedTransactions, - selectedNetwork, - selectedWallet, - }, + const wallet = getOnChainWallet(); + return await wallet.updateTransactions({ + scanAllAddresses, + replaceStoredTransactions, }); - - updateSlashPayConfig2({ selectedWallet, selectedNetwork }); - - return ok(notificationTxid); }; /** * Deletes a given on-chain trnsaction by id. * @param {string} txid - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] + * @returns {Promise} */ export const deleteOnChainTransactionById = async ({ txid, - selectedWallet, - selectedNetwork, }: { txid: string; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; }): Promise => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const payload = { + const wallet = getOnChainWallet(); + return await wallet.deleteOnChainTransactionById({ txid, - selectedNetwork, - selectedWallet, - }; - dispatch({ - type: actions.DELETE_ON_CHAIN_TRANSACTION, - payload, }); }; @@ -1272,58 +325,25 @@ export const deleteOnChainTransactionById = async ({ * @param {string} oldTxId * @param {EBoostType} [type] * @param {number} fee - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] */ export const addBoostedTransaction = async ({ newTxId, oldTxId, type = EBoostType.cpfp, fee, - selectedWallet, - selectedNetwork, }: { newTxId: string; oldTxId: string; type?: EBoostType; fee: number; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Promise => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const boostedTransactions = - getWalletStore().wallets[selectedWallet].boostedTransactions[ - selectedNetwork - ]; - const parentTransactions = getBoostedTransactionParents({ - txid: oldTxId, - boostedTransactions, - }); - parentTransactions.push(oldTxId); - const boostedTransaction: IBoostedTransactions = { - [oldTxId]: { - parentTransactions: parentTransactions, - childTransaction: newTxId, - type, - fee, - }, - }; - const payload = { - boostedTransaction, - selectedNetwork, - selectedWallet, - }; - dispatch({ - type: actions.ADD_BOOSTED_TRANSACTION, - payload, - }); - } catch (e) {} +}): Promise> => { + const wallet = getOnChainWallet(); + return await wallet.addBoostedTransaction({ + newTxId, + oldTxId, + type, + fee, + }); }; /** @@ -1349,8 +369,6 @@ export const resetSelectedWallet = async ({ * Sets up a transaction for a given wallet by gathering inputs, setting the next available change address as an output and sets up the baseline fee structure. * This function will not override previously set transaction data. To do that you'll need to call resetSendTransaction. * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @param {EAddressType} [addressType] * @param {string[]} [inputTxHashes] * @param {IUtxo[]} [utxos] * @param {boolean} [rbf] @@ -1358,599 +376,155 @@ export const resetSelectedWallet = async ({ * @returns {Promise>>} */ export const setupOnChainTransaction = async ({ - selectedWallet, - selectedNetwork, - addressType, + //addressType, inputTxHashes, utxos, rbf = false, - satsPerByte = 1, + satsPerByte, }: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; - addressType?: EAddressType; // Preferred address type for change address. + //addressType?: EAddressType; // Preferred address type for change address. inputTxHashes?: string[]; // Used to pre-specify inputs to use by tx_hash utxos?: IUtxo[]; // Used to pre-specify utxos to use rbf?: boolean; // Enable or disable rbf. satsPerByte?: number; // Set the sats per byte for the transaction. -} = {}): Promise>> => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!addressType) { - addressType = getSelectedAddressType({ selectedWallet, selectedNetwork }); - } - - const { currentWallet } = getCurrentWallet({ - selectedWallet, - selectedNetwork, - }); - - const transaction = currentWallet.transaction[selectedNetwork]; - - // Gather required inputs. - let inputs: IUtxo[] = []; - if (inputTxHashes) { - // If specified, filter for the desired tx_hash and push the utxo as an input. - inputs = currentWallet.utxos[selectedNetwork].filter((utxo) => { - return inputTxHashes.includes(utxo.tx_hash); - }); - } else if (utxos) { - inputs = utxos; - } - - if (!inputs.length) { - // If inputs were previously selected, leave them. - if (transaction.inputs.length > 0) { - inputs = transaction.inputs; - } else { - // Otherwise, lets use our available utxo's. - inputs = currentWallet.utxos[selectedNetwork]; - } - } - - if (!inputs.length) { - return err('No inputs specified.'); - } - - const currentChangeAddresses = - currentWallet.changeAddresses[selectedNetwork]; - - const addressTypeKeys = objectKeys(EAddressType); - let changeAddresses: IAddresses = {}; - addressTypeKeys.forEach((key) => { - changeAddresses = { - ...changeAddresses, - ...currentChangeAddresses[key], - }; - }); - const changeAddressesArr = Object.values(changeAddresses).map( - ({ address }) => address, - ); - - const changeAddressIndexContent = - currentWallet.changeAddressIndex[selectedNetwork][addressType]; - // Set the current change address. - let changeAddress = changeAddressIndexContent.address; - - if (!changeAddress || changeAddressIndexContent.index < 0) { - // It's possible we haven't set the change address index yet. Generate one on the fly. - const generateAddressResponse = await generateAddresses({ - selectedWallet, - selectedNetwork, - addressAmount: 0, - changeAddressAmount: 1, - addressType, - }); - if (generateAddressResponse.isErr()) { - return err(generateAddressResponse.error.message); - } - changeAddress = generateAddressResponse.value.changeAddresses[0].address; - } - if (!changeAddress) { - return err('Unable to successfully generate a change address.'); - } - - // Set the minimum fee. - const fee = getTotalFee({ - satsPerByte, - message: '', - }); - - const lightningInvoice = - currentWallet.transaction[selectedNetwork]?.lightningInvoice; - - let outputs = currentWallet.transaction[selectedNetwork].outputs || []; - if (!lightningInvoice) { - //Remove any potential change address that may have been included from a previous tx attempt. - outputs = outputs.filter((output) => { - if (output.address && !changeAddressesArr.includes(output.address)) { - return output; - } - }); - } - - const payload = { - selectedNetwork, - selectedWallet, - inputs, - changeAddress, - fee, - outputs, - rbf, - satsPerByte, - }; - - dispatch({ - type: actions.SETUP_ON_CHAIN_TRANSACTION, - payload, - }); - - return ok(payload); - } catch (e) { - return err(e); - } +} = {}): Promise => { + const transaction = getOnChainWalletTransaction(); + return await transaction.setupTransaction({ + inputTxHashes, + utxos, + rbf, + satsPerByte, + }); }; /** * Retrieves the next available change address data. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] * @param {EAddressType} [addressType] * @returns {Promise>} */ export const getChangeAddress = async ({ - selectedWallet, - selectedNetwork, addressType, }: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; addressType?: EAddressType; }): Promise> => { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!addressType) { - addressType = getSelectedAddressType({ selectedWallet, selectedNetwork }); - } - - const { currentWallet } = getCurrentWallet({ - selectedWallet, - selectedNetwork, - }); - - const changeAddressIndexContent = - currentWallet.changeAddressIndex[selectedNetwork][addressType]; - - if ( - changeAddressIndexContent?.address && - changeAddressIndexContent.index >= 0 - ) { - return ok(changeAddressIndexContent); - } - - // It's possible we haven't set the change address index yet. Generate one on the fly. - const generateAddressResponse = await generateAddresses({ - selectedWallet, - selectedNetwork, - addressAmount: 0, - changeAddressAmount: 1, - addressType, - }); - if (generateAddressResponse.isErr()) { - console.log(generateAddressResponse.error.message); - return err('Unable to successfully generate a change address.'); - } - return ok(generateAddressResponse.value.changeAddresses[0]); + const wallet = getOnChainWallet(); + return await wallet.getChangeAddress(addressType); }; /** * This updates the transaction state used for sending. * @param {Partial} transaction - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @return {Promise>} + * @returns {Promise>} */ export const updateSendTransaction = ({ transaction, - selectedWallet, - selectedNetwork, -}: { - transaction: Partial; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Result => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - - //Add output if specified - if (transaction.outputs) { - const currentWallet = getWalletStore().wallets[selectedWallet]; - const currentTransaction = currentWallet.transaction[selectedNetwork]; - const outputs = currentTransaction.outputs.concat(); - transaction.outputs.forEach((output) => { - outputs[output.index] = output; - }); - transaction.outputs = outputs; - } - - dispatch({ - type: actions.UPDATE_SEND_TRANSACTION, - payload: { - transaction, - selectedNetwork, - selectedWallet, - }, - }); - - return ok('Transaction updated'); - } catch (e) { - return err(e); - } -}; - -export const updateSelectedFeeId = async ({ - feeId, - selectedWallet, - selectedNetwork, -}: { - feeId: EFeeId; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Promise> => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const transactionResponse = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); - if (transactionResponse.isErr()) { - return err(transactionResponse.error.message); - } - const transaction = transactionResponse.value; - transaction.selectedFeeId = feeId; - updateSendTransaction({ transaction }); - return ok('Fee updated'); - } catch (e) { - console.log(e); - return err(e); - } +}: { + transaction: Partial; +}): Result => { + const tx = getOnChainWalletTransaction(); + return tx.updateSendTransaction({ + transaction, + }); }; /** * This completely resets the send transaction state for the specified wallet and network. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] * @returns {Result} */ -export const resetSendTransaction = ({ - selectedWallet, - selectedNetwork, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -} = {}): Result => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - - dispatch({ - type: actions.RESET_SEND_TRANSACTION, - payload: { - selectedNetwork, - selectedWallet, - }, - }); - return ok('Transaction reseted'); +export const resetSendTransaction = async (): Promise> => { + const transaction = getOnChainWalletTransaction(); + return transaction.resetSendTransaction(); }; export const updateSelectedAddressType = async ({ addressType, - selectedWallet, - selectedNetwork, }: { addressType: EAddressType; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; }): Promise => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - // Ensure zero index addresses are set when switching address types. - await addAddresses({ - selectedWallet, - selectedNetwork, - addressType, - addressAmount: 1, - changeAddressAmount: 1, - addressIndex: 0, - changeAddressIndex: 0, - }); - - dispatch({ - type: actions.UPDATE_SELECTED_ADDRESS_TYPE, - payload: { - addressType, - selectedNetwork, - selectedWallet, - }, - }); + const wallet = getOnChainWallet(); + return await wallet.updateAddressType(addressType); }; /** * Removes the specified input from the current transaction. * @param {IUtxo} input - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] + * @returns {Result} */ -export const removeTxInput = ({ - input, - selectedWallet, - selectedNetwork, -}: { - input: IUtxo; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Result => { - try { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const txData = getOnchainTransactionData({ - selectedNetwork, - selectedWallet, - }); - if (txData.isErr()) { - return err(txData.error.message); - } - const txInputs = txData.value?.inputs ?? []; - const newInputs = txInputs.filter((txInput) => { - if (!objectsMatch(input, txInput)) { - return txInput; - } - }); - updateSendTransaction({ - selectedNetwork, - selectedWallet, - transaction: { - inputs: newInputs, - }, - }); - return ok(newInputs); - } catch (e) { - console.log(e); - return err(e); - } +export const removeTxInput = ({ input }: { input: IUtxo }): Result => { + const wallet = getOnChainWallet(); + return wallet.removeTxInput({ + input, + }); }; /** * Adds a specified input to the current transaction. * @param {IUtxo} input - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] + * @returns {Result} */ -export const addTxInput = ({ - input, - selectedWallet, - selectedNetwork, -}: { - input: IUtxo; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Result => { - try { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const txData = getOnchainTransactionData({ - selectedNetwork, - selectedWallet, - }); - if (txData.isErr()) { - return err(txData.error.message); - } - const inputs = txData.value?.inputs ?? []; - const newInputs = [...inputs, input]; - updateSendTransaction({ - selectedNetwork, - selectedWallet, - transaction: { - inputs: newInputs, - }, - }); - return ok(newInputs); - } catch (e) { - console.log(e); - return err(e); - } +export const addTxInput = ({ input }: { input: IUtxo }): Result => { + const wallet = getOnChainWallet(); + return wallet.addTxInput({ + input, + }); }; /** * Adds a specified tag to the current transaction. * @param {string} tag - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] + * @returns {Result} */ -export const addTxTag = ({ - tag, - selectedWallet, - selectedNetwork, -}: { - tag: string; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Result => { - try { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const txData = getOnchainTransactionData({ - selectedNetwork, - selectedWallet, - }); - if (txData.isErr()) { - return err(txData.error.message); - } - - let tags = [...txData.value.tags, tag]; - tags = [...new Set(tags)]; // remove duplicates - - updateSendTransaction({ - selectedNetwork, - selectedWallet, - transaction: { - ...txData, - tags, - }, - }); - return ok('Tag successfully added'); - } catch (e) { - console.log(e); - return err(e); - } +export const addTxTag = ({ tag }: { tag: string }): Result => { + const wallet = getOnChainWallet(); + return wallet.addTxTag({ + tag, + }); }; /** * Removes a specified tag to the current transaction. * @param {string} tag - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] */ -export const removeTxTag = ({ - tag, - selectedWallet, - selectedNetwork, -}: { - tag: string; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Result => { - try { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const txData = getOnchainTransactionData({ - selectedNetwork, - selectedWallet, - }); - if (txData.isErr()) { - return err(txData.error.message); - } - - const tags = txData.value.tags; - const newTags = tags.filter((t) => t !== tag); - - updateSendTransaction({ - selectedNetwork, - selectedWallet, - transaction: { - ...txData, - tags: newTags, - }, - }); - return ok('Tag successfully added'); - } catch (e) { - console.log(e); - return err(e); - } +export const removeTxTag = ({ tag }: { tag: string }): Result => { + const wallet = getOnChainWallet(); + return wallet.removeTxTag({ + tag, + }); }; /** * Updates the fee rate for the current transaction to the preferred value if none set. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] + * @returns {Result} */ -export const setupFeeForOnChainTransaction = ({ - selectedWallet, - selectedNetwork, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -} = {}): Result => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - - const transactionDataResponse = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); - if (transactionDataResponse.isErr()) { - return err(transactionDataResponse.error.message); - } - const transaction = transactionDataResponse.value; - - const fees = getFeesStore().onchain; - const { transactionSpeed, customFeeRate } = getSettingsStore(); - const preferredFeeRate = - transactionSpeed === ETransactionSpeed.custom - ? customFeeRate - : fees[transactionSpeed]; - - const satsPerByte = - transaction.selectedFeeId === 'none' - ? preferredFeeRate - : transaction.satsPerByte; - const selectedFeeId = - transaction.selectedFeeId === 'none' - ? EFeeId[transactionSpeed] - : transaction.selectedFeeId; - - const res = updateFee({ - satsPerByte, - selectedFeeId, - selectedNetwork, - selectedWallet, - }); - - if (res.isErr()) { - console.log(res.error.message); - return err(res.error.message); - } - - return ok('Fee has been changed successfully'); - } catch (e) { - return err(e); +export const setupFeeForOnChainTransaction = (): Result => { + const wallet = getOnChainWallet(); + const transaction = wallet.transaction.data; + const fees = getFeesStore().onchain; + const { transactionSpeed, customFeeRate } = getSettingsStore(); + const preferredFeeRate = + transactionSpeed === ETransactionSpeed.custom + ? customFeeRate + : fees[transactionSpeed]; + const selectedFeeId = txSpeedToFeeId(getSettingsStore().transactionSpeed); + const satsPerByte = + transaction.selectedFeeId === 'none' + ? preferredFeeRate + : transaction.satsPerByte; + return wallet.setupFeeForOnChainTransaction({ satsPerByte, selectedFeeId }); +}; + +const txSpeedToFeeId = (txSpeed: ETransactionSpeed): EFeeId => { + switch (txSpeed) { + case ETransactionSpeed.slow: + return EFeeId.slow; + case ETransactionSpeed.normal: + return EFeeId.normal; + case ETransactionSpeed.fast: + return EFeeId.fast; + case ETransactionSpeed.custom: + return EFeeId.custom; + default: + return EFeeId.none; } }; @@ -1990,82 +564,6 @@ export const resetExchangeRates = (): Result => { return ok(''); }; -/** - * Will ensure that both address and change address indexes are set. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @param {EAddressType} [addressType] - * @param {TAddressIndexInfo} [addressIndexInfo] - */ -export const setZeroIndexAddresses = async ({ - selectedWallet, - selectedNetwork, - addressType, - addressIndexInfo, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; - addressType?: EAddressType; - addressIndexInfo?: TAddressIndexInfo; -}): Promise> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!addressType) { - addressType = getSelectedAddressType({ selectedNetwork, selectedWallet }); - } - - if (!addressIndexInfo) { - addressIndexInfo = getAddressIndexInfo({ - selectedNetwork, - selectedWallet, - addressType, - }); - } - - const addressIndex = addressIndexInfo.addressIndex; - const changeAddressIndex = addressIndexInfo.changeAddressIndex; - - if (addressIndex.index >= 0 && changeAddressIndex.index >= 0) { - return ok('No need to set indexes.'); - } - - const currentWallet = getWalletStore().wallets[selectedWallet]; - const payload: { - addressIndex?: IAddress; - changeAddressIndex?: IAddress; - } = {}; - - if (addressIndex.index < 0) { - const addresses = currentWallet?.addresses[selectedNetwork][addressType]; - const address = Object.values(addresses).find((a) => a.index === 0); - if (address) { - payload.addressIndex = address; - } - } - if (changeAddressIndex.index < 0) { - const changeAddresses = - currentWallet?.changeAddresses[selectedNetwork][addressType]; - const address = Object.values(changeAddresses).find((a) => a.index === 0); - if (address) { - payload.changeAddressIndex = address; - } - } - - dispatch({ - type: actions.UPDATE_ADDRESS_INDEX, - payload: { - ...payload, - addressType, - }, - }); - - return ok('Set Zero Index Addresses.'); -}; - /** * Used to update/replace mismatched addresses. * @param {TWalletName} [selectedWallet] @@ -2172,3 +670,119 @@ export const replaceImpactedAddresses = async ({ return err(e); } }; + +/** + * Will attempt to return saved wallet data from redux. + * If not found, it will return the default value. + * @async + * @param {string} key + * @returns {Promise>} + */ +export const getWalletData = async ( + key: string, +): Promise> => { + const keyValue = getKeyValue(key); + try { + const selectedWallet = getSelectedWallet(); + if (!(selectedWallet in getWalletStore().wallets)) { + return err('Unable to locate wallet data.'); + } + if (keyValue === 'feeEstimates') { + // @ts-ignore + return ok(getFeesStore().onchain); + } + const wallet = getWalletStore().wallets[selectedWallet]; + if (keyValue in wallet) { + const keyValueType = typeof wallet[keyValue]; + const tArr = ['string', 'number', 'boolean', 'undefined']; + if (tArr.includes(keyValueType)) { + return ok(wallet[keyValue]); + } else { + const selectedNetwork = getSelectedNetwork(); + return ok(wallet[keyValue][selectedNetwork]); + } + } + const defaultWalletData = getDefaultWalletData(); + return ok(defaultWalletData[keyValue]); + } catch (e) { + console.error('Error in getWalletData:', e); + return ok(getDefaultWalletData()[keyValue]); + } +}; + +/** + * Will attempt to set wallet data in redux. + * @param {string} key + * @param {IWalletData[K]} data + * @returns {Promise>} + */ +export const setWalletData = async ( + key: string, + data: IWalletData[K], +): Promise> => { + if (!key) { + return err('Invalid key.'); + } + const { walletName, network, value } = getStorageKeyValues(key); + try { + switch (value) { + case 'header': + updateHeader({ + header: data as IHeader, + selectedNetwork: getNetworkFromBeignet(network), + }); + break; + case 'exchangeRates': + updateExchangeRates(data as IExchangeRates); + break; + case 'feeEstimates': + updateOnchainFeeEstimates({ + selectedNetwork: getNetworkFromBeignet(network), + feeEstimates: data as IOnchainFees, + forceUpdate: true, + }); + break; + default: + const payload = { + selectedWallet: walletName, + network: getNetworkFromBeignet(network), + value, + data, + }; + dispatch({ + type: actions.UPDATE_WALLET_DATA, + payload, + }); + } + return ok(true); + } catch (e) { + console.error('Error in setWalletData:', e); + return err(e); + } +}; + +export const getNetworkFromBeignet = ( + network: EAvailableNetworks, +): EAvailableNetwork => { + switch (network) { + case EAvailableNetworks.bitcoin: + case EAvailableNetworks.bitcoinMainnet: + return EAvailableNetwork.bitcoin; + case EAvailableNetworks.testnet: + case EAvailableNetworks.bitcoinTestnet: + return EAvailableNetwork.bitcoinTestnet; + case EAvailableNetworks.regtest: + case EAvailableNetworks.bitcoinRegtest: + return EAvailableNetwork.bitcoinRegtest; + } +}; + +/** + * Returns value after last hyphen in a string. + * @param {string} key + * @returns {string} + */ +export const getKeyValue = (key: string): string => { + const parts = key.split('-'); + return parts[parts.length - 1]; +}; diff --git a/src/store/reducers/wallet.ts b/src/store/reducers/wallet.ts index 7eb347cc8..486420c37 100644 --- a/src/store/reducers/wallet.ts +++ b/src/store/reducers/wallet.ts @@ -12,8 +12,9 @@ const wallet = ( state: IWalletStore = defaultWalletStoreShape, action, ): IWalletStore => { - let selectedWallet = state.selectedWallet; - let selectedNetwork = state.selectedNetwork; + let selectedWallet = state?.selectedWallet ?? 'wallet0'; + let selectedNetwork = state?.selectedNetwork ?? 'bitcoin'; + if (action.payload?.selectedWallet) { selectedWallet = action.payload.selectedWallet; } @@ -32,6 +33,62 @@ const wallet = ( ...action.payload, }; + case actions.UPDATE_WALLET_DATA: + const { value, data } = action.payload; + const network = action.payload?.network ?? selectedNetwork; + + // Values that are not nested and do not have a network + if (value === 'name' || value === 'id' || value === 'seedHash') { + return { + ...state, + wallets: { + ...state.wallets, + [selectedWallet]: { + ...state.wallets[selectedWallet], + [value]: data, + }, + }, + }; + } + + if ( + value === 'balance' || + value === 'utxos' || + value === 'blacklistedUtxos' || + value === 'unconfirmedTransactions' + ) { + return { + ...state, + wallets: { + ...state.wallets, + [selectedWallet]: { + ...state.wallets[selectedWallet], + [value]: { + ...state.wallets[selectedWallet][value], + [network]: data, + }, + }, + }, + }; + } + + return { + ...state, + wallets: { + ...state.wallets, + [selectedWallet]: { + ...state.wallets[selectedWallet], + [value]: { + ...state.wallets[selectedWallet][value], + [network]: { + ...state.wallets[selectedWallet][value][network], + ...data, + }, + }, + }, + }, + }; + case actions.CREATE_WALLET: return produce(state, (draftState) => { draftState.walletExists = true; @@ -52,11 +109,7 @@ const wallet = ( ...state.wallets[selectedWallet].addressIndex, [selectedNetwork]: { ...state.wallets[selectedWallet].addressIndex[selectedNetwork], - [addressType]: - action.payload?.addressIndex ?? - state.wallets[selectedWallet].addressIndex[selectedNetwork][ - addressType - ], + [addressType]: action.payload.addressIndex, }, }, changeAddressIndex: { @@ -65,11 +118,7 @@ const wallet = ( ...state.wallets[selectedWallet].changeAddressIndex[ selectedNetwork ], - [addressType]: - action.payload?.changeAddressIndex ?? - state.wallets[selectedWallet].changeAddressIndex[ - selectedNetwork - ][addressType], + [addressType]: action.payload.changeAddressIndex, }, }, lastUsedAddressIndex: { @@ -78,11 +127,7 @@ const wallet = ( ...state.wallets[selectedWallet].lastUsedAddressIndex[ selectedNetwork ], - [addressType]: - action.payload?.lastUsedAddressIndex ?? - state.wallets[selectedWallet].lastUsedAddressIndex[ - selectedNetwork - ][addressType], + [addressType]: action.payload.lastUsedAddressIndex, }, }, lastUsedChangeAddressIndex: { @@ -91,11 +136,7 @@ const wallet = ( ...state.wallets[selectedWallet].lastUsedChangeAddressIndex[ selectedNetwork ], - [addressType]: - action.payload?.lastUsedChangeAddressIndex ?? - state.wallets[selectedWallet].lastUsedChangeAddressIndex[ - selectedNetwork - ][addressType], + [addressType]: action.payload.lastUsedChangeAddressIndex, }, }, }, diff --git a/src/store/reselect/fees.ts b/src/store/reselect/fees.ts index e2b508567..42003db52 100644 --- a/src/store/reselect/fees.ts +++ b/src/store/reselect/fees.ts @@ -1,7 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { RootState } from '..'; import { TFeesState } from '../slices/fees'; -import { IOnchainFees } from '../types/fees'; +import { IOnchainFees } from 'beignet'; const feesState = (state: RootState): TFeesState => state.fees; diff --git a/src/store/reselect/wallet.ts b/src/store/reselect/wallet.ts index 86da183e4..44cb3f655 100644 --- a/src/store/reselect/wallet.ts +++ b/src/store/reselect/wallet.ts @@ -1,6 +1,5 @@ import { createSelector } from '@reduxjs/toolkit'; import { RootState } from '..'; -import { EFeeId } from '../types/fees'; import { IWalletStore, IWallets, @@ -9,13 +8,14 @@ import { IBoostedTransactions, IFormattedTransactions, IFormattedTransaction, - ISendTransaction, IUtxo, EAddressType, } from '../types/wallet'; import { defaultSendTransaction } from '../shapes/wallet'; import { EAvailableNetwork } from '../../utils/networks'; import { IExchangeRates } from '../../utils/exchange-rate'; +import { EFeeId } from '../types/fees'; +import { ISendTransaction } from 'beignet'; export const walletState = (state: RootState): IWalletStore => state.wallet; export const walletsState = (state: RootState): IWallets => diff --git a/src/store/shapes/blocktank.ts b/src/store/shapes/blocktank.ts index a611d629f..3461c9f78 100644 --- a/src/store/shapes/blocktank.ts +++ b/src/store/shapes/blocktank.ts @@ -30,6 +30,15 @@ export const defaultBlocktankInfoShape: IBtInfo = { btc: '0.0.0', ln2: '0.0.0', }, + // @ts-ignore + onchain: { + feeRates: { + fast: 10, + mid: 5, + slow: 1, + isHigh: false, + }, + }, }; export const initialBlocktankState: IBlocktank = { diff --git a/src/store/shapes/lightning.ts b/src/store/shapes/lightning.ts index 27eaa925d..bd23617a7 100644 --- a/src/store/shapes/lightning.ts +++ b/src/store/shapes/lightning.ts @@ -17,6 +17,6 @@ export const initialLightningState: TLightningState = { c_bindings: '', }, nodes: { - [defaultWalletShape.id]: defaultLightningShape, + [defaultWalletShape.name]: defaultLightningShape, }, }; diff --git a/src/store/shapes/settings.ts b/src/store/shapes/settings.ts index 70037d169..923e217b8 100644 --- a/src/store/shapes/settings.ts +++ b/src/store/shapes/settings.ts @@ -13,13 +13,11 @@ import { } from '../../constants/env'; import { TSettings } from '../slices/settings'; import { EAvailableNetwork } from '../../utils/networks'; -import { ETransactionSpeed, ICustomElectrumPeer } from '../types/settings'; +import { ETransactionSpeed } from '../types/settings'; import { EUnit } from '../types/wallet'; +import { EProtocol, TServer } from 'beignet'; -export const defaultElectrumPeer: Record< - EAvailableNetwork, - ICustomElectrumPeer[] -> = { +export const defaultElectrumPeer: Record = { bitcoin: [ { host: __ELECTRUM_BITCOIN_HOST__, @@ -33,25 +31,25 @@ export const defaultElectrumPeer: Record< host: 'testnet.hsmiths.com', ssl: 53012, tcp: 53012, - protocol: 'ssl', + protocol: EProtocol.ssl, }, { host: 'tn.not.fyi', ssl: 55002, tcp: 55002, - protocol: 'ssl', + protocol: EProtocol.ssl, }, { host: 'testnet.aranguren.org', ssl: 51002, tcp: 51001, - protocol: 'ssl', + protocol: EProtocol.ssl, }, { host: 'blackie.c3-soft.com', ssl: 57006, tcp: 57006, - protocol: 'ssl', + protocol: EProtocol.ssl, }, ], bitcoinRegtest: [ diff --git a/src/store/shapes/wallet.ts b/src/store/shapes/wallet.ts index f21cbad76..94a3c23a3 100644 --- a/src/store/shapes/wallet.ts +++ b/src/store/shapes/wallet.ts @@ -4,12 +4,10 @@ import { __WALLET_DEFAULT_SELECTED_NETWORK__ } from '../../constants/env'; import { IHeader } from '../../utils/types/electrum'; import { EAvailableNetwork } from '../../utils/networks'; import { objectKeys } from '../../utils/objectKeys'; -import { EFeeId } from '../types/fees'; import { IWalletItem, IWallet, IWalletStore, - ISendTransaction, IKeyDerivationPath, IAddressTypes, IAddresses, @@ -17,6 +15,7 @@ import { EAddressType, EBoostType, } from '../types/wallet'; +import { EFeeId, ISendTransaction } from 'beignet'; export const addressTypes: Readonly = { [EAddressType.p2wpkh]: { @@ -149,8 +148,9 @@ export const defaultHeader: Readonly = { }; export const defaultWalletShape: Readonly = { - id: 'wallet0', - name: '', + id: '', + name: 'wallet0', + seedHash: '', addresses: getAddressesShape(), addressIndex: getAddressIndexShape(), lastUsedAddressIndex: getAddressIndexShape(), diff --git a/src/store/slices/fees.ts b/src/store/slices/fees.ts index f49308865..ccd0292f7 100644 --- a/src/store/slices/fees.ts +++ b/src/store/slices/fees.ts @@ -1,5 +1,5 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; -import { IOnchainFees } from '../types/fees'; +import { IOnchainFees } from 'beignet'; export type TFeesState = { onchain: IOnchainFees; diff --git a/src/store/slices/settings.ts b/src/store/slices/settings.ts index d9bc3dc50..3bfc9bfea 100644 --- a/src/store/slices/settings.ts +++ b/src/store/slices/settings.ts @@ -5,12 +5,12 @@ import { ICustomElectrumPeer, TChest, TCoinSelectPreference, - TCustomElectrumPeers, TReceiveOption, TTheme, } from '../types/settings'; import { EAvailableNetwork } from '../../utils/networks'; import { EUnit } from '../types/wallet'; +import { TServer } from 'beignet'; export type TSettings = { enableAutoReadClipboard: boolean; @@ -24,7 +24,7 @@ export type TSettings = { rbf: boolean; theme: TTheme; unit: EUnit; - customElectrumPeers: TCustomElectrumPeers; + customElectrumPeers: Record; rapidGossipSyncUrl: string; selectedCurrency: string; selectedLanguage: string; diff --git a/src/store/types/fees.ts b/src/store/types/fees.ts index ea6c7eef7..dcd00ba12 100644 --- a/src/store/types/fees.ts +++ b/src/store/types/fees.ts @@ -1,3 +1,5 @@ +import { IOnchainFees } from 'beignet'; + export enum EFeeId { instant = 'instant', fast = 'fast', @@ -8,11 +10,7 @@ export enum EFeeId { none = 'none', } -//On-chain fee estimates in sats/vbyte -export interface IOnchainFees { - fast: number; // 10-20 mins - normal: number; // 20-60 mins - slow: number; // 1-2 hrs - minimum: number; - timestamp: number; +export interface IFees { + onchain: IOnchainFees; + override: boolean; } diff --git a/src/store/types/settings.ts b/src/store/types/settings.ts index bd78cfcff..ba773787d 100644 --- a/src/store/types/settings.ts +++ b/src/store/types/settings.ts @@ -1,11 +1,13 @@ +import { EProtocol } from 'beignet'; import { IWalletItem } from './wallet'; export type TTheme = 'dark' | 'light'; -export type TProtocol = 'ssl' | 'tcp'; - -export const isProtocol = (protocol: string): protocol is TProtocol => { - return ['tcp', 'ssl'].includes(protocol); +export const isProtocol = (protocol: unknown): protocol is EProtocol => { + if (typeof protocol === 'string') { + return Object.values(EProtocol).includes(protocol as EProtocol); + } + return false; }; export enum ETransactionSpeed { @@ -26,7 +28,7 @@ export interface ICustomElectrumPeer { host: string; ssl: number; //ssl port tcp: number; //tcp port - protocol: TProtocol; + protocol: EProtocol; } export type TReceiveOption = { diff --git a/src/store/types/ui.ts b/src/store/types/ui.ts index 53729cf6b..9fb98ef73 100644 --- a/src/store/types/ui.ts +++ b/src/store/types/ui.ts @@ -1,5 +1,5 @@ import { LNURLWithdrawParams, LNURLPayParams } from 'js-lnurl'; -import { IActivityItem, TOnchainActivityItem } from './activity'; +import { EActivityType, TOnchainActivityItem } from './activity'; import { SendStackParamList } from '../../navigation/bottom-sheet/SendNavigation'; export type ViewControllerParamList = { @@ -13,7 +13,9 @@ export type ViewControllerParamList = { forceTransfer: undefined; forgotPIN: undefined; highBalance: undefined; - newTxPrompt: { activityItem: IActivityItem }; + newTxPrompt: { + activityItem: { id: string; activityType: EActivityType; value: number }; + }; PINNavigation: { showLaterButton: boolean }; profileAddDataForm: undefined; receiveNavigation: undefined; @@ -37,7 +39,7 @@ export type TUiViewController = { // this type is needed because reselect doesn't offer good parameter typing export type IViewControllerData = { isOpen: boolean; - activityItem?: IActivityItem; + activityItem?: { id: string; activityType: EActivityType; value: number }; chestId?: string; onchainActivityItem?: TOnchainActivityItem; id?: string; diff --git a/src/store/types/wallet.ts b/src/store/types/wallet.ts index 54a3b1ffc..fb1450c12 100644 --- a/src/store/types/wallet.ts +++ b/src/store/types/wallet.ts @@ -1,9 +1,9 @@ import { EAvailableNetwork } from '../../utils/networks'; import { IExchangeRates } from '../../utils/exchange-rate'; import { IAddressTypeContent } from '../shapes/wallet'; -import { EFeeId } from './fees'; import { IHeader } from '../../utils/types/electrum'; import { IVin } from '../../utils/wallet'; +import { ISendTransaction } from 'beignet'; export enum EPaymentType { sent = 'sent', @@ -184,25 +184,6 @@ export interface IFormattedTransactions { [txId: string]: IFormattedTransaction; } -export interface ISendTransaction { - outputs: IOutput[]; - inputs: IUtxo[]; - changeAddress: string; - fiatAmount: number; - fee: number; //Total fee in sats - satsPerByte: number; - selectedFeeId: EFeeId; - message: string; // OP_RETURN data for a given transaction. - label: string; // User set label for a given transaction. - rbf: boolean; - boostType: EBoostType; - minFee: number; // (sats) Used for RBF/CPFP transactions where the fee needs to be greater than the original. - max: boolean; // If the user intends to send the max amount. - tags: string[]; - slashTagsUrl?: string; - lightningInvoice?: string; -} - export interface IBoostedTransaction { parentTransactions: string[]; // Array of parent txids to the currently boosted transaction. childTransaction: string; // Child txid of the currently boosted transaction. @@ -215,7 +196,7 @@ export interface IBoostedTransactions { } export interface IWallet { - id: TWalletName; + id: string; name: string; seedHash?: string; // Help components/hooks recognize when a seed is set/updated for the same wallet id/name. addresses: IWalletItem>; @@ -231,11 +212,7 @@ export interface IWallet { transactions: IWalletItem; transaction: IWalletItem; balance: IWalletItem; - addressType: { - bitcoin: EAddressType; - bitcoinTestnet: EAddressType; - bitcoinRegtest: EAddressType; - }; + addressType: IWalletItem; } export interface IWallets { diff --git a/src/store/utils/activity.ts b/src/store/utils/activity.ts index 3384a4d1e..4b186fa6d 100644 --- a/src/store/utils/activity.ts +++ b/src/store/utils/activity.ts @@ -4,7 +4,7 @@ import { TChannel } from '@synonymdev/react-native-ldk'; import { EPaymentType } from '../types/wallet'; import { EActivityType, TLightningActivityItem } from '../types/activity'; import { getBlocktankStore, dispatch } from '../helpers'; -import { getCurrentWallet } from '../../utils/wallet'; +import { getCurrentWallet, getOnChainWalletData } from '../../utils/wallet'; import { getLightningChannels } from '../../utils/lightning'; import { formatBoostedActivityItems } from '../../utils/boost'; import { onChainTransactionToActivityItem } from '../../utils/activity'; @@ -81,20 +81,19 @@ export const updateActivityList = (): Result => { * @returns {Result} */ export const updateOnChainActivityList = (): Result => { - const { currentWallet, selectedNetwork, selectedWallet } = getCurrentWallet(); - const blocktankTransactions = getBlocktankStore().paidOrders; - const blocktankOrders = getBlocktankStore().orders; - const boostedTransactions = - currentWallet.boostedTransactions[selectedNetwork]; - + let currentWallet = getOnChainWalletData(); if (!currentWallet) { console.warn( 'No wallet found. Cannot update activity list with transactions.', ); return ok(''); } + const { selectedNetwork, selectedWallet } = getCurrentWallet(); + const blocktankTransactions = getBlocktankStore().paidOrders; + const blocktankOrders = getBlocktankStore().orders; + const boostedTransactions = currentWallet.boostedTransactions; - const transactions = currentWallet.transactions[selectedNetwork]; + const transactions = currentWallet.transactions; const activityItems = Object.values(transactions).map((tx) => { return onChainTransactionToActivityItem({ transaction: tx, @@ -109,7 +108,6 @@ export const updateOnChainActivityList = (): Result => { selectedWallet, selectedNetwork, }); - dispatch(updateActivityItems(boostFormattedItems)); return ok('On chain transaction activity items updated'); diff --git a/src/store/utils/blocktank.ts b/src/store/utils/blocktank.ts index 54a79dd9c..9bcc39e75 100644 --- a/src/store/utils/blocktank.ts +++ b/src/store/utils/blocktank.ts @@ -282,9 +282,13 @@ export const startChannelPurchase = async ({ selectedWallet, selectedNetwork, }); - const channelOpenCost = buyChannelData.feeSat; + const buyChannelDataFeeSat = Math.ceil(buyChannelData.feeSat); + const buyChannelDataClientBalanceFeeSat = Math.ceil( + buyChannelData.clientBalanceSat, + ); + const channelOpenCost = buyChannelDataFeeSat; const channelOpenFee = Math.abs( - buyChannelData.clientBalanceSat - buyChannelData.feeSat, + buyChannelDataClientBalanceFeeSat - buyChannelDataFeeSat, ); // Ensure we have enough funds to pay for both the channel and the fee to broadcast the transaction. if (channelOpenCost > onchainBalance) { @@ -303,7 +307,7 @@ export const startChannelPurchase = async ({ outputs: [ { address: buyChannelData.payment.onchain.address, - value: buyChannelData.feeSat, + value: buyChannelDataFeeSat, index: 0, }, ], @@ -312,12 +316,11 @@ export const startChannelPurchase = async ({ const feeRes = updateFee({ satsPerByte: satPerVByteFee, - selectedWallet, - selectedNetwork, }); - if (feeRes.isOk()) { - txFeeInSats = feeRes.value.fee; + if (feeRes.isErr()) { + return err(feeRes.error.message); } + txFeeInSats = feeRes.value.fee; return ok({ order: buyChannelData, @@ -367,7 +370,6 @@ export const confirmChannelPurchase = async ({ const broadcastResponse = await broadcastTransaction({ rawTx: rawTx.value.hex, subscribeToOutputAddress: false, - selectedWallet, selectedNetwork, }); if (broadcastResponse.isErr()) { @@ -381,7 +383,7 @@ export const confirmChannelPurchase = async ({ dispatch(addPaidBlocktankOrder({ orderId, txid: broadcastResponse.value })); // Reset tx data. - resetSendTransaction({ selectedWallet, selectedNetwork }); + await resetSendTransaction(); watchOrder(orderId).then(); dispatch(setLightningSetupStep(0)); diff --git a/src/store/utils/fees.ts b/src/store/utils/fees.ts index aab937a2e..2209c1ea4 100644 --- a/src/store/utils/fees.ts +++ b/src/store/utils/fees.ts @@ -4,15 +4,19 @@ import { dispatch, getFeesStore } from '../helpers'; import { updateOnchainFees } from '../slices/fees'; import { getFeeEstimates } from '../../utils/wallet/transactions'; import { EAvailableNetwork } from '../../utils/networks'; +import { getSelectedNetwork } from '../../utils/wallet'; +import { IOnchainFees } from 'beignet'; export const REFRESH_INTERVAL = 60 * 30; // in seconds, 30 minutes export const updateOnchainFeeEstimates = async ({ - selectedNetwork, + selectedNetwork = getSelectedNetwork(), forceUpdate = false, + feeEstimates, }: { selectedNetwork: EAvailableNetwork; forceUpdate?: boolean; + feeEstimates?: IOnchainFees; }): Promise> => { const feesStore = getFeesStore(); const timestamp = feesStore.onchain.timestamp; @@ -26,12 +30,15 @@ export const updateOnchainFeeEstimates = async ({ return ok('On-chain fee estimates are up to date.'); } - const feeEstimates = await getFeeEstimates(selectedNetwork); - if (feeEstimates.isErr()) { - return err(feeEstimates.error); + if (!feeEstimates) { + const feeEstimatesRes = await getFeeEstimates(selectedNetwork); + if (feeEstimatesRes.isErr()) { + return err(feeEstimatesRes.error); + } + feeEstimates = feeEstimatesRes.value; } - dispatch(updateOnchainFees(feeEstimates.value)); + dispatch(updateOnchainFees(feeEstimates)); return ok('Successfully updated on-chain fee estimates.'); }; diff --git a/src/store/utils/settings.ts b/src/store/utils/settings.ts index 7b5407151..c95797056 100644 --- a/src/store/utils/settings.ts +++ b/src/store/utils/settings.ts @@ -34,6 +34,9 @@ export const wipeApp = async ({ selectedWallet = getSelectedWallet(); } + // Reset Redux stores & persisted storage + dispatch({ type: actions.WIPE_APP }); + // Reset everything else await Promise.all([ removePin(), @@ -41,9 +44,6 @@ export const wipeApp = async ({ wipeLdkStorage({ selectedWallet }), ]); - // Reset Redux stores & persisted storage - dispatch({ type: actions.WIPE_APP }); - if (showNotification) { showToast({ type: 'success', diff --git a/src/store/utils/ui.ts b/src/store/utils/ui.ts index 740495895..10a29c8ef 100644 --- a/src/store/utils/ui.ts +++ b/src/store/utils/ui.ts @@ -4,6 +4,7 @@ import { getBuildNumber } from 'react-native-device-info'; import { getActivityStore, dispatch } from '../helpers'; import { closeSheet, setAppUpdateInfo, showSheet } from '../slices/ui'; import { TAvailableUpdate, ViewControllerParamList } from '../types/ui'; +import { EActivityType } from '../types/activity'; const releaseUrl = 'https://github.com/synonymdev/bitkit/releases/download/updater/release.json'; @@ -17,6 +18,24 @@ export const showBottomSheet = ( dispatch(showSheet({ view, params })); }; +export const showNewOnchainTxPrompt = ({ + id, + value, +}: { + id: string; + value: number; +}): void => { + showBottomSheet('newTxPrompt', { + activityItem: { + id, + activityType: EActivityType.onchain, + value, + }, + }); + + dispatch(closeSheet('receiveNavigation')); +}; + export const showNewTxPrompt = (txId: string): void => { const activityItem = getActivityStore().items.find(({ id }) => id === txId); diff --git a/src/utils/activity/index.ts b/src/utils/activity/index.ts index 7057ea1d8..6b4b52aee 100644 --- a/src/utils/activity/index.ts +++ b/src/utils/activity/index.ts @@ -1,7 +1,7 @@ import i18n, { i18nTime } from '../../utils/i18n'; import { btcToSats } from '../conversion'; import { TPaidBlocktankOrders } from '../../store/types/blocktank'; -import { EPaymentType, IFormattedTransaction } from '../../store/types/wallet'; +import { EPaymentType } from '../../store/types/wallet'; import { EActivityType, IActivityItem, @@ -10,6 +10,7 @@ import { import { err, ok, Result } from '@synonymdev/result'; import { getActivityStore } from '../../store/helpers'; import { IBtOrder } from '@synonymdev/blocktank-lsp-http-client'; +import { IFormattedTransaction } from 'beignet'; /** * Converts a formatted transaction to an activity item @@ -46,14 +47,14 @@ export const onChainTransactionToActivityItem = ({ }); return { - exists: true, + exists: transaction?.exists ?? true, id: transaction.txid, activityType: EActivityType.onchain, txType: transaction.type, txId: transaction.txid, value: btcToSats(Math.abs(amount)), fee: btcToSats(Math.abs(transaction.fee)), - feeRate: transaction.satsPerByte, + feeRate: Math.round(transaction.satsPerByte), address: transaction.address, confirmed: transaction.height > 0, isBoosted: false, diff --git a/src/utils/blocktank/index.ts b/src/utils/blocktank/index.ts index 49ef43a36..9896ea951 100644 --- a/src/utils/blocktank/index.ts +++ b/src/utils/blocktank/index.ts @@ -29,6 +29,7 @@ import { setGeoBlock } from '../../store/utils/user'; import { refreshWallet } from '../wallet'; import { DEFAULT_CHANNEL_DURATION } from '../../screens/Lightning/CustomConfirm'; import { __BLOCKTANK_HOST__ } from '../../constants/env'; +import { IBt0ConfMinTxFeeWindow } from '@synonymdev/blocktank-lsp-http-client/dist/shared/IBt0ConfMinTxFeeWindow'; const bt = new BlocktankClient(); @@ -205,9 +206,7 @@ export const getOrder = async (orderId: string): Promise> => { export const getMin0ConfTxFee = async ( orderId: string, -): Promise< - Result<{ id: string; validityEndsAt: string; satPerVByte: number }> -> => { +): Promise> => { try { const res = await bt.getMin0ConfTxFee(orderId); return ok(res); diff --git a/src/utils/boost.ts b/src/utils/boost.ts index e310d0d90..4e97f6c5e 100644 --- a/src/utils/boost.ts +++ b/src/utils/boost.ts @@ -322,7 +322,7 @@ export const calculateBoostTransactionValue = ({ const parents = boostedTransaction.parentTransactions; parents.forEach((parentTxid) => { const parent = boostedTransactions[parentTxid]; - value = value - parent.fee; + value = Math.round(value - parent.fee); }); } diff --git a/src/utils/checks.ts b/src/utils/checks.ts index c328cd215..d6c912638 100644 --- a/src/utils/checks.ts +++ b/src/utils/checks.ts @@ -54,7 +54,6 @@ export const reportImpactedAddressBalance = async ({ const balanceRes = await getAddressBalance({ addresses: allAddresses, - selectedNetwork, }); if (balanceRes.isErr()) { diff --git a/src/utils/electrum/index.ts b/src/utils/electrum/index.ts index 70110eeb4..4205fdb95 100644 --- a/src/utils/electrum/index.ts +++ b/src/utils/electrum/index.ts @@ -1,14 +1,6 @@ -import * as electrum from 'rn-electrum-client/helpers'; -import { err, ok, Result } from '@synonymdev/result'; - import { EAvailableNetwork } from '../networks'; -import { getSelectedNetwork } from '../wallet'; -import { connectToElectrum } from '../wallet/electrum'; -import { TProtocol } from '../../store/types/settings'; - -const hardcodedPeers = require('rn-electrum-client/helpers/peers.json'); +import { EProtocol } from 'beignet'; -const POLLING_INTERVAL = 1000 * 20; export const defaultElectrumPorts = ['51002', '50002', '51001', '50001']; /** @@ -18,9 +10,9 @@ export const defaultElectrumPorts = ['51002', '50002', '51001', '50001']; */ export const getDefaultPort = ( selectedNetwork: EAvailableNetwork, - protocol: TProtocol, + protocol: EProtocol, ): number => { - if (protocol === 'ssl') { + if (protocol === EProtocol.ssl) { return selectedNetwork === 'bitcoinTestnet' ? 51002 : 50002; } else { return selectedNetwork === 'bitcoinTestnet' ? 51001 : 50001; @@ -35,165 +27,14 @@ export const getDefaultPort = ( export const getProtocolForPort = ( port: string, selectedNetwork?: EAvailableNetwork, -): TProtocol => { +): EProtocol => { if (port === '443') { - return 'ssl'; + return EProtocol.ssl; } if (selectedNetwork === 'bitcoinTestnet') { - return port === '51002' ? 'ssl' : 'tcp'; - } - - return port === '50002' ? 'ssl' : 'tcp'; -}; - -export interface IFormattedPeerData { - ip?: string; - host: string; - version?: string; - ssl: string | number; - tcp: string | number; -} - -/** - * Formats the peer data response from an Electrum server. - * @param {[string, string, [string, string, string]]} data - * @return Result - */ -export const formatPeerData = ( - data: [string, string, [string, string, string]], -): Result => { - try { - if (!data) { - return err('No data provided.'); - } - if (data?.length !== 3) { - return err('The peer data appears to be invalid.'); - } - if (data[2]?.length < 2) { - return err('The peer data appears to be invalid.'); - } - const [ip, host, ports] = data; - const [version, ssl, tcp] = ports; - return ok({ - ip, - host, - version, - ssl, - tcp, - }); - } catch (e) { - return err(e); - } -}; - -/** - * Returns an array of peers. - * If unable to acquire peers from an Electrum server the method will default to the hardcoded peers in peers.json. - * @param {EAvailableNetwork} [selectedNetwork] - * @return Promise> - */ -export const getPeers = async ({ - selectedNetwork, -}: { - selectedNetwork: EAvailableNetwork; -}): Promise> => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const response = await electrum.getPeers({ network: selectedNetwork }); - if (!response.error) { - // Return an array of peers provided by the currently connected electrum server. - let peers: IFormattedPeerData[] = []; - await Promise.all( - response.data.map(async (peer) => { - const formattedPeer = await formatPeerData(peer); - if (formattedPeer.isOk()) { - peers.push(formattedPeer.value); - } - }), - ); - if (peers?.length > 0) { - return ok(peers); - } - } - // No peers available grab hardcoded peers instead. - return ok(hardcodedPeers[selectedNetwork]); - } catch (e) { - console.log(e); - return err(e); + return port === '51002' ? EProtocol.ssl : EProtocol.tcp; } -}; -type ElectrumConnectionPubSub = { - publish: (isConnected: boolean) => void; - subscribe: ( - callback: (isConnected: boolean) => void, - ) => ElectrumConnectionSubscription; + return port === '50002' ? EProtocol.ssl : EProtocol.tcp; }; - -type ElectrumConnectionSubscription = { - remove(): void; -}; - -/** - * Background task that checks the connection to the Electrum server with a PubSub - * If connection was lost this will try to reconnect in the specified interval - */ -export const electrumConnection = ((): ElectrumConnectionPubSub => { - let subscribers: Set<(isConnected: boolean) => void> = new Set(); - let latestState: boolean | null = null; - - setInterval(async () => { - if (subscribers.size === 0) { - return; - } - - try { - const { error } = await electrum.pingServer(); - - if (error) { - console.log('Connection to Electrum Server lost, reconnecting...'); - const response = await connectToElectrum(); - - if (response.isErr()) { - electrumConnection.publish(false); - } - } else { - electrumConnection.publish(true); - } - } catch (e) { - console.error(e); - } - }, POLLING_INTERVAL); - - const publish = (isConnected: boolean): void => { - // Skip if no subscribers - if (subscribers.size === 0) { - return; - } - - // Skip if state hasn't changed - if (latestState === isConnected) { - return; - } - - latestState = isConnected; - subscribers.forEach((callback) => callback(isConnected)); - }; - - const subscribe = ( - callback: (isConnected: boolean) => void, - ): ElectrumConnectionSubscription => { - subscribers.add(callback); - - return { - remove: (): void => { - subscribers.delete(callback); - }, - }; - }; - - return { publish, subscribe }; -})(); diff --git a/src/utils/lightning/index.ts b/src/utils/lightning/index.ts index ea8627695..12bebae2c 100644 --- a/src/utils/lightning/index.ts +++ b/src/utils/lightning/index.ts @@ -28,13 +28,12 @@ import lm, { import { getBlockHeader, getBlockHex, - getScriptPubKeyHistory, getTransactionMerkle, - getTransactions, transactionExists, } from '../wallet/electrum'; import { getMnemonicPhrase, + getOnChainWalletElectrum, getReceiveAddress, getSelectedNetwork, getSelectedWallet, @@ -63,7 +62,6 @@ import { updateLightningNodeVersionThunk, } from '../../store/utils/lightning'; import { promiseTimeout, reduceValue, sleep, tryNTimes } from '../helpers'; -import { broadcastTransaction } from '../wallet/transactions'; import { EActivityType, TLightningActivityItem, @@ -222,30 +220,19 @@ export const setupLdk = async ({ break; } const getAddress = async (): Promise => { - const res = await getReceiveAddress({ selectedNetwork, selectedWallet }); + const res = await getReceiveAddress({ selectedNetwork }); if (res.isOk()) { return res.value; } return ''; }; - const _broadcastTransaction = async (rawTx: string): Promise => { - const res = await broadcastTransaction({ - rawTx, - selectedNetwork, - selectedWallet, - subscribeToOutputAddress: false, - }); - if (res.isErr()) { - return ''; - } - return res.value; - }; const storageRes = await setLdkStoragePath(); if (storageRes.isErr()) { return err(storageRes.error); } const rapidGossipSyncUrl = getStore().settings.rapidGossipSyncUrl; + const electrum = getOnChainWalletElectrum(); const lmStart = await lm.start({ account: account.value, getFees: async () => { @@ -264,13 +251,15 @@ export const setupLdk = async ({ network, getBestBlock, getAddress, - broadcastTransaction: _broadcastTransaction, + broadcastTransaction: (rawTx) => + electrum.broadcastTransaction({ + rawTx, + subscribeToOutputAddress: false, + }), getTransactionData: (txId) => getTransactionData(txId, selectedNetwork), - getScriptPubKeyHistory: (scriptPubkey) => { - return getScriptPubKeyHistory(scriptPubkey, selectedNetwork); - }, + getScriptPubKeyHistory: electrum.getScriptPubKeyHistory, getTransactionPosition: (params) => { - return getTransactionPosition({ ...params, selectedNetwork }); + return getTransactionPosition(params); }, forceCloseOnStartup: { forceClose: staleBackupRecoveryMode, @@ -1012,9 +1001,9 @@ export const getTransactionData = async ( if (selectedNetwork) { selectedNetwork = getSelectedNetwork(); } - const response = await getTransactions({ + const electrum = getOnChainWalletElectrum(); + const response = await electrum.getTransactions({ txHashes: data, - selectedNetwork, }); //Unable to reach Electrum server. @@ -1034,7 +1023,7 @@ export const getTransactionData = async ( hex: hex_encoded_tx, vout, } = response.value.data[0].result; - const header = getBlockHeader({ selectedNetwork }); + const header = getBlockHeader(); const currentHeight = header.height; let confirmedHeight = 0; if (confirmations) { @@ -1042,7 +1031,6 @@ export const getTransactionData = async ( } const hexEncodedHeader = await getBlockHex({ height: confirmedHeight, - selectedNetwork, }); if (hexEncodedHeader.isErr()) { return transactionData; @@ -1071,16 +1059,13 @@ export const getTransactionData = async ( export const getTransactionPosition = async ({ tx_hash, height, - selectedNetwork, }: { tx_hash: string; height: number; - selectedNetwork?: EAvailableNetwork; }): Promise => { const response = await getTransactionMerkle({ tx_hash, height, - selectedNetwork, }); if (response.error || isNaN(response.data?.pos) || response.data?.pos < 0) { return -1; diff --git a/src/utils/scanner.ts b/src/utils/scanner.ts index ce404c39a..a3bd48abf 100644 --- a/src/utils/scanner.ts +++ b/src/utils/scanner.ts @@ -3,7 +3,6 @@ */ import bip21 from 'bip21'; -import { address as bitcoinJSAddress } from 'bitcoinjs-lib'; import { err, ok, Result } from '@synonymdev/result'; import SDK from '@synonymdev/slashtags-sdk'; import { TInvoice } from '@synonymdev/react-native-ldk'; @@ -20,7 +19,6 @@ import { import { getOnchainTransactionData, getTransactionInputValue, - parseOnChainPaymentRequest, } from './wallet/transactions'; import { dispatch, getLightningStore } from '../store/helpers'; import { showToast, ToastOptions } from './notifications'; @@ -35,15 +33,14 @@ import { decodeLightningInvoice, getLightningBalance, } from './lightning'; -import { availableNetworks, networks, EAvailableNetwork } from './networks'; +import { EAvailableNetwork } from './networks'; import { savePeer } from '../store/utils/lightning'; import { TWalletName } from '../store/types/wallet'; import { sendNavigation } from '../navigation/bottom-sheet/SendNavigation'; import { rootNavigation } from '../navigation/root/RootNavigator'; import { findlnurl, handleLnurlAuth } from './lnurl'; import i18n from './i18n'; - -const availableNetworksList = availableNetworks(); +import { parseOnChainPaymentRequest, validateAddress } from 'beignet'; export enum EQRDataType { bitcoinAddress = 'bitcoinAddress', @@ -80,7 +77,7 @@ export type TBitcoinUrl = { qrDataType: EQRDataType.bitcoinAddress; address: string; sats: number; - network?: EAvailableNetwork | EAvailableNetwork; + network?: EAvailableNetwork; message?: string; slashTagsUrl?: string; }; @@ -133,53 +130,6 @@ export type TTreasureChestUrl = { chestId: string; }; -export const validateAddress = ({ - address, - selectedNetwork, -}: { - address: string; - selectedNetwork?: EAvailableNetwork; -}): { - isValid: boolean; - network: EAvailableNetwork; -} => { - try { - //Validate address for all available networks - let isValid = false; - let network: EAvailableNetwork = EAvailableNetwork.bitcoin; - - //Validate address for a specific network - if (selectedNetwork !== undefined) { - try { - bitcoinJSAddress.toOutputScript(address, networks[selectedNetwork]); - return { isValid: true, network: selectedNetwork }; - } catch { - // In the event the normal check fails, determine if this is a taproot address. - const taprootRes = isValidBech32mEncodedString(address); - if (taprootRes.isValid && taprootRes.network === selectedNetwork) { - return { isValid: taprootRes.isValid, network: taprootRes.network }; - } - } - return { isValid: false, network: selectedNetwork }; - } - - for (let i = 0; i < availableNetworksList.length; i++) { - const validateRes = validateAddress({ - address, - selectedNetwork: availableNetworksList[i], - }); - if (validateRes.isValid) { - isValid = validateRes.isValid; - network = validateRes.network; - break; - } - } - return { isValid, network }; - } catch (e) { - return { isValid: false, network: EAvailableNetwork.bitcoin }; - } -}; - /** * Returns if the provided string is a valid Bech32m encoded string (taproot/p2tr address). * @param {string} address @@ -217,6 +167,7 @@ export type TProcessedData = { * @param {EAvailableNetwork} [selectedNetwork] * @param {TWalletName} [selectedWallet] * @param {Array} [skip] + * @param {boolean} [showErrors] */ export const processInputData = async ({ data, @@ -493,16 +444,13 @@ export const decodeQRData = async ( //Plain bitcoin address or Bitcoin address URI try { //Validate address for selected network - const onChainParseResponse = parseOnChainPaymentRequest( - data, - selectedNetwork, - ); + const onChainParseResponse = parseOnChainPaymentRequest(data); if (onChainParseResponse.isOk()) { const { address, sats, message, network } = onChainParseResponse.value; foundNetworksInQR.push({ qrDataType: EQRDataType.bitcoinAddress, address, - network, + network: EAvailableNetwork[network], sats, message, }); @@ -653,10 +601,7 @@ export const processBitcoinTransactionData = async ({ selectedWallet, selectedNetwork, }); - const transaction = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transaction = getOnchainTransactionData(); if (transaction.isErr()) { return err(transaction.error.message); } @@ -824,23 +769,25 @@ export const handleData = async ({ } const qrDataType = data.qrDataType; - - if ( - qrDataType === EQRDataType.bitcoinAddress && - data.network && - data.network !== selectedNetwork - ) { - showToast({ - type: 'error', - title: i18n.t('other:qr_error_network_header'), - description: i18n.t('other:qr_error_network_text', { - selectedNetwork, - dataNetwork: data.network, - }), - }); - return err( - `Bitkit is currently set to ${selectedNetwork} but data is for ${data.network}.`, - ); + if (qrDataType === EQRDataType.bitcoinAddress && data.network) { + if ( + EAvailableNetwork[selectedNetwork] === EAvailableNetwork.bitcoin && + EAvailableNetwork[data.network] !== EAvailableNetwork[selectedNetwork] + ) { + showToast({ + type: 'error', + title: i18n.t('other:qr_error_network_header'), + description: i18n.t('other:qr_error_network_text', { + selectedNetwork, + dataNetwork: data.network, + }), + }); + return err( + `Bitkit is currently set to ${selectedNetwork} but data is for ${ + EAvailableNetwork[data.network] + }.`, + ); + } } //TODO(slashtags): Register Bitkit to handle all slash?x:// protocols @@ -868,8 +815,6 @@ export const handleData = async ({ sendNavigation.navigate('Amount'); updateSendTransaction({ - selectedWallet, - selectedNetwork, transaction: { label: message, outputs: [{ address: address, value: sats, index: 0 }], @@ -912,8 +857,6 @@ export const handleData = async ({ } updateSendTransaction({ - selectedWallet, - selectedNetwork, transaction: { outputs: [ { diff --git a/src/utils/slashtags/index.ts b/src/utils/slashtags/index.ts index e33cfca81..8f784ed7a 100644 --- a/src/utils/slashtags/index.ts +++ b/src/utils/slashtags/index.ts @@ -242,7 +242,6 @@ export const updateSlashPayConfig = debounce( ({ type }) => type === addressType, )?.value; const newAddress = await getReceiveAddress({ - selectedWallet, selectedNetwork, }); if (newAddress.isOk() && currentAddress !== newAddress.value) { diff --git a/src/utils/slashtags2/index.ts b/src/utils/slashtags2/index.ts index 86e4f1859..cbcacb0c5 100644 --- a/src/utils/slashtags2/index.ts +++ b/src/utils/slashtags2/index.ts @@ -115,7 +115,6 @@ export const updateSlashPayConfig2 = debounce( ({ type }) => type === addressType, )?.value; const newAddress = await getReceiveAddress({ - selectedWallet, selectedNetwork, }); if (newAddress.isOk() && currentAddress !== newAddress.value) { diff --git a/src/utils/startup/index.ts b/src/utils/startup/index.ts index c63083b43..2515fd31e 100644 --- a/src/utils/startup/index.ts +++ b/src/utils/startup/index.ts @@ -8,12 +8,12 @@ import { refreshWallet, getSelectedNetwork, getSelectedWallet, + setupOnChainWallet, + getSelectedAddressType, } from '../wallet'; -import { createWallet, updateExchangeRates } from '../../store/actions/wallet'; +import { createWallet } from '../../store/actions/wallet'; import { getWalletStore } from '../../store/helpers'; import { refreshBlocktankInfo } from '../../store/utils/blocktank'; -import { connectToElectrum, subscribeToHeader } from '../wallet/electrum'; -import { updateOnchainFeeEstimates } from '../../store/utils/fees'; import { keepLdkSynced, setupLdk } from '../lightning'; import { setupBlocktank, watchPendingOrders } from '../blocktank'; import { updateSlashPayConfig2 } from '../slashtags2'; @@ -55,8 +55,6 @@ export const restoreSeed = async ({ mnemonic, bip39Passphrase, restore: true, - addressAmount: 25, - changeAddressAmount: 25, }); if (res.isErr()) { return res; @@ -110,29 +108,6 @@ export const startWalletServices = async ({ await promiseTimeout(2500, setupBlocktank(selectedNetwork)); await promiseTimeout(2500, refreshBlocktankInfo()); - updateExchangeRates().then(); - - // Before we do anything we should connect to an Electrum server - if (onchain || lightning) { - const electrumResponse = await connectToElectrum({ - showNotification: !restore, - selectedNetwork, - }); - if (electrumResponse.isOk()) { - isConnectedToElectrum = true; - // Ensure the on-chain wallet & LDK syncs when a new block is detected. - const onReceive = (): void => { - refreshWallet({ - onchain, - lightning, - selectedWallet, - selectedNetwork, - }); - }; - // Ensure we are subscribed to and save new header information. - subscribeToHeader({ selectedNetwork, onReceive }).then(); - } - } const mnemonicResponse = await getMnemonicPhrase(); if (mnemonicResponse.isErr()) { @@ -147,7 +122,21 @@ export const startWalletServices = async ({ if (createRes.isErr()) { return err(createRes.error.message); } + } else { + const onChainSetupRes = await setupOnChainWallet({ + name: selectedWallet, + selectedNetwork, + bip39Passphrase: await getBip39Passphrase(), + addressType: getSelectedAddressType({ + selectedWallet, + selectedNetwork, + }), + }); + if (onChainSetupRes.isErr()) { + return err(onChainSetupRes.error.message); + } } + isConnectedToElectrum = true; // Setup LDK if (lightning && isConnectedToElectrum) { @@ -164,10 +153,9 @@ export const startWalletServices = async ({ if (onchain || lightning) { await Promise.all([ - updateOnchainFeeEstimates({ selectedNetwork, forceUpdate: true }), // if we restore wallet, we need to generate addresses for all types refreshWallet({ - onchain: isConnectedToElectrum, + onchain: restore, lightning: isConnectedToElectrum, scanAllAddresses: restore, updateAllAddressTypes: true, // Ensure we scan all address types when spinning up the app. diff --git a/src/utils/wallet/checks.ts b/src/utils/wallet/checks.ts index 893212d7b..5239a4a7e 100644 --- a/src/utils/wallet/checks.ts +++ b/src/utils/wallet/checks.ts @@ -153,7 +153,7 @@ export const runStorageCheck = async ({ }), ); - await clearUtxos({ selectedWallet, selectedNetwork }); + await clearUtxos(); await refreshWallet({ onchain: true, @@ -234,8 +234,6 @@ export const addressStorageCheck = async ({ } const address = await _createMinMaxData({ - selectedWallet, - selectedNetwork, addressType: addressType.type, keyDerivationPath, minMaxAddresses, @@ -245,8 +243,6 @@ export const addressStorageCheck = async ({ return err(address.error.message); } const changeAddress = await _createMinMaxData({ - selectedWallet, - selectedNetwork, addressType: addressType.type, keyDerivationPath, minMaxAddresses: minMaxChangeAddresses, @@ -289,15 +285,11 @@ export const addressStorageCheck = async ({ * @returns {Promise>} */ const _createMinMaxData = async ({ - selectedWallet, - selectedNetwork, addressType, keyDerivationPath, minMaxAddresses, isChangeAddress, }: { - selectedWallet: TWalletName; - selectedNetwork: EAvailableNetwork; addressType: EAddressType; keyDerivationPath: IKeyDerivationPath; minMaxAddresses: TGetMinMaxObject; @@ -317,8 +309,6 @@ const _createMinMaxData = async ({ const minStoredAddress = minMaxAddresses.min; const minStoredAddressIndex = minStoredAddress.index; const minGeneratedAddress = await generateAddresses({ - selectedNetwork, - selectedWallet, addressAmount: isChangeAddress ? 0 : 1, changeAddressAmount: isChangeAddress ? 1 : 0, addressIndex: minStoredAddressIndex, @@ -342,8 +332,6 @@ const _createMinMaxData = async ({ const maxStoredAddress = minMaxAddresses.max; const maxStoredAddressIndex = maxStoredAddress.index; const maxGeneratedAddress = await generateAddresses({ - selectedNetwork, - selectedWallet, addressAmount: isChangeAddress ? 0 : 1, changeAddressAmount: isChangeAddress ? 1 : 0, addressIndex: maxStoredAddressIndex, @@ -424,8 +412,6 @@ export const getImpactedAddresses = async ({ ); const allGeneratedAddresses = await generateAddresses({ - selectedWallet, - selectedNetwork, addressAmount: (address.maxGeneratedAddress?.index ?? 0) + 1, changeAddressAmount: (changeAddress.maxGeneratedAddress?.index ?? 0) + 1, addressIndex: address.minGeneratedAddress?.index ?? 0, diff --git a/src/utils/wallet/electrum.ts b/src/utils/wallet/electrum.ts index 7244bf18f..2c048cc5e 100644 --- a/src/utils/wallet/electrum.ts +++ b/src/utils/wallet/electrum.ts @@ -1,36 +1,24 @@ -import * as electrum from 'rn-electrum-client/helpers'; -import { Block } from 'bitcoinjs-lib'; import { err, ok, Result } from '@synonymdev/result'; import { EAvailableNetwork } from '../networks'; import { - IAddresses, - IAddress, - IUtxo, - TWalletName, - EAddressType, -} from '../../store/types/wallet'; -import { - getAddressFromScriptPubKey, - getCurrentWallet, - getScriptHash, + getCustomElectrumPeers, + getOnChainWallet, + getOnChainWalletElectrum, getSelectedNetwork, - getSelectedWallet, ITransaction, - ITxHash, refreshWallet, } from './index'; -import { ICustomElectrumPeer } from '../../store/types/settings'; -import { addressTypes } from '../../store/shapes/wallet'; -import { updateHeader } from '../../store/actions/wallet'; -import { getSettingsStore, getWalletStore } from '../../store/helpers'; import { + EAvailableNetworks, + EProtocol, + IAddress, + IAddresses, IHeader, - IGetHeaderResponse, - TGetAddressHistory, -} from '../types/electrum'; -import { GAP_LIMIT, CHUNK_LIMIT } from './constants'; -import { objectKeys } from '../objectKeys'; + ITxHash, + IUtxo, + TServer, +} from 'beignet'; export interface IGetUtxosResponse { utxos: IUtxo[]; @@ -46,315 +34,77 @@ export type TUnspentAddressScriptHashData = { * @returns {Promise} */ export const isConnectedElectrum = async (): Promise => { - const { error } = await electrum.pingServer(); - - if (error) { - return false; - } else { - return true; - } + const electrum = getOnChainWalletElectrum(); + return electrum.isConnected(); }; /** * Returns UTXO's for a given wallet and network along with the available balance. * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] * @param {boolean} [scanAllAddresses] * @returns {Promise>} */ export const getUtxos = async ({ - selectedWallet, - selectedNetwork, scanAllAddresses = false, }: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; scanAllAddresses?: boolean; }): Promise> => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const { currentWallet } = getCurrentWallet({ - selectedNetwork, - selectedWallet, - }); - - const addressTypeKeys = objectKeys(EAddressType); - let addresses = {} as IAddresses; - let changeAddresses = {} as IAddresses; - let existingUtxos: { [key: string]: IUtxo } = {}; - - for (const addressType of addressTypeKeys) { - const addressCount = Object.keys( - currentWallet.addresses[selectedNetwork][addressType], - )?.length; - - // Check if addresses of this type have been generated. If not, skip. - if (addressCount <= 0) { - break; - } - - // Grab the current index for both addresses and change addresses. - const addressIndex = - currentWallet.addressIndex[selectedNetwork][addressType].index; - const changeAddressIndex = - currentWallet.changeAddressIndex[selectedNetwork][addressType].index; - - // Grab all addresses and change addresses. - const allAddresses = - currentWallet.addresses[selectedNetwork][addressType]; - const allChangeAddresses = - currentWallet.changeAddresses[selectedNetwork][addressType]; - - // Instead of scanning all addresses, adhere to the gap limit. - if (!scanAllAddresses && addressIndex >= 0 && changeAddressIndex >= 0) { - Object.values(allAddresses).map((a) => { - if (Math.abs(a.index - addressIndex) <= GAP_LIMIT) { - addresses[a.scriptHash] = a; - } - }); - Object.values(allChangeAddresses).map((a) => { - if (Math.abs(a.index - changeAddressIndex) <= GAP_LIMIT) { - changeAddresses[a.scriptHash] = a; - } - }); - } else { - addresses = { ...addresses, ...allAddresses }; - changeAddresses = { ...changeAddresses, ...allChangeAddresses }; - } - } - - // Make sure we're re-check existing utxos that may exist outside the gap limit and putting them in the necessary format. - currentWallet.utxos[selectedNetwork].map((utxo) => { - existingUtxos[utxo.scriptHash] = utxo; - }); - - const data: TUnspentAddressScriptHashData = { - ...addresses, - ...changeAddresses, - ...existingUtxos, - }; - - return listUnspentAddressScriptHashes({ addresses: data, selectedNetwork }); - } catch (e) { - return err(e); - } + const wallet = getOnChainWallet(); + return await wallet.getUtxos({ scanAllAddresses }); }; /** * Formats a provided array of addresses a returns their UTXO's & balances. - * @param {EAvailableNetwork} [selectedNetwork] * @param {IAddress[]} allAddresses * @returns {Promise>} */ export const getAddressUtxos = async ({ - selectedNetwork, allAddresses, }: { - selectedNetwork?: EAvailableNetwork; allAddresses: IAddress[]; }): Promise> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } let addresses: IAddresses = {}; allAddresses.map((a) => { addresses[a.scriptHash] = a; }); - return listUnspentAddressScriptHashes({ selectedNetwork, addresses }); + return listUnspentAddressScriptHashes({ addresses }); }; /** * Queries Electrum to return the available UTXO's and balance of the provided addresses. - * @param {EAvailableNetwork} [selectedNetwork] * @param {TUnspentAddressScriptHashData} addresses */ export const listUnspentAddressScriptHashes = async ({ - selectedNetwork, addresses, }: { - selectedNetwork?: EAvailableNetwork; addresses: TUnspentAddressScriptHashData; }): Promise> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } + const electrum = getOnChainWalletElectrum(); const unspentAddressResult = await electrum.listUnspentAddressScriptHashes({ - scriptHashes: { - key: 'scriptHash', - data: addresses, - }, - network: selectedNetwork, + addresses, }); - if (unspentAddressResult.error) { - throw unspentAddressResult.data; + if (unspentAddressResult.isErr()) { + throw unspentAddressResult.error.message; } - let balance = 0; - let utxos: IUtxo[] = []; - unspentAddressResult.data.map(({ data, result }) => { - if (result?.length > 0) { - return result.map((unspentAddress: IUtxo) => { - balance = balance + unspentAddress.value; - utxos.push({ - ...data, - ...unspentAddress, - }); - }); - } - }); + const { balance, utxos } = unspentAddressResult.value; return ok({ utxos, balance }); }; -export interface ISubscribeToAddress { - data: { - id: number; - jsonrpc: string; - result: null; - }; - error: boolean; - id: number; - method: string; -} - /** * Subscribes to a number of address script hashes for receiving. - * @param {string[]} scriptHashes - * @param {EAvailableNetwork} [selectedNetwork] - * @param {TWalletName} [selectedWallet] + * @param {string[]} [scriptHashes] + * @param {Function} [onReceive] * @return {Promise>} */ export const subscribeToAddresses = async ({ scriptHashes = [], onReceive, - selectedNetwork, - selectedWallet, }: { scriptHashes?: string[]; onReceive?: () => void; - selectedNetwork?: EAvailableNetwork; - selectedWallet?: TWalletName; } = {}): Promise> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const { currentWallet } = getCurrentWallet({ - selectedNetwork, - selectedWallet, - }); - const addressTypeKeys = objectKeys(addressTypes); - - // Gather the receiving address scripthash for each address type if no scripthashes were provided. - if (!scriptHashes.length) { - for (const addressType of addressTypeKeys) { - const addresses = currentWallet.addresses[selectedNetwork][addressType]; - const addressCount = Object.keys(addresses).length; - - // Check if addresses of this type have been generated. If not, skip. - if (addressCount > 0) { - let addressIndex = - currentWallet.addressIndex[selectedNetwork][addressType]?.index; - addressIndex = addressIndex > 0 ? addressIndex : 0; - - // Only subscribe up to the gap limit. - const addressesInRange = Object.values(addresses).filter( - (address) => Math.abs(address.index - addressIndex) <= GAP_LIMIT, - ); - const addressesToSubscribe = addressesInRange.slice(-GAP_LIMIT); - const addressScriptHashes = addressesToSubscribe.map( - ({ scriptHash }) => scriptHash, - ); - - scriptHashes.push(...addressScriptHashes); - } - } - } - - // Subscribe to all provided script hashes. - const promises = scriptHashes.map(async (scriptHash) => { - const response: ISubscribeToAddress = await electrum.subscribeAddress({ - scriptHash, - network: selectedNetwork, - onReceive: (): void => { - refreshWallet(); - onReceive?.(); - }, - }); - if (response.error) { - throw Error('Unable to subscribe to receiving addresses.'); - } - }); - - try { - await Promise.all(promises); - } catch (e) { - console.log(e); - return err(e); - } - - return ok('Successfully subscribed to addresses.'); -}; - -interface ISubscribeToHeader { - data: { - height: number; - hex: string; - }; - error: boolean; - id: string; - method: string; -} - -/** - * Subscribes to the current networks headers. - * @param {string} [selectedNetwork] - * @param {Function} [onReceive] - * @return {Promise>} - */ -export const subscribeToHeader = async ({ - selectedNetwork, - onReceive, -}: { - selectedNetwork?: EAvailableNetwork; - onReceive?: () => void; -}): Promise> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const subscribeResponse: ISubscribeToHeader = await electrum.subscribeHeader({ - network: selectedNetwork, - onReceive: (data) => { - const hex = data[0].hex; - const hash = getBlockHashFromHex({ blockHex: hex, selectedNetwork }); - updateHeader({ - selectedNetwork, - header: { ...data[0], hash }, - }); - onReceive?.(); - }, - }); - if (subscribeResponse.error) { - return err('Unable to subscribe to headers.'); - } - // @ts-ignore - if (subscribeResponse?.data === 'Already Subscribed.') { - return ok(getBlockHeader({ selectedNetwork })); - } - // Update local storage with current height and hex. - const hex = subscribeResponse.data.hex; - const hash = getBlockHashFromHex({ blockHex: hex, selectedNetwork }); - const header = { ...subscribeResponse.data, hash }; - updateHeader({ - selectedNetwork, - header, - }); - return ok(header); + const electrum = getOnChainWalletElectrum(); + return electrum.subscribeToAddresses({ scriptHashes, onReceive }); }; interface IGetTransactions { @@ -393,87 +143,31 @@ export const transactionExists = (txData: ITransaction): boolean => { /** * Returns available transactions from electrum based on the provided txHashes. * @param {ITxHash[]} txHashes - * @param {EAvailableNetwork} [selectedNetwork] * @return {Promise>} */ export const getTransactions = async ({ txHashes = [], - selectedNetwork, }: { txHashes: ITxHash[]; - selectedNetwork?: EAvailableNetwork; }): Promise> => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (txHashes.length < 1) { - return ok({ - error: false, - id: 0, - method: 'getTransactions', - network: selectedNetwork, - data: [], - }); - } - - const result: ITransaction[] = []; - - // split payload in chunks of 10 transactions per-request - for (let i = 0; i < txHashes.length; i += CHUNK_LIMIT) { - const chunk = txHashes.slice(i, i + CHUNK_LIMIT); - - const data = { - key: 'tx_hash', - data: chunk, - }; - const response = await electrum.getTransactions({ - txHashes: data, - network: selectedNetwork, - }); - if (response.error) { - return err(response); - } - result.push(...response.data); - } - return ok({ - error: false, - id: 0, - method: 'getTransactions', - network: selectedNetwork, - data: result, - }); - } catch (e) { - return err(e); - } + const electrum = getOnChainWalletElectrum(); + return await electrum.getTransactions({ txHashes }); }; export interface IPeerData { host: string; port: string; - protocol: 'tcp' | 'ssl'; + protocol: EProtocol; } /** * Returns the currently connected Electrum peer. - * @param {EAvailableNetwork} [selectedNetwork] * @return {Promise>} */ -export const getConnectedPeer = async ( - selectedNetwork: EAvailableNetwork, -): Promise> => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const response = await electrum.getConnectedPeer(selectedNetwork); - if (response?.host && response?.port && response?.protocol) { - return ok(response); - } - return err('No peer available.'); - } catch (e) { - return err(e); - } +export const getConnectedPeer = async (): Promise> => { + const electrum = getOnChainWalletElectrum(); + const peerData = await electrum.getConnectedPeer(); + return peerData as Result; }; interface IGetTransactionsFromInputs { @@ -490,33 +184,15 @@ interface IGetTransactionsFromInputs { /** * Returns transactions associated with the provided transaction hashes. * @param {ITxHash[]} txHashes - * @param {EAvailableNetwork} [selectedNetwork] * @return {Promise>} */ export const getTransactionsFromInputs = async ({ txHashes = [], - selectedNetwork, }: { txHashes: ITxHash[]; - selectedNetwork?: EAvailableNetwork; }): Promise> => { - try { - const data = { - key: 'tx_hash', - data: txHashes, - }; - const response = await electrum.getTransactions({ - txHashes: data, - network: selectedNetwork, - }); - if (!response.error) { - return ok(response); - } else { - return err(response); - } - } catch (e) { - return err(e); - } + const electrum = getOnChainWalletElectrum(); + return await electrum.getTransactionsFromInputs({ txHashes }); }; export interface TTxResult { @@ -524,232 +200,65 @@ export interface TTxResult { height: number; } -interface TTxResponse { - data: IAddress; - id: number; - jsonrpc: string; - param: string; - result: TTxResult[]; -} - -interface IGetAddressScriptHashesHistoryResponse { - data: TTxResponse[]; - error: boolean; - id: number; - method: string; - network: string; -} - export interface IGetAddressHistoryResponse extends TTxResult, IAddress {} /** * Returns the available history for the provided address script hashes. * @param {IAddress[]} [scriptHashes] - * @param {EAvailableNetwork} [selectedNetwork] - * @param {TWalletName} [selectedWallet] * @param {boolean} [scanAllAddresses] * @returns {Promise>} */ export const getAddressHistory = async ({ scriptHashes = [], - selectedNetwork, - selectedWallet, scanAllAddresses = false, }: { scriptHashes?: IAddress[]; - selectedNetwork?: EAvailableNetwork; - selectedWallet?: TWalletName; scanAllAddresses?: boolean; }): Promise> => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const { currentWallet } = getCurrentWallet({ - selectedNetwork, - selectedWallet, - }); - const currentAddresses = currentWallet.addresses[selectedNetwork]; - const currentChangeAddresses = - currentWallet.changeAddresses[selectedNetwork]; - - const addressIndexes = currentWallet.addressIndex[selectedNetwork]; - const changeAddressIndexes = - currentWallet.changeAddressIndex[selectedNetwork]; - - if (scriptHashes.length < 1) { - const addressTypeKeys = objectKeys(addressTypes); - addressTypeKeys.forEach((addressType) => { - const addresses = currentAddresses[addressType]; - const changeAddresses = currentChangeAddresses[addressType]; - let addressValues = Object.values(addresses); - let changeAddressValues = Object.values(changeAddresses); - - const addressIndex = addressIndexes[addressType].index; - const changeAddressIndex = changeAddressIndexes[addressType].index; - - // Instead of scanning all addresses, adhere to the gap limit. - if (!scanAllAddresses && addressIndex >= 0 && changeAddressIndex >= 0) { - addressValues = addressValues.filter( - (a) => Math.abs(addressIndex - a.index) <= GAP_LIMIT, - ); - changeAddressValues = changeAddressValues.filter( - (a) => Math.abs(changeAddressIndex - a.index) <= GAP_LIMIT, - ); - } - - scriptHashes = [ - ...scriptHashes, - ...addressValues, - ...changeAddressValues, - ]; - }); - } - // remove items with same path - scriptHashes = scriptHashes.filter((sh, index, arr) => { - return index === arr.findIndex((v) => sh.path === v.path); - }); - if (scriptHashes.length < 1) { - return err('No scriptHashes available to check.'); - } - - let combinedResponse: TTxResponse[] = []; - - // split payload in chunks of 10 addresses per-request - for (let i = 0; i < scriptHashes.length; i += CHUNK_LIMIT) { - const chunk = scriptHashes.slice(i, i + CHUNK_LIMIT); - const payload = { - key: 'scriptHash', - data: chunk, - }; - - const response: IGetAddressScriptHashesHistoryResponse = - await electrum.getAddressScriptHashesHistory({ - scriptHashes: payload, - network: selectedNetwork, - }); - - const mempoolResponse: IGetAddressScriptHashesHistoryResponse = - await electrum.getAddressScriptHashesMempool({ - scriptHashes: payload, - network: selectedNetwork, - }); - - if (response.error || mempoolResponse.error) { - return err('Unable to get address history.'); - } - combinedResponse.push(...response.data, ...mempoolResponse.data); - } - - const history: IGetAddressHistoryResponse[] = []; - combinedResponse.forEach( - ({ data, result }: { data: IAddress; result: TTxResult[] }): void => { - if (result && result?.length > 0) { - result.forEach((item) => { - history.push({ ...data, ...item }); - }); - } - }, - ); - - return ok(history); - } catch (e) { - return err(e); - } -}; - -/** - * Used to retrieve scriptPubkey history for LDK. - * @param {string} scriptPubkey - * @param {EAvailableNetwork} [selectedNetwork] - * @returns {Promise} - */ -export const getScriptPubKeyHistory = async ( - scriptPubkey: string, - selectedNetwork?: EAvailableNetwork, -): Promise => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - let history: { txid: string; height: number }[] = []; - const address = getAddressFromScriptPubKey(scriptPubkey, selectedNetwork); - if (!address) { - return history; - } - const scriptHash = await getScriptHash(address, selectedNetwork); - if (!scriptHash) { - return history; - } - const response = await electrum.getAddressScriptHashesHistory({ - scriptHashes: [scriptHash], - network: selectedNetwork, - }); - if (response.error) { - return history; - } - await Promise.all( - response.data.map(({ result }): void => { - if (result && result?.length > 0) { - result.map((item) => { - history.push({ - txid: item?.tx_hash ?? '', - height: item?.height ?? 0, - }); - }); - } - }), - ); - return history; + const electrum = getOnChainWalletElectrum(); + return await electrum.getAddressHistory({ scriptHashes, scanAllAddresses }); }; /** * Connects to the provided electrum peer. Otherwise, it will attempt to connect to a set of default peers. - * @param {ICustomElectrumPeer[]} [customPeers] - * @param {showNotification} [boolean * @param {EAvailableNetwork} [selectedNetwork] + * @param {ICustomElectrumPeer[]} [customPeers] + * @param {{ net: undefined, tls: undefined }} [options] * @return {Promise>} */ export const connectToElectrum = async ({ - peer, + customPeers, showNotification = true, - selectedNetwork, + selectedNetwork = getSelectedNetwork(), }: { - peer?: ICustomElectrumPeer; + customPeers?: TServer[]; showNotification?: boolean; selectedNetwork?: EAvailableNetwork; } = {}): Promise> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } + const electrum = getOnChainWalletElectrum(); // Attempt to disconnect from any old/lingering connections - await electrum.stop({ network: selectedNetwork }); - - let customPeers = [peer]; + await electrum.disconnect(); - if (!peer) { - customPeers = getSettingsStore().customElectrumPeers[selectedNetwork]; + // Fetch any stored custom peers. + if (!customPeers) { + customPeers = getCustomElectrumPeers({ selectedNetwork }); } - const { error, data } = await electrum.start({ - network: selectedNetwork, - customPeers, - net: global.net, - tls: global.tls, + const connectRes = await electrum.connectToElectrum({ + network: EAvailableNetworks[selectedNetwork], + servers: customPeers, }); - if (error) { - const msg = data || 'An unknown error occurred.'; + if (connectRes.isErr()) { + const msg = connectRes.error.message || 'An unknown error occurred.'; return err(msg); } // Check for any new transactions that we might have missed while disconnected. refreshWallet({ showNotification }).then(); - return ok(data); + return ok(connectRes.value); }; /** @@ -759,42 +268,11 @@ export const connectToElectrum = async ({ */ export const getAddressBalance = async ({ addresses = [], - selectedNetwork, }: { addresses: string[]; - selectedNetwork?: EAvailableNetwork; }): Promise> => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const scriptHashes = await Promise.all( - addresses.map(async (address) => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - return await getScriptHash(address, selectedNetwork); - }), - ); - const res = await electrum.getAddressScriptHashBalances({ - scriptHashes, - network: selectedNetwork, - }); - if (res.error) { - return err(res.data); - } - return ok( - res.data.reduce((acc, cur) => { - return ( - acc + - Number(cur.result?.confirmed ?? 0) + - Number(cur.result?.unconfirmed ?? 0) - ); - }, 0) || 0, - ); - } catch (e) { - return err(e); - } + const wallet = getOnChainWallet(); + return await wallet.getAddressesBalance(addresses); }; /** @@ -805,22 +283,11 @@ export const getAddressBalance = async ({ */ export const getBlockHex = async ({ height = 0, - selectedNetwork, }: { height?: number; - selectedNetwork?: EAvailableNetwork; }): Promise> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const response: IGetHeaderResponse = await electrum.getHeader({ - height, - network: selectedNetwork, - }); - if (response.error) { - return err(response.data); - } - return ok(response.data); + const electrum = getOnChainWalletElectrum(); + return await electrum.getBlockHex({ height }); }; /** @@ -832,79 +299,32 @@ export const getBlockHex = async ({ */ export const getBlockHashFromHex = ({ blockHex, - selectedNetwork, }: { blockHex?: string; - selectedNetwork?: EAvailableNetwork; }): string => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - // If empty, return the last known block hex from storage. - if (!blockHex) { - const { hex } = getBlockHeader({ selectedNetwork }); - blockHex = hex; - } - const block = Block.fromHex(blockHex); - const hash = block.getId(); - return hash; + const electrum = getOnChainWalletElectrum(); + return electrum.getBlockHashFromHex({ blockHex }); }; /** * Returns last known block height, and it's corresponding hex from local storage. - * @param {EAvailableNetwork} [selectedNetwork] * @returns {IHeader} */ -export const getBlockHeader = ({ - selectedNetwork, -}: { - selectedNetwork?: EAvailableNetwork; -}): IHeader => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - return getWalletStore().header[selectedNetwork]; -}; - -/** - * Returns the block hash for the provided height and network. - * @param {number} [height] - * @param {EAvailableNetwork} [selectedNetwork] - * @returns {Promise>} - */ -export const getBlockHashFromHeight = async ({ - height = 0, - selectedNetwork, -}: { - height?: number; - selectedNetwork?: EAvailableNetwork; -}): Promise> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const response = await getBlockHex({ height, selectedNetwork }); - if (response.isErr()) { - return err(response.error.message); - } - const blockHash = getBlockHashFromHex({ blockHex: response.value }); - return ok(blockHash); +export const getBlockHeader = (): IHeader => { + const electrum = getOnChainWalletElectrum(); + return electrum.getBlockHeader(); }; export const getTransactionMerkle = async ({ tx_hash, height, - selectedNetwork, }: { tx_hash: string; height: number; - selectedNetwork?: EAvailableNetwork; }): Promise => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - return await electrum.getTransactionMerkle({ + const electrum = getOnChainWalletElectrum(); + return electrum.getTransactionMerkle({ tx_hash, height, - network: selectedNetwork, }); }; diff --git a/src/utils/wallet/index.ts b/src/utils/wallet/index.ts index 1042ffd37..715d55afd 100644 --- a/src/utils/wallet/index.ts +++ b/src/utils/wallet/index.ts @@ -7,92 +7,98 @@ import { BIP32Factory } from 'bip32'; import ecc from '@bitcoinerlab/secp256k1'; import { err, ok, Result } from '@synonymdev/result'; -import { networks, EAvailableNetwork } from '../networks'; +import { EAvailableNetwork, networks } from '../networks'; import { + addressTypes, getDefaultWalletShape, - defaultWalletStoreShape, TAddressIndexInfo, - addressTypes, } from '../../store/shapes/wallet'; import { + EAddressType, EPaymentType, - IAddresses, IAddress, - IWallets, - IWallet, - IFormattedTransactions, + IAddresses, + IAddressTypes, IFormattedTransaction, + IFormattedTransactions, IKeyDerivationPath, + IKeyDerivationPathData, IOutput, IUtxo, - EAddressType, + IWallet, + IWallets, TKeyDerivationAccount, TKeyDerivationAccountType, + TKeyDerivationChange, + TKeyDerivationCoinType, TKeyDerivationPurpose, - IKeyDerivationPathData, TWalletName, - TKeyDerivationCoinType, - TKeyDerivationChange, - IAddressTypes, } from '../../store/types/wallet'; import { IGetAddress, IGenerateAddresses, - IGetInfoFromAddressPath, - IGenerateAddressesResponse, IGetAddressResponse, + IGetInfoFromAddressPath, } from '../types'; import i18n from '../i18n'; import { btcToSats } from '../conversion'; import { getKeychainValue, setKeychainValue } from '../keychain'; import { + dispatch, getLightningStore, - getUiStore, + getSettingsStore, + getStore, getWalletStore, } from '../../store/helpers'; import { - addAddresses, - clearAddresses, - clearTransactions, - clearUtxos, - generateNewReceiveAddress, - resetAddressIndexes, - setZeroIndexAddresses, - updateAddressIndexes, - updateExchangeRates, - updateTransactions, - updateUtxos, + createDefaultWalletStructure, + getNetworkFromBeignet, + getWalletData, + setWalletData, } from '../../store/actions/wallet'; import { TCoinSelectPreference } from '../../store/types/settings'; import { updateActivityList } from '../../store/utils/activity'; -import { getByteCount } from './transactions'; import { - getAddressHistory, getBlockHeader, - getTransactions, getTransactionsFromInputs, - isConnectedElectrum, - subscribeToAddresses, TTxResult, } from './electrum'; import { invokeNodeJsMethod } from '../nodejs-mobile'; import { DefaultNodeJsMethodsShape } from '../nodejs-mobile/shapes'; import { refreshLdk } from '../lightning'; -import { - BITKIT_WALLET_SEED_HASH_PREFIX, - GENERATE_ADDRESS_AMOUNT, - CHUNK_LIMIT, -} from './constants'; +import { BITKIT_WALLET_SEED_HASH_PREFIX, CHUNK_LIMIT } from './constants'; import { moveMetaIncTxTags } from '../../store/utils/metadata'; import { refreshOrdersList } from '../../store/utils/blocktank'; import { TNode } from '../../store/types/lightning'; -import { showNewTxPrompt } from '../../store/utils/ui'; +import { showNewOnchainTxPrompt, showNewTxPrompt } from '../../store/utils/ui'; import { reduceValue } from '../helpers'; import { objectKeys } from '../objectKeys'; +import { + EAvailableNetworks, + EElectrumNetworks, + Electrum, + getByteCount, + ICustomGetAddress, + IGenerateAddressesResponse, + IRbfData, + ISendTransaction, + IWalletData, + TOnMessage, + Transaction, + TTransactionMessage, + Wallet, +} from 'beignet'; +import { TServer } from 'beignet/src/types/electrum'; +import { showToast } from '../notifications'; +import { updateUi } from '../../store/slices/ui'; +import { startWalletServices } from '../startup'; +import { ICustomGetScriptHash } from 'beignet/src/types/wallet'; bitcoin.initEccLib(ecc); const bip32 = BIP32Factory(ecc); +let wallet: Wallet; + export const refreshWallet = async ({ onchain = true, lightning = true, @@ -115,7 +121,6 @@ export const refreshWallet = async ({ await new Promise((resolve) => { InteractionManager.runAfterInteractions(() => resolve(null)); }); - const { isConnectedToElectrum } = getUiStore(); if (!selectedWallet) { selectedWallet = getSelectedWallet(); } @@ -126,48 +131,9 @@ export const refreshWallet = async ({ let notificationTxid: string | undefined; if (onchain) { - let addressType: EAddressType | undefined; - if (!updateAllAddressTypes) { - addressType = getSelectedAddressType({ - selectedNetwork, - selectedWallet, - }); - } - await updateAddressIndexes({ - selectedWallet, - selectedNetwork, - addressType, - }); - if (isConnectedToElectrum) { - const [_result1, _result2, updateTransactionResult] = await Promise.all( - [ - subscribeToAddresses({ - selectedWallet, - selectedNetwork, - }), - updateUtxos({ - selectedWallet, - selectedNetwork, - scanAllAddresses, - }), - updateTransactions({ - scanAllAddresses, - selectedWallet, - selectedNetwork, - }), - ], - ); - - if (updateTransactionResult.isOk()) { - notificationTxid = updateTransactionResult.value; - } - } - - updateExchangeRates().then(); - - await setZeroIndexAddresses({ - selectedWallet, - selectedNetwork, + await wallet.refreshWallet({ + scanAllAddresses, + updateAllAddressTypes, }); } @@ -205,123 +171,22 @@ export const refreshWallet = async ({ * @param {string} [addressType] - Determines what type of address to generate (p2pkh, p2sh, p2wpkh). */ export const generateAddresses = async ({ - selectedWallet, addressAmount = 10, changeAddressAmount = 10, addressIndex = 0, changeAddressIndex = 0, - selectedNetwork, keyDerivationPath, - accountType = 'onchain', addressType, }: IGenerateAddresses): Promise> => { try { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!addressType) { - addressType = getSelectedAddressType({ selectedNetwork, selectedWallet }); - } - - if (!keyDerivationPath) { - // Set derivation path accordingly based on address type. - const keyDerivationPathResponse = getKeyDerivationPath({ - selectedNetwork, - addressType, - }); - if (keyDerivationPathResponse.isErr()) { - return err(keyDerivationPathResponse.error.message); - } - keyDerivationPath = keyDerivationPathResponse.value; - } - - const type = addressType; - const addresses = {} as IAddresses; - const changeAddresses = {} as IAddresses; - const addressArray = new Array(addressAmount).fill(null); - const changeAddressArray = new Array(changeAddressAmount).fill(null); - - await Promise.all( - addressArray.map(async (_item, i) => { - const index = i + addressIndex; - const path = { ...keyDerivationPath! }; - path.addressIndex = `${index}`; - const addressPath = formatKeyDerivationPath({ - path, - selectedNetwork, - accountType, - changeAddress: false, - addressIndex: `${index}`, - }); - if (addressPath.isErr()) { - throw addressPath.error; - } - const address = await getAddress({ - path: addressPath.value.pathString, - selectedNetwork, - type, - }); - if (address.isErr()) { - throw address.error; - } - const scriptHash = await getScriptHash( - address.value.address, - selectedNetwork, - ); - if (!scriptHash) { - throw new Error('Unable to get script hash.'); - } - addresses[scriptHash] = { - ...address.value, - index, - scriptHash, - }; - }), - ); - - await Promise.all( - changeAddressArray.map(async (_item, i) => { - const index = i + changeAddressIndex; - const path = { ...keyDerivationPath! }; - path.addressIndex = `${index}`; - const changeAddressPath = formatKeyDerivationPath({ - path, - selectedNetwork, - accountType, - changeAddress: true, - addressIndex: `${index}`, - }); - if (changeAddressPath.isErr()) { - throw changeAddressPath.error; - } - - const address = await getAddress({ - path: changeAddressPath.value.pathString, - selectedNetwork, - type, - }); - if (address.isErr()) { - throw address.error; - } - const scriptHash = await getScriptHash( - address.value.address, - selectedNetwork, - ); - if (!scriptHash) { - throw new Error('Unable to get script hash.'); - } - changeAddresses[scriptHash] = { - ...address.value, - index, - scriptHash, - }; - }), - ); - - return ok({ addresses, changeAddresses }); + return await wallet.generateAddresses({ + addressAmount, + changeAddressAmount, + addressIndex, + changeAddressIndex, + keyDerivationPath, + addressType, + }); } catch (e) { return err(e); } @@ -618,6 +483,46 @@ export const getScriptHash = async ( } }; +/** + * Get scriptHash for a given address + * @param {string} address + * @param {EAvailableNetwork} [selectedNetwork] + * @return {string} + */ +export const getCustomScriptHash = async ({ + address, + selectedNetwork, +}: ICustomGetScriptHash): Promise => { + try { + if (!address) { + return ''; + } + const data = DefaultNodeJsMethodsShape.getScriptHash(); + data.data.address = address; + data.data.selectedNetwork = electrumNetworkToBitkitNetwork(selectedNetwork); + const getScriptHashResponse = await invokeNodeJsMethod(data); + if (getScriptHashResponse.error) { + return ''; + } + return getScriptHashResponse.value; + } catch { + return ''; + } +}; + +export const electrumNetworkToBitkitNetwork = ( + network: EElectrumNetworks, +): EAvailableNetwork => { + switch (network) { + case EElectrumNetworks.bitcoin: + return EAvailableNetwork.bitcoin; + case EElectrumNetworks.bitcoinRegtest: + return EAvailableNetwork.bitcoinRegtest; + case EElectrumNetworks.bitcoinTestnet: + return EAvailableNetwork.bitcoinTestnet; + } +}; + /** * Get address for a given keyPair, network and type. * @param {string} path @@ -648,6 +553,33 @@ export const getAddress = async ({ } }; +/** + * Get address for a given keyPair, network and type. + * @param {string} path + * @param {EAvailableNetwork} [selectedNetwork] + * @param {EAddressType} type - Determines what type of address to generate (p2pkh, p2sh, p2wpkh). + * @return {string} + */ +export const customGetAddress = async ({ + path, + selectedNetwork, + type, +}: ICustomGetAddress): Promise> => { + if (!path) { + return err('No path specified'); + } + try { + const data = DefaultNodeJsMethodsShape.getAddress(); + data.data.path = path; + data.data.type = type; + data.data.selectedNetwork = electrumNetworkToBitkitNetwork(selectedNetwork); + const addressResponse = await invokeNodeJsMethod(data); + return ok(addressResponse.value); + } catch (e) { + return err(e); + } +}; + /** * Get info from an address path "m/49'/0'/0'/0/1" * @param {string} path - The path to derive information from. @@ -794,350 +726,6 @@ export const removeDuplicateAddresses = async ({ interface ITxHashes extends TTxResult { scriptHash: string; } - -interface IGetNextAvailableAddressResponse { - addressIndex: IAddress; - lastUsedAddressIndex: IAddress; - changeAddressIndex: IAddress; - lastUsedChangeAddressIndex: IAddress; -} - -interface IGetNextAvailableAddress { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; - addressType?: EAddressType; -} - -export const getNextAvailableAddress = async ({ - selectedWallet, - selectedNetwork, - addressType, -}: IGetNextAvailableAddress): Promise< - Result -> => { - const isConnected = await isConnectedElectrum(); - if (!isConnected) { - return err('Not connected to Electrum Server.'); - } - - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const { currentWallet } = getCurrentWallet({ - selectedNetwork, - selectedWallet, - }); - if (!addressType) { - addressType = getSelectedAddressType({ - selectedNetwork, - selectedWallet, - }); - } - const { path } = addressTypes[addressType]; - - const result = formatKeyDerivationPath({ path, selectedNetwork }); - if (result.isErr()) { - return err(result.error.message); - } - const { pathObject: keyDerivationPath } = result.value; - - //The currently known/stored address index. - let addressIndex = currentWallet.addressIndex[selectedNetwork][addressType]; - let lastUsedAddressIndex = - currentWallet.lastUsedAddressIndex[selectedNetwork][addressType]; - let changeAddressIndex = - currentWallet.changeAddressIndex[selectedNetwork][addressType]; - let lastUsedChangeAddressIndex = - currentWallet.lastUsedChangeAddressIndex[selectedNetwork][addressType]; - - if (!addressIndex.address) { - const generatedAddresses = await generateAddresses({ - selectedWallet, - selectedNetwork, - addressAmount: GENERATE_ADDRESS_AMOUNT, - changeAddressAmount: 0, - keyDerivationPath, - addressType, - }); - if (generatedAddresses.isErr()) { - return err(generatedAddresses.error); - } - const addresses = generatedAddresses.value.addresses; - const sorted = Object.values(addresses).sort((a, b) => a.index - b.index); - addressIndex = sorted[0]; - } - - if (!changeAddressIndex.address) { - const generatedAddresses = await generateAddresses({ - selectedWallet, - selectedNetwork, - addressAmount: 0, - changeAddressAmount: GENERATE_ADDRESS_AMOUNT, - keyDerivationPath, - addressType, - }); - if (generatedAddresses.isErr()) { - return err(generatedAddresses.error); - } - const addresses = generatedAddresses.value.changeAddresses; - const sorted = Object.values(addresses).sort((a, b) => a.index - b.index); - changeAddressIndex = sorted[0]; - } - - let addresses = currentWallet.addresses[selectedNetwork][addressType]; - let changeAddresses = - currentWallet.changeAddresses[selectedNetwork][addressType]; - - //How many addresses/changeAddresses are currently stored - const addressCount = Object.values(addresses).length; - const changeAddressCount = Object.values(changeAddresses).length; - - /* - * Create more addresses if none exist or the highest address index matches the current address count - */ - if (addressCount <= 0 || addressIndex.index === addressCount) { - const newAddresses = await addAddresses({ - addressAmount: GENERATE_ADDRESS_AMOUNT, - changeAddressAmount: 0, - addressIndex: addressIndex.index, - changeAddressIndex: 0, - selectedNetwork, - selectedWallet, - keyDerivationPath, - addressType, - }); - if (!newAddresses.isErr()) { - addresses = newAddresses.value.addresses; - } - } - - /* - * Create more change addresses if none exist or the highest change address index matches the current - * change address count - */ - if ( - changeAddressCount <= 0 || - changeAddressIndex.index === changeAddressCount - ) { - const newChangeAddresses = await addAddresses({ - addressAmount: 0, - changeAddressAmount: GENERATE_ADDRESS_AMOUNT, - addressIndex: 0, - changeAddressIndex: changeAddressIndex.index, - selectedNetwork, - selectedWallet, - keyDerivationPath, - addressType, - }); - if (!newChangeAddresses.isErr()) { - changeAddresses = newChangeAddresses.value.changeAddresses; - } - } - - //Store all addresses that are to be searched and used in this method. - let allAddresses = Object.values(addresses).filter( - ({ index }) => index >= addressIndex.index, - ); - let addressesToScan = allAddresses; - - //Store all change addresses that are to be searched and used in this method. - let allChangeAddresses = Object.values(changeAddresses).filter( - ({ index }) => index >= changeAddressIndex.index, - ); - let changeAddressesToScan = allChangeAddresses; - - //Prep for batch request - let combinedAddressesToScan = [ - ...addressesToScan, - ...changeAddressesToScan, - ]; - - let foundLastUsedAddress = false; - let foundLastUsedChangeAddress = false; - let addressHasBeenUsed = false; - let changeAddressHasBeenUsed = false; - - // If an error occurs, return last known/available indexes. - const lastKnownIndexes = ok({ - addressIndex, - lastUsedAddressIndex, - changeAddressIndex, - lastUsedChangeAddressIndex, - }); - - while (!foundLastUsedAddress || !foundLastUsedChangeAddress) { - //Check if transactions are pending in the mempool. - const addressHistory = await getAddressHistory({ - scriptHashes: combinedAddressesToScan, - selectedNetwork, - selectedWallet, - }); - - if (addressHistory.isErr()) { - console.log(addressHistory.error.message); - return lastKnownIndexes; - } - - const txHashes = addressHistory.value; - - const highestUsedIndex = getHighestUsedIndexFromTxHashes({ - txHashes, - addresses, - changeAddresses, - addressIndex, - changeAddressIndex, - }); - if (highestUsedIndex.isErr()) { - console.log(highestUsedIndex.error.message); - return lastKnownIndexes; - } - - addressIndex = highestUsedIndex.value.addressIndex; - changeAddressIndex = highestUsedIndex.value.changeAddressIndex; - if (highestUsedIndex.value.foundAddressIndex) { - addressHasBeenUsed = true; - } - if (highestUsedIndex.value.foundChangeAddressIndex) { - changeAddressHasBeenUsed = true; - } - - const highestStoredIndex = getHighestStoredAddressIndex({ - selectedNetwork, - selectedWallet, - addressType, - }); - - if (highestStoredIndex.isErr()) { - console.log(highestStoredIndex.error.message); - return lastKnownIndexes; - } - - const { - addressIndex: highestUsedAddressIndex, - changeAddressIndex: highestUsedChangeAddressIndex, - } = highestUsedIndex.value; - const { - addressIndex: highestStoredAddressIndex, - changeAddressIndex: highestStoredChangeAddressIndex, - } = highestStoredIndex.value; - - if (highestUsedAddressIndex.index < highestStoredAddressIndex.index) { - foundLastUsedAddress = true; - } - - if ( - highestUsedChangeAddressIndex.index < - highestStoredChangeAddressIndex.index - ) { - foundLastUsedChangeAddress = true; - } - - if (foundLastUsedAddress && foundLastUsedChangeAddress) { - //Increase index by one if the current index was found in a txHash or is greater than the previous index. - let newAddressIndex = addressIndex.index; - if ( - highestUsedAddressIndex.index > addressIndex.index || - addressHasBeenUsed - ) { - const index = highestUsedAddressIndex.index; - if ( - highestUsedAddressIndex && - index >= 0 && - highestUsedIndex.value.foundAddressIndex - ) { - lastUsedAddressIndex = highestUsedAddressIndex; - } - newAddressIndex = index >= 0 ? index + 1 : index; - } - - let newChangeAddressIndex = changeAddressIndex.index; - if ( - highestUsedChangeAddressIndex.index > changeAddressIndex.index || - changeAddressHasBeenUsed - ) { - const index = highestUsedChangeAddressIndex.index; - if ( - highestUsedChangeAddressIndex && - index >= 0 && - highestUsedIndex.value.foundChangeAddressIndex - ) { - lastUsedChangeAddressIndex = highestUsedChangeAddressIndex; - } - newChangeAddressIndex = index >= 0 ? index + 1 : index; - } - - //Find and return the new address index. - const nextAvailableAddress = Object.values(allAddresses).find( - ({ index }) => index === newAddressIndex, - ); - //Find and return the new change address index. - const nextAvailableChangeAddress = Object.values( - allChangeAddresses, - ).find(({ index }) => index === newChangeAddressIndex); - if (!nextAvailableAddress || !nextAvailableChangeAddress) { - return lastKnownIndexes; - } - return ok({ - addressIndex: nextAvailableAddress, - lastUsedAddressIndex, - changeAddressIndex: nextAvailableChangeAddress, - lastUsedChangeAddressIndex, - }); - } - - //Create receiving addresses for the next round - if (!foundLastUsedAddress) { - const newAddresses = await addAddresses({ - addressAmount: GENERATE_ADDRESS_AMOUNT, - changeAddressAmount: 0, - addressIndex: highestStoredIndex.value.addressIndex.index, - changeAddressIndex: 0, - selectedNetwork, - selectedWallet, - keyDerivationPath, - addressType, - }); - if (!newAddresses.isErr()) { - addresses = newAddresses.value.addresses || {}; - } - } - //Create change addresses for the next round - if (!foundLastUsedChangeAddress) { - const newChangeAddresses = await addAddresses({ - addressAmount: 0, - changeAddressAmount: GENERATE_ADDRESS_AMOUNT, - addressIndex: 0, - changeAddressIndex: highestStoredIndex.value.changeAddressIndex.index, - selectedNetwork, - selectedWallet, - keyDerivationPath, - addressType, - }); - if (!newChangeAddresses.isErr()) { - changeAddresses = newChangeAddresses.value.changeAddresses || {}; - } - } - - // Store newly created addresses to scan in the next round. - addressesToScan = Object.values(addresses); - changeAddressesToScan = Object.values(changeAddresses); - combinedAddressesToScan = [...addressesToScan, ...changeAddressesToScan]; - // Store the newly created addresses used for this method. - allAddresses = [...allAddresses, ...addressesToScan]; - allChangeAddresses = [...allChangeAddresses, ...changeAddressesToScan]; - } - - return lastKnownIndexes; - } catch (e) { - console.log(e); - return err(e); - } -}; - interface IIndexes { addressIndex: IAddress; changeAddressIndex: IAddress; @@ -1196,25 +784,17 @@ export const getHighestUsedIndexFromTxHashes = ({ * Returns the highest address and change address index stored in the app for the specified wallet and network. */ export const getHighestStoredAddressIndex = ({ - selectedWallet = defaultWalletStoreShape.selectedWallet, - selectedNetwork = defaultWalletStoreShape.selectedNetwork, addressType, }: { - selectedWallet: TWalletName; - selectedNetwork: EAvailableNetwork; addressType: EAddressType; }): Result<{ addressIndex: IAddress; changeAddressIndex: IAddress; }> => { try { - const { currentWallet } = getCurrentWallet({ - selectedWallet, - selectedNetwork, - }); - const addresses = currentWallet.addresses[selectedNetwork][addressType]; - const changeAddresses = - currentWallet.changeAddresses[selectedNetwork][addressType]; + const currentWallet = getOnChainWalletData(); + const addresses = currentWallet.addresses[addressType]; + const changeAddresses = currentWallet.changeAddresses[addressType]; const addressIndex = Object.values(addresses).reduce((prev, current) => { return prev.index > current.index ? prev : current; @@ -1235,7 +815,7 @@ export const getHighestStoredAddressIndex = ({ * @return {EAvailableNetwork} */ export const getSelectedNetwork = (): EAvailableNetwork => { - return getWalletStore().selectedNetwork; + return getWalletStore()?.selectedNetwork ?? 'bitcoin'; }; /** @@ -1243,22 +823,15 @@ export const getSelectedNetwork = (): EAvailableNetwork => { * @returns {EAddressType} */ export const getSelectedAddressType = ({ - selectedWallet, - selectedNetwork, + selectedWallet = getSelectedWallet(), + selectedNetwork = getSelectedNetwork(), }: { selectedWallet?: TWalletName; selectedNetwork?: EAvailableNetwork; } = {}): EAddressType => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - - const wallet = getWalletStore().wallets[selectedWallet]; - if (wallet?.addressType[selectedNetwork]) { - return wallet.addressType[selectedNetwork]; + const storedWallet = getWalletStore().wallets[selectedWallet]; + if (storedWallet?.addressType[selectedNetwork]) { + return storedWallet.addressType[selectedNetwork]; } else { return getDefaultWalletShape().addressType[selectedNetwork]; } @@ -1269,7 +842,7 @@ export const getSelectedAddressType = ({ * @return {TWalletName} */ export const getSelectedWallet = (): TWalletName => { - return getWalletStore().selectedWallet; + return getWalletStore()?.selectedWallet ?? 'wallet0'; }; /** @@ -1279,8 +852,8 @@ export const getSelectedWallet = (): TWalletName => { * @return {{ currentWallet: IWallet, currentLightningNode: TNode, selectedWallet: TWalletName, selectedNetwork: EAvailableNetwork }} */ export const getCurrentWallet = ({ - selectedNetwork, - selectedWallet, + selectedNetwork = getSelectedNetwork(), + selectedWallet = getSelectedWallet(), }: { selectedNetwork?: EAvailableNetwork; selectedWallet?: TWalletName; @@ -1290,16 +863,16 @@ export const getCurrentWallet = ({ selectedNetwork: EAvailableNetwork; selectedWallet: TWalletName; } => { - const wallet = getWalletStore(); + const walletStore = getWalletStore(); const lightning = getLightningStore(); + const currentLightningNode = lightning.nodes[selectedWallet]; if (!selectedNetwork) { - selectedNetwork = wallet.selectedNetwork; + selectedNetwork = walletStore.selectedNetwork; } if (!selectedWallet) { - selectedWallet = wallet.selectedWallet; + selectedWallet = walletStore.selectedWallet; } - const currentWallet = wallet.wallets[selectedWallet]; - const currentLightningNode = lightning.nodes[selectedWallet]; + const currentWallet = walletStore.wallets[selectedWallet]; return { currentWallet, currentLightningNode, @@ -1411,7 +984,6 @@ export const getInputData = async ({ const getTransactionsResponse = await getTransactionsFromInputs({ txHashes: chunk, - selectedNetwork, }); if (getTransactionsResponse.isErr()) { return err(getTransactionsResponse.error.message); @@ -1623,6 +1195,14 @@ export const decodeOpReturnMessage = (opReturn = ''): string[] => { } }; +export const getCustomElectrumPeers = ({ + selectedNetwork = getSelectedNetwork(), +}: { + selectedNetwork?: EAvailableNetwork; +}): TServer[] => { + return getSettingsStore().customElectrumPeers[selectedNetwork]; +}; + export interface IVin { scriptSig: { asm: string; @@ -1647,219 +1227,17 @@ export interface IVout { value: number; } -// TODO: Update ICreateTransaction to match this pattern. -export interface IRbfData { - outputs: IOutput[]; - selectedWallet: TWalletName; - balance: number; - selectedNetwork: EAvailableNetwork; - addressType: EAddressType; - fee: number; // Total fee in sats. - inputs: IUtxo[]; - message: string; -} - /** * Using a tx_hash this method will return the necessary data to create a * replace-by-fee transaction for any 0-conf, RBF-enabled tx. * @param txHash - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] */ - export const getRbfData = async ({ txHash, - selectedWallet, - selectedNetwork, }: { txHash: ITxHash; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; }): Promise> => { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const txResponse = await getTransactions({ - txHashes: [txHash], - selectedNetwork, - }); - if (txResponse.isErr()) { - return err(txResponse.error.message); - } - const txData = txResponse.value.data; - - const wallet = getWalletStore(); - const addressTypeKeys = objectKeys(EAddressType); - const addresses = wallet.wallets[selectedWallet].addresses[selectedNetwork]; - const changeAddresses = - wallet.wallets[selectedWallet].changeAddresses[selectedNetwork]; - - let allAddresses = {} as IAddresses; - let allChangeAddresses = {} as IAddresses; - - await Promise.all( - addressTypeKeys.map((addressType) => { - allAddresses = { - ...allAddresses, - ...addresses[addressType], - ...changeAddresses[addressType], - }; - allChangeAddresses = { - ...allChangeAddresses, - ...changeAddresses[addressType], - }; - }), - ); - - let changeAddressData: IOutput = { - address: '', - value: 0, - index: 0, - }; - let inputs: IUtxo[] = []; - let address: string = ''; - let scriptHash = ''; - let path = ''; - let value: number = 0; - let addressType = EAddressType.p2wpkh; - let outputs: IOutput[] = []; - let message: string = ''; - let inputTotal = 0; - let outputTotal = 0; - let fee = 0; - - const insAndOuts = await Promise.all( - txData.map(({ result }) => { - const vin = result.vin ?? []; - const vout = result.vout ?? []; - return { vins: vin, vouts: vout }; - }), - ); - const { vins, vouts } = insAndOuts[0]; - for (let i = 0; i < vins.length; i++) { - try { - const input = vins[i]; - const txId = input.txid; - const tx = await getTransactions({ - txHashes: [{ tx_hash: txId }], - selectedNetwork, - }); - if (tx.isErr()) { - return err(tx.error.message); - } - if (tx.value.data[0].data.height > 0) { - return err('Transaction is already confirmed. Unable to RBF.'); - } - const txVout = tx.value.data[0].result.vout[input.vout]; - if (txVout.scriptPubKey?.address) { - address = txVout.scriptPubKey.address; - } else if ( - txVout.scriptPubKey?.addresses && - txVout.scriptPubKey.addresses.length - ) { - address = txVout.scriptPubKey.addresses[0]; - } - if (!address) { - continue; - } - scriptHash = await getScriptHash(address, selectedNetwork); - // Check that we are in possession of this scriptHash. - if (!(scriptHash in allAddresses)) { - // This output did not come from us. - continue; - } - path = allAddresses[scriptHash].path; - value = btcToSats(txVout.value); - inputs.push({ - tx_hash: input.txid, - index: input.vout, - tx_pos: input.vout, - height: 0, - address, - scriptHash, - path, - value, - }); - if (value) { - inputTotal = inputTotal + value; - } - } catch (e) { - console.log(e); - } - } - for (let i = 0; i < vouts.length; i++) { - const vout = vouts[i]; - const voutValue = btcToSats(vout.value); - if (vout.scriptPubKey?.addresses) { - address = vout.scriptPubKey.addresses[0]; - } else if (vout.scriptPubKey?.address) { - address = vout.scriptPubKey.address; - } else { - try { - if (vout.scriptPubKey.asm.includes('OP_RETURN')) { - message = decodeOpReturnMessage(vout.scriptPubKey.asm)[0] || ''; - } - } catch (e) {} - } - if (!address) { - continue; - } - const changeAddressScriptHash = await getScriptHash( - address, - selectedNetwork, - ); - - // If the address scripthash matches one of our address scripthashes, add it accordingly. Otherwise, add it as another output. - if (Object.keys(allAddresses).includes(changeAddressScriptHash)) { - changeAddressData = { - address, - value: voutValue, - index: i, - }; - } else { - const index = outputs?.length ?? 0; - outputs.push({ - address, - value: voutValue, - index, - }); - outputTotal = outputTotal + voutValue; - } - } - - if (!changeAddressData?.address && outputs.length >= 2) { - /* - * Unable to determine change address. - * Performing an RBF could divert funds from the incorrect output. - * - * It's very possible that this tx sent the max amount of sats to a foreign/unknown address. - * Instead of pulling sats from that output to accommodate the higher fee (reducing how much the recipient receives) - * suggest a CPFP transaction. - */ - return err('cpfp'); - } - - if (outputTotal > inputTotal) { - return err('Outputs should not be greater than the inputs.'); - } - fee = Number(inputTotal - (changeAddressData?.value ?? 0) - outputTotal); - //outputs = outputs.filter((o) => o); - - return ok({ - selectedWallet, - changeAddress: changeAddressData.address, - inputs, - balance: inputTotal, - outputs, - fee, - selectedNetwork, - message, - addressType, - rbf: true, - }); + return await wallet.getRbfData({ txHash }); }; /** @@ -1927,8 +1305,6 @@ export const getRbfData = async ({ /** * Generates a newly specified wallet. * @param {string} [wallet] - * @param {number} [addressAmount] - * @param {number} [changeAddressAmount] * @param {string} [mnemonic] * @param {string} [bip39Passphrase] * @param {EAddressType} [addressTypesToCreate] @@ -1939,16 +1315,12 @@ export const createDefaultWallet = async ({ mnemonic, bip39Passphrase, restore, - addressAmount, - changeAddressAmount, addressTypesToCreate, }: { walletName: TWalletName; mnemonic: string; bip39Passphrase: string; restore: boolean; - addressAmount: number; - changeAddressAmount: number; addressTypesToCreate: Partial; }): Promise> => { try { @@ -1981,54 +1353,21 @@ export const createDefaultWallet = async ({ }); const seed = await bip39.mnemonicToSeed(mnemonic, bip39Passphrase); - const defaultWalletShape = getDefaultWalletShape(); - - //Generate a set of addresses & changeAddresses for each network. - const addressesObj = defaultWalletShape.addresses; - const changeAddressesObj = defaultWalletShape.changeAddresses; - const addressIndex = defaultWalletShape.addressIndex; - const changeAddressIndex = defaultWalletShape.changeAddressIndex; - const lastUsedAddressIndex = defaultWalletShape.lastUsedAddressIndex; - const lastUsedChangeAddressIndex = - defaultWalletShape.lastUsedChangeAddressIndex; - await setKeychainSlashtagsPrimaryKey(seed); - for (const { type, path } of Object.values(addressTypesToCreate)) { - const pathObject = getKeyDerivationPathObject({ - path, - selectedNetwork, - }); - if (pathObject.isErr()) { - return err(pathObject.error.message); - } - const generatedAddresses = await generateAddresses({ - selectedWallet: walletName, - selectedNetwork, - addressAmount, - changeAddressAmount, - keyDerivationPath: pathObject.value, - addressType: type, - }); - if (generatedAddresses.isErr()) { - return err(generatedAddresses.error); - } - const { addresses, changeAddresses } = generatedAddresses.value; - const addressZeroIndex = Object.values(addresses).find( - (a) => a.index === 0, - ); - const changeAddressZeroIndex = Object.values(changeAddresses).find( - (a) => a.index === 0, - ); - if (addressZeroIndex) { - addressIndex[selectedNetwork][type] = addressZeroIndex; - } - if (changeAddressZeroIndex) { - changeAddressIndex[selectedNetwork][type] = changeAddressZeroIndex; - } - addressesObj[selectedNetwork][type] = addresses; - changeAddressesObj[selectedNetwork][type] = changeAddresses; + await createDefaultWalletStructure({ walletName }); + + const defaultWalletShape = getDefaultWalletShape(); + const setupWalletRes = await setupOnChainWallet({ + name: walletName, + selectedNetwork, + bip39Passphrase: bip39Passphrase, + addressType: selectedAddressType, + }); + if (setupWalletRes.isErr()) { + return err(setupWalletRes.error.message); } + const walletData = setupWalletRes.value.data; const payload: IWallets = { [walletName]: { @@ -2039,13 +1378,49 @@ export const createDefaultWallet = async ({ bitcoinTestnet: selectedAddressType, bitcoinRegtest: selectedAddressType, }, - addressIndex, - changeAddressIndex, - addresses: addressesObj, - changeAddresses: changeAddressesObj, - lastUsedAddressIndex, - lastUsedChangeAddressIndex, - id: walletName, + addressIndex: { + ...defaultWalletShape.addressIndex, + [selectedNetwork]: { + ...defaultWalletShape.addressIndex[selectedNetwork], + ...walletData.addressIndex, + }, + }, + changeAddressIndex: { + ...defaultWalletShape.changeAddressIndex, + [selectedNetwork]: { + ...defaultWalletShape.changeAddressIndex[selectedNetwork], + ...walletData.changeAddressIndex, + }, + }, + addresses: { + ...defaultWalletShape.addresses, + [selectedNetwork]: { + ...defaultWalletShape.addresses[selectedNetwork], + ...walletData.addresses, + }, + }, + changeAddresses: { + ...defaultWalletShape.changeAddresses, + [selectedNetwork]: { + ...defaultWalletShape.changeAddresses[selectedNetwork], + ...walletData.changeAddresses, + }, + }, + lastUsedAddressIndex: { + ...defaultWalletShape.lastUsedAddressIndex, + [selectedNetwork]: { + ...defaultWalletShape.lastUsedAddressIndex[selectedNetwork], + ...walletData.lastUsedAddressIndex, + }, + }, + lastUsedChangeAddressIndex: { + ...defaultWalletShape.lastUsedChangeAddressIndex, + [selectedNetwork]: { + ...defaultWalletShape.lastUsedChangeAddressIndex[selectedNetwork], + ...walletData.lastUsedChangeAddressIndex, + }, + }, + id: walletData.id, }, }; return ok(payload); @@ -2054,6 +1429,134 @@ export const createDefaultWallet = async ({ } }; +const onElectrumConnectionChange = (isConnected: boolean): void => { + // get state fresh from store everytime + const { isConnectedToElectrum } = getStore().ui; + + if (!isConnectedToElectrum && isConnected) { + dispatch(updateUi({ isConnectedToElectrum: isConnected })); + showToast({ + type: 'success', + title: i18n.t('other:connection_restored_title'), + description: i18n.t('other:connection_restored_message'), + }); + } + + if (isConnectedToElectrum && !isConnected) { + dispatch(updateUi({ isConnectedToElectrum: isConnected })); + showToast({ + type: 'error', + title: i18n.t('other:connection_reconnect_title'), + description: i18n.t('other:connection_reconnect_msg'), + }); + } +}; + +const onMessage: TOnMessage = (key, data) => { + switch (key) { + case 'transactionReceived': + const txMsg: TTransactionMessage = data as TTransactionMessage; + showNewOnchainTxPrompt({ + id: txMsg.transaction.txid, + value: btcToSats(txMsg.transaction.value), + }); + updateActivityList(); + break; + case 'transactionSent': + updateActivityList(); + break; + case 'connectedToElectrum': + onElectrumConnectionChange(data as boolean); + break; + case 'reorg': + const utxoArr = data as IUtxo[]; + // Notify user that a reorg has occurred and that the transaction has been pushed back into the mempool. + showToast({ + type: 'info', + title: i18n.t('wallet:reorg_detected'), + description: i18n.t('wallet:reorg_msg_begin', { + count: utxoArr.length, + }), + autoHide: false, + }); + break; + case 'rbf': + const rbfData = data as string[]; + showToast({ + type: 'error', + title: i18n.t('wallet:activity_removed_title'), + description: i18n.t('wallet:activity_removed_msg', { + count: rbfData.length, + }), + autoHide: false, + }); + break; + case 'newBlock': + refreshWallet({}).then(); + } +}; + +export const setupOnChainWallet = async ({ + name = getSelectedWallet(), + mnemonic, + bip39Passphrase, + selectedNetwork = getSelectedNetwork(), + addressType = getSelectedAddressType(), + setStorage = true, +}: { + name: TWalletName; + mnemonic?: string; + bip39Passphrase?: string; + selectedNetwork?: EAvailableNetwork; + addressType?: EAddressType; + setStorage?: boolean; +}): Promise> => { + if ( + wallet && + wallet.name === name && + getNetworkFromBeignet(wallet.network) === selectedNetwork + ) { + return ok(wallet); + } + if (!mnemonic) { + const mnemonicRes = await getMnemonicPhrase(name); + if (mnemonicRes.isErr()) { + return err(mnemonicRes.error.message); + } + mnemonic = mnemonicRes.value; + } + // Fetch any stored custom peers. + const customPeers = getCustomElectrumPeers({ selectedNetwork }); + let storage; + if (setStorage) { + storage = { + getData: getWalletData, + setData: setWalletData, + }; + } + const createWalletResponse = await Wallet.create({ + name, + mnemonic, + onMessage, + passphrase: bip39Passphrase, + network: EAvailableNetworks[selectedNetwork], + electrumOptions: { + servers: customPeers, + tls: global.tls, + net: global.net, + }, + storage, + addressType, + customGetAddress: customGetAddress, + customGetScriptHash: getCustomScriptHash, + }); + if (createWalletResponse.isErr()) { + return err(createWalletResponse.error.message); + } + wallet = createWalletResponse.value; + return ok(wallet); +}; + /** * large = Sort by and use largest UTXO first. Lowest fee, but reveals your largest UTXO's and reduces privacy. * small = Sort by and use smallest UTXO first. Higher fee, but hides your largest UTXO's and increases privacy. @@ -2253,9 +1756,10 @@ export const getKeyDerivationPathString = ({ } if (selectedNetwork) { - path.coinType = selectedNetwork.toLocaleLowerCase().includes('testnet') - ? '1' - : '0'; + path.coinType = + selectedNetwork.toLocaleLowerCase() === EAvailableNetworks.bitcoin + ? '0' + : '1'; } if (accountType) { path.account = getKeyDerivationAccount(accountType); @@ -2305,9 +1809,10 @@ export const getKeyDerivationPathObject = ({ let coinType = parsedPath[2] as TKeyDerivationCoinType; if (selectedNetwork) { - coinType = selectedNetwork.toLocaleLowerCase().includes('testnet') - ? '1' - : '0'; + coinType = + selectedNetwork.toLocaleLowerCase() === EAvailableNetworks.bitcoin + ? '0' + : '1'; } let account = parsedPath[3] as TKeyDerivationAccount; @@ -2389,64 +1894,24 @@ export const getAddressTypePath = ({ * Returns the next available receive address for the given network and wallet. * @param {EAddressType} [addressType] * @param {EAvailableNetwork} [selectedNetwork] - * @param {TWalletName} [selectedWallet] * @return {Result} */ export const getReceiveAddress = async ({ addressType, - selectedNetwork, - selectedWallet, + selectedNetwork = getSelectedNetwork(), }: { addressType?: EAddressType; selectedNetwork?: EAvailableNetwork; - selectedWallet?: TWalletName; }): Promise> => { try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } if (!addressType) { - addressType = getSelectedAddressType({ selectedNetwork, selectedWallet }); - } - const wallet = getWalletStore().wallets[selectedWallet]; - const addressIndex = wallet.addressIndex[selectedNetwork]; - const receiveAddress = addressIndex[addressType].address; - if (receiveAddress) { - return ok(receiveAddress); - } - const addresses = wallet?.addresses[selectedNetwork][addressType]; - - // Check if addresses were generated, but the index has not been set yet. - if ( - Object.keys(addresses).length > 0 && - addressIndex[addressType].index < 0 - ) { - // Grab and return the address at index 0. - const address = Object.values(addresses).find(({ index }) => index === 0); - if (address) { - return ok(address.address); - } - } - // Fallback to generating a new receive address on the fly. - const generatedAddress = await generateNewReceiveAddress({ - selectedWallet, - selectedNetwork, - addressType, - }); - if (generatedAddress.isOk()) { - return ok(generatedAddress.value.address); - } else { - console.log(generatedAddress.error.message); + addressType = getSelectedAddressType({ selectedNetwork }); } - return err('No receive address available.'); + return wallet.getReceiveAddress({ addressType }); } catch (e) { return err(e); } }; - /** * Retrieves wallet balances for the currently selected wallet and network. * @param {TWalletName} [selectedWallet] @@ -2638,79 +2103,23 @@ export const getAddressIndexInfo = ({ * This method will clear the utxo array for each address type and reset the * address indexes back to the original/default app values. Once cleared & reset * the app will rescan the wallet's addresses from index zero. - * @param selectedWallet - * @param selectedNetwork * @param {boolean} [shouldClearAddresses] - Clears and re-generates all addresses when true. + * @param {boolean} [shouldClearTransactions] * @returns {Promise>} */ export const rescanAddresses = async ({ - selectedWallet, - selectedNetwork, shouldClearAddresses = true, + shouldClearTransactions = false, }: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; shouldClearAddresses?: boolean; -}): Promise> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (shouldClearAddresses) { - clearAddresses({ selectedWallet, selectedNetwork }); - } - clearTransactions({ selectedWallet, selectedNetwork }); - await clearUtxos({ selectedWallet, selectedNetwork }).then(); - await resetAddressIndexes({ selectedWallet, selectedNetwork }); - // Wait to generate our zero index addresses. - await createZeroIndexAddresses({ selectedWallet, selectedNetwork }); - return await refreshWallet({ - onchain: true, - lightning: false, - scanAllAddresses: true, - selectedWallet, - selectedNetwork, - updateAllAddressTypes: true, - showNotification: false, + shouldClearTransactions?: boolean; +}): Promise> => { + return wallet.rescanAddresses({ + shouldClearAddresses, + shouldClearTransactions, }); }; -/** - * Creates and sets zero address indexes for each address type. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @returns {Promise} - */ -export const createZeroIndexAddresses = async ({ - selectedWallet, - selectedNetwork, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Promise => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - await Promise.all( - Object.values(addressTypes).map(async ({ type }) => { - await addAddresses({ - selectedWallet, - selectedNetwork, - addressAmount: 1, - changeAddressAmount: 1, - addressIndex: 0, - changeAddressIndex: 0, - addressType: type, - }); - }), - ); -}; - /** * Returns the current wallet's unconfirmed transactions. * @param {TWalletName} [selectedWallet] @@ -2743,23 +2152,20 @@ export const getUnconfirmedTransactions = async ({ * Returns the number of confirmations for a given block height. * @param {number} height * @param {number} [currentHeight] - * @param {EAvailableNetwork} [selectedNetwork] * @returns {number} */ export const blockHeightToConfirmations = ({ blockHeight, currentHeight, - selectedNetwork, }: { blockHeight?: number; currentHeight?: number; - selectedNetwork?: EAvailableNetwork; }): number => { if (!blockHeight) { return 0; } if (!currentHeight) { - const header = getBlockHeader({ selectedNetwork }); + const header = getBlockHeader(); currentHeight = header.height; } if (currentHeight < blockHeight) { @@ -2772,23 +2178,17 @@ export const blockHeightToConfirmations = ({ * Returns the block height for a given number of confirmations. * @param {number} confirmations * @param {number} [currentHeight] - * @param {EAvailableNetwork} [selectedNetwork] * @returns {number} */ export const confirmationsToBlockHeight = ({ confirmations, currentHeight, - selectedNetwork, }: { confirmations: number; currentHeight?: number; - selectedNetwork?: EAvailableNetwork; }): number => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } if (!currentHeight) { - const header = getBlockHeader({ selectedNetwork }); + const header = getBlockHeader(); currentHeight = header.height; } if (confirmations > currentHeight) { @@ -2796,3 +2196,50 @@ export const confirmationsToBlockHeight = ({ } return currentHeight - confirmations; }; + +export const getOnChainWallet = (): Wallet => { + return wallet; +}; + +export const getOnChainWalletTransaction = (): Transaction => { + return wallet.transaction; +}; + +export const getOnChainWalletElectrum = (): Electrum => { + return wallet?.electrum; +}; + +export const getOnChainWalletTransactionData = (): ISendTransaction => { + return wallet.transaction.data; +}; + +export const getOnChainWalletData = (): IWalletData => { + return wallet.data; +}; + +export const switchNetwork = async ( + selectedNetwork: EAvailableNetwork, + servers?: TServer | TServer[], +): Promise> => { + if (!servers) { + servers = getCustomElectrumPeers({ + selectedNetwork, + }); + } + const response = await wallet.switchNetwork( + EAvailableNetworks[selectedNetwork], + servers, + ); + if (response.isErr()) { + return err(response.error.message); + } + // Start wallet services with the newly selected network. + await startWalletServices({ + selectedNetwork, + onchain: false, + }); + setTimeout(() => { + updateActivityList(); + }, 500); + return ok(true); +}; diff --git a/src/utils/wallet/transactions.ts b/src/utils/wallet/transactions.ts index d0f41b7a7..ecb6afad3 100644 --- a/src/utils/wallet/transactions.ts +++ b/src/utils/wallet/transactions.ts @@ -1,15 +1,12 @@ -import * as bip21 from 'bip21'; import ecc from '@bitcoinerlab/secp256k1'; import { BIP32Factory, BIP32Interface } from 'bip32'; import * as bip39 from 'bip39'; import * as bitcoin from 'bitcoinjs-lib'; import { Psbt } from 'bitcoinjs-lib'; import { err, ok, Result } from '@synonymdev/result'; -import * as electrum from 'rn-electrum-client/helpers'; import validate, { getAddressInfo } from 'bitcoin-address-validation'; import { __E2E__, __JEST__ } from '../../constants/env'; -import { validateAddress } from '../scanner'; import { networks, EAvailableNetwork } from '../networks'; import { reduceValue, shuffleArray } from '../helpers'; import { btcToSats, satsToBtc } from '../conversion'; @@ -17,14 +14,11 @@ import { getKeychainValue } from '../keychain'; import { EBoostType, EPaymentType, - ISendTransaction, IOutput, IUtxo, EAddressType, TGetByteCountInputs, TGetByteCountOutputs, - TGetByteCountInput, - TGetByteCountOutput, TWalletName, } from '../../store/types/wallet'; import { @@ -32,14 +26,14 @@ import { getCurrentWallet, getMnemonicPhrase, getOnChainBalance, + getOnChainWallet, + getOnChainWalletElectrum, + getOnChainWalletTransaction, getRbfData, - getReceiveAddress, getScriptHash, getSelectedNetwork, getSelectedWallet, getTransactionById, - IVin, - IVout, refreshWallet, } from './index'; import { @@ -47,7 +41,6 @@ import { getSettingsStore, getWalletStore, } from '../../store/helpers'; -import { objectKeys } from '../objectKeys'; import { addBoostedTransaction, deleteOnChainTransactionById, @@ -62,101 +55,14 @@ import { import { showToast } from '../notifications'; import { getTransactions, subscribeToAddresses } from './electrum'; import { TOnchainActivityItem } from '../../store/types/activity'; -import { EFeeId, IOnchainFees } from '../../store/types/fees'; import { initialFeesState } from '../../store/slices/fees'; import { TRANSACTION_DEFAULTS } from './constants'; import i18n from '../i18n'; +import { EFeeId, getByteCount, IOnchainFees, ISendTransaction } from 'beignet'; bitcoin.initEccLib(ecc); const bip32 = BIP32Factory(ecc); -/* - * Attempts to parse any given string as an on-chain payment request. - * Returns an error if invalid. - */ -export const parseOnChainPaymentRequest = ( - data: string, - selectedNetwork?: EAvailableNetwork, -): Result<{ - address: string; - network: EAvailableNetwork; - sats: number; - message: string; -}> => { - try { - if (!data) { - return err('No data provided to parseOnChainPaymentRequest.'); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - - let validateAddressResult = validateAddress({ - address: data, - selectedNetwork, - }); - - if ( - validateAddressResult.isValid && - !data.includes(':' || '?' || '&' || '//') - ) { - return ok({ - address: data, - network: validateAddressResult.network, - sats: 0, - message: '', - }); - } - - //Determine if we need to parse any invoice data. - if (data.includes(':' || '?' || '&' || '//')) { - try { - //Remove slashes - if (data.includes('//')) { - data = data.replace('//', ''); - } - //bip21.decode will throw if anything other than "bitcoin" is passed to it. - //Replace any instance of "testnet" or "litecoin" with "bitcoin" - if (data.includes(':')) { - data = data.substring(data.indexOf(':') + 1); - data = `bitcoin:${data}`; - } - - // types are wrong for package 'bip21' - const result = bip21.decode(data) as { - address: string; - options: { [key: string]: string }; - }; - const address = result.address; - validateAddressResult = validateAddress({ address }); - //Ensure address is valid - if (!validateAddressResult.isValid) { - return err(`Invalid address: ${data}`); - } - let amount = 0; - let message = ''; - try { - amount = Number(result.options.amount) || 0; - } catch (e) {} - try { - message = result.options.message || ''; - } catch (e) {} - return ok({ - address, - network: validateAddressResult.network, - sats: Number((amount * 100000000).toFixed(0)), - message, - }); - } catch { - return err(data); - } - } - return err(data); - } catch { - return err(data); - } -}; - const setReplaceByFee = ({ psbt, setRbf = true, @@ -185,119 +91,6 @@ const setReplaceByFee = ({ } catch (e) {} }; -/* - Adapted from: https://gist.github.com/junderw/b43af3253ea5865ed52cb51c200ac19c - Usage: - getByteCount({'MULTISIG-P2SH:2-4':45},{'P2PKH':1}) Means "45 inputs of P2SH Multisig and 1 output of P2PKH" - getByteCount({'P2PKH':1,'MULTISIG-P2SH:2-3':2},{'P2PKH':2}) means "1 P2PKH input and 2 Multisig P2SH (2 of 3) inputs along with 2 P2PKH outputs" -*/ -export const getByteCount = ( - inputs: TGetByteCountInputs, - outputs: TGetByteCountOutputs, - message?: string, -): number => { - try { - // Base transaction weight - let totalWeight = 40; - let hasWitness = false; - - const types: { - inputs: { - [key in TGetByteCountInput]: number; - }; - multiSigInputs: { - 'MULTISIG-P2SH': number; - 'MULTISIG-P2WSH': number; - 'MULTISIG-P2SH-P2WSH': number; - }; - outputs: { - [key in TGetByteCountOutput]: number; - }; - } = { - inputs: { - P2WPKH: 108 + 41 * 4, - p2wpkh: 108 + 41 * 4 + 1, - P2PKH: 148 * 4, - p2pkh: 148 * 4 + 1, - P2SH: 108 + 64 * 4, - p2sh: 108 + 64 * 4 + 1, - 'P2SH-P2WPKH': 108 + 64 * 4, - P2TR: 57.5 * 4, - p2tr: 57.5 * 4 + 1, - }, - multiSigInputs: { - 'MULTISIG-P2SH': 49 * 4, - 'MULTISIG-P2WSH': 6 + 41 * 4, - 'MULTISIG-P2SH-P2WSH': 6 + 76 * 4, - }, - outputs: { - P2SH: 32 * 4, - P2PKH: 34 * 4, - P2WPKH: 31 * 4, - P2WSH: 43 * 4, - p2wpkh: 31 * 4 + 1, - p2sh: 32 * 4 + 1, - p2pkh: 34 * 4 + 1, - P2TR: 43 * 4, - p2tr: 43 * 4 + 1, - }, - }; - - const checkUInt53 = (n: number): void => { - if (n < 0 || n > Number.MAX_SAFE_INTEGER || n % 1 !== 0) { - throw new RangeError('value out of range'); - } - }; - - const inputKeys = objectKeys(inputs); - inputKeys.forEach(function (key) { - const input = inputs[key]!; - checkUInt53(input); - const addressTypeCount = input || 1; - if (key.slice(0, 8) === 'MULTISIG') { - // ex. "MULTISIG-P2SH:2-3" would mean 2 of 3 P2SH MULTISIG - const keyParts = key.split(':'); - if (keyParts.length !== 2) { - throw new Error('invalid input: ' + key); - } - const newKey = keyParts[0]; - const mAndN = keyParts[1].split('-').map((item) => parseInt(item, 10)); - - totalWeight += types.multiSigInputs[newKey] * addressTypeCount; - const multiplyer = newKey === 'MULTISIG-P2SH' ? 4 : 1; - totalWeight += - (73 * mAndN[0] + 34 * mAndN[1]) * multiplyer * addressTypeCount; - } else { - totalWeight += types.inputs[key] * addressTypeCount; - } - if (['p2sh', 'P2SH', 'P2SH-P2WPKH', 'p2wpkh', 'P2WPKH'].includes(key)) { - hasWitness = true; - } - }); - - const outputKeys = objectKeys(outputs); - outputKeys.forEach(function (key) { - const output = outputs[key]!; - checkUInt53(output); - totalWeight += types.outputs[key] * output; - }); - - if (hasWitness) { - totalWeight += 2; - } - - if (message?.length) { - // Multiply by 2 to help ensure Electrum servers will broadcast the tx. - totalWeight += message.length * 2; - } - - // Convert from Weight Units to virtual size - return Math.ceil(totalWeight / 4); - } catch (e) { - return TRANSACTION_DEFAULTS.recommendedBaseFee; - } -}; - /** * Constructs the parameter for getByteCount via an array of addresses. * @param {string[]} addresses @@ -349,10 +142,7 @@ export const getTotalFee = ({ if (!selectedWallet) { selectedWallet = getSelectedWallet(); } - const txDataResponse = getOnchainTransactionData({ - selectedNetwork, - selectedWallet, - }); + const txDataResponse = getOnchainTransactionData(); if (txDataResponse.isErr()) { // If error, return minimum fallback fee. return baseTransactionSize * satsPerByte; @@ -494,10 +284,7 @@ const createPsbtFromTransactionData = async ({ // If we have spare sats hanging around and the difference is greater than the dust limit, generate a changeAddress to send them to. const diffValue = balance - (outputValue + fee); if (diffValue > TRANSACTION_DEFAULTS.dustLimit) { - const changeAddressRes = await getChangeAddress({ - selectedWallet, - selectedNetwork, - }); + const changeAddressRes = await getChangeAddress({}); if (changeAddressRes.isErr()) { return err(changeAddressRes.error.message); } @@ -603,10 +390,7 @@ export const createFundedPsbtTransaction = async ({ selectedWallet: TWalletName; selectedNetwork: EAvailableNetwork; }): Promise> => { - const transactionData = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transactionData = getOnchainTransactionData(); if (transactionData.isErr()) { return err(transactionData.error.message); @@ -631,18 +415,11 @@ export const createFundedPsbtTransaction = async ({ export const signPsbt = async ({ psbt, bip32Interface, - selectedWallet, - selectedNetwork, }: { psbt: Psbt; bip32Interface: BIP32Interface; - selectedWallet: TWalletName; - selectedNetwork: EAvailableNetwork; }): Promise> => { - const transactionDataRes = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transactionDataRes = getOnchainTransactionData(); if (transactionDataRes.isErr()) { return err(transactionDataRes.error.message); } @@ -682,10 +459,7 @@ export const createTransaction = async ({ } // If no transaction data is provided, use the stored transaction object from storage. if (!transactionData) { - const transactionDataRes = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transactionDataRes = getOnchainTransactionData(); if (transactionDataRes.isErr()) { return err(transactionDataRes.error.message); } @@ -753,8 +527,6 @@ export const createTransaction = async ({ const signedPsbtRes = await signPsbt({ psbt, bip32Interface: bip32InterfaceRes.value, - selectedWallet, - selectedNetwork, }); if (signedPsbtRes.isErr()) { @@ -783,25 +555,11 @@ export const removeDustOutputs = (outputs: IOutput[]): IOutput[] => { /** * Returns onchain transaction data related to the specified network and wallet. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] + * @returns {Result} */ -export const getOnchainTransactionData = ({ - selectedWallet, - selectedNetwork, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Result => { +export const getOnchainTransactionData = (): Result => { try { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const transaction = - getWalletStore().wallets[selectedWallet].transaction[selectedNetwork]; + const transaction = getOnChainWalletTransaction().data; if (transaction) { return ok(transaction); } @@ -876,7 +634,6 @@ export const addInput = async ({ if (type === 'p2pkh') { const transaction = await getTransactions({ - selectedNetwork, txHashes: [{ tx_hash: input.tx_hash }], }); if (transaction.isErr()) { @@ -899,12 +656,10 @@ export const addInput = async ({ export const broadcastTransaction = async ({ rawTx, selectedNetwork, - selectedWallet, subscribeToOutputAddress = true, }: { rawTx: string; selectedNetwork?: EAvailableNetwork; - selectedWallet?: TWalletName; subscribeToOutputAddress?: boolean; }): Promise> => { if (!selectedNetwork) { @@ -916,13 +671,7 @@ export const broadcastTransaction = async ({ * This prevents updating the wallet prior to the Electrum server detecting the new tx in the mempool. */ if (subscribeToOutputAddress) { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const transaction = getOnchainTransactionData({ - selectedNetwork, - selectedWallet, - }); + const transaction = getOnchainTransactionData(); if (transaction.isErr()) { return err(transaction.error.message); } @@ -931,22 +680,15 @@ export const broadcastTransaction = async ({ const scriptHash = await getScriptHash(address, selectedNetwork); if (scriptHash) { await subscribeToAddresses({ - selectedNetwork, scriptHashes: [scriptHash], }); } } } - - const broadcastResponse = await electrum.broadcastTransaction({ + const electrum = getOnChainWalletElectrum(); + return await electrum.broadcastTransaction({ rawTx, - network: selectedNetwork, }); - // TODO: This needs to be resolved in rn-electrum-client - if (broadcastResponse.error || broadcastResponse.data.includes(' ')) { - return err(broadcastResponse.data); - } - return ok(broadcastResponse.data); }; /** @@ -973,10 +715,7 @@ export const getTransactionOutputValue = ({ if (!selectedNetwork) { selectedNetwork = getSelectedNetwork(); } - const transaction = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transaction = getOnchainTransactionData(); if (transaction.isErr()) { return 0; } @@ -1016,10 +755,7 @@ export const getTransactionInputValue = ({ if (!selectedNetwork) { selectedNetwork = getSelectedNetwork(); } - const transaction = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transaction = getOnchainTransactionData(); if (transaction.isErr()) { return 0; } @@ -1037,126 +773,31 @@ export const getTransactionInputValue = ({ } }; -/** - * Returns all inputs for the current transaction. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - */ -export const getTransactionInputs = ({ - selectedWallet, - selectedNetwork, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Result => { - try { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const txData = getOnchainTransactionData({ - selectedNetwork, - selectedWallet, - }); - if (txData.isErr()) { - return err(txData.error.message); - } - return ok(txData.value.inputs?.map((input) => input) ?? []); - } catch (e) { - console.log(e); - return err(e); - } -}; - /** * Updates the fee for the current transaction by the specified amount. * @param {number} [satsPerByte] * @param {EFeeId} [selectedFeeId] - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] * @param {number} [index] * @param {ISendTransaction} [transaction] */ export const updateFee = ({ satsPerByte, selectedFeeId = EFeeId.custom, - selectedWallet, - selectedNetwork, index = 0, transaction, }: { satsPerByte: number; selectedFeeId?: EFeeId; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; index?: number; transaction?: ISendTransaction; }): Result<{ fee: number }> => { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!transaction) { - const transactionDataResponse = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); - if (transactionDataResponse.isErr()) { - return err(transactionDataResponse.error.message); - } - transaction = transactionDataResponse.value; - } - const inputTotal = getTransactionInputValue({ - selectedNetwork, - selectedWallet, - inputs: transaction.inputs, - }); - - const { max, message, outputs } = transaction; - let address = ''; - if (outputs.length > index) { - address = outputs[index]?.address ?? ''; - } - - const newFee = getTotalFee({ satsPerByte, message }); - - //Return if the new fee exceeds half of the user's balance - if (newFee >= inputTotal / 2) { - return err(i18n.t('wallet:send_fee_error_half')); - } - - const totalTransactionValue = getTransactionOutputValue({ - selectedWallet, - selectedNetwork, - outputs, - }); - const newTotalAmount = totalTransactionValue + newFee; - const _transaction: Partial = { + const tx = getOnChainWalletTransaction(); + return tx.updateFee({ satsPerByte, - fee: newFee, + index, + transaction, selectedFeeId, - }; - - if (max) { - // Update the tx value with the new fee to continue sending the max amount. - _transaction.outputs = [{ address, value: inputTotal - newFee, index }]; - } - - // Check that the user has enough funds - if (max || newTotalAmount <= inputTotal) { - updateSendTransaction({ - selectedNetwork, - selectedWallet, - transaction: _transaction, - }); - return ok({ fee: newFee }); - } - - return err(i18n.t('wallet:send_fee_error_max')); + }); }; /** @@ -1341,67 +982,6 @@ export const autoCoinSelect = async ({ } }; -/** - * Used to validate transaction form data. - * @param {ISendTransaction} transaction - * @return {Result} - */ -export const validateTransaction = ( - transaction: ISendTransaction, -): Result => { - const baseFee = TRANSACTION_DEFAULTS.recommendedBaseFee; - - try { - if (!transaction.fee) { - return err('Please provide a transaction fee.'); - } - if (transaction.outputs.length < 1 || !transaction.outputs[0].address) { - return err('Please provide an address to send funds to.'); - } - if (transaction.outputs.length > 0 && !transaction.outputs[0].value) { - return err('Please provide an amount to send.'); - } - const inputs = transaction.inputs; - const outputs = transaction.outputs; - for (let i = 0; i < outputs.length; i++) { - const address = outputs[i]?.address ?? ''; - const value = outputs[i]?.value ?? 0; - const { isValid } = validateAddress({ address }); - if (!isValid) { - return err('The provided address is invalid.'); - } - if (value < baseFee) { - return err( - `The output value must be greater than or equal to ${baseFee} sats.`, - ); - } - if (!Number.isInteger(value)) { - return err('Please specify an integer value.'); - } - } - - const inputsReduce = reduceValue(inputs, 'value'); - if (inputsReduce.isErr()) { - return err(inputsReduce.error.message); - } - //Remove the change address from the outputs array, if any. - let filteredOutputs = outputs; - if (transaction.changeAddress) { - filteredOutputs = outputs.filter((output) => { - return output.address !== transaction.changeAddress; - }); - } - const outputsReduce = reduceValue(filteredOutputs, 'value'); - if (outputsReduce.isErr()) { - return err(outputsReduce.error.message); - } - - return ok('Transaction is valid.'); - } catch (e) { - return err(e); - } -}; - export interface ICanBoostResponse { canBoost: boolean; rbf: boolean; @@ -1425,9 +1005,7 @@ export const canBoost = (txid: string): ICanBoostResponse => { const balance = getOnChainBalance(); const { currentWallet, selectedNetwork } = getCurrentWallet(); - const hasUtxo = currentWallet.utxos[selectedNetwork].some( - (utxo) => txid === utxo.tx_hash, - ); + const hasUtxo = currentWallet.utxos[selectedNetwork].length > 0; const { type, matchedOutputValue, totalOutputValue, fee, height } = transactionResponse.value; @@ -1479,10 +1057,7 @@ export const getMaxSendAmount = ({ }): Result<{ amount: number; fee?: number }> => { try { if (!transaction) { - const transactionDataResponse = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transactionDataResponse = getOnchainTransactionData(); if (transactionDataResponse.isErr()) { return err(transactionDataResponse.error.message); } @@ -1502,29 +1077,7 @@ export const getMaxSendAmount = ({ }; return ok(maxAmount); } else { - // onchain transaction - const { onchainBalance } = getBalance({ - selectedWallet, - selectedNetwork, - }); - - const inputValue = getTransactionInputValue({ - selectedNetwork, - selectedWallet, - inputs: transaction.inputs, - }); - const amount = onchainBalance > inputValue ? onchainBalance : inputValue; - - const currentWallet = getWalletStore().wallets[selectedWallet]; - let utxos: IUtxo[] = []; - //Ensure we add the larger utxo set for a more accurate fee. - if ( - transaction.inputs.length > currentWallet?.utxos[selectedNetwork].length - ) { - utxos = transaction.inputs; - } else { - utxos = currentWallet?.utxos[selectedNetwork] ?? []; - } + const wallet = getOnChainWallet(); const fees = getFeesStore().onchain; const { transactionSpeed, customFeeRate } = getSettingsStore(); @@ -1542,28 +1095,11 @@ export const getMaxSendAmount = ({ ? EFeeId[transactionSpeed] : transaction.selectedFeeId; - const fee = getTotalFee({ + return wallet.transaction.getMaxSendAmount({ satsPerByte, - message: transaction.message, - transaction: { - ...transaction, - max: true, - inputs: utxos, - selectedFeeId, - satsPerByte, - }, + selectedFeeId, + transaction, }); - - const maxAmount = { - amount: amount - fee, - fee, - }; - - if (amount <= fee) { - return err('Balance is too low to spend.'); - } - - return ok(maxAmount); } } catch (e) { return err(e); @@ -1575,75 +1111,39 @@ export const getMaxSendAmount = ({ * @param {string} [address] If left undefined, the current receiving address will be provided. * @param {ISendTransaction} [transaction] * @param {number} [index] - * @param {EAvailableNetwork} [selectedNetwork] - * @param {TWalletName} [selectedWallet] */ -export const sendMax = ({ +export const sendMax = async ({ address, - transaction, index = 0, - selectedNetwork, - selectedWallet, }: { address?: string; - transaction?: Partial; index?: number; - selectedNetwork?: EAvailableNetwork; - selectedWallet?: TWalletName; -} = {}): Result => { +} = {}): Promise> => { try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!transaction) { - const transactionDataResponse = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); - if (transactionDataResponse.isErr()) { - return err(transactionDataResponse.error.message); - } - transaction = transactionDataResponse.value; - } - const outputs = transaction.outputs ?? []; - // No address specified, attempt to assign the address currently specified in the current output index. - if (!address) { - address = outputs[index]?.address ?? ''; - } - - const maxAmountResponse = getMaxSendAmount({ - selectedWallet, - selectedNetwork, + const tx = getOnChainWalletTransaction(); + const transaction = tx.data; + const fees = getFeesStore().onchain; + const { transactionSpeed, customFeeRate } = getSettingsStore(); + + const preferredFeeRate = + transactionSpeed === ETransactionSpeed.custom + ? customFeeRate + : fees[transactionSpeed]; + + const satsPerByte = + transaction.selectedFeeId === 'none' + ? preferredFeeRate + : transaction.satsPerByte; + const _transaction = { + ...tx.data, + ...transaction, + }; + return await tx.sendMax({ + address, + transaction: _transaction, + index, + satsPerByte, }); - if (maxAmountResponse.isErr()) { - return err(maxAmountResponse.error); - } - const { amount, fee } = maxAmountResponse.value; - - if (!transaction.max) { - updateSendTransaction({ - selectedWallet, - selectedNetwork, - transaction: { - max: true, - outputs: [{ address, value: amount, index }], - fee, - }, - }); - } else { - updateSendTransaction({ - selectedWallet, - selectedNetwork, - transaction: { - max: false, - }, - }); - } - - return ok('Successfully setup max send transaction.'); } catch (e) { return err(e); } @@ -1653,32 +1153,17 @@ export const sendMax = ({ * Adjusts the fee by a given sat per byte. * @param {number} [adjustBy] * @param {ISendTransaction} [transaction] - * @param {EAvailableNetwork} [selectedNetwork] - * @param {TWalletName} [selectedWallet] */ export const adjustFee = ({ adjustBy, transaction, - selectedNetwork, - selectedWallet, }: { adjustBy: number; transaction?: ISendTransaction; - selectedNetwork?: EAvailableNetwork; - selectedWallet?: TWalletName; }): Result<{ fee: number }> => { try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } if (!transaction) { - const transactionDataResponse = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transactionDataResponse = getOnchainTransactionData(); if (transactionDataResponse.isErr()) { return err(transactionDataResponse.error.message); } @@ -1691,11 +1176,9 @@ export const adjustFee = ({ } const response = updateFee({ transaction, - selectedWallet, - selectedNetwork, satsPerByte: newSatsPerByte, - selectedFeeId: EFeeId.custom, }); + // TODO: Enable runCoinSelect. // if (address && coinSelectPreference !== 'consolidate') { // runCoinSelect({ selectedWallet, selectedNetwork }); // } @@ -1730,10 +1213,7 @@ export const updateSendAmount = ({ selectedWallet = getSelectedWallet(); } if (!transaction) { - const transactionDataResponse = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transactionDataResponse = getOnchainTransactionData(); if (transactionDataResponse.isErr()) { return err(transactionDataResponse.error.message); } @@ -1798,8 +1278,6 @@ export const updateSendAmount = ({ } updateSendTransaction({ - selectedWallet, - selectedNetwork, transaction: { outputs: [{ ...currentOutput, value: amount }], max, @@ -1838,10 +1316,7 @@ export const updateMessage = async ({ selectedWallet = getSelectedWallet(); } if (!transaction) { - const transactionDataResponse = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transactionDataResponse = getOnchainTransactionData(); if (transactionDataResponse.isErr()) { return err(transactionDataResponse.error.message); } @@ -1876,16 +1351,12 @@ export const updateMessage = async ({ _transaction.outputs = [{ address, value: inputTotal - newFee, index }]; //Update the tx value with the new fee to continue sending the max amount. updateSendTransaction({ - selectedNetwork, - selectedWallet, transaction: _transaction, }); return ok('Successfully updated the message.'); } if (totalNewAmount <= inputTotal) { updateSendTransaction({ - selectedNetwork, - selectedWallet, transaction: _transaction, }); } @@ -1996,7 +1467,7 @@ export const setupBoost = async ({ if (canBoostResponse.rbf) { return await setupRbf({ selectedWallet, selectedNetwork, txid }); } else { - return await setupCpfp({ selectedNetwork, selectedWallet, txid }); + return await setupCpfp({ txid }); } }; @@ -2008,58 +1479,14 @@ export const setupBoost = async ({ * @param {number} [satsPerByte] */ export const setupCpfp = async ({ - selectedWallet, - selectedNetwork, txid, satsPerByte, }: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; txid?: string; // txid of utxo to include in the CPFP tx. Undefined will gather all utxo's. satsPerByte?: number; -}): Promise>> => { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - const response = await setupOnChainTransaction({ - selectedWallet, - selectedNetwork, - inputTxHashes: txid ? [txid] : undefined, - rbf: true, - }); - if (response.isErr()) { - return err(response.error?.message); - } - - const receiveAddress = await getReceiveAddress({ - selectedWallet, - selectedNetwork, - }); - if (receiveAddress.isErr()) { - return err(receiveAddress.error.message); - } - - // Construct the tx to send funds back to ourselves using the assigned inputs, receive address and fee. - const sendMaxResponse = sendMax({ - selectedWallet, - selectedNetwork, - transaction: { - ...response.value, - satsPerByte: satsPerByte ?? response.value.satsPerByte, - boostType: EBoostType.cpfp, - }, - address: receiveAddress.value, - }); - if (sendMaxResponse.isErr()) { - return err(sendMaxResponse.error.message); - } - - const transaction = - getWalletStore().wallets[selectedWallet].transaction[selectedNetwork]; - return ok(transaction); +}): Promise> => { + const transaction = getOnChainWalletTransaction(); + return await transaction.setupCpfp({ txid, satsPerByte }); }; /** @@ -2085,20 +1512,14 @@ export const setupRbf = async ({ selectedWallet = getSelectedWallet(); } await setupOnChainTransaction({ - selectedNetwork, - selectedWallet, rbf: true, }); const response = await getRbfData({ txHash: { tx_hash: txid }, - selectedNetwork, - selectedWallet, }); if (response.isErr()) { if (response.error.message === 'cpfp') { return await setupCpfp({ - selectedNetwork, - selectedWallet, txid, }); } @@ -2139,8 +1560,6 @@ export const setupRbf = async ({ 'Not enough sats to support an RBF transaction. Attempting to CPFP instead.', ); return await setupCpfp({ - selectedNetwork, - selectedWallet, txid, }); } @@ -2154,8 +1573,6 @@ export const setupRbf = async ({ }; updateSendTransaction({ - selectedWallet, - selectedNetwork, transaction: newTransaction, }); return ok(newTransaction); @@ -2188,10 +1605,7 @@ export const broadcastBoost = async ({ if (!selectedNetwork) { selectedNetwork = getSelectedNetwork(); } - const transactionDataResponse = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transactionDataResponse = getOnchainTransactionData(); if (transactionDataResponse.isErr()) { return err(transactionDataResponse.error.message); } @@ -2221,8 +1635,6 @@ export const broadcastBoost = async ({ newTxId, oldTxId, type: transaction.boostType, - selectedWallet, - selectedNetwork, fee: boostedFee, }); @@ -2230,8 +1642,6 @@ export const broadcastBoost = async ({ if (transaction.boostType === EBoostType.rbf && oldTxId in transactions) { await deleteOnChainTransactionById({ txid: oldTxId, - selectedNetwork, - selectedWallet, }); } @@ -2250,71 +1660,6 @@ export const broadcastBoost = async ({ } }; -/** - * Attempts to decode a tx hex. - * Source: https://github.com/bitcoinjs/bitcoinjs-lib/issues/1606#issuecomment-664740672 - * @param {string} hex - * @param {EAvailableNetwork} [selectedNetwork] - */ -export const decodeRawTransaction = ( - hex: string, - selectedNetwork?: EAvailableNetwork, -): Result<{ - txid: string; - tx_hash: string; - size: number; - vsize: number; - weight: number; - version: number; - locktime: number; - vin: IVin[]; - vout: IVout[]; -}> => { - try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - const network = networks[selectedNetwork]; - const tx = bitcoin.Transaction.fromHex(hex); - return ok({ - txid: tx.getId(), - tx_hash: tx.getHash(true).toString('hex'), - size: tx.byteLength(), - vsize: tx.virtualSize(), - weight: tx.weight(), - version: tx.version, - locktime: tx.locktime, - vin: tx.ins.map((input) => ({ - txid: Buffer.from(input.hash).reverse().toString('hex'), - vout: input.index, - scriptSig: { - asm: bitcoin.script.toASM(input.script), - hex: input.script.toString('hex'), - }, - txinwitness: input.witness.map((b) => b.toString('hex')), - sequence: input.sequence, - })), - vout: tx.outs.map((output, i) => { - let address; - try { - address = bitcoin.address.fromOutputScript(output.script, network); - } catch (e) {} - return { - value: output.value, - n: i, - scriptPubKey: { - asm: bitcoin.script.toASM(output.script), - hex: output.script.toString('hex'), - address, - }, - }; - }), - }); - } catch (e) { - return err(e); - } -}; - export interface IGetFeeEstimatesResponse { fastestFee: number; halfHourFee: number; @@ -2383,10 +1728,7 @@ export const getSelectedFeeId = ({ if (!selectedNetwork) { selectedNetwork = getSelectedNetwork(); } - const transaction = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transaction = getOnchainTransactionData(); if (transaction.isErr()) { return EFeeId.none; } @@ -2395,23 +1737,15 @@ export const getSelectedFeeId = ({ /** * Returns the amount of sats to send to a given output address in the transaction object by its index. - * @param selectedWallet - * @param selectedNetwork * @param outputIndex + * @returns {Result} */ export const getTransactionOutputAmount = ({ - selectedWallet, - selectedNetwork, outputIndex = 0, }: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; outputIndex?: number; }): Result => { - const transaction = getOnchainTransactionData({ - selectedWallet, - selectedNetwork, - }); + const transaction = getOnchainTransactionData(); if (transaction.isErr()) { return err(transaction.error.message); } diff --git a/yarn.lock b/yarn.lock index 5cc10a973..54b6227c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1674,7 +1674,7 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@bitcoinerlab/secp256k1@1.0.5": +"@bitcoinerlab/secp256k1@1.0.5", "@bitcoinerlab/secp256k1@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@bitcoinerlab/secp256k1/-/secp256k1-1.0.5.tgz#4643ba73619c24c7c455cc63c6338c69c2cf187c" integrity sha512-8gT+ukTCFN2rTxn4hD9Jq3k+UJwcprgYjfK/SQUSLgznXoIgsBnlPuARMkyyuEjycQK9VvnPiejKdszVTflh+w== @@ -3159,10 +3159,10 @@ cross-fetch "^3.1.4" node-fetch "3.1.1" -"@synonymdev/blocktank-lsp-http-client@0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@synonymdev/blocktank-lsp-http-client/-/blocktank-lsp-http-client-0.9.0.tgz#b0458ec8c17d01071ecd743121e52ad6b84734b8" - integrity sha512-6Q7cTpCY/rg3qk4Pp5HqDnrIUGBNnzTV1zPNzvJKfMtS3dGapR2BwsVzyIkTswZAP3qGKFOrtKs1LA7GbolPiw== +"@synonymdev/blocktank-lsp-http-client@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@synonymdev/blocktank-lsp-http-client/-/blocktank-lsp-http-client-0.10.0.tgz#9bedd8e73241c9c4182eccf51b76484f4ed717f3" + integrity sha512-TcFzU9AQRAUk/D9RvtsGHwgCrVkaSjaqEY71T2D+yJPwx5U2H0coc9eBfr5AYfVi49QZzgIfV8A0wRcvVlbcLA== dependencies: axios "^1.4.0" @@ -4385,6 +4385,24 @@ bech32@^1.1.2, bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== +beignet@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.3.tgz#174c2849b42f84d066b98774234c5a0517f6eca2" + integrity sha512-NJ2WRBCaML8vj24f4/n62Cj5Jcbi5x9dJBzmV/BUGG+y37RMlX+YYP7rrJRtw2tbHFLsBaejoHze4NefBBAs4w== + dependencies: + "@bitcoinerlab/secp256k1" "^1.0.5" + bech32 "^2.0.0" + bip21 "^2.0.3" + bip32 "^4.0.0" + bip39 "^3.1.0" + bitcoin-address-validation "^2.2.3" + bitcoin-units "^0.3.0" + bitcoinjs-lib "6.1.4" + ecpair "^2.1.0" + lodash.clonedeep "^4.5.0" + net "^1.0.2" + rn-electrum-client "^0.0.10" + big-integer@1.6.x: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" @@ -4434,14 +4452,14 @@ bip174@^2.1.1: resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.1.tgz#ef3e968cf76de234a546962bcf572cc150982f9f" integrity sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ== -bip21@2.0.3: +bip21@2.0.3, bip21@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/bip21/-/bip21-2.0.3.tgz#6e95b542718af497b442d92ae8af433d6ea80368" integrity sha512-L4ODmASLjsHYAU+TG7xffkYNMvHzAe4mkVX7mcvOUyKAr/MDBPrsRgqUhE8EmKdeEKHk5SYpX1Aexzvm/6WdbQ== dependencies: qs "^6.3.0" -bip32@4.0.0: +bip32@4.0.0, bip32@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/bip32/-/bip32-4.0.0.tgz#7fac3c05072188d2d355a4d6596b37188f06aa2f" integrity sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ== @@ -4474,14 +4492,14 @@ bip39@3.0.4: pbkdf2 "^3.0.9" randombytes "^2.0.1" -bip39@^3.0.4: +bip39@^3.0.4, bip39@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A== dependencies: "@noble/hashes" "^1.2.0" -bitcoin-address-validation@2.2.3: +bitcoin-address-validation@2.2.3, bitcoin-address-validation@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/bitcoin-address-validation/-/bitcoin-address-validation-2.2.3.tgz#ffae6d48facd5ce7ef60574891aab979d21f9828" integrity sha512-1uGCGl26Ye8JG5qcExtFLQfuib6qEZWNDo1ZlLlwp/z7ygUFby3IxolgEfgMGaC+LG9csbVASLcH8fRLv7DIOg== @@ -4502,13 +4520,25 @@ bitcoin-json-rpc@^1.3.3: io-ts ">=2.2.0" lodash ">=4.17.21" -bitcoin-units@0.3.0: +bitcoin-units@0.3.0, bitcoin-units@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/bitcoin-units/-/bitcoin-units-0.3.0.tgz#014b22db005c31c02865166b5e5a46932865a9c0" integrity sha512-CxmCxfoYDKQhKXMvT1HnShVL1E6rT5IUkVenhrFDphrRGKQHJUTGtK68UY7JnAQKqJA4510cC5MO6aEd/8v1EQ== dependencies: big.js "^5.2.2" +bitcoinjs-lib@6.1.4: + version "6.1.4" + resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.4.tgz#c985c90ae4d0385f951436c36dc3d2555961a63f" + integrity sha512-MRbVBPJ0DI7nhnisoFYVbUGMQwZDrkfkTuZghtsblokq3OYRMuek2We1jhpAqDVwtM3CrO7AbwANdBZ/KgzQyg== + dependencies: + "@noble/hashes" "^1.2.0" + bech32 "^2.0.0" + bip174 "^2.1.0" + bs58check "^3.0.1" + typeforce "^1.11.3" + varuint-bitcoin "^1.1.2" + bitcoinjs-lib@6.1.5: version "6.1.5" resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz#3b03509ae7ddd80a440f10fc38c4a97f0a028d8c" @@ -5984,6 +6014,15 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecpair@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ecpair/-/ecpair-2.1.0.tgz#673f826b1d80d5eb091b8e2010c6b588e8d2cb45" + integrity sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw== + dependencies: + randombytes "^2.1.0" + typeforce "^1.18.0" + wif "^2.0.6" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -8683,6 +8722,11 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -9448,6 +9492,11 @@ neo-async@^2.5.0, neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +net@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/net/-/net-1.0.2.tgz#d1757ec9a7fb2371d83cf4755ce3e27e10829388" + integrity sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ== + no-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" @@ -10432,7 +10481,7 @@ random-array-iterator@^1.0.0: resolved "https://registry.yarnpkg.com/random-array-iterator/-/random-array-iterator-1.0.0.tgz#1081f8922d68d2817368c13028f858ec308e146b" integrity sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q== -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== @@ -11167,6 +11216,11 @@ rn-android-keyboard-adjust@2.1.2: resolved "https://registry.yarnpkg.com/rn-android-keyboard-adjust/-/rn-android-keyboard-adjust-2.1.2.tgz#f2b792628700dcb026215420192317ce43fd954f" integrity sha512-pUYiMT7aucw5YnV4geGdalyl6o6rEwRYhDnw2oPQwDym1BZHtmKsxFLupky1Kj2ZNkNKadpEyoyHOElLo+f3sA== +rn-electrum-client@^0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/rn-electrum-client/-/rn-electrum-client-0.0.10.tgz#a889e30967484b1312c2afd260501e9b19cd845d" + integrity sha512-sYP9mdPGEnDjQZtMrv1JWc4pWEFTUBcGqe/K64whnmmz67aYppUAyblPCzyndsNr3Eu4KdjUK3tgmMh+Ha4v9g== + "rn-electrum-client@github:synonymdev/react-native-electrum-client#a32a292d4e4918a04d280137e24e5961c74c543f": version "0.0.8" resolved "https://codeload.github.com/synonymdev/react-native-electrum-client/tar.gz/a32a292d4e4918a04d280137e24e5961c74c543f" @@ -12342,7 +12396,7 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" -typeforce@^1.11.3, typeforce@^1.11.5: +typeforce@^1.11.3, typeforce@^1.11.5, typeforce@^1.18.0: version "1.18.0" resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== From 5b73ae9087187793418321e8e3fefca216d301e5 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Tue, 2 Jan 2024 11:00:53 -0500 Subject: [PATCH 02/50] chore(wallet): Upgrade Beignet to 0.0.4 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3b82ec2b5..7f7973d66 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@synonymdev/web-relay": "1.0.7", "backpack-client": "github:synonymdev/bitkit-backup-client#f08fdb28529d8a3f8bfecc789443c43b966a7581", "bech32": "2.0.0", - "beignet": "^0.0.3", + "beignet": "^0.0.4", "bip21": "2.0.3", "bip32": "4.0.0", "bip39": "3.0.4", diff --git a/yarn.lock b/yarn.lock index 54b6227c9..5e423120d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4385,10 +4385,10 @@ bech32@^1.1.2, bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -beignet@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.3.tgz#174c2849b42f84d066b98774234c5a0517f6eca2" - integrity sha512-NJ2WRBCaML8vj24f4/n62Cj5Jcbi5x9dJBzmV/BUGG+y37RMlX+YYP7rrJRtw2tbHFLsBaejoHze4NefBBAs4w== +beignet@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.4.tgz#1c82c793275bd214224e3b8b457a5f8355991cb9" + integrity sha512-LtQRnXefK+LfUm0ATbWOXOiNCNMvre7ZtdxY428YR1c6A5UopH/0Q1X036tLLUD8JcwnCJiqu0/Tsjh8Eiq9RQ== dependencies: "@bitcoinerlab/secp256k1" "^1.0.5" bech32 "^2.0.0" From f74ff96c425f73893da5c8fb9c6f7716b7774cc2 Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Tue, 2 Jan 2024 17:17:42 +0100 Subject: [PATCH 03/50] fix(profile): fix crash when saving profile (Android) (#1448) * fix(profile): fix crash when saving profile (Android) * chore: update Podfile --- ios/.xcode.env | 10 ++ ios/Podfile.lock | 4 +- ios/_xcode.env | 11 -- ios/bitkit.xcodeproj/project.pbxproj | 166 +++++++++++++-------------- package.json | 2 +- yarn.lock | 8 +- 6 files changed, 100 insertions(+), 101 deletions(-) delete mode 100644 ios/_xcode.env diff --git a/ios/.xcode.env b/ios/.xcode.env index 772b339b4..3d5782c71 100644 --- a/ios/.xcode.env +++ b/ios/.xcode.env @@ -1 +1,11 @@ +# This `.xcode.env` file is versioned and is used to source the environment +# used when running script phases inside Xcode. +# To customize your local environment, you can create an `.xcode.env.local` +# file that is not versioned. + +# NODE_BINARY variable contains the PATH to the node executable. +# +# Customize the NODE_BINARY variable here. +# For example, to use nvm with brew, add the following line +# . "$(brew --prefix nvm)/nvm.sh" --no-use export NODE_BINARY=$(command -v node) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 144f4961d..6cca7008f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -577,7 +577,7 @@ PODS: - React-Core - SSZipArchive (~> 2.2) - SocketRocket (0.6.1) - - sodium-react-native-direct (0.4.0): + - sodium-react-native-direct (0.4.1): - RCT-Folly (= 2021.07.22.00) - React-Core - SSZipArchive (2.4.3) @@ -951,7 +951,7 @@ SPEC CHECKSUMS: RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396 RNZipArchive: ef9451b849c45a29509bf44e65b788829ab07801 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 - sodium-react-native-direct: 9329173e4741e0bf301c1aba6499056013244620 + sodium-react-native-direct: bf2f1de7f24af4e7f368ac66f71f33c9c8f59449 SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef Yoga: 4c3aa327e4a6a23eeacd71f61c81df1bcdf677d5 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 diff --git a/ios/_xcode.env b/ios/_xcode.env deleted file mode 100644 index 3d5782c71..000000000 --- a/ios/_xcode.env +++ /dev/null @@ -1,11 +0,0 @@ -# This `.xcode.env` file is versioned and is used to source the environment -# used when running script phases inside Xcode. -# To customize your local environment, you can create an `.xcode.env.local` -# file that is not versioned. - -# NODE_BINARY variable contains the PATH to the node executable. -# -# Customize the NODE_BINARY variable here. -# For example, to use nvm with brew, add the following line -# . "$(brew --prefix nvm)/nvm.sh" --no-use -export NODE_BINARY=$(command -v node) diff --git a/ios/bitkit.xcodeproj/project.pbxproj b/ios/bitkit.xcodeproj/project.pbxproj index 8d42a65dd..8d883a3bb 100644 --- a/ios/bitkit.xcodeproj/project.pbxproj +++ b/ios/bitkit.xcodeproj/project.pbxproj @@ -11,13 +11,13 @@ 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 564E55664252E8BD1597D528 /* libPods-bitkit-bitkitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F3402FD392148CB7BCCDE99 /* libPods-bitkit-bitkitTests.a */; }; + 6EE171E60CA562D76D6C164B /* libPods-bitkit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1126359186FB8C99BF5A1C4B /* libPods-bitkit.a */; }; 777F5BE129EDEB75005E0E4B /* InterTight-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 777F5BDD29EDEB75005E0E4B /* InterTight-Bold.ttf */; }; 777F5BE229EDEB75005E0E4B /* InterTight-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 777F5BDE29EDEB75005E0E4B /* InterTight-Regular.ttf */; }; 777F5BE329EDEB75005E0E4B /* InterTight-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 777F5BDF29EDEB75005E0E4B /* InterTight-SemiBold.ttf */; }; 777F5BE429EDEB75005E0E4B /* InterTight-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 777F5BE029EDEB75005E0E4B /* InterTight-Medium.ttf */; }; - 794976AAAB7F4FE1195B031D /* libPods-bitkit-bitkitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A35C3C8516A56BABDE56B7AF /* libPods-bitkit-bitkitTests.a */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; - A7FDF85919C9BAEC4F91EE15 /* libPods-bitkit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 455CED7D780082F8A237F57F /* libPods-bitkit.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -34,23 +34,23 @@ 00E356EE1AD99517003FC87E /* bitkitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = bitkitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* bitkitTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = bitkitTests.m; sourceTree = ""; }; + 01B968BECD96E8A11F341553 /* Pods-bitkit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bitkit.release.xcconfig"; path = "Target Support Files/Pods-bitkit/Pods-bitkit.release.xcconfig"; sourceTree = ""; }; + 1126359186FB8C99BF5A1C4B /* libPods-bitkit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-bitkit.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* bitkit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitkit.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = bitkit/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = bitkit/AppDelegate.mm; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = bitkit/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = bitkit/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = bitkit/main.m; sourceTree = ""; }; - 1F0E33A9BDA691FD5327977D /* Pods-bitkit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bitkit.release.xcconfig"; path = "Target Support Files/Pods-bitkit/Pods-bitkit.release.xcconfig"; sourceTree = ""; }; - 455CED7D780082F8A237F57F /* libPods-bitkit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-bitkit.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 2316ADDE3C53AB36EA2F206A /* Pods-bitkit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bitkit.debug.xcconfig"; path = "Target Support Files/Pods-bitkit/Pods-bitkit.debug.xcconfig"; sourceTree = ""; }; + 4F3402FD392148CB7BCCDE99 /* libPods-bitkit-bitkitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-bitkit-bitkitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 76B572537312405FDE7A4134 /* Pods-bitkit-bitkitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bitkit-bitkitTests.debug.xcconfig"; path = "Target Support Files/Pods-bitkit-bitkitTests/Pods-bitkit-bitkitTests.debug.xcconfig"; sourceTree = ""; }; 777F5BDD29EDEB75005E0E4B /* InterTight-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "InterTight-Bold.ttf"; sourceTree = ""; }; 777F5BDE29EDEB75005E0E4B /* InterTight-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "InterTight-Regular.ttf"; sourceTree = ""; }; 777F5BDF29EDEB75005E0E4B /* InterTight-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "InterTight-SemiBold.ttf"; sourceTree = ""; }; 777F5BE029EDEB75005E0E4B /* InterTight-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "InterTight-Medium.ttf"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = bitkit/LaunchScreen.storyboard; sourceTree = ""; }; - A35C3C8516A56BABDE56B7AF /* libPods-bitkit-bitkitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-bitkit-bitkitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - B42588E3FAF378E3F67D20FE /* Pods-bitkit-bitkitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bitkit-bitkitTests.debug.xcconfig"; path = "Target Support Files/Pods-bitkit-bitkitTests/Pods-bitkit-bitkitTests.debug.xcconfig"; sourceTree = ""; }; - CB1FD1A7ACDB2A8388A52B2C /* Pods-bitkit-bitkitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bitkit-bitkitTests.release.xcconfig"; path = "Target Support Files/Pods-bitkit-bitkitTests/Pods-bitkit-bitkitTests.release.xcconfig"; sourceTree = ""; }; - CCE7C83BFEE37FC9EC817ADF /* Pods-bitkit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bitkit.debug.xcconfig"; path = "Target Support Files/Pods-bitkit/Pods-bitkit.debug.xcconfig"; sourceTree = ""; }; + EA05145DA1EC06DFE061B8A8 /* Pods-bitkit-bitkitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bitkit-bitkitTests.release.xcconfig"; path = "Target Support Files/Pods-bitkit-bitkitTests/Pods-bitkit-bitkitTests.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -59,7 +59,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 794976AAAB7F4FE1195B031D /* libPods-bitkit-bitkitTests.a in Frameworks */, + 564E55664252E8BD1597D528 /* libPods-bitkit-bitkitTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -67,7 +67,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A7FDF85919C9BAEC4F91EE15 /* libPods-bitkit.a in Frameworks */, + 6EE171E60CA562D76D6C164B /* libPods-bitkit.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -108,8 +108,8 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 455CED7D780082F8A237F57F /* libPods-bitkit.a */, - A35C3C8516A56BABDE56B7AF /* libPods-bitkit-bitkitTests.a */, + 1126359186FB8C99BF5A1C4B /* libPods-bitkit.a */, + 4F3402FD392148CB7BCCDE99 /* libPods-bitkit-bitkitTests.a */, ); name = Frameworks; sourceTree = ""; @@ -161,10 +161,10 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - CCE7C83BFEE37FC9EC817ADF /* Pods-bitkit.debug.xcconfig */, - 1F0E33A9BDA691FD5327977D /* Pods-bitkit.release.xcconfig */, - B42588E3FAF378E3F67D20FE /* Pods-bitkit-bitkitTests.debug.xcconfig */, - CB1FD1A7ACDB2A8388A52B2C /* Pods-bitkit-bitkitTests.release.xcconfig */, + 2316ADDE3C53AB36EA2F206A /* Pods-bitkit.debug.xcconfig */, + 01B968BECD96E8A11F341553 /* Pods-bitkit.release.xcconfig */, + 76B572537312405FDE7A4134 /* Pods-bitkit-bitkitTests.debug.xcconfig */, + EA05145DA1EC06DFE061B8A8 /* Pods-bitkit-bitkitTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -176,12 +176,12 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "bitkitTests" */; buildPhases = ( - 33885E64D1F69CB75FB3BBD1 /* [CP] Check Pods Manifest.lock */, + F79F1EE17B85D27D8EB59431 /* [CP] Check Pods Manifest.lock */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - B8D0F583214E2164909FA583 /* [CP] Embed Pods Frameworks */, - 03DEBD92F0CF0C9879BF0559 /* [CP] Copy Pods Resources */, + 1BE825321D028306DF8EB642 /* [CP] Embed Pods Frameworks */, + 080A1F6AEDF2C291821A27C8 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -197,18 +197,18 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "bitkit" */; buildPhases = ( - 9831FD017864DC494A4DDCEC /* [CP] Check Pods Manifest.lock */, + B5C93C9028A870F8510F2130 /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - D1823A00ACF23C5F86CA6872 /* [CP] Embed Pods Frameworks */, - B5DAA68D809936A4E21BD000 /* [CP] Copy Pods Resources */, - AC3512EAFD3E3100455458C0 /* [CP-User] [NODEJS MOBILE] Copy Node.js Project files */, - ABAE3C2DC9B77646B889AED1 /* [CP-User] [NODEJS MOBILE] Build Native Modules */, - 5DD6BA9B0AA57CC0718F105B /* [CP-User] [NODEJS MOBILE] Sign Native Modules */, - 8ED569BA1D250408258AAB9E /* [CP-User] [NODEJS MOBILE] Remove Simulator Strip */, + B424B9A1BD37F5E7BC1D053E /* [CP] Embed Pods Frameworks */, + 1BAEF5FD7E98DD79EEEFF1C6 /* [CP] Copy Pods Resources */, + 4A9D8FB1AF87A74F57B76601 /* [CP-User] [NODEJS MOBILE] Copy Node.js Project files */, + 347DD791752A7B176D279DD7 /* [CP-User] [NODEJS MOBILE] Build Native Modules */, + 44EB2C7BE4B91CB48E117507 /* [CP-User] [NODEJS MOBILE] Sign Native Modules */, + 89AE9C07B06BD68DD8245C47 /* [CP-User] [NODEJS MOBILE] Remove Simulator Strip */, ); buildRules = ( ); @@ -295,7 +295,7 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 03DEBD92F0CF0C9879BF0559 /* [CP] Copy Pods Resources */ = { + 080A1F6AEDF2C291821A27C8 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -312,139 +312,139 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-bitkit-bitkitTests/Pods-bitkit-bitkitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 33885E64D1F69CB75FB3BBD1 /* [CP] Check Pods Manifest.lock */ = { + 1BAEF5FD7E98DD79EEEFF1C6 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-bitkit/Pods-bitkit-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-bitkit-bitkitTests-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-bitkit/Pods-bitkit-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-bitkit/Pods-bitkit-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 5DD6BA9B0AA57CC0718F105B /* [CP-User] [NODEJS MOBILE] Sign Native Modules */ = { + 1BE825321D028306DF8EB642 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - name = "[CP-User] [NODEJS MOBILE] Sign Native Modules"; + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-bitkit-bitkitTests/Pods-bitkit-bitkitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-bitkit-bitkitTests/Pods-bitkit-bitkitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/bin/sh\nset -e\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, look for it in the project's\n#nodejs-assets/BUILD_NATIVE_MODULES.txt file.\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nPREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"\n if [ -f \"$PREFERENCE_FILE_PATH\" ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"\n fi\nfi\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, try to find .gyp files\n#to turn it on.\n gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))\n if [ ${#gypfiles[@]} -gt 0 ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=1\n else\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=0\n fi\nfi\nif [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi\n# Delete object files\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete\n# Create Info.plist for each framework built and loader override.\nPATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"\nNODEJS_PROJECT_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/ && pwd )\"\nnode \"$PATCH_SCRIPT_DIR\"/ios-create-plists-and-dlopen-override.js $NODEJS_PROJECT_DIR\n# Embed every resulting .framework in the application and delete them afterwards.\nembed_framework()\n{\n FRAMEWORK_NAME=\"$(basename \"$1\")\"\n cp -r \"$1\" \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/\"\n /usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY --preserve-metadata=identifier,entitlements,flags --timestamp=none \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/$FRAMEWORK_NAME\"\n}\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d | while read frmwrk_path; do embed_framework \"$frmwrk_path\"; done\n\n#Delete gyp temporary .deps dependency folders from the project structure.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/.deps/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \".deps\" -type d -delete\n\n#Delete frameworks from their build paths\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-bitkit-bitkitTests/Pods-bitkit-bitkitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; - 8ED569BA1D250408258AAB9E /* [CP-User] [NODEJS MOBILE] Remove Simulator Strip */ = { + 347DD791752A7B176D279DD7 /* [CP-User] [NODEJS MOBILE] Build Native Modules */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - name = "[CP-User] [NODEJS MOBILE] Remove Simulator Strip"; + name = "[CP-User] [NODEJS MOBILE] Build Native Modules"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/bin/sh\nset -e\nFRAMEWORK_BINARY_PATH=\"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/NodeMobile.framework/NodeMobile\"\nFRAMEWORK_STRIPPED_PATH=\"$FRAMEWORK_BINARY_PATH-strip\"\nif [ \"$PLATFORM_NAME\" != \"iphonesimulator\" ]; then\n if $(lipo \"$FRAMEWORK_BINARY_PATH\" -verify_arch \"x86_64\") ; then\n lipo -output \"$FRAMEWORK_STRIPPED_PATH\" -remove \"x86_64\" \"$FRAMEWORK_BINARY_PATH\"\n rm \"$FRAMEWORK_BINARY_PATH\"\n mv \"$FRAMEWORK_STRIPPED_PATH\" \"$FRAMEWORK_BINARY_PATH\"\n echo \"Removed simulator strip from NodeMobile.framework\"\n fi\nfi\n"; + shellScript = "#!/bin/sh\nset -e\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, look for it in the project's\n#nodejs-assets/BUILD_NATIVE_MODULES.txt file.\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nPREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"\n if [ -f \"$PREFERENCE_FILE_PATH\" ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"\n fi\nfi\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, try to find .gyp files\n#to turn it on.\n gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))\n if [ ${#gypfiles[@]} -gt 0 ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=1\n else\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=0\n fi\nfi\nif [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi\n# Delete object files that may already come from within the npm package.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type f -delete\n# Delete bundle contents that may be there from previous builds.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.node/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type d -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete\n# Apply patches to the modules package.json\nif [ -d \"$CODESIGNING_FOLDER_PATH\"/nodejs-project/node_modules/ ]; then\n PATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"\n NODEJS_PROJECT_MODULES_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/node_modules/ && pwd )\"\n node \"$PATCH_SCRIPT_DIR\"/patch-package.js $NODEJS_PROJECT_MODULES_DIR\nfi\n# Get the nodejs-mobile-gyp location\nif [ -d \"$PROJECT_DIR/../node_modules/nodejs-mobile-gyp/\" ]; then\n NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-gyp/ && pwd )\"\nelse\n NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/node_modules/nodejs-mobile-gyp/ && pwd )\"\nfi\nNODEJS_MOBILE_GYP_BIN_FILE=\"$NODEJS_MOBILE_GYP_DIR\"/bin/node-gyp.js\n# Support building neon-bindings (Rust) native modules\nif [ -f ~/.cargo/env ]; then\n source ~/.cargo/env;\nfi\n# Rebuild modules with right environment\nNODEJS_HEADERS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/ios/libnode/ && pwd )\"\npushd $CODESIGNING_FOLDER_PATH/nodejs-project/\nif [ \"$PLATFORM_NAME\" == \"iphoneos\" ]\nthen\n GYP_DEFINES=\"OS=ios\" CARGO_BUILD_TARGET=\"aarch64-apple-ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"arm64\" npm --verbose rebuild --build-from-source\nelse\n GYP_DEFINES=\"OS=ios\" CARGO_BUILD_TARGET=\"x86_64-apple-ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"x64\" npm --verbose rebuild --build-from-source\nfi\npopd\n"; }; - 9831FD017864DC494A4DDCEC /* [CP] Check Pods Manifest.lock */ = { + 44EB2C7BE4B91CB48E117507 /* [CP-User] [NODEJS MOBILE] Sign Native Modules */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-bitkit-checkManifestLockResult.txt", - ); + name = "[CP-User] [NODEJS MOBILE] Sign Native Modules"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "#!/bin/sh\nset -e\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, look for it in the project's\n#nodejs-assets/BUILD_NATIVE_MODULES.txt file.\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nPREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"\n if [ -f \"$PREFERENCE_FILE_PATH\" ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"\n fi\nfi\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, try to find .gyp files\n#to turn it on.\n gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))\n if [ ${#gypfiles[@]} -gt 0 ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=1\n else\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=0\n fi\nfi\nif [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi\n# Delete object files\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete\n# Create Info.plist for each framework built and loader override.\nPATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"\nNODEJS_PROJECT_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/ && pwd )\"\nnode \"$PATCH_SCRIPT_DIR\"/ios-create-plists-and-dlopen-override.js $NODEJS_PROJECT_DIR\n# Embed every resulting .framework in the application and delete them afterwards.\nembed_framework()\n{\n FRAMEWORK_NAME=\"$(basename \"$1\")\"\n cp -r \"$1\" \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/\"\n /usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY --preserve-metadata=identifier,entitlements,flags --timestamp=none \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/$FRAMEWORK_NAME\"\n}\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d | while read frmwrk_path; do embed_framework \"$frmwrk_path\"; done\n\n#Delete gyp temporary .deps dependency folders from the project structure.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/.deps/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \".deps\" -type d -delete\n\n#Delete frameworks from their build paths\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete\n"; }; - ABAE3C2DC9B77646B889AED1 /* [CP-User] [NODEJS MOBILE] Build Native Modules */ = { + 4A9D8FB1AF87A74F57B76601 /* [CP-User] [NODEJS MOBILE] Copy Node.js Project files */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - name = "[CP-User] [NODEJS MOBILE] Build Native Modules"; + name = "[CP-User] [NODEJS MOBILE] Copy Node.js Project files"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/bin/sh\nset -e\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, look for it in the project's\n#nodejs-assets/BUILD_NATIVE_MODULES.txt file.\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nPREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"\n if [ -f \"$PREFERENCE_FILE_PATH\" ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"\n fi\nfi\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, try to find .gyp files\n#to turn it on.\n gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))\n if [ ${#gypfiles[@]} -gt 0 ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=1\n else\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=0\n fi\nfi\nif [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi\n# Delete object files that may already come from within the npm package.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type f -delete\n# Delete bundle contents that may be there from previous builds.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.node/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type d -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete\n# Apply patches to the modules package.json\nif [ -d \"$CODESIGNING_FOLDER_PATH\"/nodejs-project/node_modules/ ]; then\n PATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"\n NODEJS_PROJECT_MODULES_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/node_modules/ && pwd )\"\n node \"$PATCH_SCRIPT_DIR\"/patch-package.js $NODEJS_PROJECT_MODULES_DIR\nfi\n# Get the nodejs-mobile-gyp location\nif [ -d \"$PROJECT_DIR/../node_modules/nodejs-mobile-gyp/\" ]; then\n NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-gyp/ && pwd )\"\nelse\n NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/node_modules/nodejs-mobile-gyp/ && pwd )\"\nfi\nNODEJS_MOBILE_GYP_BIN_FILE=\"$NODEJS_MOBILE_GYP_DIR\"/bin/node-gyp.js\n# Support building neon-bindings (Rust) native modules\nif [ -f ~/.cargo/env ]; then\n source ~/.cargo/env;\nfi\n# Rebuild modules with right environment\nNODEJS_HEADERS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/ios/libnode/ && pwd )\"\npushd $CODESIGNING_FOLDER_PATH/nodejs-project/\nif [ \"$PLATFORM_NAME\" == \"iphoneos\" ]\nthen\n GYP_DEFINES=\"OS=ios\" CARGO_BUILD_TARGET=\"aarch64-apple-ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"arm64\" npm --verbose rebuild --build-from-source\nelse\n GYP_DEFINES=\"OS=ios\" CARGO_BUILD_TARGET=\"x86_64-apple-ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"x64\" npm --verbose rebuild --build-from-source\nfi\npopd\n"; + shellScript = "#!/bin/sh\nset -e\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nNODEJS_BUILT_IN_MODULES_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/install/resources/nodejs-modules/ && pwd )\"\nif [ -d \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" ]\nthen\nrm -rf \"$CODESIGNING_FOLDER_PATH/nodejs-project/\"\nfi\nif [ -d \"$CODESIGNING_FOLDER_PATH/builtin_modules/\" ]\nthen\nrm -rf \"$CODESIGNING_FOLDER_PATH/builtin_modules/\"\nfi\nrsync -av --delete \"$NODEJS_ASSETS_DIR/nodejs-project\" \"$CODESIGNING_FOLDER_PATH\"\nrsync -av --delete \"$NODEJS_BUILT_IN_MODULES_DIR/builtin_modules\" \"$CODESIGNING_FOLDER_PATH\"\n"; }; - AC3512EAFD3E3100455458C0 /* [CP-User] [NODEJS MOBILE] Copy Node.js Project files */ = { + 89AE9C07B06BD68DD8245C47 /* [CP-User] [NODEJS MOBILE] Remove Simulator Strip */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - name = "[CP-User] [NODEJS MOBILE] Copy Node.js Project files"; + name = "[CP-User] [NODEJS MOBILE] Remove Simulator Strip"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/bin/sh\nset -e\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nNODEJS_BUILT_IN_MODULES_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/install/resources/nodejs-modules/ && pwd )\"\nif [ -d \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" ]\nthen\nrm -rf \"$CODESIGNING_FOLDER_PATH/nodejs-project/\"\nfi\nif [ -d \"$CODESIGNING_FOLDER_PATH/builtin_modules/\" ]\nthen\nrm -rf \"$CODESIGNING_FOLDER_PATH/builtin_modules/\"\nfi\nrsync -av --delete \"$NODEJS_ASSETS_DIR/nodejs-project\" \"$CODESIGNING_FOLDER_PATH\"\nrsync -av --delete \"$NODEJS_BUILT_IN_MODULES_DIR/builtin_modules\" \"$CODESIGNING_FOLDER_PATH\"\n"; + shellScript = "#!/bin/sh\nset -e\nFRAMEWORK_BINARY_PATH=\"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/NodeMobile.framework/NodeMobile\"\nFRAMEWORK_STRIPPED_PATH=\"$FRAMEWORK_BINARY_PATH-strip\"\nif [ \"$PLATFORM_NAME\" != \"iphonesimulator\" ]; then\n if $(lipo \"$FRAMEWORK_BINARY_PATH\" -verify_arch \"x86_64\") ; then\n lipo -output \"$FRAMEWORK_STRIPPED_PATH\" -remove \"x86_64\" \"$FRAMEWORK_BINARY_PATH\"\n rm \"$FRAMEWORK_BINARY_PATH\"\n mv \"$FRAMEWORK_STRIPPED_PATH\" \"$FRAMEWORK_BINARY_PATH\"\n echo \"Removed simulator strip from NodeMobile.framework\"\n fi\nfi\n"; }; - B5DAA68D809936A4E21BD000 /* [CP] Copy Pods Resources */ = { + B424B9A1BD37F5E7BC1D053E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-bitkit/Pods-bitkit-resources-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-bitkit/Pods-bitkit-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-bitkit/Pods-bitkit-resources-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-bitkit/Pods-bitkit-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-bitkit/Pods-bitkit-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-bitkit/Pods-bitkit-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - B8D0F583214E2164909FA583 /* [CP] Embed Pods Frameworks */ = { + B5C93C9028A870F8510F2130 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-bitkit-bitkitTests/Pods-bitkit-bitkitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-bitkit-bitkitTests/Pods-bitkit-bitkitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-bitkit-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-bitkit-bitkitTests/Pods-bitkit-bitkitTests-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - D1823A00ACF23C5F86CA6872 /* [CP] Embed Pods Frameworks */ = { + F79F1EE17B85D27D8EB59431 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-bitkit/Pods-bitkit-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-bitkit/Pods-bitkit-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-bitkit-bitkitTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-bitkit/Pods-bitkit-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; FD10A7F022414F080027D42C /* Start Packager */ = { @@ -499,7 +499,7 @@ /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B42588E3FAF378E3F67D20FE /* Pods-bitkit-bitkitTests.debug.xcconfig */; + baseConfigurationReference = 76B572537312405FDE7A4134 /* Pods-bitkit-bitkitTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -527,7 +527,7 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = CB1FD1A7ACDB2A8388A52B2C /* Pods-bitkit-bitkitTests.release.xcconfig */; + baseConfigurationReference = EA05145DA1EC06DFE061B8A8 /* Pods-bitkit-bitkitTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -552,7 +552,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = CCE7C83BFEE37FC9EC817ADF /* Pods-bitkit.debug.xcconfig */; + baseConfigurationReference = 2316ADDE3C53AB36EA2F206A /* Pods-bitkit.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -583,7 +583,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1F0E33A9BDA691FD5327977D /* Pods-bitkit.release.xcconfig */; + baseConfigurationReference = 01B968BECD96E8A11F341553 /* Pods-bitkit.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/package.json b/package.json index 7f7973d66..2acff7486 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "rn-electrum-client": "github:synonymdev/react-native-electrum-client#a32a292d4e4918a04d280137e24e5961c74c543f", "rn-qr-generator": "1.3.1", "secp256k1": "4.0.3", - "sodium-react-native-direct": "0.4.0", + "sodium-react-native-direct": "0.4.1", "stream-browserify": "3.0.0", "styled-components": "5.3.11", "url-parse": "1.5.10", diff --git a/yarn.lock b/yarn.lock index 5e423120d..381b7711d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11669,10 +11669,10 @@ sodium-native@^4.0.0: dependencies: node-gyp-build "^4.3.0" -sodium-react-native-direct@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/sodium-react-native-direct/-/sodium-react-native-direct-0.4.0.tgz#7bcfcbf0126bb5ecc92bf5a9907f3e137e3ffd97" - integrity sha512-AjXW03oDKwRHgHoyxRTOT0yWx/vtcxWSAMbaxyGUhOc2zyyZdLuLwLP1WTYTVay/QT8g+jkBfg9zBcEgcrMOzA== +sodium-react-native-direct@0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/sodium-react-native-direct/-/sodium-react-native-direct-0.4.1.tgz#fec484fe42803a53acdd944e59e8164613a15b6a" + integrity sha512-+gzqIdBRDo4EfIn+KZfbBNw6GTsUn1QcYKnyPmYzWW1OJO/OsaNh7KtbyfhNT4bxg0G7cQ6ZmDPONvIKdFcjng== sodium-secretstream@^1.1.0: version "1.1.0" From 20574cd483a81c978b0ce11b910263b275051bac Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Mon, 8 Jan 2024 21:35:41 -0500 Subject: [PATCH 04/50] fix(wallet): Fee Calculation Updates Updates beignet to 0.0.5. Adjusts several methods accordinly. Updates fee calculations on Amount screen for coin control. Updated getCurrentAddressIndex method to work with beignet. --- package.json | 2 +- src/navigation/root/RootNavigator.tsx | 2 +- src/screens/Settings/ElectrumConfig/index.tsx | 11 +++ src/screens/Wallets/Send/Amount.tsx | 73 ++++++++++++++----- src/screens/Wallets/Send/CoinSelection.tsx | 35 ++++----- src/store/actions/wallet.ts | 61 +++++++++++++++- src/utils/lightning/index.ts | 32 +++++--- src/utils/wallet/index.ts | 27 ++----- src/utils/wallet/transactions.ts | 70 +++--------------- yarn.lock | 8 +- 10 files changed, 182 insertions(+), 139 deletions(-) diff --git a/package.json b/package.json index b0b95eef2..5daf1be93 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@synonymdev/web-relay": "1.0.7", "backpack-client": "github:synonymdev/bitkit-backup-client#f08fdb28529d8a3f8bfecc789443c43b966a7581", "bech32": "2.0.0", - "beignet": "^0.0.4", + "beignet": "^0.0.5", "bip21": "2.0.3", "bip32": "4.0.0", "bip39": "3.0.4", diff --git a/src/navigation/root/RootNavigator.tsx b/src/navigation/root/RootNavigator.tsx index def633fdb..d6e82a2ac 100644 --- a/src/navigation/root/RootNavigator.tsx +++ b/src/navigation/root/RootNavigator.tsx @@ -150,7 +150,7 @@ const RootNavigator = (): ReactElement => { const onConfirmClipboardRedirect = async (): Promise => { setShowDialog(false); const clipboardData = await Clipboard.getString(); - resetSendTransaction(); + await resetSendTransaction(); rootNavigation.navigate('Wallet'); await processInputData({ data: clipboardData, showErrors: false }); }; diff --git a/src/screens/Settings/ElectrumConfig/index.tsx b/src/screens/Settings/ElectrumConfig/index.tsx index 9fa50aedb..aa72869b1 100644 --- a/src/screens/Settings/ElectrumConfig/index.tsx +++ b/src/screens/Settings/ElectrumConfig/index.tsx @@ -28,6 +28,9 @@ import { showToast } from '../../../utils/notifications'; import { getConnectedPeer, IPeerData } from '../../../utils/wallet/electrum'; import type { SettingsScreenProps } from '../../../navigation/types'; import { EProtocol } from 'beignet'; +import { refreshWallet, rescanAddresses } from '../../../utils/wallet'; +import { EAvailableNetwork } from '../../../utils/networks'; +import { updateActivityList } from '../../../store/utils/activity'; type RadioButtonItem = { label: string; value: EProtocol }; @@ -158,6 +161,14 @@ const ElectrumConfig = ({ title: t('es.server_updated_title'), description: t('es.server_updated_message', { host, port }), }); + if (selectedNetwork === EAvailableNetwork.bitcoinRegtest) { + await rescanAddresses({ + shouldClearAddresses: false, + shouldClearTransactions: true, + }); + await refreshWallet({}); + updateActivityList(); + } } else { console.log(connectResponse.error.message); dispatch(updateUi({ isConnectedToElectrum: false })); diff --git a/src/screens/Wallets/Send/Amount.tsx b/src/screens/Wallets/Send/Amount.tsx index 3fc6ab250..c2db60264 100644 --- a/src/screens/Wallets/Send/Amount.tsx +++ b/src/screens/Wallets/Send/Amount.tsx @@ -8,7 +8,7 @@ import React, { } from 'react'; import { StyleSheet, View } from 'react-native'; import { useTranslation } from 'react-i18next'; -import { useRoute } from '@react-navigation/native'; +import { useFocusEffect, useRoute } from '@react-navigation/native'; import { TouchableOpacity } from '../../../styles/components'; import { Caption13Up, Text02B } from '../../../styles/text'; @@ -33,16 +33,20 @@ import { selectedWalletSelector, transactionMaxSelector, transactionSelector, + utxosSelector, } from '../../../store/reselect/wallet'; import { primaryUnitSelector, coinSelectAutoSelector, } from '../../../store/reselect/settings'; import { useAppSelector } from '../../../hooks/redux'; -import { useSwitchUnit } from '../../../hooks/wallet'; +import { useBalance, useSwitchUnit } from '../../../hooks/wallet'; import { useCurrency } from '../../../hooks/displayValues'; import { EUnit } from '../../../store/types/wallet'; -import { updateSendTransaction } from '../../../store/actions/wallet'; +import { + setupOnChainTransaction, + updateSendTransaction, +} from '../../../store/actions/wallet'; import { getNumberPadText } from '../../../utils/numberpad'; import { showToast } from '../../../utils/notifications'; import { convertToSats } from '../../../utils/conversion'; @@ -62,22 +66,14 @@ const Amount = ({ navigation }: SendScreenProps<'Amount'>): ReactElement => { const isMaxSendAmount = useAppSelector(transactionMaxSelector); const [text, setText] = useState(''); const [error, setError] = useState(false); + const utxos = useAppSelector(utxosSelector); + const { onchainBalance } = useBalance(); - // Set initial text for NumberPadTextField - useEffect(() => { - const transactionOutputValue = getTransactionOutputValue({ - selectedWallet, - selectedNetwork, + const outputAmount = useMemo(() => { + return getTransactionOutputValue({ + outputs: transaction.outputs, }); - - const result = getNumberPadText(transactionOutputValue, unit); - setText(result); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [transaction.outputs, selectedWallet, selectedNetwork]); - - const amount = useMemo((): number => { - return convertToSats(text, unit); - }, [text, unit]); + }, [transaction.outputs]); const availableAmount = useMemo(() => { const maxAmountResponse = getMaxSendAmount({ @@ -96,6 +92,47 @@ const Amount = ({ navigation }: SendScreenProps<'Amount'>): ReactElement => { selectedNetwork, ]); + useFocusEffect( + useCallback(() => { + // This is triggered when the user removes all inputs from the coin selection screen. + if ( + !transaction.lightningInvoice && + onchainBalance > TRANSACTION_DEFAULTS.dustLimit && + (availableAmount === 0 || + outputAmount === 0 || + !transaction.inputs.length) + ) { + setupOnChainTransaction({ + utxos, + satsPerByte: transaction.satsPerByte, + outputs: [], + }); + const result = getNumberPadText(0, unit); + setText(result); + } + }, [ + availableAmount, + onchainBalance, + outputAmount, + transaction.inputs.length, + transaction.lightningInvoice, + transaction.satsPerByte, + unit, + utxos, + ]), + ); + + // Set initial text for NumberPadTextField + useEffect(() => { + const result = getNumberPadText(outputAmount, unit); + setText(result); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [transaction.outputs, outputAmount, selectedWallet, selectedNetwork]); + + const amount = useMemo((): number => { + return convertToSats(text, unit); + }, [text, unit]); + const availableAmountProps = { ...(error && { color: 'brand' as keyof IColors }), }; @@ -228,7 +265,7 @@ const Amount = ({ navigation }: SendScreenProps<'Amount'>): ReactElement => { onPress={onMaxAmount}> + color={isMaxSendAmount ? 'orange' : 'white'}> {t('send_max')} diff --git a/src/screens/Wallets/Send/CoinSelection.tsx b/src/screens/Wallets/Send/CoinSelection.tsx index 2d7732e2a..8764171ab 100644 --- a/src/screens/Wallets/Send/CoinSelection.tsx +++ b/src/screens/Wallets/Send/CoinSelection.tsx @@ -15,7 +15,6 @@ import useColors from '../../../hooks/colors'; import { useAppSelector } from '../../../hooks/redux'; import useDisplayValues from '../../../hooks/displayValues'; import { - getTotalFee, getTransactionInputValue, getTransactionOutputValue, } from '../../../utils/wallet/transactions'; @@ -23,12 +22,11 @@ import { addTxInput, removeTxInput } from '../../../store/actions/wallet'; import { IUtxo } from '../../../store/types/wallet'; import type { SendScreenProps } from '../../../navigation/types'; import { - selectedNetworkSelector, - selectedWalletSelector, transactionSelector, utxosSelector, } from '../../../store/reselect/wallet'; import { coinSelectPreferenceSelector } from '../../../store/reselect/settings'; +import { TRANSACTION_DEFAULTS } from '../../../utils/wallet/constants'; /** * Some UTXO's may contain the same tx_hash. @@ -82,8 +80,6 @@ const CoinSelection = ({ const { t } = useTranslation('wallet'); const { gray4 } = useColors(); - const selectedWallet = useAppSelector(selectedWalletSelector); - const selectedNetwork = useAppSelector(selectedNetworkSelector); const transaction = useAppSelector(transactionSelector); const utxos = useAppSelector(utxosSelector); const coinSelectPreference = useAppSelector(coinSelectPreferenceSelector); @@ -97,7 +93,8 @@ const CoinSelection = ({ //Combine known utxo's with current transaction inputs in the event we're using utxo's from the address viewer. const combinedUtxos = useMemo(() => { const combined: IUtxo[] = [...inputs, ...utxos]; - return combined.reduce((acc: IUtxo[], current) => { + + const combinedAndUnique = combined.reduce((acc: IUtxo[], current) => { const x = acc.find( (item) => item.index === current.index && @@ -110,6 +107,8 @@ const CoinSelection = ({ return acc; } }, []); + combinedAndUnique.sort((a, b) => b.value - a.value); + return combinedAndUnique; }, [inputs, utxos]); const preference = useMemo( @@ -118,9 +117,8 @@ const CoinSelection = ({ ); const txInputValue = useMemo( - () => getTransactionInputValue({ selectedNetwork, selectedWallet }), - // eslint-disable-next-line react-hooks/exhaustive-deps - [selectedWallet, selectedNetwork, transaction.inputs], + () => getTransactionInputValue({ inputs: transaction.inputs }), + [transaction], ); const txInputDV = useDisplayValues(txInputValue); const inputKeys = useMemo( @@ -129,18 +127,10 @@ const CoinSelection = ({ ); const txOutputValue = useMemo(() => { - const amount = getTransactionOutputValue({ - selectedWallet, - selectedNetwork, - }); - const fee = getTotalFee({ - satsPerByte: transaction.satsPerByte, - message: transaction.message, - selectedWallet, - selectedNetwork, - }); + const amount = getTransactionOutputValue(); + const fee = transaction.fee; return fee + amount; - }, [selectedNetwork, selectedWallet, transaction]); + }, [transaction]); const txOutputDV = useDisplayValues(txOutputValue); const onAutoSelectionPress = (): void => { @@ -159,7 +149,10 @@ const CoinSelection = ({ setAutoSelectionEnabled(true); }; - const isValid = txInputValue >= txOutputValue; + const isValid = + txInputValue > TRANSACTION_DEFAULTS.dustLimit && + txOutputValue > TRANSACTION_DEFAULTS.dustLimit && + txInputValue >= txOutputValue; return ( diff --git a/src/store/actions/wallet.ts b/src/store/actions/wallet.ts index 18d8eddd8..38e222dc5 100644 --- a/src/store/actions/wallet.ts +++ b/src/store/actions/wallet.ts @@ -45,12 +45,14 @@ import { IBoostedTransaction, IExchangeRates, IOnchainFees, + IOutput, ISendTransaction, IWalletData, TSetupTransactionResponse, } from 'beignet'; import { ETransactionSpeed } from '../types/settings'; import { updateOnchainFeeEstimates } from '../utils/fees'; +import { getMaxSendAmount } from '../../utils/wallet/transactions'; export const updateWallet = ( payload: Partial, @@ -381,12 +383,14 @@ export const setupOnChainTransaction = async ({ utxos, rbf = false, satsPerByte, + outputs, }: { //addressType?: EAddressType; // Preferred address type for change address. inputTxHashes?: string[]; // Used to pre-specify inputs to use by tx_hash utxos?: IUtxo[]; // Used to pre-specify utxos to use rbf?: boolean; // Enable or disable rbf. satsPerByte?: number; // Set the sats per byte for the transaction. + outputs?: IOutput[]; // Used to pre-specify outputs to use. } = {}): Promise => { const transaction = getOnChainWalletTransaction(); return await transaction.setupTransaction({ @@ -394,6 +398,7 @@ export const setupOnChainTransaction = async ({ utxos, rbf, satsPerByte, + outputs, }); }; @@ -452,9 +457,35 @@ export const updateSelectedAddressType = async ({ */ export const removeTxInput = ({ input }: { input: IUtxo }): Result => { const wallet = getOnChainWallet(); - return wallet.removeTxInput({ + const removeRes = wallet.removeTxInput({ input, }); + if (removeRes.isErr()) { + return err(removeRes.error.message); + } + const newInputs = removeRes.value; + const transaction = wallet.transaction; + if (transaction.data.max) { + const maxRes = getMaxSendAmount({ + transaction: { + ...transaction.data, + inputs: newInputs, + }, + }); + if (maxRes.isErr()) { + return err(maxRes.error.message); + } + const currentOutput = transaction.data.outputs[0]; + transaction.updateSendTransaction({ + transaction: { + ...transaction.data, + inputs: newInputs, + outputs: [{ ...currentOutput, value: maxRes.value.amount }], + fee: maxRes.value.fee, + }, + }); + } + return removeRes; }; /** @@ -464,9 +495,35 @@ export const removeTxInput = ({ input }: { input: IUtxo }): Result => { */ export const addTxInput = ({ input }: { input: IUtxo }): Result => { const wallet = getOnChainWallet(); - return wallet.addTxInput({ + const addRes = wallet.addTxInput({ input, }); + if (addRes.isErr()) { + return err(addRes.error.message); + } + const newInputs = addRes.value; + const transaction = wallet.transaction; + if (transaction.data.max) { + const maxRes = getMaxSendAmount({ + transaction: { + ...transaction.data, + inputs: newInputs, + }, + }); + if (maxRes.isErr()) { + return err(maxRes.error.message); + } + const currentOutput = transaction.data.outputs[0]; + transaction.updateSendTransaction({ + transaction: { + ...transaction.data, + inputs: newInputs, + outputs: [{ ...currentOutput, value: maxRes.value.amount }], + fee: maxRes.value.fee, + }, + }); + } + return addRes; }; /** diff --git a/src/utils/lightning/index.ts b/src/utils/lightning/index.ts index 12bebae2c..f29cc1b74 100644 --- a/src/utils/lightning/index.ts +++ b/src/utils/lightning/index.ts @@ -32,9 +32,9 @@ import { transactionExists, } from '../wallet/electrum'; import { + getCurrentAddressIndex, getMnemonicPhrase, getOnChainWalletElectrum, - getReceiveAddress, getSelectedNetwork, getSelectedWallet, } from '../wallet'; @@ -219,12 +219,25 @@ export const setupLdk = async ({ network = ENetworks.regtest; break; } - const getAddress = async (): Promise => { - const res = await getReceiveAddress({ selectedNetwork }); - if (res.isOk()) { - return res.value; + const getAddress = async (): Promise<{ + address: string; + publicKey: string; + }> => { + const error = { address: '', publicKey: '' }; + try { + const addressIndex = await getCurrentAddressIndex({}); + if (addressIndex.isErr()) { + return error; + } + return { + address: addressIndex.value.address, + publicKey: addressIndex.value.publicKey, + }; + } catch { + console.error('Error getting address for LDK'); + //TODO react-native-ldk should be updated to handle errors where an address cannot be generated + return error; } - return ''; }; const storageRes = await setLdkStoragePath(); @@ -256,7 +269,7 @@ export const setupLdk = async ({ rawTx, subscribeToOutputAddress: false, }), - getTransactionData: (txId) => getTransactionData(txId, selectedNetwork), + getTransactionData: (txId) => getTransactionData(txId), getScriptPubKeyHistory: electrum.getScriptPubKeyHistory, getTransactionPosition: (params) => { return getTransactionPosition(params); @@ -988,19 +1001,14 @@ export const getBestBlock = async ( /** * Returns the transaction header, height and hex (transaction) for a given txid. * @param {string} txId - * @param {EAvailableNetwork} [selectedNetwork] * @returns {Promise} */ export const getTransactionData = async ( txId: string = '', - selectedNetwork?: EAvailableNetwork, ): Promise => { let transactionData = DefaultTransactionDataShape; try { const data = [{ tx_hash: txId }]; - if (selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } const electrum = getOnChainWalletElectrum(); const response = await electrum.getTransactions({ txHashes: data, diff --git a/src/utils/wallet/index.ts b/src/utils/wallet/index.ts index 506d6c87b..0a8a1607c 100644 --- a/src/utils/wallet/index.ts +++ b/src/utils/wallet/index.ts @@ -52,6 +52,7 @@ import { } from '../../store/helpers'; import { createDefaultWalletStructure, + generateNewReceiveAddress, getNetworkFromBeignet, getWalletData, setWalletData, @@ -1915,36 +1916,22 @@ export const getReceiveAddress = async ({ /** * Returns the current addressIndex value and will create one if none existed. * @param {EAddressType} [addressType] - * @param {EAvailableNetwork} [selectedNetwork] - * @param {TWalletName} [selectedWallet] * @return {Result} */ export const getCurrentAddressIndex = async ({ addressType, - selectedNetwork, - selectedWallet, }: { addressType?: EAddressType; - selectedNetwork?: EAvailableNetwork; - selectedWallet?: TWalletName; }): Promise> => { try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!addressType) { - addressType = getSelectedAddressType({ selectedNetwork, selectedWallet }); - } - const wallet = getWalletStore().wallets[selectedWallet]; - const addressIndex = wallet.addressIndex[selectedNetwork]; - const receiveAddress = addressIndex[addressType]; + addressType = addressType ?? wallet.data.addressType; + const currentWallet = wallet.data; + const addressIndex = currentWallet.addressIndex[addressType]; + const receiveAddress = currentWallet.addressIndex[addressType]; if (receiveAddress) { return ok(receiveAddress); } - const addresses = wallet?.addresses[selectedNetwork][addressType]; + const addresses = currentWallet?.addresses[addressType]; // Check if addresses were generated, but the index has not been set yet. if ( @@ -1959,8 +1946,6 @@ export const getCurrentAddressIndex = async ({ } // Fallback to generating a new receive address on the fly. const generatedAddress = await generateNewReceiveAddress({ - selectedWallet, - selectedNetwork, addressType, }); if (generatedAddress.isOk()) { diff --git a/src/utils/wallet/transactions.ts b/src/utils/wallet/transactions.ts index ecb6afad3..aa71d39c1 100644 --- a/src/utils/wallet/transactions.ts +++ b/src/utils/wallet/transactions.ts @@ -123,25 +123,15 @@ export const getTotalFee = ({ message = '', transaction, // If left undefined, the method will retrieve the tx data from state. fundingLightning = false, - selectedWallet, - selectedNetwork, }: { satsPerByte: number; message?: string; transaction?: Partial; fundingLightning?: boolean; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; }): number => { const baseTransactionSize = TRANSACTION_DEFAULTS.recommendedBaseFee; try { if (!transaction) { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } const txDataResponse = getOnchainTransactionData(); if (txDataResponse.isErr()) { // If error, return minimum fallback fee. @@ -249,15 +239,11 @@ const createPsbtFromTransactionData = async ({ //Get balance of current inputs. const balance = getTransactionInputValue({ - selectedWallet, - selectedNetwork, inputs, }); //Get value of current outputs. const outputValue = getTransactionOutputValue({ - selectedNetwork, - selectedWallet, outputs, }); @@ -470,13 +456,9 @@ export const createTransaction = async ({ removeDustOutputs(transactionData.outputs); const inputValue = getTransactionInputValue({ - selectedNetwork, - selectedWallet, inputs: transactionData.inputs, }); const outputValue = getTransactionOutputValue({ - selectedWallet, - selectedNetwork, outputs: transactionData.outputs, }); if (inputValue === 0) { @@ -693,28 +675,16 @@ export const broadcastTransaction = async ({ /** * Returns total value of all outputs. Excludes any value that would be sent to the change address. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] * @param {IOutput[]} [outputs] * @returns {number} */ export const getTransactionOutputValue = ({ - selectedWallet, - selectedNetwork, outputs, }: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; outputs?: IOutput[]; } = {}): number => { try { if (!outputs) { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } const transaction = getOnchainTransactionData(); if (transaction.isErr()) { return 0; @@ -734,27 +704,15 @@ export const getTransactionOutputValue = ({ /** * Returns total value of all utxos. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] * @param {IUtxo[]} [inputs] */ export const getTransactionInputValue = ({ - selectedWallet, - selectedNetwork, inputs, }: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; inputs?: IUtxo[]; }): number => { try { if (!inputs) { - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } const transaction = getOnchainTransactionData(); if (transaction.isErr()) { return 0; @@ -1052,9 +1010,9 @@ export const getMaxSendAmount = ({ selectedWallet, }: { transaction?: ISendTransaction; - selectedNetwork: EAvailableNetwork; - selectedWallet: TWalletName; -}): Result<{ amount: number; fee?: number }> => { + selectedNetwork?: EAvailableNetwork; + selectedWallet?: TWalletName; +}): Result<{ amount: number; fee: number }> => { try { if (!transaction) { const transactionDataResponse = getOnchainTransactionData(); @@ -1065,6 +1023,12 @@ export const getMaxSendAmount = ({ } if (transaction.lightningInvoice) { + if (!selectedWallet) { + selectedWallet = getSelectedWallet(); + } + if (!selectedNetwork) { + selectedNetwork = getSelectedNetwork(); + } // lightning transaction const { spendingBalance } = getBalance({ selectedWallet, @@ -1074,6 +1038,7 @@ export const getMaxSendAmount = ({ const fee = 1; const maxAmount = { amount: spendingBalance - fee, + fee, }; return ok(maxAmount); } else { @@ -1241,19 +1206,10 @@ export const updateSendAmount = ({ } else { // onchain transaction const inputTotal = getTransactionInputValue({ - selectedNetwork, - selectedWallet, inputs: transaction.inputs, }); - const fee = getTotalFee({ - satsPerByte: transaction.satsPerByte, - message: transaction.message, - selectedWallet, - selectedNetwork, - }); - - const totalAmount = amount + fee; + const totalAmount = amount + transaction.fee; if (totalAmount === inputTotal) { max = true; @@ -1329,13 +1285,9 @@ export const updateMessage = async ({ const newFee = getTotalFee({ satsPerByte, message }); const inputTotal = getTransactionInputValue({ - selectedWallet, - selectedNetwork, inputs, }); const outputTotal = getTransactionOutputValue({ - selectedWallet, - selectedNetwork, outputs, }); const totalNewAmount = outputTotal + newFee; diff --git a/yarn.lock b/yarn.lock index 7574e2a29..dc08364a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4386,10 +4386,10 @@ bech32@^1.1.2, bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -beignet@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.4.tgz#1c82c793275bd214224e3b8b457a5f8355991cb9" - integrity sha512-LtQRnXefK+LfUm0ATbWOXOiNCNMvre7ZtdxY428YR1c6A5UopH/0Q1X036tLLUD8JcwnCJiqu0/Tsjh8Eiq9RQ== +beignet@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.5.tgz#3c3e57cfbbbe4d95a875492c515e80581e190557" + integrity sha512-ej4RPHCF/Trz92qRvxSKjQBShYO2N+yrP/jri93Wxhzt6ooIu0OFI6YrVGAGckLZbXZp6RGuO5KcpxUFjImUZA== dependencies: "@bitcoinerlab/secp256k1" "^1.0.5" bech32 "^2.0.0" From aa3189b8acded16554e31da473c2314c054f1958 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Mon, 8 Jan 2024 21:48:14 -0500 Subject: [PATCH 05/50] fix(wallet): Fix Errors Removed unneeded params. --- src/screens/Wallets/Send/FeeRate.tsx | 7 ++----- src/screens/Wallets/Send/ReviewAndSend.tsx | 12 ++---------- src/store/utils/blocktank.ts | 2 -- src/utils/scanner.ts | 2 -- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/screens/Wallets/Send/FeeRate.tsx b/src/screens/Wallets/Send/FeeRate.tsx index 9d5c2d88b..847b9b597 100644 --- a/src/screens/Wallets/Send/FeeRate.tsx +++ b/src/screens/Wallets/Send/FeeRate.tsx @@ -38,8 +38,7 @@ const FeeRate = ({ navigation }: SendScreenProps<'FeeRate'>): ReactElement => { const transactionTotal = useCallback(() => { return getTransactionOutputValue({ - selectedWallet, - selectedNetwork, + outputs: transaction.outputs, }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [transaction.outputs, selectedNetwork, selectedWallet]); @@ -49,11 +48,9 @@ const FeeRate = ({ navigation }: SendScreenProps<'FeeRate'>): ReactElement => { return getTotalFee({ satsPerByte: _satsPerByte, message: transaction.message, - selectedWallet, - selectedNetwork, }); }, - [transaction.message, selectedNetwork, selectedWallet], + [transaction.message], ); const _updateFee = useCallback( diff --git a/src/screens/Wallets/Send/ReviewAndSend.tsx b/src/screens/Wallets/Send/ReviewAndSend.tsx index 56a5ba16f..8b5a4b128 100644 --- a/src/screens/Wallets/Send/ReviewAndSend.tsx +++ b/src/screens/Wallets/Send/ReviewAndSend.tsx @@ -163,8 +163,7 @@ const ReviewAndSend = ({ */ const amount = useMemo((): number => { return getTransactionOutputValue({ - selectedWallet, - selectedNetwork, + outputs: transaction.outputs, }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [transaction.outputs, selectedNetwork, selectedWallet]); @@ -341,15 +340,8 @@ const ReviewAndSend = ({ return getTotalFee({ satsPerByte: transaction.satsPerByte, message: transaction.message, - selectedWallet, - selectedNetwork, }); - }, [ - transaction.satsPerByte, - transaction.message, - selectedWallet, - selectedNetwork, - ]); + }, [transaction.satsPerByte, transaction.message]); const fiatTransactionFee = useDisplayValues(feeSats); diff --git a/src/store/utils/blocktank.ts b/src/store/utils/blocktank.ts index 9bcc39e75..beb866278 100644 --- a/src/store/utils/blocktank.ts +++ b/src/store/utils/blocktank.ts @@ -279,8 +279,6 @@ export const startChannelPurchase = async ({ const satPerVByteFee = Math.ceil(min0ConfTxFee.value.satPerVByte); // might be float let txFeeInSats = getTotalFee({ satsPerByte: satPerVByteFee, - selectedWallet, - selectedNetwork, }); const buyChannelDataFeeSat = Math.ceil(buyChannelData.feeSat); const buyChannelDataClientBalanceFeeSat = Math.ceil( diff --git a/src/utils/scanner.ts b/src/utils/scanner.ts index a3bd48abf..e1a35cb40 100644 --- a/src/utils/scanner.ts +++ b/src/utils/scanner.ts @@ -606,8 +606,6 @@ export const processBitcoinTransactionData = async ({ return err(transaction.error.message); } const inputValue = getTransactionInputValue({ - selectedWallet, - selectedNetwork, inputs: transaction.value.inputs, }); // In the event we have preset inputs from address viewer. From de7b3602e4243de6215b6a15cc4b4bbd4c312c17 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Tue, 9 Jan 2024 06:46:18 -0500 Subject: [PATCH 06/50] chore(wallet): Bump beignet version Bumps beignet version to 0.0.6. --- package.json | 2 +- yarn.lock | 50 +++++++++++++++++++++++++------------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 5daf1be93..46ba6ef34 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@synonymdev/web-relay": "1.0.7", "backpack-client": "github:synonymdev/bitkit-backup-client#f08fdb28529d8a3f8bfecc789443c43b966a7581", "bech32": "2.0.0", - "beignet": "^0.0.5", + "beignet": "0.0.6", "bip21": "2.0.3", "bip32": "4.0.0", "bip39": "3.0.4", diff --git a/yarn.lock b/yarn.lock index dc08364a6..dd655a362 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1674,7 +1674,7 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@bitcoinerlab/secp256k1@1.0.5", "@bitcoinerlab/secp256k1@^1.0.5": +"@bitcoinerlab/secp256k1@1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@bitcoinerlab/secp256k1/-/secp256k1-1.0.5.tgz#4643ba73619c24c7c455cc63c6338c69c2cf187c" integrity sha512-8gT+ukTCFN2rTxn4hD9Jq3k+UJwcprgYjfK/SQUSLgznXoIgsBnlPuARMkyyuEjycQK9VvnPiejKdszVTflh+w== @@ -4386,23 +4386,23 @@ bech32@^1.1.2, bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -beignet@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.5.tgz#3c3e57cfbbbe4d95a875492c515e80581e190557" - integrity sha512-ej4RPHCF/Trz92qRvxSKjQBShYO2N+yrP/jri93Wxhzt6ooIu0OFI6YrVGAGckLZbXZp6RGuO5KcpxUFjImUZA== +beignet@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.6.tgz#a082c80fee6b7261b5f6f673489ffa9117be13ba" + integrity sha512-dpI8bwH4B2cBHUTjsMhO+kIJJSnSJI2Mo3h3bZ98k7dUH1tb0J/DLyx0GBpFORSg9Q4skp0HY3I39kQEZJCf2g== dependencies: - "@bitcoinerlab/secp256k1" "^1.0.5" - bech32 "^2.0.0" - bip21 "^2.0.3" - bip32 "^4.0.0" - bip39 "^3.1.0" - bitcoin-address-validation "^2.2.3" - bitcoin-units "^0.3.0" + "@bitcoinerlab/secp256k1" "1.0.5" + bech32 "2.0.0" + bip21 "2.0.3" + bip32 "4.0.0" + bip39 "3.1.0" + bitcoin-address-validation "2.2.3" + bitcoin-units "0.3.0" bitcoinjs-lib "6.1.4" - ecpair "^2.1.0" - lodash.clonedeep "^4.5.0" - net "^1.0.2" - rn-electrum-client "^0.0.10" + ecpair "2.1.0" + lodash.clonedeep "4.5.0" + net "1.0.2" + rn-electrum-client "0.0.10" big-integer@1.6.x: version "1.6.51" @@ -4453,14 +4453,14 @@ bip174@^2.1.1: resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.1.tgz#ef3e968cf76de234a546962bcf572cc150982f9f" integrity sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ== -bip21@2.0.3, bip21@^2.0.3: +bip21@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/bip21/-/bip21-2.0.3.tgz#6e95b542718af497b442d92ae8af433d6ea80368" integrity sha512-L4ODmASLjsHYAU+TG7xffkYNMvHzAe4mkVX7mcvOUyKAr/MDBPrsRgqUhE8EmKdeEKHk5SYpX1Aexzvm/6WdbQ== dependencies: qs "^6.3.0" -bip32@4.0.0, bip32@^4.0.0: +bip32@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/bip32/-/bip32-4.0.0.tgz#7fac3c05072188d2d355a4d6596b37188f06aa2f" integrity sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ== @@ -4493,14 +4493,14 @@ bip39@3.0.4: pbkdf2 "^3.0.9" randombytes "^2.0.1" -bip39@^3.0.4, bip39@^3.1.0: +bip39@3.1.0, bip39@^3.0.4: version "3.1.0" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A== dependencies: "@noble/hashes" "^1.2.0" -bitcoin-address-validation@2.2.3, bitcoin-address-validation@^2.2.3: +bitcoin-address-validation@2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/bitcoin-address-validation/-/bitcoin-address-validation-2.2.3.tgz#ffae6d48facd5ce7ef60574891aab979d21f9828" integrity sha512-1uGCGl26Ye8JG5qcExtFLQfuib6qEZWNDo1ZlLlwp/z7ygUFby3IxolgEfgMGaC+LG9csbVASLcH8fRLv7DIOg== @@ -4521,7 +4521,7 @@ bitcoin-json-rpc@^1.3.3: io-ts ">=2.2.0" lodash ">=4.17.21" -bitcoin-units@0.3.0, bitcoin-units@^0.3.0: +bitcoin-units@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/bitcoin-units/-/bitcoin-units-0.3.0.tgz#014b22db005c31c02865166b5e5a46932865a9c0" integrity sha512-CxmCxfoYDKQhKXMvT1HnShVL1E6rT5IUkVenhrFDphrRGKQHJUTGtK68UY7JnAQKqJA4510cC5MO6aEd/8v1EQ== @@ -6015,7 +6015,7 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecpair@^2.1.0: +ecpair@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ecpair/-/ecpair-2.1.0.tgz#673f826b1d80d5eb091b8e2010c6b588e8d2cb45" integrity sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw== @@ -8723,7 +8723,7 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== -lodash.clonedeep@^4.5.0: +lodash.clonedeep@4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== @@ -9493,7 +9493,7 @@ neo-async@^2.5.0, neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -net@^1.0.2: +net@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/net/-/net-1.0.2.tgz#d1757ec9a7fb2371d83cf4755ce3e27e10829388" integrity sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ== @@ -11217,7 +11217,7 @@ rn-android-keyboard-adjust@2.1.2: resolved "https://registry.yarnpkg.com/rn-android-keyboard-adjust/-/rn-android-keyboard-adjust-2.1.2.tgz#f2b792628700dcb026215420192317ce43fd954f" integrity sha512-pUYiMT7aucw5YnV4geGdalyl6o6rEwRYhDnw2oPQwDym1BZHtmKsxFLupky1Kj2ZNkNKadpEyoyHOElLo+f3sA== -rn-electrum-client@^0.0.10: +rn-electrum-client@0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/rn-electrum-client/-/rn-electrum-client-0.0.10.tgz#a889e30967484b1312c2afd260501e9b19cd845d" integrity sha512-sYP9mdPGEnDjQZtMrv1JWc4pWEFTUBcGqe/K64whnmmz67aYppUAyblPCzyndsNr3Eu4KdjUK3tgmMh+Ha4v9g== From c68ef2aac492d9d2e3ad6f47940adf2d138af2e1 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Tue, 9 Jan 2024 14:56:10 -0500 Subject: [PATCH 07/50] fix(wallet): Fix Restore Tests Fixes wallet-restore tests. --- __tests__/wallet-restore.ts | 150 ++++++++++++++++--------------- package.json | 2 +- src/store/actions/wallet.ts | 2 +- src/utils/startup/index.ts | 15 +++- src/utils/wallet/transactions.ts | 16 +--- yarn.lock | 8 +- 6 files changed, 95 insertions(+), 98 deletions(-) diff --git a/__tests__/wallet-restore.ts b/__tests__/wallet-restore.ts index f50e4cb98..8c0ba0427 100644 --- a/__tests__/wallet-restore.ts +++ b/__tests__/wallet-restore.ts @@ -29,7 +29,7 @@ describe('Wallet - wallet restore and receive', () => { it("can restore wallet and it's balance", async () => { // send some bitcoin to wallet address const incomigTxid = await rpc.sendToAddress( - 'bcrt1qcr8te4kr609gcawutmrza0j4xv80jy8zeqchgx', // scriptHash: 6e4f16236139f15046b38f399a683fb2aa8edf5fd128b3e5db017fb0ac74078a + 'bcrt1q6rz28mcfaxtmd6v789l9rrlrusdprr9pz3cppk', // scriptHash: 71d53db103b8dedac12267edc183a38240654842bc98fd9776515a86a84f9590 '1', ); await rpc.generateToAddress(1, await rpc.getNewAddress()); @@ -55,12 +55,14 @@ describe('Wallet - wallet restore and receive', () => { res = await wallet.electrum.connectToElectrum({ network: EAvailableNetworks.bitcoinRegtest, - servers: { - host: '127.0.0.1', - ssl: 60002, - tcp: 60001, - protocol: EProtocol.tcp, - }, + servers: [ + { + host: '127.0.0.1', + ssl: 60002, + tcp: 60001, + protocol: EProtocol.tcp, + }, + ], }); if (res.isErr()) { throw res.error; @@ -83,77 +85,77 @@ describe('Wallet - wallet restore and receive', () => { const addresses = state.wallet.wallets.wallet0.addresses.bitcoinRegtest; expect(addresses.p2sh).toHaveProperty( - 'e5c5dbe8c82341872337d56fa52b120e3bac428855d9a450cacf63d15da5c65e', + 'fe50ad3261e3c01e61a107f40ae7d762b1e276721c803cc4aee55187effe6d7f', { - address: '2My47gHNc8nhX5kBWqXHU4f8uuQvQKEgwMd', - path: "m/49'/0'/0'/0/0", - publicKey: - '039b3b694b8fc5b5e07fb069c783cac754f5d38c3e08bed1960e31fdb1dda35c24', + address: '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2', index: 0, + path: "m/49'/1'/0'/0/0", + publicKey: + '03a1af804ac108a8a51782198c2d034b28bf90c8803f5a53f76276fa69a4eae77f', scriptHash: - 'e5c5dbe8c82341872337d56fa52b120e3bac428855d9a450cacf63d15da5c65e', + 'fe50ad3261e3c01e61a107f40ae7d762b1e276721c803cc4aee55187effe6d7f', }, ); expect(addresses.p2sh).toHaveProperty( - 'fb2e7b1ff6c6a9ba347c23df6ecc0cfae3daa15184125bed0b69ed82a4d736ef', + '37748c721f575a9bfe5ab2b545ef59af417bac2e73a625420fe79bec134ce06e', { - address: '2MyKoi3kva7uYJDzv1RmSkbeTkQQ6wBR1ZQ', - path: "m/49'/0'/0'/0/4", - publicKey: - '0315e44fc567dbec14d55491b383334b6ddbcaf9de0aa339481a83feff2a509803', + address: '2MuKeQzUHhUQWUZgx5AuNWoQ7YWx6vsXxrv', index: 4, + path: "m/49'/1'/0'/0/4", + publicKey: + '03765505df9cc00d2cd578c961a494214402283b9f6e8f28684e8798862057a02b', scriptHash: - 'fb2e7b1ff6c6a9ba347c23df6ecc0cfae3daa15184125bed0b69ed82a4d736ef', + '37748c721f575a9bfe5ab2b545ef59af417bac2e73a625420fe79bec134ce06e', }, ); expect(addresses.p2pkh).toHaveProperty( - '1e8750b8a4c0912d8b84f7eb53472cbdcb57f9e0cde263b2e51ecbe30853cd68', + '62e79b96e30507526450ca144ceeca7b90954acbd641f3de50ba1191c292af9a', { - address: 'n1M8ZVQtL7QoFvGMg24D6b2ojWvFXCGpoS', - path: "m/44'/0'/0'/0/0", - publicKey: - '03aaeb52dd7494c361049de67cc680e83ebcbbbdbeb13637d92cd845f70308af5e', + address: 'mkpZhYtJu2r87Js3pDiWJDmPte2NRZ8bJV', index: 0, + path: "m/44'/1'/0'/0/0", + publicKey: + '02a7451395735369f2ecdfc829c0f774e88ef1303dfe5b2f04dbaab30a535dfdd6', scriptHash: - '1e8750b8a4c0912d8b84f7eb53472cbdcb57f9e0cde263b2e51ecbe30853cd68', + '62e79b96e30507526450ca144ceeca7b90954acbd641f3de50ba1191c292af9a', }, ); expect(addresses.p2pkh).toHaveProperty( - '895ecf285d41541713a31a26dc1e38406decb98bdf478ac500e4c061dc579ee1', + 'a75838b21cbcab06b8c2c469a165778a50d126dc07ed23bcc562e375a07b11ba', { - address: 'mwGXMMivWNPgie3opNJk6ymE6oHMYrdZeY', - path: "m/44'/0'/0'/0/4", - publicKey: - '029efbcb2db9ee44cb12739e9350e19e5f1ce4563351b770096f0e408f93400c70', + address: 'n2BMo5arHDyAK2CM8c56eoEd18uEkKnRLC', index: 4, + path: "m/44'/1'/0'/0/4", + publicKey: + '02b9988be7219be78b82e659155d02d3e1462f3febe7c87d33964b37831efd8884', scriptHash: - '895ecf285d41541713a31a26dc1e38406decb98bdf478ac500e4c061dc579ee1', + 'a75838b21cbcab06b8c2c469a165778a50d126dc07ed23bcc562e375a07b11ba', }, ); expect(addresses.p2wpkh).toHaveProperty( - '6e4f16236139f15046b38f399a683fb2aa8edf5fd128b3e5db017fb0ac74078a', + '71d53db103b8dedac12267edc183a38240654842bc98fd9776515a86a84f9590', { - address: 'bcrt1qcr8te4kr609gcawutmrza0j4xv80jy8zeqchgx', - path: "m/84'/0'/0'/0/0", - publicKey: - '0330d54fd0dd420a6e5f8d3624f5f3482cae350f79d5f0753bf5beef9c2d91af3c', + address: 'bcrt1q6rz28mcfaxtmd6v789l9rrlrusdprr9pz3cppk', index: 0, + path: "m/84'/1'/0'/0/0", + publicKey: + '02e7ab2537b5d49e970309aae06e9e49f36ce1c9febbd44ec8e0d1cca0b4f9c319', scriptHash: - '6e4f16236139f15046b38f399a683fb2aa8edf5fd128b3e5db017fb0ac74078a', + '71d53db103b8dedac12267edc183a38240654842bc98fd9776515a86a84f9590', }, ); expect(addresses.p2wpkh).toHaveProperty( - '5828594685d9bb1f63bc0ea44f24d9ca309b419ba49efc756677d363892d33c3', + '796e84f36cdb86f770d555b0decf619ce87e5e0f3b783b6a3acff33801a18457', { - address: 'bcrt1qm97vqzgj934vnaq9s53ynkyf9dgr05rat8p3ef', - path: "m/84'/0'/0'/0/4", - publicKey: - '03995137c8eb3b223c904259e9b571a8939a0ec99b0717684c3936407ca8538c1b', + address: 'bcrt1q677973lw0w796gttpy52f296jqaaksz0kadvlr', index: 4, + path: "m/84'/1'/0'/0/4", + publicKey: + '03bb5db212192d5b428c5db726aba21426d0a63b7a453b0104f2398326bca43fc2', scriptHash: - '5828594685d9bb1f63bc0ea44f24d9ca309b419ba49efc756677d363892d33c3', + '796e84f36cdb86f770d555b0decf619ce87e5e0f3b783b6a3acff33801a18457', }, ); @@ -162,33 +164,33 @@ describe('Wallet - wallet restore and receive', () => { state.wallet.wallets.wallet0.addressIndex.bitcoinRegtest; expect(addressIndex.p2sh).toStrictEqual({ - address: '2My47gHNc8nhX5kBWqXHU4f8uuQvQKEgwMd', - path: "m/49'/0'/0'/0/0", + address: '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2', + path: "m/49'/1'/0'/0/0", publicKey: - '039b3b694b8fc5b5e07fb069c783cac754f5d38c3e08bed1960e31fdb1dda35c24', + '03a1af804ac108a8a51782198c2d034b28bf90c8803f5a53f76276fa69a4eae77f', index: 0, scriptHash: - 'e5c5dbe8c82341872337d56fa52b120e3bac428855d9a450cacf63d15da5c65e', + 'fe50ad3261e3c01e61a107f40ae7d762b1e276721c803cc4aee55187effe6d7f', }); expect(addressIndex.p2pkh).toStrictEqual({ - address: 'n1M8ZVQtL7QoFvGMg24D6b2ojWvFXCGpoS', - path: "m/44'/0'/0'/0/0", + address: 'mkpZhYtJu2r87Js3pDiWJDmPte2NRZ8bJV', + path: "m/44'/1'/0'/0/0", publicKey: - '03aaeb52dd7494c361049de67cc680e83ebcbbbdbeb13637d92cd845f70308af5e', + '02a7451395735369f2ecdfc829c0f774e88ef1303dfe5b2f04dbaab30a535dfdd6', index: 0, scriptHash: - '1e8750b8a4c0912d8b84f7eb53472cbdcb57f9e0cde263b2e51ecbe30853cd68', + '62e79b96e30507526450ca144ceeca7b90954acbd641f3de50ba1191c292af9a', }); expect(addressIndex.p2wpkh).toStrictEqual({ - address: 'bcrt1qnjg0jd8228aq7egyzacy8cys3knf9xvr3v5hfj', - path: "m/84'/0'/0'/0/1", + address: 'bcrt1qd7spv5q28348xl4myc8zmh983w5jx32cs707jh', + path: "m/84'/1'/0'/0/1", publicKey: - '03e775fd51f0dfb8cd865d9ff1cca2a158cf651fe997fdc9fee9c1d3b5e995ea77', + '03eeed205a69022fed4a62a02457f3699b19c06bf74bf801acc6d9ae84bc16a9e1', index: 1, scriptHash: - 'acb101e9312975c11bd7adc75aa91fed37147214218b7dc0343e54b2e863a482', + 'fb51113b012a2eb8b8157ed8336ef31aa669993372bd5a0e712004989eee044b', }); // check addressIndex @@ -199,7 +201,7 @@ describe('Wallet - wallet restore and receive', () => { expect(lastUsedAddressIndex.p2pkh.index).toEqual(-1); expect(lastUsedAddressIndex.p2wpkh.index).toEqual(0); expect(lastUsedAddressIndex.p2wpkh.address).toEqual( - 'bcrt1qcr8te4kr609gcawutmrza0j4xv80jy8zeqchgx', + 'bcrt1q6rz28mcfaxtmd6v789l9rrlrusdprr9pz3cppk', ); // check changeAddresses @@ -207,27 +209,27 @@ describe('Wallet - wallet restore and receive', () => { state.wallet.wallets.wallet0.changeAddresses.bitcoinRegtest; expect(changeAddresses.p2wpkh).toHaveProperty( - '48d4bc4257d5177c6a44dfa0e3fd17916fc15b39b8a1cbb0aa297b059f826425', + 'bb913937bc604ebdb5538198d85c5ea211a88926d36926ecf0800ecc507d0fcc', { - address: 'bcrt1q8c6fshw2dlwun7ekn9qwf37cu2rn755ufhry49', - path: "m/84'/0'/0'/1/0", - publicKey: - '03025324888e429ab8e3dbaf1f7802648b9cd01e9b418485c5fa4c1b9b5700e1a6', + address: 'bcrt1q9u62588spffmq4dzjxsr5l297znf3z6jkgnhsw', index: 0, + path: "m/84'/1'/0'/1/0", + publicKey: + '035d49eccd54d0099e43676277c7a6d4625d611da88a5df49bf9517a7791a777a5', scriptHash: - '48d4bc4257d5177c6a44dfa0e3fd17916fc15b39b8a1cbb0aa297b059f826425', + 'bb913937bc604ebdb5538198d85c5ea211a88926d36926ecf0800ecc507d0fcc', }, ); expect(changeAddresses.p2wpkh).toHaveProperty( - '0c325f0eb65ae59e75125ee3a823f408e469002e840c670cf5eba33270497028', + 'a012aa15544d67973b9fef1bf3852b246cf44dc18dadd23c3325c9e4a7ced949', { - address: 'bcrt1qetrkzfslk0d4kqjnu29fdh04tkav9vj377cjsd', - path: "m/84'/0'/0'/1/4", - publicKey: - '02a8dee7573bcc7d3c1e9b9e267dbf0cd717343c31d322c5b074a3a97090a0d952', + address: 'bcrt1qw3xfnyuspj8qnr2envc448mxwam7f7p249rqs0', index: 4, + path: "m/84'/1'/0'/1/4", + publicKey: + '02e6c60079372951c3024a033ecf6584579ebf2f7927ae99c42633e805596f2935', scriptHash: - '0c325f0eb65ae59e75125ee3a823f408e469002e840c670cf5eba33270497028', + 'a012aa15544d67973b9fef1bf3852b246cf44dc18dadd23c3325c9e4a7ced949', }, ); @@ -236,13 +238,13 @@ describe('Wallet - wallet restore and receive', () => { state.wallet.wallets.wallet0.changeAddressIndex.bitcoinRegtest; expect(changeAddressIndex.p2wpkh).toStrictEqual({ - address: 'bcrt1q8c6fshw2dlwun7ekn9qwf37cu2rn755ufhry49', - path: "m/84'/0'/0'/1/0", + address: 'bcrt1q9u62588spffmq4dzjxsr5l297znf3z6jkgnhsw', + path: "m/84'/1'/0'/1/0", publicKey: - '03025324888e429ab8e3dbaf1f7802648b9cd01e9b418485c5fa4c1b9b5700e1a6', + '035d49eccd54d0099e43676277c7a6d4625d611da88a5df49bf9517a7791a777a5', index: 0, scriptHash: - '48d4bc4257d5177c6a44dfa0e3fd17916fc15b39b8a1cbb0aa297b059f826425', + 'bb913937bc604ebdb5538198d85c5ea211a88926d36926ecf0800ecc507d0fcc', }); // check utxos @@ -250,7 +252,7 @@ describe('Wallet - wallet restore and receive', () => { // wallet UTXO should have input with new txid const newOutput = utxos.find((o) => { return ( - o.address === 'bcrt1qcr8te4kr609gcawutmrza0j4xv80jy8zeqchgx' && + o.address === 'bcrt1q6rz28mcfaxtmd6v789l9rrlrusdprr9pz3cppk' && o.tx_hash === incomigTxid ); }); @@ -263,7 +265,7 @@ describe('Wallet - wallet restore and receive', () => { expect(transactions).toHaveProperty( incomigTxid, expect.objectContaining({ - address: 'bcrt1qcr8te4kr609gcawutmrza0j4xv80jy8zeqchgx', + address: 'bcrt1q6rz28mcfaxtmd6v789l9rrlrusdprr9pz3cppk', matchedOutputValue: 1, satsPerByte: 1, type: 'received', diff --git a/package.json b/package.json index 46ba6ef34..dfa084135 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@synonymdev/web-relay": "1.0.7", "backpack-client": "github:synonymdev/bitkit-backup-client#f08fdb28529d8a3f8bfecc789443c43b966a7581", "bech32": "2.0.0", - "beignet": "0.0.6", + "beignet": "0.0.7", "bip21": "2.0.3", "bip32": "4.0.0", "bip39": "3.0.4", diff --git a/src/store/actions/wallet.ts b/src/store/actions/wallet.ts index 38e222dc5..0124ca711 100644 --- a/src/store/actions/wallet.ts +++ b/src/store/actions/wallet.ts @@ -125,7 +125,7 @@ export const createDefaultWalletStructure = async ({ export const updateExchangeRates = async ( exchangeRates: IExchangeRates, ): Promise> => { - if (!exchangeRates) { + if (!exchangeRates || Object.keys(exchangeRates).length === 0) { const res = await getExchangeRates(); if (res.isErr()) { return err(res.error); diff --git a/src/utils/startup/index.ts b/src/utils/startup/index.ts index 2515fd31e..5d4c60bcb 100644 --- a/src/utils/startup/index.ts +++ b/src/utils/startup/index.ts @@ -3,13 +3,13 @@ import { err, ok, Result } from '@synonymdev/result'; import { generateMnemonic, - getMnemonicPhrase, getBip39Passphrase, - refreshWallet, + getMnemonicPhrase, + getSelectedAddressType, getSelectedNetwork, getSelectedWallet, + refreshWallet, setupOnChainWallet, - getSelectedAddressType, } from '../wallet'; import { createWallet } from '../../store/actions/wallet'; import { getWalletStore } from '../../store/helpers'; @@ -59,6 +59,15 @@ export const restoreSeed = async ({ if (res.isErr()) { return res; } + const setupRes = await setupOnChainWallet({ + name: getSelectedWallet(), + selectedNetwork: EAvailableNetwork.bitcoin, + mnemonic, + bip39Passphrase, + }); + if (setupRes.isErr()) { + return err(setupRes.error.message); + } return ok('Seed restored'); }; diff --git a/src/utils/wallet/transactions.ts b/src/utils/wallet/transactions.ts index aa71d39c1..723c65385 100644 --- a/src/utils/wallet/transactions.ts +++ b/src/utils/wallet/transactions.ts @@ -1417,7 +1417,7 @@ export const setupBoost = async ({ return err('Unable to boost this transaction.'); } if (canBoostResponse.rbf) { - return await setupRbf({ selectedWallet, selectedNetwork, txid }); + return await setupRbf({ txid }); } else { return await setupCpfp({ txid }); } @@ -1425,8 +1425,6 @@ export const setupBoost = async ({ /** * Sets up a CPFP transaction. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] * @param {string} [txid] * @param {number} [satsPerByte] */ @@ -1444,25 +1442,13 @@ export const setupCpfp = async ({ /** * Sets up a transaction for RBF. * @param {string} txid - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] */ export const setupRbf = async ({ txid, - selectedWallet, - selectedNetwork, }: { txid: string; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; }): Promise>> => { try { - if (!selectedNetwork) { - selectedNetwork = getSelectedNetwork(); - } - if (!selectedWallet) { - selectedWallet = getSelectedWallet(); - } await setupOnChainTransaction({ rbf: true, }); diff --git a/yarn.lock b/yarn.lock index dd655a362..6e9b38bb7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4386,10 +4386,10 @@ bech32@^1.1.2, bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -beignet@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.6.tgz#a082c80fee6b7261b5f6f673489ffa9117be13ba" - integrity sha512-dpI8bwH4B2cBHUTjsMhO+kIJJSnSJI2Mo3h3bZ98k7dUH1tb0J/DLyx0GBpFORSg9Q4skp0HY3I39kQEZJCf2g== +beignet@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.7.tgz#c99b2796ada269ad913d8568e8e60fcf86c9b7c3" + integrity sha512-0hFlb4H6LzC9Px1MGCzAjSo9jjuxbla+QaCn+3px04h/ZNlMxZaaql48DNVHpPGO5Sqdmw5tdPHCWRrQOT4Cww== dependencies: "@bitcoinerlab/secp256k1" "1.0.5" bech32 "2.0.0" From 349122a8fe27f0225628120ce5e7b22090d2a0f3 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Tue, 9 Jan 2024 15:00:57 -0500 Subject: [PATCH 08/50] fix(wallet): Fix Restore Tests --- src/utils/startup/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/startup/index.ts b/src/utils/startup/index.ts index 5d4c60bcb..7d2505b9d 100644 --- a/src/utils/startup/index.ts +++ b/src/utils/startup/index.ts @@ -61,7 +61,7 @@ export const restoreSeed = async ({ } const setupRes = await setupOnChainWallet({ name: getSelectedWallet(), - selectedNetwork: EAvailableNetwork.bitcoin, + selectedNetwork: getSelectedNetwork(), mnemonic, bip39Passphrase, }); From 45aae65b70b587383612acd120d2ff38b8752a42 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Tue, 9 Jan 2024 15:39:07 -0500 Subject: [PATCH 09/50] fix(wallet): Add startWalletServices method to wallet-restore --- __tests__/wallet-restore.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/__tests__/wallet-restore.ts b/__tests__/wallet-restore.ts index 8c0ba0427..ffeac2c07 100644 --- a/__tests__/wallet-restore.ts +++ b/__tests__/wallet-restore.ts @@ -5,6 +5,7 @@ import store from '../src/store'; import { restoreSeed, startWalletServices } from '../src/utils/startup'; import { EAvailableNetworks, EProtocol } from 'beignet'; import { getOnChainWallet } from '../src/utils/wallet'; +import { EAvailableNetwork } from '../src/utils/networks'; jest.setTimeout(60_000); @@ -51,6 +52,14 @@ describe('Wallet - wallet restore and receive', () => { // switch to regtest await wallet.switchNetwork(EAvailableNetworks.bitcoinRegtest); + // Start wallet services with the newly selected network. + res = await startWalletServices({ + selectedNetwork: EAvailableNetwork.bitcoinRegtest, + onchain: true, + }); + if (res.isErr()) { + throw res.error; + } expect(store.getState().wallet.selectedNetwork).toEqual('bitcoinRegtest'); res = await wallet.electrum.connectToElectrum({ @@ -68,12 +77,6 @@ describe('Wallet - wallet restore and receive', () => { throw res.error; } - // rescan - res = await startWalletServices({ lightning: false, restore: true }); - if (res.isErr()) { - throw res.error; - } - const state = store.getState(); expect(state.wallet.selectedNetwork).toEqual('bitcoinRegtest'); expect(state.wallet.selectedWallet).toEqual('wallet0'); From d4c0f9f5c7de8f35f4dc1b939ec309da28b69078 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Tue, 9 Jan 2024 15:54:59 -0500 Subject: [PATCH 10/50] fix(wallet): Remove connectToElectrum from wallet-restore.ts --- __tests__/wallet-restore.ts | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/__tests__/wallet-restore.ts b/__tests__/wallet-restore.ts index ffeac2c07..5dff704ec 100644 --- a/__tests__/wallet-restore.ts +++ b/__tests__/wallet-restore.ts @@ -51,7 +51,17 @@ describe('Wallet - wallet restore and receive', () => { expect(store.getState().wallet.selectedWallet).toEqual('wallet0'); // switch to regtest - await wallet.switchNetwork(EAvailableNetworks.bitcoinRegtest); + res = await wallet.switchNetwork(EAvailableNetworks.bitcoinRegtest, [ + { + host: '127.0.0.1', + ssl: 60002, + tcp: 60001, + protocol: EProtocol.tcp, + }, + ]); + if (res.isErr()) { + throw res.error; + } // Start wallet services with the newly selected network. res = await startWalletServices({ selectedNetwork: EAvailableNetwork.bitcoinRegtest, @@ -60,22 +70,6 @@ describe('Wallet - wallet restore and receive', () => { if (res.isErr()) { throw res.error; } - expect(store.getState().wallet.selectedNetwork).toEqual('bitcoinRegtest'); - - res = await wallet.electrum.connectToElectrum({ - network: EAvailableNetworks.bitcoinRegtest, - servers: [ - { - host: '127.0.0.1', - ssl: 60002, - tcp: 60001, - protocol: EProtocol.tcp, - }, - ], - }); - if (res.isErr()) { - throw res.error; - } const state = store.getState(); expect(state.wallet.selectedNetwork).toEqual('bitcoinRegtest'); From 68091a5f03ac68e1e9aa8fbfc4da642f86567b15 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Thu, 11 Jan 2024 08:02:44 -0500 Subject: [PATCH 11/50] fix(wallet): Fix Send Flow Fix useFocusEffect in Amount.tsx. Update getBestBlock method. Reduce addPeer timeout. Remove startWalletServices method from restoreRemoteBackups. Updated onMessage method. Bumped beignet version to 0.0.8. --- package.json | 2 +- src/screens/Wallets/Send/Amount.tsx | 9 ++++----- src/utils/lightning/index.ts | 12 +++++++++--- src/utils/startup/index.ts | 9 +++------ src/utils/wallet/index.ts | 9 ++++++--- yarn.lock | 8 ++++---- 6 files changed, 27 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index dfa084135..44021144c 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@synonymdev/web-relay": "1.0.7", "backpack-client": "github:synonymdev/bitkit-backup-client#f08fdb28529d8a3f8bfecc789443c43b966a7581", "bech32": "2.0.0", - "beignet": "0.0.7", + "beignet": "^0.0.8", "bip21": "2.0.3", "bip32": "4.0.0", "bip39": "3.0.4", diff --git a/src/screens/Wallets/Send/Amount.tsx b/src/screens/Wallets/Send/Amount.tsx index c2db60264..21dd0952e 100644 --- a/src/screens/Wallets/Send/Amount.tsx +++ b/src/screens/Wallets/Send/Amount.tsx @@ -98,14 +98,13 @@ const Amount = ({ navigation }: SendScreenProps<'Amount'>): ReactElement => { if ( !transaction.lightningInvoice && onchainBalance > TRANSACTION_DEFAULTS.dustLimit && - (availableAmount === 0 || - outputAmount === 0 || - !transaction.inputs.length) + (availableAmount === 0 || !transaction.inputs.length) ) { + const output = { ...transaction.outputs[0], amount: 0 }; setupOnChainTransaction({ utxos, satsPerByte: transaction.satsPerByte, - outputs: [], + outputs: [output], }); const result = getNumberPadText(0, unit); setText(result); @@ -113,9 +112,9 @@ const Amount = ({ navigation }: SendScreenProps<'Amount'>): ReactElement => { }, [ availableAmount, onchainBalance, - outputAmount, transaction.inputs.length, transaction.lightningInvoice, + transaction.outputs, transaction.satsPerByte, unit, utxos, diff --git a/src/utils/lightning/index.ts b/src/utils/lightning/index.ts index f29cc1b74..dee92c9e8 100644 --- a/src/utils/lightning/index.ts +++ b/src/utils/lightning/index.ts @@ -34,6 +34,7 @@ import { import { getCurrentAddressIndex, getMnemonicPhrase, + getOnChainWalletData, getOnChainWalletElectrum, getSelectedNetwork, getSelectedWallet, @@ -990,8 +991,13 @@ export const getBestBlock = async ( selectedNetwork = getSelectedNetwork(); } try { - const header = getWalletStore()?.header[selectedNetwork]; - return header?.hash ? header : defaultHeader; + const beignetHeader = getOnChainWalletData().header; + const storageHeader = getWalletStore().header[selectedNetwork]; + const header = + beignetHeader.height > storageHeader.height + ? beignetHeader + : storageHeader; + return header?.height ? header : defaultHeader; } catch (e) { console.log(e); return defaultHeader; @@ -1167,7 +1173,7 @@ export const parseUri = ( */ export const addPeer = async ({ peer, - timeout = 5000, + timeout = 2000, }: { peer: string; timeout?: number; diff --git a/src/utils/startup/index.ts b/src/utils/startup/index.ts index 7d2505b9d..233900426 100644 --- a/src/utils/startup/index.ts +++ b/src/utils/startup/index.ts @@ -78,8 +78,7 @@ export const restoreRemoteBackups = async ( if (res.isErr()) { return err(res.error); } - // Only set restore to true if we found that a backup exists to restore with. - return await startWalletServices({ restore: res.value.backupExists }); + return ok('Remote Backups Restored'); }; /** @@ -113,7 +112,6 @@ export const startWalletServices = async ({ if (!selectedNetwork) { selectedNetwork = getSelectedNetwork(); } - let isConnectedToElectrum = false; await promiseTimeout(2500, setupBlocktank(selectedNetwork)); await promiseTimeout(2500, refreshBlocktankInfo()); @@ -145,10 +143,9 @@ export const startWalletServices = async ({ return err(onChainSetupRes.error.message); } } - isConnectedToElectrum = true; // Setup LDK - if (lightning && isConnectedToElectrum) { + if (lightning) { const setupResponse = await setupLdk({ selectedNetwork, shouldRefreshLdk: false, @@ -165,7 +162,7 @@ export const startWalletServices = async ({ // if we restore wallet, we need to generate addresses for all types refreshWallet({ onchain: restore, - lightning: isConnectedToElectrum, + lightning, scanAllAddresses: restore, updateAllAddressTypes: true, // Ensure we scan all address types when spinning up the app. showNotification: !restore, diff --git a/src/utils/wallet/index.ts b/src/utils/wallet/index.ts index 0a8a1607c..83f506160 100644 --- a/src/utils/wallet/index.ts +++ b/src/utils/wallet/index.ts @@ -1461,10 +1461,14 @@ const onMessage: TOnMessage = (key, data) => { id: txMsg.transaction.txid, value: btcToSats(txMsg.transaction.value), }); - updateActivityList(); + setTimeout(() => { + updateActivityList(); + }, 500); break; case 'transactionSent': - updateActivityList(); + setTimeout(() => { + updateActivityList(); + }, 500); break; case 'connectedToElectrum': onElectrumConnectionChange(data as boolean); @@ -2283,7 +2287,6 @@ export const switchNetwork = async ( // Start wallet services with the newly selected network. await startWalletServices({ selectedNetwork, - onchain: false, }); setTimeout(() => { updateActivityList(); diff --git a/yarn.lock b/yarn.lock index 6e9b38bb7..626f4780a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4386,10 +4386,10 @@ bech32@^1.1.2, bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -beignet@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.7.tgz#c99b2796ada269ad913d8568e8e60fcf86c9b7c3" - integrity sha512-0hFlb4H6LzC9Px1MGCzAjSo9jjuxbla+QaCn+3px04h/ZNlMxZaaql48DNVHpPGO5Sqdmw5tdPHCWRrQOT4Cww== +beignet@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.8.tgz#596b40b71deab8ef53916253e7df3dcaeecd6b55" + integrity sha512-d0Oq4VvhP1WzkhkivjgvjdwWxhe/0guWJcWhTxnldzwfMJFpP8ns70kty9R6o019CaTH/5MxsIQji9CdGh9xNQ== dependencies: "@bitcoinerlab/secp256k1" "1.0.5" bech32 "2.0.0" From 1007c586b6a9b7258e343d7149790def9c19d70f Mon Sep 17 00:00:00 2001 From: Corey Date: Thu, 11 Jan 2024 08:14:31 -0500 Subject: [PATCH 12/50] Update src/store/reducers/wallet.ts Co-authored-by: Philipp Walter --- src/store/reducers/wallet.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/store/reducers/wallet.ts b/src/store/reducers/wallet.ts index 486420c37..2efc043a7 100644 --- a/src/store/reducers/wallet.ts +++ b/src/store/reducers/wallet.ts @@ -12,8 +12,7 @@ const wallet = ( state: IWalletStore = defaultWalletStoreShape, action, ): IWalletStore => { - let selectedWallet = state?.selectedWallet ?? 'wallet0'; - let selectedNetwork = state?.selectedNetwork ?? 'bitcoin'; + let { selectedWallet, selectedNetwork } = state; if (action.payload?.selectedWallet) { selectedWallet = action.payload.selectedWallet; From 5ec5503654c09b81cd03c9f063eb5caa5aff4972 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Thu, 11 Jan 2024 08:15:23 -0500 Subject: [PATCH 13/50] fix(wallet): Update updateExchangeRates Type --- src/store/actions/wallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/actions/wallet.ts b/src/store/actions/wallet.ts index 0124ca711..16bc3fc7f 100644 --- a/src/store/actions/wallet.ts +++ b/src/store/actions/wallet.ts @@ -123,7 +123,7 @@ export const createDefaultWalletStructure = async ({ }; export const updateExchangeRates = async ( - exchangeRates: IExchangeRates, + exchangeRates?: IExchangeRates, ): Promise> => { if (!exchangeRates || Object.keys(exchangeRates).length === 0) { const res = await getExchangeRates(); From f345df423b370a3c02e11d456376c30037d3db7c Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Thu, 11 Jan 2024 08:29:03 -0500 Subject: [PATCH 14/50] fix(wallet): Update Regtest Fee Estimates Bump beignet version to 0.0.9. --- package.json | 2 +- src/utils/wallet/transactions.ts | 18 ++++++------------ yarn.lock | 8 ++++---- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 44021144c..5c51d4b56 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@synonymdev/web-relay": "1.0.7", "backpack-client": "github:synonymdev/bitkit-backup-client#f08fdb28529d8a3f8bfecc789443c43b966a7581", "bech32": "2.0.0", - "beignet": "^0.0.8", + "beignet": "^0.0.9", "bip21": "2.0.3", "bip32": "4.0.0", "bip39": "3.0.4", diff --git a/src/utils/wallet/transactions.ts b/src/utils/wallet/transactions.ts index 723c65385..7f5462574 100644 --- a/src/utils/wallet/transactions.ts +++ b/src/utils/wallet/transactions.ts @@ -1632,18 +1632,12 @@ export const getFeeEstimates = async ( }); } - const urlModifier = selectedNetwork === 'bitcoin' ? '' : 'testnet/'; - const response = await fetch( - `https://mempool.space/${urlModifier}api/v1/fees/recommended`, - ); - const res: IGetFeeEstimatesResponse = await response.json(); - return ok({ - fast: res.fastestFee, - normal: res.halfHourFee, - slow: res.hourFee, - minimum: res.minimumFee, - timestamp: Date.now(), - }); + const wallet = getOnChainWallet(); + const feeRes = await wallet.getFeeEstimates(); + if (!feeRes) { + return err('Unable to get fee estimates.'); + } + return ok(feeRes); } catch (e) { return err(e); } diff --git a/yarn.lock b/yarn.lock index 626f4780a..58c529dc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4386,10 +4386,10 @@ bech32@^1.1.2, bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -beignet@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.8.tgz#596b40b71deab8ef53916253e7df3dcaeecd6b55" - integrity sha512-d0Oq4VvhP1WzkhkivjgvjdwWxhe/0guWJcWhTxnldzwfMJFpP8ns70kty9R6o019CaTH/5MxsIQji9CdGh9xNQ== +beignet@^0.0.9: + version "0.0.9" + resolved "https://registry.yarnpkg.com/beignet/-/beignet-0.0.9.tgz#fe72abb0d8203c50483834fa355567969d7cfc06" + integrity sha512-AdD38Z21F2/WHA/CPWcgnLbdhGgxsDomhX3VIoTOWG3mPhrFvwi+I1ZFKmMGFaPYv1Xun5pZewQTzmdIjhirwQ== dependencies: "@bitcoinerlab/secp256k1" "1.0.5" bech32 "2.0.0" From fa165429f454fe22b21dd0b11c30a5e6c6758df2 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Thu, 11 Jan 2024 08:51:23 -0500 Subject: [PATCH 15/50] fix(wallet): Fix updateAddressType Adds addressType to UPDATE_WALLET_DATA condition. --- src/store/reducers/wallet.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/store/reducers/wallet.ts b/src/store/reducers/wallet.ts index 2efc043a7..77b2b747a 100644 --- a/src/store/reducers/wallet.ts +++ b/src/store/reducers/wallet.ts @@ -54,7 +54,8 @@ const wallet = ( value === 'balance' || value === 'utxos' || value === 'blacklistedUtxos' || - value === 'unconfirmedTransactions' + value === 'unconfirmedTransactions' || + value === 'addressType' ) { return { ...state, From ca22585e2647b9fc4240c9a79023bfb58aafe03d Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Thu, 11 Jan 2024 11:38:57 -0500 Subject: [PATCH 16/50] fix(wallet): Remove startWalletServices from test --- __tests__/wallet-restore.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/__tests__/wallet-restore.ts b/__tests__/wallet-restore.ts index 5dff704ec..59176d584 100644 --- a/__tests__/wallet-restore.ts +++ b/__tests__/wallet-restore.ts @@ -2,7 +2,7 @@ import BitcoinJsonRpc from 'bitcoin-json-rpc'; import '../src/utils/i18n'; import store from '../src/store'; -import { restoreSeed, startWalletServices } from '../src/utils/startup'; +import { restoreSeed } from '../src/utils/startup'; import { EAvailableNetworks, EProtocol } from 'beignet'; import { getOnChainWallet } from '../src/utils/wallet'; import { EAvailableNetwork } from '../src/utils/networks'; @@ -47,7 +47,6 @@ describe('Wallet - wallet restore and receive', () => { const wallet = getOnChainWallet(); expect(res.value).toEqual('Seed restored'); - expect(wallet.network).toEqual(EAvailableNetworks.bitcoin); expect(store.getState().wallet.selectedWallet).toEqual('wallet0'); // switch to regtest @@ -62,17 +61,10 @@ describe('Wallet - wallet restore and receive', () => { if (res.isErr()) { throw res.error; } - // Start wallet services with the newly selected network. - res = await startWalletServices({ - selectedNetwork: EAvailableNetwork.bitcoinRegtest, - onchain: true, - }); - if (res.isErr()) { - throw res.error; - } - const state = store.getState(); - expect(state.wallet.selectedNetwork).toEqual('bitcoinRegtest'); + expect(state.wallet.selectedNetwork).toEqual( + EAvailableNetwork.bitcoinRegtest, + ); expect(state.wallet.selectedWallet).toEqual('wallet0'); expect( state.wallet.wallets.wallet0.balance.bitcoinRegtest, From 68434e0df1fd99a012735a23700db6f976541916 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Thu, 11 Jan 2024 12:36:55 -0500 Subject: [PATCH 17/50] fix(wallet): Add switchNetwork to restore test --- __tests__/wallet-restore.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/__tests__/wallet-restore.ts b/__tests__/wallet-restore.ts index 59176d584..3d1dbe269 100644 --- a/__tests__/wallet-restore.ts +++ b/__tests__/wallet-restore.ts @@ -4,7 +4,7 @@ import '../src/utils/i18n'; import store from '../src/store'; import { restoreSeed } from '../src/utils/startup'; import { EAvailableNetworks, EProtocol } from 'beignet'; -import { getOnChainWallet } from '../src/utils/wallet'; +import { getOnChainWallet, switchNetwork } from '../src/utils/wallet'; import { EAvailableNetwork } from '../src/utils/networks'; jest.setTimeout(60_000); @@ -50,7 +50,7 @@ describe('Wallet - wallet restore and receive', () => { expect(store.getState().wallet.selectedWallet).toEqual('wallet0'); // switch to regtest - res = await wallet.switchNetwork(EAvailableNetworks.bitcoinRegtest, [ + const switchRes = await switchNetwork(EAvailableNetwork.bitcoinRegtest, [ { host: '127.0.0.1', ssl: 60002, @@ -58,17 +58,13 @@ describe('Wallet - wallet restore and receive', () => { protocol: EProtocol.tcp, }, ]); - if (res.isErr()) { - throw res.error; + if (switchRes.isErr()) { + throw switchRes.error; } const state = store.getState(); - expect(state.wallet.selectedNetwork).toEqual( - EAvailableNetwork.bitcoinRegtest, - ); + expect(wallet.network).toEqual(EAvailableNetworks.bitcoinRegtest); expect(state.wallet.selectedWallet).toEqual('wallet0'); - expect( - state.wallet.wallets.wallet0.balance.bitcoinRegtest, - ).toBeGreaterThanOrEqual(1); + expect(wallet.data.balance).toBeGreaterThanOrEqual(1); // check addresses const addresses = state.wallet.wallets.wallet0.addresses.bitcoinRegtest; From f7d31df31bc5b1fea2653302d560c17397220f9d Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Thu, 11 Jan 2024 12:50:25 -0500 Subject: [PATCH 18/50] fix(wallet): Add getCurrentWallet to updateOnChainActivityList --- src/store/utils/activity.ts | 7 ++++--- src/utils/wallet/index.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/store/utils/activity.ts b/src/store/utils/activity.ts index 4b186fa6d..5ad719d84 100644 --- a/src/store/utils/activity.ts +++ b/src/store/utils/activity.ts @@ -4,7 +4,7 @@ import { TChannel } from '@synonymdev/react-native-ldk'; import { EPaymentType } from '../types/wallet'; import { EActivityType, TLightningActivityItem } from '../types/activity'; import { getBlocktankStore, dispatch } from '../helpers'; -import { getCurrentWallet, getOnChainWalletData } from '../../utils/wallet'; +import { getCurrentWallet } from '../../utils/wallet'; import { getLightningChannels } from '../../utils/lightning'; import { formatBoostedActivityItems } from '../../utils/boost'; import { onChainTransactionToActivityItem } from '../../utils/activity'; @@ -81,7 +81,7 @@ export const updateActivityList = (): Result => { * @returns {Result} */ export const updateOnChainActivityList = (): Result => { - let currentWallet = getOnChainWalletData(); + let { currentWallet } = getCurrentWallet({}); if (!currentWallet) { console.warn( 'No wallet found. Cannot update activity list with transactions.', @@ -91,7 +91,8 @@ export const updateOnChainActivityList = (): Result => { const { selectedNetwork, selectedWallet } = getCurrentWallet(); const blocktankTransactions = getBlocktankStore().paidOrders; const blocktankOrders = getBlocktankStore().orders; - const boostedTransactions = currentWallet.boostedTransactions; + const boostedTransactions = + currentWallet.boostedTransactions[selectedNetwork]; const transactions = currentWallet.transactions; const activityItems = Object.values(transactions).map((tx) => { diff --git a/src/utils/wallet/index.ts b/src/utils/wallet/index.ts index 83f506160..fdeb2b63a 100644 --- a/src/utils/wallet/index.ts +++ b/src/utils/wallet/index.ts @@ -2265,7 +2265,7 @@ export const getOnChainWalletTransactionData = (): ISendTransaction => { }; export const getOnChainWalletData = (): IWalletData => { - return wallet.data; + return wallet?.data; }; export const switchNetwork = async ( From f0db5b2b4da9d62bcf99ea16ef9125b2491724f3 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Thu, 11 Jan 2024 13:02:10 -0500 Subject: [PATCH 19/50] fix(wallet): Add refreshWallet to restore test --- __tests__/wallet-restore.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/__tests__/wallet-restore.ts b/__tests__/wallet-restore.ts index 3d1dbe269..59c2d6749 100644 --- a/__tests__/wallet-restore.ts +++ b/__tests__/wallet-restore.ts @@ -61,8 +61,10 @@ describe('Wallet - wallet restore and receive', () => { if (switchRes.isErr()) { throw switchRes.error; } + await wallet.refreshWallet({}); const state = store.getState(); expect(wallet.network).toEqual(EAvailableNetworks.bitcoinRegtest); + expect(wallet.name).toEqual('wallet0'); expect(state.wallet.selectedWallet).toEqual('wallet0'); expect(wallet.data.balance).toBeGreaterThanOrEqual(1); From 03bc56de25b2d9fd6d273d79ca2dfaf74c9bc3b4 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Thu, 11 Jan 2024 13:44:48 -0500 Subject: [PATCH 20/50] fix(wallet): Add addressIndex check to restore test --- __tests__/wallet-restore.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/__tests__/wallet-restore.ts b/__tests__/wallet-restore.ts index 59c2d6749..9f54082c5 100644 --- a/__tests__/wallet-restore.ts +++ b/__tests__/wallet-restore.ts @@ -65,6 +65,9 @@ describe('Wallet - wallet restore and receive', () => { const state = store.getState(); expect(wallet.network).toEqual(EAvailableNetworks.bitcoinRegtest); expect(wallet.name).toEqual('wallet0'); + expect(wallet.data.addressIndex.p2wpkh.address).toEqual( + 'bcrt1qd7spv5q28348xl4myc8zmh983w5jx32cs707jh', + ); expect(state.wallet.selectedWallet).toEqual('wallet0'); expect(wallet.data.balance).toBeGreaterThanOrEqual(1); From 1fe35a8af94e444487fca5a46d2222f1c7341fb0 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Thu, 11 Jan 2024 13:56:50 -0500 Subject: [PATCH 21/50] fix(wallet): Update getOnChainWallet in restore test --- __tests__/wallet-restore.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/__tests__/wallet-restore.ts b/__tests__/wallet-restore.ts index 9f54082c5..9b6b61226 100644 --- a/__tests__/wallet-restore.ts +++ b/__tests__/wallet-restore.ts @@ -44,8 +44,6 @@ describe('Wallet - wallet restore and receive', () => { throw res.error; } - const wallet = getOnChainWallet(); - expect(res.value).toEqual('Seed restored'); expect(store.getState().wallet.selectedWallet).toEqual('wallet0'); @@ -61,8 +59,9 @@ describe('Wallet - wallet restore and receive', () => { if (switchRes.isErr()) { throw switchRes.error; } - await wallet.refreshWallet({}); const state = store.getState(); + const wallet = getOnChainWallet(); + expect(wallet.network).toEqual(EAvailableNetworks.bitcoinRegtest); expect(wallet.name).toEqual('wallet0'); expect(wallet.data.addressIndex.p2wpkh.address).toEqual( From 60afa2134894df6bdebb3c891a3e4f8f9e8339b6 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Thu, 11 Jan 2024 14:08:13 -0500 Subject: [PATCH 22/50] fix(wallet): Updated address storage check in restore test --- __tests__/wallet-restore.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/__tests__/wallet-restore.ts b/__tests__/wallet-restore.ts index 9b6b61226..147356de7 100644 --- a/__tests__/wallet-restore.ts +++ b/__tests__/wallet-restore.ts @@ -64,6 +64,12 @@ describe('Wallet - wallet restore and receive', () => { expect(wallet.network).toEqual(EAvailableNetworks.bitcoinRegtest); expect(wallet.name).toEqual('wallet0'); + const selectedWallet = state.wallet.selectedWallet; + expect( + state.wallet.wallets[selectedWallet].addressIndex[ + EAvailableNetwork.bitcoinRegtest + ].p2wpkh.address, + ).toEqual('bcrt1qd7spv5q28348xl4myc8zmh983w5jx32cs707jh'); expect(wallet.data.addressIndex.p2wpkh.address).toEqual( 'bcrt1qd7spv5q28348xl4myc8zmh983w5jx32cs707jh', ); From 712484b747fa1319a5538cc9c4bbe33db3c6b060 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Fri, 12 Jan 2024 13:36:25 -0500 Subject: [PATCH 23/50] fix(wallet): Fix updateOnChainActivityList Fixes transactions variable in updateOnChainActivityList. Adds key to groupedItems in ActivityShortList.tsx. Removes IFormattedTransaction type. --- src/screens/Activity/ActivityListShort.tsx | 6 +++++- src/store/types/wallet.ts | 22 +--------------------- src/store/utils/activity.ts | 6 +++--- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/src/screens/Activity/ActivityListShort.tsx b/src/screens/Activity/ActivityListShort.tsx index 80e649fab..8386b2c29 100644 --- a/src/screens/Activity/ActivityListShort.tsx +++ b/src/screens/Activity/ActivityListShort.tsx @@ -77,7 +77,11 @@ const ActivityListShort = (): ReactElement => { ) : ( <> - {groupedItems.map((item, index) => renderItem({ item, index }))} + {groupedItems.map((item, index) => ( + + {renderItem({ item, index })} + + ))}