@exodus/bitcoin-api 4.8.2 → 4.9.0
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/CHANGELOG.md +20 -0
- package/package.json +2 -2
- package/src/index.js +1 -0
- package/src/psbt-parser.js +1 -1
- package/src/tx-create/create-tx.js +8 -5
- package/src/tx-log/bitcoin-monitor-scanner.js +20 -1
- package/src/tx-send/index.js +29 -95
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [4.9.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.8.3...@exodus/bitcoin-api@4.9.0) (2025-12-23)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
* feat: Expose utxo sendTx workflow to accept provided unsigned tx (#6890)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## [4.8.3](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.8.2...@exodus/bitcoin-api@4.8.3) (2025-12-16)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
* fix: keep pending txs until /tx check fails for utxo coins (#7009)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
6
26
|
## [4.8.2](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.8.1...@exodus/bitcoin-api@4.8.2) (2025-12-04)
|
|
7
27
|
|
|
8
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "Bitcoin transaction and fee monitors, RPC with the blockchain node, other networking code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"type": "git",
|
|
61
61
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "39b743562bc1171bf3d145b47382db9023c660dd"
|
|
64
64
|
}
|
package/src/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export * from './unconfirmed-ancestor-data.js'
|
|
|
16
16
|
export * from './parse-unsigned-tx.js'
|
|
17
17
|
export { getCreateBatchTransaction } from './tx-send/batch-tx.js'
|
|
18
18
|
export { createPsbtToUnsignedTx } from './psbt-utils.js'
|
|
19
|
+
export { createTxFactory } from './tx-create/create-tx.js'
|
|
19
20
|
export * from './insight-api-client/util.js'
|
|
20
21
|
export * from './move-funds.js'
|
|
21
22
|
export { createEncodeMultisigContract } from './multisig-address.js'
|
package/src/psbt-parser.js
CHANGED
|
@@ -318,7 +318,7 @@ function buildAddressPathsMap(selectedUtxos, changeOutputData) {
|
|
|
318
318
|
function buildOutputAddressPurposesMap(outputs) {
|
|
319
319
|
const map = Object.create(null)
|
|
320
320
|
for (const output of outputs) {
|
|
321
|
-
if (output.address?.meta?.purpose) {
|
|
321
|
+
if (output.address?.meta?.purpose && output.address?.meta?.path) {
|
|
322
322
|
map[output.address.address] = output.address.meta.purpose
|
|
323
323
|
}
|
|
324
324
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Psbt as DefaultPsbt, Transaction as DefaultTransaction } from '@exodus/bitcoinjs'
|
|
1
2
|
import { Address, UtxoCollection } from '@exodus/models'
|
|
2
3
|
import lodash from 'lodash'
|
|
3
4
|
import assert from 'minimalistic-assert'
|
|
@@ -471,7 +472,8 @@ const transferHandler = {
|
|
|
471
472
|
changeAddress: context.changeAddress,
|
|
472
473
|
})
|
|
473
474
|
|
|
474
|
-
//
|
|
475
|
+
// Track our own outputs and their purposes. Today this is only the change
|
|
476
|
+
// output; in the future we may add self-send primaries when applicable.
|
|
475
477
|
const outputAddressPurposesMap = Object.create(null)
|
|
476
478
|
|
|
477
479
|
// Add the keypath of change address to support Trezor detect the change output.
|
|
@@ -517,13 +519,14 @@ export const createTxFactory =
|
|
|
517
519
|
assetClientInterface,
|
|
518
520
|
changeAddressType,
|
|
519
521
|
allowedPurposes,
|
|
520
|
-
Psbt,
|
|
521
|
-
Transaction,
|
|
522
|
+
Psbt = DefaultPsbt,
|
|
523
|
+
Transaction = DefaultTransaction,
|
|
522
524
|
}) =>
|
|
523
525
|
async ({
|
|
524
526
|
asset,
|
|
525
527
|
walletAccount,
|
|
526
528
|
type,
|
|
529
|
+
address, // same as toAddress.
|
|
527
530
|
toAddress,
|
|
528
531
|
amount,
|
|
529
532
|
multipleAddressesEnabled,
|
|
@@ -532,7 +535,7 @@ export const createTxFactory =
|
|
|
532
535
|
isSendAll,
|
|
533
536
|
bumpTxId,
|
|
534
537
|
isExchange,
|
|
535
|
-
isRbfAllowed,
|
|
538
|
+
isRbfAllowed = true,
|
|
536
539
|
taprootInputWitnessSize,
|
|
537
540
|
}) => {
|
|
538
541
|
const assetName = asset.name
|
|
@@ -547,7 +550,7 @@ export const createTxFactory =
|
|
|
547
550
|
return txHandler.buildTransaction({
|
|
548
551
|
asset,
|
|
549
552
|
walletAccount,
|
|
550
|
-
toAddress,
|
|
553
|
+
toAddress: toAddress ?? address,
|
|
551
554
|
amount,
|
|
552
555
|
multipleAddressesEnabled,
|
|
553
556
|
feePerKB,
|
|
@@ -669,7 +669,26 @@ export class BitcoinMonitorScanner {
|
|
|
669
669
|
// Keep new utxos when they intersect with the stored utxos.
|
|
670
670
|
utxoCol = utxoCol.union(currentStoredUtxos).difference(utxosToRemoveCol)
|
|
671
671
|
|
|
672
|
-
|
|
672
|
+
const pendingDropCandidates = Object.values(unconfirmedTxsToCheck)
|
|
673
|
+
const verificationResults = await Promise.all(
|
|
674
|
+
pendingDropCandidates.map(async (tx) => {
|
|
675
|
+
try {
|
|
676
|
+
const txStatus = await insightClient.fetchTx(tx.txId)
|
|
677
|
+
return { tx, shouldDrop: !txStatus }
|
|
678
|
+
} catch (err) {
|
|
679
|
+
console.log(
|
|
680
|
+
`${assetName}: failed to verify pending tx ${tx.txId} before drop check: ${
|
|
681
|
+
err?.message || 'unknown'
|
|
682
|
+
}`
|
|
683
|
+
)
|
|
684
|
+
return { tx, shouldDrop: false }
|
|
685
|
+
}
|
|
686
|
+
})
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
for (const { tx, shouldDrop } of verificationResults) {
|
|
690
|
+
if (!shouldDrop) continue
|
|
691
|
+
|
|
673
692
|
existingTxs.push({ ...tx, dropped: true }) // TODO: this will decrease the chain index, it shouldn't be an issue considering the gap limit
|
|
674
693
|
utxoCol = utxoCol.difference(utxoCol.getTxIdUtxos(tx.txId))
|
|
675
694
|
const utxosToAdd = []
|
package/src/tx-send/index.js
CHANGED
|
@@ -3,7 +3,6 @@ import { Psbt as DefaultPsbt, Transaction as DefaultTransaction } from '@exodus/
|
|
|
3
3
|
import assert from 'minimalistic-assert'
|
|
4
4
|
|
|
5
5
|
import { extractTransactionContext } from '../psbt-parser.js'
|
|
6
|
-
import { createTxFactory } from '../tx-create/create-tx.js'
|
|
7
6
|
import { broadcastTransaction } from './broadcast-tx.js'
|
|
8
7
|
import { updateAccountState, updateTransactionLog } from './update-state.js'
|
|
9
8
|
|
|
@@ -99,46 +98,6 @@ export async function signTransaction({
|
|
|
99
98
|
return { rawTx, txId, tx }
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
function getTransferUnsignedTx(txContext) {
|
|
103
|
-
const {
|
|
104
|
-
inputs,
|
|
105
|
-
outputs,
|
|
106
|
-
psbtBuffer,
|
|
107
|
-
useCashAddress,
|
|
108
|
-
addressPathsMap,
|
|
109
|
-
outputAddressPurposesMap,
|
|
110
|
-
blockHeight,
|
|
111
|
-
rawTxs,
|
|
112
|
-
txType,
|
|
113
|
-
rbfEnabled,
|
|
114
|
-
bumpTxId,
|
|
115
|
-
changeOutputIndex,
|
|
116
|
-
sendOutputIndexes,
|
|
117
|
-
} = txContext
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
unsignedTx: {
|
|
121
|
-
txData: {
|
|
122
|
-
inputs,
|
|
123
|
-
outputs,
|
|
124
|
-
psbtBuffer,
|
|
125
|
-
},
|
|
126
|
-
txMeta: {
|
|
127
|
-
useCashAddress,
|
|
128
|
-
addressPathsMap,
|
|
129
|
-
outputAddressPurposesMap,
|
|
130
|
-
blockHeight,
|
|
131
|
-
rawTxs,
|
|
132
|
-
txType,
|
|
133
|
-
rbfEnabled,
|
|
134
|
-
bumpTxId,
|
|
135
|
-
changeOutputIndex,
|
|
136
|
-
sendOutputIndexes,
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
101
|
// Get additional transaction metadata that are not part of the unsignedTx.txMeta
|
|
143
102
|
function getExtendedTxMeta(txContext) {
|
|
144
103
|
const {
|
|
@@ -170,64 +129,40 @@ function getExtendedTxMeta(txContext) {
|
|
|
170
129
|
|
|
171
130
|
// not ported from Exodus; but this demos signing / broadcasting
|
|
172
131
|
// NOTE: this will be ripped out in the coming weeks
|
|
173
|
-
export const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}) =>
|
|
185
|
-
|
|
186
|
-
asset,
|
|
187
|
-
walletAccount,
|
|
188
|
-
address,
|
|
189
|
-
amount,
|
|
190
|
-
multipleAddressesEnabled,
|
|
191
|
-
feePerKB,
|
|
192
|
-
customFee,
|
|
193
|
-
isSendAll,
|
|
194
|
-
bumpTxId: bumpTxIdProvided,
|
|
195
|
-
isExchange,
|
|
196
|
-
isRbfAllowed = true,
|
|
197
|
-
taprootInputWitnessSize,
|
|
198
|
-
}) => {
|
|
132
|
+
export const sendTxFactory = ({
|
|
133
|
+
createTx,
|
|
134
|
+
getSizeAndChangeScript = getSizeAndChangeScriptFactory(), // for decred customizations
|
|
135
|
+
assetClientInterface,
|
|
136
|
+
allowedPurposes,
|
|
137
|
+
Psbt = DefaultPsbt,
|
|
138
|
+
Transaction = DefaultTransaction,
|
|
139
|
+
}) => {
|
|
140
|
+
assert(assetClientInterface, 'assetClientInterface is required')
|
|
141
|
+
assert(createTx, 'createTx is required')
|
|
142
|
+
|
|
143
|
+
return async ({ asset, walletAccount, unsignedTx: providedUnsignedTx, ...legacyParams }) => {
|
|
144
|
+
const { address, ...createTxParams } = legacyParams
|
|
199
145
|
const assetName = asset.name
|
|
200
146
|
const accountState = await assetClientInterface.getAccountState({ assetName, walletAccount })
|
|
201
147
|
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
assetClientInterface,
|
|
207
|
-
changeAddressType,
|
|
208
|
-
allowedPurposes,
|
|
209
|
-
Psbt,
|
|
210
|
-
Transaction,
|
|
211
|
-
})
|
|
148
|
+
const resolveUnsignedTx = async () => {
|
|
149
|
+
if (providedUnsignedTx) {
|
|
150
|
+
return { unsignedTx: providedUnsignedTx }
|
|
151
|
+
}
|
|
212
152
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
isSendAll,
|
|
224
|
-
bumpTxId: bumpTxIdProvided,
|
|
225
|
-
isExchange,
|
|
226
|
-
taprootInputWitnessSize,
|
|
227
|
-
})
|
|
153
|
+
return createTx({
|
|
154
|
+
asset,
|
|
155
|
+
walletAccount,
|
|
156
|
+
type: 'transfer',
|
|
157
|
+
toAddress: address,
|
|
158
|
+
...createTxParams,
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const { unsignedTx } = await resolveUnsignedTx()
|
|
228
163
|
|
|
229
164
|
const txContext = await extractTransactionContext({
|
|
230
|
-
unsignedTx
|
|
165
|
+
unsignedTx,
|
|
231
166
|
asset,
|
|
232
167
|
assetClientInterface,
|
|
233
168
|
walletAccount,
|
|
@@ -236,8 +171,6 @@ export const createAndBroadcastTXFactory =
|
|
|
236
171
|
Transaction,
|
|
237
172
|
})
|
|
238
173
|
|
|
239
|
-
const { unsignedTx } = getTransferUnsignedTx(txContext)
|
|
240
|
-
|
|
241
174
|
const {
|
|
242
175
|
fee,
|
|
243
176
|
sendAmount,
|
|
@@ -330,6 +263,7 @@ export const createAndBroadcastTXFactory =
|
|
|
330
263
|
replacedTxId: replaceTx?.txId,
|
|
331
264
|
}
|
|
332
265
|
}
|
|
266
|
+
}
|
|
333
267
|
|
|
334
268
|
// back compatibiliy
|
|
335
269
|
export { getSendDustValue as getDustValue } from '../dust.js'
|