beignet 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +4 -0
- package/.eslintrc +40 -0
- package/.prettierrc +9 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/electrum/constants.js +31 -0
- package/dist/electrum/constants.js.map +1 -0
- package/dist/electrum/index.js +552 -0
- package/dist/electrum/index.js.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/shapes/electrum.js +14 -0
- package/dist/shapes/electrum.js.map +1 -0
- package/dist/shapes/index.js +19 -0
- package/dist/shapes/index.js.map +1 -0
- package/dist/shapes/wallet.js +115 -0
- package/dist/shapes/wallet.js.map +1 -0
- package/dist/transaction/index.js +692 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/types/electrum/constants.d.ts +7 -0
- package/dist/types/electrum/index.d.ts +85 -0
- package/dist/types/electrum.js +9 -0
- package/dist/types/electrum.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +20 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/shapes/electrum.d.ts +5 -0
- package/dist/types/shapes/index.d.ts +2 -0
- package/dist/types/shapes/wallet.d.ts +10 -0
- package/dist/types/transaction/index.d.ts +92 -0
- package/dist/types/transaction.js +12 -0
- package/dist/types/transaction.js.map +1 -0
- package/dist/types/types/electrum.d.ts +113 -0
- package/dist/types/types/index.d.ts +3 -0
- package/dist/types/types/transaction.d.ts +51 -0
- package/dist/types/types/wallet.d.ts +400 -0
- package/dist/types/utils/conversion.d.ts +10 -0
- package/dist/types/utils/derivation-path.d.ts +19 -0
- package/dist/types/utils/electrum.d.ts +10 -0
- package/dist/types/utils/exchange-rate.d.ts +30 -0
- package/dist/types/utils/helpers.d.ts +23 -0
- package/dist/types/utils/index.d.ts +7 -0
- package/dist/types/utils/result.d.ts +16 -0
- package/dist/types/utils/transaction.d.ts +21 -0
- package/dist/types/utils/wallet.d.ts +47 -0
- package/dist/types/wallet/constants.d.ts +12 -0
- package/dist/types/wallet/index.d.ts +214 -0
- package/dist/types/wallet.js +36 -0
- package/dist/types/wallet.js.map +1 -0
- package/dist/utils/conversion.js +52 -0
- package/dist/utils/conversion.js.map +1 -0
- package/dist/utils/derivation-path.js +104 -0
- package/dist/utils/derivation-path.js.map +1 -0
- package/dist/utils/electrum.js +149 -0
- package/dist/utils/electrum.js.map +1 -0
- package/dist/utils/exchange-rate.js +49 -0
- package/dist/utils/exchange-rate.js.map +1 -0
- package/dist/utils/helpers.js +137 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/index.js +24 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/result.js +36 -0
- package/dist/utils/result.js.map +1 -0
- package/dist/utils/transaction.js +327 -0
- package/dist/utils/transaction.js.map +1 -0
- package/dist/utils/wallet.js +227 -0
- package/dist/utils/wallet.js.map +1 -0
- package/dist/wallet/constants.js +15 -0
- package/dist/wallet/constants.js.map +1 -0
- package/dist/wallet/index.js +2113 -0
- package/dist/wallet/index.js.map +1 -0
- package/docs/html/.nojekyll +1 -0
- package/docs/html/assets/highlight.css +22 -0
- package/docs/html/assets/main.js +58 -0
- package/docs/html/assets/search.js +1 -0
- package/docs/html/assets/style.css +1367 -0
- package/docs/html/classes/Electrum.html +812 -0
- package/docs/html/classes/Transaction.html +687 -0
- package/docs/html/classes/Wallet.html +2030 -0
- package/docs/html/enums/EAddressType.html +227 -0
- package/docs/html/enums/EAvailableNetworks.html +255 -0
- package/docs/html/enums/EBoostType.html +220 -0
- package/docs/html/enums/EFeeId.html +241 -0
- package/docs/html/enums/EPaymentType.html +220 -0
- package/docs/html/enums/EProtocol.html +220 -0
- package/docs/html/enums/EUnit.html +227 -0
- package/docs/html/functions/availableNetworks.html +200 -0
- package/docs/html/functions/constructByteCountParam.html +210 -0
- package/docs/html/functions/decodeOpReturnMessage.html +202 -0
- package/docs/html/functions/decodeRawTransaction.html +210 -0
- package/docs/html/functions/err.html +214 -0
- package/docs/html/functions/formatKeyDerivationPath.html +219 -0
- package/docs/html/functions/formatPeerData.html +207 -0
- package/docs/html/functions/generateMnemonic.html +223 -0
- package/docs/html/functions/generateWalletId.html +202 -0
- package/docs/html/functions/getAddressFromScriptPubKey.html +209 -0
- package/docs/html/functions/getAddressTypeFromPath.html +206 -0
- package/docs/html/functions/getByteCount.html +206 -0
- package/docs/html/functions/getDataFallback.html +207 -0
- package/docs/html/functions/getDefaultPort.html +209 -0
- package/docs/html/functions/getDefaultWalletData.html +200 -0
- package/docs/html/functions/getDefaultWalletDataKeys.html +200 -0
- package/docs/html/functions/getExchangeRates.html +200 -0
- package/docs/html/functions/getHighestUsedIndexFromTxHashes.html +216 -0
- package/docs/html/functions/getKeyDerivationPath.html +211 -0
- package/docs/html/functions/getKeyDerivationPathObject.html +216 -0
- package/docs/html/functions/getKeyDerivationPathString.html +220 -0
- package/docs/html/functions/getKeyValue.html +206 -0
- package/docs/html/functions/getPeers.html +211 -0
- package/docs/html/functions/getProtocolForPort.html +209 -0
- package/docs/html/functions/getScriptHash.html +210 -0
- package/docs/html/functions/getSeed.html +209 -0
- package/docs/html/functions/getSeedHash.html +206 -0
- package/docs/html/functions/getSha256.html +206 -0
- package/docs/html/functions/getStorageKeyValues.html +209 -0
- package/docs/html/functions/getTxFee.html +210 -0
- package/docs/html/functions/getWalletDataStorageKey.html +206 -0
- package/docs/html/functions/isValidBech32mEncodedString.html +211 -0
- package/docs/html/functions/objectKeys-1.html +211 -0
- package/docs/html/functions/objectsMatch.html +210 -0
- package/docs/html/functions/ok.html +214 -0
- package/docs/html/functions/parseOnChainPaymentRequest.html +204 -0
- package/docs/html/functions/reduceValue.html +215 -0
- package/docs/html/functions/removeDustOutputs.html +206 -0
- package/docs/html/functions/setReplaceByFee.html +210 -0
- package/docs/html/functions/shuffleArray.html +211 -0
- package/docs/html/functions/validateAddress.html +216 -0
- package/docs/html/functions/validateMnemonic.html +206 -0
- package/docs/html/functions/validateTransaction.html +206 -0
- package/docs/html/index.html +360 -0
- package/docs/html/interfaces/IAddInput.html +231 -0
- package/docs/html/interfaces/IAddress.html +247 -0
- package/docs/html/interfaces/IAddressData.html +231 -0
- package/docs/html/interfaces/IAddressType.html +199 -0
- package/docs/html/interfaces/IAddressTypeData.html +252 -0
- package/docs/html/interfaces/IAddresses.html +199 -0
- package/docs/html/interfaces/IBoostedTransaction.html +238 -0
- package/docs/html/interfaces/IBoostedTransactions.html +199 -0
- package/docs/html/interfaces/ICreateTransaction.html +224 -0
- package/docs/html/interfaces/ICustomGetAddress.html +231 -0
- package/docs/html/interfaces/ICustomGetScriptHash.html +224 -0
- package/docs/html/interfaces/IElectrumGetAddressBalanceRes.html +235 -0
- package/docs/html/interfaces/IExchangeRates.html +210 -0
- package/docs/html/interfaces/IFormattedPeerData.html +245 -0
- package/docs/html/interfaces/IFormattedTransaction.html +343 -0
- package/docs/html/interfaces/IFormattedTransactions.html +199 -0
- package/docs/html/interfaces/IGenerateAddresses.html +252 -0
- package/docs/html/interfaces/IGenerateAddressesResponse.html +224 -0
- package/docs/html/interfaces/IGetAddress.html +231 -0
- package/docs/html/interfaces/IGetAddressBalanceRes.html +226 -0
- package/docs/html/interfaces/IGetAddressByPath.html +224 -0
- package/docs/html/interfaces/IGetAddressHistoryResponse.html +269 -0
- package/docs/html/interfaces/IGetAddressResponse.html +231 -0
- package/docs/html/interfaces/IGetAddressScriptHashesHistoryResponse.html +245 -0
- package/docs/html/interfaces/IGetDerivationPath.html +259 -0
- package/docs/html/interfaces/IGetFeeEstimatesResponse.html +238 -0
- package/docs/html/interfaces/IGetHeaderResponse.html +245 -0
- package/docs/html/interfaces/IGetNextAvailableAddressResponse.html +238 -0
- package/docs/html/interfaces/IGetTransactions.html +245 -0
- package/docs/html/interfaces/IGetTransactionsFromInputs.html +245 -0
- package/docs/html/interfaces/IGetUtxosResponse.html +224 -0
- package/docs/html/interfaces/IHeader.html +231 -0
- package/docs/html/interfaces/IIndexes.html +238 -0
- package/docs/html/interfaces/IKeyDerivationPath.html +247 -0
- package/docs/html/interfaces/IKeyDerivationPathData.html +224 -0
- package/docs/html/interfaces/INewBlock.html +224 -0
- package/docs/html/interfaces/IOnchainFees.html +245 -0
- package/docs/html/interfaces/IOutput.html +231 -0
- package/docs/html/interfaces/IPeerData.html +231 -0
- package/docs/html/interfaces/IRbfData.html +252 -0
- package/docs/html/interfaces/ISend.html +224 -0
- package/docs/html/interfaces/ISendTransaction.html +322 -0
- package/docs/html/interfaces/ISendTx.html +231 -0
- package/docs/html/interfaces/ISetupTransaction.html +238 -0
- package/docs/html/interfaces/ISubscribeToAddress.html +247 -0
- package/docs/html/interfaces/ISubscribeToHeader.html +245 -0
- package/docs/html/interfaces/ITargets.html +238 -0
- package/docs/html/interfaces/ITransaction.html +281 -0
- package/docs/html/interfaces/ITxHash.html +217 -0
- package/docs/html/interfaces/ITxHashes.html +235 -0
- package/docs/html/interfaces/IUtxo.html +275 -0
- package/docs/html/interfaces/IVin.html +252 -0
- package/docs/html/interfaces/IVout.html +246 -0
- package/docs/html/interfaces/IWallet.html +350 -0
- package/docs/html/interfaces/IWalletData.html +343 -0
- package/docs/html/types/ElectrumConnectionPubSub.html +233 -0
- package/docs/html/types/ElectrumConnectionSubscription.html +204 -0
- package/docs/html/types/InputData.html +203 -0
- package/docs/html/types/ObjectKeys.html +198 -0
- package/docs/html/types/Result.html +201 -0
- package/docs/html/types/TAddressIndexInfo.html +204 -0
- package/docs/html/types/TAddressLabel.html +193 -0
- package/docs/html/types/TAddressType.html +193 -0
- package/docs/html/types/TAddressTypeContent.html +198 -0
- package/docs/html/types/TAddressTypes.html +193 -0
- package/docs/html/types/TAvailableNetworks.html +193 -0
- package/docs/html/types/TConnectToElectrumRes.html +193 -0
- package/docs/html/types/TDecodeRawTx.html +214 -0
- package/docs/html/types/TElectrumNetworks.html +193 -0
- package/docs/html/types/TGetAddressHistory.html +200 -0
- package/docs/html/types/TGetByteCountInput.html +193 -0
- package/docs/html/types/TGetByteCountInputs.html +193 -0
- package/docs/html/types/TGetByteCountOutput.html +193 -0
- package/docs/html/types/TGetByteCountOutputs.html +193 -0
- package/docs/html/types/TGetData.html +211 -0
- package/docs/html/types/TGetTotalFeeObj.html +204 -0
- package/docs/html/types/TKeyDerivationAccount.html +193 -0
- package/docs/html/types/TKeyDerivationChange.html +193 -0
- package/docs/html/types/TKeyDerivationCoinType.html +193 -0
- package/docs/html/types/TKeyDerivationIndex.html +193 -0
- package/docs/html/types/TKeyDerivationPurpose.html +193 -0
- package/docs/html/types/TMessageDataMap.html +210 -0
- package/docs/html/types/TMessageKeys.html +193 -0
- package/docs/html/types/TOnMessage.html +213 -0
- package/docs/html/types/TProcessUnconfirmedTransactions.html +202 -0
- package/docs/html/types/TProtocol.html +193 -0
- package/docs/html/types/TServer.html +204 -0
- package/docs/html/types/TSetData.html +213 -0
- package/docs/html/types/TSetupTransactionResponse.html +193 -0
- package/docs/html/types/TStorage.html +200 -0
- package/docs/html/types/TSubscribedReceive.html +193 -0
- package/docs/html/types/TTransactionMessage.html +198 -0
- package/docs/html/types/TTxResponse.html +206 -0
- package/docs/html/types/TTxResult.html +200 -0
- package/docs/html/types/TUnspentAddressScriptHashData.html +198 -0
- package/docs/html/types/TWalletDataKeys.html +193 -0
- package/docs/html/variables/defaultElectrumPorts.html +193 -0
- package/docs/html/variables/electrumConnection.html +199 -0
- package/docs/html/variables/mostUsedExchangeTickers.html +241 -0
- package/docs/markdown/.nojekyll +1 -0
- package/docs/markdown/README.md +1875 -0
- package/docs/markdown/classes/Electrum.md +718 -0
- package/docs/markdown/classes/Transaction.md +536 -0
- package/docs/markdown/classes/Wallet.md +2189 -0
- package/docs/markdown/enums/EAddressType.md +41 -0
- package/docs/markdown/enums/EAvailableNetworks.md +85 -0
- package/docs/markdown/enums/EBoostType.md +30 -0
- package/docs/markdown/enums/EFeeId.md +63 -0
- package/docs/markdown/enums/EPaymentType.md +30 -0
- package/docs/markdown/enums/EProtocol.md +30 -0
- package/docs/markdown/enums/EUnit.md +41 -0
- package/docs/markdown/interfaces/IAddInput.md +41 -0
- package/docs/markdown/interfaces/IAddress.md +69 -0
- package/docs/markdown/interfaces/IAddressData.md +41 -0
- package/docs/markdown/interfaces/IAddressType.md +7 -0
- package/docs/markdown/interfaces/IAddressTypeData.md +74 -0
- package/docs/markdown/interfaces/IAddresses.md +7 -0
- package/docs/markdown/interfaces/IBoostedTransaction.md +52 -0
- package/docs/markdown/interfaces/IBoostedTransactions.md +7 -0
- package/docs/markdown/interfaces/ICreateTransaction.md +30 -0
- package/docs/markdown/interfaces/ICustomGetAddress.md +41 -0
- package/docs/markdown/interfaces/ICustomGetScriptHash.md +30 -0
- package/docs/markdown/interfaces/IElectrumGetAddressBalanceRes.md +55 -0
- package/docs/markdown/interfaces/IExchangeRates.md +7 -0
- package/docs/markdown/interfaces/IFormattedPeerData.md +63 -0
- package/docs/markdown/interfaces/IFormattedTransaction.md +217 -0
- package/docs/markdown/interfaces/IFormattedTransactions.md +7 -0
- package/docs/markdown/interfaces/IGenerateAddresses.md +74 -0
- package/docs/markdown/interfaces/IGenerateAddressesResponse.md +30 -0
- package/docs/markdown/interfaces/IGetAddress.md +41 -0
- package/docs/markdown/interfaces/IGetAddressBalanceRes.md +36 -0
- package/docs/markdown/interfaces/IGetAddressByPath.md +30 -0
- package/docs/markdown/interfaces/IGetAddressHistoryResponse.md +121 -0
- package/docs/markdown/interfaces/IGetAddressResponse.md +41 -0
- package/docs/markdown/interfaces/IGetAddressScriptHashesHistoryResponse.md +63 -0
- package/docs/markdown/interfaces/IGetDerivationPath.md +100 -0
- package/docs/markdown/interfaces/IGetFeeEstimatesResponse.md +52 -0
- package/docs/markdown/interfaces/IGetHeaderResponse.md +63 -0
- package/docs/markdown/interfaces/IGetNextAvailableAddressResponse.md +52 -0
- package/docs/markdown/interfaces/IGetTransactions.md +63 -0
- package/docs/markdown/interfaces/IGetTransactionsFromInputs.md +63 -0
- package/docs/markdown/interfaces/IGetUtxosResponse.md +30 -0
- package/docs/markdown/interfaces/IHeader.md +41 -0
- package/docs/markdown/interfaces/IIndexes.md +52 -0
- package/docs/markdown/interfaces/IKeyDerivationPath.md +69 -0
- package/docs/markdown/interfaces/IKeyDerivationPathData.md +30 -0
- package/docs/markdown/interfaces/INewBlock.md +30 -0
- package/docs/markdown/interfaces/IOnchainFees.md +63 -0
- package/docs/markdown/interfaces/IOutput.md +41 -0
- package/docs/markdown/interfaces/IPeerData.md +41 -0
- package/docs/markdown/interfaces/IRbfData.md +74 -0
- package/docs/markdown/interfaces/ISend.md +30 -0
- package/docs/markdown/interfaces/ISendTransaction.md +184 -0
- package/docs/markdown/interfaces/ISendTx.md +41 -0
- package/docs/markdown/interfaces/ISetupTransaction.md +52 -0
- package/docs/markdown/interfaces/ISubscribeToAddress.md +60 -0
- package/docs/markdown/interfaces/ISubscribeToHeader.md +59 -0
- package/docs/markdown/interfaces/ITargets.md +52 -0
- package/docs/markdown/interfaces/ITransaction.md +88 -0
- package/docs/markdown/interfaces/ITxHash.md +19 -0
- package/docs/markdown/interfaces/ITxHashes.md +55 -0
- package/docs/markdown/interfaces/IUtxo.md +112 -0
- package/docs/markdown/interfaces/IVin.md +70 -0
- package/docs/markdown/interfaces/IVout.md +52 -0
- package/docs/markdown/interfaces/IWallet.md +209 -0
- package/docs/markdown/interfaces/IWalletData.md +217 -0
- package/example/helpers.ts +76 -0
- package/example/index.ts +77 -0
- package/package.json +69 -0
- package/src/electrum/constants.ts +28 -0
- package/src/electrum/index.ts +838 -0
- package/src/index.ts +6 -0
- package/src/shapes/electrum.ts +13 -0
- package/src/shapes/index.ts +2 -0
- package/src/shapes/wallet.ts +133 -0
- package/src/transaction/index.ts +1078 -0
- package/src/types/electrum.ts +139 -0
- package/src/types/index.ts +3 -0
- package/src/types/transaction.ts +58 -0
- package/src/types/wallet.ts +503 -0
- package/src/utils/conversion.ts +88 -0
- package/src/utils/derivation-path.ts +171 -0
- package/src/utils/electrum.ts +188 -0
- package/src/utils/exchange-rate.ts +70 -0
- package/src/utils/helpers.ts +191 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/result.ts +79 -0
- package/src/utils/transaction.ts +417 -0
- package/src/utils/wallet.ts +351 -0
- package/src/wallet/constants.ts +24 -0
- package/src/wallet/index.ts +3304 -0
- package/tests/constants.ts +9 -0
- package/tests/derivation.test.ts +146 -0
- package/tests/electrum.test.ts +137 -0
- package/tests/storage.test.ts +222 -0
- package/tests/transaction.test.ts +187 -0
- package/tests/wallet.test.ts +307 -0
- package/tsconfig.json +32 -0
- package/typedoc.json +5 -0
|
@@ -0,0 +1,1078 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EAddressType,
|
|
3
|
+
EBoostType,
|
|
4
|
+
EFeeId,
|
|
5
|
+
IAddresses,
|
|
6
|
+
IOutput,
|
|
7
|
+
ISendTransaction,
|
|
8
|
+
IUtxo,
|
|
9
|
+
TGetTotalFeeObj
|
|
10
|
+
} from '../types';
|
|
11
|
+
import { getDefaultSendTransaction } from '../shapes';
|
|
12
|
+
import { Wallet } from '../wallet';
|
|
13
|
+
import { Result, ok, err, validateTransaction } from '../utils';
|
|
14
|
+
import { reduceValue, shuffleArray } from '../utils';
|
|
15
|
+
import { TRANSACTION_DEFAULTS } from '../wallet/constants';
|
|
16
|
+
import {
|
|
17
|
+
constructByteCountParam,
|
|
18
|
+
getByteCount,
|
|
19
|
+
removeDustOutputs,
|
|
20
|
+
setReplaceByFee
|
|
21
|
+
} from '../utils';
|
|
22
|
+
import {
|
|
23
|
+
IAddInput,
|
|
24
|
+
ICreateTransaction,
|
|
25
|
+
ISetupTransaction,
|
|
26
|
+
ITargets,
|
|
27
|
+
TSetupTransactionResponse
|
|
28
|
+
} from '../types';
|
|
29
|
+
import { networks, Psbt } from 'bitcoinjs-lib';
|
|
30
|
+
import { BIP32Interface } from 'bip32';
|
|
31
|
+
import ecc from '@bitcoinerlab/secp256k1';
|
|
32
|
+
import * as bitcoin from 'bitcoinjs-lib';
|
|
33
|
+
import { getAddressInfo } from 'bitcoin-address-validation';
|
|
34
|
+
|
|
35
|
+
bitcoin.initEccLib(ecc);
|
|
36
|
+
|
|
37
|
+
export class Transaction {
|
|
38
|
+
private _data: ISendTransaction;
|
|
39
|
+
private readonly _wallet: Wallet;
|
|
40
|
+
|
|
41
|
+
constructor({ wallet }: { wallet: Wallet }) {
|
|
42
|
+
this._wallet = wallet;
|
|
43
|
+
this._data = getDefaultSendTransaction();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public get data(): ISendTransaction {
|
|
47
|
+
return this._data;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 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.
|
|
52
|
+
* This function will not override previously set transaction data. To do that you'll need to call resetSendTransaction.
|
|
53
|
+
* @param {string[]} [inputTxHashes]
|
|
54
|
+
* @param {IUtxo[]} [utxos]
|
|
55
|
+
* @param {boolean} [rbf]
|
|
56
|
+
* @param {number} [satsPerByte]
|
|
57
|
+
* @returns {Promise<Result<Partial<ISendTransaction>>>}
|
|
58
|
+
*/
|
|
59
|
+
public async setupTransaction({
|
|
60
|
+
inputTxHashes,
|
|
61
|
+
utxos,
|
|
62
|
+
rbf = false,
|
|
63
|
+
satsPerByte = 1
|
|
64
|
+
}: ISetupTransaction = {}): Promise<TSetupTransactionResponse> {
|
|
65
|
+
try {
|
|
66
|
+
const addressType = this._wallet.data.addressType;
|
|
67
|
+
|
|
68
|
+
const currentWallet = this._wallet.data;
|
|
69
|
+
|
|
70
|
+
const transaction = currentWallet.transaction;
|
|
71
|
+
|
|
72
|
+
// Gather required inputs.
|
|
73
|
+
let inputs: IUtxo[] = [];
|
|
74
|
+
if (inputTxHashes) {
|
|
75
|
+
// If specified, filter for the desired tx_hash and push the utxo as an input.
|
|
76
|
+
inputs = currentWallet.utxos.filter((utxo) => {
|
|
77
|
+
return inputTxHashes.includes(utxo.tx_hash);
|
|
78
|
+
});
|
|
79
|
+
} else if (utxos) {
|
|
80
|
+
inputs = utxos;
|
|
81
|
+
} else {
|
|
82
|
+
inputs = currentWallet.utxos;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!inputs.length) {
|
|
86
|
+
// If inputs were previously selected, leave them.
|
|
87
|
+
if (transaction.inputs.length > 0) {
|
|
88
|
+
inputs = transaction.inputs;
|
|
89
|
+
} else {
|
|
90
|
+
// Otherwise, lets use our available utxo's.
|
|
91
|
+
//inputs = currentWallet.utxos;
|
|
92
|
+
inputs = this.removeBlackListedUtxos(currentWallet.utxos);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!inputs.length) {
|
|
97
|
+
return err('No inputs specified.');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const currentChangeAddresses = currentWallet.changeAddresses;
|
|
101
|
+
|
|
102
|
+
const addressTypeKeys = Object.values(EAddressType);
|
|
103
|
+
let changeAddresses: IAddresses = {};
|
|
104
|
+
addressTypeKeys.forEach((key) => {
|
|
105
|
+
changeAddresses = {
|
|
106
|
+
...changeAddresses,
|
|
107
|
+
...currentChangeAddresses[key]
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
const changeAddressesArr = Object.values(changeAddresses).map(
|
|
111
|
+
({ address }) => address
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const changeAddressIndexContent =
|
|
115
|
+
currentWallet.changeAddressIndex[addressType];
|
|
116
|
+
// Set the current change address.
|
|
117
|
+
const changeAddress = changeAddressIndexContent.address;
|
|
118
|
+
|
|
119
|
+
// The following code is no longer needed in beignet, but keeping until 1:1 migration with Bitkit is complete.
|
|
120
|
+
// if (!changeAddress || changeAddressIndexContent.index < 0) {
|
|
121
|
+
// // It's possible we haven't set the change address index yet. Generate one on the fly.
|
|
122
|
+
// const generateAddressResponse = await this._wallet.generateAddresses({
|
|
123
|
+
// addressAmount: 0,
|
|
124
|
+
// changeAddressAmount: 1,
|
|
125
|
+
// addressType
|
|
126
|
+
// });
|
|
127
|
+
// if (generateAddressResponse.isErr()) {
|
|
128
|
+
// return err(generateAddressResponse.error.message);
|
|
129
|
+
// }
|
|
130
|
+
// changeAddress =
|
|
131
|
+
// generateAddressResponse.value.changeAddresses[0].address;
|
|
132
|
+
// }
|
|
133
|
+
if (!changeAddress) {
|
|
134
|
+
return err('Unable to successfully generate a change address.');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Set the minimum fee.
|
|
138
|
+
const fee = this.getTotalFee({
|
|
139
|
+
satsPerByte,
|
|
140
|
+
message: ''
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const lightningInvoice = currentWallet.transaction?.lightningInvoice;
|
|
144
|
+
|
|
145
|
+
let outputs = currentWallet.transaction.outputs || [];
|
|
146
|
+
if (!lightningInvoice) {
|
|
147
|
+
//Remove any potential change address that may have been included from a previous tx attempt.
|
|
148
|
+
outputs = outputs.filter((output) => {
|
|
149
|
+
if (output.address && !changeAddressesArr.includes(output.address)) {
|
|
150
|
+
return output;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const payload = {
|
|
156
|
+
inputs,
|
|
157
|
+
changeAddress,
|
|
158
|
+
fee,
|
|
159
|
+
outputs,
|
|
160
|
+
rbf,
|
|
161
|
+
satsPerByte
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
this._data = {
|
|
165
|
+
...this._data,
|
|
166
|
+
...payload
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Save the transaction data.
|
|
170
|
+
await this._wallet.saveWalletData('transaction', this._data);
|
|
171
|
+
|
|
172
|
+
return ok(payload);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
return err(e);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* This completely resets the send transaction state.
|
|
180
|
+
* @returns {Promise<Result<string>>}
|
|
181
|
+
*/
|
|
182
|
+
async resetSendTransaction(): Promise<Result<string>> {
|
|
183
|
+
this._data = getDefaultSendTransaction();
|
|
184
|
+
await this._wallet.saveWalletData('transaction', this._data);
|
|
185
|
+
return ok('Transaction reset.');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Removes blacklisted UTXO's from the UTXO array.
|
|
190
|
+
* @param utxos
|
|
191
|
+
*/
|
|
192
|
+
removeBlackListedUtxos(utxos?: IUtxo[]): IUtxo[] {
|
|
193
|
+
if (!utxos) {
|
|
194
|
+
utxos = this._wallet.data.utxos;
|
|
195
|
+
}
|
|
196
|
+
const blacklistedUtxos = this._wallet.data.blacklistedUtxos;
|
|
197
|
+
return utxos.filter((utxo) => {
|
|
198
|
+
return !blacklistedUtxos.find(
|
|
199
|
+
(blacklistedUtxo) =>
|
|
200
|
+
utxo.address === blacklistedUtxo.address &&
|
|
201
|
+
utxo.height === blacklistedUtxo.height &&
|
|
202
|
+
utxo.tx_hash === blacklistedUtxo.tx_hash &&
|
|
203
|
+
utxo.tx_pos === blacklistedUtxo.tx_pos
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Attempt to estimate the current fee for a given transaction and its UTXO's
|
|
210
|
+
* @param {number} [satsPerByte]
|
|
211
|
+
* @param {string} [message]
|
|
212
|
+
* @param {Partial<ISendTransaction>} [transaction]
|
|
213
|
+
* @param {boolean} [fundingLightning]
|
|
214
|
+
* @returns {number}
|
|
215
|
+
*/
|
|
216
|
+
getTotalFee = ({
|
|
217
|
+
satsPerByte,
|
|
218
|
+
message = '',
|
|
219
|
+
transaction = this.data,
|
|
220
|
+
fundingLightning = false
|
|
221
|
+
}: {
|
|
222
|
+
satsPerByte: number;
|
|
223
|
+
message?: string;
|
|
224
|
+
transaction?: Partial<ISendTransaction>;
|
|
225
|
+
fundingLightning?: boolean;
|
|
226
|
+
}): number => {
|
|
227
|
+
const baseTransactionSize = TRANSACTION_DEFAULTS.recommendedBaseFee;
|
|
228
|
+
try {
|
|
229
|
+
const inputs = transaction.inputs || [];
|
|
230
|
+
const outputs = transaction.outputs || [];
|
|
231
|
+
const changeAddress = transaction.changeAddress;
|
|
232
|
+
|
|
233
|
+
//Group all input & output addresses into their respective array.
|
|
234
|
+
const inputAddresses = inputs.map((input) => input.address);
|
|
235
|
+
const outputAddresses = outputs.map((output) => output.address);
|
|
236
|
+
|
|
237
|
+
//No need for a change address when draining the wallet
|
|
238
|
+
if (changeAddress && !transaction.max) {
|
|
239
|
+
outputAddresses.push(changeAddress);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
//Determine the address type of each address and construct the object for fee calculation
|
|
243
|
+
const inputParam = constructByteCountParam(inputAddresses);
|
|
244
|
+
const outputParam = constructByteCountParam(outputAddresses);
|
|
245
|
+
//Increase P2WPKH output address by one for lightning funding calculation.
|
|
246
|
+
if (fundingLightning) {
|
|
247
|
+
outputParam.P2WPKH = (outputParam.P2WPKH || 0) + 1;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const transactionByteCount = getByteCount(
|
|
251
|
+
inputParam,
|
|
252
|
+
outputParam,
|
|
253
|
+
message
|
|
254
|
+
);
|
|
255
|
+
return transactionByteCount * satsPerByte;
|
|
256
|
+
} catch {
|
|
257
|
+
return baseTransactionSize * satsPerByte;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Attempt to estimate the current fee for a given transaction and its UTXO's
|
|
263
|
+
* @param {number} [satsPerByte]
|
|
264
|
+
* @param {string} [message]
|
|
265
|
+
* @param {Partial<ISendTransaction>} [transaction]
|
|
266
|
+
* @param {boolean} [fundingLightning]
|
|
267
|
+
* @returns {Result<TGetTotalFeeObj>}
|
|
268
|
+
*/
|
|
269
|
+
getTotalFeeObj = ({
|
|
270
|
+
satsPerByte = this._wallet.feeEstimates.normal,
|
|
271
|
+
message = '',
|
|
272
|
+
transaction = this.data,
|
|
273
|
+
fundingLightning = false
|
|
274
|
+
}: {
|
|
275
|
+
satsPerByte?: number;
|
|
276
|
+
message?: string;
|
|
277
|
+
transaction?: Partial<ISendTransaction>;
|
|
278
|
+
fundingLightning?: boolean;
|
|
279
|
+
} = {}): Result<TGetTotalFeeObj> => {
|
|
280
|
+
try {
|
|
281
|
+
if (!transaction.inputs?.length) {
|
|
282
|
+
this.setupTransaction({});
|
|
283
|
+
transaction = this.data;
|
|
284
|
+
}
|
|
285
|
+
const inputs = transaction.inputs || [];
|
|
286
|
+
const outputs = transaction.outputs || [];
|
|
287
|
+
const changeAddress = transaction.changeAddress;
|
|
288
|
+
|
|
289
|
+
//Group all input & output addresses into their respective array.
|
|
290
|
+
const inputAddresses = inputs.map((input) => input.address);
|
|
291
|
+
const outputAddresses = outputs.map((output) => output.address);
|
|
292
|
+
|
|
293
|
+
// Always assume we're sending to at least one output for a proper base calculation.
|
|
294
|
+
let increaseAddressCount = 0;
|
|
295
|
+
if (!outputAddresses.length) {
|
|
296
|
+
increaseAddressCount++;
|
|
297
|
+
}
|
|
298
|
+
//No need for a change address when draining the wallet
|
|
299
|
+
if (changeAddress && !transaction.max) {
|
|
300
|
+
outputAddresses.push(changeAddress);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
//Determine the address type of each address and construct the object for fee calculation
|
|
304
|
+
const inputParam = constructByteCountParam(inputAddresses);
|
|
305
|
+
const outputParam = constructByteCountParam(outputAddresses, [
|
|
306
|
+
{ addrType: this._wallet.data.addressType, count: increaseAddressCount }
|
|
307
|
+
]);
|
|
308
|
+
//Increase P2WPKH output address by one for lightning funding calculation.
|
|
309
|
+
if (fundingLightning) {
|
|
310
|
+
outputParam.P2WPKH = (outputParam.P2WPKH || 0) + 1;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const transactionByteCount = getByteCount(
|
|
314
|
+
inputParam,
|
|
315
|
+
outputParam,
|
|
316
|
+
message
|
|
317
|
+
);
|
|
318
|
+
const maxSatPerByte = this.getMaxSatsPerByte({
|
|
319
|
+
transactionByteCount,
|
|
320
|
+
balance: this._wallet.getBalance()
|
|
321
|
+
});
|
|
322
|
+
if (maxSatPerByte < satsPerByte) {
|
|
323
|
+
return ok({
|
|
324
|
+
totalFee: transactionByteCount * maxSatPerByte,
|
|
325
|
+
transactionByteCount: maxSatPerByte ? transactionByteCount : 0,
|
|
326
|
+
satsPerByte: maxSatPerByte,
|
|
327
|
+
maxSatPerByte
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return ok({
|
|
331
|
+
totalFee: transactionByteCount * satsPerByte,
|
|
332
|
+
transactionByteCount,
|
|
333
|
+
satsPerByte,
|
|
334
|
+
maxSatPerByte
|
|
335
|
+
});
|
|
336
|
+
} catch (e) {
|
|
337
|
+
return err(e);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Returns the maximum sats per byte that can be used for a given transaction.
|
|
343
|
+
* @param {number} transactionByteCount
|
|
344
|
+
* @param {number} [balance]
|
|
345
|
+
* @returns {number}
|
|
346
|
+
*/
|
|
347
|
+
getMaxSatsPerByte = ({
|
|
348
|
+
transactionByteCount,
|
|
349
|
+
balance = this._wallet.getBalance()
|
|
350
|
+
}: {
|
|
351
|
+
transactionByteCount: number;
|
|
352
|
+
balance?: number;
|
|
353
|
+
}): number => {
|
|
354
|
+
return Math.floor(balance / (2 * transactionByteCount));
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Creates complete signed transaction using the transaction data store
|
|
359
|
+
* @param {ISendTransaction} [transactionData]
|
|
360
|
+
* @param {boolean} [shuffleOutputs]
|
|
361
|
+
* @returns {Promise<Result<{id: string, hex: string}>>}
|
|
362
|
+
*/
|
|
363
|
+
createTransaction = async ({
|
|
364
|
+
transactionData = this.data,
|
|
365
|
+
shuffleOutputs = true
|
|
366
|
+
}: ICreateTransaction = {}): Promise<Result<{ id: string; hex: string }>> => {
|
|
367
|
+
//Remove any outputs that are below the dust limit and apply them to the fee.
|
|
368
|
+
removeDustOutputs(transactionData.outputs);
|
|
369
|
+
|
|
370
|
+
const inputValue = this.getTransactionInputValue({
|
|
371
|
+
inputs: transactionData.inputs
|
|
372
|
+
});
|
|
373
|
+
const outputValue = this.getTransactionOutputValue({
|
|
374
|
+
outputs: transactionData.outputs
|
|
375
|
+
});
|
|
376
|
+
if (inputValue === 0) {
|
|
377
|
+
const message = 'No inputs to spend.';
|
|
378
|
+
return err(message);
|
|
379
|
+
}
|
|
380
|
+
const fee = inputValue - outputValue;
|
|
381
|
+
|
|
382
|
+
//Refuse tx if the fee is greater than the amount we're attempting to send.
|
|
383
|
+
if (fee > inputValue) {
|
|
384
|
+
const message = 'Fee is larger than the intended payment.';
|
|
385
|
+
return err(message);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const validateRes = validateTransaction(transactionData);
|
|
389
|
+
if (validateRes.isErr()) return err(validateRes.error.message);
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
const bip32InterfaceRes = await this._wallet.getBip32Interface();
|
|
393
|
+
if (bip32InterfaceRes.isErr()) {
|
|
394
|
+
return err(bip32InterfaceRes.error.message);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
//Create PSBT before signing inputs
|
|
398
|
+
const psbtRes = await this.createPsbtFromTransactionData({
|
|
399
|
+
transactionData,
|
|
400
|
+
bip32Interface: bip32InterfaceRes.value,
|
|
401
|
+
shuffleTargets: shuffleOutputs
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
if (psbtRes.isErr()) {
|
|
405
|
+
return err(psbtRes.error);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const psbt = psbtRes.value;
|
|
409
|
+
|
|
410
|
+
const signedPsbtRes = await this.signPsbt({
|
|
411
|
+
psbt,
|
|
412
|
+
bip32Interface: bip32InterfaceRes.value
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
if (signedPsbtRes.isErr()) {
|
|
416
|
+
return err(signedPsbtRes.error);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const tx = signedPsbtRes.value.extractTransaction();
|
|
420
|
+
const id = tx.getId();
|
|
421
|
+
const hex = tx.toHex();
|
|
422
|
+
return ok({ id, hex });
|
|
423
|
+
} catch (e) {
|
|
424
|
+
return err(e);
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Returns total value of all utxos.
|
|
430
|
+
* @param {IUtxo[]} [inputs]
|
|
431
|
+
*/
|
|
432
|
+
getTransactionInputValue = ({ inputs }: { inputs?: IUtxo[] }): number => {
|
|
433
|
+
try {
|
|
434
|
+
if (!inputs) {
|
|
435
|
+
const transaction = this.data;
|
|
436
|
+
inputs = transaction.inputs;
|
|
437
|
+
}
|
|
438
|
+
if (inputs) {
|
|
439
|
+
const response = reduceValue({ arr: inputs, value: 'value' });
|
|
440
|
+
if (response.isOk()) {
|
|
441
|
+
return response.value;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return 0;
|
|
445
|
+
} catch (e) {
|
|
446
|
+
return 0;
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Loops through inputs and signs them
|
|
452
|
+
* @param {Psbt} psbt
|
|
453
|
+
* @param {BIP32Interface} bip32Interface
|
|
454
|
+
* @returns {Promise<Result<Psbt>>}
|
|
455
|
+
*/
|
|
456
|
+
signPsbt = async ({
|
|
457
|
+
psbt,
|
|
458
|
+
bip32Interface
|
|
459
|
+
}: {
|
|
460
|
+
psbt: Psbt;
|
|
461
|
+
bip32Interface: BIP32Interface;
|
|
462
|
+
}): Promise<Result<Psbt>> => {
|
|
463
|
+
const transactionDataRes = this.data;
|
|
464
|
+
|
|
465
|
+
const { inputs } = transactionDataRes;
|
|
466
|
+
for (const [index, input] of inputs.entries()) {
|
|
467
|
+
try {
|
|
468
|
+
const keyPair = bip32Interface.derivePath(input.path);
|
|
469
|
+
psbt.signInput(index, keyPair);
|
|
470
|
+
} catch (e) {
|
|
471
|
+
return err(e);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
psbt.finalizeAllInputs();
|
|
476
|
+
|
|
477
|
+
return ok(psbt);
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Returns a PSBT that includes unsigned funding inputs.
|
|
482
|
+
* @param {ISendTransaction} transactionData
|
|
483
|
+
* @param {BIP32Interface} bip32Interface
|
|
484
|
+
* @param shuffleTargets
|
|
485
|
+
* @returns {Promise<Result<Psbt>>}
|
|
486
|
+
*/
|
|
487
|
+
createPsbtFromTransactionData = async ({
|
|
488
|
+
transactionData,
|
|
489
|
+
bip32Interface,
|
|
490
|
+
shuffleTargets = true
|
|
491
|
+
}: {
|
|
492
|
+
transactionData: ISendTransaction;
|
|
493
|
+
bip32Interface?: BIP32Interface;
|
|
494
|
+
shuffleTargets?: boolean;
|
|
495
|
+
}): Promise<Result<Psbt>> => {
|
|
496
|
+
const { inputs, outputs, fee, rbf } = transactionData;
|
|
497
|
+
let { changeAddress, message } = transactionData;
|
|
498
|
+
|
|
499
|
+
//Get balance of current inputs.
|
|
500
|
+
const balance = this.getTransactionInputValue({
|
|
501
|
+
inputs
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
//Get value of current outputs.
|
|
505
|
+
const outputValue = this.getTransactionOutputValue({
|
|
506
|
+
outputs
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const network = networks[this._wallet.network];
|
|
510
|
+
|
|
511
|
+
//Collect all outputs.
|
|
512
|
+
let targets: ITargets[] = outputs.concat();
|
|
513
|
+
|
|
514
|
+
//Change address and amount to send back to wallet.
|
|
515
|
+
if (changeAddress) {
|
|
516
|
+
const changeAddressValue = balance - (outputValue + fee);
|
|
517
|
+
// Ensure we're not creating unspendable dust.
|
|
518
|
+
// If we have less than 2x the recommended base fee, just contribute it to the fee in this transaction.
|
|
519
|
+
if (changeAddressValue > TRANSACTION_DEFAULTS.dustLimit) {
|
|
520
|
+
targets.push({
|
|
521
|
+
address: changeAddress,
|
|
522
|
+
value: changeAddressValue,
|
|
523
|
+
index: targets.length
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
// Looks like we don't need a change address.
|
|
527
|
+
// Double check we don't have any spare sats hanging around.
|
|
528
|
+
} else if (outputValue + fee < balance) {
|
|
529
|
+
// If we have spare sats hanging around and the difference is greater than the dust limit, generate a changeAddress to send them to.
|
|
530
|
+
const diffValue = balance - (outputValue + fee);
|
|
531
|
+
if (diffValue > TRANSACTION_DEFAULTS.dustLimit) {
|
|
532
|
+
const changeAddressRes = await this._wallet.getChangeAddress();
|
|
533
|
+
if (changeAddressRes.isErr()) {
|
|
534
|
+
return err(changeAddressRes.error.message);
|
|
535
|
+
}
|
|
536
|
+
changeAddress = changeAddressRes.value.address;
|
|
537
|
+
targets.push({
|
|
538
|
+
address: changeAddress,
|
|
539
|
+
value: diffValue,
|
|
540
|
+
index: targets.length
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
//Embed any OP_RETURN messages.
|
|
546
|
+
if (message.trim() !== '') {
|
|
547
|
+
const messageLength = message.length;
|
|
548
|
+
const lengthMin = 5;
|
|
549
|
+
//This is a patch for the following: https://github.com/coreyphillips/moonshine/issues/52
|
|
550
|
+
if (messageLength > 0 && messageLength < lengthMin) {
|
|
551
|
+
message += ' '.repeat(lengthMin - messageLength);
|
|
552
|
+
}
|
|
553
|
+
const data = Buffer.from(message, 'utf8');
|
|
554
|
+
const embed = bitcoin.payments.embed({
|
|
555
|
+
data: [data],
|
|
556
|
+
network
|
|
557
|
+
});
|
|
558
|
+
targets.push({ script: embed.output!, value: 0, index: targets.length });
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (!bip32Interface) {
|
|
562
|
+
const bip32InterfaceRes = await this._wallet.getBip32Interface();
|
|
563
|
+
if (bip32InterfaceRes.isErr()) {
|
|
564
|
+
return err(bip32InterfaceRes.error.message);
|
|
565
|
+
}
|
|
566
|
+
bip32Interface = bip32InterfaceRes.value;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const root = bip32Interface;
|
|
570
|
+
const psbt = new bitcoin.Psbt({ network });
|
|
571
|
+
|
|
572
|
+
//Add Inputs from inputs array
|
|
573
|
+
try {
|
|
574
|
+
for (const input of inputs) {
|
|
575
|
+
const path = input.path;
|
|
576
|
+
const keyPair: BIP32Interface = root.derivePath(path);
|
|
577
|
+
await this.addInput({
|
|
578
|
+
psbt,
|
|
579
|
+
keyPair,
|
|
580
|
+
input
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
} catch (e) {
|
|
584
|
+
return err(e);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
//Set RBF if supported and prompted via rbf in Settings.
|
|
588
|
+
setReplaceByFee({ psbt, setRbf: !!rbf });
|
|
589
|
+
|
|
590
|
+
// Shuffle targets if not run from unit test and add outputs.
|
|
591
|
+
if (shuffleTargets) {
|
|
592
|
+
targets = shuffleArray(targets);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
targets.forEach((target) => {
|
|
596
|
+
//Check if OP_RETURN
|
|
597
|
+
let isOpReturn = false;
|
|
598
|
+
try {
|
|
599
|
+
isOpReturn = !!target.script;
|
|
600
|
+
} catch (e) {}
|
|
601
|
+
if (isOpReturn) {
|
|
602
|
+
if (target.script) {
|
|
603
|
+
psbt.addOutput({
|
|
604
|
+
script: target.script,
|
|
605
|
+
value: target.value ?? 0
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
} else {
|
|
609
|
+
if (target.address && target.value) {
|
|
610
|
+
psbt.addOutput({
|
|
611
|
+
address: target.address,
|
|
612
|
+
value: target.value
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
return ok(psbt);
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
addInput = async ({
|
|
622
|
+
psbt,
|
|
623
|
+
keyPair,
|
|
624
|
+
input
|
|
625
|
+
}: IAddInput): Promise<Result<string>> => {
|
|
626
|
+
try {
|
|
627
|
+
const network = networks[this._wallet.network];
|
|
628
|
+
const { type } = getAddressInfo(input.address);
|
|
629
|
+
|
|
630
|
+
if (!input.value) {
|
|
631
|
+
return err('No input provided.');
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (type === 'p2wpkh') {
|
|
635
|
+
const p2wpkh = bitcoin.payments.p2wpkh({
|
|
636
|
+
pubkey: keyPair.publicKey,
|
|
637
|
+
network
|
|
638
|
+
});
|
|
639
|
+
if (!p2wpkh?.output) {
|
|
640
|
+
return err('p2wpkh.output is undefined.');
|
|
641
|
+
}
|
|
642
|
+
psbt.addInput({
|
|
643
|
+
hash: input.tx_hash,
|
|
644
|
+
index: input.tx_pos,
|
|
645
|
+
witnessUtxo: {
|
|
646
|
+
script: p2wpkh.output,
|
|
647
|
+
value: input.value
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (type === 'p2sh') {
|
|
653
|
+
const p2wpkh = bitcoin.payments.p2wpkh({
|
|
654
|
+
pubkey: keyPair.publicKey,
|
|
655
|
+
network
|
|
656
|
+
});
|
|
657
|
+
const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network });
|
|
658
|
+
if (!p2sh?.output) {
|
|
659
|
+
return err('p2sh.output is undefined.');
|
|
660
|
+
}
|
|
661
|
+
if (!p2sh?.redeem) {
|
|
662
|
+
return err('p2sh.redeem.output is undefined.');
|
|
663
|
+
}
|
|
664
|
+
psbt.addInput({
|
|
665
|
+
hash: input.tx_hash,
|
|
666
|
+
index: input.tx_pos,
|
|
667
|
+
witnessUtxo: {
|
|
668
|
+
script: p2sh.output,
|
|
669
|
+
value: input.value
|
|
670
|
+
},
|
|
671
|
+
redeemScript: p2sh.redeem.output
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (type === 'p2pkh') {
|
|
676
|
+
const transaction = await this._wallet.electrum.getTransactions({
|
|
677
|
+
txHashes: [{ tx_hash: input.tx_hash }]
|
|
678
|
+
});
|
|
679
|
+
if (transaction.isErr()) {
|
|
680
|
+
return err(transaction.error.message);
|
|
681
|
+
}
|
|
682
|
+
const hex = transaction.value.data[0].result.hex;
|
|
683
|
+
const nonWitnessUtxo = Buffer.from(hex, 'hex');
|
|
684
|
+
psbt.addInput({
|
|
685
|
+
hash: input.tx_hash,
|
|
686
|
+
index: input.tx_pos,
|
|
687
|
+
nonWitnessUtxo
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
return ok('Success');
|
|
691
|
+
} catch {
|
|
692
|
+
return err('Unable to add input.');
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Adds an output at the specified index to the current transaction.
|
|
698
|
+
* @param {string} address
|
|
699
|
+
* @param {number} value
|
|
700
|
+
* @param {number} [index]
|
|
701
|
+
* @returns {IOutput}
|
|
702
|
+
*/
|
|
703
|
+
addOutput = async ({
|
|
704
|
+
address,
|
|
705
|
+
value,
|
|
706
|
+
index = 0
|
|
707
|
+
}: IOutput): Promise<Result<string>> => {
|
|
708
|
+
if (!this.data.inputs?.length) {
|
|
709
|
+
const setupRes = await this.setupTransaction();
|
|
710
|
+
if (setupRes.isErr()) return err(setupRes.error.message);
|
|
711
|
+
}
|
|
712
|
+
return this.updateSendTransaction({
|
|
713
|
+
transaction: {
|
|
714
|
+
outputs: [{ address, value, index }]
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Returns total value of all outputs. Excludes any value that would be sent to the change address.
|
|
721
|
+
* @param {IOutput[]} [outputs]
|
|
722
|
+
* @returns {number}
|
|
723
|
+
*/
|
|
724
|
+
getTransactionOutputValue = ({
|
|
725
|
+
outputs
|
|
726
|
+
}: {
|
|
727
|
+
outputs?: IOutput[];
|
|
728
|
+
} = {}): number => {
|
|
729
|
+
try {
|
|
730
|
+
if (!outputs) {
|
|
731
|
+
const transaction = this.data;
|
|
732
|
+
outputs = transaction.outputs;
|
|
733
|
+
}
|
|
734
|
+
const response = reduceValue({ arr: outputs, value: 'value' });
|
|
735
|
+
if (response.isOk()) {
|
|
736
|
+
return response.value;
|
|
737
|
+
}
|
|
738
|
+
return 0;
|
|
739
|
+
} catch {
|
|
740
|
+
return 0;
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* This updates the transaction state used for sending.
|
|
746
|
+
* @param {Partial<ISendTransaction>} transaction
|
|
747
|
+
* @return {Promise<Result<string>>}
|
|
748
|
+
*/
|
|
749
|
+
updateSendTransaction = ({
|
|
750
|
+
transaction
|
|
751
|
+
}: {
|
|
752
|
+
transaction: Partial<ISendTransaction>;
|
|
753
|
+
}): Result<string> => {
|
|
754
|
+
try {
|
|
755
|
+
//Add output if specified
|
|
756
|
+
if (transaction.outputs) {
|
|
757
|
+
const currentTransaction = this._wallet.transaction.data;
|
|
758
|
+
const outputs = currentTransaction.outputs.concat();
|
|
759
|
+
transaction.outputs.forEach((output) => {
|
|
760
|
+
outputs[output.index] = output;
|
|
761
|
+
});
|
|
762
|
+
transaction.outputs = outputs;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
this._data = {
|
|
766
|
+
...this._data,
|
|
767
|
+
...transaction
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
this._wallet.saveWalletData('transaction', this.data).then();
|
|
771
|
+
|
|
772
|
+
return ok('Transaction updated');
|
|
773
|
+
} catch (e) {
|
|
774
|
+
return err(e);
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Updates the fee for the current transaction by the specified amount.
|
|
780
|
+
* @param {number} [satsPerByte]
|
|
781
|
+
* @param {EFeeId} [selectedFeeId]
|
|
782
|
+
* @param {number} [index]
|
|
783
|
+
* @param {ISendTransaction} [transaction]
|
|
784
|
+
* @returns {Result<{ fee: number }>}
|
|
785
|
+
*/
|
|
786
|
+
public updateFee({
|
|
787
|
+
satsPerByte,
|
|
788
|
+
selectedFeeId = EFeeId.custom,
|
|
789
|
+
index = 0,
|
|
790
|
+
transaction = this.data
|
|
791
|
+
}: {
|
|
792
|
+
satsPerByte: number;
|
|
793
|
+
selectedFeeId?: EFeeId;
|
|
794
|
+
index?: number;
|
|
795
|
+
transaction?: ISendTransaction;
|
|
796
|
+
}): Result<{ fee: number }> {
|
|
797
|
+
const inputTotal = this.getTransactionInputValue({
|
|
798
|
+
inputs: transaction.inputs
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
const { max, message, outputs } = transaction;
|
|
802
|
+
let address = '';
|
|
803
|
+
if (outputs.length > index) {
|
|
804
|
+
address = outputs[index]?.address ?? '';
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const newFee = this.getTotalFee({ satsPerByte, message });
|
|
808
|
+
|
|
809
|
+
//Return if the new fee exceeds half of the user's balance
|
|
810
|
+
if (newFee >= inputTotal / 2) {
|
|
811
|
+
return err(
|
|
812
|
+
'Unable to increase the fee any further. Otherwise, it will exceed half the current balance.'
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const totalTransactionValue = this.getTransactionOutputValue({
|
|
817
|
+
outputs
|
|
818
|
+
});
|
|
819
|
+
const newTotalAmount = totalTransactionValue + newFee;
|
|
820
|
+
const _transaction: Partial<ISendTransaction> = {
|
|
821
|
+
satsPerByte,
|
|
822
|
+
fee: newFee,
|
|
823
|
+
selectedFeeId
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
if (max) {
|
|
827
|
+
// Update the tx value with the new fee to continue sending the max amount.
|
|
828
|
+
_transaction.outputs = [{ address, value: inputTotal - newFee, index }];
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Check that the user has enough funds
|
|
832
|
+
if (max || newTotalAmount <= inputTotal) {
|
|
833
|
+
this.updateSendTransaction({
|
|
834
|
+
transaction: _transaction
|
|
835
|
+
});
|
|
836
|
+
return ok({ fee: newFee });
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return err(
|
|
840
|
+
'New total amount exceeds the available balance. Unable to update the transaction fee.'
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Toggles the max amount to the provided output index.
|
|
846
|
+
* @param {string} [address] If left undefined, the current receiving address will be provided.
|
|
847
|
+
* @param {ISendTransaction} [transaction]
|
|
848
|
+
* @param {number} [index]
|
|
849
|
+
* @param satsPerByte
|
|
850
|
+
* @param rbf
|
|
851
|
+
*/
|
|
852
|
+
sendMax = async ({
|
|
853
|
+
address,
|
|
854
|
+
transaction,
|
|
855
|
+
index = 0,
|
|
856
|
+
satsPerByte,
|
|
857
|
+
rbf = false
|
|
858
|
+
}: {
|
|
859
|
+
address?: string;
|
|
860
|
+
transaction?: ISendTransaction;
|
|
861
|
+
index?: number;
|
|
862
|
+
satsPerByte?: number;
|
|
863
|
+
rbf?: boolean;
|
|
864
|
+
} = {}): Promise<Result<string>> => {
|
|
865
|
+
try {
|
|
866
|
+
if (!transaction) {
|
|
867
|
+
transaction = this.data;
|
|
868
|
+
}
|
|
869
|
+
if (!transaction.inputs?.length) {
|
|
870
|
+
const setupRes = await this.setupTransaction({ rbf });
|
|
871
|
+
if (setupRes.isErr()) return err(setupRes.error.message);
|
|
872
|
+
}
|
|
873
|
+
const outputs = transaction.outputs ?? [];
|
|
874
|
+
// No address specified, attempt to assign the address currently specified in the current output index.
|
|
875
|
+
if (!address) {
|
|
876
|
+
address = outputs[index]?.address ?? '';
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const maxAmountResponse = this.getMaxSendAmount({
|
|
880
|
+
satsPerByte: satsPerByte ?? 1,
|
|
881
|
+
selectedFeeId: transaction.selectedFeeId,
|
|
882
|
+
transaction
|
|
883
|
+
});
|
|
884
|
+
if (maxAmountResponse.isErr()) {
|
|
885
|
+
return err(maxAmountResponse.error);
|
|
886
|
+
}
|
|
887
|
+
const { amount, fee } = maxAmountResponse.value;
|
|
888
|
+
|
|
889
|
+
if (!transaction.max) {
|
|
890
|
+
this.updateSendTransaction({
|
|
891
|
+
transaction: {
|
|
892
|
+
max: true,
|
|
893
|
+
outputs: [{ address, value: amount, index }],
|
|
894
|
+
fee
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
} else {
|
|
898
|
+
this.updateSendTransaction({
|
|
899
|
+
transaction: {
|
|
900
|
+
max: false
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
return ok('Successfully setup max send transaction.');
|
|
906
|
+
} catch (e) {
|
|
907
|
+
return err(e);
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Calculates the max amount able to send for onchain/lightning
|
|
913
|
+
* @param {ISendTransaction} [transaction]
|
|
914
|
+
* @param {number} [index]
|
|
915
|
+
*/
|
|
916
|
+
//TODO: Double check the transactionSpeed and customFeeRate are being used correctly.
|
|
917
|
+
estimateTransactionCosts = ({
|
|
918
|
+
transaction,
|
|
919
|
+
customFeeRate
|
|
920
|
+
}: {
|
|
921
|
+
transaction?: ISendTransaction;
|
|
922
|
+
customFeeRate?: number;
|
|
923
|
+
}): Result<{ amount: number; fee: number; satsPerByte: number }> => {
|
|
924
|
+
try {
|
|
925
|
+
if (!transaction) {
|
|
926
|
+
transaction = this.data;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const currentWallet = this._wallet.data;
|
|
930
|
+
const onchainBalance = currentWallet.balance;
|
|
931
|
+
|
|
932
|
+
const inputValue = this.getTransactionInputValue({
|
|
933
|
+
inputs: transaction.inputs
|
|
934
|
+
});
|
|
935
|
+
const amount = onchainBalance > inputValue ? onchainBalance : inputValue;
|
|
936
|
+
|
|
937
|
+
let utxos: IUtxo[] = [];
|
|
938
|
+
//Ensure we add the larger utxo set for a more accurate fee.
|
|
939
|
+
if (transaction.inputs.length > currentWallet?.utxos.length) {
|
|
940
|
+
utxos = transaction.inputs;
|
|
941
|
+
} else {
|
|
942
|
+
utxos = currentWallet?.utxos ?? [];
|
|
943
|
+
}
|
|
944
|
+
const fees = this._wallet.feeEstimates;
|
|
945
|
+
const selectedFeeId = this._wallet.selectedFeeId;
|
|
946
|
+
const satsPerByte = customFeeRate ?? fees[selectedFeeId] ?? 1;
|
|
947
|
+
const fee = this.getTotalFee({
|
|
948
|
+
satsPerByte,
|
|
949
|
+
message: transaction.message,
|
|
950
|
+
transaction: {
|
|
951
|
+
...transaction,
|
|
952
|
+
max: true,
|
|
953
|
+
inputs: utxos,
|
|
954
|
+
selectedFeeId,
|
|
955
|
+
satsPerByte
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
const maxAmount = {
|
|
960
|
+
amount: amount - fee,
|
|
961
|
+
fee,
|
|
962
|
+
satsPerByte
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
if (amount <= fee) {
|
|
966
|
+
return err('Balance is too low to spend.');
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
return ok(maxAmount);
|
|
970
|
+
} catch (e) {
|
|
971
|
+
return err(e);
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Calculates the max amount able to send for the provided/current onchain transaction.
|
|
977
|
+
* @param {number} satsPerByte
|
|
978
|
+
* @param {EFeeId} [selectedFeeId]
|
|
979
|
+
* @param {ISendTransaction} [transaction]
|
|
980
|
+
* @returns {Result<{ amount: number; fee?: number }>}
|
|
981
|
+
*/
|
|
982
|
+
getMaxSendAmount({
|
|
983
|
+
satsPerByte,
|
|
984
|
+
selectedFeeId = EFeeId.none,
|
|
985
|
+
transaction
|
|
986
|
+
}: {
|
|
987
|
+
satsPerByte: number;
|
|
988
|
+
selectedFeeId?: EFeeId;
|
|
989
|
+
transaction?: ISendTransaction;
|
|
990
|
+
}): Result<{ amount: number; fee?: number }> {
|
|
991
|
+
try {
|
|
992
|
+
if (!transaction) {
|
|
993
|
+
transaction = this.data;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const currentWallet = this._wallet.data;
|
|
997
|
+
const onchainBalance = currentWallet.balance;
|
|
998
|
+
|
|
999
|
+
const inputValue = this.getTransactionInputValue({
|
|
1000
|
+
inputs: transaction.inputs
|
|
1001
|
+
});
|
|
1002
|
+
const amount = onchainBalance > inputValue ? onchainBalance : inputValue;
|
|
1003
|
+
|
|
1004
|
+
let utxos: IUtxo[] = [];
|
|
1005
|
+
//Ensure we add the larger utxo set for a more accurate fee.
|
|
1006
|
+
if (transaction.inputs.length > currentWallet?.utxos.length) {
|
|
1007
|
+
utxos = transaction.inputs;
|
|
1008
|
+
} else {
|
|
1009
|
+
utxos = currentWallet?.utxos ?? [];
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const fee = this.getTotalFee({
|
|
1013
|
+
satsPerByte,
|
|
1014
|
+
message: transaction.message,
|
|
1015
|
+
transaction: {
|
|
1016
|
+
...transaction,
|
|
1017
|
+
max: true,
|
|
1018
|
+
inputs: utxos,
|
|
1019
|
+
selectedFeeId,
|
|
1020
|
+
satsPerByte
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
const maxAmount = {
|
|
1025
|
+
amount: amount - fee,
|
|
1026
|
+
fee
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
if (amount <= fee) {
|
|
1030
|
+
return err('Balance is too low to spend.');
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
return ok(maxAmount);
|
|
1034
|
+
} catch (e) {
|
|
1035
|
+
return err(e);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Sets up a CPFP transaction.
|
|
1041
|
+
* @param {string} [txid]
|
|
1042
|
+
* @param {number} [satsPerByte]
|
|
1043
|
+
*/
|
|
1044
|
+
async setupCpfp({
|
|
1045
|
+
txid,
|
|
1046
|
+
satsPerByte
|
|
1047
|
+
}: {
|
|
1048
|
+
txid?: string; // txid of utxo to include in the CPFP tx. Undefined will gather all utxo's.
|
|
1049
|
+
satsPerByte?: number;
|
|
1050
|
+
}): Promise<Result<ISendTransaction>> {
|
|
1051
|
+
const response = await this.setupTransaction({
|
|
1052
|
+
inputTxHashes: txid ? [txid] : undefined,
|
|
1053
|
+
rbf: true
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
const receiveAddress = await this._wallet.getReceiveAddress({});
|
|
1057
|
+
if (receiveAddress.isErr()) {
|
|
1058
|
+
return err(receiveAddress.error.message);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
this._data = {
|
|
1062
|
+
...this._data,
|
|
1063
|
+
...response,
|
|
1064
|
+
satsPerByte: satsPerByte ?? this.data?.satsPerByte ?? 1,
|
|
1065
|
+
boostType: EBoostType.cpfp
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
// Construct the tx to send funds back to ourselves using the assigned inputs, receive address and fee.
|
|
1069
|
+
const sendMaxResponse = await this.sendMax({
|
|
1070
|
+
address: receiveAddress.value
|
|
1071
|
+
});
|
|
1072
|
+
if (sendMaxResponse.isErr()) {
|
|
1073
|
+
return err(sendMaxResponse.error.message);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
return ok(this.data);
|
|
1077
|
+
}
|
|
1078
|
+
}
|